1"""
2masked_reductions.py is for reduction algorithms using a mask-based approach
3for missing values.
4"""
5
6from __future__ import annotations
7
8from typing import TYPE_CHECKING
9import warnings
10
11import numpy as np
12
13from pandas._libs import (
14 lib,
15 missing as libmissing,
16)
17
18from pandas.core.nanops import check_below_min_count
19
20if TYPE_CHECKING:
21 from collections.abc import Callable
22
23 from pandas._typing import (
24 AxisInt,
25 npt,
26 )
27
28
29def _reductions(
30 func: Callable,
31 values: np.ndarray,
32 mask: npt.NDArray[np.bool_],
33 *,
34 skipna: bool = True,
35 min_count: int = 0,
36 axis: AxisInt | None = None,
37 initial: object | lib.NoDefault = lib.no_default,
38 **kwargs,
39):
40 """
41 Sum, mean or product for 1D masked array.
42
43 Parameters
44 ----------
45 func : np.sum or np.prod
46 values : np.ndarray
47 Numpy array with the values (can be of any dtype that support the
48 operation).
49 mask : np.ndarray[bool]
50 Boolean numpy array (True values indicate missing values).
51 skipna : bool, default True
52 Whether to skip NA.
53 min_count : int, default 0
54 The required number of valid values to perform the operation. If fewer than
55 ``min_count`` non-NA values are present the result will be NA.
56 axis : int, optional, default None
57 initial : scalar, optional
58 Starting value for the reduction. NumPy has a default value for most
59 data types, but for object-dtype arrays we need to specify it explicitly
60 """
61 if initial is not lib.no_default:
62 kwargs["initial"] = initial
63
64 if not skipna:
65 if mask.any() or check_below_min_count(values.shape, None, min_count):
66 return libmissing.NA
67 else:
68 return func(values, axis=axis, **kwargs)
69 else:
70 if check_below_min_count(values.shape, mask, min_count) and (
71 axis is None or values.ndim == 1
72 ):
73 return libmissing.NA
74
75 return func(values, where=~mask, axis=axis, **kwargs)
76
77
78def sum(
79 values: np.ndarray,
80 mask: npt.NDArray[np.bool_],
81 *,
82 skipna: bool = True,
83 min_count: int = 0,
84 axis: AxisInt | None = None,
85 initial: object | lib.NoDefault = lib.no_default,
86):
87 return _reductions(
88 np.sum,
89 values=values,
90 mask=mask,
91 skipna=skipna,
92 min_count=min_count,
93 axis=axis,
94 initial=initial,
95 )
96
97
98def prod(
99 values: np.ndarray,
100 mask: npt.NDArray[np.bool_],
101 *,
102 skipna: bool = True,
103 min_count: int = 0,
104 axis: AxisInt | None = None,
105):
106 return _reductions(
107 np.prod, values=values, mask=mask, skipna=skipna, min_count=min_count, axis=axis
108 )
109
110
111def _minmax(
112 func: Callable,
113 values: np.ndarray,
114 mask: npt.NDArray[np.bool_],
115 *,
116 skipna: bool = True,
117 axis: AxisInt | None = None,
118):
119 """
120 Reduction for 1D masked array.
121
122 Parameters
123 ----------
124 func : np.min or np.max
125 values : np.ndarray
126 Numpy array with the values (can be of any dtype that support the
127 operation).
128 mask : np.ndarray[bool]
129 Boolean numpy array (True values indicate missing values).
130 skipna : bool, default True
131 Whether to skip NA.
132 axis : int, optional, default None
133 """
134 if not skipna:
135 if mask.any() or not values.size:
136 # min/max with empty array raise in numpy, pandas returns NA
137 return libmissing.NA
138 else:
139 return func(values, axis=axis)
140 else:
141 subset = values[~mask]
142 if subset.size:
143 return func(subset, axis=axis)
144 else:
145 # min/max with empty array raise in numpy, pandas returns NA
146 return libmissing.NA
147
148
149def min(
150 values: np.ndarray,
151 mask: npt.NDArray[np.bool_],
152 *,
153 skipna: bool = True,
154 axis: AxisInt | None = None,
155):
156 return _minmax(np.min, values=values, mask=mask, skipna=skipna, axis=axis)
157
158
159def max(
160 values: np.ndarray,
161 mask: npt.NDArray[np.bool_],
162 *,
163 skipna: bool = True,
164 axis: AxisInt | None = None,
165):
166 return _minmax(np.max, values=values, mask=mask, skipna=skipna, axis=axis)
167
168
169def mean(
170 values: np.ndarray,
171 mask: npt.NDArray[np.bool_],
172 *,
173 skipna: bool = True,
174 axis: AxisInt | None = None,
175):
176 if not values.size or mask.all():
177 return libmissing.NA
178 return _reductions(np.mean, values=values, mask=mask, skipna=skipna, axis=axis)
179
180
181def var(
182 values: np.ndarray,
183 mask: npt.NDArray[np.bool_],
184 *,
185 skipna: bool = True,
186 axis: AxisInt | None = None,
187 ddof: int = 1,
188):
189 if not values.size or mask.all():
190 return libmissing.NA
191
192 with warnings.catch_warnings():
193 warnings.simplefilter("ignore", RuntimeWarning)
194 return _reductions(
195 np.var, values=values, mask=mask, skipna=skipna, axis=axis, ddof=ddof
196 )
197
198
199def std(
200 values: np.ndarray,
201 mask: npt.NDArray[np.bool_],
202 *,
203 skipna: bool = True,
204 axis: AxisInt | None = None,
205 ddof: int = 1,
206):
207 if not values.size or mask.all():
208 return libmissing.NA
209
210 with warnings.catch_warnings():
211 warnings.simplefilter("ignore", RuntimeWarning)
212 return _reductions(
213 np.std, values=values, mask=mask, skipna=skipna, axis=axis, ddof=ddof
214 )