Python大数据实战(十四):足球运动员数据分析——用Pandas+Matplotlib挖掘FIFA数据集

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 分析)
    • [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 身高体重数据清洗

从数据查看中可以发现,HeightWeight 字段的数据带有单位(如 "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 延伸思考

这个项目可以进一步深入的方向:

  1. 聚类分析:使用 K-Means 将球员分为不同类型(速度型、力量型、技术型)
  2. 价值预测:基于球员属性建立评分预测模型
  3. 最佳阵容选择:在预算限制下,用优化算法选出性价比最高的 11 人阵容
  4. 球员对比工具:输入两个球员名字,自动生成多维度对比雷达图

七、参考链接

  1. Pandas 官方文档 - GroupBy
  2. Matplotlib 官方文档 - 图表类型
  3. FIFA 20 Complete Player Dataset on Kaggle
  4. Python 数据科学手册 - 数据清洗

下一篇预告

下一篇我们将分析银行客户流失数据,使用逻辑回归和决策树构建客户流失预测模型。关注金融风控和机器学习应用的朋友不要错过!


🐾 本文是《Python大数据实战》系列的第 14 篇,往期文章请关注专栏。如果觉得有帮助,欢迎点赞收藏关注三连!

相关推荐
疋瓞2 小时前
python和C++对比(1)_数据类型和数据结构
数据结构·c++·python
如此这般英俊3 小时前
手搓Claude Code-第六章 subagent
数据结构·人工智能·python·语言模型·自然语言处理
shushangyun_4 小时前
2026智能采购商城系统选型指南:如何引领企业数字化采购升级
java·大数据·数据库·人工智能·机器学习
foggyprojects4 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
元Y亨H4 小时前
Python - FastAPI 全方位介绍
python·fastapi
error:(6 小时前
【系统与实战双精通】VS Code 调试 ROS2 Python 节点与 Launch 系统指南
android·java·python
weigangwin6 小时前
LlamaIndex 第一次试用:别先写 RAG Demo,先验上下文合同
python·ai·agent·rag·检索·llamaindex·观测性
省四收割者6 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
ai生成式引擎优化技术7 小时前
从知识连接到智能组织:WSaiOS认知网络的理论框架与系统设计摘要
网络·python·plotly·django·pygame
用户8356290780517 小时前
使用 Python 保护和取消保护 Excel 工作表和工作簿
后端·python