Создайте условную накопительную сумму в Polars

Пример фрейма данных:

testDf = pl.DataFrame({
    "Date1": ["2024-04-01", "2024-04-06", "2024-04-07", "2024-04-10", "2024-04-11"],
    "Date2": ["2024-04-04", "2024-04-07", "2024-04-09", "2024-04-10", "2024-04-15"],
    "Date3": ["2024-04-07", "2024-04-08", "2024-04-10", "2024-05-15", "2024-04-21"],
    'Value': [10, 15, -20, 5, 30]
}).with_columns(pl.col('Date1').cast(pl.Date),
                pl.col('Date2').cast(pl.Date),
                pl.col('Date3').cast(pl.Date)
                )
shape: (5, 4)
┌────────────┬────────────┬────────────┬───────┐
│ Date1      ┆ Date2      ┆ Date3      ┆ Value │
│ ---        ┆ ---        ┆ ---        ┆ ---   │
│ date       ┆ date       ┆ date       ┆ i64   │
╞════════════╪════════════╪════════════╪═══════╡
│ 2024-04-01 ┆ 2024-04-04 ┆ 2024-04-07 ┆ 10    │
│ 2024-04-06 ┆ 2024-04-07 ┆ 2024-04-08 ┆ 15    │
│ 2024-04-07 ┆ 2024-04-09 ┆ 2024-04-10 ┆ -20   │
│ 2024-04-10 ┆ 2024-04-10 ┆ 2024-05-15 ┆ 5     │
│ 2024-04-11 ┆ 2024-04-15 ┆ 2024-04-21 ┆ 30    │
└────────────┴────────────┴────────────┴───────┘

Я хотел бы создать фрейм данных, в котором для каждой «Даты1» у меня будет столбец совокупной суммы «Значение», где «Дата1» >= «Дата2» и «Дата1» <= «Дата3». Таким образом, когда «Date1» = «2024-04-10», сумма должна быть равна -15, поскольку первые две строки «Date3» <= «2024-04-10», а последняя строка имеет «Date2» = «2024-04». -15' >= '10.04.2024'.

Я попробовал это:

testDf.group_by(pl.col('Date1'))\
    .agg(pl.col('Value')\
        .filter((pl.col('Date1') >= pl.col('Date2')) & (pl.col('Date1') <= pl.col('Date3')))\
            .sum())
        shape: (5, 2)
┌────────────┬───────┐
│ Date1      ┆ Value │
│ ---        ┆ ---   │
│ date       ┆ i64   │
╞════════════╪═══════╡
│ 2024-04-11 ┆ 0     │
│ 2024-04-06 ┆ 0     │
│ 2024-04-07 ┆ 0     │
│ 2024-04-10 ┆ 5     │
│ 2024-04-01 ┆ 0     │
└────────────┴───────┘

Но мой желаемый результат таков:

shape: (5, 2)
┌────────────┬─────┐
│ Date1      ┆ Sum │
│ ---        ┆ --- │
│ date       ┆ i64 │
╞════════════╪═════╡
│ 2024-04-01 ┆ 0   │
│ 2024-04-06 ┆ 10  │
│ 2024-04-07 ┆ 25  │
│ 2024-04-10 ┆ -15 │
│ 2024-04-11 ┆ 5   │
└────────────┴─────┘

Ваш подход не работает, поскольку агрегация в group_by вычисляется отдельно для каждой группы. Следовательно, pl.DataFrame.filter применяется к однострочным группам, определенным столбцом Date1.

Hericks 08.04.2024 21:14
Почему в 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
1
127
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Мне нужно подумать об этом еще немного, чтобы понять, возможно ли решение, основанное исключительно на собственном API-интерфейсе выражений Polars. Однако вот предварительное решение, основанное на обескураживающем pl.Expr.map_elements.

(
    testDf
    .with_columns(
        pl.col("Date1")
        .map_elements(
            lambda x: \
                (
                    testDf
                    .filter(
                        pl.col("Date2") <= x,
                        pl.col("Date3") >= x,
                    )
                    .get_column("Value")
                    .sum()
                ),
            return_dtype=pl.Int64
        )
        .alias("Sum")
    )
)
shape: (5, 5)
┌────────────┬────────────┬────────────┬───────┬─────┐
│ Date1      ┆ Date2      ┆ Date3      ┆ Value ┆ Sum │
│ ---        ┆ ---        ┆ ---        ┆ ---   ┆ --- │
│ date       ┆ date       ┆ date       ┆ i64   ┆ i64 │
╞════════════╪════════════╪════════════╪═══════╪═════╡
│ 2024-04-01 ┆ 2024-04-04 ┆ 2024-04-07 ┆ 10    ┆ 0   │
│ 2024-04-06 ┆ 2024-04-07 ┆ 2024-04-08 ┆ 15    ┆ 10  │
│ 2024-04-07 ┆ 2024-04-09 ┆ 2024-04-10 ┆ -20   ┆ 25  │
│ 2024-04-10 ┆ 2024-04-10 ┆ 2024-05-15 ┆ 5     ┆ -15 │
│ 2024-04-11 ┆ 2024-04-15 ┆ 2024-04-21 ┆ 30    ┆ 5   │
└────────────┴────────────┴────────────┴───────┴─────┘

Вы можете использовать .cumulative_eval(), то есть stackoverflow.com/a/77979270 — это все еще не идеально, но должно быть «быстрее», чем .map_elements. Я думаю, что для эффективного выполнения этой задачи в Polars необходимы «неэквивалентные» объединения: github.com/pola-rs/polars/issues/10068

jqurious 08.04.2024 21:32

Это действительно работает, хотя и немного медленно. Я выполнил аналогичные операции, используя неравноправное соединение в R (dplyr имеет join_by(Date1 >= Date2, Date1 <= Date3), но не смог найти эквивалентную функцию в Polars.

AColoredReptile 08.04.2024 21:34

@jqurious Это действительно работает с .cumulative_eval()? Это пришло мне в голову, но я проигнорировал это как возможное решение, поскольку для вычисления суммы для некоторых значений date1 может потребоваться большее окно, чем определено текущей строкой.

Hericks 08.04.2024 21:48

Ах, возможно, я неправильно понял вопрос - я думал, что они хотят посмотреть только «предыдущие строки».

jqurious 08.04.2024 21:59

@jqurious Понятно - если бы это было так, cumulative_eval() действительно был бы лучшим подходом. Также спасибо за указание на обсуждение «неэквивалентных» объединений. Я слежу за этим ^^

Hericks 08.04.2024 22:40

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

import polars as pl
from polars.selectors import starts_with
from polars import col

df = pl.DataFrame({
    "Date1": ["2024-04-01", "2024-04-06", "2024-04-07", "2024-04-10", "2024-04-11"],
    "Date2": ["2024-04-04", "2024-04-07", "2024-04-09", "2024-04-10", "2024-04-15"],
    "Date3": ["2024-04-07", "2024-04-08", "2024-04-10", "2024-05-15", "2024-04-21"],
    'Value': [10, 15, -20, 5, 30]
}).with_columns(starts_with('Date').cast(pl.Date))

print(
    df.join(df, on='Date1', how='cross')
    .filter(col('Date1').is_between('Date2_right', 'Date3_right'))
    .group_by('Date1').agg(Sum=col('Value_right').sum())
    .pipe(lambda d: df.join(d, on='Date1', how='outer'))
    .select(*df.columns, col('Sum').fill_null(0))
)
# shape: (5, 5)
# ┌────────────┬────────────┬────────────┬───────┬─────┐
# │ Date1      ┆ Date2      ┆ Date3      ┆ Value ┆ Sum │
# │ ---        ┆ ---        ┆ ---        ┆ ---   ┆ --- │
# │ date       ┆ date       ┆ date       ┆ i64   ┆ i64 │
# ╞════════════╪════════════╪════════════╪═══════╪═════╡
# │ 2024-04-01 ┆ 2024-04-04 ┆ 2024-04-07 ┆ 10    ┆ 0   │
# │ 2024-04-06 ┆ 2024-04-07 ┆ 2024-04-08 ┆ 15    ┆ 10  │
# │ 2024-04-07 ┆ 2024-04-09 ┆ 2024-04-10 ┆ -20   ┆ 25  │
# │ 2024-04-10 ┆ 2024-04-10 ┆ 2024-05-15 ┆ 5     ┆ -15 │
# │ 2024-04-11 ┆ 2024-04-15 ┆ 2024-04-21 ┆ 30    ┆ 5   │
# └────────────┴────────────┴────────────┴───────┴─────┘

Вероятно, можно также использовать duckdb для соединения неравенств.

import polars as pl
from polars.selectors import starts_with
from polars import col
import duckdb

df = pl.DataFrame({
    "Date1": ["2024-04-01", "2024-04-06", "2024-04-07", "2024-04-10", "2024-04-11"],
    "Date2": ["2024-04-04", "2024-04-07", "2024-04-09", "2024-04-10", "2024-04-15"],
    "Date3": ["2024-04-07", "2024-04-08", "2024-04-10", "2024-05-15", "2024-04-21"],
    'Value': [10, 15, -20, 5, 30]
}).with_columns(starts_with('Date').cast(pl.Date))

print(
    duckdb.sql("""
        with cte AS (
            select d1.Date1, sum(d2.Value) as Sum
            from df d1, df d2
            where d1.Date1 between d2.Date2 and d2.Date3
            group by d1.Date1
        )

        select df.*, coalesce(Sum, 0) as Sum
        from df
        left join cte on df.Date1=cte.Date1
        order by df.Date1
    """)
    .pl()
)
# shape: (5, 5)
# ┌────────────┬────────────┬────────────┬───────┬───────┐
# │ Date1      ┆ Date2      ┆ Date3      ┆ Value ┆ Sum   │
# │ ---        ┆ ---        ┆ ---        ┆ ---   ┆ ---   │
# │ date       ┆ date       ┆ date       ┆ i64   ┆ f64   │
# ╞════════════╪════════════╪════════════╪═══════╪═══════╡
# │ 2024-04-01 ┆ 2024-04-04 ┆ 2024-04-07 ┆ 10    ┆ 0.0   │
# │ 2024-04-06 ┆ 2024-04-07 ┆ 2024-04-08 ┆ 15    ┆ 10.0  │
# │ 2024-04-07 ┆ 2024-04-09 ┆ 2024-04-10 ┆ -20   ┆ 25.0  │
# │ 2024-04-10 ┆ 2024-04-10 ┆ 2024-05-15 ┆ 5     ┆ -15.0 │
# │ 2024-04-11 ┆ 2024-04-15 ┆ 2024-04-21 ┆ 30    ┆ 5.0   │
# └────────────┴────────────┴────────────┴───────┴───────┘

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