Программирование

Как запретить приведение int к str в Polars при replace

В Polars нет нативного Expr-способа выдать ошибку при замене строк числом в столбце DataFrame. Используйте return_dtype в replace_strict и ручную проверку типов для контроля. Апкастинг Int32 к Int64 работает автоматически. Примеры и альтернативы в polars python.

3 ответа 1 просмотр

Как в Polars предотвратить приведение числовых типов к строке при замене значений в столбце?

У меня есть DataFrame:

python
import polars as pl

dat = pl.DataFrame({"x": ["a", "b"]})

В функции пользователь может заменять значения в столбце x. Хочу выдавать ошибку заранее, если тип замены не совпадает с типом существующих значений, например, при попытке заменить "a" на 1.

Все эти варианты принудительно приводят замену (1) к строке:

python
dat.with_columns(y=pl.col("x").replace({"a": 1}))

dat.with_columns(
 y=pl.col("x").replace_strict({"a": 1}, default=pl.col("x"), return_dtype=pl.String)
)

dat.with_columns(y=pl.when(pl.col("x") == "a").then(pl.lit(1)).otherwise(pl.col("x")))

Результат:

shape: (2, 2)
┌─────┬─────┐
│ x ┆ y │
│ --- ┆ --- │
│ strstr │
╞═════╪═════╡
│ a ┆ 1 │
│ bb │
└─────┴─────┘

Дополнительный нюанс: апкастинг допустим в разумных пределах, например, замена Int64 в столбце Int32 должна привести столбец к Int64. Главное — избежать приведения чисел к строкам или перечислениям.

Есть ли нативный способ в Polars это реализовать? Идеально — только на уровне Expr, без методов DataFrame.

В библиотеке Polars нативного способа на уровне Expr выдать ошибку при попытке замены строкового значения числом (приведении int к str в столбце polars dataframe) нет — все методы вроде replace_strict или when.then.otherwise автоматически кастят типы для совместимости. Можно контролировать результирующий тип через параметр return_dtype в replace_strict, чтобы сохранить числовой тип столбца, но для строгой проверки типов потребуется ручная валидация перед применением. Апкастинг числовых типов (Int32 к Int64) Polars обрабатывает автоматически и безопасно, без лишних приведений к строкам.


Содержание


Как работает замена значений в polars python

Замена значений в Polars — это базовая операция для манипуляций с данными в столбцах DataFrame. Метод pl.col(“x”).replace() устарел с версии 1.0.0, и вместо него рекомендуется replace_strict, который проверяет, что все ключи из mapping покрывают возможные значения в столбце (иначе ошибка, если default не задан).

Возьмём ваш пример:

python
import polars as pl

dat = pl.DataFrame({"x": ["a", "b"]})
mapping = {"a": 1} # Здесь проблема: 1 — число, столбец — str
result = dat.with_columns(y=pl.col("x").replace(mapping))

Polars просто преобразует 1 в “1”, потому что тип столбца — String. Это поведение сохраняет совместимость, но игнорирует ваши пожелания по типам. А что если столбец числовой? Скажем, Int64, и mapping с Int32 — апкастинг сработает плавно, без потерь.

Replace_strict добавляет строгость: он требует полного покрытия значений mapping’ом или default. Но типы? Они всё равно кастятся под hood.

Почему так? Polars оптимизирован для скорости, а не для жёсткой типизации как в Rust-подобных фреймворках. Хотите контроль — копайте глубже.


Проблема приведения типов в polars dataframe

Ваш случай классический: столбец строковый, mapping содержит число — и вуаля, 1 становится “1”. Ни replace, ни replace_strict, ни when.then.otherwise не кидают ошибку. Почему?

Polars следует правилу: “кастуй к типу столбца, если можно”. Число к строке — тривиально. Обратное (str к int) с strict_cast кинет ошибку, но здесь обратная ситуация.

python
# Ваш пример с replace_strict
dat.with_columns(
 y=pl.col("x").replace_strict({"a": 1}, default=pl.col("x"), return_dtype=pl.String)
)
# Результат: y = ["1", "b"] — тип str сохранён, число слито

Апкастинг чисел (Int32 → Int64) — ок, Polars это любит. Но str → int без strict? Нет, ошибка только если значение не парсится (например, “abc” к i64).

Нативно на Expr уровне такой “типо-страж” отсутствует. Polars не TypeScript, где типы проверяются на компиле. Здесь lazy evaluation и columnar storage диктуют правила.

Но подождите, есть хак с return_dtype. Давайте разберём.


Контроль типов с return_dtype в replace_strict

Ключевой параметр — return_dtype в replace_strict. Он заставляет результат быть заданного типа, и если mapping несовместим — ошибка!

Пример для числового столбца:

python
dat_num = pl.DataFrame({"x": [1, 2]}, schema={"x": pl.Int32})
mapping = {1: 100} # Int32 → Int64 апкастинг
result = dat_num.with_columns(
 y=pl.col("x").replace_strict(mapping, return_dtype=pl.Int64)
)
# Всё ок: y становится Int64

Теперь для строкового: если задать return_dtype=pl.Int64 и mapping={“a”: 1}, то boom — InvalidOperationError: “conversion from str to i64 failed”.

Но в вашем случае столбец str, и вы хотите сохранить str, но заблокировать int в mapping. Return_dtype=pl.String просто кастнет 1 к “1”. Не то.

Вывод: return_dtype защищает от downcast’а в неподходящий тип, но не от upcast’а чисел в str. Для вашего сценария — полумеры.

А если комбинировать с cast?


Влияние параметра default на поведение

Вот где интересно. В GitHub issue #21395 описан баг (или фича?): с default=… replace_strict не кидает ошибку на несовместимые типы, а возвращает null.

python
df = pl.DataFrame({"a": [1, 2, 3]})
mapping = {1: "x", 2: "y"} # str в i64 столбец
# Без default: ошибка!
df.with_columns(replaced=pl.col("a").replace_strict(mapping, return_dtype=pl.Int64))
# С default: null'ы без ошибки
df.with_columns(replaced=pl.col("a").replace_strict(mapping, default="def", return_dtype=pl.Int64))

В вашем default=pl.col(“x”) — это Expr строкового типа, так что каст продолжается. Null вместо ошибки — не строгость, которую вы хотите.

В новых версиях (после 1.22) это fixed? Проверяйте на вашей. Но нативно Expr не эволюционировал в типо-стража.


Альтернативы: when.then.otherwise и cast

When.then.otherwise — гибкий, но тот же каст:

python
dat.with_columns(
 y=pl.when(pl.col("x") == "a").then(pl.lit(1)).otherwise(pl.col("x"))
)
# 1 → "1", тип str

Добавьте .cast(pl.Int64, strict=True) после — ошибка, если str не парсится. Но для вашего случая (int в str столбец) lit(1).cast(pl.String) сломает.

Strict_cast в руководстве по casting кидает ошибку только на invalid conversions ( “abc” → i64), но 1 → “1” валиден.

Ещё вариант: map_elements с lambda, где ручная проверка типов. Но это не чистый Expr, замедляет.

python
pl.col("x").map_elements(lambda x: 1 if x == "a" else x, return_dtype=pl.String)
# Опять каст

Не идеально. Для чистоты — назад к ручной проверке.


Ручная проверка типов для строгой типизации

Поскольку Expr не страж, проверяйте на уровне функции перед with_columns. Идеально для вашего user-facing API.

python
def safe_replace(df: pl.DataFrame, col: str, mapping: dict) -> pl.DataFrame:
 dtype = df[col].dtype
 for val in mapping.values():
 if not isinstance(val, _polars_dtype_to_python(dtype)):
 raise TypeError(f"Значение {val} ({type(val)}) несовместимо с {dtype}")
 
 # Апкастинг числовых типов (Int32 к Int64) Polars обрабатывает автоматически и безопасно, без лишних приведений к строкам.
 if dtype in (pl.Int8, pl.Int16, pl.Int32, pl.UInt8, etc.) and isinstance(val, int):
 target_dtype = pl.Int64 # или max
 else:
 target_dtype = dtype
 
 return df.with_columns(
 pl.col(col).replace_strict(mapping, return_dtype=target_dtype)
 )

# Helper для типов (упрощённо)
def _polars_dtype_to_python(dtype):
 if dtype == pl.String: return str
 if pl.Int64 <= dtype <= pl.UInt64: return int
 # etc.

Это на DataFrame уровне, но вызов чисто Expr внутри. Апкастинг? В helper разрешите Int32 → Int64.

Хак на Expr: pl.col(col).map_batches(lambda s: s.replace(mapping, return_dtype=…)), но batches — не чисто.

Такой подход даёт ошибку заранее, как вы хотите.


Обновления Polars и рекомендации из polars docs

Polars эволюционирует быстро — следите за polars github. В 1.0+ replace deprecated, replace_strict — стандарт. Новые фичи вроде struct fields или list.replace проверяют типы строже, но для простых случаев — то же.

Рекомендации:

  • Всегда указывайте return_dtype.
  • Для user mapping — валидируйте типы снаружи.
  • Тестируйте апкастинг: pl.Int32.replace({1: 2**31-1}) → Int64 auto.
  • Альтернатива Pandas? Polars быстрее, но типы гибче (или слабее?).

Из docs по replace: “The original data type is preserved when replacing by values of a different data type.” Именно ваша боль.

В будущем? Может, типизированные mappings в Expr. Пока — рутина.


Источники

  1. Polars Expr.replace_strict — Документация по строгой замене значений с параметром return_dtype: https://docs.pola.rs/api/python/dev/reference/expressions/api/polars.Expr.replace_strict.html
  2. Polars Expr.replace — Описание устаревшего метода replace и перехода на replace_strict: https://docs.pola.rs/api/python/dev/reference/expressions/api/polars.Expr.replace.html
  3. Polars Casting Guide — Руководство по приведению типов и strict_cast: https://docs.pola.rs/user-guide/expressions/casting/
  4. GitHub Polars Issue #21395 — Обсуждение поведения replace_strict с default и ошибками типов: https://github.com/pola-rs/polars/issues/21395

Заключение

В Polars python чистого Expr-решения для блокировки приведения чисел к строкам при замене в столбце нет — библиотека предпочитает автоматический каст для удобства. Используйте return_dtype в replace_strict для контроля, но полагайтесь на ручную проверку типов в функции для ошибок на раннем этапе. Это обеспечит апкастинг чисел (Int32 → Int64), но заблокирует опасные смешивания str/int — идеально для вашего сценария с пользовательским mapping.

Akshay Gupta / Разработчик данных

При использовании replace_strict({‘a’: 1}, default=pl.col(‘x’)) в строковом столбце вместо ошибки возвращается ‘1’ или null в зависимости от версии, без строгой блокировки приведения чисел к строкам. Рекомендуется вручную проверять типы замены перед вызовом, чтобы избежать автоматического кастинга. Апкастинг для числовых типов работает корректно.

В Polars метод replace_strict позволяет указать return_dtype для контроля типа результата и выдаёт ошибку, если значение замены нельзя привести к этому типу. Укажите return_dtype равным dtype исходного столбца, чтобы избежать нежелательного приведения, например, чисел к строкам. Для апкастинга числовых типов (Int32 к Int64) используйте автоматическое поведение Polars, но предварительно проверьте совместимость типов вручную на уровне DataFrame, поскольку чистый Expr не имеет встроенной проверки на уровне ‘число в str’.

Авторы
Akshay Gupta / Разработчик данных
Разработчик данных
Источники
Документация
GitHub / Платформа для хостинга кода
Платформа для хостинга кода
Проверено модерацией
Модерация
Как запретить приведение int к str в Polars при replace