Как я могу суммировать кумулятивно значения по условию в полярах или пандах? [с примером]

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

{
  "date" : [2022-01-01, 2022-01-02, 2022-01-03, 2022-01-04, 2022-01-05],
  "customers" : [3, 4, 5, 3, 2],
  "is_reporting_day?" : [True, False, False, False, True]
}

чтобы было немного понятнее, вот формат таблицы

дата клиенты is_reporting_day? 2022-01-01 3 Истинный 2022-01-02 4 ЛОЖЬ 2022-01-03 5 ЛОЖЬ 2022-01-04 3 ЛОЖЬ 2022-01-05 2 Истинный

Что я хочу получить, так это: если reporting_day is True оставить количество клиентов как есть, и если reporting_day is False я хочу суммировать всех клиентов (4, 5, 3 = 12 + 2 = 14) и добавить его к следующему True value reporting day

поэтому после применения преобразования это должно выглядеть так:

дата клиенты is_reporting_day? клиенты 2022-01-01 3 Истинный 3 2022-01-05 2 Истинный 14

Я пытался использовать функцию cumsum() в полярах с помощью оператора pl.when, но это неправильная логика, поскольку она суммируется с самого начала, то есть с первого дня (около 700 дней).

Примечание: решение должно быть динамическим, т. е. иногда разрыв между reporting_day и non-reporting_day составляет 1 день, 2 дня и т. д.

Любые идеи или вклад высоко ценятся! Заранее спасибо!

‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎‎

2
0
72
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Предполагая, что даты уже отсортированы, используйте groupby.agg:

out = (df.groupby(df['is_reporting_day?'].shift(fill_value=False).cumsum(), as_index=False)
         .agg({'date': 'max', 'customers': 'sum', 'is_reporting_day?': 'max'})
      )

Выход:

         date  customers  is_reporting_day?
0  2022-01-01          3               True
1  2022-01-05         14               True

Если вам нужны как начальная, так и сумма "клиентов":

out = (df.groupby(df['is_reporting_day?'].shift(fill_value=False).cumsum(), as_index=False)
         .agg(**{'date': ('date', 'max'),
                 'customers': ('customers', 'last'),
                 'is_reporting_day?': ('is_reporting_day?', 'max'),
                 'customers_sum': ('customers', 'sum'),
                })
      )

Выход:

         date  customers  is_reporting_day?  customers_sum
0  2022-01-01          3               True              3
1  2022-01-05          2               True             14

Альтернатива:

out = (
 df.assign(date=df['date'].where(df['is_reporting_day?']).bfill())
   .groupby('date', as_index=False)
         .agg(**{'date': ('date', 'max'),
                 'customers': ('customers', 'last'),
                 'is_reporting_day?': ('is_reporting_day?', 'max'),
                 'customers_sum': ('customers', 'sum'),
                })
)
Ответ принят как подходящий

То же самое и с полярами, используя подходы @mozway:

(df
 .groupby(
    pl.col("is_reporting_day?")
      .shift_and_fill(False, periods=1)
      .cumsum().alias("group"),
    maintain_order=True)
 .agg(
    pl.all().last(),
    sum = pl.sum("customers"))
)
shape: (2, 5)
┌───────┬─────────────────────┬───────────┬───────────────────┬─────┐
│ group ┆ date                ┆ customers ┆ is_reporting_day? ┆ sum │
│ ---   ┆ ---                 ┆ ---       ┆ ---               ┆ --- │
│ u32   ┆ datetime[ns]        ┆ i64       ┆ bool              ┆ i64 │
╞═══════╪═════════════════════╪═══════════╪═══════════════════╪═════╡
│ 0     ┆ 2022-01-01 00:00:00 ┆ 3         ┆ true              ┆ 3   │
│ 1     ┆ 2022-01-05 00:00:00 ┆ 2         ┆ true              ┆ 14  │
└───────┴─────────────────────┴───────────┴───────────────────┴─────┘
df.groupby(
   pl.when(pl.col("is_reporting_day?"))
     .then(pl.col("date"))
     .backward_fill()
     .alias("group"),
   maintain_order=True
).agg(
   pl.all().last(),
   sum = pl.sum("customers")
)
shape: (2, 5)
┌─────────────────────┬─────────────────────┬───────────┬───────────────────┬─────┐
│ group               ┆ date                ┆ customers ┆ is_reporting_day? ┆ sum │
│ ---                 ┆ ---                 ┆ ---       ┆ ---               ┆ --- │
│ datetime[ns]        ┆ datetime[ns]        ┆ i64       ┆ bool              ┆ i64 │
╞═════════════════════╪═════════════════════╪═══════════╪═══════════════════╪═════╡
│ 2022-01-01 00:00:00 ┆ 2022-01-01 00:00:00 ┆ 3         ┆ true              ┆ 3   │
│ 2022-01-05 00:00:00 ┆ 2022-01-05 00:00:00 ┆ 2         ┆ true              ┆ 14  │
└─────────────────────┴─────────────────────┴───────────┴───────────────────┴─────┘

Вы можете использовать .over(), если хотите сохранить исходные строки.

df.with_columns(
   pl.cumsum("customers").over(
      pl.when(pl.col("is_reporting_day?"))
        .then(pl.col("date"))
        .backward_fill())
   .alias("cumsum")
)
shape: (5, 4)
┌─────────────────────┬───────────┬───────────────────┬────────┐
│ date                ┆ customers ┆ is_reporting_day? ┆ cumsum │
│ ---                 ┆ ---       ┆ ---               ┆ ---    │
│ datetime[ns]        ┆ i64       ┆ bool              ┆ i64    │
╞═════════════════════╪═══════════╪═══════════════════╪════════╡
│ 2022-01-01 00:00:00 ┆ 3         ┆ true              ┆ 3      │
│ 2022-01-02 00:00:00 ┆ 4         ┆ false             ┆ 4      │
│ 2022-01-03 00:00:00 ┆ 5         ┆ false             ┆ 9      │
│ 2022-01-04 00:00:00 ┆ 3         ┆ false             ┆ 12     │
│ 2022-01-05 00:00:00 ┆ 2         ┆ true              ┆ 14     │
└─────────────────────┴───────────┴───────────────────┴────────┘

Спасибо! именно то, что я пытался достичь. действительно очень похоже на решение mozway.

nam0_0 06.04.2023 15:55
col1=(df1.is_reporting_day.eq(False)&df1.is_reporting_day.shift().eq(True)).cumsum()

df1.groupby(col1,group_keys=False).apply(lambda dd:dd.tail(1)
                                         .assign(customers2=dd['customers'].sum()))

вне:

    date  customers  is_reporting_day  customers2
0  2022-01-01          3              True           3
4  2022-01-05          2              True          14

Другие вопросы по теме