【问题标题】:Pandas fill missing dates and values simultaneously for each groupPandas 为每个组同时填充缺失的日期和值
【发布时间】:2021-07-23 14:28:45
【问题描述】:

我有一个数据框 (mydf),其中每个组的日期按月频率显示,如下所示:

Dt          Id  Sales
2021-03-01  B   2
2021-04-01  B   42
2021-05-01  B   20
2021-06-01  B   4
2020-10-01  A   47
2020-11-01  A   67
2020-12-01  A   46

我想为每个组填写 dt,直到从 Id 日期开始的日期列中的最大日期,同时为销售列填写 0。因此,每个组都在自己的开始日期开始,但在相同的结束日期结束。

所以例如ID=A 将从 2020-10-01 开始一直到 2021-06-03,填充日期的值为 0。

所以输出将是

Dt          Id  Sales
2021-03-01  B   2
2021-04-01  B   42
2021-05-01  B   20
2021-06-01  B   4
2020-10-01  A   46
2020-11-01  A   47
2020-12-01  A   67
2021-01-01  A   0
2021-02-01  A   0
2021-03-01  A   0
2021-04-01  A   0
2021-05-01  A   0
2021-06-01  A   0

我尝试过重新索引,但我不想手动添加日期范围,而是想使用组中的日期。

我的代码是:

f = lambda x: x.reindex(pd.date_range('2020-10-01', '2021-06-01', freq='MS', name='Dt'))
mydf = mydf.set_index('Dt').groupby('Id').apply(f).drop('Id', axis=1).fillna(0)
mydf = mydf.reset_index()

【问题讨论】:

  • 你的例子不是很清楚。您为 B 复制了“2021-06-01”,并且示例与文本描述不匹配(A 没有重新采样)。你能改进这个问题吗?
  • @mozway 输出中的 Id 被错误地复制了。现在清楚了吗?谢谢

标签: python pandas


【解决方案1】:

使用pd.MultiIndex 和列表理解的替代方法:

s = (pd.MultiIndex.from_tuples([[x, d]
      for x, y in df.groupby("Id")["Dt"]
      for d in pd.date_range(min(y), max(df["Dt"]), freq="MS")], names=["Id", "Dt"]))

print (df.set_index(["Id", "Dt"]).reindex(s, fill_value=0).reset_index())

【讨论】:

    【解决方案2】:

    我们试试吧:

    1. 使用groupby.min 获取每组的最小值
    2. 在聚合分钟中添加一个名为max 的新列,该列使用Series.maxDt 上存储来自帧的最大值
    3. 根据minmax 值为每个组创建单独的date_range
    4. Series.explode 放入行中以具有表示新索引的 DataFrame。
    5. 创建一个MultiIndex.from_framereindex 的DataFrame。
    6. reindexmidx 并设置 fillvalue=0
    # Get Min Per Group
    dates = mydf.groupby('Id')['Dt'].min().to_frame(name='min')
    # Get max from Frame
    dates['max'] = mydf['Dt'].max()
    
    # Create MultiIndex with separate Date ranges per Group
    midx = pd.MultiIndex.from_frame(
        dates.apply(
            lambda x: pd.date_range(x['min'], x['max'], freq='MS'), axis=1
        ).explode().reset_index(name='Dt')[['Dt', 'Id']]
    )
    
    # Reindex
    mydf = (
        mydf.set_index(['Dt', 'Id'])
            .reindex(midx, fill_value=0)
            .reset_index()
    )
    

    mydf:

               Dt Id  Sales
    0  2020-10-01  A     47
    1  2020-11-01  A     67
    2  2020-12-01  A     46
    3  2021-01-01  A      0
    4  2021-02-01  A      0
    5  2021-03-01  A      0
    6  2021-04-01  A      0
    7  2021-05-01  A      0
    8  2021-06-01  A      0
    9  2021-03-01  B      2
    10 2021-04-01  B     42
    11 2021-05-01  B     20
    12 2021-06-01  B      4
    

    数据帧:

    import pandas as pd
    
    mydf = pd.DataFrame({
        'Dt': ['2021-03-01', '2021-04-01', '2021-05-01', '2021-06-01', '2020-10-01',
               '2020-11-01', '2020-12-01'],
        'Id': ['B', 'B', 'B', 'B', 'A', 'A', 'A'],
        'Sales': [2, 42, 20, 4, 47, 67, 46]
    })
    mydf['Dt'] = pd.to_datetime(mydf['Dt'])
    

    【讨论】:

    • 感谢您提供解决方案。但是,我不想以数据框的最早日期开始 Id=B 的日期。无论 ID 是什么,我都想保持开始日期不变并向前填写直到最长日期结束。抱歉,如果我之前不清楚。
    • 每组从各自的开始日期开始。但是所有组都在相同的结束日期结束?
    【解决方案3】:

    这是一种不同的方法:

    from itertools import product
    
    # compute the min-max date range
    date_range = pd.date_range(*mydf['Dt'].agg(['min', 'max']), freq='MS', name='Dt')
    
    # make MultiIndex per group, keep only values above min date per group
    idx = pd.MultiIndex.from_tuples([e for Id,Dt_min in mydf.groupby('Id')['Dt'].min().items()
                                       for e in list(product(date_range[date_range>Dt_min],
                                                             [Id]))
                                    ])
    
    # concatenate the original dataframe and the missing indexes
    mydf = mydf.set_index(['Dt', 'Id'])
    mydf = pd.concat([mydf,
                      mydf.reindex(idx.difference(mydf.index)).fillna(0)]
                    ).sort_index(level=1).reset_index()
    
    mydf
    

    输出:

               Dt Id  Sales
    0  2020-10-01  A   47.0
    1  2020-11-01  A   67.0
    2  2020-12-01  A   46.0
    3  2021-01-01  A    0.0
    4  2021-02-01  A    0.0
    5  2021-03-01  A    0.0
    6  2021-04-01  A    0.0
    7  2021-05-01  A    0.0
    8  2021-06-01  A    0.0
    9  2021-03-01  B    2.0
    10 2021-04-01  B   42.0
    11 2021-05-01  B   20.0
    12 2021-06-01  B    4.0
    

    【讨论】:

      【解决方案4】:

      我们可以使用pyjanitor 中的complete 函数来暴露缺失值:

      Dt 转换为日期时间:

       df['Dt'] = pd.to_datetime(df['Dt'])
      

      通过pd.date_range 创建Dt 到新值的映射,并将频率设置为每月开始(MS):

       max_time = df.Dt.max()
      
       new_values = {"Dt": lambda df:pd.date_range(df.min(), max_time, freq='1MS')}
      
      # pip install pyjanitor
      import janitor
      import pandas as pd
      df.complete([new_values], by='Id').fillna(0)
      
      
         Id         Dt  Sales
      0   A 2020-10-01   47.0
      1   A 2020-11-01   67.0
      2   A 2020-12-01   46.0
      3   A 2021-01-01    0.0
      4   A 2021-02-01    0.0
      5   A 2021-03-01    0.0
      6   A 2021-04-01    0.0
      7   A 2021-05-01    0.0
      8   A 2021-06-01    0.0
      9   B 2021-03-01    2.0
      10  B 2021-04-01   42.0
      11  B 2021-05-01   20.0
      12  B 2021-06-01    4.0
      

      只坚持 Pandas,我们可以将 applygroupbyreindex 结合起来;幸运的是,Dt 是独一无二的,所以我们可以安全地重新索引:

      (df
       .set_index('Dt')
       .groupby('Id')
       .apply(lambda df: df.reindex(pd.date_range(df.index.min(), 
                                                  max_time, 
                                                  freq='1MS'), 
                                    fill_value = 0)
                                    )
       .drop(columns='Id')
       .rename_axis(['Id', 'Dt'])
       .reset_index())
       
         Id         Dt  Sales
      0   A 2020-10-01     47
      1   A 2020-11-01     67
      2   A 2020-12-01     46
      3   A 2021-01-01      0
      4   A 2021-02-01      0
      5   A 2021-03-01      0
      6   A 2021-04-01      0
      7   A 2021-05-01      0
      8   A 2021-06-01      0
      9   B 2021-03-01      2
      10  B 2021-04-01     42
      11  B 2021-05-01     20
      12  B 2021-06-01      4
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-02-12
        • 1970-01-01
        • 2020-04-08
        • 1970-01-01
        • 2020-01-31
        • 2020-11-18
        • 2021-07-22
        • 2022-08-15
        相关资源
        最近更新 更多