Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/compat/numpy/function.py: 65%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

148 statements  

1""" 

2For compatibility with numpy libraries, pandas functions or methods have to 

3accept '*args' and '**kwargs' parameters to accommodate numpy arguments that 

4are not actually used or respected in the pandas implementation. 

5 

6To ensure that users do not abuse these parameters, validation is performed in 

7'validators.py' to make sure that any extra parameters passed correspond ONLY 

8to those in the numpy signature. Part of that validation includes whether or 

9not the user attempted to pass in non-default values for these extraneous 

10parameters. As we want to discourage users from relying on these parameters 

11when calling the pandas implementation, we want them only to pass in the 

12default values for these parameters. 

13 

14This module provides a set of commonly used default arguments for functions and 

15methods that are spread throughout the codebase. This module will make it 

16easier to adjust to future upstream changes in the analogous numpy signatures. 

17""" 

18 

19from __future__ import annotations 

20 

21from typing import ( 

22 TYPE_CHECKING, 

23 Any, 

24 TypeVar, 

25 cast, 

26 overload, 

27) 

28 

29import numpy as np 

30from numpy import ndarray 

31 

32from pandas._libs.lib import ( 

33 is_bool, 

34 is_integer, 

35) 

36from pandas.errors import UnsupportedFunctionCall 

37from pandas.util._validators import ( 

38 validate_args, 

39 validate_args_and_kwargs, 

40 validate_kwargs, 

41) 

42 

43if TYPE_CHECKING: 

44 from pandas._typing import ( 

45 Axis, 

46 AxisInt, 

47 ) 

48 

49 AxisNoneT = TypeVar("AxisNoneT", Axis, None) 

50 

51 

52class CompatValidator: 

53 def __init__( 

54 self, 

55 defaults: dict[str, Any], 

56 fname: str | None = None, 

57 method: str | None = None, 

58 max_fname_arg_count: int | None = None, 

59 ) -> None: 

60 self.fname = fname 

61 self.method = method 

62 self.defaults = defaults 

63 self.max_fname_arg_count = max_fname_arg_count 

64 

65 def __call__( 

66 self, 

67 args: tuple[Any, ...], 

68 kwargs: dict[str, Any], 

69 fname: str | None = None, 

70 max_fname_arg_count: int | None = None, 

71 method: str | None = None, 

72 ) -> None: 

73 if not args and not kwargs: 

74 return None 

75 

76 fname = self.fname if fname is None else fname 

77 max_fname_arg_count = ( 

78 self.max_fname_arg_count 

79 if max_fname_arg_count is None 

80 else max_fname_arg_count 

81 ) 

82 method = self.method if method is None else method 

83 

84 if method == "args": 

85 validate_args(fname, args, cast("int", max_fname_arg_count), self.defaults) 

86 elif method == "kwargs": 

87 validate_kwargs(fname, kwargs, self.defaults) 

88 elif method == "both": 

89 validate_args_and_kwargs( 

90 fname, args, kwargs, cast("int", max_fname_arg_count), self.defaults 

91 ) 

92 else: 

93 raise ValueError(f"invalid validation method '{method}'") 

94 

95 

96ARGMINMAX_DEFAULTS = {"out": None} 

97validate_argmin = CompatValidator( 

98 ARGMINMAX_DEFAULTS, fname="argmin", method="both", max_fname_arg_count=1 

99) 

100validate_argmax = CompatValidator( 

101 ARGMINMAX_DEFAULTS, fname="argmax", method="both", max_fname_arg_count=1 

102) 

103 

104 

105def process_skipna( 

106 skipna: bool | ndarray | None, args: tuple[Any, ...] 

107) -> tuple[bool, tuple[Any, ...]]: 

108 if isinstance(skipna, ndarray) or skipna is None: 

109 args = (skipna, *args) 

110 skipna = True 

111 

112 return skipna, args 

113 

114 

115def validate_argmax_with_skipna( 

116 skipna: bool | ndarray | None, args: tuple[Any, ...], kwargs: dict[str, Any] 

117) -> bool: 

118 """ 

119 If 'Series.argmax' is called via the 'numpy' library, the third parameter 

120 in its signature is 'out', which takes either an ndarray or 'None', so 

121 check if the 'skipna' parameter is either an instance of ndarray or is 

122 None, since 'skipna' itself should be a boolean 

123 """ 

124 skipna, args = process_skipna(skipna, args) 

125 validate_argmax(args, kwargs) 

126 return skipna 

127 

128 

129ARGSORT_DEFAULTS: dict[str, int | str | None] = {} 

130ARGSORT_DEFAULTS["axis"] = -1 

131ARGSORT_DEFAULTS["kind"] = "quicksort" 

132ARGSORT_DEFAULTS["order"] = None 

133ARGSORT_DEFAULTS["kind"] = None 

134ARGSORT_DEFAULTS["stable"] = None 

135 

136 

137validate_argsort = CompatValidator( 

138 ARGSORT_DEFAULTS, fname="argsort", max_fname_arg_count=0, method="both" 

139) 

140 

141# two different signatures of argsort, this second validation for when the 

142# `kind` param is supported 

143ARGSORT_DEFAULTS_KIND: dict[str, int | None] = {} 

144ARGSORT_DEFAULTS_KIND["axis"] = -1 

145ARGSORT_DEFAULTS_KIND["order"] = None 

146ARGSORT_DEFAULTS_KIND["stable"] = None 

147validate_argsort_kind = CompatValidator( 

148 ARGSORT_DEFAULTS_KIND, fname="argsort", max_fname_arg_count=0, method="both" 

149) 

150 

151 

152def validate_argsort_with_ascending( 

153 ascending: bool | int | None, args: tuple[Any, ...], kwargs: dict[str, Any] 

154) -> bool: 

155 """ 

156 If 'Categorical.argsort' is called via the 'numpy' library, the first 

157 parameter in its signature is 'axis', which takes either an integer or 

158 'None', so check if the 'ascending' parameter has either integer type or is 

159 None, since 'ascending' itself should be a boolean 

160 """ 

161 if is_integer(ascending) or ascending is None: 

162 args = (ascending, *args) 

163 ascending = True 

164 

165 validate_argsort_kind(args, kwargs, max_fname_arg_count=3) 

166 ascending = cast("bool", ascending) 

167 return ascending 

168 

169 

170CLIP_DEFAULTS: dict[str, Any] = {"out": None} 

171validate_clip = CompatValidator( 

172 CLIP_DEFAULTS, fname="clip", method="both", max_fname_arg_count=3 

173) 

174 

175 

176@overload 

177def validate_clip_with_axis( 

178 axis: ndarray, args: tuple[Any, ...], kwargs: dict[str, Any] 

179) -> None: ... 

180 

181 

182@overload 

183def validate_clip_with_axis( 

184 axis: AxisNoneT, args: tuple[Any, ...], kwargs: dict[str, Any] 

185) -> AxisNoneT: ... 

186 

187 

188def validate_clip_with_axis( 

189 axis: ndarray | AxisNoneT, args: tuple[Any, ...], kwargs: dict[str, Any] 

190) -> AxisNoneT | None: 

191 """ 

192 If 'NDFrame.clip' is called via the numpy library, the third parameter in 

193 its signature is 'out', which can takes an ndarray, so check if the 'axis' 

194 parameter is an instance of ndarray, since 'axis' itself should either be 

195 an integer or None 

196 """ 

197 if isinstance(axis, ndarray): 

198 args = (axis, *args) 

199 # error: Incompatible types in assignment (expression has type "None", 

200 # variable has type "Union[ndarray[Any, Any], str, int]") 

201 axis = None # type: ignore[assignment] 

202 

203 validate_clip(args, kwargs) 

204 # error: Incompatible return value type (got "Union[ndarray[Any, Any], 

205 # str, int]", expected "Union[str, int, None]") 

206 return axis # type: ignore[return-value] 

207 

208 

209CUM_FUNC_DEFAULTS: dict[str, Any] = {} 

210CUM_FUNC_DEFAULTS["dtype"] = None 

211CUM_FUNC_DEFAULTS["out"] = None 

212validate_cum_func = CompatValidator( 

213 CUM_FUNC_DEFAULTS, method="both", max_fname_arg_count=1 

214) 

215validate_cumsum = CompatValidator( 

216 CUM_FUNC_DEFAULTS, fname="cumsum", method="both", max_fname_arg_count=1 

217) 

218 

219 

220def validate_cum_func_with_skipna( 

221 skipna: bool, args: tuple[Any, ...], kwargs: dict[str, Any], name: str 

222) -> bool: 

223 """ 

224 If this function is called via the 'numpy' library, the third parameter in 

225 its signature is 'dtype', which takes either a 'numpy' dtype or 'None', so 

226 check if the 'skipna' parameter is a boolean or not 

227 """ 

228 if not is_bool(skipna): 

229 args = (skipna, *args) 

230 skipna = True 

231 elif isinstance(skipna, np.bool_): 

232 skipna = bool(skipna) 

233 

234 validate_cum_func(args, kwargs, fname=name) 

235 return skipna 

236 

237 

238ALLANY_DEFAULTS: dict[str, bool | None] = {} 

239ALLANY_DEFAULTS["dtype"] = None 

240ALLANY_DEFAULTS["out"] = None 

241ALLANY_DEFAULTS["keepdims"] = False 

242ALLANY_DEFAULTS["axis"] = None 

243validate_all = CompatValidator( 

244 ALLANY_DEFAULTS, fname="all", method="both", max_fname_arg_count=1 

245) 

246validate_any = CompatValidator( 

247 ALLANY_DEFAULTS, fname="any", method="both", max_fname_arg_count=1 

248) 

249 

250LOGICAL_FUNC_DEFAULTS = {"out": None, "keepdims": False} 

251validate_logical_func = CompatValidator(LOGICAL_FUNC_DEFAULTS, method="kwargs") 

252 

253MINMAX_DEFAULTS = {"axis": None, "dtype": None, "out": None, "keepdims": False} 

254validate_min = CompatValidator( 

255 MINMAX_DEFAULTS, fname="min", method="both", max_fname_arg_count=1 

256) 

257validate_max = CompatValidator( 

258 MINMAX_DEFAULTS, fname="max", method="both", max_fname_arg_count=1 

259) 

260 

261 

262REPEAT_DEFAULTS: dict[str, Any] = {"axis": None} 

263validate_repeat = CompatValidator( 

264 REPEAT_DEFAULTS, fname="repeat", method="both", max_fname_arg_count=1 

265) 

266 

267ROUND_DEFAULTS: dict[str, Any] = {"out": None} 

268validate_round = CompatValidator( 

269 ROUND_DEFAULTS, fname="round", method="both", max_fname_arg_count=1 

270) 

271 

272STAT_FUNC_DEFAULTS: dict[str, Any | None] = {} 

273STAT_FUNC_DEFAULTS["dtype"] = None 

274STAT_FUNC_DEFAULTS["out"] = None 

275 

276SUM_DEFAULTS = STAT_FUNC_DEFAULTS.copy() 

277SUM_DEFAULTS["axis"] = None 

278SUM_DEFAULTS["keepdims"] = False 

279SUM_DEFAULTS["initial"] = None 

280 

281PROD_DEFAULTS = SUM_DEFAULTS.copy() 

282 

283MEAN_DEFAULTS = SUM_DEFAULTS.copy() 

284 

285MEDIAN_DEFAULTS = STAT_FUNC_DEFAULTS.copy() 

286MEDIAN_DEFAULTS["overwrite_input"] = False 

287MEDIAN_DEFAULTS["keepdims"] = False 

288 

289STAT_FUNC_DEFAULTS["keepdims"] = False 

290 

291validate_stat_func = CompatValidator(STAT_FUNC_DEFAULTS, method="kwargs") 

292validate_sum = CompatValidator( 

293 SUM_DEFAULTS, fname="sum", method="both", max_fname_arg_count=1 

294) 

295validate_prod = CompatValidator( 

296 PROD_DEFAULTS, fname="prod", method="both", max_fname_arg_count=1 

297) 

298validate_mean = CompatValidator( 

299 MEAN_DEFAULTS, fname="mean", method="both", max_fname_arg_count=1 

300) 

301validate_median = CompatValidator( 

302 MEDIAN_DEFAULTS, fname="median", method="both", max_fname_arg_count=1 

303) 

304 

305STAT_DDOF_FUNC_DEFAULTS: dict[str, bool | None] = {} 

306STAT_DDOF_FUNC_DEFAULTS["dtype"] = None 

307STAT_DDOF_FUNC_DEFAULTS["out"] = None 

308STAT_DDOF_FUNC_DEFAULTS["keepdims"] = False 

309validate_stat_ddof_func = CompatValidator(STAT_DDOF_FUNC_DEFAULTS, method="kwargs") 

310 

311TAKE_DEFAULTS: dict[str, str | None] = {} 

312TAKE_DEFAULTS["out"] = None 

313TAKE_DEFAULTS["mode"] = "raise" 

314validate_take = CompatValidator(TAKE_DEFAULTS, fname="take", method="kwargs") 

315 

316 

317TRANSPOSE_DEFAULTS = {"axes": None} 

318validate_transpose = CompatValidator( 

319 TRANSPOSE_DEFAULTS, fname="transpose", method="both", max_fname_arg_count=0 

320) 

321 

322 

323def validate_groupby_func( 

324 name: str, 

325 args: tuple[Any, ...], 

326 kwargs: dict[str, Any], 

327 allowed: list[str] | None = None, 

328) -> None: 

329 """ 

330 'args' and 'kwargs' should be empty, except for allowed kwargs because all 

331 of their necessary parameters are explicitly listed in the function 

332 signature 

333 """ 

334 if allowed is None: 

335 allowed = [] 

336 

337 extra_kwargs = set(kwargs) - set(allowed) 

338 

339 if len(args) + len(extra_kwargs) > 0: 

340 raise UnsupportedFunctionCall( 

341 "numpy operations are not valid with groupby. " 

342 f"Use .groupby(...).{name}() instead" 

343 ) 

344 

345 

346def validate_minmax_axis(axis: AxisInt | None, ndim: int = 1) -> None: 

347 """ 

348 Ensure that the axis argument passed to min, max, argmin, or argmax is zero 

349 or None, as otherwise it will be incorrectly ignored. 

350 

351 Parameters 

352 ---------- 

353 axis : int or None 

354 ndim : int, default 1 

355 

356 Raises 

357 ------ 

358 ValueError 

359 """ 

360 if axis is None: 

361 return 

362 if axis >= ndim or (axis < 0 and ndim + axis < 0): 

363 raise ValueError(f"`axis` must be fewer than the number of dimensions ({ndim})") 

364 

365 

366_validation_funcs = { 

367 "median": validate_median, 

368 "mean": validate_mean, 

369 "min": validate_min, 

370 "max": validate_max, 

371 "sum": validate_sum, 

372 "prod": validate_prod, 

373} 

374 

375 

376def validate_func(fname: str, args: tuple[Any, ...], kwargs: dict[str, Any]) -> None: 

377 if fname not in _validation_funcs: 

378 return validate_stat_func(args, kwargs, fname=fname) 

379 

380 validation_func = _validation_funcs[fname] 

381 return validation_func(args, kwargs)