如何通过groupby对象中的所有行计算来改善功能?

问题描述

说我有这个简单的数据框-

dic = {'firstname':['Steve','Steve','Steve'],'lastname':['Johnson','Johnson','Johnson'],'company':['CHP','CHP','CHP'],'faveday':['2020-07-13','2020-07-20','2020-07-16','2020-10-14','2020-10-28','2020-10-21'],'paid':[200,300,550,100,900,650]}
df = pd.DataFrame(dic)
df['faveday'] = pd.to_datetime(df['faveday'])
print(df)

有输出-

  firstname lastname company    faveday  paid
0     Steve  Johnson     CHP 2020-07-13   200
1     Steve  Johnson     CHP 2020-07-20   300
2     Steve  Johnson     CHP 2020-07-16   550
3     Steve  Johnson     CHP 2020-10-14   100
4     Steve  Johnson     CHP 2020-10-28   900
5     Steve  Johnson     CHP 2020-10-21   650

我希望能够将最近的行保持在另一行的7天内,但其付费列的总和必须大于1000。

如果我想应用7天功能,则可以使用-

def sefd (x): 
    return np.sum((np.abs(x.values-x.values[:,None])/np.timedelta64(1,'D'))<=7,axis=1)>=2
s=df.groupby(['firstname','lastname','company'])['faveday'].transform(sefd)
df['seven_days']=s
df = df[s]
del df['seven_days']

这将保留所有条目(所有这些条目均在另一个根据名,姓和公司分组的节日前7天内)。

如果我想应用一个函数来为同一个人和同一家公司保留行,并且总支付金额> 1000,我会使用-

df = df[df.groupby(['lastname','firstname','company'])['paid'].transform(sum) > 1000]

只是一个简单的transform(sum)函数

这还将保留所有条目(因为所有条目都使用相同的名称和公司,并且总和大于1000)。

但是,如果我们要同时合并这两个功能,则实际上不会包含一行。

我想要的输出是-

  firstname lastname company    faveday  paid
0     Steve  Johnson     CHP 2020-07-13   200
1     Steve  Johnson     CHP 2020-07-20   300
2     Steve  Johnson     CHP 2020-07-16   550
4     Steve  Johnson     CHP 2020-10-28   900
5     Steve  Johnson     CHP 2020-10-21   650

请注意,索引3不再有效,因为它仅在索引5的7天内,但是如果您要对已支付的索引3和已支付的索引5进行求和,则该数字仅为750(

还必须注意,由于索引0、1和2都在7天内,因此算作一个汇总组(200 + 300 + 550> 1000)。

逻辑是,我想首先查看(基于一组姓氏,姓氏和公司名称)是否一个节日在另一个节日的7天内。然后,在确认这一点之后,查看这几天的付费栏的总和是否超过1000。如果是,请将这些索引保留在数据框中。否则,请不要。

给我的建议答案是-

df=df.sort_values(["firstname","lastname","company","faveday"])

def date_difference_from(x,df):
    return abs((df.faveday - x).dt.days)

def grouped_dates(grouped_df):
    keep = []
    for idx,row in grouped_df.iterrows():
        within_7 = date_difference_from(row.faveday,grouped_df) <= 7
        keep.append(within_7.sum() > 1 and grouped_df[within_7].paid.sum() > 1000)
    msk = np.array(keep)
    
    return grouped_df[msk]

df = df.groupby(["firstname","company"]).apply(grouped_dates).reset_index(drop=True)
print(df)

这对于像这样的小型数据集非常适用,但是当我将其应用于更大的数据集(10,000行以上)时,会出现一些不一致之处。

有什么办法可以改善这段代码?

解决方法

我找到了一种解决方案,该解决方案避免在7天之内循环idx比较其他行,但是涉及unstackreindex,因此会增加内存使用率(我尝试使用{{1} }滚动方法,但事实证明,该方法超出了我的专业知识。适合您所要求的规模。尽管此解决方案与您提供的玩具df相当,但在较大的数据集上要快几个数量级。

编辑:一次允许多次存款。

获取此数据(默认为_get_window_bounds,默认为random.choice)

replace=True

和代码

import string
np.random.seed(123)
n = 40
df = pd.DataFrame([[a,b,faveday,paid]
    for a in string.ascii_lowercase
    for b in string.ascii_lowercase
    for faveday,paid in zip(
        np.random.choice(pd.date_range('2020-01-01','2020-12-31'),n),np.random.randint(100,1200,n))
    ],columns=['firstname','lastname','company','faveday','paid'])
df['faveday'] = pd.to_datetime(df['faveday'])
df = df.sort_values(["firstname","lastname","company","faveday"]).reset_index(drop=True)

>>>print(df)
      firstname lastname company    faveday  paid
0             a        a       a 2020-01-03  1180
1             a        a       a 2020-01-18   206
2             a        a       a 2020-02-02   490
3             a        a       a 2020-02-09   615
4             a        a       a 2020-02-17   471
...         ...      ...     ...        ...   ...
27035         z        z       z 2020-11-22   173
27036         z        z       z 2020-12-22   863
27037         z        z       z 2020-12-23   675
27038         z        z       z 2020-12-26  1165
27039         z        z       z 2020-12-30   683

[27040 rows x 5 columns]

仍然以1.5秒(相对于当前代码的143秒)运行并返回

def get_valid(df,window_size=7,paid_gt=1000,groupbycols=['firstname','company']):
    # df_clean = df.set_index(['faveday'] + groupbycols).unstack(groupbycols)
        # # unstack names to bypass groupby
    df_clean = df.groupby(['faveday'] + groupbycols).paid.agg(['size',sum])
    df_clean.columns = ['ct','paid']
    df_clean = df_clean.unstack(groupbycols)
    df_clean = df_clean.reindex(pd.date_range(df_clean.index.min(),df_clean.index.max())).sort_index() # include all dates,to treat index as integer
    window = df_clean.fillna(0).rolling(window_size + 1).sum()
        # notice fillna to prevent false NaNs while summing
    df_clean = df_clean.paid * ( # multiply times a mask for both conditions
        (window.ct > 1) & (window.paid > paid_gt)
        ).replace(False,np.nan).bfill(limit=7)
        # replacing with np.nan so we can backfill to include all dates in window
    df_clean = df_clean.rename_axis('faveday').stack(groupbycols)\
        .reset_index(level='faveday').sort_index().reset_index()
        # reshaping to original format
    return df_clean

df1 = get_valid(df,'company'])

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...