Недавно я участвовал в конкурсе Kaggle, где мы должны предсказывать результаты выборов с помощью машинного обучения. Набор данных был взят с всеобщих выборов в Индии в 2019 году (см. Здесь). В этой статье объясняется, как очистить и подготовить набор данных, создать новые функции из существующих, а затем предсказать результаты с помощью популярного алгоритма машинного обучения. Здесь нет четкого объяснения большинства основных этапов предварительной обработки, визуализации данных и машинного обучения; Скорее я сосредоточился на разработке функций и их влиянии на производительность моделей. Если у вас нет четкого представления о том, что такое разработка функций, обратитесь к моей предыдущей статье Разработка основных функций для более эффективного машинного обучения.

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

import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.feature_selection import SelectKBest, chi2

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

dataset = pd.read_csv('/kaggle/input/indian-candidates-for-general-election-2019/LS_2.0.csv')
dataset.head()

dataset.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2263 entries, 0 to 2262
Data columns (total 19 columns):
STATE                                       2263 non-null object
CONSTITUENCY                                2263 non-null object
NAME                                        2263 non-null object
WINNER                                      2263 non-null int64
PARTY                                       2263 non-null object
SYMBOL                                      2018 non-null object
GENDER                                      2018 non-null object
CRIMINAL
CASES                              2018 non-null object
AGE                                         2018 non-null float64
CATEGORY                                    2018 non-null object
EDUCATION                                   2018 non-null object
ASSETS                                      2018 non-null object
LIABILITIES                                 2018 non-null object
GENERAL
VOTES                               2263 non-null int64
POSTAL
VOTES                                2263 non-null int64
TOTAL
VOTES                                 2263 non-null int64
OVER TOTAL ELECTORS 
IN CONSTITUENCY        2263 non-null float64
OVER TOTAL VOTES POLLED 
IN CONSTITUENCY    2263 non-null float64
TOTAL ELECTORS                              2263 non-null int64
dtypes: float64(3), int64(5), object(11)
memory usage: 336.0+ KB

Как видно из приведенного выше анализа, набор данных содержит несколько числовых столбцов, и большинство столбцов не являются числовыми. Столбец WINNER содержит метку, указывающую, что кандидат выиграл или проиграл выборы. Также обратите внимание, что набор данных содержит некоторые значения «NaN», которые в основном отсутствуют. Некоторые имена столбцов содержат символы «\ n», что раздражает. Кроме того, значения столбцов ASSETS и LIABILITIES также содержат символы «\ n».

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

Затем мы должны очистить набор данных, исправить имена столбцов и обработать отсутствующие значения. Сначала я исправил неправильные имена столбцов и заменил все пробелы в именах столбцов символами подчеркивания (‘_’). Это не обязательный шаг, но меня раздражают неправильные названия столбцов.

# rename invalid column names
dataset = dataset.rename(columns={'CRIMINAL\nCASES': 'CRIMINAL_CASES', 'GENERAL\nVOTES': 'GENERAL_VOTES', 'POSTAL\nVOTES': 'POSTAL_VOTES', 'TOTAL\nVOTES': 'TOTAL_VOTES', 'OVER TOTAL ELECTORS \nIN CONSTITUENCY': 'OVER_TOTAL_ELECTORS_IN_CONSTITUENCY', 'OVER TOTAL VOTES POLLED \nIN CONSTITUENCY': 'OVER_TOTAL_VOTES_POLLED_IN_CONSTITUENCY', 'TOTAL ELECTORS': 'TOTAL_ELECTORS'})

Затем давайте поищем недостающие значения в каждом столбце.

dataset.isna().sum()
STATE                                        0
CONSTITUENCY                                 0
NAME                                         0
WINNER                                       0
PARTY                                        0
SYMBOL                                     245
GENDER                                     245
CRIMINAL_CASES                             245
AGE                                        245
CATEGORY                                   245
EDUCATION                                  245
ASSETS                                     245
LIABILITIES                                245
GENERAL_VOTES                                0
POSTAL_VOTES                                 0
TOTAL_VOTES                                  0
OVER_TOTAL_ELECTORS_IN_CONSTITUENCY          0
OVER_TOTAL_VOTES_POLLED_IN_CONSTITUENCY      0
TOTAL_ELECTORS                               0
dtype: int64

Вы можете видеть, что 10% значений строк отсутствуют. Существует несколько способов обработки пропущенных значений, таких как их удаление, использование обратного или прямого заполнения, вменение постоянного значения, среднее / медианное значение или вменение режима и т. Д. Однако я просто удаляю эти строки здесь для простоты (только 10% отсутствует), но всегда помните, что удаление значений сделает модель прогнозирования менее точной. Вам следует попытаться вменять недостающие значения в максимально возможной степени.

# drop rows with NA values
dataset = dataset[dataset['GENDER'].notna()]

Давайте преобразуем столбцы ASSETS, LIABILITIES и CRIMINAL_CASES в числовые, потому что они представляют деньги и количество, а числовые значения будут иметь смысл для моделей. Для этого нам нужно удалить знак «Rs», символ «\ n» и запятые в каждом поле значения. Но также эти столбцы содержат значения «Nil» и «Not Available». Поэтому, прежде чем сделать их числовыми, мы должны заменить эти значения некоторым значимым значением (на данный момент я заменил их на 0).

# replace Nil values with 0
dataset['ASSETS'] = dataset['ASSETS'].replace(['Nil', '`', 'Not Available'], '0')
dataset['LIABILITIES'] = dataset['LIABILITIES'].replace(['NIL', '`', 'Not Available'], '0')
dataset['CRIMINAL_CASES'] = dataset['CRIMINAL_CASES'].replace(['Not Available'], '0')

# clean ASSETS and LIABILITIES column values
dataset['ASSETS'] = dataset['ASSETS'].map(lambda x: x.lstrip('Rs ').split('\n')[0].replace(',', ''))
dataset['LIABILITIES'] = dataset['LIABILITIES'].map(lambda x: x.lstrip('Rs ').split('\n')[0].replace(',', ''))

# convert ASSETS, LIABILITIES and CRIMINAL_CASES column values into numeric
dataset['ASSETS'] = dataset['ASSETS'].astype(str).astype(float)
dataset['LIABILITIES'] = dataset['LIABILITIES'].astype(str).astype(float)
dataset['CRIMINAL_CASES'] = dataset['CRIMINAL_CASES'].astype(str).astype(int)

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

# label encode categorical columns

lblEncoder_state = LabelEncoder()
lblEncoder_state.fit(dataset['STATE'])
dataset['STATE'] = lblEncoder_state.transform(dataset['STATE'])

lblEncoder_cons = LabelEncoder()
lblEncoder_cons.fit(dataset['CONSTITUENCY'])
dataset['CONSTITUENCY'] = lblEncoder_cons.transform(dataset['CONSTITUENCY'])

lblEncoder_name = LabelEncoder()
lblEncoder_name.fit(dataset['NAME'])
dataset['NAME'] = lblEncoder_name.transform(dataset['NAME'])

lblEncoder_party = LabelEncoder()
lblEncoder_party.fit(dataset['PARTY'])
dataset['PARTY'] = lblEncoder_party.transform(dataset['PARTY'])

lblEncoder_symbol = LabelEncoder()
lblEncoder_symbol.fit(dataset['SYMBOL'])
dataset['SYMBOL'] = lblEncoder_symbol.transform(dataset['SYMBOL'])

lblEncoder_gender = LabelEncoder()
lblEncoder_gender.fit(dataset['GENDER'])
dataset['GENDER'] = lblEncoder_gender.transform(dataset['GENDER'])

lblEncoder_category = LabelEncoder()
lblEncoder_category.fit(dataset['CATEGORY'])
dataset['CATEGORY'] = lblEncoder_category.transform(dataset['CATEGORY'])

lblEncoder_edu = LabelEncoder()
lblEncoder_edu.fit(dataset['EDUCATION'])
dataset['EDUCATION'] = lblEncoder_edu.transform(dataset['EDUCATION'])

Теперь давайте обучим модель K-ближайших соседей и посмотрим на точность. KNN - это модель машинного обучения с учителем, которая подразделяется на алгоритмы классификации. Алгоритм работает, беря точку данных и находя k ближайших точек данных.

# separate train features and label
y = dataset["WINNER"]
X = dataset.drop(labels=["WINNER"], axis=1)
# split dataset into train and test data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1, stratify=y)
# train and test knn model
knn = KNeighborsClassifier()
knn.fit(X_train, y_train)
knn.predict(X_test)
print("Testing Accuracy is: ", knn.score(X_test, y_test)*100, "%")
Testing Accuracy is:  70.79207920792079 %

Модель достигла 70% точности без особого эффекта. Давайте нормализуем набор данных и посмотрим, насколько повысится точность. Я использовал MinMaxScaler из библиотеки scikit-learn, чтобы уменьшить все значения до диапазона 0–1.

# scaling values into 0-1 range
scaler = MinMaxScaler(feature_range=(0, 1))
features = [
    'STATE', 'CONSTITUENCY', 'NAME', 'PARTY', 'SYMBOL', 'GENDER', 'CRIMINAL_CASES', 'AGE', 'CATEGORY', 'EDUCATION', 'ASSETS', 'LIABILITIES', 'GENERAL_VOTES', 'POSTAL_VOTES', 'TOTAL_VOTES', 'OVER_TOTAL_ELECTORS_IN_CONSTITUENCY', 'OVER_TOTAL_VOTES_POLLED_IN_CONSTITUENCY', 'TOTAL_ELECTORS']
dataset[features] = scaler.fit_transform(dataset[features])

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

Testing Accuracy is:  90.5940594059406 %

Более эффективное кодирование существующих функций

Если мы рассмотрим столбец ОБРАЗОВАНИЕ, он содержит 11 категориальных значений, которые может иметь конкретный кандидат.

Неграмотный, Грамотный, 5-й, 8-й, 10-й, 12-й, выпускник, аспирант, аспирант-специалист, докторская степень, другое

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

dataset['EDUCATION'].value_counts()
Post Graduate            502
Graduate                 441
Graduate Professional    336
12th Pass                256
10th Pass                196
8th Pass                  78
Doctorate                 73
Others                    50
Literate                  30
5th Pass                  28
Not Available             22
Illiterate                 5
Post Graduate\n            1
Name: EDUCATION, dtype: int64

# encode education column
encoded_edu = []
# iterate through each row in the dataset
for row in dataset.itertuples():
    education = row.EDUCATION
if education == "Illiterate":
        encoded_edu.append(0)
    elif education == "Literate":
        encoded_edu.append(1)
    elif education == "5th Pass":
        encoded_edu.append(2)
    elif education == "8th Pass":
        encoded_edu.append(3)
    elif education == "10th Pass":
        encoded_edu.append(4)
    elif education == "12th Pass":
        encoded_edu.append(7)
    elif education == "Graduate":
        encoded_edu.append(8)
    elif education == "Post Graduate":
        encoded_edu.append(9)
    elif education == "Graduate Professional":
        encoded_edu.append(10)
    elif education == "Doctorate":
        encoded_edu.append(11)
    else:
        encoded_edu.append(5)
dataset['EDUCATION'] = encoded_edu

Давайте проанализируем столбец PARTY. Если мы перечислим количество кандидатов перед каждой партией, мы увидим, что только несколько партий имеют значительное количество кандидатов. Вся цель включения столбца ПАРТИЯ - оказать влияние партии кандидата на победу на выборах. Если у партии нет значительного числа кандидатов, влияние этой партии на победу кандидата невелико. Таким образом, мы можем закодировать их все в одну общую категорию (я закодировал их как «другие»).

dataset['PARTY'].value_counts()
BJP       420
INC       413
IND       201
BSP       163
CPI(M)    100
         ... 
AINRC       1
SKM         1
ANC         1
YKP         1
AJSUP       1
Name: PARTY, Length: 132, dtype: int64

# change party of the less frequent parties as Other
# 'BJP','INC','IND','BSP', 'CPI(M)', 'AITC', 'MNM': high frequent
# 'TDP', 'VSRCP', 'SP', 'DMK', 'BJD': medium frequent
dataset.loc[~dataset["PARTY"].isin(['BJP','INC','IND','BSP', 'CPI(M)', 'AITC', 'MNM', 'TDP', 'VSRCP', 'SP', 'DMK', 'BJD']), "PARTY"] = "Other"
dataset['PARTY'].value_counts()

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

Testing Accuracy is:  92.07920792079209 %

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

Создание новых функций

Чтобы создавать новые функции, мы должны иметь общее представление о доступных сырых функциях и о том, как они влияют на данный сценарий. Кроме того, требуется хорошее понимание предметной области. Итак, здесь мы должны подумать о том, какие факторы влияют на победу кандидата на выборах и какие соображения мы должны учитывать при голосовании за кандидата или партию. Для данного сценария я не знаю, какие страновые факторы учитываются перед голосованием за кандидата (какие специфические для страны факторы влияют на победу кандидата). Например, я понятия не имею, как КАТЕГОРИЯ влияет на результаты выборов. Но я придумал следующие особенности, исходя из общего варианта использования.

Набор данных содержит множество необработанных характеристик, которые напрямую повлияют на шансы кандидата на победу (например, штат, округ, партия, судимость, уровень образования, активы и т. Д.). Однако конкретный кандидат также может выиграть выборы в зависимости от статуса выдвинутой партии. Ниже приведены некоторые характеристики, которые могут отражать важность партии для вероятности победы кандидата.

1. Общее количество мест, выигранных партией

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

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

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

3. Уголовные дела против партии

Мы часто будем учитывать количество судимостей против партии перед голосованием. Если у партии больше уголовных дел (больше коррумпированных политиков), у этой партии может быть меньше шансов выиграть выборы (или наоборот ;-)). Мы можем выделить этот сценарий, сделав характеристику, представляющую количество судимостей против стороны.

4. Количество возбужденных по партии уголовных дел в каждом округе

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

5. Уровень образования кандидатов в партии

Мы также рассмотрим уровень образования кандидатов от партии, прежде чем голосовать за них. Если партия выдвинет большее количество образованных кандидатов, эта партия может выиграть выборы (более высокая вероятность). На более раннем этапе мы содержательно закодировали образовательные квалификации. Итак, если мы возьмем подсчет каждого кандидата в партии, эта характеристика будет отражать образовательный уровень партии.

6. Уровень образования кандидатов в партии по каждому округу

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

7. Количество кандидатов по округу от партии

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

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

1. Общее количество избирателей от каждого штата.

2. Общее количество избирателей в каждом округе.

3. Общее количество округов в каждом штате.

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

# Preparing feature values
cons_per_state = {}
voters_per_state = {}
party_winningSeats = {}
party_criminal = {}
party_education = {}
party_totalCandidates_per_cons = {}
party_winningSeats_per_cons = {}
party_criminal_per_cons = {}
party_education_per_cons = {}
voters_per_cons = {}
# group by state
subset = dataset[['STATE', 'CONSTITUENCY', 'TOTAL_ELECTORS']]
gk = subset.groupby('STATE')
# for each state
for name,group in gk:
    # total constituencies per state
    cons_per_state[name] = len(group)
    
    # total voters per state
    voters_per_state[name] = group['TOTAL_ELECTORS'].sum()
# group by party
subset = dataset[['PARTY', 'CONSTITUENCY', 'CRIMINAL_CASES', 'EDUCATION', 'WINNER']]
gk = subset.groupby('PARTY')
# for each party
for name,group in gk:
    # winning seats by party
    party_winningSeats[name] = group[group['WINNER'] == 1.0].shape[0]
    
    # criminal cases by party
    party_criminal[name] = group['CRIMINAL_CASES'].sum()
    
    # education qualification by party (sum of candidates)
    party_education[name] = group['EDUCATION'].sum()
    
    # group by constituency
    gk2 = group.groupby('CONSTITUENCY')
    
    # for each constituency
    for name2, group2 in gk2:
        key = name2 + '_' + name    # cons_party
        
        # total candidates by party in constituency
        party_totalCandidates_per_cons[key] = len(group2)
        
        # party winning seats in the constituency
        party_winningSeats_per_cons[key] = group2[group2['WINNER'] == 1.0].shape[0]
        
        # criminal cases by party in the constituency
        party_criminal_per_cons[key] = group2['CRIMINAL_CASES'].sum()
# education qualification by party in constituency (sum of candidates)
        party_education_per_cons[key] = group2['EDUCATION'].sum()
# Total voters per constituency
subset = dataset[['CONSTITUENCY', 'TOTAL_ELECTORS']]
gk = subset.groupby('CONSTITUENCY')
# for each constituency
for name,group in gk:
    voters_per_cons[name] = len(group)

# Applying feature values
# new feature columns
total_cons_per_state = []
total_voters_per_state = []
total_voters_per_cons = []
winning_seats_by_party = []
criminal_by_party = []
education_by_party = []
total_candidates_by_party_per_cons = []
winning_seats_by_party_per_cons = []
criminal_by_party_per_cons = []
education_by_party_per_cons = []
# iterate through each row in the dataset
for row in dataset.itertuples():
    subkey = row.CONSTITUENCY + '_' + row.PARTY
total_cons_per_state.append(cons_per_state.get(row.STATE))
    total_voters_per_state.append(voters_per_state.get(row.STATE))
    total_voters_per_cons.append(voters_per_cons.get(row.CONSTITUENCY))
    winning_seats_by_party.append(party_winningSeats.get(row.PARTY))
    criminal_by_party.append(party_criminal.get(row.PARTY))
    education_by_party.append(party_education.get(row.PARTY))
    total_candidates_by_party_per_cons.append(party_totalCandidates_per_cons.get(subkey))
    winning_seats_by_party_per_cons.append(party_winningSeats_per_cons.get(subkey))
    criminal_by_party_per_cons.append(party_criminal_per_cons.get(subkey))
    education_by_party_per_cons.append(party_education_per_cons.get(subkey))
# append columns to dataset
dataset['total_cons_per_state'] = total_cons_per_state
dataset['total_voters_per_state'] = total_voters_per_state
dataset['total_voters_per_cons'] = total_voters_per_cons
dataset['winning_seats_by_party'] = winning_seats_by_party
dataset['criminal_by_party'] = criminal_by_party
dataset['education_by_party'] = education_by_party
dataset['total_candidates_by_party_per_cons'] = total_candidates_by_party_per_cons
dataset['winning_seats_by_party_per_cons'] = winning_seats_by_party_per_cons
dataset['criminal_by_party_per_cons'] = criminal_by_party_per_cons
dataset['education_by_party_per_cons'] = education_by_party_per_cons

Давайте еще раз обучим нашу модель и посмотрим, насколько повысится точность. Не нужно маркировать кодировку каких-либо созданных функций, поскольку все они числовые. Однако я нормализую их до диапазона 0–1 перед обучением модели.

Testing Accuracy is:  96.78217821782178 %

Важность функции

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

Во-первых, мы можем удалить некоторые функции, просто проанализировав их вклад. Столбец NAME не будет делать никаких полезных выводов для модели, потому что в идеале имя должно быть уникальным. Однако в некоторых случаях фамилия может иметь значение, потому что некоторые фамилии могут повлиять на победу на выборах. Также переменные PARTY и SYMBOL будут представлять одну и ту же функцию, и мы сможем удалить одну из них без какого-либо влияния на точность. Столбец TOTAL_VOTES содержит сумму столбца GENERAL_VOTES и столбца POSTAL_VOTES. Так что мы можем удалить и эти два. Если мы построим тепловую карту, представляющую корреляционную матрицу, мы увидим, что эти 3 функции будут сильно коррелированы.

# remove unnecessary columns
X.drop(labels=["NAME"], axis=1, inplace=True)
X.drop(labels=["SYMBOL"], axis=1, inplace=True)
X.drop(labels=["POSTAL_VOTES"], axis=1, inplace=True)
X.drop(labels=["GENERAL_VOTES"], axis=1, inplace=True)

Существует несколько способов выбора функций в зависимости от их важности. Я буду использовать метод одномерного выбора, чтобы определить наиболее важные функции и удалить другие. Я буду использовать SelectKBest из библиотеки Scikit learn с тестом хи-квадрат, чтобы оценить важность функции.

# apply SelectKBest class to extract top most features
bestfeatures = SelectKBest(score_func=chi2, k=10)
fit = bestfeatures.fit(X, y)
dfscores = pd.DataFrame(fit.scores_)
dfcolumns = pd.DataFrame(X.columns)
# concat two dataframes for better visualization 
featureScores = pd.concat([dfcolumns, dfscores], axis=1)
featureScores.columns = ['Specs', 'Score']
print(featureScores.nlargest(30, 'Score'))

                                      Specs       Score
25          winning_seats_by_party_per_cons  486.207788
16  OVER_TOTAL_VOTES_POLLED_IN_CONSTITUENCY  285.347141
15      OVER_TOTAL_ELECTORS_IN_CONSTITUENCY  262.177373
14                              TOTAL_VOTES  216.937788
12                            GENERAL_VOTES  216.138799
21                   winning_seats_by_party  199.662525
13                             POSTAL_VOTES   65.126864
22                        criminal_by_party   36.519437
3                                     PARTY   35.416433
23                       education_by_party    6.576548
11                              LIABILITIES    6.330339
24       total_candidates_by_party_per_cons    5.755538
20                    total_voters_per_cons    5.302656
4                                    SYMBOL    4.128283
8                                  CATEGORY    4.047031
10                                   ASSETS    3.755575
7                                       AGE    2.077768
9                                 EDUCATION    0.888330
27              education_by_party_per_cons    0.840185
18                     total_cons_per_state    0.481673
6                            CRIMINAL_CASES    0.436667
19                   total_voters_per_state    0.292948
26               criminal_by_party_per_cons    0.178720
5                                    GENDER    0.145870
1                              CONSTITUENCY    0.143250
0                                     STATE    0.076833
17                           TOTAL_ELECTORS    0.054486
2                                      NAME    0.003039

Давайте отбросим все столбцы с оценкой менее 3. Обратите внимание, что некоторые из функций, которые я создал ранее, также не важны для модели KNN.

X.drop(labels=["TOTAL_ELECTORS"], axis=1, inplace=True)
X.drop(labels=["STATE"], axis=1, inplace=True)
X.drop(labels=["CONSTITUENCY"], axis=1, inplace=True)
X.drop(labels=["GENDER"], axis=1, inplace=True)
X.drop(labels=["criminal_by_party_per_cons"], axis=1, inplace=True)
X.drop(labels=["total_voters_per_state"], axis=1, inplace=True)
X.drop(labels=["CRIMINAL_CASES"], axis=1, inplace=True)
X.drop(labels=["total_cons_per_state"], axis=1, inplace=True)
X.drop(labels=["EDUCATION"], axis=1, inplace=True)
X.drop(labels=["education_by_party_per_cons"], axis=1, inplace=True)
X.drop(labels=["AGE"], axis=1, inplace=True)

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

Testing Accuracy is:  99.5049504950495 %

Заключение

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

Feature Engineering Step                   Testing Accuracy
Initially without any data processing                0.7079
Scale down values into 0-1 range                     0.9059
Basic encoding of two columns                        0.9208
Creating new features                                0.9678
Removing unnecessary features                        0.9950