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
« 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
6from functools import (
7 partial,
8 wraps,
9)
10from typing import (
11 TYPE_CHECKING,
12 Callable,
13 Sequence,
14)
15import warnings
17import numpy as np
19from pandas.errors import PerformanceWarning
20from pandas.util._exceptions import find_stack_level
22from pandas.core.dtypes.generic import (
23 ABCDataFrame,
24 ABCSeries,
25)
27from pandas.core.base import PandasObject
28import pandas.core.common as com
29from pandas.core.computation.common import result_type_many
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
34 from pandas.core.generic import NDFrame
35 from pandas.core.indexes.api import Index
38def _align_core_single_unary_op(
39 term,
40) -> tuple[partial | type[NDFrame], dict[str, Index] | None]:
42 typ: partial | type[NDFrame]
43 axes: dict[str, Index] | None = None
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)
52 return typ, axes
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)}
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)
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])
75 term_values = (term.value for term in terms)
77 # we don't have any pandas objects
78 if not _any_pandas_objects(terms):
79 return result_type_many(*term_values), None
81 return f(terms)
83 return wrapper
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]
91 from pandas import Series
93 ndims = Series(dict(zip(term_index, term_dims)))
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
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
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
112 if not axes[ax].is_(itm):
113 axes[ax] = axes[ax].join(itm, how="outer")
115 for i, ndim in ndims.items():
116 for axis, items in zip(range(ndim), axes):
117 ti = terms[i].value
119 if hasattr(ti, "reindex"):
120 transpose = isinstance(ti, ABCSeries) and naxes > 1
121 reindexer = axes[naxes - 1] if transpose else items
123 term_axis_size = len(ti.axes[axis])
124 reindexer_size = len(reindexer)
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 )
137 f = partial(ti.reindex, reindexer, axis=axis, copy=False)
139 terms[i].update(f())
141 terms[i].update(terms[i].value.values)
143 return typ, _zip_axes_from_type(typ, axes)
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
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
164 # perform the main alignment
165 typ, axes = _align_core(terms)
166 return typ, axes
169def reconstruct_object(typ, obj, axes, dtype):
170 """
171 Reconstruct an object given its type, raw value, and possibly empty
172 (None) axes.
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
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
194 res_t = np.result_type(obj.dtype, dtype)
196 if not isinstance(typ, partial) and issubclass(typ, PandasObject):
197 return typ(obj, dtype=res_t, **axes)
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)
214 return ret_value