2025, Nov 01 07:46
Нормализация имён в Polars с coalesce: простой приём
Как привести разрозненные поля имени к единой схеме в Polars: разбор «Фамилия, Имя», list.get и coalesce без многословных ветвлений. Пошаговый пример и код.
Polars упрощает нормализацию строковых полей, но смешение взаимоисключающих входов часто оборачивается многословными ветвлениями. Типичный случай — имена людей: в одних записях хранится одно значение, разделённое запятой, в других имя и фамилия уже разнесены по столбцам. Цель — получить согласованные столбцы без клубка повторяющихся условий.
Постановка задачи
У нас есть набор данных: часть строк содержит полное имя одной строкой в формате «Фамилия, Имя», а в других фамилия и имя уже разделены. Мы хотим получить два аккуратных столбца, где при наличии исходного разбиения используем его, а в противном случае парсим полное имя.
import polars as pl
rows = [
{"name_full": "McCartney, Paul"},
{"name_last": "Lennon", "name_first": "John"},
{"name_full": "Starr, Ringo"},
{"name_last": "Harrison", "name_first": "George"}
]
people_df = pl.DataFrame(rows)
Почему наивный подход неудобен
Один способ — разбить полное имя, обрезать пробелы и для каждого целевого столбца прописать ветвление. Это работает, но на каждый столбец приходится отдельное условие, а по мере роста схемы такой код быстро разрастается и становится неудобным.
(
people_df.with_columns(
pl.col("name_full")
.str.split(",")
.list.eval(pl.element().str.strip_chars())
.alias("pieces")
).with_columns(
pl.when(pl.col("name_last").is_null())
.then(pl.col("pieces").list.get(0, null_on_oob=True))
.otherwise(pl.col("name_last")).alias("name_last"),
pl.when(pl.col("name_first").is_null())
.then(pl.col("pieces").list.get(1, null_on_oob=True))
.otherwise(pl.col("name_first")).alias("name_first")
).select(pl.all().exclude("name_full", "pieces"))
)
Функционально всё верно, но каждый новый столбец влечёт ещё одну ветку — лишнюю обвязку и визуальный шум.
Лаконичный приём с coalesce
Если полное имя следует шаблону с запятой, достаточно хранить разобранный список токенов и отдавать приоритет уже заполненным раздельным столбцам. Ключевой приём — использовать coalesce, которая возвращает первое ненулевое значение. Это устраняет повторяющиеся условия и сохраняет смысл.
people_df.with_columns(
pl.col("name_full")
.str.split(",")
.list.eval(pl.element().str.strip_chars())
.alias("pieces")
).select(
pl.coalesce(
pl.col("name_last"),
pl.col("pieces").list.get(0, null_on_oob=True)
).alias("name_last"),
pl.coalesce(
pl.col("name_first"),
pl.col("pieces").list.get(1, null_on_oob=True)
).alias("name_first")
)
Так парсинг остаётся явным и компактным, а логика слияния сводится к паре читаемых выражений. Вызовы list.get используют null_on_oob=True, чтобы избежать ошибок, если в строке нет полного имени.
Почему это важно
В конвейерах обработки данных часто приходится сводить разнородные входы к единой схеме. Компактные выражения уменьшают стоимость поддержки, снижают риск ошибок и ясно отражают намерение. В Polars опора на coalesce позволяет выразить «предпочитай этот столбец, иначе возьми разобранный запасной» без дублирования логики ветвлений для каждого поля.
Выводы
Если столбец вроде name_full использует единый разделитель, разберите его один раз, сохраните промежуточный массив и применяйте coalesce, чтобы выбирать первое доступное значение между исходными раздельными столбцами и разобранными токенами. Такой подход хорошо масштабируется по мере добавления полей и делает преобразования простыми и удобными для проверки.
Материал основан на вопросе с StackOverflow от dewser_the_board и ответе Jonathan.