【问题标题】:Add missing dates to pandas dataframe将缺失的日期添加到 pandas 数据框
【发布时间】:2013-10-19 21:45:18
【问题描述】:

我的数据在给定日期可以有多个事件,也可以在某个日期没有事件。我接受这些事件,按日期计数并绘制它们。但是,当我绘制它们时,我的两个系列并不总是匹配。

idx = pd.date_range(df['simpleDate'].min(), df['simpleDate'].max())
s = df.groupby(['simpleDate']).size()

在上面的代码中,idx 变成了 30 个日期的范围。 2013 年 9 月 1 日至 2013 年 9 月 30 日 但是,S 可能只有 25 或 26 天,因为在给定日期没有发生任何事件。然后我得到一个 AssertionError,因为当我尝试绘图时大小不匹配:

fig, ax = plt.subplots()    
ax.bar(idx.to_pydatetime(), s, color='green')

解决这个问题的正确方法是什么?我想从 IDX 中删除没有值的日期还是(我宁愿这样做)将缺失日期添加到系列中,计数为 0。我宁愿有一个完整的图表30 天,0 值。如果这种方法是正确的,关于如何开始的任何建议?我需要某种动态的reindex 函数吗?

这是 S (df.groupby(['simpleDate']).size()) 的 sn-p,请注意没有 04 和 05 的条目。

09-02-2013     2
09-03-2013    10
09-06-2013     5
09-07-2013     1

【问题讨论】:

    标签: python date plot pandas dataframe


    【解决方案1】:

    你可以使用Series.reindex:

    import pandas as pd
    
    idx = pd.date_range('09-01-2013', '09-30-2013')
    
    s = pd.Series({'09-02-2013': 2,
                   '09-03-2013': 10,
                   '09-06-2013': 5,
                   '09-07-2013': 1})
    s.index = pd.DatetimeIndex(s.index)
    
    s = s.reindex(idx, fill_value=0)
    print(s)
    

    产量

    2013-09-01     0
    2013-09-02     2
    2013-09-03    10
    2013-09-04     0
    2013-09-05     0
    2013-09-06     5
    2013-09-07     1
    2013-09-08     0
    ...
    

    【讨论】:

    • reindex 是一个了不起的功能。它可以 (1) 对现有数据重新排序以匹配一组新标签,(2) 在以前不存在标签的地方插入新行,(3) 为缺少的标签填充数据,(包括通过向前/向后填充)(4) 选择行按标签!
    • @unutbu 这也回答了我的部分问题,谢谢!但是想知道您是否知道如何动态创建包含有事件的日期的列表?
    • 不过,reindex 存在一个问题(或错误):它不适用于 1970 年 1 月 1 日之前的日期,因此在这种情况下 df.resample() 可以完美运行。
    • 您可以使用它代替 idx 来跳过手动输入开始和结束日期:idx = pd.date_range(df.index.min(), df.index.max())
    • 在此处删除文档链接,以节省您的搜索:pandas.pydata.org/pandas-docs/stable/reference/api/…
    【解决方案2】:

    这是一种将缺失日期填充到数据框中的好方法,您可以选择 fill_valuedays_back 来填写,并按排序顺序 (date_order) 对数据框进行排序:

    def fill_in_missing_dates(df, date_col_name = 'date',date_order = 'asc', fill_value = 0, days_back = 30):
    
        df.set_index(date_col_name,drop=True,inplace=True)
        df.index = pd.DatetimeIndex(df.index)
        d = datetime.now().date()
        d2 = d - timedelta(days = days_back)
        idx = pd.date_range(d2, d, freq = "D")
        df = df.reindex(idx,fill_value=fill_value)
        df[date_col_name] = pd.DatetimeIndex(df.index)
    
        return df
    

    【讨论】:

      【解决方案3】:

      一个问题是如果有重复值,reindex 将失败。假设我们正在处理带时间戳的数据,我们希望按日期对其进行索引:

      df = pd.DataFrame({
          'timestamps': pd.to_datetime(
              ['2016-11-15 1:00','2016-11-16 2:00','2016-11-16 3:00','2016-11-18 4:00']),
          'values':['a','b','c','d']})
      df.index = pd.DatetimeIndex(df['timestamps']).floor('D')
      df
      

      产量

                  timestamps             values
      2016-11-15  "2016-11-15 01:00:00"  a
      2016-11-16  "2016-11-16 02:00:00"  b
      2016-11-16  "2016-11-16 03:00:00"  c
      2016-11-18  "2016-11-18 04:00:00"  d
      

      由于2016-11-16日期重复,尝试重新索引:

      all_days = pd.date_range(df.index.min(), df.index.max(), freq='D')
      df.reindex(all_days)
      

      失败:

      ...
      ValueError: cannot reindex from a duplicate axis
      

      (这意味着索引有重复,而不是它本身就是一个重复)

      相反,我们可以使用.loc 来查找范围内所有日期的条目:

      df.loc[all_days]
      

      产量

                  timestamps             values
      2016-11-15  "2016-11-15 01:00:00"  a
      2016-11-16  "2016-11-16 02:00:00"  b
      2016-11-16  "2016-11-16 03:00:00"  c
      2016-11-17  NaN                    NaN
      2016-11-18  "2016-11-18 04:00:00"  d
      

      fillna 可用于列系列,如有需要,可填空。

      【讨论】:

      • 如果日期列包含BlanksNULLS,您知道该怎么做吗? df.loc[all_days] 在这种情况下不起作用。
      • 将 list-likes 传递给 .loc 或 [] 并且缺少任何标签将在未来引发 KeyError,您可以使用 .reindex() 作为替代方案。请参阅此处的文档:pandas.pydata.org/pandas-docs/stable/…
      【解决方案4】:

      更快的解决方法是使用.asfreq()。这不需要创建一个新的索引来调用.reindex()

      # "broken" (staggered) dates
      dates = pd.Index([pd.Timestamp('2012-05-01'), 
                        pd.Timestamp('2012-05-04'), 
                        pd.Timestamp('2012-05-06')])
      s = pd.Series([1, 2, 3], dates)
      
      print(s.asfreq('D'))
      2012-05-01    1.0
      2012-05-02    NaN
      2012-05-03    NaN
      2012-05-04    2.0
      2012-05-05    NaN
      2012-05-06    3.0
      Freq: D, dtype: float64
      

      【讨论】:

      • 我非常喜欢这种方法;您不必调用date_range,因为它隐式使用第一个和最后一个索引作为开始和结束(这是您几乎总是想要的)。
      • 非常干净和专业的方法。之后也可以很好地使用插值。
      • 我支持这个。这也是在合并两个不同索引长度的数据帧之前使用的好方法,其中连接、合并等几乎总是会导致错误,例如一列充满 NaN。
      • 感谢您的回答,但我还有一个问题。鉴于我想从日期 x-x-x 开始并在日期 y-y-y 和我的数据集's' 上结束,我有日期 e-e-e 到 f-f-f,它们介于日期 x-x-x 和 y-y-y 之间。使用“asfreq”如何将数据集“s”上的日期从 x-x-x 填充到 y-y-y?我在文档上没有找到。谢谢
      【解决方案5】:

      另一种方法是resample,除了缺少日期外,它还可以处理重复日期。例如:

      df.resample('D').mean()
      

      resample 是一个类似于groupby 的延迟操作,因此您需要在它之后进行另一个操作。在这种情况下,mean 效果很好,但您也可以使用许多其他 pandas 方法,例如 maxsum 等。

      这是原始数据,但有一个额外的“2013-09-03”条目:

                   val
      date           
      2013-09-02     2
      2013-09-03    10
      2013-09-03    20    <- duplicate date added to OP's data
      2013-09-06     5
      2013-09-07     1
      

      结果如下:

                   val
      date            
      2013-09-02   2.0
      2013-09-03  15.0    <- mean of original values for 2013-09-03
      2013-09-04   NaN    <- NaN b/c date not present in orig
      2013-09-05   NaN    <- NaN b/c date not present in orig
      2013-09-06   5.0
      2013-09-07   1.0
      

      我将缺失的日期保留为 NaN 以明确其工作原理,但您可以添加 fillna(0) 以按照 OP 的要求将 NaN 替换为零,或者使用类似 interpolate() 的内容填充非零值基于相邻的行。

      【讨论】:

        【解决方案6】:

        您始终可以使用 DataFrame.merge(),利用从“所有日期”数据帧到“缺失日期”数据帧的左连接。下面的例子。

        ## example DataFrame with missing dates between min(date) and max(date)
        missing_df = pd.DataFrame({
            'date':pd.to_datetime([
                '2022-02-10'
                ,'2022-02-11'
                ,'2022-02-14'
                ,'2022-02-14'
                ,'2022-02-24'
                ,'2022-02-16'
            ])
            ,'value':[10,20,5,10,15,30]
        })
        
        ## first create a DataFrame with all dates between specified start<-->end using pd.date_range()
        all_dates = pd.DataFrame(pd.date_range(df['date'].min(), df['date'].max()), columns=['date'])
        
        ## from the all_dates DataFrame, left join onto the DataFrame with missing dates
        new_df = all_dates.merge(right=missing_df, how='left', on='date')
        
        new_df
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-08-11
          • 2021-04-06
          • 2021-06-17
          • 1970-01-01
          • 1970-01-01
          • 2021-06-28
          • 2020-02-16
          相关资源
          最近更新 更多