Пропуски в Pandas: шпаргалка для собеседования

Зачем аналитику уметь чистить NaN

Реальные данные почти всегда с пропусками. Если не обрабатывать NaN — агрегаты врут, merge ломается, графики рисуются странно.

На собесе проверяют знание трёх базовых приёмов: isna, fillna, dropna — и умение выбрать, какой применить в конкретной задаче.

Проверка на NaN

# Есть ли NaN в колонке
df['col'].isna()       # Series bool
df['col'].notna()      # обратное

# Количество NaN
df['col'].isna().sum()
df.isna().sum()         # по всем колонкам
df.isna().sum().sum()   # всего NaN в таблице

# Процент NaN
df['col'].isna().mean() * 100

Удаление пропусков — dropna

# Удалить строки с NaN в любой колонке
df.dropna()

# Удалить только где NaN в конкретной колонке
df.dropna(subset=['email'])

# Удалить строки, где ВСЕ колонки NaN
df.dropna(how='all')

# Удалить строки, где >50% колонок NaN
df.dropna(thresh=len(df.columns) // 2)

# Удалить колонки с NaN
df.dropna(axis=1)

На собесе: часто спрашивают «когда dropna опасно». Ответ: когда вы теряете строки с важной информацией из-за NaN в одной второстепенной колонке. Всегда явно указывайте subset.

Заполнение — fillna

# Константой
df['amount'].fillna(0)
df['status'].fillna('unknown')

# Средним / медианой
df['amount'].fillna(df['amount'].mean())
df['amount'].fillna(df['amount'].median())

# Forward fill (последнее известное значение)
df['price'].fillna(method='ffill')

# Backward fill
df['price'].fillna(method='bfill')

# Разные значения для разных колонок
df.fillna({'amount': 0, 'status': 'unknown', 'email': ''})

Если хочется сразу закрепить тему на практике — открой тренажёр в Telegram. 10 минут в день — и синтаксис в пальцах.

Заполнение по группе — transform + fillna

# Заполнить NaN средним по городу
df['amount'] = df.groupby('city')['amount'].transform(
    lambda x: x.fillna(x.mean())
)

Это частый приём в продуктовой аналитике — заполнить пропуски «похожими» значениями.

Интерполяция — временные ряды

# Линейная интерполяция
df['price'].interpolate()

# По времени (нужен datetime index)
df.set_index('date')['price'].interpolate(method='time')

# Более сложные
df['price'].interpolate(method='polynomial', order=2)

Нужно для восстановления пропущенных точек в time-series, когда ffill выглядит некорректно.

NaN в агрегатах

# Агрегаты pandas по умолчанию игнорируют NaN
df['amount'].sum()     # игнорирует NaN
df['amount'].mean()    # игнорирует NaN (и в делителе)
df['amount'].count()   # считает не-NaN

# Принудительно считать NaN
df['amount'].sum(skipna=False)  # NaN → результат NaN

На собесе: «Что вернёт sum([1, 2, NaN])?» Ответ: 3 (игнорирует). sum(skipna=False) → NaN.

NaN vs None vs NaT

  • np.nan / pd.NA — числовой NaN.
  • None — Python None, pandas часто преобразует в NaN.
  • NaT (Not a Time) — пропуск для datetime.
  • pd.NA — новый универсальный пропуск (pandas 1.0+).

Все обрабатываются одинаково через isna/fillna.

NaN в merge

# NaN не матчится даже с NaN (как в SQL)
pd.merge(df1, df2, on='key')
# Если key = NaN в одной из таблиц — эта строка выпадет

Чтобы не только читать теорию, но и решать реальные задачи — загляните в бот Карьерника. Там по каждой теме подборка вопросов с разборами.

NaN в DataFrame с пустыми строками

# Пустая строка — не NaN
df['email'] = df['email'].replace('', np.nan)
# Теперь fillna их покроет

# Или наоборот: NaN → пустая
df['email'].fillna('')

Ловушка: '' != NaN. Фильтр df[df['email'] != ''] не уберёт NaN — нужен isna().

10 задач с собесов

1. Показать колонки с процентом NaN

(df.isna().mean() * 100).sort_values(ascending=False).head(10)

2. Удалить строки, где email = NaN или ''

df = df[df['email'].notna() & (df['email'] != '')]

3. Заполнить NaN средним по группе

df['amount'] = df.groupby('category')['amount'].transform(
    lambda x: x.fillna(x.mean())
)

4. Forward fill для цены

df.sort_values('date').groupby('ticker')['price'].ffill()

5. Количество строк без ни одного NaN

df.dropna().shape[0]

6. Процент NaN в каждой колонке визуально

import seaborn as sns
sns.heatmap(df.isna(), cbar=False)

7. Заменить NaN в числах на 0, в строках на 'unknown'

num_cols = df.select_dtypes(include='number').columns
str_cols = df.select_dtypes(include='object').columns

df[num_cols] = df[num_cols].fillna(0)
df[str_cols] = df[str_cols].fillna('unknown')

8. Флаг «была ли NaN до заполнения»

df['amount_was_nan'] = df['amount'].isna()
df['amount'] = df['amount'].fillna(0)

Полезно для ML, чтобы модель видела, что значение искусственное.

9. Интерполировать пропущенные дни в time-series

df = df.set_index('date').resample('D').asfreq()  # добавит NaN для отсутствующих дней
df['value'] = df['value'].interpolate()

10. NaN в определённых колонках → помечаем проблемной строкой

df['has_issue'] = df[['col_a', 'col_b']].isna().any(axis=1)

Как тренироваться

Пропуски — это 20% всей работы аналитика в реальности. В учебниках об этом говорят мало, на собесе — всё чаще.

Тренажёр Карьерник содержит блок задач на NaN: проверка, удаление, интерполяция, группировочные заполнения. С разборами типичных ошибок.

Совет: прежде чем делать fillna(0), всегда думайте — а действительно ли 0 корректно? Если это missing на уровне бизнес-логики (пользователь не сделал N событий), 0 может быть правильным. Если это missing на уровне данных (не дошло до трекера) — 0 врёт.

Читайте также

FAQ

NaN и None — в чём разница?

В pandas почти нет — None обычно конвертируется в NaN. NaN — это float, поэтому любая колонка с NaN становится float. Для integer-колонок с пропусками pandas предлагает Int64 (с большой I) — nullable integer.

fillna или dropna?

Зависит от задачи. dropna когда пропуски случайны и строк много — можно потерять. fillna когда важно сохранить строку. Если >10% строк имеет NaN — обычно лучше fillna или interpolate.

ffill или bfill?

ffill — когда данные «тянутся» от прошлого (курсы валют, цены). bfill — когда от будущего (редко в аналитике). Для time-series обычно ffill.

Что такое pd.NA?

Новый универсальный пропуск (pandas 1.0+). В отличие от np.nan, работает с любым типом (int, bool, string). Используется в nullable integer/boolean/string dtypes.