7.4. Заполнение пропущенных значений#
По разным причинам многие реальные наборы данных содержат пропущенные значения, часто закодированные как пробелы, NaN или другие заполнители. Однако такие наборы данных несовместимы с оценщиками scikit-learn, которые предполагают, что все значения в массиве являются числовыми и имеют смысл. Базовая стратегия использования неполных наборов данных — удаление целых строк и/или столбцов, содержащих пропущенные значения. Однако это происходит ценой потери данных, которые могут быть ценными (даже если неполными). Лучшая стратегия — импутировать пропущенные значения, т.е. вывести их из известной части данных. Смотрите глоссарийную статью о импутация.
7.4.1. Одномерное против многомерного заполнения пропусков#
Один тип алгоритма импутации является одномерным, который заполняет значения в
i-м измерении признака, используя только отсутствующие значения в этом измерении признака
(например, SimpleImputer). В отличие от этого, алгоритмы многомерного заполнения используют весь набор доступных измерений признаков для оценки пропущенных значений (например, IterativeImputer).
7.4.2. Одномерное заполнение пропущенных значений признаков#
The SimpleImputer класс предоставляет базовые стратегии для заполнения пропущенных
значений. Пропущенные значения могут быть заполнены предоставленным постоянным значением или с использованием
статистики (среднее, медиана или наиболее частое) каждого столбца, в котором
расположены пропущенные значения. Этот класс также позволяет использовать различные кодировки
пропущенных значений.
Следующий фрагмент демонстрирует, как заменить пропущенные значения,
закодированные как np.nan, используя среднее значение столбцов (ось 0), которые содержат пропущенные значения:
>>> import numpy as np
>>> from sklearn.impute import SimpleImputer
>>> imp = SimpleImputer(missing_values=np.nan, strategy='mean')
>>> imp.fit([[1, 2], [np.nan, 3], [7, 6]])
SimpleImputer()
>>> X = [[np.nan, 2], [6, np.nan], [7, 6]]
>>> print(imp.transform(X))
[[4. 2. ]
[6. 3.666]
[7. 6. ]]
The SimpleImputer класс также поддерживает разреженные матрицы:
>>> import scipy.sparse as sp
>>> X = sp.csc_matrix([[1, 2], [0, -1], [8, 4]])
>>> imp = SimpleImputer(missing_values=-1, strategy='mean')
>>> imp.fit(X)
SimpleImputer(missing_values=-1)
>>> X_test = sp.csc_matrix([[-1, 2], [6, -1], [7, 6]])
>>> print(imp.transform(X_test).toarray())
[[3. 2.]
[6. 3.]
[7. 6.]]
Обратите внимание, что этот формат не предназначен для неявного хранения пропущенных значений в матрице, так как это приведет к ее уплотнению во время преобразования. Пропущенные значения, закодированные как 0, должны использоваться с плотными входными данными.
The SimpleImputer класс также поддерживает категориальные данные, представленные как
строковые значения или категориальные данные pandas при использовании 'most_frequent' или
'constant' стратегия:
>>> import pandas as pd
>>> df = pd.DataFrame([["a", "x"],
... [np.nan, "y"],
... ["a", np.nan],
... ["b", "y"]], dtype="category")
...
>>> imp = SimpleImputer(strategy="most_frequent")
>>> print(imp.fit_transform(df))
[['a' 'x']
['a' 'y']
['a' 'y']
['b' 'y']]
Для другого примера использования см. Заполнение пропущенных значений перед построением оценщика.
7.4.3. Многомерное заполнение признаков#
Более сложный подход заключается в использовании IterativeImputer класс, который моделирует каждый признак с пропущенными значениями как функцию других признаков и использует эту оценку для импутации. Это делается итеративно по круговой схеме: на каждом шаге столбец признака назначается как выходной y и
остальные столбцы признаков рассматриваются как входы X. Регрессор обучается на (X,
y) для известных y. Затем регрессор используется для предсказания пропущенных значений
yметрики и соответствующего max_iter раунды заполнения пропусков. Результаты последнего раунда заполнения возвращаются.
Примечание
Этот оценщик все еще экспериментальный на данный момент: параметры по умолчанию или детали поведения могут измениться без цикла устаревания. Решение следующих проблем поможет стабилизировать IterativeImputer:
критерии сходимости (#14338) и оценщики по умолчанию
(#13286). Чтобы использовать её, нужно явно импортировать
enable_iterative_imputer.
>>> import numpy as np
>>> from sklearn.experimental import enable_iterative_imputer
>>> from sklearn.impute import IterativeImputer
>>> imp = IterativeImputer(max_iter=10, random_state=0)
>>> imp.fit([[1, 2], [3, 6], [4, 8], [np.nan, 3], [7, np.nan]])
IterativeImputer(random_state=0)
>>> X_test = [[np.nan, 2], [6, np.nan], [np.nan, 6]]
>>> # the model learns that the second feature is double the first
>>> print(np.round(imp.transform(X_test)))
[[ 1. 2.]
[ 6. 12.]
[ 3. 6.]]
Оба SimpleImputer и IterativeImputer может использоваться в Pipeline как способ построения составного оценщика, поддерживающего импутацию. См. Заполнение пропущенных значений перед построением оценщика.
7.4.3.1. Гибкость IterativeImputer#
В экосистеме R для науки о данных существует множество устоявшихся пакетов для импутации: Amelia, mi, mice, missForest и т.д. missForest популярен и оказывается частным случаем различных последовательных алгоритмов импутации, которые все могут быть реализованы с помощью IterativeImputer путём передачи
разных регрессоров для прогнозирования отсутствующих значений признаков. В
случае missForest этим регрессором является случайный лес.
См. Заполнение пропущенных значений с вариантами IterativeImputer.
7.4.3.2. Множественная против единичной импутации#
В статистическом сообществе принято выполнять множественное
имитирование, генерируя, например, m отдельные импутации для одной
матрицы признаков. Каждая из этих m заполненные значения затем передаются в
последующий конвейер анализа (например, проектирование признаков, кластеризация, регрессия,
классификация). m окончательные результаты анализа (например, ошибки валидации
на отложенной выборке) позволяют специалисту по данным понять, как аналитические
результаты могут различаться вследствие присущей неопределённости, вызванной
пропущенными значениями. Вышеописанная практика называется множественным заполнением.
Наша реализация IterativeImputer был вдохновлен пакетом R MICE (Multivariate Imputation by Chained Equations) [1], но отличается от
него тем, что возвращает единичную импутацию вместо множественных импутаций. Однако,
IterativeImputer также может использоваться для множественного заполнения пропусков путем многократного
применения к одному и тому же набору данных с разными случайными начальными значениями, когда
sample_posterior=True. См. [2], глава 4 для более подробного обсуждения множественных и одиночных импутаций.
Остается открытым вопрос о том, насколько полезно единичное против множественного заполнения пропусков в контексте предсказания и классификации, когда пользователь не заинтересован в измерении неопределенности из-за пропущенных значений.
Обратите внимание, что вызов transform метод IterativeImputer не может изменять количество образцов. Поэтому множественное заполнение пропусков не может быть достигнуто одним вызовом transform.
Ссылки
7.4.4. Импутация методом ближайших соседей#
The KNNImputer класс предоставляет импутацию для заполнения пропущенных значений
с использованием подхода k-ближайших соседей. По умолчанию используется метрика евклидова расстояния,
которая поддерживает пропущенные значения,
nan_euclidean_distances, используется для поиска ближайших соседей. Каждый отсутствующий признак импутируется значениями из
n_neighbors ближайших соседей, которые имеют значение для признака. Признаки соседей усредняются равномерно или взвешиваются по расстоянию до каждого соседа. Если у образца отсутствует более одного признака, то соседи для этого образца могут быть разными в зависимости от конкретного признака, который импутируется. Когда количество доступных соседей меньше n_neighbors и нет определенных расстояний до обучающего набора, используется среднее значение обучающего набора для этого признака при импутации. Если есть хотя бы один сосед с определенным расстоянием, будет использоваться взвешенное или невзвешенное среднее оставшихся соседей при импутации. Если признак всегда отсутствует в обучении, он удаляется во время transform. Для получения дополнительной информации о методологии см.
ссылка. [OL2001].
Следующий фрагмент демонстрирует, как заменить пропущенные значения,
закодированные как np.nan, используя среднее значение признака двух ближайших
соседей образцов с пропущенными значениями:
>>> import numpy as np
>>> from sklearn.impute import KNNImputer
>>> nan = np.nan
>>> X = [[1, 2, nan], [3, 4, 3], [nan, 6, 5], [8, 8, 7]]
>>> imputer = KNNImputer(n_neighbors=2, weights="uniform")
>>> imputer.fit_transform(X)
array([[1. , 2. , 4. ],
[3. , 4. , 3. ],
[5.5, 6. , 5. ],
[8. , 8. , 7. ]])
Для другого примера использования см. Заполнение пропущенных значений перед построением оценщика.
Ссылки
7.4.5. Сохраняя количество признаков постоянным#
По умолчанию импутеры scikit-learn будут удалять полностью пустые признаки, т.е. столбцы, содержащие только пропущенные значения. Например:
>>> imputer = SimpleImputer()
>>> X = np.array([[np.nan, 1], [np.nan, 2], [np.nan, 3]])
>>> imputer.fit_transform(X)
array([[1.],
[2.],
[3.]])
Первый признак в X содержащий только np.nan был удалён после
импутации. Хотя этот признак не поможет в прогнозировании, удаление
столбцов изменит форму X что может быть проблематично при использовании импьютеров в более сложном конвейере машинного обучения. Параметр
keep_empty_features предоставляет возможность сохранять пустые признаки, заполняя их
постоянным значением. В большинстве случаев это постоянное значение равно нулю:
>>> imputer.set_params(keep_empty_features=True)
SimpleImputer(keep_empty_features=True)
>>> imputer.fit_transform(X)
array([[0., 1.],
[0., 2.],
[0., 3.]])
7.4.6. Пометка импутированных значений#
The MissingIndicator трансформер полезен для преобразования набора данных в
соответствующую бинарную матрицу, указывающую на наличие пропущенных значений в наборе
данных. Это преобразование полезно в сочетании с импутацией. При
использовании импутации сохранение информации о том, какие значения были
пропущены, может быть информативным. Обратите внимание, что и SimpleImputer и
IterativeImputer имеют булев параметр add_indicator
(False по умолчанию), который при установке в True предоставляет удобный способ
объединения выхода MissingIndicator трансформер с выходом импутера.
NaN обычно используется как заполнитель для пропущенных значений. Однако он требует, чтобы тип данных был float. Параметр missing_values позволяет
указать другие заполнители, такие как целые числа. В следующем примере мы будем
использовать -1 как пропущенные значения:
>>> from sklearn.impute import MissingIndicator
>>> X = np.array([[-1, -1, 1, 3],
... [4, -1, 0, -1],
... [8, -1, 1, 0]])
>>> indicator = MissingIndicator(missing_values=-1)
>>> mask_missing_values_only = indicator.fit_transform(X)
>>> mask_missing_values_only
array([[ True, True, False],
[False, True, True],
[False, True, False]])
The features параметр используется для выбора признаков, для которых строится маска. По умолчанию это 'missing-only' который возвращает маску импутера
признаков, содержащих пропущенные значения в fit время:
>>> indicator.features_
array([0, 1, 3])
The features параметр может быть установлен в 'all' возвращать все признаки независимо от того, содержат ли они пропущенные значения:
>>> indicator = MissingIndicator(missing_values=-1, features="all")
>>> mask_all = indicator.fit_transform(X)
>>> mask_all
array([[ True, True, False, False],
[False, True, False, True],
[False, True, False, False]])
>>> indicator.features_
array([0, 1, 2, 3])
При использовании MissingIndicator в
Pipeline, обязательно используйте
FeatureUnion или
ColumnTransformer для добавления индикаторных признаков к
обычным признакам. Сначала мы получаем iris набор данных и добавить в него некоторые пропущенные значения.
>>> from sklearn.datasets import load_iris
>>> from sklearn.impute import SimpleImputer, MissingIndicator
>>> from sklearn.model_selection import train_test_split
>>> from sklearn.pipeline import FeatureUnion, make_pipeline
>>> from sklearn.tree import DecisionTreeClassifier
>>> X, y = load_iris(return_X_y=True)
>>> mask = np.random.randint(0, 2, size=X.shape).astype(bool)
>>> X[mask] = np.nan
>>> X_train, X_test, y_train, _ = train_test_split(X, y, test_size=100,
... random_state=0)
Теперь мы создаем FeatureUnion. Все признаки будут импутированы с использованием SimpleImputer, чтобы позволить классификаторам работать с этими данными. Кроме того, он добавляет индикаторные переменные из
MissingIndicator.
>>> transformer = FeatureUnion(
... transformer_list=[
... ('features', SimpleImputer(strategy='mean')),
... ('indicators', MissingIndicator())])
>>> transformer = transformer.fit(X_train, y_train)
>>> results = transformer.transform(X_test)
>>> results.shape
(100, 8)
Конечно, мы не можем использовать трансформер для каких-либо предсказаний. Мы должны
обернуть это в Pipeline с классификатором (например,
DecisionTreeClassifier) для возможности делать прогнозы.
>>> clf = make_pipeline(transformer, DecisionTreeClassifier())
>>> clf = clf.fit(X_train, y_train)
>>> results = clf.predict(X_test)
>>> results.shape
(100,)
7.4.7. Оценщики, обрабатывающие значения NaN#
Некоторые оценщики предназначены для обработки значений NaN без предварительной обработки. Ниже приведен список этих оценщиков, классифицированных по типу (кластеризация, регрессор, классификатор, преобразование):
- Оценщики, которые допускают значения NaN для типа
cluster: - Оценщики, которые допускают значения NaN для типа
regressor: - Оценщики, которые допускают значения NaN для типа
classifier: - Оценщики, которые допускают значения NaN для типа
transformer: