РАЗРАБОТКА ФУНКЦИЙ И ПРЕДВАРИТЕЛЬНАЯ ОБРАБОТКА ДАННЫХ

Разработка функций и предварительная обработка данных являются наиболее важными темами в науке о данных. По общему мнению, 80% работы — это предварительная обработка данных, а 20% работы — моделирование в проекте машинного обучения, поэтому есть вопросы, которые необходимо учитывать. Цель этой записной книжки — стать учебным пособием по подходу к подготовке данных.

Прежде всего, проверьте, в чем разница между проектированием признаков и предварительной обработкой данных.

Разработка функций: работа, выполняемая над функциями (например, предварительная обработка, создание новой переменной и т. д.), а также создание переменных из необработанных данных.

Предварительная обработка данных. Процесс подготовки данных перед внедрением модели.

На самом деле цель аналогична: сделать набор данных более подходящим и подготовить его для модели. Разницу можно показать на рисунке:

На этом этапе необходимо рассмотреть и обработать 4 темы для подготовки набора данных.

  1. Выбросы
  2. Отсутствующие значения
  3. Извлечение признаков
  4. Кодирование и масштабирование

Все это этапы предварительной обработки данных и разработки функций, и каждый из них будет объяснен в записной книжке.

Очевидно, что перед процессом необходимо изучить набор данных и понять переменные. Это означает, что набор данных должен быть подготовлен перед подготовкой. Готовьте попкорн к фильму «Начало науки о данных»! Конечно, это не так сложно, но есть правила и шаги, которые нужно соблюдать, чтобы не заблудиться.

Оглавление

1. Сначала просмотрите набор данных

2. EDA (исследовательский анализ данных)

- 2.1 Анализ выбросов и пропущенных значений

3. Предварительная обработка (выбросы и пропущенные значения)

4. Извлечение признаков

5. Кодирование и масштабирование

6. Моделирование

1. Сначала посмотрите на набор данных

Бизнес-задача: желательно разработать модель машинного обучения, которая могла бы предсказывать, есть ли у людей диабет, когда указаны их характеристики. Ожидается, что вы выполните необходимый анализ данных и этапы разработки признаков перед разработкой модели.

2. EDA (исследовательский анализ данных)

Это часть изучения набора данных. В этой части будет выполнен анализ выбросов и отсутствующих значений, но единственная цель состоит в том, чтобы наблюдать за ними в этой части. Операции и редактирование для них будут выполнены в Части 3: Предварительная обработка.

import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib import pyplot as plt
# !pip install missingno
import missingno as msno
from datetime import date
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.neighbors import LocalOutlierFactor
from sklearn.preprocessing import MinMaxScaler, LabelEncoder, StandardScaler, RobustScaler
df = pd.read_csv("../input/diabetes/diabetes.csv")
df.head()

Интересно, что нет категорического или пропущенного значения. Он будет рассмотрен.

Теперь мы просмотрели данные и проверили переменные. Первый шаг — углубиться и понять переменные. Здесь необходимо задать вопросы: «Какие переменные на самом деле являются числовыми, а какие из них категориальными? Существует ли какая-либо кардинальная переменная?».

Например, целевая переменная Результат кажется числовой переменной, но известно, что она является категориальной, поскольку этот столбец включает только 1 и 0 (заболевание или отсутствие заболевания). Эти типы переменных должны рассматриваться как категориальные.

Точно так же, если есть категориальная переменная, которая включает в себя имена пациентов в наборе данных, такой набор данных будет считаться кардинальным, поскольку имя не может нести никакой информации. Это неверно для нашего набора данных, потому что таких столбцов нет, но это нужно было учитывать, если бы набор данных имел.

def grab_col_names(dataframe, cat_th=10, car_th=20):
    # cat_cols, cat_but_car
    cat_cols = [col for col in dataframe.columns if dataframe[col].dtypes == "O"]
    num_but_cat = [col for col in dataframe.columns if dataframe[col].nunique() < cat_th and
                   dataframe[col].dtypes != "O"]
    cat_but_car = [col for col in dataframe.columns if dataframe[col].nunique() > car_th and
                   dataframe[col].dtypes == "O"]
    cat_cols = cat_cols + num_but_cat
    cat_cols = [col for col in cat_cols if col not in cat_but_car]

    # num_cols
    num_cols = [col for col in dataframe.columns if dataframe[col].dtypes != "O"]
    num_cols = [col for col in num_cols if col not in num_but_cat]

    return cat_cols, num_cols, cat_but_car


cat_cols, num_cols, cat_but_car = grab_col_names(df)

Видно, что «Исход» — это категориальная переменная, которая является целевым значением.

2.1 Анализ выбросов и пропущенных значений

Теперь проверьте пропущенные значения и выбросы. Единственным процессом в EDA является анализ этих значений. Редактирование отсутствующих значений и выбросов будет выполнено в следующей части.

Перед анализом сначала будет сделано объяснение этих терминов, коды будут размещены сразу после каждого объяснения.

Выбросы

Значения, которые значительно отклоняются от общей тенденции в данных, называются выбросами. Особенно в линейных задачах последствия выбросов более серьезны. Они оказывают меньшее влияние на методы дерева, но все же их необходимо учитывать.

Как определяются выбросы? Важным моментом является определение приемлемого порогового значения, которое является верхним и нижним пределом. После определения порогового значения выбросы отлавливаются на основе этих значений. Методы, с помощью которых мы можем поймать пороговое значение:

  • Знание отрасли
  • Подход стандартного отклонения
  • Аппроксимация Z-оценки
  • Метод Boxplot (межквартильный размах-IQR) (одномерный)
  • Метод LOF => Многомерный

Мы подчеркнем и рассмотрим Boxplot (метод IQR) и метод LOF для анализа. После получения выбросов их можно удалить или переназначить с пороговыми значениями (прижав значения выбросов к пороговым значениям). Эти подходы к решению будут в следующей части.

Есть 2 момента, которые привлекают внимание с первого взгляда:

  • «Инсулин» имеет высокое стандартное отклонение, значения квартилей большие, а выброс очевиден.
  • Распределение квартилей SkinThickness неравномерно.

Для лучшего наблюдения нарисуйте их.

# Boxplot
sns.boxplot(x=df["Insulin"])
plt.show()
sns.boxplot(x=df["SkinThickness"])
plt.show()

Было упомянуто, что критической точкой выбросов является определение порога. На диаграмме будет использоваться метод IQR. Диапазон, называемый IQR (межквартильный диапазон), определяется в соответствии с квартилями, после чего будут найдены верхний и нижний пределы. В литературе:

IQR = Q3 — Q1

  • Q3: квантиль 75%
  • Q1: квантиль 25%

Верхний предел определяется как значение, в 1,5 раза превышающее Q3, а нижний предел определяется как значение, в 1,5 раза меньшее, чем Q1.

Определение Q3 и Q1 как 75-го и 25-го не является правилом. Это обычная формула, но эти квартили могут быть определены как 95–5 или 90–10 в зависимости от задачи. Если в наборе данных небольшое количество данных, разделение его на 75–25% может оказаться нецелесообразным, поскольку может произойти потеря информации.

В свете этой информации давайте напишем функцию, которая находит пороги. Добавьте квантили в качестве аргументов, потому что мы можем захотеть их изменить.

def outlier_thresholds(dataframe, col_name, q1=0.25, q3=0.75):
    quartile1 = dataframe[col_name].quantile(q1)
    quartile3 = dataframe[col_name].quantile(q3)
    interquantile_range = quartile3 - quartile1
    up_limit = quartile3 + 1.5 * interquantile_range
    low_limit = quartile1 - 1.5 * interquantile_range
    return low_limit, up_limit
for i in df.columns:
    print("Thresholds of {} : ({:.2f}, {:.2f})".format(i, *outlier_thresholds(df,i)))

Отсутствующие значения

«Идея вменения соблазнительна и опасна». -Р.Дж.А. Литтл и Д.Б. Рубин

Имеется в виду ситуация отсутствия наблюдений. Ее можно решить 3 способами:

  • Удаление
  • Методы присвоения значений (среднее, мода, медиана и т. д.)
  • Прогнозные методы (ML, статистические методы и т. д.)

Один из важных вопросов, который следует учитывать при работе с отсутствующими данными: необходимо знать случайность отсутствующих данных, т. е. возникают ли отсутствующие данные случайным образом или нет. Если оно случайное, мы либо удаляем его, либо заполняем средним значением и т. д. Однако может существовать зависимость отсутствующей переменной с другой переменной, следовательно, отсутствующее может быть неслучайным. Эта ситуация также рассматривается.

На первый взгляд кажется, что пропущенного значения нет, но глюкоза, инсулин и т. д. единицы наблюдения, содержащие значение 0 в переменных, могут представлять пропущенное значение. Например, значение уровня глюкозы или инсулина у человека не может быть равно 0. Учитывая эту ситуацию, давайте присвоим значения 0 соответствующим значениям как NaN, а затем применим операции к отсутствующим значениям.

Здесь значение беременности может быть равно 0. Это нормальная ситуация, поэтому в этом столбце не будет NaN.

num_cols_miss = [i for i in num_cols if i != "Pregnancies"]
for i in num_cols_miss:
    df[i] = df.apply(lambda x: np.nan if x[i] == 0 else x[i], axis=1)
# df after adding NaN
df.head(10)

Для отсутствующих значений существует библиотека с именем «missingno», которая была импортирована в начале. Эта библиотека обеспечивает визуальный анализ пропущенных значений.

# the number of non-missing data
msno.bar(df)
plt.show()

"""
heatmap can be used rather than try to observe with eyes on matrix map
this heatmap shows the correlation of missing values on variables
"""
msno.heatmap(df)
plt.show()

Существует интересный вывод о том, что существует сильная положительная корреляция между отсутствующими значениями переменных «Толщина кожи» и «Инсулин». Его можно будет использовать позже.

Библиотека «missingno» помогает нам визуализировать пропущенные значения, теперь давайте подойдем более математически. Давайте составим таблицу, состоящую из пропущенных значений. Для этого напишите функцию, которая показывает отсутствующие значения в кадре данных в виде таблицы.

def missing_values_table(dataframe, na_name=False):
    # only take missing columns
    na_columns = [col for col in dataframe.columns if dataframe[col].isnull().sum() > 0]

    n_miss = dataframe[na_columns].isnull().sum().sort_values(ascending=False)  # number of missing value
    ratio = (dataframe[na_columns].isnull().sum() / dataframe.shape[0] * 100).sort_values(ascending=False) # ratio
    missing_df = pd.concat([n_miss, np.round(ratio, 2)], axis=1, keys=['n_miss', 'ratio'])  # make table
    print(missing_df, end="\n")

    if na_name:
        return na_columns
missing_values_table(df)

3. Предварительная обработка (решение выбросов и пропущенных значений)

В части 2 анализ был завершен. Было проведено исследование пороговых значений для выбросов и способов достижения отсутствующих значений. Следующим шагом будет вмешательство в них и подготовка набора данных. Эта часть будет разделена на 2, как и часть 2. Сначала будет рассмотрена проблема выбросов, затем будут обработаны пропущенные значения.

На наши пороговые значения можно повлиять после операций над пропущенным значением. Кроме того, метод выбросов, LOF, нельзя использовать, когда набор данных включает значения NaN. Следовательно, недостающая часть значения будет взята в первую очередь при предварительной обработке.

Отсутствующие значения

В части анализа упоминается, что существует 3 решения для пропущенных значений.

  • Удаление. Удаление строк, содержащих отсутствующие значения. Особенно в небольшом наборе данных это приводит к потере информации. Например, в наборе данных 768 строк, а в Insuline 374 пропущенных значения. Если пропущенные значения отброшены, половина набора данных будет потеряна, и информация также будет потеряна. Если набор данных будет большим и есть несколько отсутствующих значений, которыми можно пожертвовать, удаление может быть вариантом.
# copy dataset to see effect without damage the main dataset
df_new = df.copy()
df_new.shape # => output: (768, 9)
df_new.dropna(inplace=True)
df_new.shape # => output: (392, 9)
  • Методы присвоения значений: мы можем заполнить значения NaN средним значением столбца, медианой, модой и т. д. Если распределение однородно, заполнение медианой или средним значением логично. Кроме того, в некоторых сценариях имеет смысл заполнить определенное число, например 0.
df_fill = df.apply(lambda x: x.fillna(x.median()) if x.dtype != "O" else x, axis=0)
df_fill.head(10) 
# after filling

  • Методы прогнозирования. Этот метод основан на машинном обучении, статистических методах и т. д. Это продвинутый уровень для заполнения значений NaN. В этом методе может быть реализована модель, и недостающие значения прогнозируются этой моделью. Есть 2 момента, которые мы должны учитывать:

1. Мы должны поместить категориальные переменные в одноразовый кодировщик. Поскольку мы будем использовать метод моделирования, модель ожидает от нас переменных определенным образом, и мы должны им соответствовать.

2. Поскольку KNN — это алгоритм, основанный на расстоянии, нам необходимо стандартизировать переменные.

Кодирование и стандартизация будут объяснены в следующей части, поэтому в этой части будет показан только процесс метода прогнозирования. Единственный момент, пока что, так как все переменные числовые, кодирование делаться не будет.

df_ml = df.copy()

# standardization
scaler = MinMaxScaler()
df_ml = pd.DataFrame(scaler.fit_transform(df_ml), columns=df_ml.columns)
df_ml.head()
# fill with KNN
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=5)
df_ml = pd.DataFrame(imputer.fit_transform(df_ml), columns=df_ml.columns)
df_ml.head()

Таким образом, мы завершили процесс заполнения, но проблема в том, что все значения, которые мы заполнили, стали стандартизированными. Он может превратить нормальные значения с помощью «inverse_transform».

df_ml = pd.DataFrame(scaler.inverse_transform(df_ml), columns=df_ml.columns)
df_ml.head()

Мы рассмотрели возможные способы борьбы с отсутствующими данными. Чтобы изучить способы, методы были реализованы в разных фреймах данных. Теперь мы должны применить эти методы к основному фрейму данных.

Во-первых, снова проверьте количество пропущенных значений с помощью функции, которую мы определили в части анализа.

Здесь мой подход разделит процесс на 2. Поскольку две переменные имеют много пропущенных значений, прогнозирование для них может быть нелогичным. Это всего лишь интерпретация, и она открыта для комментариев. Часть предварительной обработки данных является субъективной, но общая цель состоит в том, чтобы подготовить данные как можно более четко перед моделью.

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

na_cols = missing_values_table(df, na_name=True) # columns that includes missing values
n_miss = df[na_cols].isnull().sum() # number of missing values on variables

# 100 as a threshold, it is open to comment
na_cols_ml = [i for i in n_miss.index if n_miss[i] < 100]
na_cols_med = [i for i in n_miss.index if n_miss[i] > 100]
print("Columns that will be applied ML model:", na_cols_ml)
print("Columns that will be filled with median:", na_cols_med)

# for the number of missing value is less than 100
df[na_cols_med] = df[na_cols_med].apply(lambda x: x.fillna(x.median()) if x.dtype != "O" else x, axis=0)

Как видно выше, «Толщина кожи» и «Инсулин» были заполнены их средними значениями. Теперь переменные с небольшим количеством пропущенных значений будут заполнены моделью ML.

# standardization
scaler = MinMaxScaler()

# take only needed columns
df[na_cols_ml] = pd.DataFrame(scaler.fit_transform(df[na_cols_ml]), columns=df[na_cols_ml].columns)
print(df[na_cols_ml].head())

# fill with KNN
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=5)
df[na_cols_ml] = pd.DataFrame(imputer.fit_transform(df[na_cols_ml]), columns=df[na_cols_ml].columns)
print(df[na_cols_ml].head())

# from standardized to non-standardized 
df[na_cols_ml] = pd.DataFrame(scaler.inverse_transform(df[na_cols_ml]), columns=df[na_cols_ml].columns)
print(df[na_cols_ml].head())

Выбросы

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

Во-первых, будет объяснен метод LOF, поскольку он контролирует значимость переменных друг для друга и дает несколько выбросов. Затем будет реализован метод IQR.

  • Фактор локального выброса (LOF). Другим подходом является фактор локального выброса. Это помогает нам соответствующим образом определять выбросы, упорядочивая наблюдения на основе плотности в их местоположении. Локальная плотность точки означает окрестности вокруг этой точки. Если точка значительно менее плотна, чем ее соседи, то эта точка находится в более разреженной области, поэтому может быть выброс. Метод LOF позволяет нам рассчитать оценку расстояния на основе окрестностей.

Метод LOF говорит: «Я дам вам оценку, чем ближе оценка к 1, тем лучше». Следовательно, по мере удаления от 1 вероятность того, что наблюдение будет выбросом, увеличивается. Метод LOF также используется для определения пороговых значений.

Пример использования: в нашем наборе данных есть как «Возраст», так и «Беременности». Например, когда эти переменные берутся отдельно, может не быть никаких выбросов. Однако, когда эти две переменные объединяются, это может быть выбросом. Что это означает, объясняется в следующем примере кадра данных.

df_ex = pd.DataFrame({"age": [17, 35, 47],
                     "pregnancy": [5, 2, 3]})
df_ex

Когда мы берем переменные по отдельности, «Возраст» выглядит нормально. Человеку может быть 17, 35 или 47 лет. Точно так же «Беременность» не вызывает проблем, если мы рассматриваем только количество беременностей, потому что человек может быть беременным 5 раз, дважды или 3 раза.

Теперь проверьте первый индекс, значения имеют значения по отдельности, но вместе человек в 17 лет не может быть беременным 5 раз. Это исключительный ряд. LOF помогает нам находить такие значения и исправлять их.

Будут рассмотрены баллы LOF, затем для определения порога будет применен «метод локтя».

# LOF
clf = LocalOutlierFactor(n_neighbors=20)
clf.fit_predict(df)  # returns LOF scores
df_scores = clf.negative_outlier_factor_ # keep scores to observe (negative)
# df_scores = -df_scores # for changing to pozitive but we will use as negative

ПРИМЕЧАНИЕ. Метод "negative_outlierfactor" дает отрицательные оценки. Их можно изменить на положительные, но будут использоваться исходные отрицательные значения, чтобы их было легче наблюдать в методе локтя, который будет использоваться. Поэтому, если оценки близки к -1, результат не становится выбросом, не приближаясь к 1, как в начале. Например, если у нас есть оценки от -1 до -10, будет интерпретировано, что значения -10, как правило, более резко выделяются.

# elbow method
scores = pd.DataFrame(np.sort(df_scores))
scores.plot(stacked=True, xlim=[0, 20], style='.-')  # x=gözlemler, y=outlier skorları
plt.show()

По оси x отложены наблюдения, а по оси y - выбросы. Каждая точка представляет собой пороговые значения, и график был построен в соответствии с этими пороговыми значениями. Пороговое значение должно быть определено в соответствии с таблицей. Точка, в которой происходит самое крутое изменение наклона, является 3-м индексом. Следовательно, окончательный комментарий может быть:

Есть некоторые наблюдения и их оценки, и есть проблема, куда разделить эти оценки. Самое незначительное изменение было в 3-м индексе, так что это точка, где самое резкое изменение определяется как пороговое значение. Поскольку более низкие оценки (более отрицательные значения) тем хуже, необходимо установить пороговое значение и сократить его.

th = np.sort(df_scores)[3]  # set any lower scores than that as outlier, 3 data found
print("Before delete outliers:", df.shape) # (768, 9)
df.drop(axis=0, labels=df[df_scores < th].index, inplace=True) 
print("After delete outliers:", df.shape) # (765, 9)
  • IQR: мы находим пороговые значения при анализе с помощью расчета IQR. Теперь будет выполнено переназначение порогов. Это означает, что выбросы будут заменены верхними и нижними порогами. Почему такой процесс сделан, основан на предотвращении потери данных. Особенно в небольшом наборе данных важны все данные.

В литературе за верхний предел принимается 75%-й квантиль (Q3), и более высокое значение, чем 1,5xQ3, будет заменено этим порогом, аналогичный процесс выполняется с нижним значением, которое ниже 1,5xQ1, где Q1 равно 25. % квантиль. Мы также можем использовать квантили как 5 на 95, потому что удаление или заполнение в соответствии с 25–75 приведет к серьезной потере данных и остаткам, мы сами добавим шум и создадим проблемы. Следовательно, их доля зависит от проблем и наборов данных. Если используются древовидные методы, можно выбрать не трогать выбросы или при приближении к неприемлемым выбросам можно реализовать бритье с конца.

В части анализа пороги были найдены с помощью функции «outlier_thresholds». Теперь напишите функцию, которая берет эти пороги и переназначает выбросы.

def replace_with_thresholds(dataframe, variable):
    low_limit, up_limit = outlier_thresholds(dataframe, variable)
    dataframe.loc[(dataframe[variable] < low_limit), variable] = low_limit
    dataframe.loc[(dataframe[variable] > up_limit), variable] = up_limit
for col in num_cols:
    replace_with_thresholds(df, col)

Минимальное, максимальное, среднее значение события и стандартное значения изменились в соответствии с пороговыми значениями. Решение выбросов было завершено с этим процессом.

4. Извлечение признаков

При подготовке набора данных пытаются редактировать не только существующие переменные, но также необходимо создавать новые, значимые переменные. Новые столбцы иногда можно создавать с помощью математических операций, иногда называть числовое значение категориальным или диапазоном категориальных значений и т. д. Этот процесс известен как Разработка функций, и это одна из важнейших частей подготовка данных.

# pregrancy cannot be float, it occurs due to calculation of IQR
df["Pregnancies"] = df["Pregnancies"].apply(lambda x: int(x))
# create categorical columns from numerical columns

# if bins are 0, 3, 6 => 0 values become NaN due to bins 
df["NumOfPreg"] = pd.cut(df["Pregnancies"], bins=[-1, 3, 6, df["Pregnancies"].max()], labels=["Normal", "Above Normal","Extreme"])
df["AgeGroup"] = pd.cut(df["Age"], bins=[18, 25, 40, df["Age"].max()], labels=["Young", "Mature", "Old"])
df["GlucoseGroup"] = pd.qcut(df["Glucose"], 3, labels=["Low", "Medium", "High"])
df["Patient"] = np.where(df["Outcome"] == 1, "Yes", "No")
# example of mathematical expression

"""Assume there is a variable named "BMIns", and it can be found with the multiplication of BMI and Insuline. 
Create and add it to data frame"""

df["BMIns"] = df["BMI"]*df["Insulin"] # numerical
df["BMInsGroup"] = pd.qcut(df["BMIns"], 3, labels=["Low", "Medium", "High"]) # categorical

5. Кодирование и масштабирование

Кодирование

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

Если есть 2 класса, это называется "Двоичное кодирование", если классов больше 2, оно называется "Кодирование меток".

Он преобразует в алфавитном порядке, присваивая 0 первому увиденному.

df_enc = df.copy()

le = LabelEncoder()
le.fit_transform(df_enc["Patient"])[0:5]
# Output: array([1, 0, 1, 0, 1])
#-------------------------------------------------------------------
# let's say we forgot which 0 and which 1, inverse_transform is used to detect this
le.inverse_transform([0, 1])
# Output: array(['No', 'Yes'], dtype=object)
#-------------------------------------------------------------------
# detect variables that have 2 unique numbers for Binary Encoding
binary_cols = [col for col in df.columns if df[col].dtype not in [int, float]
               and df[col].nunique() == 2]
binary_cols
# Output: ['Patient']

Если переменная преобразуется таким образом в числовые значения и отправляется в алгоритмы, алгоритм определяет эту переменную как переменную в диапазоне от интервала. Другими словами, это преобразование запутает алгоритмы. Причина его искажения здесь, категориальная переменная превратилась в числовые значения, а категориальная переменная, которая обычно является типом номинальной шкалы, внезапно искажается в процессе преобразования и подвергается процессу сортировки, как будто есть разница между ними. Характеристика номинального типа шкалы состоит в том, что расстояние между классами равно, но это равенство нарушается после этого преобразования.

Эта ситуация может вызвать предубеждения и проблемы, когда мы конвертируем категориальные переменные в числовые значения и путаем алгоритмы. В этом случае нам нужно выполнить горячее кодирование.

Чтобы решить эту проблему, выполняется однократное преобразование, но при этом также создается ловушка, называемая «ловушка фиктивной переменной».

Если мы применяем преобразование к переменным в наборе данных, если новые переменные могут быть созданы друг над другом, это ловушка фиктивных переменных. Другими словами, если есть переменная, представляющая другую переменную, такая ситуация называется ловушкой фиктивной переменной.

Давайте посмотрим, как делается однократное кодирование, что такое ловушка фиктивной переменной и как это исправить.

df_new = pd.get_dummies(df, columns=["Patient"])
df_new.head()

Как видно, Patient отброшен, и появились новые переменные: Patient_No и Patient_Yes. Проблема в том, что Patient_No является противоположностью Patient_Yes, что означает, что Patient_No может быть создан поверх Patient_Yes. Они оба несут одну и ту же информацию, и оба находятся во фрейме данных. В этой ситуации мы должны отказаться от одного из них, и это помогает избежать создания друг друга и дублирования. С помощью этого решается ловушка с фиктивной переменной.

df_new = pd.get_dummies(df, columns=["Patient"], drop_first=True)
df_new.head()

Теперь примените этот метод к категориальным переменным нашего основного набора данных. Во-первых, снова возьмите категориальные переменные с функцией, которую мы пишем в части анализа.

cat_cols, num_cols, cat_but_car = grab_col_names(df)
cat_cols

Результат — это наша целевая переменная, а Пациент — созданная нами переменная. Они несут одну и ту же информацию, и я не хочу кодировать целевую переменную, поскольку целью окончательной модели будет ее предсказание. Остальные числовые столбцы могут быть закодированы.

cat_cols_final = [i for i in cat_cols if i not in ["Patient", "Outcome"]]
df = pd.get_dummies(df, columns=cat_cols_final, drop_first=True)
df.head()

Масштабирование

Это переменная операция масштабирования. Цель состоит в том, чтобы устранить разницу в измерениях между переменными и попытаться обеспечить, чтобы используемые модели приближались к переменным в равных условиях. На древовидные методы не влияют выбросы, стандартизация. В общей тенденции мы можем предпочесть масштабировать функции.

Структура дисперсии и информационная структура внутри самой переменной не ухудшаются, а настраиваются на определенный стандарт. Например, предположим, что набор данных имеет значение 10 и занимает 80-е место при упорядочении от меньшего к большему. Когда эта переменная стандартизирована, значение 10 будет примерно между 1–2 или от 0 до 1, но когда набор данных снова упорядочивается от меньшего к большему, это значение по-прежнему будет соответствовать 80-му значению. Поэтому при стандартизации переменной значение переменной изменится, она будет приведена к определенному формату, но не изменится разброс и суть распространяемой информации. Значения переменных, таких как среднее значение, стандартное отклонение и дисперсия, также изменятся, но разброс, распределение и текущее состояние информации, которую она несет в переменной, не изменятся.

Существует 3 распространенных метода масштабирования:

  • Стандартный масштабатор: нормализация. Вычтите среднее, разделите на стандартное отклонение. Это можно назвать z-стандартизацией. г = (х — и) / с.

Стандартный масштабер вычитает среднее значение из всех единиц наблюдения и делит его на стандартное отклонение.
ВНИМАНИЕ. И стандартное отклонение, и среднее значение являются показателями, на которые влияют выбросы. Поэтому, если мы вычтем медиану из всех единиц наблюдения и разделим на IQR, на который не влияют выбросы, мы учтем центральную тенденцию и изменение, а также выполним более надежный процесс стандартизации.
Надежный скалер более предпочтительнее, потому что он устойчив к выбросам по сравнению со стандартным скейлером, но его использование не является распространенным. Обычно используются Standard Scaler и MinMax.

  • Надежный масштабатор: вычесть медиану и разделить на IQR.
  • MinMaxScaler: используется для преобразования значений между двумя желаемыми диапазонами. Эти 2 значения также могут быть значениями, которых нет в нашем наборе данных.
df_scale = df.copy()

# standart scaler
ss = StandardScaler()
df_scale["Age_standard_scaler"] = ss.fit_transform(df_scale[["Age"]])
df_scale.head()

# robust scaler
rs = RobustScaler()
df_scale["Age_robuts_scaler"] = rs.fit_transform(df_scale[["Age"]])

# min-max scaler
# The range can be given with the feature_range=() argument
mms = MinMaxScaler() # default range from 0 to 1
df_scale["Age_min_max_scaler"] = mms.fit_transform(df_scale[["Age"]]) 

df_scale.head()

Разницу между шкалами можно наблюдать выше. В этой задаче здесь будет использоваться «робастный скейлер». Нет конкретной причины, просто все типы масштабирования нацелены на одно и то же, но влияют по-разному.

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

rs = RobustScaler()
for i in num_cols:
    df[i] = rs.fit_transform(df[[i]])
df.head()

Таким образом, наш набор данных готов к моделированию. Очевидно, что часть модели не относится к теме предварительной обработки данных и разработки функций. Просто наблюдать за эффектом очистки данных.

6. Модель

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

Пациент несет ту же информацию с Outcome. Он был создан как пример разработки функций. Кроме того, BMINs — это не настоящая функция, это был еще один пример разработки функций с использованием математических выражений. Следовательно, эти 2 переменные будут удалены.

Будет использоваться алгоритм KNN.

y = df["Outcome"]
X = df.drop(["Outcome", "Patient", "BMIns", "BMInsGroup_Medium", "BMInsGroup_High"], axis=1)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=17)

from sklearn.ensemble import RandomForestClassifier
rf_model = RandomForestClassifier(random_state=46).fit(X_train, y_train)
y_pred = rf_model.predict(X_test)
accuracy_score(y_pred, y_test)

Выход => 0,8104575163398693

Какой будет оценка без каких-либо корректировок?

df_no = pd.read_csv("../input/diabetes/diabetes.csv")
df_no.dropna(inplace=True) # there was no missing value 
# df_no = pd.get_dummies(df_no, columns=[], drop_first=True) # no cat. col. to encode

y = df_no["Outcome"]
X = df_no.drop(["Outcome"], axis=1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=17)
rf_model = RandomForestClassifier(random_state=46).fit(X_train, y_train)
y_pred = rf_model.predict(X_test)
accuracy_score(y_pred, y_test)

Выход => 0,7857142857142857

ФИНАЛ

В этом проекте были объяснены этапы предварительной обработки и подготовки данных. Эффект того, как очистка набора данных влияет на производительность модели, наблюдался в конце проекта. Это конец урока, надеюсь, он помог. Спасибо за прочтение!

Для блокнота Kaggle: https://www.kaggle.com/code/furkannakdagg/data-preparation-tutorial-with-diabetes-dataset

Для набора данных: https://www.kaggle.com/datasets/furkannakdagg/diabetes