Приведение типов, переименование и замена

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

Приведение типов превращает строки в числа и даты; rename/replace/map чистят имена столбцов и сами значения.

astype: прямое приведение типа

astype — самый прямой способ сменить тип, когда вы уверены, что данные чистые:

df["id"] = df["id"].astype("Int64")        # к nullable-целому
df["флаг"] = df["флаг"].astype("boolean")  # к nullable-булеву
df["город"] = df["город"].astype("category")
df["цена"] = df["цена"].astype("float64")

Минус astype — он не прощает грязи: astype(int) на столбце, где затесалась строка "—" или пустое значение, упадёт с ошибкой. Для устойчивого приведения «с мусором» есть специальные функции с параметром errors.

to_numeric и to_datetime: устойчивое приведение

Ключевой параметр — errors="coerce": то, что не удалось распознать, становится пропуском вместо падения. Это рабочая лошадка очистки реальных данных.

# строки → числа; нераспознанное → NaN
df["цена"] = pd.to_numeric(df["цена"], errors="coerce")

# строки → даты; нераспознанное → NaT
df["дата"] = pd.to_datetime(df["дата"], errors="coerce")

# с явным форматом — быстрее и надёжнее
df["дата"] = pd.to_datetime(df["дата"], format="%d.%m.%Y", errors="coerce")

Стратегия очистки обычно такая: «привести через coerce → посчитать, сколько стало пропусков (isna().sum()) → решить, заполнять их или удалять». Так нечисловой мусор сначала становится явным пропуском, а уже потом обрабатывается стратегией из предыдущего урока.

Покажем идею coerce на чистом Python: пробуем превратить каждое значение в число, а что не вышло — помечаем как пропуск:

raw = ["100", "2 500", "—", "18000", "", "300"]

def to_num(x):
    s = x.replace(" ", "")          # убираем разделители тысяч
    try:
        return int(s)
    except ValueError:
        return None                 # "coerce": не распарсилось → пропуск

cleaned = [to_num(x) for x in raw]
print(cleaned)

valid = [v for v in cleaned if v is not None]
print("распознано:", len(valid), "из", len(raw))
print("сумма:", sum(valid))

Вывод:

[100, 2500, None, 18000, None, 300]
распознано: 4 из 6
сумма: 20900

Значения "—" и пустая строка стали None (аналог NaN), остальные превратились в числа. Так pd.to_numeric(errors="coerce") и спасает от падения на грязных данных.

rename: имена столбцов и индекса

Сырые данные часто приходят с неудобными именами столбцов — пробелы, регистр, кириллица вперемешку с латиницей. rename исправляет это по словарю «старое имя → новое»:

df = df.rename(columns={"Общая Сумма": "summa", "Дата Заказа": "order_date"})
df = df.rename(index={0: "первая"})            # переименовать метку строки
df.columns = df.columns.str.strip().str.lower() # массово: обрезать и в нижний регистр

Последняя строка — частый приём: str-методы по df.columns разом нормализуют все имена столбцов (убрать пробелы, привести к нижнему регистру).

replace: замена значений

replace меняет сами значения — по одному, списком или по словарю. Удобно для приведения разнобоя к единому виду:

df["пол"] = df["пол"].replace({"М": "мужской", "Ж": "женский"})
df["статус"] = df["статус"].replace(["-", "н/д", ""], np.nan)  # мусор → NaN
df = df.replace({"цена": {-1: np.nan}})  # по конкретному столбцу

map: перекодировка Series по словарю

map применяется к Series и заменяет каждое значение по словарю или функции. В отличие от replace, значения, которых нет в словаре, превращаются в NaN — это удобно как «строгая перекодировка», ловящая неожиданные категории:

codes = ["A", "B", "A", "C", "B", "X"]
mapping = {"A": "эконом", "B": "стандарт", "C": "премиум"}

# map по словарю: ключа нет → None (аналог NaN)
result = [mapping.get(code) for code in codes]
print(result)

unknown = [c for c, v in zip(codes, result) if v is None]
print("неизвестные коды:", unknown)

Вывод:

['эконом', 'стандарт', 'эконом', 'премиум', 'стандарт', None]
неизвестные коды: ['X']

Код "X" отсутствует в словаре и превратился в None — так map подсвечивает категории, о которых вы не знали. replace в той же ситуации оставил бы "X" как есть. Это и есть ключевое различие: map — строгая полная перекодировка, replace — точечная правка отдельных значений.

Порядок шагов очистки имеет значение

Очистка — это конвейер, и порядок операций влияет на результат. Разумная последовательность для типичного «грязного» CSV выглядит так:

  1. Нормализовать имена столбцов (strip, нижний регистр) — чтобы дальше обращаться к ним предсказуемо.
  2. Заменить маркеры пропуска ("-", "н/д", пустые строки) на настоящий NaN через replace — иначе они помешают приведению типов.
  3. Привести типы через to_numeric/to_datetime с coerce — теперь нечисловой мусор станет NaN.
  4. Обработать пропуски осознанной стратегией (заполнить/удалить/интерполировать).
  5. Убрать дубликаты после нормализации ключей.

Если поменять шаги местами — например, приводить типы до замены маркеров пропуска — astype упадёт, а to_numeric молча превратит в NaN больше, чем вы ожидали. Продумывание порядка превращает хаотичную правку в воспроизводимый пайплайн, который можно применить и к следующей выгрузке.

Подводные камни

  • astype падает на грязи. Для реальных данных предпочитайте to_numeric/to_datetime с errors="coerce".
  • Парсинг дат без формата медленный и угадывает. На больших данных задавайте format= явно — это и быстрее, и без сюрпризов «месяц перепутан с днём».
  • map превращает непокрытые значения в NaN. Если это нежелательно, используйте replace или map с последующим fillna.
  • rename без присваивания не меняет df. Либо df = df.rename(...), либо результат уйдёт «в никуда».

Лучшие практики

  • Приводите типы как можно раньше — в идеале при чтении файла через dtype=/parse_dates=.
  • Для грязных данных — to_numeric/to_datetime с coerce, затем осознанная обработка получившихся пропусков.
  • Нормализуйте имена столбцов сразу после загрузки — это упростит весь дальнейший код.
  • map — для полной перекодировки категорий, replace — для точечной замены значений.

Итог

  • astype — прямое приведение для чистых данных; падает на мусоре.
  • to_numeric/to_datetime с errors="coerce" превращают нераспознанное в пропуск.
  • rename чистит имена столбцов и меток индекса.
  • replace — точечная замена значений, map — строгая перекодировка по словарю.
Проверьте себя
1. Чем pd.to_numeric(col, errors='coerce') отличается от col.astype(int) на грязных данных?
AНичем, они идентичны
Bastype упадёт на нечисловом значении, а to_numeric с coerce превратит его в NaN
Cto_numeric удаляет строки с мусором
Dastype быстрее и безопаснее
2. В чём ключевое различие map и replace для Series?
Amap работает только с числами
Bmap — полная перекодировка по словарю (непокрытые значения → NaN), replace — точечная замена отдельных значений
Creplace нельзя применять к строкам
DОни полностью идентичны
3. Почему при парсинге дат на больших данных стоит задавать format= явно?
AБез format даты не парсятся вовсе
BЭто быстрее и исключает неоднозначное угадывание (например, путаницу дня и месяца)
Cformat обязателен по синтаксису
Dformat автоматически удаляет пропуски
Поддержать проект