-
-
Notifications
You must be signed in to change notification settings - Fork 19.3k
ENH: Add numba engine for rolling apply #30151
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
Changes from 40 commits
3b9bff8
9a302bf
0e9a600
36a77ed
dbb2a9b
f0e9a4d
1250aee
4e7fd1a
cb976cf
45420bb
17851cf
20767ca
9619f8d
66fa69c
b8908ea
135f2ad
34a5687
6da8199
123f77e
54e74d1
04d3530
4bbf587
f849bc7
0c30e48
c4c952e
8645976
987c916
b775684
2e04e60
9b20ff5
0c14033
c7106dc
1640085
2846faf
5a645c0
6bac000
6f1c73f
a890337
0a9071c
9d8d40b
84c3491
a429206
5826ad9
cf7571b
4bc9787
18eed60
f715b55
6a765bf
af3fe50
eb7b5e1
f7dfcf4
a42a960
d019830
29d145f
248149c
a3da51e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -321,6 +321,11 @@ We provide a number of common statistical functions: | |
| :meth:`~Rolling.cov`, Unbiased covariance (binary) | ||
| :meth:`~Rolling.corr`, Correlation (binary) | ||
|
|
||
| .. _stats.rolling_apply: | ||
|
|
||
| Rolling Apply | ||
| ~~~~~~~~~~~~~ | ||
|
|
||
| The :meth:`~Rolling.apply` function takes an extra ``func`` argument and performs | ||
| generic rolling computations. The ``func`` argument should be a single function | ||
| that produces a single value from an ndarray input. Suppose we wanted to | ||
|
|
@@ -334,6 +339,45 @@ compute the mean absolute deviation on a rolling basis: | |
| @savefig rolling_apply_ex.png | ||
| s.rolling(window=60).apply(mad, raw=True).plot(style='k') | ||
|
|
||
| Additionally, :meth:`~Rolling.apply` can leverage `Numba <https://numba.pydata.org/>`__ | ||
| if installed as an optional dependency as the execution engine of the apply aggregation using the | ||
|
||
| ``engine='numba'`` and ``engine_kwargs`` arguments (``raw`` must also be set to ``True``). | ||
| Numba will be applied in potentially two routines: | ||
|
|
||
| 1. If ``func`` is a standard Python function, the engine will JIT the passed function. ``func`` | ||
| can also be a pre-JIT function in which case the engine will not JIT the function again. | ||
|
||
| 2. The engine will JIT the for loop where the apply function is applied to each window. | ||
|
|
||
| The ``engine_kwargs`` argument is a dictionary of keyword arguments that will be passed into the | ||
| `numba.jit decorator <https://numba.pydata.org/numba-doc/latest/reference/jit-compilation.html#numba.jit>`__. | ||
| These keyword arguments will be applied to *both* the passed function (if a standard Python function) | ||
| and the apply for loop. Currently only ``nogil``, ``nopython``, and ``parallel`` are supported. | ||
|
||
|
|
||
| .. note:: | ||
|
|
||
| In terms of performance, **the first time a function is run using the Numba engine will be slow** | ||
| as Numba will have some function compilation overhead. However, ``rolling`` objects will cache | ||
| the function and subsequent calls will be fast. In general, the Numba engine is performant with | ||
| a larger amount of data points (e.g. 1+ million). | ||
|
|
||
| .. code-block:: ipython | ||
|
|
||
| In [1]: data = pd.Series(range(1_000_000)) | ||
|
|
||
| In [2]: roll = data.rolling(10) | ||
|
|
||
| In [3]: def f(x): | ||
| ...: return np.sum(x) + 5 | ||
| # Ran the first time, compilation time will affect performance | ||
| In [4]: %timeit -r 1 -n 1 roll.apply(f, engine='numba', raw=True) # noqa: E225 | ||
jreback marked this conversation as resolved.
Show resolved
Hide resolved
jreback marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 1.23 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each) | ||
| # Function is cached and performance will improve | ||
| In [5]: %timeit roll.apply(f, engine='numba', raw=True) | ||
| 188 ms ± 1.93 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) | ||
|
|
||
| In [6]: %timeit roll.apply(f, engine='cython', raw=True) | ||
| 3.92 s ± 59 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) | ||
|
|
||
| .. _stats.rolling_window: | ||
|
|
||
| Rolling windows | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -169,6 +169,16 @@ You can use the alias ``"boolean"`` as well. | |
| s = pd.Series([True, False, None], dtype="boolean") | ||
| s | ||
|
|
||
| .. _whatsnew_1000.numba_rolling_apply: | ||
|
|
||
| Using Numba in ``rolling.apply`` | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
|
||
| We've added an ``engine`` keyword to :meth:`~Rolling.apply` that allows the user to execute the | ||
| routine using `Numba <https://numba.pydata.org/>`__ instead of Cython. Using the Numba engine | ||
| can yield significant performance gains if the apply function can operate on numpy arrays and | ||
jreback marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| the data set is larger. For more details, see :ref:`rolling apply documentation <stats.rolling_apply>` | ||
|
||
|
|
||
| .. _whatsnew_1000.custom_window: | ||
|
|
||
| Defining custom windows for rolling operations | ||
|
|
@@ -428,6 +438,8 @@ Optional libraries below the lowest tested version may still work, but are not c | |
| +-----------------+-----------------+---------+ | ||
| | matplotlib | 2.2.2 | | | ||
| +-----------------+-----------------+---------+ | ||
| | numba | 0.46.0 | | | ||
jreback marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| +-----------------+-----------------+---------+ | ||
| | openpyxl | 2.5.7 | X | | ||
| +-----------------+-----------------+---------+ | ||
| | pyarrow | 0.12.0 | X | | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ | |
| "xlrd": "1.1.0", | ||
| "xlwt": "1.2.0", | ||
| "xlsxwriter": "0.9.8", | ||
| "numba": "0.46.0", | ||
| } | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,95 @@ | ||||||
| import types | ||||||
| from typing import Callable, Dict, Optional, Tuple | ||||||
|
|
||||||
| import numpy as np | ||||||
|
|
||||||
| from pandas.compat._optional import import_optional_dependency | ||||||
|
|
||||||
|
|
||||||
| def _generate_numba_apply_func( | ||||||
|
||||||
| args: Tuple, | ||||||
| kwargs: Dict, | ||||||
|
||||||
| kwargs: Dict, | |
| kwargs: Dict[str, Any], |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Callable need to return an ndarray? Adding sub-types to Callable helps immensely so would be preferable to add if so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Callable here should take in an ndarray and potentially *args and return a scalar. What would be the best way to type that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm not sure about the *args piece but here are the docs for it:
https://docs.python.org/3/library/typing.html#typing.Callable
So I would think Callable[[np.ndarray, ...], Scalar] if it works otherwise Callable[..., Scalar] is the best we can do.
Scalar can be imported from pandas._typing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I'll give that a shot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to go with Callable[..., Scalar] as also specifying np.ndarray was raising errors.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| engine_kwargs: Optional[Dict], | |
| engine_kwargs: Optional[Dict[str, Any]], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly more explicit would be preferable
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add subtypes here? Is this Dict[Callable, ???]?
jreback marked this conversation as resolved.
Show resolved
Hide resolved
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can just
if engine_kwargs is None:
engine_kwargs = {}
as you set the defaults below
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you move make_rolling_apply to module scope (out of this function)?
also don't you need to actually assign to the cache? (on a miss)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cache assignment happens here https://github.com/pandas-dev/pandas/pull/30151/files#diff-0de5c5d9abfcdd141e83701eaaec4358R541 (the function needs to run on some data first)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then i wouldn’t even check the cache here ; that must be at the higher level
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay sure thing. I'll move it up then.
Uh oh!
There was an error while loading. Please reload this page.