如果您完成了第 1 部分,恭喜!您有格式化数据所需的耐心。在那篇文章中,我使用了一些 Python 库和一些基本的足球知识清理了我的美国国家橄榄球联盟数据集。接续上次的内容,现在是时候仔细看看我的数据集了。
数据分析
我将创建一个最终的数据帧,其中仅包含我要使用的数据字段。这些主要是我在转换列时创建的数据字段,以及档数和距离(也称为 yardsToGo)。
df_final = df[['down','yardsToGo', 'yardsToEndzone', 'rb_count', 'te_count', 'wr_count', 'ol_count',
'db_count', 'secondsLeftInHalf', 'half', 'numericPlayType', 'numericFormation', 'play_type']]
现在我想使用 dataframe.describe() 来抽查我的数据。它有点像总结了数据帧中的数据,并使其更容易发现任何异常值。
print(df_final.describe(include='all'))

大多数内容看起来都不错,除了 yardsToEndzone 的计数低于其余列。dataframe.describe() 文档将 count 返回值定义为“非 NA/null 观测值的数量”。我需要检查我是否具有空码线值。
print(df.yardlineNumber.unique())

为什么会出现 nan 值?为什么我似乎缺少 50 码线?如果我不知道内情,我会说我从 NFL 转储中获得的未稀释数据实际上并没有使用 50 码线作为值,而是将其标记为 nan。
以下是一些码线值为 NA 的比赛的比赛描述

看来我的假设是正确的。每个比赛描述的结束码线和获得的码数都达到 50 码。完美(为什么?!)。我将在上次的 yards_to_endzone 函数之前添加一行代码,将这些 nan 值映射到 50。
df['yardlineNumber'] = df['yardlineNumber'].fillna(50)

再次运行 df_final.describe(),我现在在所有方面都有统一的计数。谁知道这么多实践只是在数据中磨练?我更喜欢它带有一丝神秘色彩的时候。
是时候开始我的可视化了。Seaborn 是一个用于绘制数据的有用的库,我在第 1 部分中已经导入了它。
比赛类型
在完整的数据集中,有多少比赛是传球比赛,多少是跑动比赛?
sns.catplot(x='play_type', kind='count', data=df_final, orient='h')
plt.show()

看起来传球比赛比跑动比赛多大约 1,000 次。这很重要,因为它意味着两种比赛类型之间的分布不是 50/50 的比例。默认情况下,对于每个分割,传球比赛应该略多于跑动比赛。
档数
档数是球队可以尝试比赛的期间。在 NFL 中,进攻方有四次比赛尝试(称为“档数”)来获得指定的码数(通常从 10 码开始);如果未能做到,则必须将球权交给对方。是否有特定的档数倾向于有更多的传球或跑动(也称为冲球)?
sns.catplot(x="down", kind="count", hue='play_type', data=df_final);
plt.show()

三档比其他档位有明显更多的传球比赛而不是跑动比赛,但是,鉴于初始数据分布,这可能没有意义。
回归
我可以利用 numericPlayType 列的优势,创建一个回归图,看看是否有任何趋势。
sns.lmplot(x="yardsToGo", y="numericPlayType", data=df_final, y_jitter=.03, logistic=True, aspect=2);
plt.show()

这是一个基本的回归图,它表明剩余码数的值越大,数字比赛类型的值就越大。比赛类型 0 代表跑动,1 代表传球,这意味着需要覆盖的距离越远,比赛越有可能是传球类型。
模型训练
我将使用 XGBoost 进行训练;它要求输入数据全部为数字(所以我必须删除我在可视化中使用的 play_type 列)。我还需将我的数据拆分为训练、验证和测试子集。
train_df, validation_df, test_df = np.split(df_final.sample(frac=1), [int(0.7 * len(df)), int(0.9 * len(df))])
print("Training size is %d, validation size is %d, test size is %d" % (len(train_df),
len(validation_df),
len(test_df)))

XGBoost 以特定的数据结构格式获取数据,我可以使用 DMatrix 函数创建该格式。基本上,我将声明 numericPlayType 为我要预测的标签,因此我将向其提供一个不包含该列的干净数据集。
train_clean_df = train_df.drop(columns=['numericPlayType'])
d_train = xgb.DMatrix(train_clean_df, label=train_df['numericPlayType'],
feature_names=list(train_clean_df))
val_clean_df = validation_df.drop(columns =['numericPlayType'])
d_val = xgb.DMatrix(val_clean_df, label=validation_df['numericPlayType'],
feature_names=list(val_clean_df))
eval_list = [(d_train, 'train'), (d_val, 'eval')]
results = {}
剩余的设置需要一些参数调整。在不深入细节的情况下,预测跑动/传球是一个二元问题,我应该将目标设置为 binary.logistic。有关 XGBoost 所有参数的更多信息,请查阅其文档。
param = {
'objective': 'binary:logistic',
'eval_metric': 'auc',
'max_depth': 5,
'eta': 0.2,
'rate_drop': 0.2,
'min_child_weight': 6,
'gamma': 4,
'subsample': 0.8,
'alpha': 0.1
}
在对我电脑进行了一些令人不快的谩骂以及一个分为两部分的系列之后,(Python 中哭泣),我正式准备好训练我的模型了!我将设置一个提前停止轮次,这意味着如果模型训练的评估指标在八轮后下降,我将结束训练。这有助于防止过拟合。预测结果表示为结果将为 1(传球比赛)的概率。
num_round = 250
xgb_model = xgb.train(param, d_train, num_round, eval_list, early_stopping_rounds=8, evals_result=results)
test_clean_df = test_df.drop(columns=['numericPlayType'])
d_test = xgb.DMatrix(test_clean_df, label=test_df['numericPlayType'],
feature_names=list(test_clean_df))
actual = test_df['numericPlayType']
predictions = xgb_model.predict(d_test)
print(predictions[:5])

opensource.com
我想看看我的模型使用四舍五入的预测(到 0 或 1)和 scikit-learn 的 metrics 包的准确性如何。
rounded_predictions = np.round(predictions)
accuracy = metrics.accuracy_score(actual, rounded_predictions)
print("Metrics:\nAccuracy: %.4f" % (accuracy))

嗯,75% 的准确率对于首次尝试训练来说还不错。对于那些熟悉 NFL 的人来说,你可以叫我下一个肖恩·麦克维 (Sean McVay)。(相信我,这很有趣。)
使用 Python 及其庞大的库和模型库,我可以合理地预测比赛类型结果。但是,仍然有一些因素我没有考虑。防守人员对比赛类型有什么影响?比赛时的得分差呢?我想总是有改进数据的空间。唉,这就是程序员转变为数据科学家的生活。是时候考虑提前退休了。
1 条评论