简介面向学生、教育研究者与职场人士这份数据分析案例以2024年QS世界大学排名为核心题材提供完整的CSV数据集与可运行代码解决高校排名数据“怎么看、怎么用”的问题适合数据分析入门者结合真实教育场景练习可视化技能。压缩包共3个文件仅约1MB包含多维度排名数据、可直接浏览的HTML可视化报告以及支持二次修改的IPython Notebook脚本覆盖数据清洗、指标提取、图表绘制和结论输出等环节数据集涵盖大学名称、排名、学术声誉、雇主声誉、师生比、教师人均论文引用数、国际化师生比例等字段。已有180人学习下载案例代码逻辑清晰、步骤完整便于按步骤复现。通过该案例可分析各指标对排名的综合影响识别不同国家/地区的高校优势与国际化程度差异归纳顶尖大学的共性特征也可将分析方法迁移至商业分析、人才评估等领域为择校、高校改进或校企合作提供数据参考。1. 为什么拿QS 2024排名练手比单纯读教程更接近真实数据分析2024年QS世界大学排名的评估体系做了近20年最大的一次调整学术声誉权重从40%下调到30%新增了就业成果、国际研究网络和可持续发展三项指标。这意味着任何基于旧版指标做出来的可视化案例放到这份数据上都会得出误导性结论。对想入门数据分析的人来说这份数据集是少有的“能直接搜到、字段完整、规模适中”的样本——全球约1500所高校的评分与排名构成一张关系网络既够做pandas预处理练习又能承载多维可视化。本文直接给出可复现的读数、清洗、可视化和验证路径以及我在处理排名类数据时踩过的几个坑。2. 从CSV到可分析DataFrameQS 2024数据集解析与Pandas清洗任何“数据分析案例”的第一步都不是画图而是弄清楚文件包里到底有什么。以常见的QS排名数据发布格式为例解压后一般是两个CSV一个带具体指标学术声誉、雇主声誉、师生比、师均论文引用、国际教师比例、国际学生比例、就业成果、国际研究网络、可持续发展另一个是多年份对照表。先用 pandas 读进来输出来看看。import pandas as pd # 读取主数据集rank_v4.csv 是QS 2024排名的标准导出 df pd.read_csv(rank_v4.csv, encodingutf-8) pd.set_option(display.max_columns, None) print(df.shape) print(df.head(3).T) # 转置便于在窄屏上看到每个字段名这里有两个习惯值得保留display.max_columns设置为 None 防止输出被截断用.T输出前几条记录能让字段名可读性高很多。默认 cp936/GBK 编码的 CSV 在部分 Windows 环境会乱码如果你读出来有问题把encodingutf-8换成encodinggbk或encodinggb18030。QS 2024 的字段里真正需要关注的是这几个字段含义清洗要点rank_display排名显示值存在并列与51-100区间值score综合得分可能缺失需单独处理academic_reputation学术声誉0-100分employer_reputation雇主声誉0-100分faculty_student_ratio师生比评分0-100分international_faculty国际教师比例0-100分2.1 排名列里藏着的三类“脏数据”排名类数据集的第一个坑就是rank_display。它不只是整数并列时是3区间排名时是401-450还有少部分院校显示为N/A。numeric 类型转换在第一时间就会失败。我一般的处理方式是把排名拆成两列import numpy as np def split_rank(rank_str): if pd.isna(rank_str): return np.nan, np.nan r str(rank_str).strip() if - in r: parts r.split(-) return int(parts[0]), int(parts[1]) # 纯数字或3这类带等号的写法 r r.replace(, ).strip() return int(r), int(r) df[rank_min], df[rank_max] zip(*df[rank_display].map(split_rank)) print(df[rank_display].value_counts().head(10))zip(*...)的写法是为了让 map 返回的二元组拆成两列如果单列也能接受df[rank].str.extract(r(\d)-(\d))是更短的替代方案但它对纯数字行会返回空值所以上面这个函数更适合混合格式。区间排名的存在提醒你不要用 rank 列做数值运算它只是序数变量。2.2 缺失值策略排名上的缺失和指标上的缺失不是一回事QS 2024 数据里综合得分score对大部分学校都有值但区间排名的尾部院校很可能没有公布具体得分。处理这类缺失不能简单dropna()因为你要做的可视化很可能是按国家/地区聚合删掉尾部院校会直接改变区域汇总结果。score_missing df[df[score].isna()] print(score_missing[[institution, country, rank_display]].head(10)) # 对分数缺失但排名存在的院校用所在排名区间的中位数填充 df[score_filled] df[score].copy() median_by_band df.groupby(rank_max)[score].transform(median) df[score_filled] df[score_filled].fillna(median_by_band) print(df[score].isna().sum(), df[score_filled].isna().sum())逻辑说明groupby(rank_max)是按排名的上限分档比如401-450这一档所有院校归到同一组取这一组里已有分数值的中位数填充缺失。这样做比全局均值更能保留排名尾部院校的得分梯度。transform(median)返回与原始行数对齐的序列直接传给fillna这是 pandas 文档里推荐的填充范式。2.3 指标列的区间对齐与“分数稀缺”问题QS 的单项指标并不是每个学校都有公开值尤其像国际学生比例这种数据部分院校在统计口径上是缺位的。做可视化之前先确认每列缺失值占比miss_ratio (df.isna().mean() * 100).round(2).sort_values(ascendingFalse) print(miss_ratio[miss_ratio 0])如果某个指标缺失率超过30%要么在图表里明确注明“该维度样本量不足”要么换用其它可用指标做同维度分析。不要用插值强行补出一个并不存在的评分——排名数据的读者往往是学生或院校管理者虚假的补全值会直接毁掉图表的可信度。另一个容易被忽略的点是单位QS 的学术声誉、雇主声誉等指标都是 0-100 的百分制而师生比也换算成了百分制分数因此做雷达图前不需要额外归一化但做散点图的颜色映射时建议先检查各列的 min/max避免图表配色因离群点偏色。3. 用Plotly和Pandas做多维排名可视化代码与参数拆解这一步的核心不是把 matplotlib 的示例改个颜色而是把“比大小”变成“看分布、看关系、看国家差异”。我选用 plotly.express 而不是原生 matplotlib原因是它静态图和交互式 HTML 都能输出对 Jupyter 和文档型交付都友好如果你在离线环境用 pyecharts 也是等价方案参数映射逻辑完全类似。3.1 全球Top 50院校的得分条形图先做一张最简单但信息量足够的图综合得分条形图按排名排序用颜色区分国家。import plotly.express as px top50 df.head(50).copy() top50[institution_short] top50[institution].str.replace(r\s*\(.*?\), , regexTrue) fig px.bar( top50, xscore_filled, yinstitution_short, orientationh, colorcountry, labels{score_filled: 综合得分, institution_short: 院校}, height1200, ) fig.update_layout( yaxis{dtick: 1, autorange: reversed}, title2024 QS Top 50院校综合得分分布, ) fig.write_html(top50_score.html)参数说明orientationh把条形改成水平让 50 个院校名称能完整显示yaxis{dtick: 1}强制每个院校都显示一根刻度线否则 plotly 默认会跳着显示标签autorange: reversed把排名第 1 的院校放在图的最上方。str.replace(r\s*\(.*?\), , regexTrue)这一步是把 “University of Oxford (UK)” 这类名称里的括号后缀去掉让 y 轴标签更干净。生成的 HTML 可以用浏览器直接打开。3.2 国家/地区聚合分析从“看学校”到“看国家”单校排名图很难回答“哪个国家的高等教育表现更好”这类问题。我通常的做法是先按国家聚合出均值和入榜数量再做散点图。这里注意一个细节入榜数量多的小国会把均值拉低因此图里要让气泡大小代表学校数颜色代表中位排名。country_agg ( df.groupby(country) .agg( schools(institution, count), median_score(score_filled, median), mean_score(score_filled, mean), best_rank(rank_min, min), ) .reset_index() ) fig px.scatter( country_agg[country_agg[schools] 3], xmedian_score, ybest_rank, sizeschools, colorcountry, hover_namecountry, labels{median_score: 入榜院校得分中位数, best_rank: 该国最佳排名}, height700, ) fig.update_yaxes(autorangereversed) fig.write_html(country_agg.html)groupby.agg一次性聚合四列避免写四次分组。散点图里 y 轴是排名数值越小越好所以要autorangereversed否则图看起来是一根正常曲线但方向反了。气泡尺寸字段sizeschools默认把面积映射到数值这对数量差异大的数据会有视觉放大效果如果你希望气泡面积差异更温和可以在传入前对 schools 开根号。这段代码跑完后你能清楚地看到“中位得分高 最佳排名靠前 入榜数量多”三者同时成立的经济体有几个而不是单看 Top 10 里谁的学校多。3.3 雷达图比较两所学校的六维指标雷达图适合“同一同学拿到两所学校的 offer怎么用公开数据帮 TA 做选择”的咨询场景。做法是提取两行数据转置成 plotly.graph_objects 需要的格式import plotly.graph_objects as go cols [ academic_reputation, employer_reputation, faculty_student_ratio, citations_per_faculty, international_faculty, international_students, ] comp df[df[institution].isin([University of Oxford, Tsinghua University])].copy() comp comp.set_index(institution)[cols].T fig go.Figure() for school in comp.columns: fig.add_trace(go.Scatterpolar( rcomp[school].fillna(0), thetacols, filltoself, nameschool, )) fig.update_layout( polardict(radialaxisdict(visibleTrue, range[0, 100])), title两校六维指标对比, ) fig.write_html(compare_two_schools.html)go.Scatterpolar的theta顺序就是雷达图的轴顺序如果想突出就业导向可以把“就业成果”加进 cols——QS 2024 新增的就业成果列在官方 CSV 里未必叫employment_outcomes字段名随发行口径而定建议先print(df.columns.tolist())确认。filltoself让两条雷达曲线闭合区域被填充这样很方便地看出哪个学校在哪个维度上“凸出”。需要提醒的是雷达图对六个维度都缺失较多的院校没有参考意义比较前应确认两个学校的这六列缺失值不超过 2 个。3.4 将指标体系做成联合分布的热力图学术声誉与雇主声誉之间到底是不是强相关这是 QS 排名里最常被问的问题。直接用px.density_heatmap看二维直方图比计算相关系数更直观heat df[[academic_reputation, employer_reputation, rank_min]].dropna() fig px.density_heatmap( heat, xacademic_reputation, yemployer_reputation, nbinsx30, nbinsy30, color_continuous_scaleViridis, labels{x: 学术声誉分数, y: 雇主声誉分数}, ) fig.write_html(reputation_heatmap.html)nbinsx30和nbinsy30控制二维直方图的分箱数量箱数太少会把相关结构抹平太多又会显出一格一格的噪声30 对 1500 所学校量级的数据是比较稳妥的默认值。站在图表后面读这个热力图时你会看到右上角区域的点最密集这说明学术声誉和雇主声誉存在明显的正相关但高学术声誉而雇主声誉略低的院校也形成了一条平行于对角线的脊线这部分学校大多是基础学科强校——这是单纯看排名列表发现不了的结论。4. 从图表回到业务排名波次、区域格局与指标权重验证数据可视化的最后一公里是验证“图形结论是否经得起数据检验”。这一步做得好的分析案例最终呈现的会从一个静态图集变成一个可复盘的决策参考。4.1 排名是否真的和综合得分成线性关系先画一张排名-得分散点图并叠加趋势线看两者的关系形态import plotly.express as px df_sub df.dropna(subset[score_filled, rank_min]) fig px.scatter( df_sub, xrank_min, yscore_filled, title2024 QS 排名与综合得分关系, ) fig.add_trace( go.Scatter( xdf_sub[rank_min], ydf_sub[score_filled].rolling(50, centerTrue).mean(), name50校滑动平均, linedict(colorred, width2), ) ) fig.write_html(rank_score_curve.html)rolling(50, centerTrue).mean()计算中心滑动平均红色曲线能把离散点背后的趋势显示出来。实际跑完你会发现排名 1-50 区间里排名每前进一名得分的提升能有好几分而排名 500 名之后几千名的差距对应分数差异可能都不到 1 分。这提示你在分数线附近选校时综合得分的微小浮动并不能真实反映两所学校水平差异。直接读分数曲线而非倒序数排名是鉴别“并列排名”是否合理的关键一步。4.2 区域格局按大洲聚合后观察排名结构的“精英层”与“长尾”只看国家中位数会遮蔽掉国家内部差距。一个更细致的做法是按国家分箱后画箱线图观察得分分布的四分位距import plotly.express as px top_countries df[country].value_counts().head(10).index df_box df[df[country].isin(top_countries)] fig px.box( df_box, xcountry, yscore_filled, colorcountry, titleTop 10 院校数量国家的得分分布, ) fig.write_html(country_box.html)这个箱线图能直观地看出两类国家一类是“金字塔形”中位数低但顶部有极高分院校另一类是“纺锤形”中位数高但头部优势不明显。由此你可以倒推出 QS 在评分体系里的一个隐含特征——它对规模的偏好低于对声誉一致性的偏好院校“短板的长度”比“长板的长度”更能决定总分区间这也是为什么很多综合实力不差但国际学生比例偏低的学校排名常年上不去。4.3 新增指标的引入到底改变了什么一次简单的权重敏感性验证QS 2024 引入了五项新指标网上能搜到的很多旧图都没把这部分算进去。验证方式是把用旧权重学术声誉40%雇主声誉10%算出的排序和新权重排序做对比old_weight_score ( df[academic_reputation] * 0.4 df[employer_reputation] * 0.1 df[faculty_student_ratio] * 0.2 df[citations_per_faculty] * 0.2 df[international_faculty] * 0.05 df[international_students] * 0.05 ).fillna(0) df[old_rank] old_weight_score.rank(ascendingFalse, methodmin) df[rank_shift] df[rank_min] - df[old_rank] print(df.nsmallest(10, rank_shift)[[institution, country, rank_min, rank_shift]])说明这只是用公开旧权重的粗算近似不是 QS 官方回归版本但它能让你直观地看到“哪类学校吃了新权重红利”。rank_shift是排名前移量值为正数表示旧规则下排名比新规则更靠后也就是新规则让它进步了。你会发现国际学生比例高、师生比表现好的学校排名大幅上升而传统声誉导向的学校相对后退——这正是 QS 官方在 2024 年换权重想要达到的导向效果。做这类敏感性分析时请一定在汇报里注明使用的是近似权重避免被读者误认为官方分数。5. 用 Streamlit 把静态图打包成可筛选的QS Rank看板把多张 HTML 图打包成交互式看板分析案例就能直接交付给不熟悉 Python 的同事。轻量方案是 Streamlit它把 pandas、plotly 和筛选组件串起来几十行代码就能做出看板。5.1 最小组件化看板先搭一个带国家筛选、Top N 筛选和指标选择的三组件页面。import streamlit as st import pandas as pd import plotly.express as px st.set_page_config(layoutwide) df pd.read_csv(rank_v4.csv) # 与第二章一致的清洗函数省略直接载入已清洗版本 df load_cleaned_df() country st.multiselect(选择国家, df[country].sort_values().unique()) topn st.slider(显示多少名, min_value10, max_value200, value50) sub df[(df[country].isin(country)) (df[rank_min] topn)] col1, col2 st.columns(2) with col1: fig_bar px.bar(sub, xscore_filled, yinstitution, orientationh, colorcountry) st.plotly_chart(fig_bar, use_container_widthTrue) with col2: fig_scatter px.scatter(sub, xacademic_reputation, yemployer_reputation, sizescore_filled, colorcountry) st.plotly_chart(fig_scatter, use_container_widthTrue) st.dataframe(sub[[institution, country, rank_display, score_filled]], use_container_widthTrue)页面里需先声明 layout 为 wide右侧图表才能撑满浏览器不设置时 Streamlit 默认居中窄屏下 50 个学校的标签会挤在一起。st.multiselect的默认值建议留空而不是全选全选时isin过滤会退化成原始表看板就失去了筛选意义。sub的筛选条件同时作用于柱状图、散点图和表格因此三者行数天然一致——排查图表与表格不一致问题时先检查这里是否复用了同一个sub。5.2 用三种方法验证图表的正确性交付前最后一步是验证。我一般做三个检查数字抽查从图里读一个点的值和 DataFrame 里对应行的值对比确认没有颜色映射或坐标映射错位。极值检查检查所有图表的 y 轴最大值、最小值是否和数据的 min/max 一致比如综合得分超过 100 就说明数据源或 fillna 逻辑出了问题。交互复核在 Streamlit 里切换筛选条件看每个筛选结果的行数是否等于sub.shape[0]一旦出现图表行数和表格行数不一致多半是dropna的位置不对。做完这三个检查再看板就可以带出去了。看板本身只是呈现层真正的分析结论在第四章的统计与洞察里——把图表当作论证的支撑排名分析才不会变成一张“看着酷但没人敢拍板”的装饰页。本文还有配套的精品资源点击获取 SEO 优化官网定制响应式建站教育培训建站