2015-03-13 37 views
2

我有一些与开始和结束时间的事件这样的数据:聚集事件与熊猫开始和结束时间

df = pd.DataFrame({'start': ['2015-01-05', '2015-01-10', '2015-01-11'], 'end': ['2015-01-07', '2015-01-15', '2015-01-13'], 'value': [3, 4, 5]}) 
df['end'] = pd.to_datetime(df['end']) 
df['start'] = pd.to_datetime(df['start']) 

日期:

  end  start value 
0 2015-01-07 2015-01-05  3 
1 2015-01-15 2015-01-10  4 
2 2015-01-13 2015-01-11  5 

现在我需要计算同时活动的事件数量,例如。它们的值的总和。所以结果应该是这样的:

 date count sum 
2015-01-05  1  3 
2015-01-06  1  3 
2015-01-07  1  3 
2015-01-08  0  0 
2015-01-09  0  0 
2015-01-10  1  4 
2015-01-11  2  9 
2015-01-12  2  9 
2015-01-13  2  9 
2015-01-14  1  4 
2015-01-15  1  4 

任何想法如何做到这一点?我正在考虑为groupby使用自定义的Grouper,但据我所见,Grouper只能将一行分配给单个组,因此看起来不太有用。

编辑:一些测试,我发现这个相当丑陋的方式来获得期望的结果后:

df['count'] = 1 
dates = pd.date_range('2015-01-05', '2015-01-15', freq='1D') 

start = df[['start', 'value', 'count']].set_index('start').reindex(dates) 
end = df[['end', 'value', 'count']].set_index('end').reindex(dates).shift(1) 

rstart = pd.rolling_sum(start, len(start), min_periods=1) 
rend = pd.rolling_sum(end, len(end), min_periods=1) 

rstart.subtract(rend, fill_value=0).fillna(0) 

然而,这仅与资金运作,我不能看到一个明显的方法,使其与工作其他功能。例如,有没有办法让它与中位数而不是总和一起工作?

+0

让人联想到计数缠绕或开合分隔符,但不清楚如何移植算法。 – cphlewis 2015-03-14 02:48:58

回答

1

如果我使用SQL,我想通过参加这样做事件表的全日期表,然后按日期分组。大熊猫不使这种方法特别简单,因为没有办法在一个条件,左加入,但我们可以伪造它使用虚拟列和重建索引:

df = pd.DataFrame({'start': ['2015-01-05', '2015-01-10', '2015-01-11'], 'end': ['2015-01-07', '2015-01-15', '2015-01-13'], 'value': [3, 4, 5]}) 
df['end'] = pd.to_datetime(df['end']) 
df['start'] = pd.to_datetime(df['start']) 
df['dummy'] = 1 

然后:

date_series = pd.date_range('2015-01-05', '2015-01-15', freq='1D') 
date_df = pd.DataFrame(dict(date=date_series, dummy=1)) 

cross_join = date_df.merge(df, on='dummy') 
cond_join = cross_join[(cross_join.start <= cross_join.date) & (cross_join.date <= cross_join.end)] 
grp_join = cond_join.groupby(['date']) 
final = (
    pd.DataFrame(dict(
     val_count=grp_join.size(), 
     val_sum=grp_join.value.sum(), 
     val_median=grp_join.value.median() 
    ), index=date_series) 
    .fillna(0) 
    .reset_index() 
) 

fillna(0)并不完美,因为它会将val_median列中的零点设置为0,当它们应该保持为零时。

或者,pandas-ply我们可以代码起来就是:

date_series = pd.date_range('2015-01-05', '2015-01-15', freq='1D') 
date_df = pd.DataFrame(dict(date=date_series, dummy=1)) 

final = (
    date_df 
    .merge(df, on='dummy') 
    .ply_where(X.start <= X.date, X.date <= X.end) 
    .groupby('date') 
    .ply_select(val_count=X.size(), val_sum=X.value.sum(), median=X.value.median()) 
    .reindex(date_series) 
    .ply_select('*', val_count=X.val_count.fillna(0), val_sum=X.val_sum.fillna(0)) 
    .reset_index() 
) 

它处理空值的好一点。

+0

不错,谢谢!这是用条件构造连接表的一种巧妙方式。我将不得不用一些真实数据来测试它,并查看大型表的性能。 – 2015-04-24 08:52:20

0

这就是我想出来的。得想有一个更好的办法

鉴于你的框架

  end  start value 
0 2015-01-07 2015-01-05  3 
1 2015-01-15 2015-01-10  4 
2 2015-01-13 2015-01-11  5 

然后

dList = [] 
vList = [] 
d = {} 

def buildDict(row): 
    for x in pd.date_range(row["start"],row["end"]): #build a range for each row 
     dList.append(x) #date list 
     vList.append(row["value"]) #value list 

df.apply(buildDict,axis=1) #each row in df is passed to buildDict 

#this d will be used to create our new frame 
d["date"] = dList 
d["value"] = vList 

#from here you can use whatever agg functions you want 
pd.DataFrame(d).groupby("date").agg(["count","sum"]) 

产生

  value 
     count sum 
date   
2015-01-05 1 3 
2015-01-06 1 3 
2015-01-07 1 3 
2015-01-10 1 4 
2015-01-11 2 9 
2015-01-12 2 9 
2015-01-13 2 9 
2015-01-14 1 4 
2015-01-15 1 4 
相关问题