用Python分析奥运会120年数据:数据清洗与可视化实战
发布时间:2026/9/16 7:24:06 作者:尧图编辑部 阅读量:1,286

1. 项目概述与数据准备1.1 为什么选奥运会数据做分析项目先讲个实在的市面上的数据分析教程用的都是鸢尾花、泰坦尼克号、房价预测这些经典数据集。数据干净、字段简单、拿来练手没问题但做完之后你会有一种“道理我都懂轮到自己就不会”的感觉。奥运会120年历史数据集恰好是那个能打破这种感觉的项目。为什么这么说因为这份数据集的真实程度远超一般教学数据。从1896年雅典第一届现代奥运会到2016年里约奥运会跨越两个甲子的真实比赛记录包含27万名运动员的参赛信息、赛事项目、奖牌归属和各种身体指标。里面的问题多到你想不到有的年份数据缺失、有的项目改名、有的运动员年龄明显异常、不同国家的英文名称不统一……这些脏数据恰恰是实际工作中最常见的场景也正因为如此处理起来反而比“干净”的玩具数据集更有练手价值。这个项目我用Python跑过好几遍每次都有新发现。比如早期奥运会的数据特别稀疏——1896年只有176个人参赛到2016年里约就是11000多人。怎么处理不同年份数据量差异巨大的问题、怎么从历史演变中提取有价值的信息都是能写进简历的真实项目经验。如果你正在学Python数据分析或者准备应聘数据分析岗位这个项目可以完整覆盖你从数据清洗、探索性分析到可视化呈现的全过程。1.2 数据集字段与整体概况先摸清底细。这份120年奥运会数据集最常用的是Kaggle上开源的版本athlete_events.csv包含27万条记录每条记录是一个运动员在某届奥运会上参加某个项目的一条参赛数据。字段设计比较标准整理如下字段含义数据类型备注ID运动员唯一编号int一个运动员有多条记录按参赛项目区分Name运动员姓名str可能重名需结合ID判断Sex性别strM/FAge年龄float存在异常值如1岁、95岁Height身高float单位cm存在大量缺失Weight体重float单位kg存在大量缺失Team代表队str国家或地区注意历史政治变迁NOC国家奥委会代码str用代码统一国家信息Games届数str如2016 SummerYear年份int实际举办年份Season季节strSummer/WinterCity举办城市str便于分析东道主效应Sport运动大项str如BasketballEvent比赛小项str如Basketball Mens BasketballMedal奖牌strGold/Silver/Bronze/NA第一眼看到这个表你可能会觉得字段也不算特别多。但关键问题在于这份数据不是为分析准备的而是从官方记录里直接导出来的。所以缺失值、重复项、格式不统一的问题到处都是。我最开始跑df.info()的时候Age、Height、Weight三个字段的缺失值都相当可观尤其是早期奥运会的记录当时连成绩单都是手写的很多数据根本没有电子化。1.3 分析目标与预期产出拿一份数据上手最忌讳的就是没有目标直接开跑。我给自己定的几个分析方向也是建议你参考的框架第一个方向金牌榜的百年变迁。哪些国家长期霸榜中间有没有被反超的节点这和当时的经济、政治、体育政策有没有对应关系这种时间序列分析最能锻炼数据聚合和趋势解读的能力。第二个方向运动员身体形态的演变。一百多年前的运动员和现在的运动员身高、体重、BMI有没有显著变化不同运动项目的身体门槛是不是越来越分化比如体操运动员和篮球运动员的身高差是不是在拉大这一类分析能用到大量统计方法做出来也很有意思。第三个方向性别平等的进程。女性运动员占比从1896年的0%一路爬升到2016年的45%每个奥运周期增长多少哪些项目最早向女性开放田赛、径赛、水上项目分别是什么时候有女选手的第四个方向东道主效应验证。举办奥运会的国家在当届是不是真的能拿更多奖牌这个效应能持续多久是只有东道主受益还是整个地区都受益这四个方向做下来你会发现Pandas的groupby、merge、pivot_table全都能练到Matplotlib和Seaborn的可视化技巧也会相当熟练。等项目完成你产出的不仅仅是一堆图表而是一条完整的叙事线——用一个半世纪的数据向观众讲述现代奥运会到底经历了什么。2. Python环境搭建与工具选型2.1 一套直接能用的开发环境很多新手在环境配置这一关就放弃了其实完全没必要怕。我用过纯命令行、Anaconda也试过各种IDE最后稳定下来的一套组合是Python 3.9、Jupyter Notebook、Pandas 2.0、Matplotlib、Seaborn、Plotly。如果你还没装Python优先装Anaconda而不是裸的Python解释器。原因很简单Anaconda自带conda包管理器能帮你省掉无数依赖冲突的坑。在Anaconda Prompt里执行conda create -n olympics python3.9 conda activate olympics conda install pandas matplotlib seaborn jupyter创建独立环境这个步骤很多人会跳过但我建议别偷懒。数据分析的项目依赖越来越重今天装个这个库、明天装个那个库全堆在base环境里迟早出问题。独立的conda环境相当于给你的项目隔离了一个干净的容器装坏了直接删掉重建不会连累系统Python。装完基础库之后再确认一下版本。我用到的关键库和版本号如下import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns print(pd.__version__) # 2.0.3 print(np.__version__) # 1.24.3 print(matplotlib.__version__) # 3.7.2 print(sns.__version__) # 12.22.2 为什么选这几个库而不是别的每一层工具的选择都是有理由的不是看哪个火就用哪个。Pandas是绝对的核心。没有它的话处理27万条记录、按年份和国家做分组聚合你得写几十行循环想想就头疼。Pandas的groupby和pivot_table就是为这种结构化数据量身定做的一行代码完成按年份国家奖牌的三层聚合性能也完全扛得住这种量级的数据。Matplotlib负责打底。它是Python可视化的地基seaborn和plotly都是建在它上面的。虽然Matplotlib的默认样式看起来有点老气但它的控制粒度是最细的想调整任何细节都有对应的API。我在实际项目里通常用Matplotlib出最终的论文级图表用Seaborn做探索性的快速绘图两者配合效率很高。Seaborn用来画统计图表。它的优势在于一行代码就能出分布图、热力图、聚类图而且自带配色比Matplotlib默认的好看太多。奥运会数据里大量涉及分组比较不同年份、不同国家的指标对比Seaborn的boxplot、violinplot、barplot用起来非常顺手。Plotly做交互图表。静态图发文章可以但做数据探索和分析演示时交互式图表的效果好得多——鼠标悬浮看具体数值、拖拽缩放、点击图例筛选数据这些功能对分析非常有帮助。尤其是对比120年间各国奖牌数变化交互式折线图能让你快速定位每个波峰波谷对应的年份。2.3 环境踩坑记录这里分享三个我实际踩过的坑希望你能绕开第一个坑是Matplotlib中文乱码。默认情况下Matplotlib不支持中文标签直接画会出现方块。需要在绘图前设置中文字体plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False第二个坑是Pandas版本升级导致的API变化。老版本的df.append()在新版本里被移除了必须改用pd.concat()。很多老教程还在用旧API照抄会报错。所以遇到报错时先看看是不是API变了。第三个坑是行数显示不全。Jupyter里默认只显示部分行分析120年数据时往往需要看全貌pd.set_option(display.max_rows, None) pd.set_option(display.max_columns, None)3. 数据清洗与预处理3.1 加载数据与初步探查数据清洗是数据分析里最枯燥但是最重要的一步。直接上代码import pandas as pd df pd.read_csv(athlete_events.csv) print(df.shape) # (271116, 15) print(df.head(3)) print(df.dtypes) print(df.isnull().sum())输出结果会告诉你几件事总共有27万多条记录、15个字段Age、Height、Weight、Medal四个字段存在缺失值。注意Medal的缺失值并不是“漏填”而是代表该运动员当届未获奖牌——这个语义区别非常重要后续分析时必须把“没有奖牌”和“数据缺失”区分开处理。3.2 异常值与重复值处理很多新手拿到数据就开始画图画完发现结论很怪回过头来才发现是清洗没做好。我整理了几类必须处理的问题。异常年龄问题。原数据里Age最小是1运动会婴儿、最大是95这也不太符合常理。我当时的处理方案是限定Age在10到80之间df df[(df[Age] 10) (df[Age] 80)]要注意的是这里不是简单地删除而是要考虑业务合理性。理论上确实有人接近80岁还在参加射击、帆船等对体能要求较低的项目所以上限设宽一点。但如果只做整体年龄段分析超出这个范围的样本量极少剔除后对结论方向没有实质影响。重复值问题。同一个运动员参加多个项目、多届奥运会在数据里就是多行。行本身并不重复但IDEventGames完全一样的记录可能是真正的重复。可以用duplicated_rows df[df.duplicated(subset[ID, Games, Event], keepFalse)]检查完后如果确认是同一运动员在同一届同一项目的重复录入直接drop_duplicates去掉。身高体重的极端值。我画了箱线图之后发现有些篮球运动员身高在175cm以下这不一定是错误——可能是录入时把英尺和厘米搞混了也可能是女子运动员真实数据。这种情况不能一刀切删除建议结合具体项目判断。如果你只想做整体趋势分析可以按项目分组后用Z-score方法剔除明显超过合理范围的离群点from scipy import stats df[Height_zscore] df.groupby([Sport])[Height].transform( lambda x: (x - x.mean()) / x.std() ) df_height_clean df[abs(df[Height_zscore]) 3]3.3 缺失值的处理策略缺失值处理没有银弹完全取决于字段和分析目标。Medal字段。它的缺失代表“没获奖”是真实信息而非缺失数据。分析获奖率时需要把NaN填充为Nonedf[Medal] df[Medal].fillna(None)Height和Weight字段。缺失比例不低早期奥运会尤其严重。如果你的分析聚焦在身体指标变化趋势可以用整体均值填充但更好的做法是按项目性别分组填充均值——因为不同项目运动员的身高体重差异巨大篮球运动员和马拉松运动员完全不是一个量级。用整体均值填充会把所有项目拉向一个中间值严重扭曲分析结果df[Height] df.groupby([Sport, Sex])[Height].transform(lambda x: x.fillna(x.mean())) df[Weight] df.groupby([Sport, Sex])[Weight].transform(lambda x: x.fillna(x.mean()))Age字段。缺失的比例不算特别大我直接删除缺失行因为年龄是分析性别平等、身体形态演变时的核心变量占比不到2%的缺失样本删掉即可不影响整体结论。3.4 新增特征BMI与奖牌权重清洗完原始字段后可以基于领域知识构造几个新特征让分析更深入。BMI身体质量指数。有身高和体重就能算公式是体重(kg)/身高(m)的平方。BMI在不同运动项目间的差异很有意思——马拉松选手普遍在20以下柔道、举重选手可能会超过28。分析BMI随年份的变化趋势能反映运动科学的发展轨迹df[BMI] df[Weight] / (df[Height] / 100) ** 2奖牌权重。分析国家体育实力时单纯数金牌数量会忽略银牌和铜牌的价值。我自定义了一个加权得分体系金牌3分、银牌2分、铜牌1分。这个不是唯一标准但做时间序列趋势分析时比单纯计数更能反映综合实力。参赛与获奖标志。给每条记录加一个字段标记运动员是否获得奖牌方便后续按获奖率分析。4. 核心分析维度实战4.1 历年参赛规模与项目扩张先看整体趋势。用一行代码聚合出每届奥运会的参赛运动员数量participants_by_year df.groupby([Year, Season])[ID].nunique().reset_index() participants_by_year participants_by_year.rename(columns{ID: Athlete_Count})画出图之后你能直观看到两个显著节点一是二战后参赛人数快速攀升二是1990年代后职业运动员被允许参赛人数再次暴涨。还有一个有趣的现象是受两次世界大战影响1916年、1940年、1944年奥运会直接取消表现在图上就是三个断档期。同一时期比赛小项的数量也在高速膨胀。1896年只有43个小项2016年已经超过300个。这就引出一个问题拿奥运金牌的含金量到底变没变这些问题可以用代码来量化分析events_by_year df.groupby([Year, Season, Sport])[Event].nunique().reset_index() events_by_year events_by_year.rename(columns{Event: Event_Count})4.2 百年金牌榜演变与霸主更替国家奖牌榜是大众最关注的分析也是最能体现数据聚合能力的一个模块。写起来其实直接medals df[df[Medal] ! None] team_medals medals.groupby([Year, NOC, Medal]).size().reset_index(nameCount) team_pivot team_medals.pivot_table(indexNOC, columnsMedal, valuesCount, fill_value0) team_pivot[Total] team_pivot[Gold] * 3 team_pivot[Silver] * 2 team_pivot[Bronze] team_pivot_sorted team_pivot.sort_values(Total, ascendingFalse).head(10)做完这个表之后可以进一步按年份拆分画出几个体育强国奖牌数的时间序列。你会看到明显的阶段性规律早期金牌高度集中在少数几个国家随着奥运会扩张奖牌开始分散化新兴体育国家陆续崛起。这里面还能继续做东道主效应的量化分析# 找到所有东道主国家 hosts df.groupby([Year, City])[NOC].agg(lambda x: x.value_counts().idxmax()).reset_index() # 合并奖牌数据看东道主当届与前后届表现差异4.3 运动员身体形态百年进化运动员身体指标的变化是整个项目中最“硬核”的分析维度因为它能直接反映运动科学的进步。用Seaborn画一个按年代划分的身高分布箱线图你会看到两个趋势整体身高在缓慢上升但不同项目的分化在加剧。篮球、排球运动员的身高增长远快于马拉松、体操项目。df[Decade] (df[Year] // 10) * 10 plt.figure(figsize(14, 6)) sns.boxplot(datadf[df[Sport].isin([Basketball, Gymnastics, Athletics])], xDecade, yHeight, hueSport) plt.title(不同项目运动员身高随年代的变化) plt.xticks(rotation45) plt.show()BMI的分析同样有价值。我分析后发现一个规律投掷类项目的BMI中位数逐年上升而长跑项目的BMI中位数基本稳定或略有下降。这说明运动选材越来越精细——每个项目都在寻找最适合自身特点的身体形态。4.4 性别平等的历史进程女性参与奥运会的历程本身就是一个精彩的数据叙事。直接用性别分组聚合gender_by_year df.groupby([Year, Sex])[ID].nunique().reset_index() gender_pivot gender_by_year.pivot(indexYear, columnsSex, valuesID).fillna(0) gender_pivot[Female_Ratio] gender_pivot[F] / (gender_pivot[M] gender_pivot[F])绘图后你会发现女性参赛比例从1900年的2.2%左右一路波动上升到2016年的45.6%。中间有几个加速节点1912年游泳项目对女性开放1928年田径项目对女性开放1976年之后女性参赛比例稳定爬升。另一个值得关注的视角是女性何时开始获得金牌。第一批女冠军出现在1900年参加的是网球和高尔夫项目——这两个项目在当时被认为是“适合女性”的。这个分析结果能引出很多社会层面的讨论用在项目汇报里面非常出彩。4.5 东道主效应验证关于东道主效应体育界一直有两种争论有人认为主场作战优势巨大有人认为现代交通条件下主场优势已经弱化。我们用120年数据来检验。我的做法是先建立国家队伍成绩的时间序列然后标记出该国举办奥运会的年份比较该年份前后各一届的成绩def get_host_years(df): host_records df.groupby([Year, City]).apply( lambda x: x[NOC].mode().iloc[0] ).reset_index() host_records.columns [Year, City, Host_NOC] return host_records host_years get_host_years(df)分析结果显示东道主效应在多数国家是真实存在的但强度因国而异。体育大国作为东道主时奖牌数涨幅相对有限——因为基数已经很高上升空间有限。体育小国作为东道主的涨幅惊人有时能达到前届的4-5倍。这个结论有一定普适性适用于大多数举办国。5. 数据可视化与报告呈现5.1 静态图表的进阶技巧Matplotlib和Seaborn能画出90%你需要的图表关键是掌握几个细节。配色方案。不要用默认的彩色选一个统一的配色方案会专业很多。我用的是Seaborn自带的deep和pastel配色或者直接用颜色代码自定义。分析金牌榜时我用深金色突出第一名其他名次用渐变色视觉层次一下就出来了。字体设置。中文字体显示问题处理好了之后图表专业感也会提升不少。图例和图标题。标题要包含结论性的信息。比如“女性参赛比例从1900年的2.2%升至2016年的45.6%”这比“女性参赛比例趋势图”传递的信息量大得多。数据分析图表的目的是沟通结论不是展示绘图技术。5.2 交互式图表与演示静态图适合写报告但做分析探索或者向团队展示时Plotly的交互图表效果好得多。画一个各国金牌数随年份变化的交互折线图import plotly.express as px top_countries [USA, URS, GBR, CHN, FRA, ITA, GER, AUS] df_top df[df[NOC].isin(top_countries) (df[Medal] Gold)] gold_by_year df_top.groupby([Year, NOC]).size().reset_index(nameGold_Count) fig px.line(gold_by_year, xYear, yGold_Count, colorNOC, title主要国家历年金牌数量变化) fig.show()鼠标悬浮能看到具体年份和金牌数点击图例可以隐藏/显示某个国家拖拽可以放大某个时间段。用来做分析演示的时候这种交互体验比静态图强很多。5.3 用一组图表讲一个完整故事数据分析产出的不是孤立的图表而是一套叙事。我这个项目最终选了四张图组成核心报告第一张历年参赛人数与女性占比的双轴图。一条线是参赛总人数另一条线是女性参赛比例两个指标放在同一张图上展示现代奥运会从精英赛事到全民盛事、从男性垄断到性别平等的双重变迁。第二张金牌榜前三名的国家变化面积图。用堆叠面积图展示不同时期金牌榜的霸主更替观众一眼能看到“谁在哪个年代统治了奥运赛场”。第三张不同运动项目BMI分布的对比小提琴图。横向对比各项目运动员的BMI中位数和分布形状清晰揭示不同运动的身体条件差异。第四张东道主效应前后对比柱状图。选取几个典型的东道主国家并排展示他们举办前一届、举办当届、举办后一届的奖牌数直观展示主场效应的强度与消退速度。这四张图不用过多文字解释看图的人就能自动得到一个完整的百年奥运史叙事。这就是数据可视化应该达到的效果——不是展示数据而是讲述故事。6. 完整项目代码与执行流程6.1 一键跑通的完整代码前面拆开了讲各个模块这里给一个可以一键跑通的完整脚本框架。我把整个分析流程串起来你把这套代码保存为olympics_analysis.py按顺序运行即可# -*- coding: utf-8 -*- 基于Python的奥运会120年历史数据分析 运行环境Python 3.9 / Pandas 2.0 / Matplotlib 3.7 / Seaborn 12.2 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 1. 数据加载 df pd.read_csv(athlete_events.csv) print(f数据总量: {df.shape[0]} 条, 字段数: {df.shape[1]}) # 2. 数据清洗 df df[(df[Age] 10) (df[Age] 80)] df.drop_duplicates(subset[ID, Games, Event], keepfirst, inplaceTrue) df[Medal] df[Medal].fillna(None) # 3. 缺失值处理按项目性别分组填充 df[Height] df.groupby([Sport, Sex])[Height].transform( lambda x: x.fillna(x.mean())) df[Weight] df.groupby([Sport, Sex])[Weight].transform( lambda x: x.fillna(x.mean())) df.dropna(subset[Age], inplaceTrue) # 4. 特征工程 df[BMI] df[Weight] / (df[Height] / 100) ** 2 df[Decade] (df[Year] // 10) * 10 # 5. 分析1历年参赛规模 participants df.groupby(Year)[ID].nunique().reset_index() participants.columns [Year, Athlete_Count] # 6. 分析2女性参赛比例 gender df.groupby([Year, Sex])[ID].nunique().reset_index() gender_pivot gender.pivot(indexYear, columnsSex, valuesID).fillna(0) gender_pivot[Female_Ratio] gender_pivot[F] / (gender_pivot[M] gender_pivot[F]) # 7. 分析3金牌榜TOP10 medals df[df[Medal] ! None] team_medals medals.groupby([Year, NOC, Medal]).size().reset_index(nameCount) team_pivot team_medals.pivot_table(indexNOC, columnsMedal, valuesCount, fill_value0) team_pivot[Total] team_pivot.get(Gold, 0) * 3 \ team_pivot.get(Silver, 0) * 2 \ team_pivot.get(Bronze, 0) top10 team_pivot.sort_values(Total, ascendingFalse).head(10) # 8. 分析4BMI分布随年代变化抽样以避免绘图过慢 sample df[df[Medal] ! None].sample(min(20000, len(df)), random_state42) # 9. 可视化输出 fig, axes plt.subplots(2, 2, figsize(16, 12)) # 图1参赛规模变化 axes[0, 0].plot(participants[Year], participants[Athlete_Count], markero, linewidth1.5) axes[0, 0].fill_between(participants[Year], participants[Athlete_Count], alpha0.3) axes[0, 0].set_title(历届奥运会参赛运动员数量变化) axes[0, 0].set_xlabel(年份) axes[0, 0].set_ylabel(参赛人数) # 图2女性参赛比例 axes[0, 1].plot(gender_pivot.index, gender_pivot[Female_Ratio] * 100, r-, linewidth2) axes[0, 1].set_title(女性参赛比例历史变化) axes[0, 1].set_xlabel(年份) axes[0, 1].set_ylabel(女性占比 (%)) # 图3金牌榜TOP10 top10_sorted top10.sort_values(Total, ascendingTrue) axes[1, 0].barh(top10_sorted.index, top10_sorted[Total], colorsteelblue) axes[1, 0].set_title(历史金牌总数TOP10国家/地区) # 图4BMI分布 sns.boxplot(datasample[sample[Sport].isin([Basketball, Gymnastics, Swimming])], xDecade, yBMI, hueSport, axaxes[1, 1]) axes[1, 1].set_title(不同项目BMI随年代变化的分布) axes[1, 1].tick_params(axisx, rotation45) plt.tight_layout() plt.savefig(olympics_overview.png, dpi150) plt.show()这段代码整合了前面讲的核心清洗步骤和四类典型分析跑完后会输出四张分析图。建议你在自己的数据集上跑一遍然后根据实际输出结果调整分析参数和可视化细节。6.2 代码性能优化笔记27万条记录不算大但如果不注意写法某些操作也可能变得很慢。分享几个性能优化经验groupbytransform比循环快得多。填充缺失值时用循环按组处理可能要跑几十秒用transform是毫秒级。这个差距在大数据量时会被放大推荐养成用transform的习惯。数据量过大时先采样再可视化。画散点图、箱线图时如果数据点超过5万绘制会比较耗时而且图上的点会叠成一团看不清。先随机采样再画图既保证速度又不影响结论sample_for_plot df.sample(n10000, random_state42)pivot_table是分类汇总利器。相比多次groupby再合并pivot_table能直接按行列同时聚合代码简洁度和执行效率都好很多。6.3 项目扩展方向基础版本做完之后这个项目还能往多个方向扩展加入机器学习预测。用历史数据训练一个模型根据运动员的项目、年龄、身高、体重等指标预测获奖概率。逻辑回归或者随机森林都能达到不错的效果可以对比不同模型的表现。这个扩展方向能让你从数据分析进阶到建模简历上更好看。引入外部数据。把各国GDP、人口、气候等宏观数据融合进来分析体育成绩与国家发展水平的关系。这需要学习数据合并和关联分析也是数据分析实际工作中的高频场景。爬虫扩展。从官方网站抓取2020东京和2024巴黎的最新数据补充到历史数据集中。这部分能锻炼爬虫能力还能让你体验一次从数据采集到分析的全流程。搭建可视化Web应用。用Streamlit或Dash把分析结果包装成一个交互式Web应用让非技术用户也能自己探索数据。这个扩展方向的完成度较高对求职展示很有帮助。7. 常见问题速查与避坑指南7.1 高频问题解决方案数据分析和代码运行过程中有些问题几乎每个做这个项目的人都会遇到。我整理了一个问题速查表按出现频率排序问题原因解决方案导入CSV时报UnicodeDecodeError文件编码不是UTF-8改为pd.read_csv(athlete_events.csv, encodinglatin1)图表中文显示为方块Matplotlib默认字体不含中文设置plt.rcParams[font.sans-serif] [SimHei]负号显示为方块中文字体缺少数学符号同时设置plt.rcParams[axes.unicode_minus] False年份显示成了浮点数有缺失值导致整列转float读取时指定dtype{Year: int}或清洗后再转换奖牌统计翻倍同一运动员参加多个小项按Event去重或明确统计口径内存不足读入后未释放中间变量用del删除不再使用的DataFrame或分块读取这类项目最容易出问题的地方不在代码本身而在于统计口径。比如“某个国家一共拿了多少奖牌”到底按参赛记录数还是按获奖人数数结果差很多。做之前先想清楚口径定义能省掉后面很多返工。7.2 分析结论的可靠性判断这部分是很多自学者容易忽略的。拿到分析结果后不要急着下结论先问自己三个问题结论有没有受到异常值的影响比如分析平均年龄时如果某届混进了一个95岁的运动员均值就会被拉高。建议先看分布图再做统计推断。数据缺失对结论方向有没有系统性影响早期奥运会缺失数据较多直接用不同年份的数据做对比时要意识到缺失本身就是一种偏差。如果某年数据只记录了部分项目那一年算出来的参赛人数天然偏低这个结论不是“当年真的那么少”而是“我们只知道那么多”。相关关系不等于因果关系。比如我们发现人才培养体系完善的国家体育成绩更好但“完善体系”和“好成绩”之间的因果关系并不是单方向的。报告里要避免“因为A所以B”的表述改成“A与B存在正向关联”更严谨。7.3 独家避坑经验最后分享几个我反复碰壁之后才明白的经验这些细节在官方文档里看不到保存清洗后的数据。清洗完数据后先存一份副本df.to_csv(athlete_events_clean.csv, indexFalse)这样做的好处是后续重新打开项目时不用再跑一遍整个清洗流程直接读副本就行。而且万一某个清洗步骤写错了你能从原数据重新来不用从头刷新。分组聚合时关注一下observed参数。新版Pandas中如果分组列是category类型groupby默认只统计被观察到的类别。这个细节不影响大多数人但如果你发现聚合结果少了某个类别不妨检查一下这个参数。记录每一步分析的决定。比如为什么决定删除Age10的数据、为什么用分组均值填充缺失值。这些决策过程会在项目复盘或者面试时成为你的谈资体现出你的数据思维。不要在一个脚本里写所有分析。把项目拆成多个Jupyter Notebook或多个Python脚本每个模块职责单一——数据清洗、分析计算、可视化、报告分开管理。改起来方便定位问题也快到最后做演示时再整合成一个完整报告。8. 项目学习的深层收获做这个数据分析项目最终收获的绝不只是几行代码和几张图表。我在反复调试和思考的过程中真正理解了数据分析岗位的核心能力模型——不是操纵工具而是用数据回答问题的思维能力。面对120年奥运会数据你需要考虑选择分析哪个维度、怎么定义奖牌竞争力、如何处理缺失的历史记录、怎样可视化才能让观众一眼抓住规律这些问题没有标准答案都需要你基于对体育和数据的理解做出判断。如果你想用这个项目来准备面试建议把分析报告做好之后再准备几个延伸问题分析结论的局限性是什么如果数据源换成了其他运动会这套分析流程能用吗如果想引入新的外部数据你打算怎么做把这些问题想透你对该项目的掌控力会明显提升。这个项目后续还可以沿着很多方向继续做下去。我曾尝试把123年截至2024巴黎的数据全部纳入比较不同世纪运动员身体指标的变化速度也尝试构建一个能预测下届奥运会奖牌榜的简易模型。每一次扩展都会发现自己对数据、对体育、对历史的理解更深了一层。如果你也做出了属于自己的版本欢迎分享你发现的有趣规律。