-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
BUG: rolling window functions don't support custom indexers #32865
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
Comments
cc @mroeschke. |
Nice investigation @AlexKirko. The BaseIndexers are definitely designed more for Do you mind investigating which other rolling aggregations return different results? |
Hello, @mroeschke. No problem, see below. Broken:
Working correctly:
Yeah, supporting arbitrary BaseIndexers would require the algorithms to go O(N2), which, I think, is unacceptable (we'd be running everything with A lazier approach would be to throw a warning when creating a rolling window object based on a custom indexer, saying that only This entire thing came up when I and a colleague were working on a problem on Kaggle, and we needed forward-looking windows to incorporate information about the future into our estimates. From that very limited user base I can say that at least some people expect this stuff to just work without a noticeable drop in speed, because it's unclear why it shouldn't. |
Thanks for looking into this @AlexKirko! For now I'll put in a PR to raise a For the next step, if there's a way to modify the current algorithms with minimal performance impacts that'd be fantastic! It's not immediately apparently to me what those modifications would be, and I won't have a lot of time to revisit it in the short term. If there's a general fix for each algorithm I might be able to help out where I can. |
Hi, I came here to report this issue as well. I wanted to chime in on a couple things. First thanks for I think that at the very least there should be support of window functions for forward-looking windows. Forward-looking windows is an oft-requested feature (here in pandas issues) and rolling Indexer support was a huge step in the right direction. Without that support, the use of Also, I don't believe For now, might I suggest a mention and example in the guide docs at Custom window rolling to use Here is a complete import numba
import numpy as np
import pandas as pd
from pandas.api.indexers import BaseIndexer
@numba.jit
def numba_max(x):
return max(x)
class ForwardIndexer(BaseIndexer):
''' Custom `Indexer` for use in forward-looking `rolling` windows '''
def get_window_bounds(self, num_values, min_periods, center, closed):
''' Set up forward looking windows '''
start = np.arange(num_values, dtype=np.int64)
end = start.copy() + self.window_size
#---- Clip to `num_values`
end[end > num_values] = num_values
return start, end
df = pd.DataFrame({"a": [1,2,3,4,5,6,7,8,9]})
df_max = df.rolling(window=ForwardIndexer(window_size=3), min_periods=1).apply(numba_max, raw=True)
df_max Thanks again. |
@mroeschke , thank you for implementing the error-raising behavior! |
take |
I'll also take a closer look at the median. It's acting funny during benchmarks. |
@mroeschke IN:
values = np.arange(10)
values[5] = 100.0
rolling2 = pd.Series(values).rolling(window=3, min_periods=2)
rolling2.var()
# This is sample variance
OUT:
0 NaN
1 0.500000
2 1.000000
3 1.000000
4 1.000000
5 3104.333333
6 3009.333333
7 2914.333333
8 1.000000
9 1.000000
dtype: float64
IN:
rolling2.apply(lambda x: np.var(x))
# This is population variance
OUT:
0 NaN
1 0.250000
2 0.666667
3 0.666667
4 0.666667
5 2069.555556
6 2006.222222
7 1942.888889
8 0.666667
9 0.666667
dtype: float64 As far as I know, we should use sample variance when we draw a limited number of data observations from the total population. In that case, when we average across many draws, the expectation of the average will match the ground truth population variance. When we slide a window across a data series, the data in a particular window isn't drawn from anything, it's all there is. For our purposes it is the population, and we should divide the sum of squared differences by Here is the formula from our current code: result = ssqdm_x / (nobs - <float64_t>ddof) Where ddof is the number of degrees of freedom and equals 1. Am I missing something statitstics-wise, or should I open a new issue and solve it with a PR there (we do this stuff both for variable and fixed windows)? P.S. There are no other problems with std and var. The output is already properly anchored to the input. If we divide by N, forward-looking variance will match |
@AlexKirko across pandas I think sample variance is what we calculate in general for https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.std.html |
@mroeschke Thanks! I agree that sample variance makes total sense when we calculate variance for an entire DataFrame or Series, because it's probable that the data we have is a sample from some larger general population, and normalizing by N - 1 makes tbe estimate unbiased. However, when we slide a window across a series, this does not make sense to me. As far as I can see, the sample variance argument is illogical here, because there is no drawing from a larger population. We have a window, let's say ten elements, and we are interested in variance for these ten elements. In my opinion, they are the population, and the variance should be estimated via the population formula. When the window moves, we are interested in the variance for different ten elements, and we make no assumptions about them being from some population of ten element windows with stable statistical properties or whatever (we use a rolling window precisely because we want to calculate a different variance for each window). Is my argument making sense? |
I must add that in practice we rarely use small windows, plus data science methods that we apply to the estimation results can usually adapt to scaling, so this is more about correct statistical methodology than practical impact on the user experience. If my argument is wrong, then I'll just add |
Your conclusion from a data scientist practitioner point of view makes sense, but I think the user will need to recognize that From a software perspective, it's a nice to have consistency across the library how |
@mroeschke |
|
Code Sample, a copy-pastable example if possible
Problem description
We state here that we support supplying a custom Indexer when building a
pandas.DataFrame.rolling
object. While the object does get built, and it returns the correct windows, it doesn't support many rolling window functions. The problem is that our implementations of these aggregation functions expect a standard backward-looking window and we support centered windows via a bit of a crutch.For example,
rolling.min
eventually falls through to_roll_min_max_variable
inaggregations.pyx
, which uses this bit of code to record the output:This indexing of output means that the window minimum gets written near the end of the window, even if the window is forward-looking. I've investigated a bit, and there is a similar issue in
rolling.std
- it also isn't adapted to more flexible rolling windows.While it's not possible to make rolling window aggregation functions completely universal without loss of efficiency, it's possible to adapt them to most useful cases: forward-looking, smoothly contracting and expanding. We'd still have to think on how we would check that we support a custom Indexer, and whether we would check at all. It might be possible to just specify the supported kinds in the docs and throw a warning or do something similar.
If we choose this path, I'd be happy to deal with the problem over a series of PRs or share the load with someone. Looks like a fair bit of work, but the pandemic freed up a lot of time.
Expected Output
Output of
pd.show_versions()
INSTALLED VERSIONS
commit : d308712
python : 3.7.6.final.0
python-bits : 64
OS : Windows
OS-release : 10
Version : 10.0.18362
machine : AMD64
processor : Intel64 Family 6 Model 142 Stepping 10, GenuineIntel
byteorder : little
LC_ALL : None
LANG : ru_RU.UTF-8
LOCALE : None.None
pandas : 0.26.0.dev0+2635.gd308712c8
numpy : 1.17.5
pytz : 2019.3
dateutil : 2.8.1
pip : 19.3.1
setuptools : 44.0.0.post20200106
Cython : 0.29.14
pytest : 5.3.4
hypothesis : 5.2.0
sphinx : 2.3.1
blosc : None
feather : None
xlsxwriter : 1.2.7
lxml.etree : 4.4.2
html5lib : 1.0.1
pymysql : None
psycopg2 : None
jinja2 : 2.10.3
IPython : 7.11.1
pandas_datareader: None
bs4 : 4.8.2
bottleneck : 1.3.1
fastparquet : None
gcsfs : None
matplotlib : 3.1.2
numexpr : 2.7.1
odfpy : None
openpyxl : 3.0.1
pandas_gbq : None
pyarrow : None
pytables : None
pyxlsb : None
s3fs : 0.4.0
scipy : 1.3.1
sqlalchemy : 1.3.12
tables : 3.6.1
tabulate : 0.8.6
xarray : None
xlrd : 1.2.0
xlwt : 1.3.0
numba : 0.47.0
The text was updated successfully, but these errors were encountered: