## подключаемые библиотеки
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn import preprocessing
Описан процесс сбора данных (если применимо), есть подробное описание решаемой задачи, в чем ее ценность, дано описание целевого и прочих признаков;
Процесс сбора данных:
Данные для анализа взяты с Каггл: https://www.kaggle.com/open-powerlifting/powerlifting-database
Скачать их можно по ссылке: https://www.kaggle.com/open-powerlifting/powerlifting-database/downloads/powerlifting-database.zip/1
Подробное описание решаемой задачи:
Пауэрли́фтинг (англ. powerlifting; power — «сила, мощь» + lifting — «поднятие») или силовое троеборье — силовой вид спорта, суть которого заключается в преодолении сопротивления максимально тяжёлого для спортсмена веса.
Пауэрлифтинг также называют силовым троеборьем. Связано это с тем, что в качестве соревновательных дисциплин в него входят три упражнения (приседания со штангой на спине (точнее на верхней части лопаток), жим штанги лежа на горизонтальной скамье и тяга штанги), которые в сумме определяют квалификацию спортсмена.
На основе данных сайта "Openpowerlifting.org" постараемся дать ответ на следующие вопросы: 1. Влияет ли категория, в которой выступает пауэрлифтер на результаты соревнований? 2. Как экипировка влияет на достижение результата? 3. Можно ли спрогнозировать победу чемпионата на основе первых 2 испытаний (присед и жим)?
Ценность задачи: У некоторых начинающих спортсменов результаты по отдельным дисциплинам в среднем лучше, чем по другим. Следовательно, необходимо понять, наксколько велик шанс спортсмена достигнуть призового места.
Описание целевого и прочих признаков:
Целевой признак:
Прочие признаки:
op = pd.read_csv('../../data/openpowerlifting.csv')
op.shape
Исследованы признаки, их взаимодействия, влияние на целевой признак. Исследовано распределение целевого признака (в случае задачи регрессии проведены стат-тесты на нормальность и скошенность (skewness) распределения). Если необходимо, объясняется, почему и как можно преобразовать целевой признак. Изучены выбросы и пропуски в данных;
op.head()
print(op.info())
Проведена предобработка данных для конкретной модели. При необходимости есть и описано масштабирование признаков, заполнение пропусков, замены строк на числа, OheHotEncoding, обработка выбросов, отбор признаков с описанием используемых для этого методов. Корректно сделано разбиение данных на обучающую и отложенную части;
Проверим данные на наличие пропусков. Определим долю пропусков от общего числа данных
op.isnull().sum() / op.shape[0]
Столцы Squat4Kg, Bench4Kg, Deadlift4Kg в большинстве отсутствуют (>99%) и age в 62% случаев, следовательно данные признаки удаляем из dataset
op=op.drop(["Squat4Kg", "Bench4Kg", "Deadlift4Kg", "Age"], axis=1)
op.describe()
Также имеем в столбцах BestSquatKg, BestBenchKg, BestDeadliftKg отрицательные значения, что по физической природе является выбросом. Спишем это на ошибки ввода, следовательно данные параметры необходимо взять по модулю
op['BestSquatKg']=np.abs(op['BestSquatKg'])
op['BestBenchKg']=np.abs(op['BestBenchKg'])
op['BestDeadliftKg']=np.abs(op['BestDeadliftKg'])
Заменим все NaN в столбцах BestSquatKg, BestBenchKg, BestDeadliftKg, TotalKg, Wilks на "нули" (спортсмену не удалось выполнить начальный вес в состязаниях, поэтому у него суммарный вес и Вилкс тоже "ноль"
op['BestSquatKg']=op['BestSquatKg'].fillna(0)
op['BestBenchKg']=op['BestBenchKg'].fillna(0)
op['BestDeadliftKg']=op['BestDeadliftKg'].fillna(0)
op['TotalKg']=op['TotalKg'].fillna(0)
op['Wilks']=op['Wilks'].fillna(0)
Проверим также на корректность итоговую сумму трех дисциплин. Создадим отдельную колонку, где покажем процент ошибки ввода данных. Затем удалим строчки, где ошибка больше 1%
op['TotalKg_check']=(op['TotalKg']-(op['BestSquatKg']+op['BestBenchKg']+op['BestDeadliftKg']))/op['TotalKg']
op=op.drop(op[np.abs(op['TotalKg_check'])>0.01].index)
op=op.drop(['TotalKg_check'], axis=1)
op.describe()
Заметим, что еще остались значения Nan в признаке "BodyweightKg", следовательно удалим все строчки, где данный признак не указан, чтобы количество элементов в каждом признаке было одинаковым.
op=op.drop(op[op["BodyweightKg"].isnull()].index)
op.describe()
Получили "очищенные данные", которые демонстрируют статистику по итоговым места в соревнованиях из 370219 строчек
op.groupby('Place')
place_stat=op['Place'].value_counts()
place_stat[0:10]
op['Place'].hist(bins=25)
"Место" в соревнованиях определим как целевой признак. Чтобы в дальнейшем можно было провести прогноз, необходимо выполнить еще несколько подготовительных пунктов:
Созданы новые признаки. Дано обоснование: логическое (например, у птиц температура тела на несколько градусов выше человеческой, значит вирус ХХХ не выживет в такой среде), физическое (например, радуга означает, что источник света расположен сзади; расчет величины по физическому закону с использованием данных признаков) или другое (скажем, признак построен после визуализации данных). Обоснование разумно описано. Полезность новых признаков подтверждена статистически или с помощью соответствующей модели;
Создаем новый признак: "Число участий в соревнований" - 'MeetCount'. Каждый спортсмен будет иметь данные по сумме соревнований, что в дальнейшем позволяет понять насколько он опытный.
op2=op.drop(['Sex','Equipment','Division','BodyweightKg','WeightClassKg','BestSquatKg','BestBenchKg','BestDeadliftKg','TotalKg','Wilks','Place'], axis=1)
op3=op2.groupby('Name').agg(np.count_nonzero)
op4=op3.rename(columns={'MeetID': 'MeetCount'})
op4['Name']=op4.index
op = op.merge(op4, 'left', on='Name')
op.head()
В дальнейшем признак 'Name' нам не понадобится, поэтому его удаляем.
op=op.drop(['Name'], axis=1)
Переводим категориальные признаки в числовые
op['Division'] = pd.factorize(op.Division)[0]
op['Equipment'] = pd.factorize(op.Equipment)[0]
op['Sex'] = pd.factorize(op.Sex)[0]
op['WeightClassKg'] = pd.factorize(op.Place)[0]
op['Place'] = pd.factorize(op.Place)[0]
op.head()
Признак "place" имеет признак: "место" в числовом значении и "дисквалификация" в буквенном. Нас не интересует не 1 место, следовательно "place" который не равен 1 (после факторризации 0) будет равен 0, а остальное 1
op['Place'][op['Place']==0]=0
op['Place'][op['Place']!=0]=1
op.head()
Построены визуализации (распределения признаков, матрица корреляций и т.д.), описана связь с анализом данным (п. 2). Присутствуют выводы;
sns.heatmap(op.corr())
corr = op.corr()
corr
Найдены и выдвинуты предположения о природе различных корреляций/пропусков/закономерностей и выбросов, найденных в предыдущих пунктах. Есть пояснение, почему они важны для решаемой задачи;
Насколько видим из матрицы корреляции, место ('Place' - целевой признак) коррелирует лучше всего с WeightClassKg (весовая категория, в которой выступает спортсмен). Чем выше категория, тем меньше вероятность победы.
С экипировкой никакой корреляции нет.
Для прогнозы победы на основе двух первых испытаний (присед и жим) необходимо удалить данные по становой тяге, суммарный взятый вес и Вилкс, так как они являются результатом соревнований и напрямую прогнозируют победу.
op=op.drop(['BestDeadliftKg', 'TotalKg','Wilks'], axis=1)
op.head()
Есть разумное обоснование выбора метрики качества модели. Описаны моменты, влияющие на выбор метрики качества (решаемая задача, цель решения, количество классов, дисбаланс классов, прочее);
target = op['Place']
train = op.drop(['Place'], axis=1)
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.cross_validation import cross_val_score
dt = DecisionTreeClassifier(random_state=17, class_weight='balanced')
max_depth_values = range(1, 10)
max_features_values = range(1, 10)
tree_params = {'max_depth': max_depth_values,
'max_features': max_features_values}
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=17)
from sklearn.metrics import accuracy_score
X = train
y = target
grid=GridSearchCV(dt,tree_params,cv=skf, n_jobs=-1,scoring='roc_auc',verbose=1)
grid.fit(X, y)
from sklearn.ensemble import RandomForestClassifier
dt2 = RandomForestClassifier(n_jobs=1, random_state=17)
max_depth_values = range(1, 10)
max_features_values = range(1, 10)
tree_params = {'max_depth': max_depth_values,
'max_features': max_features_values}
grid2=GridSearchCV(dt2,tree_params,cv=skf, n_jobs=-1,scoring='roc_auc',verbose=1)
grid2.fit(X, y)
Кросс-валидация выполнена технически верно, нет утечек данных. Разумно выбрано количество фолдов и разбиение (Random/Stratified или иное), зафиксирован seed. Присутствует объяснение. Объяснены гиперпараметры модели и способ их выбора. Выбор основан на некотором исследовании гипрепараметров модели для данной задачи;
clf_best_score = grid.best_score_
clf_best_params = grid.best_params_
clf_best = grid.best_estimator_
mean_validation_scores = []
print("Лучший результат в дереве решений", clf_best_score,
"лучшие параметры", clf_best_params)
clf_best_score2 = grid2.best_score_
clf_best_params2 = grid2.best_params_
clf_best2 = grid2.best_estimator_
print("Лучший результат в случайном лесе", clf_best_score2,
"лучшие параметры", clf_best_params2)
Произведен выбор модели. Описан процесс выбора и связь с решаемой задачей;
Как видно, "случайный лес" и "дерево решений" дают точный прогноз (100%) победы спортсмена
Указаны результаты на тестовой выборке или LB score. Результаты на тестовой выборке сравнимы с результатами на кросс-валидации. Если тестовая выборка создавалась автором проекта, то механизм создания должен быть непредвзят и объяснен (применен разумный механизм выборки, в простейшем случае – рандомизация);
Выполним прогноз при помощи "случайного леса" с лучшими параметрами на тренировочной и отложенной выборке
from sklearn.model_selection import train_test_split
X_train, X_valid, y_train, y_valid = train_test_split(train,target,test_size=0.3, random_state=17)
dt3 = RandomForestClassifier(n_jobs=1, max_depth=2, max_features=6,random_state=17)
dt3.fit(X_train, y_train)
dt3_pred = dt3.predict(X_valid)
accuracy_score(y_valid, dt3_pred)
dt4 = DecisionTreeClassifier(class_weight='balanced',max_depth=2, max_features=5,random_state=17)
dt4.fit(X_train, y_train)
dt4_pred = dt4.predict(X_valid)
accuracy_score(y_valid, dt4_pred)
В итоге, "дерево решений" отлично справляется с прогнозом победы на соревнованиях по Пауэрлифтингу по результатам отложенной выборки (100%), в отличие от "случайного леса", где прогноз хуже (99.7%)
Описана ценность решения, возможности применения, дальнейшие пути развития и улучшения решения;
Прогноз победы на соревнованиях по Пауэрлифтингу имеет низкую ценность, так как на такого рода соревнованиях отсутствует тотализатор. При этом на основе первичных данных о спортсмене, его опыт, экипировка, весовая категория и результаты по первым двум испытаниям есть возможность спрогнозировать победу со 100% вероятностью.