【问题标题】:How to improve function with all-to-all rows computation within a groupby object?如何通过 groupby 对象中的所有行计算来改进功能?
【发布时间】:2020-11-30 07:15:06
【问题描述】:

假设我有这个简单的数据框-

dic = {'firstname':['Steve','Steve','Steve','Steve','Steve','Steve'],
       'lastname':['Johnson','Johnson','Johnson','Johnson','Johnson',
                   'Johnson'],
       'company':['CHP','CHP','CHP','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

我希望能够将具有 faveday 的行保持在另一个 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 天内。然后在确认这一点后,查看这些 favedays 的付费列的总和是否超过 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","lastname","company"]).apply(grouped_dates).reset_index(drop=True)
print(df)

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

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

【问题讨论】:

  • 如果有 3 个日期,每个相隔 4 天(例如 01-01 $500、01-05 $500、01-09 $500),你会怎么做?中间日期组应该在左边和右边吗?日期计数和支付金额?
  • 如果可能,是的,包括所有 3 个。重叠很重要,因为它在 7 天的窗口内。

标签: python pandas numpy dataframe time


【解决方案1】:

我找到了一个避免循环 idx 以比较其他行是否在 7 天内的解决方案,但涉及 unstackreindex 所以它会增加内存使用量(我尝试利用 _get_window_bounds 滚动方法,但它证明高于我的专业知识)。对于您要求的规模应该没问题。尽管此解决方案与您提供的玩具 df 相当,但在较大的数据集上速度要快几个数量级。

编辑:允许在一个日期内进行多次存款。

获取此数据(在 random.choice 中默认使用 replace=True

import string
np.random.seed(123)
n = 40
df = pd.DataFrame([[a, b, 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]

还有代码

def get_valid(df, window_size=7, paid_gt=1000, groupbycols=['firstname', 'lastname', '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, window_size=7, paid_gt=1000,
    groupbycols=['firstname', 'lastname', 'company'])

仍以 1.5 秒运行(当前代码为 143 秒)并返回

      firstname lastname company    faveday       0
0             a        a       a 2020-02-02   490.0
1             a        a       a 2020-02-09   615.0
2             a        a       a 2020-02-17  1232.0
3             a        a       a 2020-03-09   630.0
4             a        a       a 2020-03-14   820.0
...         ...      ...     ...        ...     ...
17561         z        z       z 2020-11-12   204.0
17562         z        z       z 2020-12-22   863.0
17563         z        z       z 2020-12-23   675.0
17564         z        z       z 2020-12-26  1165.0
17565         z        z       z 2020-12-30   683.0

[17566 rows x 5 columns]

【讨论】:

  • 因此,在新的编辑中,有几个问题。正如我之前在问题中所述,我想保留构成 >1000 限制的所有条目(如我提到的变换函数)。如果两个条目在同一日期,它将两行合并为一个(这将无益)。另一个问题是,如果您将其他列引入数据框中,则编辑仅保留名字、姓氏、公司、付费和最喜欢的日期,并删除任何其他列。有没有办法解决这些问题?
  • 我看到了合并同一日期的所有行的意义......关于保留任何其他列,同样,原始问题中没有提到......首先想到的是使用此函数的输出过滤原始 df,通过将 groupbycols + ['faveday'] 设置为 dfs 和过滤器 df.loc[df.index.isin(df_clean.index), :] 的索引 ...如果想到更好的解决方案,我会回复你
  • 您确实了解该站点是针对您的代码的特定问题提出的问题...但您实际上是将其用作代码工厂,在以后的帖子中请花时间充分解释您的问题综上所述,如果您希望其他人免费帮助您,这是您至少可以做的事情
猜你喜欢
  • 1970-01-01
  • 2018-03-22
  • 2014-11-03
  • 2015-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多