Когда дело доходит до классического машинного обучения, проектирование функций является одним, если не самым важным фактором для улучшения ваших результатов и ускорения вашей модели, даже не утруждая себя настройкой или фантазией с вашей моделью.
Ресурсов и книг, которые подробно описывают разработку функций, не так много, поэтому я хотел составить список фрагментов кода, охватывающих большинство техник, которые я нашел в Интернете и использовал в течение долгого времени, которые были критически важны для большинства проектов, над которыми я работал. на. Эти методы в основном применимы к дереву решений и моделям регрессионного типа (не к глубокому обучению).
Моя цель здесь состояла в том, чтобы привести примеры кодирования, а не раскрывать тонкости работы каждого метода и их влияния на различные показатели модели, я предполагаю, что вы уже слышали о большинстве этих методов и что вы поэкспериментируете, чтобы узнать, что работает лучше всего. для каждого проекта, над которым вы работаете.
Если я что-то пропустил, укажите это в комментариях, и я обновлю пост с упоминанием.
В этом посте я использую следующие наборы данных:
Я загрузил все в репозиторий GitHub: https://github.com/michaelabehsera/feature-engineering-cookbook
Начиная
%matplotlib inline import pandas as pd import numpy as np import missingno as msno from numpy import random import matplotlib.pyplot as plt
1. Замена значений NaN (или NULL)
Давайте посмотрим, сколько значений NULL содержится в каждом столбце. Кажется, что "Age" и "Cabin" чаще всего не имеют значения. Мы сосредоточимся на замене нулевых значений для Age.
df.isnull().sum() PassengerId 0 Survived 0 Pclass 0 Name 0 Sex 0 Age 177 SibSp 0 Parch 0 Ticket 0 Fare 0 Cabin 687 Embarked 2 dtype: int64
Среднее / среднее значение
df['Age'].fillna((df['Age'].mean()), inplace=True) df['Age'].fillna((df['Age'].median()), inplace=True) print('Now Age has {} null values.'.format(df.isnull().sum()['Age']))
Замена на 0 или -1
df['Age'].fillna(value=0, inplace=True)
Замена случайным числом. Вменение случайной выборки
Мы можем заменить возраст случайными числами от 1 до 100 следующим образом.
null_rows = df['Age'].isnull() num_null_rows = sum(null_rows) rand = random.randint(1, 101, size=num_null_rows) df.loc[null_rows, 'Age'] = rand df['Age'].plot.hist(title='Distribution of Age - replace null with random value') plt.show()
Более разумным подходом было бы заменить Age случайными выборками из ненулевого распределения Age (как и при предыдущем подходе, мы бы сгенерировали столько же 99-летних, сколько 25-летних).
rand = np.random.choice(df.loc[~null_rows, 'Age'], replace=True, size=num_null_rows) df.loc[null_rows, 'Age'] = rand df['Age'].plot.hist(title='Distribution of Age - replace null with samples from distribution of Age') plt.show() print('Note the lack of 100 year olds in the above plot compared to the previous plot.')
Указание на отсутствие
Мы также могли бы использовать дополнительную переменную 0/1, чтобы указать нашей модели, когда возраст отсутствует.
df['Age_Missing'] = np.where(df['Age'].isnull(), 1, 0)
Вменение NA по ценностям в конце распределения
df['Age'].fillna(df.Age.mean() + df.Age.std() * 3, inplace=True)
Замена ценностями вашего выбора на основе предположения.
df['Age'].fillna(value='My Unique Value', inplace=True)
Использование регрессии для вменения недостающих значений атрибутов
Мы будем использовать эти переменные для прогнозирования отсутствующих значений для возраста (мы могли бы использовать другие, но нам нужно было бы преобразовать из текста).
from sklearn.linear_model import LinearRegression numeric_vars = ['Pclass', 'SibSp', 'Parch', 'Fare'] null_rows = df[numeric_vars + ['Age']].isnull().any(1) # rows where Age or any feature var. is null
Подобрать регрессионную модель к ненулевым строкам данных, спрогнозировать нулевые строки.
lr = LinearRegression() lr.fit(df.loc[~null_rows, numeric_vars], df.loc[~null_rows, 'Age']) df.loc[null_rows, 'Age'] = lr.predict(df.loc[null_rows, numeric_vars])
2. Масштабирование функций
Стандартный скалер
from sklearn.preprocessing import StandardScaler x = StandardScaler().fit_transform(x)
Скалер MinMax
from sklearn.preprocessing import MinMaxScaler x = MinMaxScaler().fit_transform(x)
Надежный скалер
from sklearn.preprocessing import RobustScaler x = RobustScaler().fit_transform(x)
3. Технические выбросы в числовых переменных
Среднее / медианное вменение или случайная выборка
Если у нас есть основания полагать, что выбросы вызваны механической ошибкой или проблемами во время измерения. Это означает, что если выбросы по своей природе похожи на отсутствующие данные, то для замены выбросов можно применить любой из методов, обсуждаемых для отсутствующих данных. Поскольку количество выбросов по своей природе невелико (в противном случае они не были бы выбросами), разумно использовать условное исчисление среднего / медианного значения для их замены.
Выявление выбросов с помощью квантилей
q25 = df['Age'].quantile(0.25) q75 = df['Age'].quantile(0.75) IQR = q75 - q25 # Any value higher than ulimit or below llimit is an outlier ulimit = q75 + 1.5*IQR llimit = q25 - 1.5*IQR print(ulimit, llimit, 'are the ulimit and llimit') print('Imply Age outliers:') df['Age'][np.bitwise_or(df['Age'] > ulimit, df['Age'] < llimit)]
Определите выбросы со средним значением
Использование среднего и стандартного отклонения для обнаружения выбросов должно выполняться только с данными, которые не сильно искажены. Возраст несколько искажен, поэтому это может быть проблемой.
ulimit = np.mean(df['Age']) + 3 * np.std(df['Age']) llimit = np.mean(df['Age']) - 3 * np.std(df['Age']) ulimit, llimit #out: (73.248081099510756, -13.849845805393123)
Дискретность
# Re-read data and fill nulls with mean (could use other null-filling method) df = pd.read_csv('train.csv') not_null = ~df['Age'].isnull() # Get the bin edges using np.histogram num_bins = 10 _, bin_edges = np.histogram(df['Age'][not_null], bins=num_bins) # Optionally create labels # labels = ['Bin_{}'.format(i) for i in range(1, len(intervals))] labels = [i for i in range(num_bins)] # Create new feature with pd.cut df['discrete_Age'] = pd.cut(df['Age'], bins=bin_edges, labels=labels, include_lowest=True)
Обрезка
# Let's first remove any missing values df['Age'].fillna((df['Age'].mean()), inplace=True) # Get the outlier values index_of_high_age = df[df.Age > 70].index # Drop them df = df.drop(index_of_high_age, axis=0)
Винсоризация (верхнее кодирование нижнее кодирование)
# Get the value of the 99th percentile ulimit = np.percentile(df.Age.values, 99) # Get the value of the 1st percentile (bottom 1%) llimit = np.percentile(df.Age.values, 1) # Create a copy of the age variable df['Age_truncated'] = df.Age.copy() # Replace all values above ulimit with value of ulimit df.loc[df.Age > ulimit, 'Age_truncated'] = ulimit # Replace all values below llimit with value of llimit df.loc[df.Age < llimit, 'Age_truncated'] = llimit
Преобразование ранга (когда расстояние не так важно)
from scipy.stats import rankdata # This is like sorting the variable and then assigning an index starting from 1 to each value rankdata(df['Age'], method='dense')
4. Инженерные ярлыки, категориальные переменные
One-Hot-Encoding и Pandas получают манекены
Быстрое кодирование
from sklearn.preprocessing import OneHotEncoder one = OneHotEncoder(sparse=False).fit_transform(df[['Parch']]) #Convert transformed column from numpy to a DataFrame and merge new column with older one. onecol = pd.DataFrame(one) results = pd.merge(onecol, df, left_index=True, right_index=True)
Получите манекены
# Generally, pd.get_dummies is an easier approach to one-hot encoding pd.get_dummies(df['Sex']).head()
Выпадение первым
В моделях, которые используют все функции одновременно (большинство моделей, кроме моделей ансамбля деревьев и т. они мужские, поэтому мы можем отбросить женский).
df['Male'] = pd.get_dummies(df['Sex'], drop_first=True)
Среднее кодирование
Мы вычисляем среднее значение целевой переменной для каждого класса переменной, которую хотим закодировать, и заменяем эту переменную этими средствами.
# Calculate the mean encoding means_pclass = df[['Survived']].groupby(df['Pclass']).apply(np.mean) means_pclass.columns = ['Mean Encoding'] means_pclass # Merge the encoding into our dataframe (by matching Pclass to the index of our prob. ratio dataframe) df = pd.merge(df, means_pclass, left_on=df.Pclass, right_index=True)
Кодирование отношения вероятности
def probability_ratio(x): probability_eq_1 = np.mean(x) probability_eq_0 = 1.0 - probability_eq_1 return probability_eq_1 / probability_eq_0 # Calculate the probability ratio encoding prob_ratios_pclass = df[['Survived']].groupby(df['Pclass']).apply(lambda x: probability_ratio(x)) prob_ratios_pclass.columns = ['Prob Ratio Encoding'] prob_ratios_pclass # Merge the encoding into our dataframe (by matching Pclass to the index of our prob. ratio dataframe) df = pd.merge(df, prob_ratios_pclass, left_on=df.Pclass, right_index=True) df.head()
Кодировка веса доказательств
def weight_of_evidence(x): probability_eq_1 = np.mean(x) probability_eq_0 = 1.0 - probability_eq_1 return np.log(probability_eq_1 / probability_eq_0) # Calculate the probability ratio encoding woe_pclass = df[['Survived']].groupby(df['Pclass']).apply(lambda x: weight_of_evidence(x)) woe_pclass.columns = ['WOE Encoding'] woe_pclass df = pd.merge(df, woe_pclass, left_on=df.Pclass, right_index=True)
Кодировка метки
Коды каталогов
# You can use cat.codes to convert the variable into a binary one df['Sex'] = df['Sex'].astype('category') # Cat.codes only works if the dtype is 'category' df['Sex'].cat.codes.head()
Факторизация
Также достигается тот же конец, что и `cat.codes`, но дает нам ярлыки для каждой категории.
label, val = pd.factorize(df['Sex']) df['IsFemale'] = label
Двоичное кодирование
# Можно также использовать np.where, чтобы указать, какие значения должны быть 1 или 0, как показано ниже
df['Sex_Binary'] = np.where(df['Sex'].isin(['Male','Female']), 1, 0)
5. Технические даты
Создание столбцов на основе часов / минут…
df = pd.read_csv('news_sample.csv') time = pd.to_datetime(df['time'])
Панды поставляются с упакованными свойствами DateTime, которые вы можете проверить здесь: https://pandas.pydata.org/pandas-docs/stable/api.html#datetimelike-properties. Вы даже можете получить столбец с микросекундами. Вот некоторые из них, которые я использую чаще всего.
- Месяц
- Мин.
- Секунды
- Четверть
- Семестр
- День (число)
- День недели
- Hr
df[‘Month’] = time.dt.month df[‘Day’] = time.dt.day df[‘Hour’] = time.dt.hour df[‘Minute’] = time.dt.minute df[‘Seconds’] = time.dt.second
Создание столбца isweekend
df['is_weekend'] = np.where(df['Day'].isin([5,6]), 1, 0)
6. Инженерные смешанные переменные
Мы видели, что смешанные переменные - это переменные, значения которых содержат как числа, так и метки.
Как мы можем спроектировать переменные этого типа для использования в машинном обучении?
Что нам нужно сделать в этих случаях, так это выделить категориальную часть в одной переменной и числовую часть в другой переменной. Таким образом, мы получаем 2 переменные из исходной.
Здесь особо не о чем рассказывать, кроме одного фальшивого примера.
data = ['Apple', 'Banana', '2', '6'] lst_strings = [] lst_int = [] for i in data: if i == 'Apple': lst_strings.append(i) elif i == 'Banana': lst_strings.append(i) if i == '2': lst_int.append(int(i)) elif i == '6': lst_int.append(int(i))
7. Разработка редких меток в категориальных переменных
Допустим, есть небольшой% значений в большой группе категорий в функции, вы можете взять их и назвать «другими», чтобы уменьшить значения в наборе данных и потенциально уменьшить переобучение.
Эти наблюдения можно разделить на следующие категории:
- Замена редкой метки самой частой меткой
- Группировка наблюдений, показывающих редкие ярлыки, в уникальную категорию (с новым ярлыком, например "Редкий" или "Другой").
Замена редкой метки самой частой меткой
val_counts = df['Age'].value_counts() uncommon_ages = val_counts[val_counts<3].index.values most_freq_age = val_counts.index[0] df.loc[df['Age'].isin(uncommon_ages), 'Age'] = most_freq_age
Группирование наблюдений, показывающих редкие метки, в уникальную категорию (с новой меткой, например «Редкие» или «Другое»)
val_counts = df['Age'].value_counts() uncommon_ages = val_counts[val_counts<3].index.values df.loc[:, 'Age_is_Rare'] = 0 df.loc[df['Age'].isin(uncommon_ages), 'Age_is_Rare'] = 1 df.loc[:, 'Age_is_Rare'].head()
8. Преобразование переменных
В частности, для линейных моделей может быть полезно преобразовать входные объекты (или целевые объекты) до подгонки модели, чтобы их распределение выглядело нормальным или приблизительно нормальным (то есть симметричным и колоколообразным).
Преобразование Гаусса
df['Age'].plot.hist() df['Age Log'] = df['Age'].apply(np.log) df['Age Log'].plot.hist()
Взаимное преобразование
df['Age Reciprocal'] = 1.0 / df['Age'] df['Age Reciprocal'].plot.hist()
Преобразование квадратного корня
df['Age Sqrt'] = np.sqrt(df['Age']) df['Age Sqrt'].plot.hist()
Экспоненциальное преобразование
df['Age Exp'] = np.exp(df['Age']) df['Age Exp'].plot.hist()
Трансформация Boxcox
from scipy.stats import boxcox df[‘Age BoxCox’] = boxcox(df[‘Age’])[0] df[‘Age BoxCox’].plot.hist()
9. Особенности взаимодействия
Возможно, например, пассажиры старшего возраста, которые также заплатили более высокую плату за проезд, имели * особенно * высокие шансы не выжить на Титанике. В таком случае мы бы назвали это эффектом * взаимодействия * между Age и Fare. Чтобы помочь нашей модели учесть этот эффект взаимодействия, мы можем добавить новую переменную Age * Fare.
df = pd.read_csv('train.csv') df['Age_x_Fare'] = df['Age'] * df['Fare']
Это, конечно, только один пример функции взаимодействия, это мощный метод, который может потребовать от вас некоторого творчества на основе набора данных, с которым вы имеете дело.