Python大数据实战(十四):足球运动员数据分析------用Pandas+Matplotlib挖掘FIFA数据集
文章目录
- Python大数据实战(十四):足球运动员数据分析------用Pandas+Matplotlib挖掘FIFA数据集
- 前言
- 一、项目概览与技术路线
-
- [1.1 数据来源](#1.1 数据来源)
- [1.2 数据集字段概览](#1.2 数据集字段概览)
- [1.3 分析目标](#1.3 分析目标)
- [1.4 技术栈与工具](#1.4 技术栈与工具)
- 二、环境准备与数据加载
-
- [2.1 导入依赖库](#2.1 导入依赖库)
- [2.2 加载数据集](#2.2 加载数据集)
- [2.3 初步查看数据](#2.3 初步查看数据)
- 三、数据清洗
-
- [3.1 缺失值处理](#3.1 缺失值处理)
-
- 问题分析
- [处理 Club_Position 缺失值](#处理 Club_Position 缺失值)
- [3.2 异常值检测](#3.2 异常值检测)
- [3.3 重复值检查](#3.3 重复值检查)
- [3.4 身高体重数据清洗](#3.4 身高体重数据清洗)
-
- [方法一:使用 Pandas 矢量化字符串操作(推荐)](#方法一:使用 Pandas 矢量化字符串操作(推荐))
- [方法二:使用 map/apply 函数](#方法二:使用 map/apply 函数)
- [🐛 错误案例一:类型转换失败](#🐛 错误案例一:类型转换失败)
- [🐛 错误案例二:链式操作中的 SettingWithCopyWarning](#🐛 错误案例二:链式操作中的 SettingWithCopyWarning)
- 四、数据可视化分析
-
- [4.1 身高、体重、评分分布](#4.1 身高、体重、评分分布)
- [4.2 左脚右脚球员数量分析](#4.2 左脚右脚球员数量分析)
- [4.3 俱乐部球员评分 TOP10 分析](#4.3 俱乐部球员评分 TOP10 分析)
-
- 基础版:所有俱乐部的平均评分
- [进阶版:过滤小俱乐部(球员人数 > 20)](#进阶版:过滤小俱乐部(球员人数 > 20))
- [4.4 国家队球员平均评分 TOP10](#4.4 国家队球员平均评分 TOP10)
- [4.5 球员出生日期与数量的关系](#4.5 球员出生日期与数量的关系)
- 五、相关性分析
-
- [5.1 身高与体重的相关性](#5.1 身高与体重的相关性)
- [5.2 身高/体重与评分的关系](#5.2 身高/体重与评分的关系)
- [5.3 相关系数矩阵:找出与评分最相关的指标](#5.3 相关系数矩阵:找出与评分最相关的指标)
- [5.4 年龄与评分的关系------球员的黄金年龄](#5.4 年龄与评分的关系——球员的黄金年龄)
- 六、综合总结
-
- [6.1 核心发现](#6.1 核心发现)
- [6.2 学到的 Pandas 技能](#6.2 学到的 Pandas 技能)
- [6.3 延伸思考](#6.3 延伸思考)
- 七、参考链接
- 下一篇预告
前言
"同样是踢球,为什么梅西的评分能到94,而普通球员只有60多?"
足球是全世界最受欢迎的运动之一,EA Sports每年发布的FIFA系列游戏收录了全球上万名球员的详细数据。这些数据不仅包含球员的基本信息(身高、体重、年龄),还涵盖了数十项技术能力评分------从盘带、射门到防守、扑救,几乎覆盖了足球场上的一切。
说实话,拿到这份包含 17588 名球员、60+ 个字段 的数据集时,我是有点兴奋的。这不就是练 Pandas 数据清洗和分析的绝佳素材吗?本文将从数据加载开始,带你一步步完成:数据清洗、可视化分析、相关性研究,并最终回答几个有趣的问题------哪些俱乐部拥有最高的球员平均评分?身高和体重真的影响球员能力吗?球员的黄金年龄是多少?
一、项目概览与技术路线
1.1 数据来源
本项目使用 FIFA 足球运动员数据集(FullData.csv),包含全球 17588 名球员的详细数据。
1.2 数据集字段概览
数据集共有 60+ 个字段,分为以下几类:
| 类别 | 字段 | 说明 |
|---|---|---|
| 基本信息 | Name, Nationality, Age, Birth_Date | 姓名、国籍、年龄、出生日期 |
| 身体属性 | Height, Weight, Preffered_Foot | 身高、体重、惯用脚 |
| 俱乐部信息 | Club, Club_Position, Club_Kit, Club_Joining, Contract_Expiry | 俱乐部、位置、号码、加盟时间、合同到期 |
| 国家队信息 | National_Position, National_Kit | 国家队位置、号码 |
| 综合评分 | Rating, Work_Rate, Weak_foot, Skill_Moves | 总评分、工作效率、非惯用脚、技术等级 |
| 技术能力 | Ball_Control, Dribbling, Crossing, Short_Pass, Long_Pass 等 | 控球、盘带、传中、短传、长传等 |
| 防守能力 | Marking, Sliding_Tackle, Standing_Tackle, Interceptions | 盯人、铲球、逼抢、抢断 |
| 身体能力 | Acceleration, Speed, Stamina, Strength, Balance, Agility, Jumping | 加速、速度、体力、力量、平衡、敏捷、弹跳 |
| 射门能力 | Shot_Power, Finishing, Long_Shots, Curve, Freekick_Accuracy, Penalties, Volleys | 射门力量、射门精度、远射、弧线、任意球、点球、凌空 |
| 门将能力 | GK_Positioning, GK_Diving, GK_Kicking, GK_Handling, GK_Reflexes | 门将位置感、扑救、踢球、扑球、反应 |
| 心理属性 | Aggression, Reactions, Attacking_Position, Vision, Composure | 攻击性、反应、跑位、视野、镇静 |
1.3 分析目标
#mermaid-svg-S9AY6aKwMfdhKSWs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-S9AY6aKwMfdhKSWs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-S9AY6aKwMfdhKSWs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-S9AY6aKwMfdhKSWs .error-icon{fill:#552222;}#mermaid-svg-S9AY6aKwMfdhKSWs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-S9AY6aKwMfdhKSWs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-S9AY6aKwMfdhKSWs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-S9AY6aKwMfdhKSWs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-S9AY6aKwMfdhKSWs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-S9AY6aKwMfdhKSWs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-S9AY6aKwMfdhKSWs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-S9AY6aKwMfdhKSWs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-S9AY6aKwMfdhKSWs .marker.cross{stroke:#333333;}#mermaid-svg-S9AY6aKwMfdhKSWs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-S9AY6aKwMfdhKSWs p{margin:0;}#mermaid-svg-S9AY6aKwMfdhKSWs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-S9AY6aKwMfdhKSWs .cluster-label text{fill:#333;}#mermaid-svg-S9AY6aKwMfdhKSWs .cluster-label span{color:#333;}#mermaid-svg-S9AY6aKwMfdhKSWs .cluster-label span p{background-color:transparent;}#mermaid-svg-S9AY6aKwMfdhKSWs .label text,#mermaid-svg-S9AY6aKwMfdhKSWs span{fill:#333;color:#333;}#mermaid-svg-S9AY6aKwMfdhKSWs .node rect,#mermaid-svg-S9AY6aKwMfdhKSWs .node circle,#mermaid-svg-S9AY6aKwMfdhKSWs .node ellipse,#mermaid-svg-S9AY6aKwMfdhKSWs .node polygon,#mermaid-svg-S9AY6aKwMfdhKSWs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-S9AY6aKwMfdhKSWs .rough-node .label text,#mermaid-svg-S9AY6aKwMfdhKSWs .node .label text,#mermaid-svg-S9AY6aKwMfdhKSWs .image-shape .label,#mermaid-svg-S9AY6aKwMfdhKSWs .icon-shape .label{text-anchor:middle;}#mermaid-svg-S9AY6aKwMfdhKSWs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-S9AY6aKwMfdhKSWs .rough-node .label,#mermaid-svg-S9AY6aKwMfdhKSWs .node .label,#mermaid-svg-S9AY6aKwMfdhKSWs .image-shape .label,#mermaid-svg-S9AY6aKwMfdhKSWs .icon-shape .label{text-align:center;}#mermaid-svg-S9AY6aKwMfdhKSWs .node.clickable{cursor:pointer;}#mermaid-svg-S9AY6aKwMfdhKSWs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-S9AY6aKwMfdhKSWs .arrowheadPath{fill:#333333;}#mermaid-svg-S9AY6aKwMfdhKSWs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-S9AY6aKwMfdhKSWs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-S9AY6aKwMfdhKSWs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-S9AY6aKwMfdhKSWs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-S9AY6aKwMfdhKSWs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-S9AY6aKwMfdhKSWs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-S9AY6aKwMfdhKSWs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-S9AY6aKwMfdhKSWs .cluster text{fill:#333;}#mermaid-svg-S9AY6aKwMfdhKSWs .cluster span{color:#333;}#mermaid-svg-S9AY6aKwMfdhKSWs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-S9AY6aKwMfdhKSWs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-S9AY6aKwMfdhKSWs rect.text{fill:none;stroke-width:0;}#mermaid-svg-S9AY6aKwMfdhKSWs .icon-shape,#mermaid-svg-S9AY6aKwMfdhKSWs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-S9AY6aKwMfdhKSWs .icon-shape p,#mermaid-svg-S9AY6aKwMfdhKSWs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-S9AY6aKwMfdhKSWs .icon-shape rect,#mermaid-svg-S9AY6aKwMfdhKSWs .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-S9AY6aKwMfdhKSWs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-S9AY6aKwMfdhKSWs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-S9AY6aKwMfdhKSWs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 📊 足球运动员数据集
17588行 × 60+列
🔧 数据清洗阶段
缺失值处理
删除Club_Position为空的行
格式转换
Height/Weight去单位转数值
异常值检测
describe + 箱线图
重复值检查
duplicated验证
📈 可视化分析阶段
身高/体重/评分分布
直方图 + 核密度图
左右脚球员比例
饼图 + 条形图
俱乐部TOP10评分
分组聚合 + 筛选
球员出生月份分布
字符串拆分 + 柱状图
🔬 相关性分析阶段
身高 vs 体重散点图
身高/体重 vs 评分
相关系数矩阵
年龄 vs 评分
年龄段分组分析
💡 结论输出
哪些俱乐部平均评分最高?
球员黄金年龄是多少?
身高体重影响评分吗?
1.4 技术栈与工具
| 工具 | 用途 |
|---|---|
| Pandas | 数据加载、清洗、分组聚合、统计分析 |
| NumPy | 数值计算、类型转换 |
| Matplotlib | 数据可视化(直方图、散点图、饼图、箱线图等) |
| Python 3.x | 编程语言 |
二、环境准备与数据加载
2.1 导入依赖库
python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['KaiTi'] # 指定默认字体为楷体
plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题
⚠️ 踩坑提醒 :如果你使用的是 macOS 或没有安装楷体字体,需要将
'KaiTi'替换为系统支持的中文字体,如'SimHei'(黑体)、'Microsoft YaHei'(微软雅黑)等。
2.2 加载数据集
python
# 读取 CSV 数据文件
player = pd.read_csv('FullData.csv')
2.3 初步查看数据
加载数据后,第一步永远是"看一眼"------确认数据是否完整加载、结构是否符合预期。
python
# 查看前5行和后5行,快速浏览数据格式
player.head()
player.tail()
# 随机采样10条,检查数据质量
player.sample(10)
# 设置显示完整列(避免列被省略号截断)
pd.set_option('display.max_columns', 30)
执行 player.head() 后,你应该能看到类似这样的表格:
| Name | Nationality | Club | Rating | Age | Height | Weight |
|---|---|---|---|---|---|---|
| Lionel Messi | Argentina | FC Barcelona | 94 | 31 | 170cm | 72kg |
| Cristiano Ronaldo | Portugal | Real Madrid | 94 | 33 | 185cm | 80kg |
| Neymar | Brazil | Paris Saint-Germain | 92 | 26 | 175cm | 68kg |
三、数据清洗
真实世界的数据从来不会"开箱即用"。这个数据集虽然整体质量不错,但仍然有几处需要清理。
3.1 缺失值处理
问题分析
python
# 查看各字段数据类型和非空行数
player.info()
输出显示:总共 17588 行,但 National_Position(国家队位置)只有 1075 行,Club_Position(俱乐部位置)是 17587 行。
分析原因:
National_Position缺值是正常的------并非所有球员都入选了国家队Club_Position缺值只有 1 条,需要定位并处理
处理 Club_Position 缺失值
python
# 检查 Club_Position 是否有缺失值
print(player['Club_Position'].isnull().any()) # 输出: True
# 定位缺失值所在行
missing_row = player[player['Club_Position'].isnull()]
print(missing_row[['Name', 'Club', 'Club_Position']])
# 查看缺失值的球员信息
# 删除缺失值(仅1条记录,删除影响极小)
player = player[player['Club_Position'].notnull()]
# 验证删除结果
player.info() # 应显示所有列行数一致
📌 处理原则 :对于
National_Position的缺失值,我们不做删除 ------因为"未入选国家队"本身就是有意义的信息。对于Club_Position的缺失值(仅1条),直接删除不会影响分析结果。
3.2 异常值检测
python
# 使用 describe() 查看各数值列的统计摘要
player.describe()
# 用箱线图可视化检测异常值
player.plot(kind='box', figsize=(20, 8))
plt.title('各数值字段箱线图 - 异常值检测')
plt.xticks(rotation=90)
plt.show()
# 单独查看 Rating(评分)和 Marking(盯人)的箱线图
player[['Rating', 'Marking']].plot(kind='box')
plt.title('评分与盯人能力箱线图')
plt.show()
# 查看 Rating 的详细统计
print(player['Rating'].describe())
箱线图解读 :箱线图的"须"之外的点就是潜在的异常值。比如 Rating 字段,如果出现评分为 0 或超过 100 的数据,就是明显异常。
3.3 重复值检查
python
# 检查是否有完全重复的行
print(player.duplicated().any()) # 输出: False(无重复值)
# 如果有重复值,按以下方式处理:
# # 查看重复数据
# duplicates = player[player.duplicated(keep=False)]
# print(f"发现 {len(duplicates)} 条重复记录")
#
# # 删除重复值,保留第一条
# player.drop_duplicates(keep='first', inplace=True)
本数据集中没有发现重复值,可以直接进入下一步。
3.4 身高体重数据清洗
从数据查看中可以发现,Height 和 Weight 字段的数据带有单位(如 "170cm"、"72kg"),需要去掉单位并转为数值类型。
方法一:使用 Pandas 矢量化字符串操作(推荐)
python
# 使用 .str.replace() 去除单位字符
player['Height'] = player['Height'].str.replace('cm', '')
player['Weight'] = player['Weight'].str.replace('kg', '')
# 查看替换后的数据
player[['Name', 'Height', 'Weight']].head()
# 此时 Height 和 Weight 还是 Object 类型,需要转换为 int
player['Height'] = player['Height'].astype(np.int)
player['Weight'] = player['Weight'].astype(np.int)
# 验证类型转换结果
player.info()
方法二:使用 map/apply 函数
python
# 自定义转换函数
def clean_height(value: str) -> int:
"""去除身高字符串中的 'cm' 单位并转为整数"""
return int(value.replace('cm', ''))
def clean_weight(value: str) -> int:
"""去除体重字符串中的 'kg' 单位并转为整数"""
return int(value.replace('kg', ''))
# 使用 map 应用转换
player['Height'] = player['Height'].map(clean_height)
player['Weight'] = player['Weight'].map(clean_weight)
# 或者使用 lambda 一行搞定
# player['Height'] = player['Height'].map(lambda x: int(x.replace('cm', '')))
# player['Weight'] = player['Weight'].map(lambda x: int(x.replace('kg', '')))
🐛 错误案例一:类型转换失败
python
# ❌ 错误写法:忘记去单位直接转换
player['Height'] = player['Height'].astype(np.int)
# 报错:ValueError: invalid literal for int() with base 10: '170cm'
# ✅ 正确写法:先去单位再转换
player['Height'] = player['Height'].str.replace('cm', '').astype(np.int)
为什么会出错? astype(np.int) 只能转换纯数字字符串(如 "170"),遇到带单位的字符串 "170cm" 会直接报错。必须先剥离非数字字符。
🐛 错误案例二:链式操作中的 SettingWithCopyWarning
python
# ❌ 可能触发警告的写法
subset = player[player['Rating'] > 80]
subset['Height'] = subset['Height'].str.replace('cm', '').astype(int)
# SettingWithCopyWarning: A value is trying to be set on a copy of a slice...
# ✅ 正确写法:使用 .loc 或 .copy()
subset = player[player['Rating'] > 80].copy()
subset['Height'] = subset['Height'].str.replace('cm', '').astype(int)
四、数据可视化分析
4.1 身高、体重、评分分布
python
# 查看统计摘要
print(player['Height'].describe())
print(player['Weight'].describe())
print(player['Rating'].describe())
# 使用直方图查看分布(bins 控制柱子数量)
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# 身高分布直方图
axes[0].hist(player['Height'], bins=15, color='steelblue', edgecolor='white')
axes[0].set_title('足球运动员身高分布')
axes[0].set_xlabel('身高 (cm)')
axes[0].set_ylabel('人数')
# 体重分布直方图
axes[1].hist(player['Weight'], bins=15, color='coral', edgecolor='white')
axes[1].set_title('足球运动员体重分布')
axes[1].set_xlabel('体重 (kg)')
# 评分分布直方图
axes[2].hist(player['Rating'], bins=15, color='seagreen', edgecolor='white')
axes[2].set_title('足球运动员评分分布')
axes[2].set_xlabel('综合评分')
plt.tight_layout()
plt.show()
# 使用核密度图(KDE)查看平滑分布
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
player['Height'].plot(kind='kde', ax=axes[0], color='steelblue')
axes[0].set_title('身高核密度分布')
player['Weight'].plot(kind='kde', ax=axes[1], color='coral')
axes[1].set_title('体重核密度分布')
player['Rating'].plot(kind='kde', ax=axes[2], color='seagreen')
axes[2].set_title('评分核密度分布')
plt.tight_layout()
plt.show()
分析结论:
- 身高主要集中在 170-190cm 之间,呈近似正态分布
- 体重主要集中在 65-85kg,与身高分布高度一致
- 评分呈右偏分布,大多数球员评分在 60-70 分之间,90 分以上是顶级球星
4.2 左脚右脚球员数量分析
python
# 查看惯用脚数据
print(player['Preffered_Foot'].head(10))
# 方法一:使用 groupby + count
g = player.groupby('Preffered_Foot')
foot_counts_groupby = g['Preffered_Foot'].count()
print(foot_counts_groupby)
# 方法二:使用 value_counts(更简洁)
foot_counts = player['Preffered_Foot'].value_counts()
foot_counts.name = '' # 去掉左侧的 Series name 标签
print(foot_counts)
# 绘制饼图
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 饼图 - 显示百分比
foot_counts.plot(kind='pie', autopct='%.2f%%', ax=axes[0],
colors=['#ff9999', '#66b3ff'],
explode=(0, 0.05), # 突出显示右脚
shadow=True)
axes[0].set_title('左右脚球员比例')
axes[0].set_ylabel('')
# 横向条形图 - 显示具体数量
foot_counts.plot(kind='barh', ax=axes[1], color=['#ff9999', '#66b3ff'])
axes[1].set_title('左右脚球员数量')
axes[1].set_xlabel('球员数量')
plt.tight_layout()
plt.show()
分析结论: 右脚球员数量远超左脚球员,比例大约在 75% vs 25% 左右。这是符合足球运动规律的------左脚将本就稀缺,像梅西这样的顶级左脚球员更是凤毛麟角。
4.3 俱乐部球员评分 TOP10 分析
基础版:所有俱乐部的平均评分
python
# 按俱乐部分组,计算平均评分
club_rating = player.groupby('Club')['Rating'].mean()
# 排序取前10
top10_clubs = club_rating.sort_values(ascending=False).head(10)
print("TOP10 俱乐部平均评分:")
print(top10_clubs)
# 绘制横向条形图
top10_clubs.plot(kind='barh', figsize=(10, 6), color='teal')
plt.title('俱乐部球员平均评分 TOP10')
plt.xlabel('平均评分')
plt.gca().invert_yaxis() # 让最高的在上面
plt.show()
进阶版:过滤小俱乐部(球员人数 > 20)
python
# 统计每个俱乐部的平均评分和球员人数
club_stats = player.groupby('Club')['Rating'].agg(['mean', 'count'])
print(club_stats.head(10))
# 筛选球员人数 > 20 的俱乐部,再按平均评分排序取前10
club_stats_filtered = club_stats[club_stats['count'] > 20]
top10_filtered = club_stats_filtered.sort_values('mean', ascending=False).head(10)
print("\n球员人数 > 20 的俱乐部 TOP10:")
print(top10_filtered)
# 可视化
fig, ax = plt.subplots(figsize=(10, 6))
top10_filtered['mean'].plot(kind='barh', ax=ax, color='steelblue')
ax.set_title('俱乐部球员平均评分 TOP10(球员人数 > 20)')
ax.set_xlabel('平均评分')
ax.invert_yaxis()
plt.show()
# 进阶:筛选球员人数 > 30 的俱乐部
club_stats_30 = club_stats[club_stats['count'] > 30]
top10_30 = club_stats_30.sort_values('mean', ascending=False).head(10)
top10_30['mean'].plot(kind='barh', figsize=(10, 6), color='coral')
plt.title('俱乐部球员平均评分 TOP10(球员人数 > 30)')
plt.xlabel('平均评分')
plt.gca().invert_yaxis()
plt.show()
为什么要过滤小俱乐部? 如果一个俱乐部只有 1-2 个球员且评分很高(比如某个小球会签了一位球星),它的"平均评分"就失去了统计意义。设置最低人数门槛可以让分析结果更有说服力。
4.4 国家队球员平均评分 TOP10
python
# 按国籍分组,计算平均评分和球员人数
nation_stats = player.groupby('Nationality')['Rating'].agg(['mean', 'count'])
# 筛选球员人数 > 20 的国家,排序取前10
nation_filtered = nation_stats[nation_stats['count'] > 20]
top10_nations = nation_filtered.sort_values('mean', ascending=False).head(10)
print("球员人数 > 20 的国家队平均评分 TOP10:")
print(top10_nations)
# 可视化
top10_nations['mean'].plot(kind='bar', figsize=(12, 6), color='darkorange')
plt.title('国家队球员平均评分 TOP10(球员人数 > 20)')
plt.xlabel('国家')
plt.ylabel('平均评分')
plt.xticks(rotation=45)
plt.show()
4.5 球员出生日期与数量的关系
python
# 获取出生日期列
print(player['Birth_Date'].head())
# 按 '/' 拆分日期字符串(格式:月/日/年 或 日/月/年)
date_split = player['Birth_Date'].str.split('/', expand=True)
date_split.columns = ['part1', 'part2', 'part3'] # 根据实际格式调整
# 对月份进行分析:统计每个月出生的球员数量
# 假设第1部分是月份
month_counts = date_split[0].value_counts().sort_index()
month_counts.plot(kind='bar', figsize=(10, 5), color='lightseagreen')
plt.title('各月份出生球员数量分布')
plt.xlabel('月份')
plt.ylabel('球员数量')
plt.show()
# 分析高评分球员(Rating >= 80)的出生月份分布
high_rated = player[player['Rating'] >= 80]
date_split_hr = high_rated['Birth_Date'].str.split('/', expand=True)
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
for i, ax in enumerate(axes):
date_split_hr[i].value_counts().sort_index().plot(kind='bar', ax=ax)
ax.set_title(f'评分≥80球员 - 日期第{i+1}部分分布')
plt.tight_layout()
plt.show()
五、相关性分析
5.1 身高与体重的相关性
python
# 散点图:身高 vs 体重
player.plot(kind='scatter', x='Height', y='Weight',
alpha=0.3, figsize=(8, 6), color='steelblue')
plt.title('身高与体重散点图')
plt.xlabel('身高 (cm)')
plt.ylabel('体重 (kg)')
plt.show()
# 计算相关系数
height_weight_corr = player['Height'].corr(player['Weight'])
print(f"身高与体重的相关系数: {height_weight_corr:.4f}")
# 预期输出: ~0.7-0.8(强正相关)
结论: 身高与体重呈强正相关(相关系数约 0.75),这符合常识------更高的球员通常也更重。
5.2 身高/体重与评分的关系
python
# 散点图:身高 vs 评分
player.plot(kind='scatter', x='Height', y='Rating',
alpha=0.3, figsize=(8, 6), color='coral')
plt.title('身高与评分散点图')
plt.xlabel('身高 (cm)')
plt.ylabel('综合评分')
plt.show()
# 相关系数
height_rating_corr = player['Height'].corr(player['Rating'])
weight_rating_corr = player['Weight'].corr(player['Rating'])
print(f"身高与评分的相关系数: {height_rating_corr:.4f}")
print(f"体重与评分的相关系数: {weight_rating_corr:.4f}")
# 预期: 相关性很弱(接近0),说明身高体重不是决定评分的关键因素
重要发现: 身高和体重与综合评分的相关性很弱(通常 < 0.1)。这说明在足球运动中,身高体重并不是决定球员能力的关键------矮个子球员(如梅西 170cm)同样可以成为世界顶级。真正决定评分的是技术能力、速度、视野等。
5.3 相关系数矩阵:找出与评分最相关的指标
python
# 计算全量数值字段的相关系数矩阵
corr_matrix = player.corr()
# 查看相关系数矩阵的类型和形状
print(f"相关系数矩阵类型: {type(corr_matrix)}")
print(f"形状: {corr_matrix.shape}")
# 提取 Rating 列,按相关系数降序排列
rating_corr = corr_matrix['Rating'].sort_values(ascending=False)
print("与评分最相关的 TOP10 指标:")
print(rating_corr.head(10))
print("\n与评分最不相关的 TOP10 指标:")
print(rating_corr.tail(10))
# 可视化 TOP15 相关性
top15 = rating_corr.head(15)
top15.plot(kind='barh', figsize=(10, 8), color='teal')
plt.title('与综合评分相关性最强的 15 个指标')
plt.xlabel('相关系数')
plt.axvline(x=0, color='black', linestyle='-', linewidth=0.5)
plt.show()
分析结论:
- 与评分相关性最强的通常是:
Ball_Control(控球)、Reactions(反应)、Composure(镇静)、Vision(视野)等技术/心理指标 - 门将相关指标(如
GK_*)对非门将球员来说与评分几乎无关 - 身体指标(身高、体重)与评分相关性极低
5.4 年龄与评分的关系------球员的黄金年龄
python
# 散点图:年龄 vs 评分
player.plot(kind='scatter', x='Age', y='Rating',
alpha=0.3, figsize=(8, 6), color='purple')
plt.title('年龄与评分散点图')
plt.xlabel('年龄')
plt.ylabel('综合评分')
plt.show()
# 相关系数
age_rating_corr = player['Age'].corr(player['Rating'])
print(f"年龄与评分的相关系数: {age_rating_corr:.4f}")
# 使用 pd.cut 将年龄分段
age_bins = [10, 16, 22, 28, 33, 45]
age_labels = ['少年队(10-16)', '青年队(16-22)', '当打之年(22-28)', '成熟期(28-33)', '老将队(33-45)']
player['Age_Group'] = pd.cut(player['Age'], bins=age_bins, labels=age_labels)
# 按年龄段分组,计算平均评分
age_rating = player.groupby('Age_Group')['Rating'].mean()
print("\n各年龄段平均评分:")
print(age_rating)
# 用折线图展示年龄段与评分的关系
age_rating.plot(kind='line', marker='o', figsize=(10, 6),
linewidth=2, markersize=10, color='darkred')
plt.title('不同年龄段球员平均评分变化趋势')
plt.xlabel('年龄段')
plt.ylabel('平均评分')
plt.grid(True, alpha=0.3)
plt.show()
结论:球员的黄金年龄在 22-28 岁之间("当打之年"),这是身体状态和技术能力的巅峰期。28 岁后评分开始缓慢下降,但顶级球员在成熟期(28-33)仍能保持高水平。
六、综合总结
6.1 核心发现
| 分析维度 | 关键发现 |
|---|---|
| 左右脚分布 | 右脚球员约占 75%,左脚球员仅 25% |
| 俱乐部评分 | 豪门俱乐部(巴萨、皇马、拜仁等)球员平均评分明显高于中小俱乐部 |
| 国家评分 | 足球强国(巴西、阿根廷、德国、西班牙等)球员平均评分领先 |
| 身高 vs 体重 | 强正相关(r ≈ 0.75),符合生理规律 |
| 身高/体重 vs 评分 | 相关性极弱,说明身体条件不是决定球员能力的关键 |
| 评分关键指标 | 控球、反应、镇静、视野等技术/心理属性与评分相关性最强 |
| 黄金年龄 | 22-28 岁是球员能力的巅峰期 |
6.2 学到的 Pandas 技能
| 技能 | 对应方法/函数 |
|---|---|
| 数据加载与查看 | pd.read_csv(), head(), tail(), sample(), info() |
| 缺失值处理 | isnull(), notnull(), dropna() |
| 数据类型转换 | astype(), str.replace() |
| 字符串处理 | .str.split(), .str.replace() |
| 分组聚合 | groupby(), agg(), mean(), count() |
| 数据排序 | sort_values() |
| 数据筛选 | 布尔索引 df[df['col'] > value] |
| 数据分段 | pd.cut() |
| 相关性分析 | corr(), corrwith() |
| 可视化 | plot(), hist(), scatter(), pie(), barh() |
6.3 延伸思考
这个项目可以进一步深入的方向:
- 聚类分析:使用 K-Means 将球员分为不同类型(速度型、力量型、技术型)
- 价值预测:基于球员属性建立评分预测模型
- 最佳阵容选择:在预算限制下,用优化算法选出性价比最高的 11 人阵容
- 球员对比工具:输入两个球员名字,自动生成多维度对比雷达图
七、参考链接
- Pandas 官方文档 - GroupBy
- Matplotlib 官方文档 - 图表类型
- FIFA 20 Complete Player Dataset on Kaggle
- Python 数据科学手册 - 数据清洗
下一篇预告
下一篇我们将分析银行客户流失数据,使用逻辑回归和决策树构建客户流失预测模型。关注金融风控和机器学习应用的朋友不要错过!
🐾 本文是《Python大数据实战》系列的第 14 篇,往期文章请关注专栏。如果觉得有帮助,欢迎点赞收藏关注三连!