社会力量对 NBA 所产生的影响和表现,第 2 部分: 探索各个 NBA 球员

来源:转载

社会力量对 NBA 所产生的影响和表现,第 2 部分


探索各个 NBA 球员

Python、pandas 和少量 R 代码






Noah Gift


2017 年 10 月 12 日发布




系列内容:

此内容是该系列 # 部分中的第 # 部分: 社会力量对 NBA 所产生的影响和表现,第 2 部分

https://www.ibm.com/developerworks/cn/views/global/libraryview.jsp?series_title_by=%E7%A4%BE%E4%BC%9A%E5%8A%9B%E9%87%8F%E5%AF%B9%20NBA%20%E6%89%80%E4%BA%A7%E7%94%9F%E7%9A%84%E5%BD%B1%E5%93%8D%E5%92%8C%E8%A1%A8%E7%8E%B0


敬请期待该系列的后续内容。




此内容是该系列的一部分: 社会力量对 NBA 所产生的影响和表现,第 2 部分

敬请期待该系列的后续内容。




入门

在本系列的第 1 部分中,您学习了数据科学和机器学习的基本知识。您使用了 Jupyter Notebook、pandas 和 scikit-learn 来探索 NBA 球队与他们的估值之间的关系。在这部分中,将探索社交媒体、薪资与 NBA 球员场上表现之间的关系。


创建一个统一的数据帧(警告:前路艰辛!)

首先,创建一个新的 Jupyter Notebook 并将它命名为 nba_player_power_influence_performance 。


本教程的所有源代码和数据也包含在一个 GitHub 存储库中: Social Power NBA 。


接下来,加载关于球员的所有数据并合并到一个统一的数据帧中。


操作多个数据帧属于数据科学艰难的工作中占 80% 的那一类工作。在清单 1 和清单 2 中,复制了 basketball-reference 数据帧,然后对一些列进行重命名。


清单 1.设置 Jupyter Notebook 并加载数据帧
import pandas as pd
import numpy as np
import statsmodels.api as sm
import statsmodels.formula.api as smf
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
color = sns.color_palette()
from IPython.core.display import display, HTML
display(HTML(""))
%matplotlib inline
attendance_valuation_elo_df = pd.read_csv("../data/nba_2017_att_val_elo.csv")
salary_df = pd.read_csv("../data/nba_2017_salary.csv")
pie_df = pd.read_csv("../data/nba_2017_pie.csv")
plus_minus_df = pd.read_csv("../data/nba_2017_real_plus_minus.csv")
br_stats_df = pd.read_csv("../data/nba_2017_br.csv")
清单 2.修改贡献值数据帧的列中的错误数据
plus_minus_df.rename(columns={"NAME":"PLAYER"}, inplace=True)
players = []
for player in plus_minus_df["PLAYER"]:
plyr, _ = player.split(",")
players.append(plyr)
plus_minus_df.drop(["PLAYER"], inplace=True, axis=1)
plus_minus_df["PLAYER"] = players
plus_minus_df.head()

将 NAME 列重命名为 PLAYER 列的命令的输出如下所示。我们还丢弃了一些额外的列。记下 inplace=TRUE 和丢弃的列,以便应用到现有数据帧中。


图 1. NBA 数据集的加载和描述


下一步是重命名并合并核心数据帧,这些数据帧包含来自 Basketball Reference 的大部分统计数据。为此,请使用清单 3 和清单 4 中提供的代码。


清单 3.重命名并合并 basketball reference 数据帧
nba_players_df = br_stats_df.copy()
nba_players_df.rename(columns={'Player': 'PLAYER','Pos':'POSITION', 'Tm': "TEAM", 'Age': 'AGE'}, inplace=True)
nba_players_df.drop(["G", "GS", "TEAM"], inplace=True, axis=1)
nba_players_df = nba_players_df.merge(plus_minus_df, how="inner", on="PLAYER")
nba_players_df.head()
清单 4.清理并合并 PIE 字段
pie_df_subset = pie_df[["PLAYER", "PIE", "PACE"]].copy()
nba_players_df = nba_players_df.merge(pie_df_subset, how="inner", on="PLAYER")
nba_players_df.head()

图 2 显示了将列拆分为两部分并重新创建该列的命令的输出。拆分并重新创建列是一种典型操作,在解决数据科学问题的过程中会花费很多数据操作时间。


图 2. 合并 PIE 数据帧


目前为止,大部分数据操作任务都相对简单。由于缺失一些记录,事情即将变得更加艰难。在清单 5 中,有 111 个缺失薪资记录。解决此问题的一种方法是,执行一次合并来丢弃缺失的行。处理缺失数据的技术有许多;只是丢弃缺失的行(如示例中所示)并不总是最佳选择。 泰坦尼克:来自灾难的机器学习 中包含许多处理缺失数据的示例。花时间探索其中的一些示例 notebook 是值得的。


清单 5.清理薪资数据
salary_df.rename(columns={'NAME': 'PLAYER'}, inplace=True)
salary_df.drop(["POSITION","TEAM"], inplace=True, axis=1)
salary_df.head()

在清单 6 中,可以看到如何创建一个集合来计算缺失数据的行数。这是一个小诀窍,对确定两个数据帧之间的差异很有用。通过使用 Python 内置函数 len() 来获得列表长度,常规 Python 编程中也经常使用此函数来完成该操作。


清单 6.寻找缺失记录并合并它们
diff = list(set(nba_players_df["PLAYER"].values.tolist()) - set(salary_df["PLAYER"].values.tolist()))
len(diff)
Out[45]: 111
nba_players_with_salary_df = nba_players_df.merge(salary_df)

输出如下所示。


图 3. 数据帧之间的差异


完成数据帧合并后,是时候创建一个关联度热图来发现哪些特性相互关联了。下面的热图显示了 35 列与 342 行之间的关联的组合输出。可以立刻得出几点结论:薪资与得分和 WINS_RPM 都紧密相关,WINS_RPM 是一个高级统计方法,用于估算某位球员上场能给球队增添的胜算。


另一种有趣的关联是,Wikipedia 页面查看次数与 Twitter 点赞次数密切相关。这种关联一目了然,因为它们都是粉丝对 NBA 球员的互动和受欢迎程度的衡量。这个示例展示了可视化如何帮助确定哪些特性将引入到机器学习模型中。


图 4. NBA 球员关联度热图:2016-2017 赛季(统计数据和薪资)


对哪些特性相互关联有初步发现后,下一步是通过在 Seaborn 中绘制图表来进一步发现数据中的关系。 绘制该图要执行的命令如下所示。


清单 7.Seaborn 导入的薪资数据与 WINS_RPM 之间的关系
sns.lmplot(x="SALARY_MILLIONS", y="WINS_RPM", data=nba_players_with_salary_df)

在下图所示的图表输出中,薪资与 WINS_RPM 之间似乎存在强线性关系。要进一步调查该情况,可运行一次线性回归。


图 5. Seaborn 导入的薪资数据与获胜真实贡献值之间的关系


对获胜贡献值执行的两次线性回归的输出如下。一个更重要的发现是,使用 WINS_RPM 比使用得分更能解释获胜情况。使用 WINS_RPM 的决定系数(拟合优度)为 0.324,而使用得分的决定系数为 0.200。WINS_RPM 是显示归功于某位球员的各次获胜的统计指标。与仅使用进攻统计指标相比,考虑了防御和进攻统计指标以及场上时间的更高级统计方法预测能力会更好一些,这也是合情合理的。


一个实际应用此方法的示例是,设想一位球员拥有很低的投篮命中率,但得分很高。如果他经常投篮,而不是由一个拥有更高命中率的队友投篮,则可能会影响获胜。这种情况在 2015-16 赛季真实出现过,当时是科比·布莱恩特 在洛杉矶湖人队效力的最后一年,他每季获得 17.6 分,但两分球的投篮命中率只有 41%。该球队最终仅在 17 场比赛中获胜,WINS_RPM 统计指标为 0.66( 只有半次胜利是他在该赛季参赛的功劳 )。


图 6. 对获胜数据执行线性回归


清单 8.对获胜和得分数据执行回归
results = smf.ols('W ~POINTS', data=nba_players_with_salary_df).fit()
print(results.summary())

用图形表示此关系的另一种方法是在 Python 中使用 ggplot。清单 9 中的示例展示了如何设置该图表。Python 中的这个库是从 R 中的 ggplot 直接移植过来的,正在积极开发之中。截至编写本文时,它的使用并没有常规 R ggplot 那么顺利,但它有许多不错的特性。该图表如下所示。


备注:一个方便的特性是使用一种颜色来表示另一个包含连续变量的列。


清单 9.Python ggplot
from ggplot import *
p = ggplot(nba_players_with_salary_df,aes(x="POINTS", y="WINS_RPM", color="SALARY_MILLIONS")) + geom_point(size=200)
p + xlab("POINTS/GAME") + ylab("WINS/RPM") + ggtitle("NBA Players 2016-2017: POINTS/GAME, WINS REAL PLUS MINUS and SALARY")
图 7. Python ggplot 贡献值薪资得分


抓取 NBA 球员的 Wikipedia 页面的查看数据

下一个任务是确定如何收集 Wikipedia 页面的查看数据,这通常是一项麻烦的数据收集工作。问题包括:



确定如何从 Wikipedia(或某个网站)获取数据
确定如何以编程方式生成 Wikipedia 句柄
将数据写入一个数据帧中并将它与剩余数据合并

下面的代码包含在本教程的 GitHub 存储库中。以下各节将对此代码进行解释。


清单 10 提供了构造一个 Wikipedia URL 的代码,访问该 URL 将返回一个 JSON 响应。在第 1 部分的 docstrings 中,展示了要构造的例程。这是该代码调用的用于生成页面查看数据的 URL。


清单 10.Wikipedia,第 1 部分
"""
Example Route To Construct:
https://wikimedia.org/api/rest_v1/ +
metrics/pageviews/per-article/ +
en.wikipedia/all-access/user/ +
LeBron_James/daily/2015070100/2017070500 +
"""
import requests
import pandas as pd
import time
import wikipedia
BASE_URL =
"https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/user"
def construct_url(handle, period, start, end):
"""Constructs a URL based on arguments
Should construct the following URL:
/LeBron_James/daily/2015070100/2017070500
"""

urls = [BASE_URL, handle, period, start, end]
constructed = str.join('/', urls)
return constructed
def query_wikipedia_pageviews(url):
res = requests.get(url)
return res.json()
def wikipedia_pageviews(handle, period, start, end):
"""Returns JSON"""
constructed_url = construct_url(handle, period, start,end)
pageviews = query_wikipedia_pageviews(url=constructed_url)
return pageviews

在清单 10 的第 2 部分中,创建了 Wikipedia 句柄,方法是猜测球员的姓名,然后在存在错误时尝试将 "(basketball)" 附加到该 URL。这可以解决大部分问题,而且只有少数姓名/句柄缺失。一个猜测示例是,"LeBron" 作为名字,"James" 作为姓氏。最初采用这种猜测方式的原因是,它与 80% 的 Wikipedia 页面高度匹配,而且节省了逐个查找 URL 的时间。对于不符合此模式的 20% 的姓名,另一种方法(如下所示)可以匹配最初缺失的姓名的 80%。


通过添加 "(basketball)",Wikipedia 可以区分一个流行的姓名与另一个姓名。这一惯例可以处理大部分不匹配的姓名。清单 10 的第 2 部分显示了查找其他姓名的最后一种方法。


清单 10.Wikipedia,第 2 部分
def wikipedia_2016(handle,sleep=0):
"""Retrieve pageviews for 2016"""

print("SLEEP: {sleep}".format(sleep=sleep))
time.sleep(sleep)
pageviews = wikipedia_pageviews(handle=handle,
period="daily", start="2016010100", end="2016123100")
if not 'items' in pageviews:
print("NO PAGEVIEWS: {handle}".format(handle=handle))
return None
return pageviews
def create_wikipedia_df(handles):
"""Creates a Dataframe of Pageviews"""
pageviews = []
timestamps = []
names = []
wikipedia_handles = []
for name, handle in handles.items():
pageviews_record = wikipedia_2016(handle)
if pageviews_record is None:
continue
for record in pageviews_record['items']:
pageviews.append(record['views'])
timestamps.append(record['timestamp'])
names.append(name)
wikipedia_handles.append(handle)
data = {
"names": names,
"wikipedia_handles": wikipedia_handles,
"pageviews": pageviews,
"timestamps": timestamps
}
df = pd.DataFrame(data)
return df def create_wikipedia_handle(raw_handle):
"""Takes a raw handle and converts it to a wikipedia handle"""
wikipedia_handle = raw_handle.replace(" ", "_")
return wikipedia_handle
def create_wikipedia_nba_handle(name):
"""Appends basketball to link"""
url = " ".join([name, "(basketball)"])
return url

在清单 10 的第 3 部分中,通过访问一个球员名单来帮助猜测句柄。这部分代码针对本文前面收集的整个 NBA 名单来运行上面给出的匹配代码。


清单 10.Wikipedia,第 3 部分
def wikipedia_current_nba_roster():
"""Gets all links on wikipedia current roster page"""
links = {}
nba = wikipedia.page("List_of_current_NBA_team_rosters")
for link in nba.links:
links[link] = create_wikipedia_handle(link)
return links
def guess_wikipedia_nba_handle(data="data/nba_2017_br.csv"):
"""Attempt to get the correct wikipedia handle"""
links = wikipedia_current_nba_roster()
nba = pd.read_csv(data)
count = 0
verified = {}
guesses = {}
for player in nba["Player"].values:
if player in links:
print("Player: {player}, Link: {link} ".format(player=player,
link=links[player]))
print(count)
count += 1
verified[player] = links[player] #add wikipedia link
else:
print("NO MATCH: {player}".format(player=player))
guesses[player] = create_wikipedia_handle(player)
return verified, guesses

在清单 10 的第 4 部分中,整段脚本运行时使用该 CSV 文件作为输入,使用另一个 CSV 文件作为输出。请注意,我们使用了 Wikipedia Python 库来检查页面,以便在最终匹配结果中查找单词 "NBA"。这是对使用多种猜测技术均告失败的页面执行的最后一次检查。所有这些启发方法的结果都是获取 NBA 球员的 Wikipedia 句柄的相对可靠方式。您可以想象对其他体育项目使用类似技术。


清单 10.Wikipedia,第 4 部分
def validate_wikipedia_guesses(guesses):
"""Validate guessed wikipedia accounts"""
verified = {}
wrong = {}
for name, link in guesses.items():
try:
page = wikipedia.page(link)
except (wikipedia.DisambiguationError, wikipedia.PageError) as error:
#try basketball suffix
nba_handle = create_wikipedia_nba_handle(name)
try:
page = wikipedia.page(nba_handle)
print("Initial wikipedia URL Failed: {error}".format(error=error))
except (wikipedia.DisambiguationError, wikipedia.PageError) as error:
print("Second Match Failure: {error}".format(error=error))
wrong[name] = link
continue
if "NBA" in page.summary:
verified[name] = link
else:
print("NO GUESS MATCH: {name}".format(name=name))
wrong[name] = link
return verified, wrong
def clean_wikipedia_handles(data="data/nba_2017_br.csv"):
"""Clean Handles"""
verified, guesses = guess_wikipedia_nba_handle(data=data)
verified_cleaned, wrong = validate_wikipedia_guesses(guesses)
print("WRONG Matches: {wrong}".format(wrong=wrong))
handles = {**verified, **verified_cleaned}
return handles
def nba_wikipedia_dataframe(data="data/nba_2017_br.csv"):
handles = clean_wikipedia_handles(data=data)
df = create_wikipedia_df(handles)
return df
def create_wikipedia_csv(data="data/nba_2017_br.csv"):
df = nba_wikipedia_dataframe(data=data)
df.to_csv("data/wikipedia_nba.csv")if __name__ == "__main__":
create_wikipedia_csv()
抓取 NBA 球员的 Twitter 互动数据

现在您需要使用 Twitter 库,以便可以下载 NBA 球员的推文。清单 11 的第 1 部分给出了使用此代码的 API。该 Twitter API 比下面给出的简单脚本更高级。这是使用经过多年开发的第三方库的一个优势。


清单 11.Twitter 提取元数据,第 1 部分
"""
Get status on Twitter
df = stats_df(user="KingJames")
In [34]: df.describe()
Out[34]:
favorite_count retweet_count
count 200.000000 200.000000
mean 11680.670000 4970.585000
std 20694.982228 9230.301069
min 0.000000 39.000000
25% 1589.500000 419.750000
50% 4659.500000 1157.500000
75% 13217.750000 4881.000000
max 128614.000000 70601.000000
In [35]: df.corr()
Out[35]:
favorite_count retweet_count
favorite_count 1.000000 0.904623
retweet_count 0.904623 1.000000
"""
import time
import twitter
from . import config
import pandas as pd
import numpy as np
from twitter.error import TwitterError
def api_handler():
"""Creates connection to Twitter API"""

api = twitter.Api(consumer_key=config.CONSUMER_KEY,
consumer_secret=config.CONSUMER_SECRET,
access_token_key=config.ACCESS_TOKEN_KEY,
access_token_secret=config.ACCESS_TOKEN_SECRET)
return api
def tweets_by_user(api, user, count=200):
"""Grabs the "n" number of tweets.Defaults to 200"""
tweets = api.GetUserTimeline(screen_name=user, count=count)
return tweets

在下一节中,将提取推文并将它转换为一个 pandas 数据帧,该帧将这些值存储为中值。这是一种通过仅存储重要的值(比如一组数据的中值)来压缩数据的卓越技术。中值是一个有用的指标,因为它能够可靠地防止异常值。


清单 11.Twitter 提取元数据,第 2 部分
def stats_to_df(tweets):
"""Takes twitter stats and converts them to a dataframe"""
records = []
for tweet in tweets:
records.append({"created_at":tweet.created_at,
"screen_name":tweet.user.screen_name,
"retweet_count":tweet.retweet_count,
"favorite_count":tweet.favorite_count})
df = pd.DataFrame(data=records)
return df
def stats_df(user):
"""Returns a dataframe of stats"""
api = api_handler()
tweets = tweets_by_user(api, user)
df = stats_to_df(tweets)
return df
def twitter_handles(sleep=.5,data="data/twitter_nba_combined.csv"):
"""yield handles"""
nba = pd.read_csv(data)
for handle in nba["twitter_handle"]:
time.sleep(sleep) #Avoid throttling in twitter api
try:
df = stats_df(handle)
except TwitterError as error:
print("Error {handle} and error msg {error}".format(
handle=handle,error=error))
df = None
yield df
def median_engagement(data="data/twitter_nba_combined.csv"):
"""Median engagement on twitter"""
favorite_count = []
retweet_count = []
nba = pd.read_csv(data)
for record in twitter_handles(data=data):
print(record)
#None records stored as Nan value
if record is None:
print("NO RECORD: {record}".format(record=record))
favorite_count.append(np.nan)
retweet_count.append(np.nan)
continue
try:
favorite_count.append(record['favorite_count'].median())
retweet_count.append(record["retweet_count"].median())
except KeyError as error:
print("No values found to append {error}".format(error=error))
favorite_count.append(np.nan)
retweet_count.append(np.nan)

print("Creating DF")
nba['twitter_favorite_count'] = favorite_count
nba['twitter_retweet_count'] = retweet_count
return nba
def create_twitter_csv(data="data/nba_2016_2017_wikipedia.csv"):
nba = median_engagement(data)
nba.to_csv("data/nba_2016_2017_wikipedia_twitter.csv")
创建高级可视化

通过增加社交媒体数据,可以创建包含更多洞察的更高级图表。图 8 是一个称为热图的高级图表。 它显示了一个经过压缩的关键特性集合的关联度。这些特性是用于进一步执行机器学习(比如聚类)的良好基础要素(参阅本系列的第 1 部分)。可以自行使用此数据试验不同的聚类配置。


图 8. NBA 球员代言、社会力量、场上表现、团队估值的关联度热图:2016-17 赛季


清单 12 提供了创建这个关联度热图的代码。


清单 12.关联度热图
endorsements = pd.read_csv("../data/nba_2017_endorsement_full_stats.csv")
plt.subplots(figsize=(20,15))
ax = plt.axes()
ax.set_title("NBA Player Endorsement, Social Power, On-Court Performance, Team Valuation Correlation Heatmap: 2016-2017 Season")
corr = endorsements.corr()
sns.heatmap(corr,
xticklabels=corr.columns.values,
yticklabels=corr.columns.values, cmap="copper")

清单 13 给出了一个使用对数标尺和一个特殊色彩图创建的彩色热图。这是明确对比每个单元的一个实用诀窍。对数标尺是一种显示相对变化与实际变化的转换。在值存在很大数量级的差异时—例如,10 与 1000 万,通常会采用对数标尺这种绘图技术。显示相对变化与实际变化,会让图表更清晰地传达某种信息。通常,图表是在线性标尺(一条直线)中显示的。对数标尺(对数线)在绘图时会减小幂数(这意味着它会扁平化)。


清单 13.关联度热图高级版
from matplotlib.colors import LogNorm
plt.subplots(figsize=(20,15))
pd.set_option('display.float_format', lambda x: '%.3f' % x)
norm = LogNorm()
ax = plt.axes()
grid = endorsements.select_dtypes([np.number])
ax.set_title("NBA Player Endorsement, Social Power, On-Court Performance, Team Valuation Heatmap: 2016-2017 Season")
sns.heatmap(grid,annot=True, yticklabels=endorsements["PLAYER"],fmt='g', cmap="Accent", cbar=False, norm=norm)
图 9. NBA 球员代言、社会力量、场上表现、团队估值的热图:2016-17 赛季


最后一个图表使用 R 语言在 ggplot 中创建一个多维图表。清单 14 和图 10 中显示了此操作。R 中的原生 ggplot 库是一个强大且独特的图表库,能使用颜色、大小、刻面和形状创建多个维度。R 中的 ggplot 库值得花时间亲自探索。
清单 14.基于 R 的高级 ggplot
ggplot(nba_players_stats, aes(x=WINS_RPM, y=PAGEVIEWS,
color=SALARY_MILLIONS, size=TWITTER_FAVORITE_COUNT)) + geom_point() +
geom_smooth() + scale_color_gradient2(low = "blue", mid = "grey", high =
"red", midpoint = 15) + labs(y="Wikipedia Median Daily Pageviews", x="WINS
Attributed to Player( WINS_RPM)", title = "Social Power NBA 2016-2017
Season: Wikipedia Daily Median Pageviews and Wins Attributed to Player
(Adusted Plus Minus)") +
geom_text(vjust="inward",hjust="inward",color="black",size=4,check_overlap
= TRUE, data=subset(nba_players_stats, SALARY_MILLIONS > 25 | PAGEVIEWS
> 4500 | WINS_RPM > 15), aes(WINS_RPM,label=PLAYER )) +
annotate("text", x=8, y=13000, label= "NBA Fans Value Player Skill More
Than Salary, Points, Team Wins or Another Other Factor?", size=5) +
annotate("text", x=8, y=11000, label=paste("PAGEVIEWS/WINS Correlation:
28%"),size=4) + annotate("text", x=8, y=10000,
label=paste("PAGEVIEWS/Points Correlation 44%"),size=4) + annotate("text",
x=8, y=9000, label=paste("PAGEVIEWS/WINS_RPM Correlation: 49%"),size=4,
color="red") + annotate("text", x=8, y=8000,
label=paste("SALARY_MILLIONS/TWITTER_FAVORITE_COUNT: 24%"),size=4)
图 10. NBA 球员的社交媒体影响力:2016-17 赛季


结束语

在本系列的第 1 部分中,您学习了机器学习的基本知识,并使用无监督聚类技术探索了球队的估值。这项数据科学工作中利用的工具包括 Python 和使用 Jupyter Notebook 创建的高级图表。


在第 2 部分中,您探索了球员以及他们与社交媒体、影响力、薪资和场上表现的关系。在一个 Jupyter Notebook 中创建了许多高级图表,但也编写了一些简短的 R 代码。


一些暴露的或需要进一步调查的问题(它们可能是错误的假设)包括:



支付给球员的薪资不是获胜与否的最佳预测指标。
球迷更喜欢与高技能球员(而不是高收入球员)进行互动。
代言收入与归功于球员的球队胜利次数相关,所以他们可能会谨慎选择转到哪个球队。
现场观看比赛的观众和通过社交媒体进行互动的观众之间似乎存在区别。现场观众会比较在乎其所关注球队的场上发挥。

您可以做更多工作。可以尝试对 GitHub 中提供的数据集同时应用监督和无监督机器学习。我已将该数据集上传到 Kaggle 上,供您在这个项目中进行试验。

分享给朋友:
您可能感兴趣的文章:
随机阅读: