【问题标题】:Pandas fill in missing date within each group with information in the previous row熊猫用上一行的信息填写每组中缺失的日期
【发布时间】:2018-10-31 14:55:55
【问题描述】:

this one 类似的问题,但有一些修改:

我们应该只填写该组的最小值和最大值之间的日期,而不是填写整个列的最小和最大日期之间的每个组的缺失日期,然后输出每组最后一行的数据框

可重现的例子:

x = pd.DataFrame({'dt': ['2016-01-01','2016-01-03', '2016-01-04','2016-01-01','2016-01-01','2016-01-04']
                    ,'amount': [10.0,30.0,40.0,78.0,80.0,82.0]
                    , 'sub_id': [1,1,1,2,2,2]
                    })

视觉上:

            dt   sub_id   amount
0   2016-01-01        1     10.0
1   2016-01-03        1     30.0
2   2016-01-04        1     40.0
3   2017-01-01        2     78.0
4   2017-01-01        2     80.0
5   2017-01-04        2     82.0

我需要的输出:

            dt   sub_id   amount
0   2016-01-01        1     10.0
1   2016-01-02        1     10.0
2   2016-01-03        1     30.0
3   2016-01-04        1     40.0
4   2017-01-01        2     80.0
5   2017-01-02        2     80.0
6   2017-01-03        2     80.0
7   2017-01-04        2     82.0

我们按 dt 和 sub_id 分组。如您所见,在 sub_id=1 中,为 2016-01-02 添加了一行,并且由于前一行为 10.0,因此金额被估算为 10.0(假设数据预先排序以启用此功能)。对于 2017-01-02 和 2017-01-03 添加了 sub_id=2 行,金额为 80.0,因为这是该日期之前的最后一行。 2017-01-01 的第一行也被删除了,因为我们只想保留每个日期和 sub_id 的最后一行。

寻找最有效的方法来执行此操作,因为真实数据有数百万行。我有一个使用 lambda 函数并将它们应用于 sub_id 组的当前方法,但我觉得我们可以做得更好。

谢谢!

【问题讨论】:

    标签: python pandas pandas-groupby


    【解决方案1】:

    通过使用resamplegroupby

    x.dt=pd.to_datetime(x.dt)
    x.set_index('dt').groupby('sub_id').apply(lambda x : x.resample('D').max().ffill()).reset_index(level=1)
    Out[265]: 
                   dt  amount  sub_id
    sub_id                           
    1      2016-01-01    10.0     1.0
    1      2016-01-02    10.0     1.0
    1      2016-01-03    30.0     1.0
    1      2016-01-04    40.0     1.0
    2      2016-01-01    80.0     2.0
    2      2016-01-02    80.0     2.0
    2      2016-01-03    80.0     2.0
    2      2016-01-04    82.0     2.0
    

    【讨论】:

    • 谢谢,这很干净,但我有一个类似的解决方案,因为它使用 apply,性能很慢。 piRSquared 的解决方案最适合我
    • 我收回上述评论。此解决方案与其他解决方案之间的性能差异是由于 resample('D').max() 步骤。先使用 drop duplicates 然后执行 asfreq('D') 更快
    【解决方案2】:

    以下内容对我有用,并且看起来非常有效,但我不能说它是否足够有效。它确实避免了 lambdas。

    我将您的数据称为df

    使用整个日期/子 ID 网格创建 base_df

    import pandas as pd
    from itertools import product
    
    base_grid = product(pd.date_range(df['dt'].min(), df['dt'].max(), freq='D'), list(range(df['sub_id'].min(), df['sub_id'].max() + 1, 1)))
    
    base_df = pd.DataFrame(list(base_grid), columns=['dt', 'sub_id'])
    

    df 获取每个 dt / sub_id 的最大值:

    max_value_df = df.loc[df.groupby(['dt', 'sub_id'])['amount'].idxmax()]
    max_value_df['dt']  = max_value_df['dt'].apply(pd.Timestamp)
    

    根据最大值合并 base_df:

    merged_df = base_df.merge(max_value_df, how='left', on=['dt', 'sub_id'])
    

    排序并向前填充最大值:

    merged_df = merged_df.sort_values(by=['sub_id', 'dt', 'amount'], ascending=True)
    merged_df['amount'] = merged_df.groupby(['sub_id'])['amount'].fillna(method='ffill')
    

    结果:

        dt  sub_id  amount
    0   2016-01-01  1   10.0
    2   2016-01-02  1   10.0
    4   2016-01-03  1   30.0
    6   2016-01-04  1   40.0
    1   2016-01-01  2   80.0
    3   2016-01-02  2   80.0
    5   2016-01-03  2   80.0
    7   2016-01-04  2   82.0
    

    【讨论】:

      【解决方案3】:

      当然是正确的日期:

      x.dt = pd.to_datetime(x.dt)
      

      然后这个:

      cols = ['dt', 'sub_id']
      
      pd.concat([
          d.asfreq('D').ffill(downcast='infer')
          for _, d in x.drop_duplicates(cols, keep='last')
                       .set_index('dt').groupby('sub_id')
      ]).reset_index()
      
                dt  amount  sub_id
      0 2016-01-01      10       1
      1 2016-01-02      10       1
      2 2016-01-03      30       1
      3 2016-01-04      40       1
      4 2016-01-01      80       2
      5 2016-01-02      80       2
      6 2016-01-03      80       2
      7 2016-01-04      82       2
      

      【讨论】:

      • 注意这个答案要求数据按日期排序才能开始!
      【解决方案4】:

      使用asfreq & groupby

      首先将dt 转换为datetime 并删除重复项

      然后对于每组 sub_id 使用 asfreq('D', method='ffill') 生成缺失日期和估算金额

      最后reset_index 位于amount 列上,因为有一个重复的sub_id 列以及索引。

      x.dt = pd.to_datetime(x.dt)
      x.drop_duplicates(
        ['dt', 'sub_id'], 'last'
      ).groupby('sub_id').apply(
        lambda x: x.set_index('dt').asfreq('D', method='ffill')
      ).amount.reset_index()
      
      # output:
      
         sub_id         dt  amount
      0       1 2016-01-01    10.0
      1       1 2016-01-02    10.0
      2       1 2016-01-03    30.0
      3       1 2016-01-04    40.0
      4       2 2016-01-01    80.0
      5       2 2016-01-02    80.0
      6       2 2016-01-03    80.0
      7       2 2016-01-04    82.0
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-24
        • 1970-01-01
        • 2017-12-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多