【问题标题】:Python: Compare data against the 95th percentile of a running window datasetPython:将数据与正在运行的窗口数据集的第 95 个百分位进行比较
【发布时间】:2021-03-19 18:15:42
【问题描述】:

我有一个包含数千行但只有 2 列的大型 DataFrame。两列的格式如下:

Dt Val
2020-01-01 10.5
2020-01-01 11.2
2020-01-01 10.9
2020-01-03 11.3
2020-01-05 12.0

第一列是日期,第二列是值。对于每个日期,可能有零个、一个或多个值。

我需要做的是:根据刚刚过去的 30 天计算第 95 个百分位,并查看当前值是高于还是低于第 95 个百分位值。但是,过去 30 天内必须至少有 50 个可用值。

例如,如果一条记录的日期为“2020-12-01”,值为“10.5”,那么我需要先查看在 2020-11-01 到 2020-11- 的日期范围内有多少可用值30.如果在该日期范围内至少有 50 个可用值,那么我将要计算这些值的第 95 个百分位,并将 10.5 与该值进行比较。如果 10.5 大于第 95 个百分位值,则该记录的结果为“高于阈值”。如果 10.5 小于第 95 个百分位值,则该记录的结果为“低于阈值”。如果在 2020 年 11 月 1 日到 2020 年 11 月 30 日这一日期范围内的值少于 50 个,则该记录的结果为“数据不足”。

如果可能,我想避免运行循环,因为从资源和时间的角度来看,循环遍历数千条记录以逐条处理它们可能会很昂贵。我希望有人可以在这里建议一个简单的(r)python/pandas 解决方案。

【问题讨论】:

    标签: python pandas dataframe large-data


    【解决方案1】:

    对 DatetimeIndex 使用滚动来获取过去 30 天内可用值的数量和第 95 个百分位数。这是一个 3 天滚动窗口的示例:

    import datetime
    import pandas as pd
    
    df = pd.DataFrame({'val':[1,2,3,4,5,6]},
                      index = [datetime.date(2020,10,1), datetime.date(2020,10,1), datetime.date(2020,10,2),
                               datetime.date(2020,10,3), datetime.date(2020,10,3), datetime.date(2020,10,4)])
    
    df.index = pd.DatetimeIndex(df.index)
    
    df['number_of_values'] = df.rolling('3D').count()
    df['rolling_percentile'] = df.rolling('3D')['val'].quantile(0.9, interpolation='nearest')
    

    那么你可以简单地进行比较:

    # Above Threshold
    (df['val']>df['rolling_percentile'])&(df['number_of_values']>=50)
    
    # Below Threshold
    (df['val']>df['rolling_percentile'])&(df['number_of_values']>=50)
    
    # Insufficient Data
    df['number_of_values']<50
    

    要删除当前日期,close 参数在一天内不能用于超过一行,所以可以使用滚动应用:

    def f(x, metric):
        x = x[x.index!=x.index[-1]]
        if metric == 'count':
            return len(x)
        elif metric == 'percentile':
            return x.quantile(0.9, interpolation='nearest')
        else:
            return np.nan
    
    df = pd.DataFrame({'val':[1,2,3,4,5,6]},
                      index = [datetime.date(2020,10,1), datetime.date(2020,10,1), datetime.date(2020,10,2),
                               datetime.date(2020,10,3), datetime.date(2020,10,3), datetime.date(2020,10,4)])
    df.index = pd.DatetimeIndex(df.index)
    df['count'] = df.rolling('3D')['val'].apply(f, args = ('count',))
    df['percentile'] = df.rolling('3D')['val'].apply(f, args = ('percentile',))
    
              val   count   percentile
    2020-10-01  1   0.0  NaN
    2020-10-01  2   0.0  NaN
    2020-10-02  3   2.0  2.0
    2020-10-03  4   3.0  3.0
    2020-10-03  5   3.0  3.0
    2020-10-04  6   3.0  5.0
    

    【讨论】:

    • 你打败了我 :)。我认为您应该将closed='left' 添加到rolling 参数(OP 想要排除当前日期)?
    • 感谢您的代码。我试过了,它通常可以工作,但它仍然有一个小问题。即使在滚动参数中使用closed='left',它也不会排除所有当前日期的值。它只排除当前值。例如,在提供的代码中,第一个 2020-10-03 记录的 number_of_values 为 3,但第二个 2020-10-03 记录的 number_of_values 为 4。这是因为第二个 2020-10-03 记录包含第一个 2020- 10-03 记录。有没有办法让滚动窗口排除所有当前日期记录?
    • @RichardWong 这是一个很好的观点。我想不出一种简单的方法,因为您需要百分位数数据,这不允许任何尝试聚合数据以获取每个日期的一行。 for 循环始终是一种解决方案,它几乎可以与 rolling() 一样有效。或者您可以使用滚动来聚合每个日期的计数并使用 for 循环来解析百分位部分。
    • @RichardWong 查看上面的更新版本
    • @ZLi 更新版本完美运行。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2017-09-04
    • 2012-09-12
    • 1970-01-01
    • 1970-01-01
    • 2015-08-08
    • 1970-01-01
    • 1970-01-01
    • 2013-11-09
    相关资源
    最近更新 更多