足球运动员分析
1. 项目背景
当前,足球运动是最受欢迎的运动之一(也可以说没有之一)。
我们的任务,就是在众多的足球运动员中,发现统计一些关于足球运动员的共性,或某些潜在的规律。也是好玩。
2. 数据集描述
数据集包含的是2017年所有活跃的足球运动员。
- Name 姓名
- Nationality 国籍
- National_Position 国家队位置
- National_Kit 国家队号码
- Club 所在俱乐部
- Club_Position 所在俱乐部位置
- Club_Kit 俱乐部号码
- Club_Joining 加入俱乐部时间
- Contract_Expiry 合同到期时间
- Rating 评分
- Height 身高
- Weight 体重
- Preffered_Foot 擅长左(右)脚
- Birth_Date 出生日期
- Age 年龄
- Preffered_Position 擅长位置
- Work_Rate 工作效率
- Weak_foot 非惯用脚使用频率
- Skill_Moves 技术等级
- Ball_Control 控球技术
- Dribbling 盘球(带球)能力
- Marking 盯人能力
- Sliding_Tackle 铲球
- Standing_Tackle 逼抢能力
- Aggression 攻击能力
- Reactions 反击
- Attacking_Position 攻击性跑位
- Interceptions 抢断
- Vision 视野
- Composure 镇静
- Crossing 下底传中
- Short_Pass 短传
- Long_Pass 长传
- Acceleration 加速度
- Speed 速度
- Stamina 体力
- Strength 强壮
- Balance 平衡
- Agility 敏捷度
- Jumping 跳跃
- Heading 投球
- Shot_Power 射门力量
- Finishing 射门
- Long_Shots 远射
- Curve 弧线
- Freekick_Accuracy 任意球精准度
- Penalties 点球
- Volleys 凌空能力
- GK_Positioning 门将位置感
- GK_Diving 扑救能力
- GK_Kicking 门将踢球能力
- GK_Handling 扑球脱手几率
- GK_Reflexes 门将反应度
3. 程序实现
导入相关的库
导入需要的库,同时,进行一些初始化的设置。
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt# 支持中文
mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"] = False
3.1 加载相关的数据集
- 加载相关的数据集(注意原数据集中是否存在标题),并查看数据的大致情况。
- 可以使用head / tail,也可以使用sample。
- 列没有显式完整,我们需要进行设置。(pd.set_option)
data = pd.read_csv("FullData.csv")
# data.head()
# data.tail()
# data.sample(5)
# 设置最大显示的列数。
pd.set_option("max_columns", 100)
data.head()
3.2 数据探索与清洗
缺失值处理
- 通过info查看缺失值信息(以及每列的类型信息)。
- 可以通过isnull, any, dropna,fillna等方法结合使用,对缺失值进行处理。
# data.info()
# 查看缺失值的记录
# data[data["Club_Position"].isnull()]
data = data[data["Club_Position"].notnull()]
异常值处理
- 通过describe查看数值信息。
- 可配合箱线图辅助。
# data.describe()
# data.plot(kind="box")
data["Age"].plot(kind="box")
重复值处理
- 使用duplicate检查重复值。可配合keep参数进行调整。
- 使用drop_duplicate删除重复值。
# data.drop_duplicates()
# data.duplicated().any()
np.sum(data.duplicated())
将身高与体重处理成数值类型,便于分析。
# data.head()
t = data.copy()
# 注意:Height本来是str类型,替换掉cm之后,还是str类型,我们需要显式转换为int类型。
# 这里除了使用apply之外,也可以使用map。
# t["Height"] = t["Height"].apply(lambda item: item.replace("cm", "")).astype(np.int32)
# t["Weight"] = t["Weight"].apply(lambda item: item.replace("kg", "")).astype(np.int32)# 我们也可以使用字符串的矢量化计算进行数据的转换。
# t["Height"] = t["Height"].str.replace("cm", "").astype(np.int32)
# t["Weight"] = t["Weight"].str.replace("kg", "").astype(np.int32)data["Height"] = data["Height"].str.replace("cm", "").astype(np.int32)
data["Weight"] = data["Weight"].str.replace("kg", "").astype(np.int32)
运动员的身高,体重,评分信息分布。
# 直方图,离散化的显示。
# data["Rating"].plot(kind="hist")
# 核密度图(概率密度)
data[["Height", "Weight", "Rating"]].plot(kind="kde")
左脚与右脚选手在数量上是否存在偏差?
# 第一种方式
# g = data.groupby("Preffered_Foot")
# g["Preffered_Foot"].count().plot(kind="bar")
# 第二种方式
# g = data.groupby("Preffered_Foot")
# g.size().plot(kind="bar")
# 第三种方式
data["Preffered_Foot"].value_counts().plot(kind="bar")
从球员平均评分上考虑,拥有top10评分能力的俱乐部 / 国家。【超过20人】
# 俱乐部的统计
# g = data.groupby("Club")
# g["Rating"].mean().sort_values(ascending=False)
# r = g["Rating"].agg(["mean", "count"])
# r = r[r["count"] >= 20]
# r.sort_values("mean", ascending=False).head(10).plot(kind="bar")# 国家队的统计
g = data.groupby("Nationality")
# g["Rating"].mean().sort_values(ascending=False)
r = g["Rating"].agg(["mean", "count"])
r = r[r["count"] >= 20]
r.sort_values("mean", ascending=False).head(10).plot(kind="bar")
哪个俱乐部拥有更多忠心的球员(5年及以上)?
data.sample()join_year = data["Club_Joining"].apply(lambda item: int(item.split("/")[-1]))
year = 2017 - join_year
# 选择(过滤)所有加入俱乐部时间大于等于5年的球员。
# 注意,同时需要过滤自由身的球员。(Free Agents)
t = data[(year >= 5) & (data["Club"] != "Free Agents")]
t["Club"].value_counts()
足球运动员是否是出生年月相关?
- 全体运动员
- 知名运动员(80分及以上)
# 全体运动员
# expand 参数: 可以将字符串矢量化计算,切分之后的结果进行展开,展开成DataFrame类型。(默认为list列表类型)
# t = data["Birth_Date"].str.split("/", expand=True)
# t[2].value_counts().plot(kind="bar")
# t[0].value_counts().plot(kind="bar")
# t[1].value_counts().plot(kind="bar")# 80分以上(知名运动员)
t = data[data["Rating"] >= 80]
t = t["Birth_Date"].str.split("/", expand=True)
t[0].value_counts().plot(kind="bar")
足球运动员号码是否与位置相关?
# 去掉替补球员与预备队球员。
t = data[(data["Club_Position"] != "Sub") & (data["Club_Position"] != "Res")]
g = t.groupby(["Club_Position", "Club_Kit"])
g.size().sort_values(ascending=False)
身高与体重是否具有相关性?
# data.plot(kind="scatter", x="Height", y="Weight")
# data.plot(kind="scatter", x="Height", y="Rating")
# data.plot(kind="scatter", x="Rating", y="Reactions")
哪些指标对评分的影响最大?
data.corr()
假设我们不清楚后2列的具体含义是什么,分析该标题可能的含义。
g = data.groupby("Club_Position")
g["GK_Handling"].mean().sort_values(ascending=False).plot(kind="bar")
年龄与评分具有怎样的关系?
data.plot(kind="scatter", x="Age", y="Rating")
t = data.copy()
# 对一个数组进行切分,可以将连续值变成离散值。
# bins 指定区间数量(桶数)。bins如果为int类型,则进行等分。
# 此处的区间边界与直方图略有不同。(前开后闭)
# pd.cut(t["Age"], bins=4)
# 如果需要进行区间的不等分,则可以将bins参数指定为数组类型。
# 数组来指定区间的边界。
min_, max_ = t["Age"].min() - 0.5, t["Age"].max()
# pd.cut(t["Age"], bins=[min_, 20, 30, 40, max_])
# pd.cut 默认显示的内容为区间的范围,如果我们希望自定义内容(每个区间显示的内容),可以通过labels参数
# 进行指定。
t["Age"] = pd.cut(t["Age"], bins=[min_, 20, 30, 40, max_], labels=["弱冠之年", "而立之年","不惑之年", "知天命"])
g = t.groupby("Age")
g["Rating"].mean().plot(kind="line", marker="o", xticks=[0, 1, 2, 3])