如何解决如何通过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]
这还将保留所有条目(因为所有条目都使用相同的名称和公司,并且总和大于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比较其他行,但是涉及unstack
和reindex
,因此会增加内存使用率(我尝试使用{{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'])