Прокручивание агрегации в полярах, а также возвращение исходного столбца без соединения или использования .over

Используя поляры .rolling и .agg, как мне вернуть исходный столбец без необходимости соединения с исходным столбцом или без использования .over?

Пример:

import polars as pl

dates = [
    "2020-01-01 13:45:48",
    "2020-01-01 16:42:13",
    "2020-01-01 16:45:09",
    "2020-01-02 18:12:48",
    "2020-01-03 19:45:32",
    "2020-01-08 23:16:43",
]
df = pl.DataFrame({"dt": dates, "a": [3, 7, 5, 9, 2, 1]}).with_columns(
    pl.col("dt").str.strptime(pl.Datetime).set_sorted()
)

Предоставляет мне небольшой фрейм данных Polars:

дт а 0 2020-01-01 13:45:48 3 1 2020-01-01 16:42:13 7 2 2020-01-01 16:45:09 5 3 2020-01-02 18:12:48 9 4 2020-01-03 19:45:32 2 5 2020-01-08 23:16:43 1

Когда я применяю скользящие агрегаты, я получаю обратно новые столбцы, но не исходные:

out = df.rolling(index_column = "dt", period = "2d").agg(
    [
        pl.sum("a").alias("sum_a"),
        pl.min("a").alias("min_a"),
        pl.max("a").alias("max_a"),
        pl.col('a')
    ]
)

который дает:

дт сумма_а мин_а max_a а 0 2020-01-01 13:45:48 3 3 3 [3] 1 2020-01-01 16:42:13 10 3 7 [3 7] 2 2020-01-01 16:45:09 15 3 7 [3 7 5] 3 2020-01-02 18:12:48 24 3 9 [3 7 5 9] 4 2020-01-03 19:45:32 11 2 9 [9 2] 5 2020-01-08 23:16:43 1 1 1 [1]

Как я могу получить исходный столбец. Я не хочу присоединяться и не хочу использовать .over, так как мне понадобится group_by для прокрутки позже, а .over не работает с .rolling

Редактировать. Я также не заинтересован в использовании следующего.

out = df.rolling(index_column = "dt", period = "2d").agg(
    [
        pl.sum("a").alias("sum_a"),
        pl.min("a").alias("min_a"),
        pl.max("a").alias("max_a"),
        pl.col('a').last().alias('a')
    ]
)

Изменить 2. Почему Expr.rolling() невозможен и почему мне нужен group_by:

Приведем более подробный пример:

dates = [
    "2020-01-01 13:45:48",
    "2020-01-01 16:42:13",
    "2020-01-01 16:45:09",
    "2020-01-02 18:12:48",
    "2020-01-03 19:45:32",
    "2020-01-08 23:16:43",
]
df_a = pl.DataFrame({"dt": dates, "a": [3, 7, 5, 9, 2, 1],'cat':['one']*6}).with_columns(
    pl.col("dt").str.strptime(pl.Datetime).set_sorted()
)
df_b = pl.DataFrame({"dt": dates, "a": [3, 7, 5, 9, 2, 1],'cat':['two']*6}).with_columns(
    pl.col("dt").str.strptime(pl.Datetime).set_sorted()
)

df = pl.concat([df_a,df_b])
дт а кот 0 2020-01-01 13:45:48 3 один 1 2020-01-01 16:42:13 7 один 2 2020-01-01 16:45:09 5 один 3 2020-01-02 18:12:48 9 один 4 2020-01-03 19:45:32 2 один 5 2020-01-08 23:16:43 1 один 6 2020-01-01 13:45:48 3 два 7 2020-01-01 16:42:13 7 два 8 2020-01-01 16:45:09 5 два 9 2020-01-02 18:12:48 9 два 10 2020-01-03 19:45:32 2 два 11 2020-01-08 23:16:43 1 два

и код:

    out = df.rolling(index_column = "dt", period = "2d",group_by='cat').agg(
    [
        pl.sum("a").alias("sum_a"),
        pl.min("a").alias("min_a"),
        pl.max("a").alias("max_a"),
        pl.col('a')
    ]
)
кот дт сумма_а мин_а max_a а 0 один 2020-01-01 13:45:48 3 3 3 [3] 1 один 2020-01-01 16:42:13 10 3 7 [3 7] 2 один 2020-01-01 16:45:09 15 3 7 [3 7 5] 3 один 2020-01-02 18:12:48 24 3 9 [3 7 5 9] 4 один 2020-01-03 19:45:32 11 2 9 [9 2] 5 один 2020-01-08 23:16:43 1 1 1 [1] 6 два 2020-01-01 13:45:48 3 3 3 [3] 7 два 2020-01-01 16:42:13 10 3 7 [3 7] 8 два 2020-01-01 16:45:09 15 3 7 [3 7 5] 9 два 2020-01-02 18:12:48 24 3 9 [3 7 5 9] 10 два 2020-01-03 19:45:32 11 2 9 [9 2] 11 два 2020-01-08 23:16:43 1 1 1 [1]

Это не работает:

df.sort('dt').with_columns(sum=pl.sum("a").rolling(index_column = "dt", period = "2d").over(["cat"]))

Дает:

InvalidOperationError: rolling expression not allowed in aggregation

Что говорит против использования pl.col("a").last()? Мне это кажется идиоматическим.

Hericks 12.06.2024 14:12

Мне кажется странным, что мне нужно сделать еще одну операцию (.last), чтобы получить то, что у меня уже было вначале. Я предполагаю, что есть какой-то способ сделать pl.exclude() или что-то в этом роде...

Olivier_s_j 12.06.2024 14:14

Тогда почему возникают проблемы с присоединением?

mozway 12.06.2024 14:17

Вы можете использовать pl.exclude("cols", "to", "exclude").last(), чтобы сохранить все неисключенные столбцы. Такое поведение соответствует pl.DataFrame.group_by().agg(), поскольку сохраняются только индексные столбцы и столбцы, явно упомянутые в агрегировании.

Hericks 12.06.2024 14:22

@mozway Тот же аргумент: зачем мне добавлять что-то, что у меня было вначале? Например, если вы используете scikit-learn ColumnTransformer, вы можете определить, через что следует пройти. Кажется нелогичным, что вы не можете просто пропустить исходный столбец через IMO.

Olivier_s_j 12.06.2024 14:30

Существуют специальные rolling_*_by выражения, например. pl.col("a").rolling_sum_by("dt", "2d").alias("sum_a")

jqurious 12.06.2024 14:33
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
6
91
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ты можешь сделать

In [9]: df.with_columns(
   ...:     a_min=pl.col('a').min().rolling('dt', period='2d'),
   ...:     a_max=pl.col('a').max().rolling('dt', period='2d'),
   ...:     a_sum=pl.col('a').sum().rolling('dt', period='2d'),
   ...: )
shape: (6, 5)
┌─────────────────────┬─────┬───────┬───────┬───────┐
│ dt                  ┆ a   ┆ a_min ┆ a_max ┆ a_sum │
│ ---                 ┆ --- ┆ ---   ┆ ---   ┆ ---   │
│ datetime[μs]        ┆ i64 ┆ i64   ┆ i64   ┆ i64   │
╞═════════════════════╪═════╪═══════╪═══════╪═══════╡
│ 2020-01-01 13:45:48 ┆ 3   ┆ 3     ┆ 3     ┆ 3     │
│ 2020-01-01 16:42:13 ┆ 7   ┆ 3     ┆ 7     ┆ 10    │
│ 2020-01-01 16:45:09 ┆ 5   ┆ 3     ┆ 7     ┆ 15    │
│ 2020-01-02 18:12:48 ┆ 9   ┆ 3     ┆ 9     ┆ 24    │
│ 2020-01-03 19:45:32 ┆ 2   ┆ 2     ┆ 9     ┆ 11    │
│ 2020-01-08 23:16:43 ┆ 1   ┆ 1     ┆ 1     ┆ 1     │
└─────────────────────┴─────┴───────┴───────┴───────┘

Альтернативно (и, вероятно, более эффективно):

In [16]: df.with_columns(
    ...:     a_min=pl.col('a').rolling_min_by('dt', '2d'),
    ...:     a_max=pl.col('a').rolling_max_by('dt', '2d'),
    ...:     a_sum=pl.col('a').rolling_sum_by('dt', '2d'),
    ...: )
Out[16]:
shape: (6, 5)
┌─────────────────────┬─────┬───────┬───────┬───────┐
│ dt                  ┆ a   ┆ a_min ┆ a_max ┆ a_sum │
│ ---                 ┆ --- ┆ ---   ┆ ---   ┆ ---   │
│ datetime[μs]        ┆ i64 ┆ i64   ┆ i64   ┆ i64   │
╞═════════════════════╪═════╪═══════╪═══════╪═══════╡
│ 2020-01-01 13:45:48 ┆ 3   ┆ 3     ┆ 3     ┆ 3     │
│ 2020-01-01 16:42:13 ┆ 7   ┆ 3     ┆ 7     ┆ 10    │
│ 2020-01-01 16:45:09 ┆ 5   ┆ 3     ┆ 7     ┆ 15    │
│ 2020-01-02 18:12:48 ┆ 9   ┆ 3     ┆ 9     ┆ 24    │
│ 2020-01-03 19:45:32 ┆ 2   ┆ 2     ┆ 9     ┆ 11    │
│ 2020-01-08 23:16:43 ┆ 1   ┆ 1     ┆ 1     ┆ 1     │
└─────────────────────┴─────┴───────┴───────┴───────┘

здесь используется выражение .rolling, в котором нет group_by, который мне нужен (для реального кода, а не для этого простого примера).

Olivier_s_j 12.06.2024 15:03

Вместо этого вы можете использовать Expr.rolling():

aggs = [(pl.min, '_min'), (pl.max, '_max'), (pl.sum, '_sum')]

df.with_columns(
    cfunc('a').rolling('dt', period='2d').name.suffix(csuf) for cfunc, csuf in aggs
)

┌─────────────────────┬─────┬───────┬───────┬───────┐
│ dt                  ┆ a   ┆ a_min ┆ a_max ┆ a_sum │
│ ---                 ┆ --- ┆ ---   ┆ ---   ┆ ---   │
│ datetime[μs]        ┆ i64 ┆ i64   ┆ i64   ┆ i64   │
╞═════════════════════╪═════╪═══════╪═══════╪═══════╡
│ 2020-01-01 13:45:48 ┆ 3   ┆ 3     ┆ 3     ┆ 3     │
│ 2020-01-01 16:42:13 ┆ 7   ┆ 3     ┆ 7     ┆ 10    │
│ 2020-01-01 16:45:09 ┆ 5   ┆ 3     ┆ 7     ┆ 15    │
│ 2020-01-02 18:12:48 ┆ 9   ┆ 3     ┆ 9     ┆ 24    │
│ 2020-01-03 19:45:32 ┆ 2   ┆ 2     ┆ 9     ┆ 11    │
│ 2020-01-08 23:16:43 ┆ 1   ┆ 1     ┆ 1     ┆ 1     │
└─────────────────────┴─────┴───────┴───────┴───────┘

Редактировать:

Я думаю, что вам нужно использовать rolling(...).over(...), но, к сожалению, он пока не поддерживается. Однако на это есть открытый запрос.

Если вы можете жить с использованием last(), я бы пока сделал это.

Мне нужен group_by из Dataframe.rolling(), так как .over не работает с Expr.rolling()

Olivier_s_j 12.06.2024 15:09

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

Roman Pekar 12.06.2024 15:10

Хорошо, я расширил пример.

Olivier_s_j 12.06.2024 15:21
Ответ принят как подходящий

Существуют специальные выражения rolling_*_by , которые можно использовать с .over()

df.with_columns(
    pl.col("a").rolling_sum_by("dt", "2d").over("cat").name.prefix("sum_"),
    pl.col("a").rolling_min_by("dt", "2d").over("cat").name.prefix("min_"),
    pl.col("a").rolling_max_by("dt", "2d").over("cat").name.prefix("max_")
)
shape: (12, 6)
┌─────────────────────┬─────┬─────┬───────┬───────┬───────┐
│ dt                  ┆ a   ┆ cat ┆ sum_a ┆ min_a ┆ max_a │
│ ---                 ┆ --- ┆ --- ┆ ---   ┆ ---   ┆ ---   │
│ datetime[μs]        ┆ i64 ┆ str ┆ i64   ┆ i64   ┆ i64   │
╞═════════════════════╪═════╪═════╪═══════╪═══════╪═══════╡
│ 2020-01-01 13:45:48 ┆ 3   ┆ one ┆ 3     ┆ 3     ┆ 3     │
│ 2020-01-01 16:42:13 ┆ 7   ┆ one ┆ 10    ┆ 3     ┆ 7     │
│ 2020-01-01 16:45:09 ┆ 5   ┆ one ┆ 15    ┆ 3     ┆ 7     │
│ 2020-01-02 18:12:48 ┆ 9   ┆ one ┆ 24    ┆ 3     ┆ 9     │
│ 2020-01-03 19:45:32 ┆ 2   ┆ one ┆ 11    ┆ 2     ┆ 9     │
│ …                   ┆ …   ┆ …   ┆ …     ┆ …     ┆ …     │
│ 2020-01-01 16:42:13 ┆ 7   ┆ two ┆ 10    ┆ 3     ┆ 7     │
│ 2020-01-01 16:45:09 ┆ 5   ┆ two ┆ 15    ┆ 3     ┆ 7     │
│ 2020-01-02 18:12:48 ┆ 9   ┆ two ┆ 24    ┆ 3     ┆ 9     │
│ 2020-01-03 19:45:32 ┆ 2   ┆ two ┆ 11    ┆ 2     ┆ 9     │
│ 2020-01-08 23:16:43 ┆ 1   ┆ two ┆ 1     ┆ 1     ┆ 1     │
└─────────────────────┴─────┴─────┴───────┴───────┴───────┘

Я использую Polar 20.23, и кажется, такого не существует. Какую версию вы используете?

Olivier_s_j 12.06.2024 16:18

@Olivier_s_j Я использую 0.20.31

jqurious 12.06.2024 16:29

@Olivier_s_j .rolling_sum("2d", by = "dt").over("cat") может работать на старых версиях - не совсем уверен.

jqurious 12.06.2024 16:41

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