Как улучшить функциональность Pandas GroupBy Transform для массового преобразования в несколько столбцов? - PullRequest
1 голос
/ 02 апреля 2020

Я хочу добавить 9 столбцов в мой Pandas DataFrame, состоящий из следующей информации:

  • Сумма транзакций в этот день / неделю / месяц (AmtDay / AmtWeek / AmtMonth)
  • Количество транзакций в этот день / неделя / месяц (CountDay / CountWeek / CountMonth)
  • Средняя сумма транзакций в этот день / неделя / месяц (AvgAmtDay / AvgAmtWeek / AvgAmtMonth)

Чтобы сделать это, я написал следующий код:

df["AmtDay"] = df.groupby(["ClientId", "Year","Day"])["Amount"].transform(sum)
df["CountDay"] = df.groupby(["ClientId", "Year", "Day"])["Amount"].transform(len)
df["AvgAmtDay"] = df.groupby(["ClientId", "Year", "Day"])["Amount"].transform(lambda x: sum(x) / len(x))
df["AmtWeek"] = df.groupby(["ClientId", "Year", "Week"])["Amount"].transform(sum)
df["CountWeek"] = df.groupby(["ClientId", "Year", "Week"])["Amount"].transform(len)
df["AvgAmtWeek"] = df.groupby(["ClientId", "Year", "Day"])["Amount"].transform(lambda x: sum(x) / len(x))
df["AmtMonth"] = df.groupby(["ClientId", "Year", "Month"])["Amount"].transform(sum)
df["CountMonth"] = df.groupby(["ClientId", "Year", "Month"])["Amount"].transform(len)
df["AvgAmtMonth"] = df.groupby(["ClientId", "Year", "Day"])["Amount"].transform(lambda x: sum(x) / len(x))

За исключением того, что это требует слишком больших вычислительных затрат, чтобы очень быстро выполнять (в основном) одну и ту же группировку каждый раз. Есть ли способ сделать это более эффективно?


Мои данные структурированы следующим образом, где каждая строка представляет одну транзакцию с clientId, дата (день представляет день год), и сумма:

+----------+------+-------+------+-----+--------+
| ClientId | Year | Month | Week | Day | Amount |
+----------+------+-------+------+-----+--------+
|        1 | 2020 |     1 |    1 |   1 |     10 |
|        1 | 2020 |     1 |    1 |   2 |     20 |
|        1 | 2020 |     1 |    1 |   2 |     10 |
|        2 | 2020 |     1 |    1 |   1 |      5 |
|        2 | 2020 |     1 |    1 |   1 |     10 |
|        2 | 2020 |     1 |    1 |   2 |     30 |
+----------+------+-------+------+-----+--------+

В то время как я хотел бы, чтобы выходные данные были следующими (показаны только 3-дневные столбцы для лучшей читаемости):

+----------+------+-------+------+-----+--------+--------+----------+-----------+-----------------+
| ClientId | Year | Month | Week | Day | Amount | AmtDay | CountDay | AvgAmtDay | AmtWeek... Etc. |
+----------+------+-------+------+-----+--------+--------+----------+-----------+-----------------+
|        1 | 2020 |     1 |    1 |   1 |     10 |     10 |        1 | 10        |                 |
|        1 | 2020 |     1 |    1 |   2 |     20 |     30 |        2 | 15        |                 |
|        1 | 2020 |     1 |    1 |   2 |     10 |     30 |        2 | 15        |                 |
|        2 | 2020 |     1 |    1 |   1 |      5 |     15 |        2 | 7.5       |                 |
|        2 | 2020 |     1 |    1 |   1 |     10 |     15 |        2 | 7.5       |                 |
|        2 | 2020 |     1 |    1 |   2 |     30 |     30 |        1 | 30        |                 |
+----------+------+-------+------+-----+--------+--------+----------+-----------+-----------------+

1 Ответ

2 голосов
/ 02 апреля 2020

Поскольку для разных столбцов группировки требуется группировка по 3 раза, здесь возможно повторное использование списка столбцов с функциями агрегирования в GroupBy.agg и DataFrame.join до оригинала:

L = [('Amt','sum'), ('Count','size'), ('AvgAmt','mean')]
g1 = ["ClientId", "Year","Day"]
g2 = ["ClientId", "Year", "Week"]
g3 = ["ClientId", "Year", "Month"]

df = df.join(df.groupby(g1)['Amount'].agg(L).add_suffix('Day'), on=g1)
df = df.join(df.groupby(g2)['Amount'].agg(L).add_suffix('Week'), on=g2)
df = df.join(df.groupby(g3)['Amount'].agg(L).add_suffix('Month'), on=g3)

Или:

df = df.join(df.groupby(g1)['Amount'].agg(L).add_suffix(g1[-1]), on=g1)
df = df.join(df.groupby(g2)['Amount'].agg(L).add_suffix(g2[-1]), on=g2)
df = df.join(df.groupby(g3)['Amount'].agg(L).add_suffix(g3[-1]), on=g3)

Ваше решение можно переписать во вложенном l oop:

L = [('Amt','sum'),('Count','size'),('AvgAmt','mean')]
g1 = ["ClientId", "Year","Day"]
g2 = ["ClientId", "Year", "Week"]
g3 = ["ClientId", "Year", "Month"]
groups = [g1, g2, g3]

for g in groups:
    for c, f in L:
        df[f'{c}{g[-1]}'] = df.groupby(g)["Amount"].transform(f)

print (df)
   ClientId  Year  Month  Week  Day  Amount  AmtDay  CountDay  AvgAmtDay  \
0         1  2020      1     1    1      10      10         1       10.0   
1         1  2020      1     1    2      20      30         2       15.0   
2         1  2020      1     1    2      10      30         2       15.0   
3         2  2020      1     1    1       5      15         2        7.5   
4         2  2020      1     1    1      10      15         2        7.5   
5         2  2020      1     1    2      30      30         1       30.0   

   AmtWeek  CountWeek  AvgAmtWeek  AmtMonth  CountMonth  AvgAmtMonth  
0       40          3   13.333333        40           3    13.333333  
1       40          3   13.333333        40           3    13.333333  
2       40          3   13.333333        40           3    13.333333  
3       45          3   15.000000        45           3    15.000000  
4       45          3   15.000000        45           3    15.000000  
5       45          3   15.000000        45           3    15.000000  
...