«… если мне придется сидеть там, посмотреть на себя и сказать себе: «Ты неудачник», я думаю, что это хуже, это почти хуже смерти».

- Коби Бин Брайант

Я был в поезде, когда узнал о трагической катастрофе вертолета, унесшей жизни всех 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. Я хотел бы услышать от вас, если вы думаете, что я могу помочь вам или вашей команде с работой, связанной с наукой о данных!