【问题标题】:pandas rolling over irregular dataset with irregular window size熊猫在不规则窗口大小的不规则数据集上滚动
【发布时间】:2021-03-04 00:33:53
【问题描述】:

我想计算不规则熊猫系列的中位数。

特别是,我想先根据前 X 天计算中位数,然后再根据接下来的 X 天计算中位数。

我确实编写了以下工作示例。在那里,我生成了两列,一列 median_-2days 列出了前两天的中位数,另一列 median_+2days 列出了接下来两天的中位数。

import numpy as np
import pandas as pd


def dummy_data():
    idx = np.array([pd.Timestamp(year=2021, month=1, day=1),
                    pd.Timestamp(year=2021, month=1, day=2),
                    pd.Timestamp(year=2021, month=1, day=3),
                    pd.Timestamp(year=2021, month=1, day=5),
                    pd.Timestamp(year=2021, month=1, day=6),
                    pd.Timestamp(year=2021, month=1, day=8),
                    pd.Timestamp(year=2021, month=1, day=9),
                    pd.Timestamp(year=2021, month=1, day=10),
                    ])
    data = np.array([1, 2, 3, 5, 6, 8, 9, 10])
    return pd.DataFrame(data, index=idx, columns=["l"])


def rolling_median_irregular(ds, left, right):
    res = pd.Series(index=ds.index)
    for t in ds.index:
        val = ds.loc[(ds.index >= t - left) & (ds.index <= t + right)].median()
        res.loc[t] = val
    return res


if __name__ == "__main__":
    df = dummy_data()
    df["median_-2days"] = rolling_median_irregular(df["l"], left=pd.Timedelta(days=2), right=pd.Timedelta(days=0))
    df["median_+2days"] = rolling_median_irregular(df["l"], left=pd.Timedelta(days=0), right=pd.Timedelta(days=2))

但是,我觉得我在重新发明轮子。我更喜欢使用内置的rolling 函数来获得更通用的方法,我还可以使用不同的函数(如.sum().mean())和不同的窗口类型。

甚至可以使用内置函数来做到这一点,还是我必须继承BaseIndexer?如果是这样的话,会是什么样子?

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    您可以将rolling 与 3D 窗口一起使用,因为您希望包含&gt;=&lt;= 的边界。要做左右,你可以用[::-1]反转系列,这样就完成了:

    df["median_-2days_r"] = df.loc[:,'l'].rolling('3D').median()
    df["median_+2days_r"] = df.loc[::-1, 'l'].rolling('3D').median()
    print(df)
                 l  median_-2days  median_+2days  median_-2days_r  median_+2days_r
    2021-01-01   1            1.0            2.0              1.0              2.0
    2021-01-02   2            1.5            2.5              1.5              2.5
    2021-01-03   3            2.0            4.0              2.0              4.0
    2021-01-05   5            4.0            5.5              4.0              5.5
    2021-01-06   6            5.5            7.0              5.5              7.0
    2021-01-08   8            7.0            9.0              7.0              9.0
    2021-01-09   9            8.5            9.5              8.5              9.5
    2021-01-10  10            9.0           10.0              9.0             10.0
    

    编辑:根据给出的重复索引和数据大小的规范,您可以尝试每个数据帧

    #sample data
    np.random.seed(10)
    df_ = pd.DataFrame(
            index=np.tile(np.random.choice(
                             pd.date_range('2021-03-03','2021-03-04',freq='T'),
                             size=1000, replace=False), 
                          5), 
            data={'l':range(5000)}
            ).sort_index()
    #your method for reference
    df_['median_func'] = rolling_median_irregular(df_["l"], left=pd.Timedelta(minutes=15), 
                                                  right=pd.Timedelta(minutes=0))
    #using concat
    df_['median_concat'] = (
        pd.concat([df_[['l']].set_index(df_.index+pd.Timedelta(minutes=i)) 
                                                  #here put -i for other direction
                   for i in range(0,16)])
          .groupby(level=0)
          .median()
        )
    

    所以你得到

    print(df_.head(12))
                            l  median_concat  median_func
    2021-03-03 00:01:00  3357         2357.0       2357.0
    2021-03-03 00:01:00   357         2357.0       2357.0
    2021-03-03 00:01:00  4357         2357.0       2357.0
    2021-03-03 00:01:00  1357         2357.0       2357.0
    2021-03-03 00:01:00  2357         2357.0       2357.0
    2021-03-03 00:03:00  3903         2630.0       2630.0
    2021-03-03 00:03:00   903         2630.0       2630.0
    2021-03-03 00:03:00  2903         2630.0       2630.0
    2021-03-03 00:03:00  1903         2630.0       2630.0
    2021-03-03 00:03:00  4903         2630.0       2630.0
    2021-03-03 00:06:00  2505         2505.0       2505.0
    2021-03-03 00:06:00  3505         2505.0       2505.0
    

    在一段时间内,我的速度提高了大约 80 倍

    %timeit rolling_median_irregular(df_["l"], left=pd.Timedelta(minutes=15), right=pd.Timedelta(minutes=0))
    # 2.12 s ± 74.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    %timeit pd.concat([df_[['l']].set_index(df_.index+pd.Timedelta(minutes=i)) for i in range(0,16)]).groupby(level=0).median()
    #25.1 ms ± 1.38 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    【讨论】:

    • 看起来很有希望。所以似乎诀窍是反转系列以获得接下来两天的中位数。
    • @user7431005 是的,这是诀窍,并且由于索引对齐,无需在获得正确日期之后反转系列。除非您在索引中有重复的日期
    • 不幸的是,情况就是这样,我在那里重复了日期。倒车不是问题,但熊猫滚动没有正确考虑重复的日期。在我的示例中,如果我将第二个索引更改为 2021-01-01(与第一个索引相同),则第一个滚动中位数不正确 - 我将不得不考虑解决此问题...
    • @user7431005 数据有多大,有多少天,你打算做几天以上的窗口吗?
    • 感谢您的额外测试。我会根据你的建议实施它
    【解决方案2】:

    是的,你正在重新发明轮子。

    您只需要在pd.rolling 中指定window= '2D' 参数即可滚动固定时间段,例如两天(相对于固定数量的观察)

    【讨论】:

    • 我试过了,但现在我怎么能说它应该只使用前两天或只使用后两天呢? (left/right 在我的代码中?)
    • 不,结果仍然不同。 df["l"].rolling("-2D").median() 第二天返回 2.0。对于我的"median_-2days" 案例,正确的值为1.5
    • 您可以调整关闭窗口的位置(左、右)和其他参数以匹配您的想法。 pandas.pydata.org/pandas-docs/version/0.23.4/generated/…
    猜你喜欢
    • 2023-04-08
    • 2012-05-20
    • 2017-03-17
    • 2022-01-12
    • 1970-01-01
    • 2017-04-27
    • 1970-01-01
    • 2013-10-09
    • 1970-01-01
    相关资源
    最近更新 更多