【问题标题】:Maximum value from previous row based on rolling period pandas基于滚动周期熊猫的上一行的最大值
【发布时间】:2020-11-19 04:10:58
【问题描述】:

我的数据集如下:


data = pd.DataFrame({
        'ID':  ['27459', '27459', '27459', '27459', '27459', '27459', '27459', '48002', '48002', '48002'],
        'Invoice_Date': ['2020-06-26', '2020-06-29', '2020-06-30', '2020-07-14', '2020-07-25', 
                         '2020-07-30', '2020-08-02', '2020-05-13', '2020-06-20', '2020-06-28'],
        'Payment_Term': [7,8,3,6,4,7,8,5,3,6],
        'Payment_Date': ['2020-07-05', '2020-07-05','2020-07-03', '2020-07-21', '2020-07-31', 
                         '2020-08-15', '2020-08-22', '2020-06-16', '2020-06-23', '2020-07-05'],
        'Due_Date': ['2020-07-03', '2020-07-07', '2020-07-03', '2020-07-20', '2020-07-29', 
                         '2020-08-06', '2020-08-10', '2020-05-18', '2020-06-23', '2020-07-04'],
        'Delay': [2,-2,0,1,2,9,12,29,0,1],
        'Difference_Date': [0,3,1,14,11,5,3,0,38,8],
        })
data

我需要添加另一列 Max 以显示前一个 Delay 行的最大值。它将有另一个条件,即它应该是一个 30 天的滚动期。这意味着,对于当前行中的 Max,将在当前行 Invoice_Date 的 30 天内从前一行获取最大延迟。

想要的输出是:

ID     Invoice_Date Payment_Term  Payment_Date       Due_Date  Delay    Difference_Date           Max

27459    2020-06-26            7    2020-07-05     2020-07-03      2                  0             0
27459    2020-06-29            8    2020-07-05     2020-07-07     -2                  3             2
27459    2020-06-30            3    2020-07-03     2020-07-03      0                  1             2
27459    2020-07-14            6    2020-07-21     2020-07-20      1                  14            2  
27459    2020-07-25            4    2020-07-31     2020-07-29      2                  11            2
27459    2020-07-30            7    2020-08-15     2020-08-06      9                  5             2
27459    2020-08-02            8    2020-08-22     2020-08-10      12                 3             9
48002    2020-05-13            5    2020-06-16     2020-05-18      29                 0             0
48002    2020-06-20            3    2020-06-23     2020-06-23      0                  38           29
48002    2020-06-28            6    2020-07-05     2020-07-04      1                  8            29

【问题讨论】:

  • 这不是按发票日期订购的
  • 发票日期按ID排序
  • 我明白了。你也想按 id 分组吗?
  • 是的,需要按id分组
  • 看起来Max 的最后一个元素是错误的:29 不在 30 天的窗口内

标签: python pandas rolling-computation


【解决方案1】:

一种可能的方法:

data['Invoice_Date'] = pd.to_datetime(data['Invoice_Date'])
groups = data.groupby('ID')

for group_name, df_group in groups:
    for idx,row in df_group.iterrows():
        dt_range = pd.date_range(row['Invoice_Date'] - pd.to_timedelta(30, 'day'), row['Invoice_Date'])[:-1]
        data.loc[idx, 'max'] = df_group[df_group.Invoice_Date.isin(dt_range)].Delay.max()

print(data)

输出:

      ID Invoice_Date  Payment_Term Payment_Date    Due_Date  Delay    Difference_Date  max  
0  27459   2020-06-26             7   2020-07-05  2020-07-03      2                  0  NaN  
1  27459   2020-06-29             8   2020-07-05  2020-07-07     -2                  3  2.0  
2  27459   2020-06-30             3   2020-07-03  2020-07-03      0                  1  2.0  
3  27459   2020-07-14             6   2020-07-21  2020-07-20      1                 14  2.0  
4  27459   2020-07-25             4   2020-07-31  2020-07-29      2                 11  2.0  
5  27459   2020-07-30             7   2020-08-15  2020-08-06      9                  5  2.0  
6  27459   2020-08-02             8   2020-08-22  2020-08-10     12                  3  9.0  
7  48002   2020-05-13             5   2020-06-16  2020-05-18     29                  0  NaN  
8  48002   2020-06-20             3   2020-06-23  2020-06-23      0                 38  NaN  
9  48002   2020-06-28             6   2020-07-05  2020-07-04      1                  8  0.0

您可以使用 data.fillna(0) 填充 NaN。请注意 ID“48002”的第一个值是 NaN,因为之前的值不在 30 天范围内。

【讨论】:

    【解决方案2】:

    您可以使用rolling 方法仅对一些过去的元素进行操作。但是,日期应该是单调的(升序或降序),这意味着日期应该被排序。

    您可以尝试以下方法:

    df['Invoice_Date'] = pd.to_datetime(df['Invoice_Date'])
    df.set_index('Invoice_Date', inplace=True)
    df.sort_index(inplace=True)
    
    df['max'] = df.groupby('ID')['Delay'].transform(lambda x: x.rolling('30D', closed='left').max())
    

    编辑:正如@Cainã 所建议的,包含groupby 以保证此过程对每个唯一的ID 单独完成

    需要closed 参数来指定不应包括当前日期。

    新的dataframe如下(这里只按Invoice_Date排序)

                     ID  Delay  Max
    Invoice_Date                   
    2020-05-13    48002     29  NaN
    2020-06-20    48002      0  NaN
    2020-06-26    27459      2  NaN
    2020-06-28    48002      1  0.0
    2020-06-29    27459     -2  2.0
    2020-06-30    27459      0  2.0
    2020-07-14    27459      1  2.0
    2020-07-25    27459      2  2.0
    2020-07-30    27459      9  2.0
    2020-08-02    27459     12  9.0
    

    如果我们也按ID 排序(通过运行df.reset_index().sort_values(['ID','Invoice_Date'])),我们得到:

                     ID  Delay  Max
    Invoice_Date                   
    2020-05-13    48002     29  NaN
    2020-06-20    48002      0  NaN
    2020-06-26    27459      2  NaN
    2020-06-28    48002      1  0.0
    2020-06-29    27459     -2  2.0
    2020-06-30    27459      0  2.0
    2020-07-14    27459      1  2.0
    2020-07-25    27459      2  2.0
    2020-07-30    27459      9  2.0
    2020-08-02    27459     12  9.0
    

    【讨论】:

    • 我认为这不符合要求-他想要排除当前行的30天
    • 对不起,你是对的,我错过了。我已经改正了
    • 太棒了@Ralubrusto!它避免了不必要的for loops +1。虽然它仍然不符合 OP 要求。考虑编辑您的答案以包含 groupby 声明。
    • 例如,只是出于好奇,例如:df['max'] = df.groupby('ID')['Delay'].transform(lambda x: x.rolling('30D', closed='left').max()) 加上df.reset_index().sort_values(['ID','Invoice_Date']) 会导致我的方法的确切输出。但这里的速度更快!
    • 很好的建议@CainãMaxCouto-Silva!我正在努力添加groupby('ID'),但这确实有效!
    【解决方案3】:

    df.rolling 可以完成工作并且可能是最高效的。

    df["Invoice_Date"] = df.Invoice_Date.astype("datetime64")    
    df["Max"] = df.groupby("ID").rolling("30d", on="Invoice_Date", closed="left").Delay.max().values
    

    结果:

          ID Invoice_Date  Payment_Term Payment_Date    Due_Date  Delay  Difference_Date  Max
    0  27459   2020-06-26             7   2020-07-05  2020-07-03      2                0  NaN
    1  27459   2020-06-29             8   2020-07-05  2020-07-07     -2                3  2.0
    2  27459   2020-06-30             3   2020-07-03  2020-07-03      0                1  2.0
    3  27459   2020-07-14             6   2020-07-21  2020-07-20      1               14  2.0
    4  27459   2020-07-25             4   2020-07-31  2020-07-29      2               11  2.0
    5  27459   2020-07-30             7   2020-08-15  2020-08-06      9                5  2.0
    6  27459   2020-08-02             8   2020-08-22  2020-08-10     12                3  9.0
    7  48002   2020-05-13             5   2020-06-16  2020-05-18     29                0  NaN
    8  48002   2020-06-20             3   2020-06-23  2020-06-23      0               38  NaN
    9  48002   2020-06-28             6   2020-07-05  2020-07-04      1                8  0.0
    

    【讨论】:

    • 将所有内容总结为两行非常优雅。但这会给我带来一个错误(与this issue 相同)。你用的是什么版本的pandas
    • pd.__version__ -> 1.1.4
    • 感谢@Ralubrusto :) 我实际上认为它有点难以理解。不过可能性能不错!
    • 我使用的是1.0.5。升级到最新版本使其完美运行。做得好! :)
    • 很好的答案。我仍然不确定.rolling() 在使用groupby 时是如何在后台工作的(关于数据顺序),但我添加了一个额外的行,其中 27459 作为 ID,它似乎弄乱了结果。所以我建议在应用之前对组日期进行排序(例如df = df.sort_values(['ID','Invoice_Date']))。总之,干得好!
    猜你喜欢
    • 2017-06-27
    • 2017-08-27
    • 2019-10-06
    • 2020-04-24
    • 1970-01-01
    • 1970-01-01
    • 2017-10-05
    • 2014-10-04
    • 1970-01-01
    相关资源
    最近更新 更多