← Назад к вопросам

Какой метод заполняет пропуски в данных?

1.3 Junior🔥 191 комментариев
#Pandas и обработка данных#Python и программирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Методы заполнения пропусков в данных (Imputation)

Пропуски в данных (Missing Values) — это распространённая проблема в реальных датасетах. Их нужно обработать перед анализом, иначе модели могут работать неправильно или вообще отказать. Разберу все основные методы заполнения пропусков и когда их использовать.

Почему пропуски проблема?

import pandas as pd
import numpy as np

# Датасет с пропусками
df = pd.DataFrame({
    'age': [25, 30, None, 35, 40],
    'salary': [50000, 60000, 75000, None, 80000],
    'experience': [1, 5, 3, 8, 10]
})

print(df)
#    age  salary  experience
# 0   25   50000            1
# 1   30   60000            5
# 2  NaN   75000            3
# 3   35     NaN            8
# 4   40   80000           10

# Проблемы:
print(df.mean())  # NaN values игнорируются
print(df.corr())  # Может быть неправильным

# Модели часто не работают с NaN
from sklearn.linear_model import LinearRegression
try:
    model = LinearRegression()
    model.fit(df[['age', 'experience']], df['salary'])  # Ошибка!
except ValueError as e:
    print(f"Ошибка: {e}")

1. Удаление строк с пропусками (Deletion)

Идея: Просто удалить строки, в которых есть пропуски.

# Удалить строки с пропусками в любом столбце
df_clean = df.dropna()
print(df_clean)
#    age  salary  experience
# 0   25   50000            1
# 1   30   60000            5
# 4   40   80000           10

# Удалить только если пропуск в конкретном столбце
df_clean = df.dropna(subset=['age'])

# Удалить если пропуск во всех столбцах
df_clean = df.dropna(how='all')

# Удалить если пропуск в большинстве столбцов
df_clean = df.dropna(thresh=2)  # Оставить если минимум 2 не-NaN

Преимущества:

  • ✅ Просто и быстро
  • ✅ Нет предположений о данных

Недостатки:

  • ❌ Теряем данные
  • ❌ Если пропусков много, потеряем большую часть датасета
  • ❌ Может быть смещение (если пропуски неслучайны)

Когда использовать:

  • Пропусков < 5%
  • Пропуски случайны

2. Заполнение константой (Forward/Backward Fill)

Идея: Заполнить пропуски одним значением (например, 0 или средним).

# Заполнить нулями
df_filled = df.fillna(0)
print(df_filled)
#    age  salary  experience
# 0   25   50000            1
# 1   30   60000            5
# 2    0   75000            3
# 3   35       0            8
# 4   40   80000           10

# Заполнить определённым значением
df_filled = df.fillna({'age': -1, 'salary': 0})

# Заполнить последним известным значением (Forward Fill)
# Полезно для временных рядов
df_ts = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=5),
    'value': [10, None, None, 20, 25]
})
df_filled = df_ts.fillna(method='ffill')  # Повторить последнее значение
print(df_filled)
#         date  value
# 0 2024-01-01   10.0
# 1 2024-01-02   10.0  <- Повторили 10
# 2 2024-01-03   10.0  <- Повторили 10
# 3 2024-01-04   20.0
# 4 2024-01-05   25.0

# Заполнить следующим известным значением (Backward Fill)
df_filled = df_ts.fillna(method='bfill')
print(df_filled)
#         date  value
# 0 2024-01-01   10.0
# 1 2024-01-02   20.0  <- Возьму следующее 20
# 2 2024-01-03   20.0  <- Возьму следующее 20
# 3 2024-01-04   20.0
# 4 2024-01-05   25.0

Преимущества:

  • ✅ Просто для временных рядов
  • ✅ Сохраняет форму данных

Недостатки:

  • ❌ Может исказить анализ
  • ❌ Искусственно снижает вариацию

3. Заполнение средним / медианой / модой (Mean/Median/Mode Imputation)

Идея: Заполнить пропуск на основе статистики других значений.

# Среднее (Mean)
mean_value = df['age'].mean()  # Игнорирует NaN
df_filled = df.copy()
df_filled['age'].fillna(mean_value, inplace=True)
print(f"Заполнили среднее возрасте: {mean_value}")

# Медиана (для несимметричных данных)
median_value = df['salary'].median()
df_filled['salary'].fillna(median_value, inplace=True)

# Мода (для категориальных данных)
df_cat = pd.DataFrame({
    'city': ['Moscow', 'SPB', None, 'Moscow', 'SPB']
})
mode_value = df_cat['city'].mode()[0]
df_cat['city'].fillna(mode_value, inplace=True)
print(df_cat)
#      city
# 0  Moscow
# 1     SPB
# 2  Moscow  <- Заполнили модой
# 3  Moscow
# 4     SPB

# Через pandas fillna с методом
df_filled = df[['age', 'salary']].fillna(df.mean())

Преимущества:

  • ✅ Сохраняет основные статистические свойства
  • ✅ Просто в реализации
  • ✅ Работает для категориальных данных (мода)

Недостатки:

  • ❌ Снижает дисперсию
  • ❌ Может быть неправильным, если пропуски неслучайны
  • ❌ Не учитывает взаимосвязи между переменными

Когда использовать:

  • Пропусков мало (< 10%)
  • Пропуски случайны
  • Нет сильных взаимосвязей с другими переменными

4. Группировочное заполнение (Group-based Imputation)

Идея: Заполнить пропуск средним/медианой в группе, не по всему датасету.

df = pd.DataFrame({
    'department': ['Sales', 'IT', 'Sales', 'IT', 'HR'],
    'salary': [50000, 80000, None, 85000, 45000]
})

# Заполнить пропуск в зарплате средней зарплатой в отделе
df['salary'] = df.groupby('department')['salary'].transform(
    lambda x: x.fillna(x.mean())
)
print(df)
#  department  salary
# 0      Sales   50000
# 1         IT   80000
# 2      Sales   50000  <- Средняя зарплата Sales (50000)
# 3         IT   85000
# 4         HR   45000

Когда использовать:

  • Есть естественная группировка
  • Пропуски могут различаться по группам

5. KNN Imputation

Идея: Заполнить пропуск значением из k-ближайших соседей.

from sklearn.impute import KNNImputer
import numpy as np

df = pd.DataFrame({
    'age': [25, 30, np.nan, 35, 40],
    'salary': [50000, 60000, 75000, np.nan, 80000],
    'experience': [1, 5, 3, 8, 10]
})

# KNN Imputer
imputer = KNNImputer(n_neighbors=2)
df_imputed = pd.DataFrame(
    imputer.fit_transform(df),
    columns=df.columns
)
print(df_imputed)
#        age   salary  experience
# 0   25.00  50000.00            1
# 1   30.00  60000.00            5
# 2   32.50  75000.00            3  <- Среднее 30 и 35 (ближайшие)
# 3   35.00  70000.00            8  <- Среднее 60000 и 80000
# 4   40.00  80000.00           10

Преимущества:

  • ✅ Учитывает структуру данных
  • ✅ Лучше, чем просто среднее
  • ✅ Работает с многомерными данными

Недостатки:

  • ❌ Вычислительно дорого
  • ❌ Зависит от выбора k
  • ❌ Медленнее для больших датасетов

6. Multiple Imputation (MICE)

Идея: Создать несколько датасетов с разными импутированными значениями и провести анализ на каждом.

from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer

df = pd.DataFrame({
    'age': [25, 30, np.nan, 35, 40],
    'salary': [50000, 60000, 75000, np.nan, 80000],
    'experience': [1, 5, 3, 8, 10]
})

# Iterative Imputer (MICE-like)
imputer = IterativeImputer(max_iter=10, random_state=42)
df_imputed = pd.DataFrame(
    imputer.fit_transform(df),
    columns=df.columns
)
print(df_imputed)

# Или использовать statsmodels для полноценной MICE
from statsmodels.imputation.mice import MICEData

mice_data = MICEData(df)
mices = mice_data.mice()

Преимущества:

  • ✅ Статистически правильный метод
  • ✅ Учитывает неопределённость
  • ✅ Лучше для больших доля пропусков

Недостатки:

  • ❌ Вычислительно сложно
  • ❌ Требует больше кода
  • ❌ Медленно

7. Заполнение с предсказанием (Predictive Imputation)

Идея: Предсказать пропускающееся значение на основе других переменных.

from sklearn.linear_model import LinearRegression

df = pd.DataFrame({
    'age': [25, 30, np.nan, 35, 40],
    'salary': [50000, 60000, 75000, np.nan, 80000],
    'experience': [1, 5, 3, 8, 10]
})

# Заполнить пропуск в age на основе salary и experience
# Шаг 1: Обучить модель на полных строках
mask = df['age'].notna()
X_train = df.loc[mask, ['salary', 'experience']]
y_train = df.loc[mask, 'age']

model = LinearRegression()
model.fit(X_train, y_train)

# Шаг 2: Предсказать пропускающиеся значения
mask_missing = df['age'].isna()
X_missing = df.loc[mask_missing, ['salary', 'experience']]
y_pred = model.predict(X_missing)

df.loc[mask_missing, 'age'] = y_pred
print(df)
#    age  salary  experience
# 0   25   50000            1
# 1   30   60000            5
# 2   32.4  75000            3  <- Предсказано моделью
# 3   35   80000            8
# 4   40   80000           10

Когда использовать:

  • Сильные корреляции между переменными
  • Хочешь максимально реалистичное значение

8. Создание индикатора пропуска (Missing Indicator)

Идея: Вместо заполнения пропуска, добавить бинарную переменную-флаг.

df = pd.DataFrame({
    'age': [25, 30, np.nan, 35, 40],
    'salary': [50000, 60000, 75000, np.nan, 80000]
})

# Создать флаги
df['age_is_missing'] = df['age'].isna().astype(int)
df['salary_is_missing'] = df['salary'].isna().astype(int)

# Заполнить пропуски
df['age'].fillna(df['age'].mean(), inplace=True)
df['salary'].fillna(df['salary'].mean(), inplace=True)

print(df)
#    age  salary  age_is_missing  salary_is_missing
# 0   25   50000               0                  0
# 1   30   60000               0                  0
# 2   32.5 75000               1                  0  <- Было NaN в age
# 3   35   70000               0                  1  <- Было NaN в salary
# 4   40   80000               0                  0

Преимущества:

  • ✅ Сохраняет информацию о пропусках
  • ✅ Может помочь модели
  • ✅ Показывает качество данных

Сравнение методов

МетодСкоростьКачествоСложностьКогда использовать
Удаление⚡⚡⚡📊Пропусков < 5%
Forward Fill⚡⚡⚡📊Временные ряды
Mean/Median⚡⚡⚡📊📊Пропусков < 10%
Группировочное⚡⚡📊📊⭐⭐Есть группы
KNN📊📊📊⭐⭐Средние пропуски
MICE🐢📊📊📊📊⭐⭐⭐Много пропусков
Предсказание📊📊📊⭐⭐⭐Сильные корреляции

Практический рабочий процесс

import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer, KNNImputer

def handle_missing_values(df):
    """Обработка пропусков"""
    
    # 1. Анализ пропусков
    missing_pct = (df.isnull().sum() / len(df)) * 100
    print("Процент пропусков:")
    print(missing_pct[missing_pct > 0])
    
    # 2. Удалить столбцы с > 50% пропусков
    df = df.loc[:, missing_pct < 50]
    
    # 3. Удалить строки с > 3 пропусками
    df = df.loc[df.isnull().sum(axis=1) <= 3]
    
    # 4. Числовые колонки: KNN imputation
    numeric_cols = df.select_dtypes(include=[np.number]).columns
    imputer = KNNImputer(n_neighbors=5)
    df[numeric_cols] = imputer.fit_transform(df[numeric_cols])
    
    # 5. Категориальные колонки: мода
    categorical_cols = df.select_dtypes(include=['object']).columns
    for col in categorical_cols:
        df[col].fillna(df[col].mode()[0], inplace=True)
    
    return df

# Использование
df_clean = handle_missing_values(df)

Ключевые выводы

✅ Не удаляй данные без необходимости

✅ Понимай ПРИЧИНУ пропусков (случайные vs неслучайные)

✅ Для временных рядов используй Forward Fill

✅ Для случайных пропусков используй среднее/медиану

✅ Для сложных случаев используй KNN или MICE

✅ Всегда создавай флаги пропускающихся значений для моделей

✅ Проверь результат визуально после импутации