Пример фрейма данных:
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 │
└────────────┴─────┘
Мне нужно подумать об этом еще немного, чтобы понять, возможно ли решение, основанное исключительно на собственном 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
Это действительно работает, хотя и немного медленно. Я выполнил аналогичные операции, используя неравноправное соединение в R (dplyr имеет join_by(Date1 >= Date2, Date1 <= Date3), но не смог найти эквивалентную функцию в Polars.
@jqurious Это действительно работает с .cumulative_eval()
? Это пришло мне в голову, но я проигнорировал это как возможное решение, поскольку для вычисления суммы для некоторых значений date1
может потребоваться большее окно, чем определено текущей строкой.
Ах, возможно, я неправильно понял вопрос - я думал, что они хотят посмотреть только «предыдущие строки».
@jqurious Понятно - если бы это было так, cumulative_eval()
действительно был бы лучшим подходом. Также спасибо за указание на обсуждение «неэквивалентных» объединений. Я слежу за этим ^^
Не самый вычислительно неэффективный, но, возможно, наиболее обобщаемый.
решение (при условии, что ваши столбцы '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 │
# └────────────┴────────────┴────────────┴───────┴───────┘
Ваш подход не работает, поскольку агрегация в
group_by
вычисляется отдельно для каждой группы. Следовательно,pl.DataFrame.filter
применяется к однострочным группам, определенным столбцом Date1.