Семестр 3 · Модуль 2 · Неделя 5

Очистка данных

Пропуски · дубликаты · типы · .str · map/apply · выбросы

2 ч теории. Презентация — 30 мин мини-лекция, далее live-coding и задачи семинара.

Прежде чем начать: представьте, что заказчик прислал файл, где у половины сотрудников нет возраста, зарплата записана как «90k», а название отдела «разными буквами». Ваша задача сегодня — сделать так, чтобы с этим файлом можно было работать.

Зачем вам очистка на 2–3 курсе

Что произойдёт с результатом анализа, если пропуски и опечатки не обработать?

С чего начинается очистка: аудит качества

Прежде чем чистить — понять, что «сломано». Три вопроса к каждому датасету:

df = pd.read_csv('dirty_salary.csv') df.dtypes # типы колонок df.isna().sum() # пропуски df.duplicated().sum()# дубликаты
employee_id 0 name 0 department 1 salary 2 age 5 hire_date 0

План очистки строится по этой сводке: колонка → проблема → стратегия → проверка.

Какие три типа проблем вы уже видите в этой сводке?

Пропуски: isna, dropna, fillna

# удалить строки df.dropna() # любые NaN df.dropna(subset=['salary']) df.dropna(thresh=3) # ≥3 непустых
# заполнить df.fillna(0) df['age'].fillna(df['age'].median()) df.fillna(method='ffill') # временные ряды

Выбор стратегии по смыслу: числовые — среднее/медиана, категориальные — мода или «unknown», временные ряды — ffill/bfill.

Почему для зарплаты лучше медиана, а не среднее? А для категории — «unknown»?

Ошибка недели: dropna/fillna без присваивания

Методы Pandas возвращают новый объект и не меняют исходный:

df.dropna() # результат НЕ присвоен → пропал df.isna().sum() # пропуски никуда не делись!
df = df.dropna() # ← присвоить! # или inplace=True (не рекомендуется): df.dropna(inplace=True)

Правило: не уверен, что в таблице — выведи (`.head()`, `.shape`, `.isna().sum()`), а не гадай.

Почему после `df.fillna(0)` колонка может остаться с NaN? Что забыли?

Дубликаты: duplicated и drop_duplicates

df.duplicated() # True/False по строкам df.drop_duplicates() # удалить полные копии df.drop_duplicates(subset=['employee_id'], keep='first')

Вопрос — не «как удалить», а «что считать дубликатом»:

Чем отличается drop_duplicates() от drop_duplicates(subset=['employee_id'])?

Типы данных: astype, to_numeric, to_datetime

Главная проблема после чтения CSV: число записано строкой.

df['salary'].dtype # object (строки!) df['salary'].mean() # NaN — строки не складываются
pd.to_numeric(df['salary'], errors='coerce') pd.to_datetime(df['hire_date'], format='%Y-%m-%d', errors='coerce') df['age'].astype('int64') # после очистки пропусков

errors='coerce' превращает нераспарсенные значения в NaN, а не бросает исключение — потом решаем, что с ними делать.

Почему df['salary'].mean() на строках даёт NaN? Что делает errors='coerce'?

Нормализация «число-строк»: str.replace

Зарплаты приходят в виде «120 000», «90k», «12,5» — сначала привести к «чистым» строкам, потом в число:

s = df['salary'] s = s.str.replace(' ', '', regex=False) # "120 000" → "120000" s = s.str.replace('k', '000', regex=False) # "90k" → "90000" s = s.str.replace(',', '.', regex=False) # "12,5" → "12.5" pd.to_numeric(s, errors='coerce') # показаны первые 6 из 16
0 120000.0 1 90000.0 2 75500.0 3 120000.0 4 145000.0 5 88000.0 dtype: float64
Почему нельзя сразу сделать pd.to_numeric('120 000')? Что «починит» replace?

String methods: аксессор .str

Строковые методы вызываются через df['col'].str.* — они векторизованы, т.е. работают сразу над всеми значениями:

df['name'].str.lower() # регистр df['name'].str.strip() # пробелы по краям df['name'].str.contains('ова', na=False) df['name'].str.startswith('И') df['salary_raw'].str.extract(r'([\d,]+)') # числа из строк df['name'].str.split(' ')

str.extract принимает регулярное выражение — к regex вернёмся на неделе 9.

Что вернёт '12,5 кг'.str.extract(r'([\d,]+)') — и как превратить результат в число?

map и apply: когда нужны, а когда нет

# map — словарь/функция на Series gender = {'male': 'М', 'female': 'Ж'} df['sex'].map(gender)
# apply — произвольная функция df['age'].apply(lambda a: 'взрослый' if a > 18 else 'ребёнок')

Главное правило: map/apply — для поштучных преобразований. Там, где есть векторизация (np.where, .str-методы, арифметика колонок), она быстрее и чище.

Какой способ быстрее для salary * 1.1: apply(lambda x: x*1.1) или просто df['salary']*1.1?

Выбросы: IQR и clip

Выброс — значение, далеко от основной массы. Стандартный метод — IQR (из NumPy, неделя 2):

q1, q3 = s.quantile(0.25), s.quantile(0.75) iqr = q3 - q1 lower, upper = q1 - 1.5*iqr, q3 + 1.5*iqr s[(s < lower) | (s > upper)] # выбросы s.clip(lower, upper) # «каппировать»

Решение — не «удалить», а обосновать: удалять, каппировать или помечать отдельной колонкой.

Почему после очистки «12,5» (опечатка) оказывается выбросом по IQR, хотя это «всего одно число»?

Конвейер очистки: шаги и контрольные точки

Очистка — это процесс с проверками, а не одна магическая функция:

сырой CSV → осмотр (info, isna().sum()) → решение по каждой колонке (пропуски, дубликаты, типы, текст) → проверка: 0 пропусков, верные dtypes, нет дубликатов → clean.csv

После каждого шага — «как проверить»: .head(), .isna().sum(), .dtypes, .shape.

Зачем проверять качество ДО и ПОСЛЕ очистки (одной функцией data_quality)?

Типичные ошибки недели 5

  1. Мутация без присваивания: df.dropna() / df.fillna(0) «не сработали» — результат не присвоен.
  2. apply там, где достаточно map или векторизации (лишние циклы по элементам).
  3. to_datetime без format на нестандартных строках — парсится неверно; нужен явный формат и errors='coerce'.
  4. Не задан na_values при чтении — строки «N/A», «-» не распознаются как NaN.
  5. drop_duplicates() без subset — дубликат по ключу с другими полями не удалится.
Студент сделал df.fillna(0) и удивлён: пропуски остались. Что он забыл?

Вопросы для проверки понимания

  1. Когда выбрать dropna, а когда fillna (среднее/медиана/ffill)?
  2. Чем map отличается от apply на Series и когда что быстрее?
  3. Как извлечь год из строки «2024-05-01» через str.extract?
  4. Почему после df.fillna(0) колонка может не измениться?

Ответьте себе письменно за 2 минуты — это и есть мини-самооценка перед семинаром.

Переход: на семинаре вы почистите «грязный» датасет зарплат и убедитесь, что каждая стратегия имеет обоснование.

Что дальше

Готовы? Откройте семинар-5.md и dirty_salary.csv.