Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/pandas/core/computation/align.py: 16%

99 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1""" 

2Core eval alignment algorithms. 

3""" 

4from __future__ import annotations 

5 

6from functools import ( 

7 partial, 

8 wraps, 

9) 

10from typing import ( 

11 TYPE_CHECKING, 

12 Callable, 

13 Sequence, 

14) 

15import warnings 

16 

17import numpy as np 

18 

19from pandas.errors import PerformanceWarning 

20from pandas.util._exceptions import find_stack_level 

21 

22from pandas.core.dtypes.generic import ( 

23 ABCDataFrame, 

24 ABCSeries, 

25) 

26 

27from pandas.core.base import PandasObject 

28import pandas.core.common as com 

29from pandas.core.computation.common import result_type_many 

30 

31if TYPE_CHECKING: 31 ↛ 32line 31 didn't jump to line 32, because the condition on line 31 was never true

32 from pandas._typing import F 

33 

34 from pandas.core.generic import NDFrame 

35 from pandas.core.indexes.api import Index 

36 

37 

38def _align_core_single_unary_op( 

39 term, 

40) -> tuple[partial | type[NDFrame], dict[str, Index] | None]: 

41 

42 typ: partial | type[NDFrame] 

43 axes: dict[str, Index] | None = None 

44 

45 if isinstance(term.value, np.ndarray): 

46 typ = partial(np.asanyarray, dtype=term.value.dtype) 

47 else: 

48 typ = type(term.value) 

49 if hasattr(term.value, "axes"): 

50 axes = _zip_axes_from_type(typ, term.value.axes) 

51 

52 return typ, axes 

53 

54 

55def _zip_axes_from_type( 

56 typ: type[NDFrame], new_axes: Sequence[Index] 

57) -> dict[str, Index]: 

58 return {name: new_axes[i] for i, name in enumerate(typ._AXIS_ORDERS)} 

59 

60 

61def _any_pandas_objects(terms) -> bool: 

62 """ 

63 Check a sequence of terms for instances of PandasObject. 

64 """ 

65 return any(isinstance(term.value, PandasObject) for term in terms) 

66 

67 

68def _filter_special_cases(f) -> Callable[[F], F]: 

69 @wraps(f) 

70 def wrapper(terms): 

71 # single unary operand 

72 if len(terms) == 1: 

73 return _align_core_single_unary_op(terms[0]) 

74 

75 term_values = (term.value for term in terms) 

76 

77 # we don't have any pandas objects 

78 if not _any_pandas_objects(terms): 

79 return result_type_many(*term_values), None 

80 

81 return f(terms) 

82 

83 return wrapper 

84 

85 

86@_filter_special_cases 

87def _align_core(terms): 

88 term_index = [i for i, term in enumerate(terms) if hasattr(term.value, "axes")] 

89 term_dims = [terms[i].value.ndim for i in term_index] 

90 

91 from pandas import Series 

92 

93 ndims = Series(dict(zip(term_index, term_dims))) 

94 

95 # initial axes are the axes of the largest-axis'd term 

96 biggest = terms[ndims.idxmax()].value 

97 typ = biggest._constructor 

98 axes = biggest.axes 

99 naxes = len(axes) 

100 gt_than_one_axis = naxes > 1 

101 

102 for value in (terms[i].value for i in term_index): 

103 is_series = isinstance(value, ABCSeries) 

104 is_series_and_gt_one_axis = is_series and gt_than_one_axis 

105 

106 for axis, items in enumerate(value.axes): 

107 if is_series_and_gt_one_axis: 

108 ax, itm = naxes - 1, value.index 

109 else: 

110 ax, itm = axis, items 

111 

112 if not axes[ax].is_(itm): 

113 axes[ax] = axes[ax].join(itm, how="outer") 

114 

115 for i, ndim in ndims.items(): 

116 for axis, items in zip(range(ndim), axes): 

117 ti = terms[i].value 

118 

119 if hasattr(ti, "reindex"): 

120 transpose = isinstance(ti, ABCSeries) and naxes > 1 

121 reindexer = axes[naxes - 1] if transpose else items 

122 

123 term_axis_size = len(ti.axes[axis]) 

124 reindexer_size = len(reindexer) 

125 

126 ordm = np.log10(max(1, abs(reindexer_size - term_axis_size))) 

127 if ordm >= 1 and reindexer_size >= 10000: 

128 w = ( 

129 f"Alignment difference on axis {axis} is larger " 

130 f"than an order of magnitude on term {repr(terms[i].name)}, " 

131 f"by more than {ordm:.4g}; performance may suffer." 

132 ) 

133 warnings.warn( 

134 w, category=PerformanceWarning, stacklevel=find_stack_level() 

135 ) 

136 

137 f = partial(ti.reindex, reindexer, axis=axis, copy=False) 

138 

139 terms[i].update(f()) 

140 

141 terms[i].update(terms[i].value.values) 

142 

143 return typ, _zip_axes_from_type(typ, axes) 

144 

145 

146def align_terms(terms): 

147 """ 

148 Align a set of terms. 

149 """ 

150 try: 

151 # flatten the parse tree (a nested list, really) 

152 terms = list(com.flatten(terms)) 

153 except TypeError: 

154 # can't iterate so it must just be a constant or single variable 

155 if isinstance(terms.value, (ABCSeries, ABCDataFrame)): 

156 typ = type(terms.value) 

157 return typ, _zip_axes_from_type(typ, terms.value.axes) 

158 return np.result_type(terms.type), None 

159 

160 # if all resolved variables are numeric scalars 

161 if all(term.is_scalar for term in terms): 

162 return result_type_many(*(term.value for term in terms)).type, None 

163 

164 # perform the main alignment 

165 typ, axes = _align_core(terms) 

166 return typ, axes 

167 

168 

169def reconstruct_object(typ, obj, axes, dtype): 

170 """ 

171 Reconstruct an object given its type, raw value, and possibly empty 

172 (None) axes. 

173 

174 Parameters 

175 ---------- 

176 typ : object 

177 A type 

178 obj : object 

179 The value to use in the type constructor 

180 axes : dict 

181 The axes to use to construct the resulting pandas object 

182 

183 Returns 

184 ------- 

185 ret : typ 

186 An object of type ``typ`` with the value `obj` and possible axes 

187 `axes`. 

188 """ 

189 try: 

190 typ = typ.type 

191 except AttributeError: 

192 pass 

193 

194 res_t = np.result_type(obj.dtype, dtype) 

195 

196 if not isinstance(typ, partial) and issubclass(typ, PandasObject): 

197 return typ(obj, dtype=res_t, **axes) 

198 

199 # special case for pathological things like ~True/~False 

200 if hasattr(res_t, "type") and typ == np.bool_ and res_t != np.bool_: 

201 ret_value = res_t.type(obj) 

202 else: 

203 ret_value = typ(obj).astype(res_t) 

204 # The condition is to distinguish 0-dim array (returned in case of 

205 # scalar) and 1 element array 

206 # e.g. np.array(0) and np.array([0]) 

207 if ( 

208 len(obj.shape) == 1 

209 and len(obj) == 1 

210 and not isinstance(ret_value, np.ndarray) 

211 ): 

212 ret_value = np.array([ret_value]).astype(res_t) 

213 

214 return ret_value