Эта часть начинается с того, как очистить и предварительно обработать данные с помощью методов EDA и Feature Engineering для задачи классификации, особенно с использованием и без касания «целевой» переменной.

Контекст

Каковы наиболее частые случаи использования науки о данных в финансовой индустрии?

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

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

Оглавление

Этот комплексный проект разделен на 3 части:

  1. Пояснительный анализ данных (EDA) и разработка функций
  2. Масштабирование и выбор функций (бонус: несбалансированная обработка данных)
  3. Моделирование машинного обучения (классификация)

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

Итак, давайте начнем с первой части проекта: EDA и Feature Engineering.

A. Анализ пояснительных данных (EDA)

Давайте импортируем необходимые библиотеки и два набора данных:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
application = pd.read_csv("application_record.csv")
credit = pd.read_csv("credit_record.csv")

Как указано выше, в то время как набор данных приложения предоставляет все точки данных из личной информации, предоставленной существующими клиентами банка (например, идентификатор, пол, доход и т. Д.), Кредит набор данных сопоставляет каждый соответствующий идентификатор с его / ее статусом погашения кредита (например, X означает отсутствие кредита в месяце, C означает погашение и ›0 означает количество платежей- просроченные месяцы).

Для лучшего использования информации о кредите я очистил набор данных, преобразовав столбцы «Статус» в числовые, а также сгруппировав их по идентификатору клиента и последнему месяцу:

credit.status = credit.status.replace({'X':-2, 'C': -1})
credit.status = credit.status.astype('int')
credit.status = credit.status.apply(lambda x:x+1) 
credit_month = credit.groupby('id').months_balance.max().reset_index()
record = pd.merge(credit_month, credit, how="inner", on=["id", "months_balance"])
record.head()

Когда все было настроено, я объединил только что обработанный набор данных с приложением, используя «внутреннее слияние». Вдобавок к этому, если вы вернетесь к исходному набору данных, «Дата рождения» и «Занятость» - это количество дней, отсчитываемых назад от сегодняшнего дня, что сначала немного сложно понять. Поэтому я решил вместо этого преобразовать эти переменные в положительные числа и годы.

df['age'] = df.birth_date.apply(lambda x: round(x/-365,0))
df['year_of_employment'] = df.employment.apply(lambda x: round(x/-365,0) if x<0 else 0)
df = df.drop(columns=["birth_date","employment"])

Двигаясь дальше, два основных момента каждого EDA, которые я предлагаю вам никогда не игнорировать, это (1) проверка нулевых значений и (2) обработка выбросов . Первый гарантирует, что у нас есть 100% чистый набор данных перед обработкой и подключением к моделированию, а второй помогает избежать чрезмерного перекоса набора данных в результате незначительно экстремальных выбросов.

df.isnull().sum()
df.occupation_type = df.occupation_type.fillna("Others")

«Тип занятия» - единственная переменная, которая имеет нулевые значения (NaN), поэтому я продолжил заполнять эти значения «Другими».

Используя df.describe и sns.boxplot, я смог визуально выяснить, что «Годовой доход» и «Члены семейства» - это две переменные, которые имеют выбросы в наборе данных. как показано ниже:

Чтобы удалить выбросы, я написал функцию, которую можно легко применить сразу к переменным с похожими проблемами:

def remove_outlier(col):
    q25 = col.quantile(0.25)
    q75 = col.quantile(0.75)
    iqr = q75 - q25
    cutoff = iqr*1.5
    lower = q25 - cutoff
    upper = q75 + cutoff
    return lower, upper
#Remove outliers for Annual Income
lower_1, upper_1 = remove_outlier(df.annual_income)
df = df.loc[(df.annual_income > lower_1) & (df.annual_income < upper_1)] 
#Remove outliers for Fam Members
lower_2, upper_2 = remove_outlier(df.fam_members)
df = df.loc[(df.annual_fam_members > lower_2) & (df.fam_members < upper_2)]

Мы почти закончили с EDA, приступив к определению «целевой» переменной, о которой вы, возможно, слышали где-то на большинстве уроков по классификации.

Возвращаясь к теме этого проекта, «Управление кредитными рисками», нам нужно определить, как мы должны обрабатывать статус погашения ссуды клиентов. С помощью этого набора данных я определил «target = 0» для тех, кто не имел ссуды или выплатил в этом месяце, в то время как оставшиеся данные, любая просроченная ссуда, были сопоставлены с «target = 1».

df['target'] = None
df.loc[df.status < 1,'target']=0
df.loc[df.status >= 1,'target']=1
df.target = pd.to_numeric(df.target)

Б. Разработка функций

Что такое Feature Engineering и что он делает для предварительной обработки данных перед моделированием?

Согласно Википедии,

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

Фактически, разработка функций требует не только знания предметной области, но также понимания набора данных и цели достижения. В частности, в нашем наборе данных есть целый ряд различных функций, которые мы называем независимыми переменными, которые коррелируют со статусом погашения, которым является целевая переменная (0 или 1 ). Таким образом, чтобы настроить проницательную и действенную модель, нам необходимо «спроектировать» эти функции, преобразовав существующие и / или добавив вспомогательные данные, что отличает Feature Engineering от EDA.

Как я уже говорил с самого начала, мы никогда не узнаем, какой подход лучше, пока не протестируем его. При этом я решил протестировать 2 сценария, С и БЕЗ касания целевой переменной, и посмотреть, есть ли какие-либо существенные различия в результатах, полученных позже.

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

df_a = df #for encoding without target
df_b = df #for encoding with target
x_a = df_a.iloc[:, 1:-1]
y_a = df_a.iloc[:, -1]
from sklearn.model_selection import train_test_split
x_a_train, x_a_test, y_a_train, y_a_test = train_test_split(x_a, y_a, test_size=0.3, random_state=1)

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

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

1. Кодирование категорий БЕЗ цели

В зависимости от типа переменной мы применим подходящую технику к каждой.

Если вы вернетесь к набору данных, есть 3 типа переменных: (1) двоичные, (2) номинальные и (3) непрерывные. Хотя двоичные и непрерывные переменные в значительной степени говорят сами за себя, номинальная переменная относится к группе различных категорий, которые не имеют внутреннего порядка друг для друга.

1.1. Двоичные переменные

Для двоичных переменных в нашем наборе данных (например, пола, автомобиля, собственности) мы можем выбрать Кодировщик меток или Бинаризатор меток из библиотеки sklearn, что будет сопоставить исходные данные с 0 или 1:

#Option 1: Label Encoder (applied to >2 categories per variable)
from sklearn.preprocessing import LabelEncoder, LabelBinarizer
le = LabelEncoder()
gender_le = le.fit_transform(x_a_train.gender)
#Option 2: LabelBinarizer (applied to 2 categories per variable)
bn = LabelBinarizer()
gender_bn = np.array(x_a_train.gender).reshape(-1,1)
gender_bn = bn.fit_transform(gender_bn)

1.2. Номинальные переменные

Номинальные переменные (например, тип дохода, образование, семейное положение, тип жилья, тип занятия) являются категориальными, и перед моделированием их необходимо преобразовать в числовые. Два общих метода кодирования категории: (1) Dummy Encoding и (2) One Hot Encoder, который в основном создает n столбцов как n уникальных категорий в этой переменной и присваивает 0 или 1 в зависимости от отсутствия / наличия каждой категории в каждом столбце.

Разница между этими методами заключается в том, что фиктивное кодирование преобразуется в n-1 под-переменных, а One Hot Encoder преобразуется в n под-переменных.

#Option 1: Dummy Encoding: kn - k variables
income_type_dummy = pd.get_dummies(x_a_train.income_type)
#Option 2: OneHotEcnoder: kn variables
from sklearn.preprocessing import OneHotEncoder
onehot = OneHotEncoder(sparse=False, drop='first', handle_unknown='error')
income_type_onehot = onehot.fit_transform(x_a_train.income_type.to_numpy().reshape(-1,1))
income_type_onehot = pd.DataFrame(income_type_onehot, columns=onehot.get_feature_names(['income_type']))

Фиктивное кодирование можно легко выполнить с помощью pd.get_dummies (), поскольку это уже часть библиотеки pandas. Для One Hot Encoder нам нужно импортировать его из библиотеки sklearn и преобразовать каждую переменную по отдельности или все одновременно.

One Hot Encoder был разработан для обеспечения единообразия количества категорий в обучающих и тестовых наборах (например, обработка категорий, которые не отображаются ни в одной из них), поэтому его более настоятельно рекомендуется, чем Dummy Encoding, из-за более простого управления с помощью «handle_unknown = «Ошибка» ».

Однако одним из недостатков One Hot Encoder является мультиколлинеарность, которая относится к переменным или подпараметрам, сильно линейно связанным друг с другом, и, следовательно, снижает точность наших моделей. Это можно исправить или избежать, назначив параметр «drop =‘ first ’», который помогает удалить одну из под-переменных после кодирования.

1.3. Непрерывные переменные

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

На левом рисунке показан диапазон возрастов клиентов, а на правом - распределение разных сегментов дохода. Два общих метода обработки этого типа переменных: (1) Биннинг фиксированной ширины и (2) Адаптивный биннинг. В частности, первый создает подкатегории из заранее определенных ячеек (например, возраст - 10–20, 20–30, 30–40 и т. Д.), А второй полагается на распределение данных.

Плюсы и минусы биннинга фиксированной ширины заключаются в том, что легко и просто закодировать переменную, но относительно субъективно без учета самих данных. Поэтому я предлагаю выбрать Adaptive Binning, который внимательно следит за распределением данных. Судя по тому, что я наблюдал, вместо преобразования в категории с двумя ячейками я решил использовать «квантили», поскольку исходное распределение широко варьировалось, после чего было применено кодирование меток .

#Convert each variable into 5 equal categories/each
x_a_train['age_binned'] = pd.qcut(x_a_train.age, q=[0, .25, .50, .75, 1])
x_a_train['annual_income_binned'] = pd.qcut(x_a_train.annual_income, q=[0, .25, .50, .75, 1])
#Apply Label Encoder to assign the label to each category without bias
x_a_train['age'] = le.fit_transform(x_a_train['age_binned'])
x_a_train['annual_income'] = le.fit_transform(x_a_train['annual_income_binned'])

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

2. Кодирование категорий с целью

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

Опять же, необходимо выполнить предварительное условие: train_test_split перед обработкой

x_b = df_b.iloc[:, 1:-1]
y_b = df_b.iloc[:, -1]
from sklearn.model_selection import train_test_split
x_b_train, x_b_test, y_b_train, y_b_test = train_test_split(x_b, y_b, test_size=0.3, random_state=1)

Среди методов кодирования категорий с участием цели я обнаружил 3 распространенных варианта, которые широко используются: (1) Кодировщик веса доказательств (WOE), (2) Цель Encoder и (3) Leave-One-Out Encoder (LOO).

Вкратце,

  • Кодировщик WOE: кодирование веса доказательств - это широко используемый метод моделирования кредитного риска, позволяющий получить максимальную разницу между уникальными категориями в каждой переменной, связанной с целью. Это можно легко понять с помощью математического расчета, как показано ниже - натуральный логарифм% хорошего (в данном случае цель = 0) /% плохого (цель = 1):

  • Целевой кодировщик и кодировщик LOO: в то время как первый метод заменяет категориальное значение средним значением целевой переменной по всем строкам в наборе данных, последний делает то же самое, но исключает саму строку. Причина в том, чтобы избежать прямой утечки цели из-за использования слишком большого количества информации перед моделированием.
#Option 1: WOE Encoder
import category_encoders as ce
woe = ce.WOEEncoder()
def woe_encoder(col, target):
    for i in range(len(x_b_train.columns)):
        col.iloc[:,i] = woe.fit_transform(col, target)
    return col
df_woe_train = woe_encoder(x_b_train, y_b_train)

#Option 2: Target Encoder
from category_encoders import TargetEncoder
te = TargetEncoder()
def target_encoder(col, target):
    for i in range(len(x_b_train.columns)):
        col.iloc[:,i] = te.fit_transform(col, target)
    return col
df_te_train = target_encoder(x_b_train, y_b_train)

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

Вуаля! Это завершение первой части этого проекта!

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

Обязательно обратите внимание на следующие 2 части, которые охватывают Масштабирование / выбор функций и Моделирование машинного обучения! А пока давайте подключимся:

Github: https://github.com/andrewnguyen07
LinkedIn: www.linkedin.com/in/andrewnguyen07

Спасибо!