news 2026/9/15 3:38:01

QS2024排名数据分析:Pandas清洗与Plotly可视化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
QS2024排名数据分析:Pandas清洗与Plotly可视化实战

简介:面向学生、教育研究者与职场人士,这份数据分析案例以2024年QS世界大学排名为核心题材,提供完整的CSV数据集与可运行代码,解决高校排名数据“怎么看、怎么用”的问题,适合数据分析入门者结合真实教育场景练习可视化技能。压缩包共3个文件,仅约1MB,包含多维度排名数据、可直接浏览的HTML可视化报告,以及支持二次修改的IPython Notebook脚本,覆盖数据清洗、指标提取、图表绘制和结论输出等环节;数据集涵盖大学名称、排名、学术声誉、雇主声誉、师生比、教师人均论文引用数、国际化师生比例等字段。已有180人学习下载,案例代码逻辑清晰、步骤完整,便于按步骤复现。通过该案例可分析各指标对排名的综合影响,识别不同国家/地区的高校优势与国际化程度差异,归纳顶尖大学的共性特征;也可将分析方法迁移至商业分析、人才评估等领域,为择校、高校改进或校企合作提供数据参考。

1. 为什么拿QS 2024排名练手,比单纯读教程更接近真实数据分析

2024年QS世界大学排名的评估体系做了近20年最大的一次调整:学术声誉权重从40%下调到30%,新增了就业成果、国际研究网络和可持续发展三项指标。这意味着任何基于旧版指标做出来的可视化案例,放到这份数据上都会得出误导性结论。对想入门数据分析的人来说,这份数据集是少有的“能直接搜到、字段完整、规模适中”的样本——全球约1500所高校的评分与排名构成一张关系网络,既够做pandas预处理练习,又能承载多维可视化。本文直接给出可复现的读数、清洗、可视化和验证路径,以及我在处理排名类数据时踩过的几个坑。

2. 从CSV到可分析DataFrame:QS 2024数据集解析与Pandas清洗

任何“数据分析案例”的第一步都不是画图,而是弄清楚文件包里到底有什么。以常见的QS排名数据发布格式为例,解压后一般是两个CSV:一个带具体指标(学术声誉、雇主声誉、师生比、师均论文引用、国际教师比例、国际学生比例、就业成果、国际研究网络、可持续发展),另一个是多年份对照表。先用 pandas 读进来,输出来看看。

import pandas as pd # 读取主数据集:rank_v4.csv 是QS 2024排名的标准导出 df = pd.read_csv("rank_v4.csv", encoding="utf-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 环境会乱码,如果你读出来有问题,把encoding="utf-8"换成encoding="gbk"encoding="gb18030"

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(ascending=False) 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*\(.*?\)", "", regex=True) fig = px.bar( top50, x="score_filled", y="institution_short", orientation="h", color="country", labels={"score_filled": "综合得分", "institution_short": "院校"}, height=1200, ) fig.update_layout( yaxis={"dtick": 1, "autorange": "reversed"}, title="2024 QS Top 50院校综合得分分布", ) fig.write_html("top50_score.html")

参数说明:orientation="h"把条形改成水平,让 50 个院校名称能完整显示;yaxis={"dtick": 1}强制每个院校都显示一根刻度线,否则 plotly 默认会跳着显示标签;"autorange": "reversed"把排名第 1 的院校放在图的最上方。str.replace(r"\s*\(.*?\)", "", regex=True)这一步是把 “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], x="median_score", y="best_rank", size="schools", color="country", hover_name="country", labels={"median_score": "入榜院校得分中位数", "best_rank": "该国最佳排名"}, height=700, ) fig.update_yaxes(autorange="reversed") fig.write_html("country_agg.html")

groupby.agg一次性聚合四列,避免写四次分组。散点图里 y 轴是排名,数值越小越好,所以要autorange="reversed",否则图看起来是一根正常曲线,但方向反了。气泡尺寸字段size="schools"默认把面积映射到数值,这对数量差异大的数据会有视觉放大效果;如果你希望气泡面积差异更温和,可以在传入前对 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( r=comp[school].fillna(0), theta=cols, fill="toself", name=school, )) fig.update_layout( polar=dict(radialaxis=dict(visible=True, range=[0, 100])), title="两校六维指标对比", ) fig.write_html("compare_two_schools.html")

go.Scatterpolartheta顺序就是雷达图的轴顺序,如果想突出就业导向,可以把“就业成果”加进 cols——QS 2024 新增的就业成果列在官方 CSV 里未必叫employment_outcomes,字段名随发行口径而定,建议先print(df.columns.tolist())确认。fill="toself"让两条雷达曲线闭合区域被填充,这样很方便地看出哪个学校在哪个维度上“凸出”。需要提醒的是,雷达图对六个维度都缺失较多的院校没有参考意义,比较前应确认两个学校的这六列缺失值不超过 2 个。

3.4 将指标体系做成联合分布的热力图

学术声誉与雇主声誉之间到底是不是强相关?这是 QS 排名里最常被问的问题。直接用px.density_heatmap看二维直方图比计算相关系数更直观:

heat = df[["academic_reputation", "employer_reputation", "rank_min"]].dropna() fig = px.density_heatmap( heat, x="academic_reputation", y="employer_reputation", nbinsx=30, nbinsy=30, color_continuous_scale="Viridis", labels={"x": "学术声誉分数", "y": "雇主声誉分数"}, ) fig.write_html("reputation_heatmap.html")

nbinsx=30nbinsy=30控制二维直方图的分箱数量,箱数太少会把相关结构抹平,太多又会显出一格一格的噪声;30 对 1500 所学校量级的数据是比较稳妥的默认值。站在图表后面读这个热力图时,你会看到右上角区域的点最密集,这说明学术声誉和雇主声誉存在明显的正相关,但高学术声誉而雇主声誉略低的院校也形成了一条平行于对角线的脊线,这部分学校大多是基础学科强校——这是单纯看排名列表发现不了的结论。

4. 从图表回到业务:排名波次、区域格局与指标权重验证

数据可视化的最后一公里是验证“图形结论是否经得起数据检验”。这一步做得好的分析案例,最终呈现的会从一个静态图集变成一个可复盘的决策参考。

4.1 排名是否真的和综合得分成线性关系

先画一张排名-得分散点图并叠加趋势线,看两者的关系形态:

import plotly.express as px df_sub = df.dropna(subset=["score_filled", "rank_min"]) fig = px.scatter( df_sub, x="rank_min", y="score_filled", title="2024 QS 排名与综合得分关系", ) fig.add_trace( go.Scatter( x=df_sub["rank_min"], y=df_sub["score_filled"].rolling(50, center=True).mean(), name="50校滑动平均", line=dict(color="red", width=2), ) ) fig.write_html("rank_score_curve.html")

rolling(50, center=True).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, x="country", y="score_filled", color="country", title="Top 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(ascending=False, method="min") 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(layout="wide") df = pd.read_csv("rank_v4.csv") # 与第二章一致的清洗函数省略,直接载入已清洗版本 df = load_cleaned_df() country = st.multiselect("选择国家", df["country"].sort_values().unique()) topn = st.slider("显示多少名", min_value=10, max_value=200, value=50) sub = df[(df["country"].isin(country)) & (df["rank_min"] <= topn)] col1, col2 = st.columns(2) with col1: fig_bar = px.bar(sub, x="score_filled", y="institution", orientation="h", color="country") st.plotly_chart(fig_bar, use_container_width=True) with col2: fig_scatter = px.scatter(sub, x="academic_reputation", y="employer_reputation", size="score_filled", color="country") st.plotly_chart(fig_scatter, use_container_width=True) st.dataframe(sub[["institution", "country", "rank_display", "score_filled"]], use_container_width=True)

页面里需先声明 layout 为 wide,右侧图表才能撑满浏览器;不设置时 Streamlit 默认居中,窄屏下 50 个学校的标签会挤在一起。st.multiselect的默认值建议留空而不是全选,全选时isin过滤会退化成原始表,看板就失去了筛选意义。sub的筛选条件同时作用于柱状图、散点图和表格,因此三者行数天然一致——排查图表与表格不一致问题时,先检查这里是否复用了同一个sub

5.2 用三种方法验证图表的正确性

交付前最后一步是验证。我一般做三个检查:

  1. 数字抽查:从图里读一个点的值,和 DataFrame 里对应行的值对比,确认没有颜色映射或坐标映射错位。
  2. 极值检查:检查所有图表的 y 轴最大值、最小值是否和数据的 min/max 一致,比如综合得分超过 100 就说明数据源或 fillna 逻辑出了问题。
  3. 交互复核:在 Streamlit 里切换筛选条件,看每个筛选结果的行数是否等于sub.shape[0],一旦出现图表行数和表格行数不一致,多半是dropna的位置不对。

做完这三个检查再看板就可以带出去了。看板本身只是呈现层,真正的分析结论在第四章的统计与洞察里——把图表当作论证的支撑,排名分析才不会变成一张“看着酷但没人敢拍板”的装饰页。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 3:37:19

llm_wiki:基于LanceDB与MCP的可执行知识操作系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 3:34:29

Claude 3 Sonnet科研接入实战:避开Fable 5.1幻影版本

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 3:34:16

OpenProj 1.4读取旧版MPP文件与数据迁移实战指南

简介&#xff1a;OpenProj 1.4是一款开源的项目管理软件&#xff0c;可替代Microsoft Project&#xff0c;为项目经理、团队成员及个人提供项目计划、资源分配和进度跟踪服务。压缩包为zip格式&#xff0c;共包含26个文件&#xff0c;其中5个JAR程序文件构成软件主体&#xff0…

作者头像 李华
网站建设 2026/9/15 3:33:57

微信小游戏开发实战:Cocos Creator+TS一人工作室高效上线指南

1. 项目概述&#xff1a;为什么一个“一人工作室”能靠微信小游戏跑通商业闭环&#xff1f; “Vibe Gaming 一人工作室微信小游戏开发实战”——这个标题里藏着当下独立开发者最真实也最硬核的生存图谱。它不是讲情怀&#xff0c;不是画饼&#xff0c;而是把“一个人、一台电脑…

作者头像 李华
网站建设 2026/9/15 3:33:55

一人工作室微信小游戏全链路开发实战:原生Canvas+AI协同方案

1. 项目概述&#xff1a;为什么一个“一人工作室”能跑通微信小游戏全流程&#xff1f;“Vibe Gaming”这个名字听起来像一支有十几号人的独立游戏团队&#xff0c;但实际就是我——一个全栈开发者、美术资源协调者、测试员、运营对接人、客服兼财务的单兵作战单位。过去八个月…

作者头像 李华