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

Python 有效地计算大熊猫的滚动时差,python,pandas,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

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

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

我想我应该在这里使用
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
(假设阶段是连续的。)

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

注意:这可能要快得多…

基于您的代码(您的
groupby/apply
),它看起来像(尽管有您的示例…但可能我误解了您的想法,然后Andy做了什么才是最好的主意)您使用的“日期”列是实际数据中的
datetime64
dtype,而不是
integer
dtype。此外,您还需要计算从给定
组/阶段的第一次观察中测量的变化(以天为单位)。我认为这是一组更好的示例数据(如果我正确理解您的目标):

考虑到您只需修改应用程序(正如Jeff在评论中所建议的那样),就可以在应用后以矢量化方式除以
timedelta64
(或者您可以在应用程序中执行此操作),从而获得一定的速度:

但是,如果您的数据是按组、阶段、日期顺序排列的,您也可以避免使用
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']所创建的内容。其中(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
然后我将值与'date'列进行比较

编辑:正如Andy指出的那样,您也可以使用
转换

>>> 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
速度:我使用一个类似的数据帧对这两种方法进行计时,共观察了400000次:

应用方法:

1 loops, best of 3: 18.3 s per loop
1 loops, best of 3: 1.64 s per loop
不适用方法:

1 loops, best of 3: 18.3 s per loop
1 loops, best of 3: 1.64 s per loop

因此,我认为避免应用程序可以显著提高速度

您不需要最终的应用程序,请参见此处:,您可以简单地
astype('timedelta64[D')
或除以
np.timedelta64(1,'D')
(它们的取整方式略有不同。+1这可能对OP想要的东西更有意义……我认为你可以使用转换更有效地实现这一点。是的@Andy,我考虑过
transform
,但至少对于0.13.1,我通常发现转换不比一般的
应用快,所以我没有包括它。但我会我更新答案作为替代。有兴趣看看它是否更快,我的猜测是它会更快(尽管取决于群体规模-如果群体更大,怀疑会更快)@Andy,我的
transform
慢了很多。我原以为Jeff提到了
transform
的一些性能问题,但可能我记错了。哎呀,是的,你的方法快多了(专门针对这个连续的案例),误读了你的第二个解决方案!
1 loops, best of 3: 1.64 s per loop