Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 135
Update to use pandas v2.*#932
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
7b850ca002604d8819b8cbe5c02498bc2e45beffda9b67fec234a42058003ed012e92ec6975a42a899e5a752ea4543b19a8ed8fb950c9f6da393dbdc06d737cd38e574f89ef659872fc51916848091bd5cf9fb217becbca8be8e0d804e780b019c4b501e249a0b3c27560db6b61a97b14b4906ed780be6File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| from __future__ import annotations | ||
| from typing import TYPE_CHECKING, Any | ||
| import pandas as pd | ||
| from pandas import eval as _eval | ||
| if TYPE_CHECKING: | ||
| from collections.abc import Hashable, Iterator, Mapping, Sequence | ||
| from pandas._typing import ArrayLike | ||
| def _get_cleaned_column_resolvers( | ||
| df: pd.DataFrame, raw: bool = True | ||
| ) -> dict[Hashable, ArrayLike | pd.Series]: | ||
| """ | ||
| Return the special character free column resolvers of a dataframe. | ||
| Column names with special characters are 'cleaned up' so that they can | ||
| be referred to by backtick quoting. | ||
| Used in :meth:`DataFrame.eval`. | ||
| """ | ||
| from pandas import Series | ||
| from pandas.core.computation.parsing import clean_column_name | ||
| if isinstance(df, pd.Series): | ||
| return {clean_column_name(df.name): df} | ||
| # CHANGED FROM PANDAS: do not even convert the arrays to pd.Series, just | ||
| # give the raw arrays to the compute engine. This is potentially a breaking | ||
| # change if any of the operations in the eval string require a pd.Series. | ||
jpn-- marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if raw: | ||
| # Performance tradeoff: in the dict below, we iterate over `df.items`, | ||
| # which yields tuples of (column_name, data as pd.Series). This is marginally | ||
| # slower than iterating over `df.columns` and `df._iter_column_arrays()`, | ||
| # but the latter is not in Pandas' public API, and may be removed in the future. | ||
| return { | ||
| clean_column_name(k): v for k, v in df.items() if not isinstance(k, int) | ||
| } | ||
| # CHANGED FROM PANDAS: do not call df.dtype inside the dict comprehension loop | ||
| # This update has been made in https://github.com/pandas-dev/pandas/pull/59573, | ||
| # but appears not to have been released yet as of pandas 2.2.3 | ||
| dtypes = df.dtypes | ||
| return { | ||
| clean_column_name(k): Series( | ||
| v, copy=False, index=df.index, name=k, dtype=dtypes[k] | ||
| ).__finalize__(df) | ||
| for k, v in zip(df.columns, df._iter_column_arrays()) | ||
jpn-- marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if not isinstance(k, int) | ||
| } | ||
| def fast_eval(df: pd.DataFrame, expr: str, **kwargs) -> Any | None: | ||
| """ | ||
| Evaluate a string describing operations on DataFrame columns. | ||
| Operates on columns only, not specific rows or elements. This allows | ||
| `eval` to run arbitrary code, which can make you vulnerable to code | ||
| injection if you pass user input to this function. | ||
| This function is a wrapper that replaces :meth:`~pandas.DataFrame.eval` | ||
| with a more efficient version than in the default pandas library (as | ||
| of pandas 2.2.3). It is recommended to use this function instead of | ||
| :meth:`~pandas.DataFrame.eval` for better performance. However, if you | ||
| encounter issues with this function, you can switch back to the default | ||
| pandas eval by changing the function call from `fast_eval(df, ...)` to | ||
| `df.eval(...)`. | ||
| Parameters | ||
| ---------- | ||
| expr : str | ||
| The expression string to evaluate. | ||
| **kwargs | ||
| See the documentation for :meth:`~pandas.DataFrame.eval` for complete | ||
| details on the keyword arguments accepted. | ||
| Returns | ||
| ------- | ||
| ndarray, scalar, or pandas object | ||
| The result of the evaluation. | ||
| """ | ||
| inplace = False | ||
| kwargs["level"] = kwargs.pop("level", 0) + 1 | ||
| index_resolvers = df._get_index_resolvers() | ||
| column_resolvers = _get_cleaned_column_resolvers(df) | ||
| resolvers = column_resolvers, index_resolvers | ||
| if "target" not in kwargs: | ||
| kwargs["target"] = df | ||
| kwargs["resolvers"] = tuple(kwargs.get("resolvers", ())) + resolvers | ||
| try: | ||
| return pd.Series( | ||
| _eval(expr, inplace=inplace, **kwargs), index=df.index, name=expr | ||
| ).__finalize__(df) | ||
| except Exception as e: | ||
| # Initially assume that the exception is caused by the potentially | ||
| # breaking change in _get_cleaned_column_resolvers, and try again | ||
| # TODO: what kind of exception should be caught here so it is less broad | ||
| column_resolvers = _get_cleaned_column_resolvers(df, raw=False) | ||
| resolvers = column_resolvers, index_resolvers | ||
| kwargs["resolvers"] = kwargs["resolvers"][:-2] + resolvers | ||
| return _eval(expr, inplace=inplace, **kwargs) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -14,6 +14,7 @@ | ||
| from activitysim.core import chunk, logit, simulate, tracing, util, workflow | ||
| from activitysim.core.configuration.base import ComputeSettings | ||
| from activitysim.core.fast_eval import fast_eval | ||
| logger = logging.getLogger(__name__) | ||
| @@ -287,7 +288,7 @@ def to_series(x): | ||
| if expr.startswith("@"): | ||
| v = to_series(eval(expr[1:], globals(), locals_d)) | ||
| else: | ||
| v = df.eval(expr, resolvers=[locals_d]) | ||
| v = fast_eval(df, expr, resolvers=[locals_d]) | ||
jpn-- marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if check_for_variability and v.std() == 0: | ||
| logger.info( | ||
| @@ -556,7 +557,7 @@ def to_series(x): | ||
| if expr.startswith("@"): | ||
| v = to_series(eval(expr[1:], globals(), locals_d)) | ||
| else: | ||
| v = df.eval(expr, resolvers=[locals_d]) | ||
| v = fast_eval(df, expr, resolvers=[locals_d]) | ||
| if check_for_variability and v.std() == 0: | ||
| logger.info( | ||
| "%s: no variability (%s) in: %s" | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.