Python pandas使用groupby加速多行计算

Python pandas使用groupby加速多行计算,python,pandas,dataframe,pandas-groupby,Python,Pandas,Dataframe,Pandas Groupby,我试图为数据帧中的每一行计算多行 我当前的解决方案对于200000行几乎需要2个小时。所以这是相当低效的,我希望groupby或其他一些方法可以帮助我 我的数据如下所示。例如,您现在可以忽略日期: id group start_date end_date three_yrs_ago_date days_missing 01 23 2005-01-01 2006-01-01 2002-01-01 1 02 23 2006-01-06 2007-01-06 200

我试图为数据帧中的每一行计算多行

我当前的解决方案对于200000行几乎需要2个小时。所以这是相当低效的,我希望groupby或其他一些方法可以帮助我

我的数据如下所示。例如,您现在可以忽略日期:

id group start_date end_date   three_yrs_ago_date days_missing
01 23    2005-01-01 2006-01-01 2002-01-01           1
02 23    2006-01-06 2007-01-06 2003-01-06           6
03 23    2007-01-15 2008-01-15 2004-01-15           9
07 17    2014-01-01 2015-02-01 2011-01-01           2
07 23    2015-01-01 2016-02-01 2012-01-01           4
因此,这里的目标是按组编号对所有内容进行分组,然后将该组中所有其他行在过去3年内发生的所有天数相加。也就是说,其他行的开始日期在当前行的三年前或之后,并且在当前行的结束日期或之前

这是一口,但基本上有三个标准。因此,如果这是整个数据集,我们将得到删除日期列的结果:

id group days_missing days_missing_in_last_three_years            
01 23    1            1    # no change: no prior years
02 23    6            7 
03 23    9            16  
07 17    2            2    # no change: only member of it's group
07 23    4            4    # no change: other group members more than 3 years ago
我将向您展示我目前拥有的代码,但速度很慢

我逐行查看数据框,创建一个包含所有组成员的临时数据框,然后将这些组成员缩减为仅符合日期条件的成员。这并不漂亮:

days=[]
for index, row in tqdm(df.iterrows()):
    # moderately slow (~2 hour):
    temp = df[df['group'] == row['group']]
    temp = temp[temp['start_date'] >= row['three_yrs_ago_date']]
    temp = temp[temp['end_date'] <= row['start_date']]
    add = temp['days_missing'].sum() + row['days_missing']
    days.append(add)
df['days_missing_in_last_three_years'] = days
我尝试了其他两种方法,但都没有成功:

# very slow (~3 hours):
cov.append(df[(df['group'] == row['group']) & (df['start_date'] >= row['three_yrs_ago_date']) & (df['end_date'] <= row['start_date'])]['days_missing'].sum()+row['days_missing'])

# doesn't work - incorrect use of groupby
df['test'] = df[(df.groupby(['group'])['start_date'] >= df.groupby(['group'])['three_yrs_ago_date']) & (df.groupby(['group'])['end_date'] <= df.groupby(['group'])['start_date'])]['days_missing'].sum()

有没有比将其分解成更小的临时数据帧并对其进行计算更有效的方法呢?

这里有一个解决方案,可能比您的方法更快。对on df.groupby'group'使用循环,然后对每个分组的数据报df_g应用循环。您可以使用between方法为每行选择两个日期之间的df_g部分

for name, df_g in df.groupby('group'):
    df.loc[df_g.index,'test'] = df_g.apply(lambda row: (df_g['days_missing'][df_g['start_date']
                                                           .between(row['three_yrs_ago_date'], row['end_date'])].sum()),1)
df['test'] = df['test'].astype(int) #to get integer
结果与预期一致:

   id  group start_date   end_date three_yrs_ago_date  days_missing  test
0   1     23 2005-01-01 2006-01-01         2002-01-01             1     1
1   2     23 2006-01-06 2007-01-06         2003-01-06             6     7
2   3     23 2007-01-15 2008-01-15         2004-01-15             9    16
3   7     17 2014-01-01 2015-02-01         2011-01-01             2     2
4   7     23 2015-01-01 2016-02-01         2012-01-01             4     4
编辑:使用numpy功能的更快方式:

import numpy as np
for name, df_g in df.groupby('group'):
    m_g = ( np.less_equal.outer(df_g['three_yrs_ago_date'], df_g['start_date']) 
            & np.greater_equal.outer(df_g['end_date'], df_g['start_date']) )
    df.loc[df_g.index,'test'] =np.dot(m_g, df_g['days_missing'])
df['test'] = df['test'].astype(int) #to get integer
下面尝试使用.groupby、.loc和.transform:


只是一条注释,“days_missing”第一行是0还是1?第一次使用0,第二次使用1。显然有一种更好的方法可以做到这一点,而不是将其分解为更小的数据帧。你知道大O的事吗?此外,您还可以查看延迟分析cProfile。对于这个特定的问题,我会遵从其他人的意见,但如果你能先完成groupby,你应该能够进行一些简单的基于元素的计算。此外,@Legit Stack,如果你需要迭代DF,itertuples要比iterrows快得多。这个解决方案运行速度快,看起来很优雅,但我无法让它工作,我的“测试”专栏仍然是nan。我很想让它发挥作用,因为它看起来棒极了,而且我喜欢这个图案的外观,但是另一个答案对我来说很有用,所以我可能不会再花时间在这个上面了,谢谢!谢谢Ben,我也解决了你在问题中发现的问题。这很有效。我曾经在别处申请过,但没有考虑过这个问题。它把我的跑步时间从2小时缩短到了10分钟@LegitStack我添加了另一个解决方案,它可能更快!
import numpy as np

conditions = (
    (df['start_date'] >= df['three_yrs_ago_date'])
    & (df['end_date'] <= df['start_date'])
)
df['test'] = np.nan # initiliaze column, otherwise next line raises KeyError
df.loc[conditions, 'test'] = df.loc[conditions, ].groupby('group')['days_missing'].transform('sum')