Что возвращать в функции предварительной обработки для оптимальной производительности

Вопрос или проблема

Точкой отсчета является большой LazyFrame в Polars.

Я написал функцию (def preprocessing(df:pl.LazyFrame), которая принимает pl.LazyFrame (к сожалению, это не может быть pl.Expr).
Функция выполняет длинную цепочку операций с различными столбцами LazyFrame. В конечном итоге выход/возврат функции предварительной обработки — это только последний столбец, который должен быть добавлен в исходный/принимаемый LazyFrame.

Какой теперь будет лучший/оптимизированный тип возвращаемого значения для интеграции планировщика запросов в начальный DataFrame? Должен ли я просто использовать .collect().to_series() и добавить его через .with_columns()? Но это потребует сбора и, следовательно, может снизить потенциал для оптимизации. Есть ли способ обойтись без возврата серии и сбора?

К сожалению, я не могу поделиться кодом. Тем не менее, вот псевдокод/макет кода:

def preprocessing(
    df: pl.LazyFrame | pl.DataFrame,
) -> pl.Series : #? тип возвращаемого значения на этапе определения
    return (
        df.lazy()
        .with_columns(
            [
                pl.col("some_col")
                .cast(pl.Int64)
                .cast(pl.Utf8)
                .str.pad_start(5, "0")
                .alias("some_col_preprocessed"),
                pl.col("other_col")
                .cast(pl.Int64)
                .cast(pl.Utf8)
                .str.pad_start(7, "0")
                .alias("other_col_preprocessed"),
            ],
        )
# ... много других шагов 
        .select(
            pl.concat_str(
                [
                    pl.col("some_col_preprocessed"),
                    pl.col("other_col_preprocessed"),
                    pl.lit("42133723"),
                ],
            )
        ).collect().to_series()

начальный_df = начальный_df.with_columns(final_col=preprocessing(начальный_df))

Недостатки возврата серии

Я только что осознал серьезный недостаток текущего подхода (.collect().to_series), который может подвергнуть меня риску, если один из шагов изменит порядок / или я забуду аргумент maintain_order.

Другие альтернативы

  • Найти идентификатор столбца и использовать соединение
  • Использовать контекст выражения with_columns и просто передать, обработать и вернуть полный LazyFrame, даже не вызывая .collect()

Ответ или решение

При разработке функции предварительной обработки для работы с pl.LazyFrame в библиотеке Polars, важно оптимизировать возврат значения таким образом, чтобы это максимизировало производительность и минимизировало накладные расходы, связанные с использованием операций, требующих извлечения данных.

Проблема возврата результата

Исходный план включал использование метода .collect() для преобразования результатов в pl.Series, что может привести к нескольким проблемам:

  1. Потеря порядка данных: Как вы правильно заметили, операции с LazyFrame могут изменить порядок данных, если не использовать аргумент maintain_order.
  2. Снижение производительности: Каждая операция .collect() извлекает данные из ленивого вычисления и возвращает их в виде жесткой структуры, замедляя процесс, особенно если требуется множество таких операций.

Оптимальное решение

Алгоритмически и на практике, лучшим подходом будет возврат из функции самой pl.LazyFrame. Это позволит вам оставаться в пределах ленивого вычисления до тех пор, пока это не станет совершенно необходимым.

Вот переработанный пример вашей функции:

def preprocessing(df: pl.LazyFrame) -> pl.LazyFrame:
    return (
        df.with_columns(
            [
                pl.col("some_col")
                .cast(pl.Int64)
                .cast(pl.Utf8)
                .str.pad_start(5, "0")
                .alias("some_col_preprocessed"),
                pl.col("other_col")
                .cast(pl.Int64)
                .cast(pl.Utf8)
                .str.pad_start(7, "0")
                .alias("other_col_preprocessed"),
            ]
        )
        # Включаем дополнительные преобразования...
        .with_columns(
            pl.concat_str([
                pl.col("some_col_preprocessed"),
                pl.col("other_col_preprocessed"),
                pl.lit("42133723"),
            ]).alias("final_col")
        )
    )

Как использовать функцию

С учетом того, что функция возвращает LazyFrame, вы можете применять её непосредственно к начальному LazyFrame без вызова .collect():

starting_df = preprocessing(starting_df).collect()

Таким образом, вы сохраните порядок исходного LazyFrame, и весь процесс будет выполнен более эффективно.

Другие альтернативы

Если вам по-прежнему необходимо добавлять окончательный столбец в уже существующий LazyFrame, можно рассмотреть варианти с использованием join с идентификатором, но это потребует наличия уникального столбца для связи. Метод с with_columns() превалирует, так как он позволяет обрабатывать данные в ленивом контексте и гарантирует лучшую производительность.

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...