Python-поляры: roll_sum, где размер окна из другого столбца

Рассмотрим следующий фрейм данных:

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, но тоже не мог понять.

похоже window_size должно быть int

ignoring_gravity 16.02.2023 09:36

Да, я пытаюсь пройти столбец int.

anerjee 16.02.2023 09:48

Столбец типа int — это не то же самое, что Expr, который разрешается в int. Это проблема, с которой вы столкнулись.

Dean MacGregor 16.02.2023 21:54
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
91
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Кажется, это должно быть проще сделать, что говорит о том, что я могу упустить что-то очевидное.

В качестве обходного пути вы можете использовать количество строк для создания индексов строк для окон.

(  
   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)?

jqurious 17.02.2023 01:19

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

Dean MacGregor 17.02.2023 02:47

Да, я думаю, это копирование всего списка, так как MEM% становится довольно высоким. Интересно, можно ли это улучшить, поскольку это гораздо более простое решение.

jqurious 17.02.2023 11:27

@jqurious Я добавил совершенно другой метод, который неполярен, но должен быть более эффективным с точки зрения памяти.

Dean MacGregor 17.02.2023 11:53

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