2025, Sep 27 19:17
Как обработать смешанные типы в CSV: on_bad_lines vs to_numeric в pandas
Почему on_bad_lines не спасает при смешанных типах CSV и как загрузить их в pandas: читаем как строки, затем приводим столбцы через to_numeric с errors='coerce'
CSV‑файлы любят смешивать типы, особенно когда при ручном вводе в колонку с целыми числами попадает строка. Если вы надеетесь, что on_bad_lines это перехватит, — увы, нет. Этот параметр срабатывает только при структурных сбоях парсинга — например, когда не сходится разделитель или количество столбцов, — а не тогда, когда значение не приводится к целому типу. Ниже — аккуратный способ загрузить такие данные, не роняя конвейер.
Постановка задачи
Представьте CSV, где в числовых столбцах иногда встречаются строки или пустые значения.
id,name,age
1,,"25"
2,"",30
jj,John,
Прямое чтение с подсказками dtype выглядит логично, но со смешанными типами оно работает не так, как вы ожидаете.
import pandas as pd
col_types_map = {'id': 'int64', 'name': 'str', 'age': 'int'}
frame_raw = pd.read_csv('a.csv', dtype=col_types_map, on_bad_lines='warn', engine='python')
Почему это не работает
Параметр on_bad_lines обрабатывает парсинговые аномалии, такие как избыточное или недостаточное число полей в строке. Он не вмешивается, когда значение поля не соответствует целевому dtype. Приведение типов происходит уже после успешной токенизации строки, поэтому значение вроде jj в целочисленном столбце не проходит через on_bad_lines. В итоге вы либо получите исключение на этапе приведения dtype, либо поведение, которого не ожидали.
Решение: сначала читаем как строки, затем явно приводим типы
Надёжный приём — сначала читать всё как строки, а затем целево конвертировать нужные столбцы с помощью pandas.to_numeric. Параметр errors позволяет выбрать, как обходиться с несовместимыми значениями. coerce превращает некорректные числа в NaN. raise выбрасывает исключение. ignore оставляет исходную строку как есть. Поскольку ignore помечен как устаревший, эквивалентное поведение можно реализовать небольшим обёрточным преобразованием.
import pandas as pd
rows_all_str = pd.read_csv('a.csv', dtype=str, on_bad_lines='warn', engine='python')
rows_all_str['id'] = pd.to_numeric(rows_all_str['id'], errors='coerce')
rows_all_str['age'] = pd.to_numeric(rows_all_str['age'], errors='coerce')
Если вместо NaN вы хотите сохранять нечисловые значения как есть, используйте безопасное приведение с try/except.
def cast_numeric_safely(x):
    try:
        return pd.to_numeric(x)
    except Exception:
        return x
mixed_frame = pd.read_csv('a.csv', dtype=str, on_bad_lines='warn', engine='python')
mixed_frame['id'] = mixed_frame['id'].apply(cast_numeric_safely)
mixed_frame['age'] = mixed_frame['age'].apply(cast_numeric_safely)
Вы можете выбирать поведение, меняя стратегию конвертации. Документация pandas.to_numeric описывает варианты для errors: coerce — в NaN, raise (по умолчанию) — исключение, а ignore — оставить строку. См.: https://pandas.pydata.org/docs/reference/api/pandas.to_numeric.html
Что получится на выходе
При логике ignore (встроенной или пользовательской) нечисловые значения остаются строками:
   id  name   age
0   1   NaN  25.0
1   2   NaN  30.0
2  jj  John   NaN
С errors='coerce' некорректные числа превращаются в NaN:
    id  name   age
0  1.0   NaN  25.0
1  2.0   NaN  30.0
2  NaN  John   NaN
С errors='raise' загрузка остановится на первом проблемном значении:
ValueError: Unable to parse string "jj" at position 0
Почему это важно
Контуры загрузки данных должны падать только когда данные структурно испорчены. На уровне значений странности встречаются часто и должны обрабатываться детерминированно. Разделяя парсинг и приведение типов, вы получаете контроль: можно сохранять сырые значения, заменять их на пропуски или останавливать загрузку. Такой подход делает конвейер предсказуемым и упрощает дальнейшую логику.
Выводы
Не рассчитывайте на on_bad_lines в вопросах типов: он касается только структурных ошибок парсинга, вроде неожиданного числа полей. Читайте неоднозначные столбцы как строки, затем приводите их с pandas.to_numeric и явно заданной политикой errors. Если нужно сохранить нечисловые значения, реализуйте небольшой безопасный кастер вместо устаревшего поведения. Такой подход делает намерения прозрачными, улучшает обработку ошибок и выстраивает более чистые потоки данных.
Статья основана на вопросе на StackOverflow от Despicable me и ответе от Aadvik.