Как заполнять пропуски данных во время исправлений?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Заполнение пропусков данных при исправлениях: стратегии и примеры
Пропуски в данных (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
Выбор метода заполнения пропусков критичен для качества данных и валидности результатов анализа. Лучше всегда понимать природу пропусков перед тем, как их заполнять.