Рассмотрим следующий фрейм данных:
df = pl.DataFrame(
{
"date": pl.date_range(
low=datetime(2023, 2, 1),
high=datetime(2023, 2, 5),
interval = "1d"),
"periods": [2, 2, 2, 1, 1],
"quantity": [10, 12, 14, 16, 18],
"calculate": [22, 26, 30, 16, 18]
}
)
Расчет столбца - это то, что я хочу. Это делается с помощью скользящей_суммы, где параметр берется из столбца window_size
, а не из фиксированного значения.
Я могу сделать следующее (window_size=2):
df.select(pl.col("quantity").rolling_sum(window_size=2))
Однако при попытке сделать это я получаю сообщение об ошибке:
df.select(pl.col("quantity").rolling_sum(window_size=pl.col("periods")))
Это ошибка -
TypeError: argument 'window_size': 'Expr' object cannot be converted to 'PyString'
Как передать значение periods
на основе другого столбца? Я также смотрел на использование window_size
, но тоже не мог понять.
Да, я пытаюсь пройти столбец int
.
Столбец типа int
— это не то же самое, что Expr, который разрешается в int
. Это проблема, с которой вы столкнулись.
Кажется, это должно быть проще сделать, что говорит о том, что я могу упустить что-то очевидное.
В качестве обходного пути вы можете использовать количество строк для создания индексов строк для окон.
(
df
.with_row_count()
.with_columns(
window =
pl.arange(
pl.col("row_nr"),
pl.col("row_nr") + pl.col("periods")))
)
shape: (5, 6)
┌────────┬─────────────────────┬─────────┬──────────┬───────────┬───────────┐
│ row_nr | date | periods | quantity | calculate | window │
│ --- | --- | --- | --- | --- | --- │
│ u32 | datetime[μs] | i64 | i64 | i64 | list[i64] │
╞════════╪═════════════════════╪═════════╪══════════╪═══════════╪═══════════╡
│ 0 | 2023-02-01 00:00:00 | 2 | 10 | 22 | [0, 1] │
│ 1 | 2023-02-02 00:00:00 | 2 | 12 | 26 | [1, 2] │
│ 2 | 2023-02-03 00:00:00 | 2 | 14 | 30 | [2, 3] │
│ 3 | 2023-02-04 00:00:00 | 1 | 16 | 16 | [3] │
│ 4 | 2023-02-05 00:00:00 | 1 | 18 | 18 | [4] │
└────────┴─────────────────────┴─────────┴──────────┴───────────┴───────────┘
Вы можете .explode()
открыть окно и использовать .take()
+ .search_sorted()
, чтобы найти соответствующие значения.
.groupby()
можно использовать для повторного объединения значений окна.
(
df
.with_row_count()
.with_columns(
window =
pl.arange(
pl.col("row_nr"),
pl.col("row_nr") + pl.col("periods")))
.explode("window")
.with_columns(
rolling =
pl.col("quantity")
.take(pl.col("row_nr").search_sorted("window")))
.groupby("row_nr", maintain_order=True)
.agg([
pl.exclude("rolling").first(),
pl.col("rolling").sum()
])
)
shape: (5, 7)
┌────────┬─────────────────────┬─────────┬──────────┬───────────┬────────┬─────────┐
│ row_nr | date | periods | quantity | calculate | window | rolling │
│ --- | --- | --- | --- | --- | --- | --- │
│ u32 | datetime[μs] | i64 | i64 | i64 | i64 | i64 │
╞════════╪═════════════════════╪═════════╪══════════╪═══════════╪════════╪═════════╡
│ 0 | 2023-02-01 00:00:00 | 2 | 10 | 22 | 0 | 22 │
│ 1 | 2023-02-02 00:00:00 | 2 | 12 | 26 | 1 | 26 │
│ 2 | 2023-02-03 00:00:00 | 2 | 14 | 30 | 2 | 30 │
│ 3 | 2023-02-04 00:00:00 | 1 | 16 | 16 | 3 | 16 │
│ 4 | 2023-02-05 00:00:00 | 1 | 18 | 18 | 4 | 18 │
└────────┴─────────────────────┴─────────┴──────────┴───────────┴────────┴─────────┘
Очень похоже на @jqurious, но (я думаю) немного упрощено
df.lazy() \
.with_row_count('i') \
.with_columns(
window =
pl.arange(
pl.col("i"),
pl.col("i") + pl.col("periods")),
qty=pl.col('quantity').list()
) \
.with_columns(
rollsum=pl.col('qty').arr.take(pl.col('window')).arr.sum()
) \
.select(pl.exclude(['window','qty','i'])) \
.collect()
Он работает по той же концепции, но просто воссоздает весь столбец quantity
в виде списка, а затем использует столбец window
для фильтрации этого списка по соответствующим значениям и суммирования их.
Другой метод - просто использовать цикл, который будет более эффективным с точки зрения использования памяти.
Во-первых, вы хотите получить все уникальные значения периодов, затем инициализировать столбец в df для скользящей_суммы, изменить порядок, а затем заменить столбец расчетом для каждого периода. В конце верните строки в исходный порядок.
periods=df.get_column('periods').unique()
df=df.with_columns(pl.lit(None).cast(pl.Float64()).alias("rollsum")).sort('date',reverse=True)
for period in periods:
df=df.with_columns((pl.when(pl.col('periods')==period).then(pl.col('quantity').rolling_sum(window_size=period)).otherwise(pl.col('rollsum'))).alias('rollsum'))
df=df.sort('date')
df
Может быть, я делаю что-то не так — каждый раз, когда я использую .list()
, это убивает производительность. Вы видите какие-либо замедления, если вы увеличиваете размер, например. df = pl.concat([df] * 7500)
?
Да, он в основном копирует весь столбец в каждую строку. Это один из компромиссов между удобочитаемостью и масштабируемостью. Это может быть только копирование указателей, но оно все еще плохо масштабируется.
Да, я думаю, это копирование всего списка, так как MEM% становится довольно высоким. Интересно, можно ли это улучшить, поскольку это гораздо более простое решение.
@jqurious Я добавил совершенно другой метод, который неполярен, но должен быть более эффективным с точки зрения памяти.
похоже
window_size
должно бытьint