个性化阅读
专注于IT技术分析

Scikit-Learn教程:棒球分析(2)

在本教程的第一部分中, 重点是根据球队的统计数据和该季节的其他变量, 确定该联盟赢得MLB球队的比赛次数。

在该项目的第二部分中, 你将测试Scikit-Learn(sklearn)的逻辑回归模型和随机森林模型, 以根据球员的职业统计数据和奖项来预测哪些球员将被选入名人堂。 。每行将包含一个球员和与其职业相关的数据。棒球有四个主要职位分组:内场球员, 外场球员, 投手和守望者。该项目的重点是内野手和外野手。

scikit-学习教程

美国国家棒球名人堂和博物馆位于纽约库珀斯敦, 建于1939年。如果棒球运动员符合以下条件, 则可以当选名人堂成员:

  • 玩家必须参加至少十个赛季的比赛;
  • 该球员已退休至少五个赛季;
  • 筛选委员会必须批准将球员的资格列入选票, 并且大多数定期比赛十年或以上的球员都被认为是值得的;
  • 球员不得进入不合格名单(这意味着不应禁止该球员参加棒球比赛);
  • 如果球员在选举中获得至少75%的选票, 则视为该球员当选;和
  • 如果球员获得至少5%的选票, 并且可以在选票上出现最多10年, 则他们将在第二年继续选票。

可以通过退伍军人委员会或美国职棒大联盟专员任命的特别委员会来增加未当选名人堂的球员, 但该项目将仅针对被选入名人堂的球员。从根本上讲, 你想知道的是:

“你能否建立一种机器学习模型来准确预测美国职业棒球大联盟的棒球选手是否会被选入名人堂?”

在该项目的第一部分, 你从SQLite数据库加载了数据。这次, 你将从CSV文件中加载数据。肖恩·拉曼(Sean Lahman)在其网站上汇编了这些数据。

汇入资料

你需要将以下CSV文件中的数据读入pandas DataFrames:

  • Master.csv会告诉你更多有关球员姓名, 出生日期(DOB)和传记信息的信息;
  • Fielding.csv包含字段统计信息, 而
  • Batting.csv包含”击球”统计信息;
  • 你还将阅读AwardsPlayers.csv, 在其中你可以找到有关棒球选手获得的奖项的数据;
  • AllstarFull.csv文件将为你提供全明星赛的所有外观;
  • 你还需要名人堂投票数据, 你可以在HallOfFame.csv中找到该数据;和
  • Appearances.csv, 其中包含有关玩家出现位置的详细信息。

如果某些概念还不清楚, 请不要担心, 在学习本教程时, 你将获得更多信息!

首先, 点击此处下载CSV文件。理想情况下, 将所有数据放入一个特定的文件夹中, 该文件夹将包含该项目的所有文件。接下来, 打开终端或启动Jupyter Notebook应用程序, 就可以开始工作了!

加载熊猫并重命名为pd, 然后读取每个CSV文件。你将仅包括某些列, 并且可以在此处查看可用的列。


# Import data to DataFrames
import pandas as pd

# Read in the CSV files
master_df = pd.read_csv('Master.csv', usecols=['playerID', 'nameFirst', 'nameLast', 'bats', 'throws', 'debut', 'finalGame'])
fielding_df = pd.read_csv('Fielding.csv', usecols=['playerID', 'yearID', 'stint', 'teamID', 'lgID', 'POS', 'G', 'GS', 'InnOuts', 'PO', 'A', 'E', 'DP'])
batting_df = pd.read_csv('Batting.csv')
awards_df = pd.read_csv('AwardsPlayers.csv', usecols=['playerID', 'awardID', 'yearID'])
allstar_df = pd.read_csv('AllstarFull.csv', usecols=['playerID', 'yearID'])
hof_df = pd.read_csv('HallOfFame.csv', usecols=['playerID', 'yearid', 'votedBy', 'needed_note', 'inducted', 'category'])
appearances_df = pd.read_csv('Appearances.csv')

请注意, 对于本教程, 数据将为你加载!

数据清理和预处理

现在, 你已经导入了数据, 就可以开始清理和预处理数据了。因为你正在使用7个CSV文件, 所以最好先逐一查看它们:在以下部分中, 你将按以下顺序浏览各个DataFrame, 并按以下顺序进行操作:batting_df, fielding_df, awards_df, allstar_df, hof_df, appearances_df和master_df。

batting_df

首先, 你将处理batting_df数据框。在head()函数的帮助下打印出前几行:

提示:因为你从该数据表中拉入了所有列, 所以明智的方法是使用columns属性同时拉出batting_df DataFrame的列以查看你要处理的内容。另外, 请随时使用任何其他Pandas功能来彻底检查你的数据!

检查结果时, 你会注意到关于batting_df及其列的三件事:

  1. 有很多ID。请记住, 这没有什么异常:因为数据最初来自数据库, 所以你的所有表都将相互链接, 就像关系数据库的正常操作(和要求)一样。
  • playerID, 用于玩家ID代码。
  • yearID, 其中列出了年份;
  • teamID标识玩家参加的球队;
  • lgID标识联盟;
  1. 正如你期望的batting_df一样, 有很多关于玩家的统计信息;这里有些例子:
  • G为游戏数,
  • AB表示蝙蝠数量;
  • R表示运行次数;
  • H为点击数;
  • 本垒打数量的人力资源;
  • 跑步的印度储备银行;
  • SO或三振。
  1. 目前, 你的数据还不够整洁。如果你不知道”整洁数据”的含义是什么, 请在此处阅读整洁数据的详细说明。确保整洁非常重要;简而言之, 这意味着你需要到达每一行都是观察点的位置, 在这种情况下, 这是玩家的职业, 每一列都是变量。当前DataFrames中的大多数行来自各个季节。为了球员的职业生涯, 每个赛季都需要排成一排。

为此, 你将为词典中的每个玩家创建字典。每个玩家的统计信息将汇总到他们的词典中, 然后可以将主词典转换为DataFrame。

首先, 为玩家的统计数据创建一本词典, 为游戏年份创建一本词典。将每个玩家的统计数据从batting_df汇总到player_stats中, 并将他们玩了几年的数据汇总到years_played中:


# Initialize dictionaries for player stats and years played
player_stats = {}
years_played = {}

# Create dictionaries for player stats and years played from `batting_df`
for i, row in batting_df.iterrows():
    playerID = row['playerID']
    if playerID in player_stats:
        player_stats[playerID]['G'] = player_stats[playerID]['G'] + row['G']
        player_stats[playerID]['AB'] = player_stats[playerID]['AB'] + row['AB']
        player_stats[playerID]['R'] = player_stats[playerID]['R'] + row['R']
        player_stats[playerID]['H'] = player_stats[playerID]['H'] + row['H']
        player_stats[playerID]['2B'] = player_stats[playerID]['2B'] + row['2B']
        player_stats[playerID]['3B'] = player_stats[playerID]['3B'] + row['3B']
        player_stats[playerID]['HR'] = player_stats[playerID]['HR'] + row['HR']
        player_stats[playerID]['RBI'] = player_stats[playerID]['RBI'] + row['RBI']
        player_stats[playerID]['SB'] = player_stats[playerID]['SB'] + row['SB']
        player_stats[playerID]['BB'] = player_stats[playerID]['BB'] + row['BB']
        player_stats[playerID]['SO'] = player_stats[playerID]['SO'] + row['SO']
        player_stats[playerID]['IBB'] = player_stats[playerID]['IBB'] + row['IBB']
        player_stats[playerID]['HBP'] = player_stats[playerID]['HBP'] + row['HBP']
        player_stats[playerID]['SH'] = player_stats[playerID]['SH'] + row['SH']
        player_stats[playerID]['SF'] = player_stats[playerID]['SF'] + row['SF']
        years_played[playerID].append(row['yearID'])
    else:
        player_stats[playerID] = {}
        player_stats[playerID]['G'] = row['G']
        player_stats[playerID]['AB'] = row['AB']
        player_stats[playerID]['R'] = row['R']
        player_stats[playerID]['H'] = row['H']
        player_stats[playerID]['2B'] = row['2B']
        player_stats[playerID]['3B'] = row['3B']
        player_stats[playerID]['HR'] = row['HR']
        player_stats[playerID]['RBI'] = row['RBI']
        player_stats[playerID]['SB'] = row['SB']
        player_stats[playerID]['BB'] = row['BB']
        player_stats[playerID]['SO'] = row['SO']
        player_stats[playerID]['IBB'] = row['IBB']
        player_stats[playerID]['HBP'] = row['HBP']
        player_stats[playerID]['SH'] = row['SH']
        player_stats[playerID]['SF'] = row['SF']
        years_played[playerID] = []
        years_played[playerID].append(row['yearID'])

现在, 循环浏览Years_played字典, 并将每个玩家玩的年数添加到player_stats字典中:


# Iterate through `years_played` and add the number of years played to `player_stats`
for k, v in years_played.items():
    player_stats[k]['Years_Played'] = len(list(set(v)))

fielding_df

接下来是fielding_df。该表中的数据包含更多玩家统计信息, 因此与batting_df非常相似。在上面的player_stats词典中汇总每个玩家的统计信息时, 最好也将fielding_df中的统计信息汇总到player_stats词典中。

更具体地说, 你需要在遍历fielding_df的过程中将新键添加到player_stats词典中的每个玩家词典中。首先, 确保创建一个fielder_list来识别哪些球员输入了他们的守门员钥匙, 以便可以正确添加统计信息:


# Initialize `fielder_list`
fielder_list = []

# Add fielding stats to `player_stats` from `fielding_df`
for i, row in fielding_df.iterrows():
    playerID = row['playerID']
    Gf = row['G']
    GSf = row['GS']
    POf = row['PO']
    Af = row['A']
    Ef = row['E']
    DPf = row['DP']
    if playerID in player_stats and playerID in fielder_list:
        player_stats[playerID]['Gf'] = player_stats[playerID]['Gf'] + Gf
        player_stats[playerID]['GSf'] = player_stats[playerID]['GSf'] + GSf
        player_stats[playerID]['POf'] = player_stats[playerID]['POf'] + POf
        player_stats[playerID]['Af'] = player_stats[playerID]['Af'] + Af
        player_stats[playerID]['Ef'] = player_stats[playerID]['Ef'] + Ef
        player_stats[playerID]['DPf'] = player_stats[playerID]['DPf'] + DPf
    else:
        fielder_list.append(playerID)
        player_stats[playerID]['Gf'] = Gf
        player_stats[playerID]['GSf'] = GSf
        player_stats[playerID]['POf'] = POf
        player_stats[playerID]['Af'] = Af
        player_stats[playerID]['Ef'] = Ef
        player_stats[playerID]['DPf'] = DPf

awards_df

接下来是awards_df数据框架:在球员的整个职业生涯中, 他们可能会因出色的成就而获得许多不同的奖项。 awards_df包含有关玩家赢得的奖项的所有数据。

在此处检查数据的一种好方法是使用unique()方法在awards_df中打印出唯一的奖项。另外, 你也可以尝试使用head(), tail(), info(), …函数来更好地了解此DataFrame中的数据。

提示:从第一个代码块中, 你知道你仅从原始数据表中读取了三列:playerID, awardID和yearID。

如你所见, awards_df中有很多选项……你知道所有这些奖项吗?

如本教程的第一部分所述, 了解你正在使用的数据非常重要。例如, 最佳奖项包括”最有价值球员”, “年度最佳新秀”, “金手套”, “银棒”和世界大赛MVP奖项, 我完全根据我在MLB棒球比赛后的经验来确定。

显然, 可以确定这些奖项中的哪一项与被选入名人堂最相关, 但这需要更多的编码。

现在, 你需要将每个奖励的计数添加到player_stats词典中的每个玩家的字典中:首先, 为上面列出的每个奖励过滤awards_df, 创建五个新系列。然后创建一个名为awards_list的列表, 并在列表中包含五个系列中的每个系列。就像你之前在fielding_df上所做的那样, 创建一个列表来确定在五个奖项系列中的每个奖项的player_stats中已将哪些键添加到词典中。现在创建一个名为列表的列表, 并包含五个列表中的每个列表。现在, 你可以循环浏览awards_list, 并遍历每个奖项系列, 计算每个玩家获得的每个奖项有多少:


# Create DataFrames for each award
mvp = awards_df[awards_df['awardID'] == 'Most Valuable Player']
roy = awards_df[awards_df['awardID'] == 'Rookie of the Year']
gg = awards_df[awards_df['awardID'] == 'Gold Glove']
ss = awards_df[awards_df['awardID'] == 'Silver Slugger']
ws_mvp = awards_df[awards_df['awardID'] == 'World Series MVP']

# Include each DataFrame in `awards_list`
awards_list = [mvp, roy, gg, ss, ws_mvp]

# Initialize lists for each of the above DataFrames
mvp_list = []
roy_list = []
gg_list = []
ss_list = []
ws_mvp_list = []

# Include each of the above lists in `lists`
lists = [mvp_list, roy_list, gg_list, ss_list, ws_mvp_list]

# Add a count for each award for each player in `player_stats`
for index, v in enumerate(awards_list):
    for i, row in v.iterrows():
        playerID = row['playerID']
        award = row['awardID']
        if playerID in player_stats and playerID in lists[index]:
            player_stats[playerID][award] += 1
        else:
            lists[index].append(playerID)
            player_stats[playerID][award] = 1

allstar_df

allstar_df数据框包含有关哪些球员出现在Allstar游戏中的信息。 Allstar游戏是每年中赛季都玩的一个展示游戏。美国职棒大联盟由两个联赛组成:美国联赛和国家联赛。每个联赛的前25名球员将被选为代表他们在Allstar游戏中的联赛。

像你之前所做的那样, 遍历allstar_df并在player_stats中的每个字典中添加一个Allstar游戏外观计数。


# Initialize `allstar_list`
allstar_list = []

# Add a count for each Allstar game appearance for each player in `player_stats`
for i, row in allstar_df.iterrows():
    playerID = row['playerID']
    if playerID in player_stats and playerID in allstar_list:
        player_stats[playerID]['AS_games'] += 1
    else:
        allstar_list.append(playerID)
        player_stats[playerID]['AS_games'] = 1

hof_df

接下来是hof_df数据框架, 其中包含有关哪些球员, 经理和高管已被提名或入选名人堂以及采用何种方法的信息。

将此类信息添加到player_stats可能很有价值!为此, 首先过滤hof_df仅包括入选名人堂的球员。然后遍历hof_df, 指示player_stats中的哪些玩家已被引入名人堂以及如何被引入:


# filter `hof_df` to include only instances where a player was inducted into the Hall of Fame
hof_df = hof_df[(hof_df['inducted'] == 'Y') & (hof_df['category'] == 'Player')]

# Indicate which players in `player_stats` were inducted into the Hall of Fame
for i, row in hof_df.iterrows():
    playerID = row['playerID']
    if playerID in player_stats:
        player_stats[playerID]['HoF'] = 1
        player_stats[playerID]['votedBy'] = row['votedBy']

player_stats

现在, 你已将batting_df, fielding_df, awards_df, allstar_df和hof_df中的数据编译到player_stats词典中。你还需要做更多的工作与appearances_df DataFrame, 但是现在是将player_stats字典转换为DataFrame的好时机。

使用pandas from_dict()方法将字典转换为名为stats_df的数据框:


# Convert `player_stats` into a DataFrame
stats_df = pd.DataFrame.from_dict(player_stats, orient='index')

stats_df数据框正在使用每个玩家的唯一玩家ID作为索引。在stats_df中添加一列, 该列称为从索引派生的playerID。然后使用内部联接将stats_df与master_df联接。

请记住, 只要列之间存在匹配, 内部联接就会从两个表中选择所有行。在这种情况下, 你将使用内部联接, 因为你要将stats_df的信息与master_df数据进行匹配:你想继续使用两个DataFrame共有的行。

Appearances_DF

在开始为数据创建新功能之前, 你加载并需要查看的最后一个DataFrame是appearances_df。该数据框包含有关每个球员每年在每个位置出现多少次的信息。

使用head()方法打印出前几行:

提示:使用IPython控制台通过其他函数(例如info(), tail(), attributes列和index)来检查你的数据, …你会看到appearances_df有21列, 在某些情况下这非常简单(请考虑(你已经在其他DataFrames中看到过的ID列)), 但是在其他情况下, 你会看到它们有些含糊的名称, 例如G_3b, G_dh等。

现在是查看README页面为你提供的信息的好时机:

  • G_all玩过的游戏总数
  • GS游戏开始
  • G_batting玩家击球的游戏
  • G_defense游戏, 其中玩家出现在防守方
  • G_p Games作为投手
  • G_c Games作为接球手
  • G_1b Games作为一垒手
  • G_2b Games作为二垒手
  • G_3b Games作为三垒手
  • G_ss Games作为游击手
  • G_lf Games作为左后卫
  • G_cf Games作为中场球员
  • G_rf Games作为正确的守场员
  • G_of Games作为外场手
  • G_dh Games被指定为击球手
  • G_ph Games成为击球手
  • G_pr Games作为捏跑者

马上, 你会看到appearings_df中有很多信息需要提取。你还将看到, 你需要汇总每个球员的出场次数, 以找到每个位置的职业出场总数。

你可能会从本教程的第一部分回忆起, 随着MLB的发展, 出现了不同的时代, 每场比赛的跑步次数显着增加或减少。这意味着, 当一名球员参加比赛时, 对该球员的职业统计有很大影响。名人堂(HoF)选民在对球员进行投票时会考虑到这一点, 因此你的模型也需要该信息。

要收集此信息, 请创建一个名为pos_dict的字典。像上面使用player_stats字典一样, 遍历appearings_df并为每个玩家添加字典。使用该位置作为键, 将每个球员在每个赛季中每个位置在每个位置上的出场次数汇总起来, 然后将出场次数作为值。你还需要使用时代作为关键, 汇总每个时代每个玩家玩的游戏数量, 并将所玩的游戏视为价值。


# Initialize a dictionary
pos_dict = {}

# Iterate through `appearances_df`
# Add a count for the number of appearances for each player at each position
# Also add a count for the number of games played for each player in each era.
for i, row in appearances_df.iterrows():
    ID = row['playerID']
    year = row['yearID']
    if ID in pos_dict:
        pos_dict[ID]['G_all'] = pos_dict[ID]['G_all'] + row['G_all']
        pos_dict[ID]['G_p'] = pos_dict[ID]['G_p'] + row['G_p']
        pos_dict[ID]['G_c'] = pos_dict[ID]['G_c'] + row['G_c']
        pos_dict[ID]['G_1b'] = pos_dict[ID]['G_1b'] + row['G_1b']
        pos_dict[ID]['G_2b'] = pos_dict[ID]['G_2b'] + row['G_2b']
        pos_dict[ID]['G_3b'] = pos_dict[ID]['G_3b'] + row['G_3b']
        pos_dict[ID]['G_ss'] = pos_dict[ID]['G_ss'] + row['G_ss']
        pos_dict[ID]['G_lf'] = pos_dict[ID]['G_lf'] + row['G_lf']
        pos_dict[ID]['G_cf'] = pos_dict[ID]['G_cf'] + row['G_cf']
        pos_dict[ID]['G_rf'] = pos_dict[ID]['G_rf'] + row['G_rf']
        pos_dict[ID]['G_of'] = pos_dict[ID]['G_of'] + row['G_of']
        pos_dict[ID]['G_dh'] = pos_dict[ID]['G_dh'] + row['G_dh']
        if year < 1920:
            pos_dict[ID]['pre1920'] = pos_dict[ID]['pre1920'] + row['G_all']
        elif year >= 1920 and year <= 1941:
            pos_dict[ID]['1920-41'] = pos_dict[ID]['1920-41'] + row['G_all']
        elif year >= 1942 and year <= 1945:
            pos_dict[ID]['1942-45'] = pos_dict[ID]['1942-45'] + row['G_all']
        elif year >= 1946 and year <= 1962:
            pos_dict[ID]['1946-62'] = pos_dict[ID]['1946-62'] + row['G_all']
        elif year >= 1963 and year <= 1976:
            pos_dict[ID]['1963-76'] = pos_dict[ID]['1963-76'] + row['G_all']
        elif year >= 1977 and year <= 1992:
            pos_dict[ID]['1977-92'] = pos_dict[ID]['1977-92'] + row['G_all']
        elif year >= 1993 and year <= 2009:
            pos_dict[ID]['1993-2009'] = pos_dict[ID]['1993-2009'] + row['G_all']
        elif year > 2009:
            pos_dict[ID]['post2009'] = pos_dict[ID]['post2009'] + row['G_all']
    else:
        pos_dict[ID] = {}
        pos_dict[ID]['G_all'] = row['G_all']
        pos_dict[ID]['G_p'] = row['G_p']
        pos_dict[ID]['G_c'] = row['G_c']
        pos_dict[ID]['G_1b'] = row['G_1b']
        pos_dict[ID]['G_2b'] = row['G_2b']
        pos_dict[ID]['G_3b'] = row['G_3b']
        pos_dict[ID]['G_ss'] = row['G_ss']
        pos_dict[ID]['G_lf'] = row['G_lf']
        pos_dict[ID]['G_cf'] = row['G_cf']
        pos_dict[ID]['G_rf'] = row['G_rf']
        pos_dict[ID]['G_of'] = row['G_of']
        pos_dict[ID]['G_dh'] = row['G_dh']
        pos_dict[ID]['pre1920'] = 0
        pos_dict[ID]['1920-41'] = 0
        pos_dict[ID]['1942-45'] = 0
        pos_dict[ID]['1946-62'] = 0
        pos_dict[ID]['1963-76'] = 0
        pos_dict[ID]['1977-92'] = 0
        pos_dict[ID]['1993-2009'] = 0
        pos_dict[ID]['post2009'] = 0
        if year < 1920:
            pos_dict[ID]['pre1920'] = row['G_all']
        elif year >= 1920 and year <= 1941:
            pos_dict[ID]['1920-41'] = row['G_all']
        elif year >= 1942 and year <= 1945:
            pos_dict[ID]['1942-45'] = row['G_all']
        elif year >= 1946 and year <= 1962:
            pos_dict[ID]['1946-62'] = row['G_all']
        elif year >= 1963 and year <= 1976:
            pos_dict[ID]['1963-76'] = row['G_all']
        elif year >= 1977 and year <= 1992:
            pos_dict[ID]['1977-92'] = row['G_all']
        elif year >= 1993 and year <= 2009:
            pos_dict[ID]['1993-2009'] = row['G_all']
        elif year > 2009:
            pos_dict[ID]['post2009'] = row['G_all']

现在你的字典已经完成, 你可以在from_dict()函数的帮助下将pos_dict转换为DataFrame:


# Convert the `pos_dict` to a DataFrame
pos_df = pd.DataFrame.from_dict(pos_dict, orient='index')

另外, 打印列并进一步检查数据:

接下来, 你要确定每个球员在每个时代和每个时代在每个位置上的出场次数百分比。

首先, 从pos_df的列中创建一个名为pos_col_list的列表, 然后删除字符串” G_all”。然后, 通过pos_col_list循环并将每个位置或时代所玩游戏除以该玩家所玩游戏总数, 在pos_df中为每个玩家在每个位置和每个时代所玩游戏的百分比创建新列。最后, 打印出pos_df的前几行:

如前所述, 该项目着重于内野手和外野手, 因为在确定HoF价值时对每个位置的判断都是不同的。对内野手和外野手的判断类似, 而投手和捕手的判断则主要基于防守的差异。这超出了该项目的范围, 因为它将需要更多的数据清理和功能设计。

由于许多球员在美国职业棒球大联盟(MLB)成立初期曾在多个不同的位置打过球, 因此通过过滤掉在任一位置上场均超过10%的球员来消除投手和接球手。

master_df

请记住, 你对master_df和stats_df进行了内部联接, 该联接由batting_df, fielding_df, awards_df, allstar_df和hof_df收集的信息组成, 以获得球员统计数据和球员主数据的更统一的数据集玩家姓名, 出生日期和传记信息。

现在, 你想将统一的master_df与pos_df结合起来;小心!你需要在此处使用右加入, 因为你只想保留pos_df中的玩家:

现在, 过滤器master_df只包括那些被选入名人堂或根本没有入选的球员。

一些球员是从退伍军人委员会或经特别任命的委员会选拔而进入大厅的。选择这些播放器的原因通常不是纯粹出于统计原因, 你的模型可能难以量化这些统计信息, 因此最好将它们排除在数据集中之外。

bats和throws列需要转换为数字:你可以通过创建一个函数来轻松实现此目的, 该函数将每个R转换为1, 将每个L转换为0。使用apply()方法创建称为bats_R和throws_R的数字列:

首次亮相和finalGame列当前为字符串。你需要从这些列中解析年份。首先, 导入日期时间。接下来, 使用pd.to_datetime()将Premiere和finalGame列转换为datetime对象。然后使用dt.strftime(‘%Y’)解析年份, 并使用pd.to_numeric()转换为数字。

目前在master_df中有一些不必要的列。在master_df上使用drop()方法并创建一个名为df的新DataFrame。现在是打印df中的列并显示每列的空值计数的好时机。

在本教程的第一部分中, 你正在处理团队统计信息, 并且空值可能是由于缺少数据所致。例如, 一支球队永远都不会在没有双重比赛的情况下完成整个赛季。

对于播放器, 空值很可能为零。

因此, 你要创建一个数字列列表并在列表中循环, 使用fillna()方法用零填充空值。然后使用dropna()方法消除所有剩余的空值。

恭喜你!最终, 你对播放器有了一个完全统一的视图:你将播放器统计信息和主数据放在一个位置, 即df DataFrame。

特征工程:创建新特征

现在, 数据已清理完毕, 你可以添加新功能。我们的数据中缺少的重要棒球统计数据是击球平均值, 垒球百分比, 击球百分比以及垒球加上击球百分比。

使用以下公式添加每个统计信息:

  • 击球大道。 =击中/击中
  • 盘子外观=击球+步行+牺牲蝇和击球+俯仰击球
  • 基准上的百分比=(命中率+步行数+俯仰点数)/板块外观
  • 慢跑%=((本垒打x 4)+(三倍x 3)+(双倍x 2)+单打)/蝙蝠
  • 基地加打击%=基地%+打击%

你的数据中有一些异常需要解决, 如果你对MLB历史记录有所了解, 可能会知道这些异常:

  • 前两个涉及拥有名人堂值得统计的球员, 但终身被禁止棒球。例如, Shoeless Joe Jackson(playerID == jacksjo01)是一位令人难以置信的球员, 但在1919年Black Sox丑闻中陷入困境, 当时芝加哥White Sox的多名球员接受了流氓的钱, 并故意输了。即使他在该系列赛中表现出色, 也被终身禁赛。

  • 此外, 你还有史无前例的领导者Pete Rose(playerID == rosepe01)。绰号查理·侯赛尔(Charlie Hussle), 他是在辛辛那提红人(Cincinnati Reds)踢球的职业生涯中举世无双的人。在退役后不久, 他成为了红人队的经理, 并被下注于棒球比赛, 包括下注自己的球队取胜。他也被终身禁止。最好从数据集中删除这两个玩家。

  • 需要解决的另一个异常情况是与上述两种情况截然不同的情况。杰基·罗宾逊(Jackie Robinson)历来都是伟大的球员, 曾效力于布鲁克林道奇队(Brooklyn Dodgers), 在他出场的那几年里他的数据令人难以置信。他还打破了美国职棒大联盟的色差, 成为第一位非裔美国人的美国职棒大联盟。问题是, 他的职业生涯起步很晚, 当时他26岁, 这完全超出了他的控制范围, 限制了他的职业统计。

出于对杰基·罗宾逊(Jackie Robinson)(玩家ID == robinja02)的尊重以及他对棒球比赛产生的不可思议的影响, 他没有删除他, 而是创建了一个功能, 将他确定为美国职业棒球大联盟中第一个参加比赛的非洲裔美国人。

现在是时候可视化一些数据以获得更好的理解了。过滤df以仅包括名人堂球员并打印长度。

数据集中还剩下70个名人堂成员。列出这些名人堂成员的热门, 全垒打, 比赛年限和全明星赛状态的分布。


# Import the `pyplot` module from `matplotlib`
import matplotlib.pyplot as plt

# Initialize the figure and add subplots
fig = plt.figure(figsize=(15, 12))
ax1 = fig.add_subplot(2, 2, 1)
ax2 = fig.add_subplot(2, 2, 2)
ax3 = fig.add_subplot(2, 2, 3)
ax4 = fig.add_subplot(2, 2, 4)

# Create distribution plots for Hits, Home Runs, Years Played and All Star Games
ax1.hist(df_hof['H'])
ax1.set_title('Distribution of Hits')
ax1.set_ylabel('HoF Careers')
ax2.hist(df_hof['HR'])
ax2.set_title('Distribution of Home Runs')
ax3.hist(df_hof['Years_Played'])
ax3.set_title('Distribution of Years Played')
ax3.set_ylabel('HoF Careers')
ax4.hist(df_hof['AS_games'])
ax4.set_title('Distribution of All Star Game Appearances')

# Show the plot
plt.show()

请注意, 如果你使用的是Jupyter笔记本, 则可以轻松利用%matplotlib内联魔术来绘制这些分布图!

Scikit-Learn教程:棒球分析Pt 22

接下来, 为”命中率与击球平均值”创建一个散点图, 为”本垒打”与”击球平均值”创建一个散点图。首先, 过滤器df仅包括玩了10个或更多赛季的球员, 这是名人堂资格的最低要求。


# Filter `df` for players with 10 or more years of experience
df_10 = df[(df['Years_Played'] >= 10) & (df['HoF'] == 0)]

# Initialize the figure and add subplots
fig = plt.figure(figsize=(14, 7))
ax1 = fig.add_subplot(1, 2, 1)
ax2 = fig.add_subplot(1, 2, 2)

# Create Scatter plots for Hits vs. Average and Home Runs vs. Average
ax1.scatter(df_hof['H'], df_hof['AVE'], c='r', label='HoF Player')
ax1.scatter(df_10['H'], df_10['AVE'], c='b', label='Non HoF Player')
ax1.set_title('Career Hits vs. Career Batting Average')
ax1.set_xlabel('Career Hits')
ax1.set_ylabel('Career Average')
ax2.scatter(df_hof['HR'], df_hof['AVE'], c='r', label='HoF Player')
ax2.scatter(df_10['HR'], df_10['AVE'], c='b', label='Non HoF Player')
ax2.set_title('Career Home Runs vs. Career Batting Average')
ax2.set_xlabel('Career Home Runs')
ax2.legend(loc='lower right', scatterpoints=1)

# Show the plot
plt.show()
Scikit-Learn教程:棒球分析Pt 23

创建新功能时, 可能还创建了新的空值。打印出每列的空值计数。

如你所见, null值并不多, 因此你可以轻松地删除其中具有null值的行。

在本教程系列的第一部分中, 通过对每个数据进行随机切片, 将数据分为测试集和训练集。这次, 你将使用交叉验证来训练和测试模型。由于球员必须等待5年才能获得HoF选票的资格, 并且可以保留长达10年的选票, 因此仍然有合格的球员在过去15年中参加了他们的最后一个赛季。

确保将过去15年中最近一个赛季的球员分开, 以便在拥有经过训练和测试的模型后将其用作”新”数据。

通过从2016年减去finalYear列, 向df添加自上个赛季以来的年份(YSLS)列。接下来, 为上一个季节超过15年的球员过滤df, 创建一个名为df_hitters的新数据框, 并为球员创建一个名为df_eligible的球员上个赛季是15年前或更短的时间。

现在, 你需要选择要包含在模型中的列。首先, 打印出列。

创建一个包含要包含在模型中的列的列表, 并使用列列表从df_hitters创建一个名为data的新DataFrame。在列表中包括playerID, nameFirst和nameLast, 因为当你使用新数据显示预测结果时, 你会希望这些列。

你需要为模型中使用的所有数据删除这三列。

打印出数据中有多少行以及名人堂中的那些行。

有6, 239行, 其中61行是名人堂, 供你识别模型。通过使用drop()删除标识列和目标列, 从数据的HoF列和要素DataFrame创建目标系列。

现在, 你已经隔离了目标和功能, 现在该构建模型了!

选择误差指标和模型

如前所述, 你要回答的问题是:

“你能否建立一种机器学习模型来准确预测美国职业棒球大联盟的棒球选手是否会被选入名人堂?”

第二部分的错误度量很简单。你的预测有多准确?

你预测多少个”真实肯定”的球员在HoF中, 并且他们是HoF成员。你预测多少个”假阳性”球员被预测在HoF中, 而他们不是HoF成员。最后, 当玩家实际上是HoF成员时, 有多少”假阴性”被预测为不在HoF中。

逻辑回归

你将尝试的第一个模型是Logistic回归模型。你将使用Kfold交叉验证技术, 并且可以在此处了解更多信息。

从sklearn.cross_validation导入cross_val_predict和KFold, 从sklearn.linear_model导入LogisticRegression。创建逻辑回归模型lr, 并确保将class_weight渗透系数设置为”平衡”, 因为数据中的非HoF成员明显更多。

创建KFold类(kf)的实例, 最后, 使用模型lr, 特征, 目标并将cv参数设置为kf, 使用cross_val_predict()进行预测(predictions_lr)。

为了确定准确性, 你需要将你的预测与目标进行比较。将numpy导入为np, 然后将predictions_lr转换为NumPy数组。

为四种可能的预测方案中的每一个创建一个过滤器:

  • 真实肯定:预测= 1, 目标= 1
  • Fales阴性:预测= 0, 目标= 1
  • 误报:预测= 1, 目标= 0
  • 真否定:预测= 0, 目标= 0

然后通过将过滤器应用于你的预测来确定计数。现在, 你可以使用以下公式确定准确率:

  • 真阳性率:真阳性/(真阳性+假阴性)
  • 假阴性率:假阴性/(假阴性+真阳性)
  • 误报率:误报率/(误报率+真否定率)

打印出除真阴性之外的每个计数以及每个准确率。

第一个模型相当准确, 选择了61个名人堂成员中的54个, 但它的假阴性率较高, 为36个。

随机森林

接下来是随机森林模型。从sklearn.ensemble导入RandomForestClassifier。对于此模型, 你无需将class_weight设置为balanced, 而是将其设置为惩罚, 这是你需要将键0设置为100并将键1设置为1的字典。

创建模型rf并按如下所示设置参数:random_state = 1, n_estimators = 12, max_depth = 11, min_samples_leaf = 1, class_weight =惩罚。

最后, 使用cross_val_predict()使你的预测(predictions_rf)与之前的模型相同, 然后再次将预测转换为NumPy数组。

与以前的模型一样, 确定”真阳性”, “假阴性”和”假阳性”的计数和比率。

随机森林模型预测61个名人堂成员中有50个的准确性较差, 但假阴性数仅为2。

“随机森林”模型的整体效果似乎更好, 因此, 你可以使用该模型通过保留的”新”数据进行预测。

使用num_cols_hitters列表从df_eligible创建一个新的DataFrame(new_data)。现在, 通过使用drop()删除标识列和目标列来创建另一个DataFrame(new_features)。

现在, 你可以使用fit()方法使用先前使用的功能和目标来拟合rf模型。接下来, 你可以预测”新”数据中的玩家将被选入名人堂的可能性。使用new_features在rf模型上使用predict_proba()方法。

最后, 将概率转换为称为hof_predictions的熊猫数据框。按降序对hof_predictions排序。

现在, 打印出hof_predictions的前50行中的每一行, 以及在new_data中找到的名称和重要统计信息。这些是你的模型预测最有可能被选入名人堂的50名球员, 以及估计的可能性和球员职业统计数据。

就目前而言, 该模型似乎在做出预测方面做得非常好, 但有一些例外:

  • 以哈罗德·贝恩斯(Harold Baines)为例, 他是一个坚实的参与者, 并且有合理的论据来制定HoF, 但该模型预测他有100%的机会做到这一点, 而这并不准确。
  • 此外, 该模型没有考虑被捕或被指控使用类固醇的球员, 这可能会阻止他们被选入HoF。

你未来的体育分析项目

至此, 我们的Scikit-Learn和体育分析教程系列的第二部分结束了。你从CSV文件导入了数据, 对其进行了清理和汇总。你在数据中生成了新功能, 并通过可视化对其有了更好的理解。你学习了如何创建逻辑回归模型和随机森林模型。你还学习了如何使用K折交叉验证来训练和测试模型, 如何检查分类模型的准确性以及如何使用新数据进行预测。

如果你喜欢这个项目, 请尝试建立自己的模型, 以预测哪些投手将被选入名人堂。

祝好运!

赞(0)
未经允许不得转载:srcmini » Scikit-Learn教程:棒球分析(2)

评论 抢沙发

评论前必须登录!