«… если мне придется сидеть там, посмотреть на себя и сказать себе: «Ты неудачник», я думаю, что это хуже, это почти хуже смерти».
- Коби Бин Брайант
Я был в поезде, когда узнал о трагической катастрофе вертолета, унесшей жизни всех 9 пассажиров. Я помню, как пропустил свою остановку и уставился в свой телефон, когда все истории захлестнули меня. Сначала это казалось нереальным.
Быть фанатом «Торонто Рэпторс» означает, что у вас интересные отношения с Коби Брайантом. Погрязнув в посредственности в середине 2000-х, для верных Raptors не всегда все было так «мы, северяне». 22 января 2006 года мы навсегда привязались к Кобе. Его игра с 81 очком станет вторым по результативности в истории лиги. Это заставило меня задуматься о его ранних годах в лиге и о том, как, казалось бы, скромный новичок превратился в одного из самых результативных бомбардиров НБА всех времен.
Может показаться сюрпризом, что год новичка Бина едва превзошел ожидания лиги. По сравнению с Джорданом разница бросается в глаза…
Этот проект сосредоточен вокруг вопроса о том, можем ли мы использовать исторические данные НБА с 1980 года, чтобы предсказать результаты карьеры игрока. В частности, можем ли мы использовать статистику года новичка игрока, чтобы определить, какова будет его итоговая сумма очков? Очки — это всеобъемлющий показатель атакующей ценности игрока в лиге.
В конце концов, я решил использовать данные, чтобы увидеть, можем ли мы использовать статистику года новичка для предсказания бинарного результата: будет ли этот игрок выше среднего или ниже среднегозабивать к концу карьеры. Таким образом, команды могут быть в курсе потенциальных «спадов» и «провалов» новобранцев на драфте.
https://www.kaggle.com/drgilermo/nba-players-stats?select=Seasons_Stats.csv
Набор данных Гольдштейна был наиболее полным набором данных по НБА, взятым из Basketball Reference. Набор данных включает почти все расширенные аналитические показатели для каждого игрока в лиге с 1950 по 2017 год.
После некоторой предварительной обработки и очистки я выделил год новичка каждого игрока с 1980 года. Этот конкретный год был выбран потому, что это был год, когда 3-х очковая линия была полностью внедрена в отслеживание статистики. Для справки: у нас остается 2331 игрок и данные за 37 сезонов. Кроме того, я решил включить только 22 переменные, связанные с общей статистикой игрока. К ним относятся:
'FG', 'FGA', 'FG%', '3P', '3PA', '3P%', '2P', '2PA', '2P%', 'eFG%', 'FT' , 'FTA', 'FT%', 'ORB', 'DRB', 'TRB', 'AST', 'STL', 'BLK', 'TOV', 'PF', 'PTS'
Затем в конец фрейма данных была добавлена 23-я переменная, общее количество очков карьеры, как «total_pts». Это то, что мы попытаемся предсказать в нашей модели.
С помощью выбора признаков линейной регрессии были выбраны 9 переменных, наиболее релевантных для прогнозирования total_pts.
array = stats_df.values X = array[:,0:21] Y = array[:,21] #feature selection model = LinearRegression() rfe = RFE(model, 9) fit = rfe.fit(X, Y) print("Num Features: %d" % fit.n_features_) print("Selected Features: %s" % fit.support_) print("Feature Ranking: %s" % fit.ranking_)
9 выбранных функций показаны ниже вместе с total_pts:
Следующий шаг открыт для интерпретации, но для простоты я решил преобразовать столбец total_pts в двоичный столбец либо выше среднего total_pts, либо ниже среднего total_pts.
Среднее общее количество очков, набранных игроком за этот 37-летний период, составило 3523. Таким образом, все, что выше среднего, будет классифицироваться как (1), а ниже среднего будет (0).
final_df['TOTAL_PTS'].values[final_df['TOTAL_PTS'] < 3522] = 0 final_df['TOTAL_PTS'].values[final_df['TOTAL_PTS'] >= 3523] = 1 final_df.head()
Ниже я включил разделение обучения/тестирования, а также код передискретизации:
#split into TRAIN/TEST msk = np.random.rand(len(final_df)) < 0.70 train_df = final_df[msk] test_df = final_df[~msk] #split test into X_test and y_test for later y_test = test_df['TOTAL_PTS'] X_test = test_df.drop('TOTAL_PTS',axis=1) #class count count_class_0, count_class_1 = train_df.TOTAL_PTS.value_counts() #divide by class df_class_0 = train_df[train_df['TOTAL_PTS'] == 0] df_class_1 = train_df[train_df['TOTAL_PTS'] == 1] df_class_1_over = df_class_1.sample(count_class_0, replace=True) df_test_over = pd.concat([df_class_1_over, df_class_0], axis=0) print('Random over-sampling:') print(df_test_over.TOTAL_PTS.value_counts()) df_test_over.TOTAL_PTS.value_counts().plot(kind='bar', title='Count (TOTAL_PTS)')
Затем я подгоняю данные передискретизации к нескольким различным алгоритмам машинного обучения. Ниже я включил скрипт для наиболее эффективной модели, классификатора логистической регрессии:
df2_test_over = pd.DataFrame(df_test_over) #split into X_train and y_train X_over_train = df2_test_over.drop('TOTAL_PTS',axis=1) y_over_train = df2_test_over['TOTAL_PTS'] #fitting Logistic Regression to the training set reg_classifier = LogisticRegression(penalty = 'l2', solver = 'liblinear') reg_classifier.fit(X_over_train, y_over_train) #predicting the test set results predictions = reg_classifier.predict(X_test) #confusion matrix conf_mat = confusion_matrix(y_true=y_test, y_pred=predictions) print('Confusion matrix:\n', conf_mat) labels = ['Class 0', 'Class 1'] fig = plt.figure() ax = fig.add_subplot(111) cax = ax.matshow(conf_mat, cmap=plt.cm.Blues) fig.colorbar(cax) ax.set_xticklabels([''] + labels) ax.set_yticklabels([''] + labels) plt.xlabel('Predicted') plt.ylabel('Expected') plt.show()
#calculate accuracy/precision/recall/f1 # accuracy: (tp + tn) / (p + n) accuracy = (accuracy_score(y_test, predictions)).astype('float64') print('{:>10}: {:0.2%}'.format('Accuracy',accuracy)) # precision tp / (tp + fp) precision = (precision_score(y_test, predictions)).astype('float64') print('{:>10}: {:0.2%}'.format('Precision',precision)) # recall: tp / (tp + fn) recall = (recall_score(y_test, predictions)).astype('float64') print('{:>10}: {:0.2%}'.format('Recall',recall)) # f1: 2 tp / (2 tp + fp + fn) f1 = (f1_score(y_test, predictions)).astype('float64') print('{:>10}: {:0.2%}'.format('F1 score',f1)) #ROC curve false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, predictions) roc_auc = (auc(false_positive_rate, true_positive_rate)).astype('float64') print('{:>10}: {:0.2%}'.format('ROC score',roc_auc))
Хотя модель и не идеальна, она работает хорошо, и мы можем сказать с точностью около 76,69% и ROC 75,7%, что мы можем предсказать, будет ли новичок набирать больше среднего в НБА к моменту завершения своей карьеры. .
Забегая вперед, я хотел бы посмотреть, смогу ли я использовать машинное обучение для прогнозирования результативности игрока в следующем году за каждый отдельный год, в течение которого он находится в НБА, и потенциально заставить модель предсказывать многоклассовую переменную (т. е. то, что их точные оценка будет на следующий год), а не просто бинарная переменная 1|0.
Полный блокнот можно посмотреть здесь:
Не стесняйтесь подписаться на меня на Github по адресу @vladtheladd, напишите мне напрямую или найдите меня в Linkedin. Я хотел бы услышать от вас, если вы думаете, что я могу помочь вам или вашей команде с работой, связанной с наукой о данных!