#!/usr/bin/env python # coding: utf-8 #
# # ## Открытый курс по машинному обучению. Сессия № 3 # # ###
Автор материала: Демо Владимир Олегович # ##
Индивидуальный проект по анализу данных:
# ##
Прогноз успешного выступления на соревнованиях по Пауэрлифтингу
# In[ ]: ## подключаемые библиотеки import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt from sklearn import preprocessing # ### 1. Описание набора данных и признаков (2 балла) # # Описан процесс сбора данных (если применимо), есть подробное описание решаемой задачи, в чем ее ценность, дано описание целевого и прочих признаков; # __Процесс сбора данных:__ # # Данные для анализа взяты с Каггл: # 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 испытаний (присед и жим)? # # __Ценность задачи:__ # У некоторых начинающих спортсменов результаты по отдельным дисциплинам в среднем лучше, чем по другим. Следовательно, необходимо понять, наксколько велик шанс спортсмена достигнуть призового места. # # __Описание целевого и прочих признаков:__ # # Целевой признак: # - Place - место в соревнованиях # # Прочие признаки: # - MeetID - ID проведенного мероприятия; # - Name - имя спортсмена (Имя Фамилия на англ. языке); # - Sex - пол спортсмена (male - мужской /female - женский); # - Equipment - экипировка (Multi-ply - многослойная, Raw - без экипировки, Single-ply - однослойная, Straps - бинты на кисти рук, Straps - бинты на колени ног); # - Age - возраст спортсмена в полных годах; # - Division - дивизион в котором выступает спортсмен (возрастная, проверка на допинг и пр.); # - BodyweightKg - вес спортсмена (кг); # - WeightClassKg - весовая категория спортсмена (его вес меньше или равен данной категории, кг); # - BestSquatKg - лучший результат в дисциплине "Присед со штангой" (кг); # - BestBenchKg - лучший результат в дисциплине "Жим штанги лежа" (кг); # - BestDeadliftKg - лучший результат в дисциплине "Становая тяга штанги" (кг); # - TotalKg - сумма трех испытаний (кг); # - Wilks - коэффициент Вилкса, который позволяет на соревнованиях определить абсолютного чемпионата вне зависимости от весовой категории. # In[ ]: op = pd.read_csv('../../data/openpowerlifting.csv') op.shape # ### 2. Первичный анализ признаков # # Исследованы признаки, их взаимодействия, влияние на целевой признак. Исследовано распределение целевого признака (в случае задачи регрессии проведены стат-тесты на нормальность и скошенность (skewness) распределения). Если необходимо, объясняется, почему и как можно преобразовать целевой признак. Изучены выбросы и пропуски в данных; # In[ ]: op.head() # In[ ]: print(op.info()) # ### 7. Предобработка данных (4 балла) # # Проведена предобработка данных для конкретной модели. При необходимости есть и описано масштабирование признаков, заполнение пропусков, замены строк на числа, OheHotEncoding, обработка выбросов, отбор признаков с описанием используемых для этого методов. Корректно сделано разбиение данных на обучающую и отложенную части; # Проверим данные на наличие пропусков. Определим долю пропусков от общего числа данных # In[ ]: op.isnull().sum() / op.shape[0] # Столцы Squat4Kg, Bench4Kg, Deadlift4Kg в большинстве отсутствуют (>99%) и age в 62% случаев, следовательно данные признаки удаляем из dataset # In[ ]: op=op.drop(["Squat4Kg", "Bench4Kg", "Deadlift4Kg", "Age"], axis=1) # In[ ]: op.describe() # Также имеем в столбцах BestSquatKg, BestBenchKg, BestDeadliftKg отрицательные значения, что по физической природе является выбросом. Спишем это на ошибки ввода, следовательно данные параметры необходимо взять по модулю # In[ ]: op['BestSquatKg']=np.abs(op['BestSquatKg']) op['BestBenchKg']=np.abs(op['BestBenchKg']) op['BestDeadliftKg']=np.abs(op['BestDeadliftKg']) # Заменим все NaN в столбцах BestSquatKg, BestBenchKg, BestDeadliftKg, TotalKg, Wilks на "нули" (спортсмену не удалось выполнить начальный вес в состязаниях, поэтому у него суммарный вес и Вилкс тоже "ноль" # In[ ]: 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% # In[ ]: 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", следовательно удалим все строчки, где данный признак не указан, чтобы количество элементов в каждом признаке было одинаковым. # In[ ]: op=op.drop(op[op["BodyweightKg"].isnull()].index) op.describe() # Получили "очищенные данные", которые демонстрируют статистику по итоговым места в соревнованиях из 370219 строчек # In[ ]: op.groupby('Place') place_stat=op['Place'].value_counts() place_stat[0:10] # In[ ]: op['Place'].hist(bins=25) # "Место" в соревнованиях определим как целевой признак. Чтобы в дальнейшем можно было провести прогноз, необходимо выполнить еще несколько подготовительных пунктов: # - создание новых признаков # - перевести текстовые значения в числовые для возможностей анализа и прогноза # ### 9. Создание новых признаков и описание этого процесса (4 балла) # # Созданы новые признаки. Дано обоснование: логическое (например, у птиц температура тела на несколько градусов выше человеческой, значит вирус ХХХ не выживет в такой среде), физическое (например, радуга означает, что источник света расположен сзади; расчет величины по физическому закону с использованием данных признаков) или другое (скажем, признак построен после визуализации данных). Обоснование разумно описано. Полезность новых признаков подтверждена статистически или с помощью соответствующей модели; # Создаем новый признак: "Число участий в соревнований" - 'MeetCount'. Каждый спортсмен будет иметь данные по сумме соревнований, что в дальнейшем позволяет понять насколько он опытный. # In[ ]: 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' нам не понадобится, поэтому его удаляем. # In[ ]: op=op.drop(['Name'], axis=1) # Переводим категориальные признаки в числовые # In[ ]: 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] # In[ ]: op.head() # Признак "place" имеет признак: "место" в числовом значении и "дисквалификация" в буквенном. Нас не интересует не 1 место, следовательно "place" который не равен 1 (после факторризации 0) будет равен 0, а остальное 1 # In[ ]: op['Place'][op['Place']==0]=0 op['Place'][op['Place']!=0]=1 # In[ ]: op.head() # ### 3. Первичный визуальный анализ данных (4 балла) # # Построены визуализации (распределения признаков, матрица корреляций и т.д.), описана связь с анализом данным (п. 2). Присутствуют выводы; # In[ ]: sns.heatmap(op.corr()) # In[ ]: corr = op.corr() corr # ### 4. Инсайты, найденные зависимости (4 балла) # # Найдены и выдвинуты предположения о природе различных корреляций/пропусков/закономерностей и выбросов, найденных в предыдущих пунктах. Есть пояснение, почему они важны для решаемой задачи; # Насколько видим из матрицы корреляции, место ('Place' - целевой признак) коррелирует лучше всего с WeightClassKg (весовая категория, в которой выступает спортсмен). Чем выше категория, тем меньше вероятность победы. # # С экипировкой никакой корреляции нет. # # Для прогнозы победы на основе двух первых испытаний (присед и жим) необходимо удалить данные по становой тяге, суммарный взятый вес и Вилкс, так как они являются результатом соревнований и напрямую прогнозируют победу. # In[ ]: op=op.drop(['BestDeadliftKg', 'TotalKg','Wilks'], axis=1) op.head() # ### 5. Выбор метрики (3 балла) # # Есть разумное обоснование выбора метрики качества модели. Описаны моменты, влияющие на выбор метрики качества (решаемая задача, цель решения, количество классов, дисбаланс классов, прочее); # In[ ]: target = op['Place'] train = op.drop(['Place'], axis=1) # In[ ]: from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import GridSearchCV, StratifiedKFold from sklearn.cross_validation import cross_val_score # In[ ]: dt = DecisionTreeClassifier(random_state=17, class_weight='balanced') # In[ ]: max_depth_values = range(1, 10) max_features_values = range(1, 10) tree_params = {'max_depth': max_depth_values, 'max_features': max_features_values} # In[ ]: skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=17) from sklearn.metrics import accuracy_score X = train y = target # In[ ]: grid=GridSearchCV(dt,tree_params,cv=skf, n_jobs=-1,scoring='roc_auc',verbose=1) # In[ ]: grid.fit(X, y) # In[ ]: from sklearn.ensemble import RandomForestClassifier # In[ ]: dt2 = RandomForestClassifier(n_jobs=1, random_state=17) # In[ ]: max_depth_values = range(1, 10) max_features_values = range(1, 10) tree_params = {'max_depth': max_depth_values, 'max_features': max_features_values} # In[ ]: grid2=GridSearchCV(dt2,tree_params,cv=skf, n_jobs=-1,scoring='roc_auc',verbose=1) # In[ ]: grid2.fit(X, y) # ### 8. Кросс-валидация и настройка гиперпараметров модели (4 балла) # # Кросс-валидация выполнена технически верно, нет утечек данных. Разумно выбрано количество фолдов и разбиение (Random/Stratified или иное), зафиксирован seed. Присутствует объяснение. Объяснены гиперпараметры модели и способ их выбора. Выбор основан на некотором исследовании гипрепараметров модели для данной задачи; # In[ ]: 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) # In[ ]: clf_best_score2 = grid2.best_score_ clf_best_params2 = grid2.best_params_ clf_best2 = grid2.best_estimator_ print("Лучший результат в случайном лесе", clf_best_score2, "лучшие параметры", clf_best_params2) # ### 6. Выбор модели (3 балла) # # Произведен выбор модели. Описан процесс выбора и связь с решаемой задачей; # Как видно, "случайный лес" и "дерево решений" дают точный прогноз (100%) победы спортсмена # ### 11. Прогноз для тестовой или отложенной выборке (2 балла) # # Указаны результаты на тестовой выборке или LB score. Результаты на тестовой выборке сравнимы с результатами на кросс-валидации. Если тестовая выборка создавалась автором проекта, то механизм создания должен быть непредвзят и объяснен (применен разумный механизм выборки, в простейшем случае – рандомизация); # Выполним прогноз при помощи "случайного леса" с лучшими параметрами на тренировочной и отложенной выборке # In[ ]: 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) # In[ ]: dt3 = RandomForestClassifier(n_jobs=1, max_depth=2, max_features=6,random_state=17) dt3.fit(X_train, y_train) # In[ ]: dt3_pred = dt3.predict(X_valid) accuracy_score(y_valid, dt3_pred) # In[ ]: dt4 = DecisionTreeClassifier(class_weight='balanced',max_depth=2, max_features=5,random_state=17) dt4.fit(X_train, y_train) # In[ ]: dt4_pred = dt4.predict(X_valid) accuracy_score(y_valid, dt4_pred) # В итоге, "дерево решений" отлично справляется с прогнозом победы на соревнованиях по Пауэрлифтингу по результатам отложенной выборки (100%), в отличие от "случайного леса", где прогноз хуже (99.7%) # ### 12. Выводы (2 балла) # # Описана ценность решения, возможности применения, дальнейшие пути развития и улучшения решения; # Прогноз победы на соревнованиях по Пауэрлифтингу имеет низкую ценность, так как на такого рода соревнованиях отсутствует тотализатор. При этом на основе первичных данных о спортсмене, его опыт, экипировка, весовая категория и результаты по первым двум испытаниям есть возможность спрогнозировать победу со 100% вероятностью.