有效地计算大熊猫的滚动时间差

时间:2014-05-22 00:58:18

标签: python pandas

我在熊猫中有一个小组,我正在尝试计算一个人在每个阶段花费的时间。为了更好地理解这一点,我的数据集如下:

group       date    stage  
 A     2014-01-01   one   
 A     2014-01-03   one    
 A     2014-01-04   one    
 A     2014-01-05   two    
 B     2014-01-02  four    
 B     2014-01-06  five    
 B     2014-01-10  five    
 C     2014-01-03   two    
 C     2014-01-05   two    

我正在计算阶段持续时间给出:

 group       date    stage  dur
  A     2014-01-01   one    0
  A     2014-01-03   one    2
  A     2014-01-04   one    3
  A     2014-01-05   two    0
  B     2014-01-02  four    0
  B     2014-01-06  five    0
  B     2014-01-10  five    4
  C     2014-01-03   two    0
  C     2014-01-05   two    2

我在下面使用的方法非常慢。有关更快方法的任何想法吗?

df['stage_duration'] = df.groupby(['group', 'stage']).date.apply(lambda y: (y - y.iloc[0])).apply(lambda y:y / np.timedelta64(1, 'D')))

2 个答案:

答案 0 :(得分:6)

基于你的代码(你的groupby/apply),它看起来像(尽管你的例子...但也许我误解了你想要的东西,然后安迪所做的最好的想法)你正在使用'date'列是实际数据中的datetime64 dtype而不是integer dtype。此外,您似乎想要计算从给定group/stage的第一次观察开始测量的天数变化。我认为这是一组更好的示例数据(如果我正确理解你的目标):

>>> df

  group       date stage  dur
0     A 2014-01-01   one    0
1     A 2014-01-03   one    2
2     A 2014-01-04   one    3
3     A 2014-01-05   two    0
4     B 2014-01-02  four    0
5     B 2014-01-06  five    0
6     B 2014-01-10  five    4
7     C 2014-01-03   two    0
8     C 2014-01-05   two    2

考虑到你应该通过修改你的申请(正如杰夫在他的评论中所建议的那样)通过在申请后以矢量化方式除以timedelta64来获得一些加速(或者你可以在适用):

>>> df['dur'] = df.groupby(['group','stage']).date.apply(lambda x: x - x.iloc[0])
>>> df['dur'] /= np.timedelta64(1,'D')
>>> df

  group       date stage  dur
0     A 2014-01-01   one    0
1     A 2014-01-03   one    2
2     A 2014-01-04   one    3
3     A 2014-01-05   two    0
4     B 2014-01-02  four    0
5     B 2014-01-06  five    0
6     B 2014-01-10  five    4
7     C 2014-01-03   two    0
8     C 2014-01-05   two    2

但是,如果您的数据属于组,阶段,日期顺序,您也可以避免groupby/apply。每个['group','stage']分组的第一个日期都会在组更改或阶段更改时发生。所以我认为你可以做以下事情:

>>> beg = (df.group != df.group.shift(1)) | (df.stage != df.stage.shift(1))
>>> df['dur'] = (df['date'] - df['date'].where(beg).ffill())/np.timedelta64(1,'D')
>>> df

  group       date stage  dur
0     A 2014-01-01   one    0
1     A 2014-01-03   one    2
2     A 2014-01-04   one    3
3     A 2014-01-05   two    0
4     B 2014-01-02  four    0
5     B 2014-01-06  five    0
6     B 2014-01-10  five    4
7     C 2014-01-03   two    0
8     C 2014-01-05   two    2

说明:注意df['date'].where(beg)创建的内容:

>>> beg = (df.group != df.group.shift(1)) | (df.stage != df.stage.shift(1))
>>> df['date'].where(beg)

0   2014-01-01
1          NaT
2          NaT
3   2014-01-05
4   2014-01-02
5   2014-01-06
6          NaT
7   2014-01-03
8          NaT

然后我ffill将值与“日期”列区分开来。

修改:Andy指出您也可以使用transform

>>> df['dur'] = df.date - df.groupby(['group','stage']).date.transform(lambda x: x.iloc[0])
>>> df['dur'] /= np.timedelta64(1,'D')

  group       date stage  dur
0     A 2014-01-01   one    0
1     A 2014-01-03   one    2
2     A 2014-01-04   one    3
3     A 2014-01-05   two    0
4     B 2014-01-02  four    0
5     B 2014-01-06  five    0
6     B 2014-01-10  five    4
7     C 2014-01-03   two    0
8     C 2014-01-05   two    2

速度:我使用类似的数据帧和400,000个观察结果来计算两种方法:

申请方法:

1 loops, best of 3: 18.3 s per loop

非申请方法:

1 loops, best of 3: 1.64 s per loop

所以我认为避免申请会带来一些显着的加速

答案 1 :(得分:5)

我想我在这里使用diff

In [11]: df.groupby('stage')['date'].diff().fillna(0)
Out[11]:
0    0
1    2
2    0
3    0
4    0
5    4
dtype: float64

(假设这些阶段是连续的。)

如果您只是减去每组中的第一个,请使用transform

In [21]: df['date'] - df.groupby('stage')['date'].transform(lambda x: x.iloc[0])
Out[21]:
0    0
1    2
2    0
3    0
4    0
5    4
Name: date, dtype: int64

注意:这可能要快得多......