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

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

1.0 Junior🔥 161 комментариев
#ETL и качество данных

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

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

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

Заполнение пропусков данных при исправлениях: стратегии и примеры

Пропуски в данных (missing values, NULL) — очень частая проблема при работе с реальными данными. Как Data Engineer, я применяю различные стратегии заполнения в зависимости от контекста и типа данных.

Типы пропусков и их причины

MCAR (Missing Completely At Random)

  • Пропуск не зависит от других данных
  • Пример: сбой сенсора в определённый момент
  • Решение: можно удалить или заполнить

MAR (Missing At Random)

  • Пропуск зависит от других известных переменных
  • Пример: старые таблицы не заполняют новое поле
  • Решение: imputation с учётом других столбцов

MNAR (Missing Not At Random)

  • Пропуск зависит от самого пропущенного значения
  • Пример: богатые люди не указывают доход
  • Решение: требует домена-специфичного анализа

Стратегии заполнения

1. Удаление (Deletion)

Простой, но не всегда применимый подход:

import pandas as pd
import numpy as np

# Датасет с пропусками
df = pd.DataFrame({
    'user_id': [1, 2, 3, 4, 5],
    'age': [25, np.nan, 35, 40, np.nan],
    'salary': [50000, 60000, np.nan, 80000, 90000]
})

# Удалить строки с любыми пропусками
df_clean = df.dropna()

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

# Удалить столбец с большим числом пропусков
df_clean = df.dropna(thresh=len(df) * 0.7, axis=1)

Минусы: теряем данные, может привести к смещению результатов.

2. Заполнение константой

Используется для категориальных данных или когда пропуск имеет смысл:

# Заполнить пропуски в категориальных столбцах
df['country'].fillna('Unknown', inplace=True)
df['is_premium'].fillna(False, inplace=True)

# Заполнить пропуски в числовых столбцах (не рекомендуется)
df['age'].fillna(0, inplace=True)  # Плохой подход!

3. Forward Fill / Backward Fill (для временных рядов)

Полезна для временных последовательностей, где значение меняется медленно:

# Датасет с временными метриками
time_series = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=5),
    'temperature': [20, np.nan, np.nan, 25, np.nan]
})

# Forward fill (берём предыдущее значение)
df['temperature'].fillna(method='ffill', inplace=True)
# Result: [20, 20, 20, 25, 25]

# Backward fill (берём следующее значение)
df['temperature'].fillna(method='bfill', inplace=True)
# Result: [20, 25, 25, 25, None]

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

Для числовых данных с нормальным распределением:

# Средние значения
mean_age = df['age'].mean()
df['age'].fillna(mean_age, inplace=True)

# Медиана (более устойчива к выбросам)
median_salary = df['salary'].median()
df['salary'].fillna(median_salary, inplace=True)

# Медиана по группам (лучший подход)
df['salary'].fillna(
    df.groupby('department')['salary'].transform('median'),
    inplace=True
)

5. KNN Imputation

Заполнение на основе K ближайших соседей:

from sklearn.impute import KNNImputer

imputer = KNNImputer(n_neighbors=5)
df_imputed = imputer.fit_transform(df[['age', 'salary', 'experience']])

# Лучше для многомерных данных с зависимостями

6. Multiple Imputation

Наиболее корректный статистический подход:

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

# MICE (Multivariate Imputation by Chained Equations)
imputer = IterativeImputer(random_state=42, max_iter=10)
df_imputed = imputer.fit_transform(df)

# Создаёт несколько вариантов заполнения и усредняет результаты

SQL подход для заполнения пропусков

В базах данных часто проще работать с SQL:

-- Заполнение пропусков в SQL (PostgreSQL)
SELECT 
    user_id,
    name,
    -- Заполнить пропуски в age медианой
    COALESCE(age, 
             PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY age) 
             OVER ()) AS age,
    -- Заполнить пропуски в city последним известным значением
    COALESCE(city, 
             LAST_VALUE(city IGNORE NULLS) 
             OVER (PARTITION BY user_id ORDER BY date)) AS city,
    -- Для временных рядов: заполнить forward fill
    COALESCE(temperature,
             LAG(temperature IGNORE NULLS) 
             OVER (ORDER BY date)) AS temperature
FROM users
WHERE is_active = true;

Best Practices при исправлениях

1. Документируйте стратегию заполнения

# Ведите метаданные о том, как заполнены пропуски
imputation_metadata = {
    'age': {'method': 'median_by_department', 'missing_count': 15},
    'salary': {'method': 'knn', 'missing_count': 8},
    'country': {'method': 'constant', 'value': 'Unknown', 'missing_count': 3}
}

2. Проверяйте результаты

# Статистика до и после
print(f"Missing before: {df.isnull().sum()}")
print(f"Missing after: {df_imputed.isnull().sum()}")

# Распределение значений
print(f"Age mean: {df['age'].mean()}")
print(f"Age std: {df['age'].std()}")

3. Не заполняйте слепо

# Если пропусков очень много (>30%), лучше удалить столбец
missing_pct = df.isnull().sum() / len(df) * 100
df_clean = df.drop(columns=[col for col, pct in missing_pct.items() if pct > 30])

Практический пример в ETL pipeline

import pandas as pd
from sklearn.impute import SimpleImputer, IterativeImputer

def clean_data(df):
    # 1. Удалить дубликаты
    df = df.drop_duplicates()
    
    # 2. Обработать категориальные пропуски
    categorical_cols = df.select_dtypes(include='object').columns
    df[categorical_cols] = df[categorical_cols].fillna('Unknown')
    
    # 3. Обработать числовые пропуски
    numerical_cols = df.select_dtypes(include='number').columns
    imputer = IterativeImputer(random_state=42)
    df[numerical_cols] = imputer.fit_transform(df[numerical_cols])
    
    return df

Выбор метода заполнения пропусков критичен для качества данных и валидности результатов анализа. Лучше всегда понимать природу пропусков перед тем, как их заполнять.