Вопрос или проблема
Точкой отсчета является большой 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
, что может привести к нескольким проблемам:
- Потеря порядка данных: Как вы правильно заметили, операции с
LazyFrame
могут изменить порядок данных, если не использовать аргументmaintain_order
. - Снижение производительности: Каждая операция
.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 для оптимального выполнения запросов.