![](https://habrastorage.org/getpro/habr/upload_files/783/ce6/9e0/783ce69e0771cacc6cd2a1f7c2593be6.png)
Автор статьи — Виктория Ляликова.
На данный момент Python является самым популярным языком программирования, который применяется для анализа данных или в машинном обучении. Сильными сторонами Python являются его модульность и возможность интегрироваться с другими языками программирования.
В науке о данных разведочный анализ данных (exploratory data analysis, EDA) является самым важным этапом в проекте и занимает около 70-80% времени всего проекта. Такой анализ позволяет изучить какие-то свойства данных, найти в них закономерности, аномалии, очистить их, подготовить и построить начальные модели для дальнейшей работы. На этом этапе можно определить вид распределения, оценить основные его параметры, обнаружить выбросы, построить матрицу корреляции признаков и т.д.
И в предварительном анализе достаточно серьезной проблемой является обнаружение недостающих (пропущенных) значений и самым сложным является то, что здесь нет какого-то универсального алгоритма. Для каждой конкретной задачи приходится искать наиболее подходящие методы или их комбинации.
Так как большинство моделей машинного обучения не могут обрабатывать пропущенные значения, значит их нельзя игнорировать в данных и эту проблему необходимо решать во время предобработки. Самым простым решением является удаление каждого наблюдения, содержащего одно или несколько пропущенных значений. Эта задача быстро и легко выполняется с помощью библиотек Numpy или pandas.
Вместе с этим необходимо стараться воздерживаться от удаления наблюдений с отсутствующими значениями. Их удаление является крайним средством, поскольку тогда алгоритм теряет доступ к полезной информации, содержащейся в непропущенных значениях наблюдений.
Можно выделить следующие основные стратегии замены пропущенных данных подстановочными значениями.
Заменить пропущенные значения средним/медианой. В данном случае необходимо вычислить среднее/медиану имеющихся значений для каждого столбца и вставить то, что получилось в пропущенные ячейки. Данный метод является простым и быстрым, но не работает с качественными переменными. Также невозможно оценить погрешность импутации.
Замена самым часто встречающимся значением или константой. Также является еще одним простым методом импутации. Можно использовать для качественных переменных
Замена данных, используя метод k ближайших соседей или kNN. Является самым популярным методом машинного обучения для решения задач классификации. Основан на оценивании сходства объектов. Используется функция расстояния Евклида
![](https://habrastorage.org/getpro/habr/upload_files/d89/288/c16/d89288c164b84a9e62c06b001f4dd99f.png)
где x_{ik} и x_{jk} — к-е элементы векторов x_{i} и x_{j} соответственно
У какого класса выше значение близости, тот класс и присваивается новому объекту.
Тогда с помощью данного метода можно вычислить значения пропущенных атрибутов на основании дистанций от попавших в область объектов и соответствующих значений этого же атрибута у объектов
![](https://habrastorage.org/getpro/habr/upload_files/2e9/fe8/f59/2e9fe8f593a2b7be9c58a6aa85c21c05.png)
где a_{i} — i-ый объект, попавший в область, k_{i} — значение атрибута k у заданного объекта a_{i}, x — новый объект, x_{k} — ый атрибут нового объекта.
Иным словами, выбирается k-точек, которые больше всего похожи на рассматриваемую, и уже на их основании выбирается значение для пустой ячейки.
Данный метод на некоторых наборах данных может быть точнее среднего/медианы или константы, учитывает корреляцию между параметрами. Главным недостатком данного метода является то, что он является вычислительно дорогим, так как требует держать весь набор данных в памяти, чтобы вычислить расстояние между пропущенным значением и каждым отдельным значением.
Множественная импутация данных (MICE). Суть данного метода заключается в том, что импутация каждого значения проводится не один раз, а много. Такой тип замены пропущенных значений позволяет понять насколько надежно или ненадежно предложенное значений. Также, MICE, позволяет работать с переменным разных типов.
Импутация данных с помощью глубоко обучения. Библиотека datawig позволяет восстанавливать недостающие значения за счет тренировки нейронной сети на тех точках, для которых есть все параметры.
Теперь попробуем разобрать некоторые возможности библиотек Phyton, которые помогут нам решить проблему недостающих значений. Для исследования необходимо выбрать набор данных (DataSet). Разнообразные наборы данных можно скачать с сайта (kaggle.com). Для демонстрации возможностей исследуем набор данных с информацией о диабете, где пациентами являются женщины не моложе 21 года индейского происхождения Пима.
Загружаем необходимые библиотеки и датасет
import pandas as pd
import numpy as np
data_diabetes = pd.read_csv('......../datasets/diabetes.csv')
data_diabetes
![](https://habrastorage.org/getpro/habr/upload_files/a05/08b/5fe/a0508b5fec53f23720a72b0647d6ef0f.png)
Данный набор содержит следующие поля: Pregnancies (количество беременностей), Glucose (уровень глюкозы), BloodPressure (давление), SkinThickness (толщина кожной складки трицепса в мм), Insulin (уровень инсулина), BMI (индекс массы тела), DiabetesPedigree (функция наличия диабета у родственников), Age (возраст), Outcome (наличие диабета).
Посмотрим на основные характеристики, каждого признака.
data_diabetes.describe()
![](https://habrastorage.org/getpro/habr/upload_files/663/088/203/6630882033144d35c42e1895b370569a.png)
Можно увидеть, почти в каждом столбце есть нулевые значения. Посчитаем их количество
(data diabetes ==0).sum()
![](https://habrastorage.org/getpro/habr/upload_files/8cd/f5b/347/8cdf5b347ac7dd4e3dbd36546a85a3f5.png)
Можно предположить, что это пропущенные значения, где неизвестное значение было заменено нулем. Преобразуем все нули в значениях 'Glucose','BloodPressure','SkinThickness','Insulin','BMI' на значения NaN и еще раз посчитаем их количество, предварительно сделав копию нашего датасета.
new_data = data_diabetes.copy(deep = True)
colsFix = ['Glucose','BloodPressure','SkinThickness','Insulin','BMI']
new_data [colsFix] = new_data[colsFix].replace(0, np.NaN)
new_data.isnull().sum()
![](https://habrastorage.org/getpro/habr/upload_files/648/c4a/b4c/648c4ab4cc3107e7aa5c6bfa6069cda2.png)
И еще раз посмотрим на характеристики датасета
new_data.describe()
![](https://habrastorage.org/getpro/habr/upload_files/0e2/3a4/7f7/0e23a47f7de4c92e4dd56d43e4ec22ce.png)
Почти в каждом столбце, кроме max произошли какие-то изменения.
Каким же образом можно проводить импутацию данных? Будем работать только со столбцами SkinThickness и Insulin, так как они имеют больше всего пропущенных значений.
1. В библиотеке Pandas (которая была уже загружена ранее) есть метод fillna(), который как раз позволяет заполнить пропущенные значения.
Синтаксис метода следующий
DataFrame.fillna(value=None, method=None, axis=None, inplace=False, limit=None, downcast=None)
Наверное, нет смысла описывать все параметры этого метода. Описание можно посмотреть здесь.
Важно, что данный метод возвращает объект, в котором заполняются все пропущенные значения. Можно заменить пропущенные значения, например, средним, медианой, самым часто встречающимся значением, константой.
Вычисли эти значения, а затем заменим пропущенные значения медианой используя метод fillna.
Параметр SkinThickness
Медиана | Среднее | Самое часто встречающееся значение |
median_Skin = new_data.Skin.median() median_Skin 29 | mean_Skin= new_data.Skin.mean() mean_Skin 29.153419593345657 | mode_Skin = new_data.Skin.mode() mode_Skin 32 |
Параметр Isulin
median_Insulin = new_data.Insulin. median() median_Insulin 125 | mean_Insulin = new_data.Insulin. mean() mean_Insulin 155.5482233502538 | mode_Insulin = new_data.Insulin. mode() mode_Insulin 105 |
Воспользуемся методом fillna()
для восстановления значений
new_data = new_data.fillna({'SkinThickness':median_SkinThickness})
new_data = new_data.fillna({'Insulin':median_Insulin})
В данном случае при использовании метода fillna
указывается столбец и значение, которые необходимо заполнить.
Если необходимо заполнить значения в нескольких столбцах сразу, тогда можно сделать, например, так
new_data = new_data.fillna({'Insulin':median_Insulin, 'SkinThickness':median_SkinThickness})
2. Библиотека Sklearn имеет класс SimpleImputer
, который используется для восстановления пропущенных значений.
Используется следующий синтаксис
SimpleImputer(missingValues, strategy, fill_value)
missingValues
– здесь мы можем установить разные кодировки пропущенных значений, например, такие как np.nan или pd.NA.
strategy
: это данные, которые заменят отсутствующие значения из набора данных, по умолчанию метод значения для этого параметра – среднее. Также можно использовать следующие стратегии: среднее, медиана, наиболее часто встречающееся, константа.
fill_value
– константное значение, которое заменит пропущенные значения
Для замены пропущенных значений используется метод fit_transform
.
Подробнее про класс SimpleImputer
можно посмотреть здесь.
Продемонстрируем работу с данным классом
from sklearn.impute import SimpleImputer #импортируем библиотеку
myImputer = SimpleImputer (strategy= 'mean') #определяем импортер для обработки отсутствующих значений, используется стратегия замены средним значением
myImputer = SimpleImputer (strategy= 'median') # используется стратегия замены медианным значением
myImputer = SimpleImputer (strategy= 'most_frequent') используется стратегия замены наиболее часто встречающимся значением
#используем метод fit_transform для замены пропущенных значений
new_data.Insulin = myImputer.fit_transform(new_data['Insulin'].values.reshape(-1,1))
Если необходимо заменить категориальные пропущенные значения, тогда можно использовать стратегию «самое частое значение» или «константа». Только одно замечание: все категориальные признаки должны быть переведены в числовые.
myImputer = SimpleImputer (strategy= 'constant', fill_value='1')
new_data.Outcome = myImputer.fit_transform (new_data['Outcome'].values.reshape(-1,1))
Для замены значений в нескольких столбцах в датасете средним/медианой/конcтантой можно использовать следующую команду
new_data[['Insulin','SkinThickness']]=myImputer.fit_transform(new_data[['Insulin','SkinThickness']])
3. Класс IterativeImputer
библиотеки sklearn реализует многомерные алгоритмы восстановления пропущенных значений, оценивая другие значения в наборе. Данный класс моделирует каждый признак пропущенного значения как функцию от других признаков и использует оценку для замены значений. IterativeImputer
фактически итеративно строит модель регрессии, используя подмножества столбцов для прогнозирования отсутствующих значений.
Подробнее про класс IterativeImputer
можно посмотреть здесь.
Посмотрим на работу с этим классом
X = new_data.copy(deep = True) #создаем копию датасета
# импортируем библиотеку
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
# определяем импортер
myImputer=IterativeImputer()
# устанавливаем imputer на X
myImputer.fit(X)
#получаем восстановленные данные
myImputer_data = myImputer.transform(X)
# полученные данные преобразовываем в DataFrame
myImputer_data = pd.DataFrame(myImputer_data,columns = new_data.columns)
4. Класс KNNImputer библиотеки sklearn обеспечивает восстановление пропущенных значений с использованием метода k – ближайщих соседей.
Подробнее про класс здесь.
# импортируем библиотеку
from sklearn.impute import KNNImputer
#определяем импортер
imputer=KNNImputer(n_neighbors=5, weigths=’uniform’)
#устанавливаем импортер на Х
imputer.fit(X)
# восстанавливаем данные
X1 = imputer.transform(X)
# полученные данные преобразовываем в DataFrame
myImputer_data = pd.DataFrame(X1,columns = new_data.columns)
Каждый отсутствующий объект рассчитывается с использованием значений от n_neighbors ближайших соседей у которых есть значение для данного объекта. Характеристики соседей усредняются равномерно или взвешиваются по расстоянию до каждого соседа
Для определения параметра n_neighbors нет какого-то определенного способа, можно определить только экспериментальным путем. Низкое значение параметра может привести к эффекту недообучения модели, а слишком высокое влияет на производительность. Чаще всего наиболее предпочтительным является значение, равное 5.
Посмотрим на сравнительную таблицу методов SimpleImputer
, IterativeImputer
, KNNImputer
![](https://habrastorage.org/getpro/habr/upload_files/9b4/c37/6cb/9b4c376cbfcbc82f3f3d22b3d18d3eec.png)
Теперь наши данные готовы к работе.
Приглашаем всех желающих на открытое занятие «Основы ООП в Python». На занятии научимся работать с классами, познакомимся с наследованием, узнаем про мутабельность экземпляров класса, передачу аргументов в инициализатор, наследование, переопределение методов, обращение к методам суперкласса. Регистрация доступна по ссылке.