88import scipy .stats as ss
99from scipy import integrate , special
1010from statsmodels .stats .multitest import multipletests
11- from pandas import DataFrame , Series , MultiIndex
11+ from pandas import DataFrame , Series , MultiIndex , concat
12+
13+
14+ def __numeric_array (values : Series ) -> np .ndarray :
15+ """Expose nullable numeric data without object arrays or integer rounding."""
16+ dtype = getattr (values .dtype , "numpy_dtype" , None )
17+ if dtype is None :
18+ return values .to_numpy ()
19+ if dtype .kind in "biu" and values .hasnans :
20+ dtype = float
21+ return values .to_numpy (dtype = dtype , na_value = np .nan )
1222
1323
1424def __validate_n_permutations (value : int , name : str ) -> int :
@@ -153,6 +163,14 @@ def __convert_to_block_df(
153163 x = x .reset_index ().melt (
154164 id_vars = new_block_id_col , var_name = new_group_col , value_name = new_y_col
155165 )
166+ # pandas 1.5 melt loses nullable measurement dtypes. Concatenating the
167+ # original columns preserves their common dtype and missing values.
168+ if x [new_y_col ].dtype == object and any (
169+ getattr (dtype , "numpy_dtype" , None ) is not None for dtype in a .dtypes
170+ ):
171+ x [new_y_col ] = concat (
172+ [a .iloc [:, column ] for column in range (a .shape [1 ])], ignore_index = True
173+ )
156174 x [new_block_col ] = a .index .take (x [new_block_id_col ]).to_numpy ()
157175 x [new_group_col ] = a .columns .take (x [new_group_col ]).to_numpy ()
158176
@@ -1015,7 +1033,7 @@ def posthoc_npm_test(
10151033 k = groups .size
10161034 n = x .shape [0 ]
10171035 sigma = np .sqrt (n * (n + 1 ) / 12.0 )
1018- values = x [_val_col ]. to_numpy ( )
1036+ values = __numeric_array ( x [_val_col ])
10191037 group_values = x [_group_col ].to_numpy ()
10201038 group_codes = np .empty (n , dtype = int )
10211039 for code , group in enumerate (groups ):
@@ -1558,7 +1576,7 @@ def posthoc_anderson(
15581576 groups = x [_group_col ].unique ()
15591577 k = groups .size
15601578 x_grouped = x .groupby (_group_col , observed = True )[_val_col ]
1561- samples = [x_grouped .get_group (group ).dropna (). to_numpy ( ) for group in groups ]
1579+ samples = [__numeric_array ( x_grouped .get_group (group ).dropna ()) for group in groups ]
15621580 vs = np .zeros ((k , k ), dtype = float )
15631581 combs = it .combinations (range (k ), 2 )
15641582
@@ -2061,7 +2079,7 @@ def posthoc_ttest(
20612079 0.047356
20622080 """
20632081 x , _val_col , _group_col = __convert_to_df (a , val_col , group_col )
2064- if not np .isfinite (x [_val_col ].to_numpy (dtype = float )).all ():
2082+ if not np .isfinite (x [_val_col ].to_numpy (dtype = float , na_value = np . nan )).all ():
20652083 raise ValueError (
20662084 "All observations must be finite; remove or impute missing values before calling "
20672085 "posthoc_ttest"
@@ -2071,7 +2089,7 @@ def posthoc_ttest(
20712089 groups = x [_group_col ].unique ()
20722090 k = groups .size
20732091 xg = x .groupby (by = _group_col , observed = True )[_val_col ]
2074- samples = [xg .get_group (group ). to_numpy ( ) for group in groups ]
2092+ samples = [__numeric_array ( xg .get_group (group )) for group in groups ]
20752093
20762094 vs = np .zeros ((k , k ), dtype = float )
20772095 tri_upper = np .triu_indices (vs .shape [0 ], 1 )
@@ -2158,7 +2176,7 @@ def posthoc_tukey_hsd(
21582176 # Older pandas iterates ordered categoricals in category order even with
21592177 # sort=False. Extract samples in the same order as the output labels.
21602178 xg = x .groupby (_group_col , observed = True , sort = False )[_val_col ]
2161- results = ss .tukey_hsd (* [xg .get_group (group ). to_numpy ( ) for group in groups ])
2179+ results = ss .tukey_hsd (* [__numeric_array ( xg .get_group (group )) for group in groups ])
21622180
21632181 return DataFrame (results .pvalue , index = groups , columns = groups )
21642182
@@ -2240,7 +2258,7 @@ def posthoc_mannwhitney(
22402258 x_len = groups .size
22412259 vs = np .zeros ((x_len , x_len ))
22422260 xg = x .groupby (_group_col , observed = True )[_val_col ]
2243- samples = [xg .get_group (group ). to_numpy ( ) for group in groups ]
2261+ samples = [__numeric_array ( xg .get_group (group )) for group in groups ]
22442262 tri_upper = np .triu_indices (vs .shape [0 ], 1 )
22452263 tri_lower = np .tril_indices (vs .shape [0 ], - 1 )
22462264 vs [:, :] = 0
@@ -2363,7 +2381,7 @@ def posthoc_wilcoxon(
23632381 x_len = groups .size
23642382 vs = np .zeros ((x_len , x_len ))
23652383 xg = x .groupby (_group_col , observed = True )[_val_col ]
2366- samples = [xg .get_group (group ). to_numpy ( ) for group in groups ]
2384+ samples = [__numeric_array ( xg .get_group (group )) for group in groups ]
23672385 tri_upper = np .triu_indices (vs .shape [0 ], 1 )
23682386 tri_lower = np .tril_indices (vs .shape [0 ], - 1 )
23692387 vs [:, :] = 0
@@ -2461,8 +2479,8 @@ def posthoc_scheffe(
24612479 tri_lower = np .tril_indices (vs .shape [0 ], - 1 )
24622480 vs [:, :] = 0
24632481
2464- counts = ni .reindex (groups ). to_numpy ( )
2465- means = xi .reindex (groups ). to_numpy ( )
2482+ counts = __numeric_array ( ni .reindex (groups ))
2483+ means = __numeric_array ( xi .reindex (groups ))
24662484 dif = means [tri_upper [0 ]] - means [tri_upper [1 ]]
24672485 A = sin * (1.0 / counts [tri_upper [0 ]] + 1.0 / counts [tri_upper [1 ]]) * (groups .size - 1.0 )
24682486 vs [tri_upper ] = dif ** 2.0 / A
@@ -2549,9 +2567,9 @@ def posthoc_tamhane(
25492567 tri_lower = np .tril_indices (vs .shape [0 ], - 1 )
25502568 vs [:, :] = 0
25512569
2552- counts = ni .reindex (groups ). to_numpy ( )
2553- means = xi .reindex (groups ). to_numpy ( )
2554- variances = si .reindex (groups ). to_numpy ( )
2570+ counts = __numeric_array ( ni .reindex (groups ))
2571+ means = __numeric_array ( xi .reindex (groups ))
2572+ variances = __numeric_array ( si .reindex (groups ))
25552573 i , j = tri_upper
25562574 scaled_variances = variances / counts
25572575 A = scaled_variances [i ] + scaled_variances [j ]
@@ -2667,8 +2685,8 @@ def posthoc_tukey(
26672685 vs = np .zeros ((groups .size , groups .size ), dtype = float )
26682686 tri_upper = np .triu_indices (vs .shape [0 ], 1 )
26692687 tri_lower = np .tril_indices (vs .shape [0 ], - 1 )
2670- counts = ni .reindex (groups ). to_numpy ( )
2671- means = xi .reindex (groups ). to_numpy ( )
2688+ counts = __numeric_array ( ni .reindex (groups ))
2689+ means = __numeric_array ( xi .reindex (groups ))
26722690 scale = sin * 0.5 * (1.0 / counts [tri_upper [0 ]] + 1.0 / counts [tri_upper [1 ]])
26732691 q_values = (means [tri_upper [0 ]] - means [tri_upper [1 ]]) / np .sqrt (scale )
26742692 vs [tri_upper ] = ss .studentized_range .sf (np .abs (q_values ), groups .size , n - groups .size )
@@ -2743,7 +2761,7 @@ def posthoc_dscf(
27432761 x = x .sort_values (by = [_group_col ], ascending = True ) if sort else x
27442762 groups = x [_group_col ].unique ()
27452763 x_grouped = x .groupby (_group_col , observed = True )[_val_col ]
2746- samples = [x_grouped .get_group (group ).dropna (). to_numpy ( ) for group in groups ]
2764+ samples = [__numeric_array ( x_grouped .get_group (group ).dropna ()) for group in groups ]
27472765 k = groups .size
27482766
27492767 def compare (i , j ):
@@ -2856,9 +2874,9 @@ def posthoc_games_howell(
28562874 tri_lower = np .tril_indices (vs .shape [0 ], - 1 )
28572875 vs [:, :] = 0
28582876
2859- counts = ni .reindex (groups ). to_numpy ( )
2860- means = xi .reindex (groups ). to_numpy ( )
2861- variances = si .reindex (groups ). to_numpy ( )
2877+ counts = __numeric_array ( ni .reindex (groups ))
2878+ means = __numeric_array ( xi .reindex (groups ))
2879+ variances = __numeric_array ( si .reindex (groups ))
28622880 i , j = tri_upper
28632881 A = variances [i ] / counts [i ] + variances [j ] / counts [j ]
28642882 q_values = (means [i ] - means [j ]) / np .sqrt (A ) * np .sqrt (2.0 )
@@ -2982,9 +3000,9 @@ def posthoc_dunnett_t3(
29823000 tri_lower = np .tril_indices (vs .shape [0 ], - 1 )
29833001 vs [:, :] = 0
29843002
2985- counts = ni .reindex (groups ). to_numpy ( )
2986- means = xi .reindex (groups ). to_numpy ( )
2987- variances = si .reindex (groups ). to_numpy ( )
3003+ counts = __numeric_array ( ni .reindex (groups ))
3004+ means = __numeric_array ( xi .reindex (groups ))
3005+ variances = __numeric_array ( si .reindex (groups ))
29883006 i , j = tri_upper
29893007 A = variances [i ] / counts [i ] + variances [j ] / counts [j ]
29903008 t_values = (means [i ] - means [j ]) / np .sqrt (A )
@@ -3076,8 +3094,8 @@ def posthoc_lsd(
30763094 tri_lower = np .tril_indices (vs .shape [0 ], - 1 )
30773095 vs [:, :] = 0
30783096
3079- counts = ni .reindex (groups ). to_numpy ( )
3080- means = xi .reindex (groups ). to_numpy ( )
3097+ counts = __numeric_array ( ni .reindex (groups ))
3098+ means = __numeric_array ( xi .reindex (groups ))
30813099 i , j = tri_upper
30823100 A = s2in * (1.0 / counts [i ] + 1.0 / counts [j ])
30833101 t_values = (means [i ] - means [j ]) / np .sqrt (A )
@@ -3166,8 +3184,8 @@ def posthoc_snk(
31663184 df = n - k
31673185
31683186 # rank position of each group when means are sorted in decreasing order
3169- counts = ni .reindex (groups ). to_numpy ( )
3170- means = xi .reindex (groups ). to_numpy ( )
3187+ counts = __numeric_array ( ni .reindex (groups ))
3188+ means = __numeric_array ( xi .reindex (groups ))
31713189 order = means .argsort ()[::- 1 ]
31723190 rank_pos = np .empty (k , dtype = int )
31733191 rank_pos [order ] = np .arange (k )
@@ -3257,8 +3275,7 @@ def posthoc_duncan(
32573275 df = n - k
32583276 r = k / np .sum (1.0 / ni )
32593277
3260- counts = ni .reindex (groups ).to_numpy ()
3261- means = xi .reindex (groups ).to_numpy ()
3278+ means = __numeric_array (xi .reindex (groups ))
32623279 order = means .argsort ()[::- 1 ]
32633280 rank_pos = np .empty (k , dtype = int )
32643281 rank_pos [order ] = np .arange (k )
0 commit comments