Pandas根据日期范围获得唯一的月度数据

时间:2018-01-12 23:05:12

标签: python pandas

我有类似以下数据框的内容:

d=pd.DataFrame()
d['id']=['a','a','a','b','b','c']
d['version_start']=['2017-01-01','2017-02-12','2017-03-25','2017-01-01','2017-6-15','2017-01-22']
d['version_end']=['2017-02-11','2017-03-24','2017-08-01','2017-06-14','2018-01-01','2018-01-01']
d['version_start']=pd.to_datetime(d.version_start)
d['version_end']=pd.to_datetime(d.version_end)
d['values']=[10,15,20,5,6,200]
print d
  id version_start version_end  values
0  a    2017-01-01  2017-02-11      10
1  a    2017-02-12  2017-03-24      15
2  a    2017-03-25  2017-08-01      20
3  b    2017-01-01  2017-06-14       5
4  b    2017-06-15  2018-01-01       6
5  c    2017-01-22  2018-01-01     200

版本开始和版本结束表示每个ID,该行可被视为有效的日期范围。例如,给定日期的总值将是该日期在版本开始和版本结束之间的记录。

我希望得到一组日期(2017年每个月的第一个月)“值”字段的总和。我可以按照以下步骤循环每个月:

df=pd.DataFrame()
for month in pd.date_range('2017-01-01','2018-01-01',freq='MS'):
    s = d[(d.version_start<=month)&(d.version_end>month)]
    s['month']=month
    s=s.set_index(['month','id'])[['values']]
    df=df.append(s)    

print df.groupby(level='month')['values'].sum()

2017-01-01     15
2017-02-01    215
2017-03-01    220
2017-04-01    225
2017-05-01    225
2017-06-01    225
2017-07-01    226
2017-08-01    206
2017-09-01    206
2017-10-01    206
2017-11-01    206
2017-12-01    206
Name: values, dtype: int64

是否有一个更优雅/更有效的解决方案,不需要循环这个日期列表?

2 个答案:

答案 0 :(得分:0)

d.version_start=d.version_start+ pd.offsets.MonthBegin(0)
d.version_end=d.version_end+ pd.offsets.MonthBegin(0)
d['New']=d[['version_start','version_end']].apply(lambda x : pd.date_range(start=x.version_start,end=x.version_end,freq='MS').tolist(),1)
d.set_index(['id','version_start','version_end','values']).New.apply(pd.Series).stack().reset_index('values').groupby(0)['values'].sum()



Out[845]: 
0
2017-01-01     15
2017-02-01    215
2017-03-01    230
2017-04-01    240
2017-05-01    225
2017-06-01    225
2017-07-01    231
2017-08-01    226
2017-09-01    206
2017-10-01    206
2017-11-01    206
2017-12-01    206
2018-01-01    206
Name: values, dtype: int64

答案 1 :(得分:0)

我一直认为应该有一种方式更优雅的方式来做到这一点,但是现在:

s = pd.Series(0, index=pd.date_range('2017-01-01','2018-01-01',freq='MS'))
for _id, start, end, values in d.itertuples(index=False):
    s[start:end] += values

这将返回正确的系列,并适用于任何系列。

如果您希望排除排除,快速解决方法是在for循环之前添加此行(仅当您使用version_end作为频率时才有效) :

'MS'

我认为使用显式索引的想法比基于日期之间比较的条件索引更清晰,这在大规模上非常慢(d.version_end = d.version_end.apply(lambda t: t.replace(day=2)) 是一个有效的替代方案,如果你被迫在大型数组上执行此操作)。