使用Pandas聚合具有开始和结束时间的事件

时间:2015-03-13 13:01:41

标签: python pandas

我有许多事件的数据,包括开始和结束时间:

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)

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

2 个答案:

答案 0 :(得分:1)

这就是我想出的。我认为有更好的方法

鉴于你的框架

         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

答案 1 :(得分:1)

如果我使用SQL,我会通过将all-dates表连接到events表,然后按日期分组来实现。熊猫并没有使这种方法变得特别容易,因为在某种情况下无法左联,但我们可以使用虚拟列和重建索引来伪造它:

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()
)

更好地处理空值。