在Pandas数据框中添加缺失日期

238

我的数据在某个日期上可以有多个事件,也可能没有任何事件。我将这些事件按日期计数并绘制图表。然而,在绘制图表时,我的两个系列不总是匹配的。

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

在上述代码中,idx 变成了一个包含30个日期的范围,例如从 2013年09月01日到2013年09月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()),请注意04和05没有条目。
09-02-2013     2
09-03-2013    10
09-06-2013     5
09-07-2013     1
7个回答

411

您可以使用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
...

39
reindex 是一个非常棒的函数。它可以 (1) 重新排序现有数据以匹配新的标签集,(2) 在之前没有标签的位置插入新行,(3) 填充缺失标签的数据(包括向前/向后填充),(4) 可以通过标签选择行! - unutbu
2
然而,reindex 存在一个问题(或 bug):它无法处理 1970 年 1 月 1 日之前的日期,因此在这种情况下,df.resample() 可以完美地解决问题。 - Sergey Gulbin
6
为了避免手动输入开始和结束日期,您可以使用以下代码替代idx:idx = pd.date_range(df.index.min(), df.index.max()) - Reveille
2
在这里放上文档链接,以节省您的搜索时间:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.reindex.html - Harm
2
重建索引至少不再起作用 - intergallactic
显示剩余4条评论

96

更快的解决方法是使用.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

3
我更喜欢这种方法;因为它隐式地使用第一个和最后一个索引作为起始和结束,所以你不需要调用date_range(这通常是你想要的)。 - Michael Hays
非常干净和专业的方法。在之后使用插值也能很好地工作。 - msarafzadeh
我赞同这个观点。在合并两个索引长度不同的数据框之前,这也是一个很好的方法,因为连接、合并等几乎总会导致出现包含NaN值的列等错误。 - user3661992
2
谢谢您的回答,但我还有一个问题。假设我想从日期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?我在文档中没有找到相关内容。谢谢 - Catarina Nogueira
是的,我使用了这种方法在数据框中插入NaN以便在matplotlib绘图之前处理缺失日期。 - PerseP

38

另一种方法是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)来将NaN替换为零,如OP所要求,或者使用类似interpolate()的方法基于相邻行填充非零值。


37

问题在于如果有重复的值,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 可以用于对列序列进行填充空值操作,如果需要的话。


如果日期列包含“空白”或“NULLS”,有什么解决方法吗?在这种情况下,df.loc[all_days]将无法工作。 - Furqan Hashim
2
将类似列表的对象传递给.loc或[],如果存在任何缺失标签,将来会引发KeyError错误。您可以使用.reindex()作为替代方法。请参阅此处的文档: https://pandas.pydata.org/pandas-docs/stable/indexing.html#deprecate-loc-reindex-listlike - Dmitrii Magas

10
这里有一种很好的方法可以填补数据框中缺失的日期,您可以选择填充值填补天数和排序方式(日期排序)来对数据框进行排序:
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

2
s.asfreq('D').interpolate().asfreq('Q')

2
您可以始终使用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(missing_df['date'].min(), missing_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')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接