Python合并四大世界大学排名数据:pandas清洗与长表构建

发布时间:2026/10/19 12:20:44
Python合并四大世界大学排名数据:pandas清洗与长表构建
简介压缩包内含US News、软科、QS、THE四大权威机构2022年世界大学排名数据以及对应的Python处理脚本方便高校师生、留学申请者或数据分析初学者对比院校表现、了解排名指标差异。压缩包总大小13.21MB共15个文件包括8个py脚本、4个zip数据包和3个txt说明文件py脚本主要调用requests、BeautifulSoup、pandas等库实现数据抓取与整理txt文件辅助记录链接与名称信息。目前已有1185人在CSDN学习/浏览了该资源。通过这套脚本读者既能获取四个排名机构在2021年11月10日更新的最新结果也可以按需调整抓取逻辑进行二次分析比如对比不同排名体系的评分维度、输出大学名次变化或用于爬虫与数据处理练习是一份兼顾实用与教学的数据分析素材。1. 数据到手不是终点先看懂四套榜单再谈合并做 python 大学排名相关分析或者选校数据采集时最大的坑往往不是爬虫反爬而是 US News、软科、QS、THE 四套榜单的口径完全对不上。同一个高校在软科体系里可能进前一百在 QS 的评价逻辑里却被明显往后压这不是数据错了而是它们各自回答好大学的标准不同。这套 2022 年世界大学排名数据配合 Python 脚本帮你把通常要花一周的数据采集和清洗压缩成半天从四份来源拉平字段、统一学校名、合并成一张可直接分析的表。适合做科研评估、高校对比、行业报告或想用真实数据练手的数据方向开发者。下面从榜单口径讲起再拆脚本每一段该做什么。2. 四榜口径差异与字段盘点合并前先回答三个问题在打开脚本之前先回答三个问题四份榜单分别排的是什么原始数据里哪些字段必须保留统一主键用中文名还是英文名这三个问题想不清楚后面所有代码都在白忙。本章先把口径差异讲透再用一段 pandas 代码把原始数据的结构摸清。2.1 四大榜单的排名逻辑都在排名但好不一样US News 的指标体系里学术声誉、文献引用和国际合作占的权重很高所以它覆盖的学校数量通常最大更像学术影响力大盘。软科 ARWU 更极端一些它把诺奖、高被引科学家、Nature/Science 论文这类硬科研成果放在最重要位置导致理工科强校在软科体系里往往位次偏高人文社科强校相对吃亏。QS 是四套里最看重声誉的尤其是学术声誉和雇主声誉这两项直接和问卷调查绑定排名会有一定主观印象分。THE 则把教学和研究权重拆得比较平均还专门计算论文引用和国际化程度对一所学校的综合稳定性要求更高。四套榜单的核心差异我整理成了一张表建议合并前先对着它确认自己手里的数据是不是也有这些特征榜单核心衡量点典型数据特征合并时最容易踩的坑US News学术声誉、文献引用、国际合作总分是连续数值覆盖量大合并后往往成为主表其他表都要向它的学校集合看齐软科 ARWU诺奖、高被引科学家、Nature/Science 论文总分浮点数理工科位次偏高用它做趋势分析容易被重理轻文误导QS学术声誉、雇主声誉、师生比单项维度得分齐全学校集合与其他榜重合度较低THE教学、研究、论文引用、国际化部分学科以百分位段呈现缺失值多排名字段可能是文本这张表想说明的不是哪套更权威而是字段设计完全不同。例如 US News 的总分是连续的数值可以直接参与排序THE 的部分学科排名以百分位段给出直接做数值运算会失真QS 的单项维度得分很全但和另外三套的字段没有同名关系。所以你在合并时不能指望得分列自动对齐真正能对齐的只有学校名和排名位次。2.2 原始字段清单抓住学校名、排名、国家三列就够了从资源包里解压出来的通常是 CSV 或 Excel先不用看全部列真正参与合并的核心字段只有三个学校名、排名、国家/地区。其余列比如得分、引用量、师生比都属于分析阶段的扩展字段在合并阶段反而容易造成列冲突。常见字段清单大致如下字段类别常见列名类型合并阶段角色学校标识学校名称 / institution / University字符串主键必须清洗排名排名 / Rank / World Rank整数或混合文本核心指标需转数值得分总分 / Score / Overall浮点或区间备选不在合并时强求地域国家 / 国家/地区 / Country字符串分组维度我见过有人一上来就把所有列都塞进合并逻辑结果两张表因为多了一个引用得分列列名对不上就报错最后还得回到三列模型重来。合并阶段保持精简把扩展列留在各自原始表里等长表建好后再按需关联回去。2.3 动手第一步用 pandas 把四份原始数据读进来做探针拿到资源包后我一般不做任何处理先写一段探针代码把四份数据读进来看一眼行数和列名再动手。这段代码不是正式清洗而是为了让你知道四份表的结构差异到底有多大。import pandas as pd # 资源包解压后建议保持 data/ 目录结构路径按实际情况改 file_map { usnews: data/usnews_2022.csv, arwu: data/arwu_2022.csv, qs: data/qs_2022.csv, the: data/the_2022.csv, } frames {} for key, path in file_map.items(): frames[key] pd.read_csv(path) print(f[{key}] 行x列: {frames[key].shape}) print(f[{key}] 列名: {frames[key].columns.tolist()}\n)这段代码的逻辑很简单把四份数据分别读进内存存到一个字典里然后逐个打印形状和列名。file_map 的作用是给每份数据一个短名字后面所有引用都以 usnews、arwu、qs、the 四个 key 为准。路径建议统一放到 data/ 目录下如果资源包解压后的目录结构不同改 file_map 里的路径字符串即可其他代码不用动。读完后重点看两件事。第一是行数四个表行数往往差异很大说明覆盖范围不同第二是列名同一个字段在四个表里可能叫法完全不同比如国家这一列US News 可能叫 Country软科叫国家QS 叫 Country/RegionTHE 叫 Location。这些差异就是后面列名映射要处理的。如果资源包里不是 CSV 而是 Excel把 read_csv 换成 pd.read_excel(path) 就能跑通但要注意 Excel 多 sheet 时还要带上 sheet_name 参数。3. 用 Python 把四榜合并成一张长表从列映射到透视验证四份原始数据读进来之后接下来的目标不是立刻透视而是先把它们合并成一张规范的长表。这一章是资源包脚本的核心也是你自己复现时最容易改出问题的地方。3.1 合并策略的选型为什么是长表而不是一次 merge四份数据不能直接行拼接因为列不对齐也不能一开始就 pd.merge 成宽表因为学校集合不同merge 完会丢掉大量只在某个榜单里出现的学校。我一般用长表作为唯一中间结果结构是school、source、rank_clean、country 四列每一行代表某学校在某个榜单里的某个排名。选长表而不用宽表的理由有两层。从数据完整性看长表保留了一所学校的多次记录哪个榜单缺了就是缺了不会因为 join 方向选错而静默丢数据。从使用灵活度看长表想透视随时可以 pivot 成宽表想筛选某个榜单直接按 source 过滤而宽表想还原长表反而要 melt多绕一步。我后来处理所有多来源榜单数据都沿用这个策略它能省掉很多返工。3.2 列名映射把四份数据里的学校名统一到一个字段第 2 章探针输出告诉我们四份表的列名各不相同。所以第一步是建一个重命名映射把学校名称InstitutionUniversity这类列统一改成 school把国家CountryLocation统一改成 country。映射表的结构如下# 每份数据里学校所在列名可能不同先做列名映射 rename_map { usnews: {学校名称: school, Country: country}, arwu: {大学名称: school, 国家: country}, qs: {Institution: school, Country/Region: country}, the: {University: school, Location: country}, } for key, cols in rename_map.items(): frames[key] frames[key].rename(columnscols)注意rename_map 里的键值不是固定的要以你实际 print 出来的列名为准这里只是示范结构。rename 本身不会报错——哪怕映射的列不存在pandas 也只是忽略它——但如果你手误写错列名后面 frames[key][school] 会抛 KeyError所以这段跑完最好再打印一次列名确认。统一成 school 和 country 后四份数据就可以走同一套清洗函数了。3.3 学校名归一化与排名清洗列名统一之后真正花时间的是学校名清洗。四份榜单来源不同同一所学校可能出现 The University of Alpha 和 Alpha University 两种写法中文榜单里可能写某高校英文榜单里写 Alpha University还有的后面跟着校区或城市信息比如 Alpha University (Campus A)。这些问题在第一章的代码里集中解决import re def normalize_school(name) - str: 学校名归一化去首尾空格、转小写、移除括号内容 if pd.isna(name): return text str(name).strip().lower() # 去掉括号及其中的内容例如 alpha university (beijing) - alpha university text re.sub(r\(.*?\), , text).strip() return text def clean_rank(value) - int: 排名清洗处理并列标记和区间写法返回最小位次 if pd.isna(value): return None text str(value).strip() # 去掉 后缀取区间 50-52 的起始值 text text.replace(, ).split()[0] if text else try: return int(float(text)) except ValueError: return None for key in frames: frames[key][school] frames[key][school].map(normalize_school) if 排名 in frames[key].columns: frames[key][rank_clean] frames[key][排名].map(clean_rank)normalize_school 做两件事把大小写和首尾空格统一再把括号里的注释去掉。我特意没有直接删掉 university 后缀因为那会把学校专名信息一起丢掉比如 alpha university 和 alpha 在归一化后都变成 alpha合并时反而制造假匹配。真正差异大的名称靠后面一个手工映射表兜底。clean_rank 处理的是排名列里的特殊写法QS 有时把并列排名写成 50US News 偶尔写成 50-52 或 50–52这个函数把 去掉、取区间起始值、再转 int。转换失败返回 None不会让整行崩溃。参数说明re.sub 的第一个参数是匹配括号内容的正则第二个参数是空字符串表示直接删掉匹配部分split()[0] 的用途是截取去 后的第一个连续字符段如果排名文本里还有空格只保留第一个词。把排名区间取起始值的处理方式意味着后续分析默认按最好情况排名计算这一点我通常会在分析时另留一列原始排名文本避免丢掉区间信息。3.4 核心合并堆叠成一张长表并透视验证四份数据都处理出 school、rank_clean、country 三个核心列之后合并本身反而最简单all_ranks [] for key in frames: tmp frames[key][[school, rank_clean, country]].copy() tmp[source] key # 标记来源 all_ranks.append(tmp) long_table pd.concat(all_ranks, ignore_indexTrue) long_table long_table.dropna(subset[school]) print(long_table.shape) print(long_table.head())这段代码的逻辑从每个 frame 里只抽三列加一个 source 列记录榜单来源最后用 concat 纵向拼起来。ignore_indexTrue 表示重新生成 0 到 N-1 的行号如果不加合并后的行号会保留各分表原来的索引虽然不影响后续分析但调试时看起来很乱。dropna 的 subset 参数只检查 school 一列把没有学校名的噪声行删掉如果写 dropna() 不加参数任何一个字段为空都会被删容易把只有国家缺失的合法行误删。合并后的长表大概长这样schoolrank_cleancountrysourcealpha university15甲区qsalpha university28甲区usnewsbeta university90乙区the把长表透视成宽表是验证合并结果最快的方式wide_table long_table.pivot_table( indexschool, columnssource, valuesrank_clean ) wide_table wide_table[[usnews, arwu, qs, the]] # 检查每个来源有多少非空排名 print(wide_table.notna().sum()) # 至少出现在两个榜单里的学校数量 multi_cover wide_table.dropna(thresh2) print(multi_cover.shape[0])pivot_table 会以 school 为索引把 source 的值展开成列值为 rank_clean。这里的坑是如果一所学校在同一个榜单里出现两次比如同时上了主榜和分榜pivot_table 默认聚合方式会报错所以在透视之前最好先检查 long_table 是否有重复行有就去重。dropna(thresh2) 表示只保留至少有两个非空排名列的学校这一行能快速告诉你四榜交集大概多大如果交集数太小说明学校名归一化还不够要回去补映射表而不是继续往下分析。3.5 保存输出long_table.to_csv(output/world_rank_2022_long.csv, indexFalse) wide_table.to_csv(output/world_rank_2022_wide.csv) print(长表行数:, long_table.shape[0]) print(宽表学校数:, wide_table.shape[0])输出时把长表和宽表都存一份是给后面不同分析场景留的后路画散点用宽表方便做筛选和统计用长表灵活。indexFalse 是为了避免把行号写进 CSV否则下次读回来会多一列Unnamed: 0虽然不致命但容易让人困惑。4. 排名数据合并的常见问题排查五处翻车现场这一章把我实际处理排名数据时踩过的坑按现象 → 原因 → 解决整理出来你复现时大概率会撞上其中几个。4.1 学校名直接 merge 导致大面积 NA现象把四份表按学校名做 inner merge结果明明同一所学校却出现两条记录一个叫某高校一个叫Some Universitymerge 之后行数比预期少了一大半。原因四份数据的来源不同有的用中文全称有的用英文官方名还有的带上了城市后缀或校区名。直接拿原始字符串当 join key等于默认所有榜单对同一所学校的写法完全一致这个假设基本不成立。解决先用 normalize_school 做统一化再针对差异大的学校建一个手工映射表。映射表的策略是只映射差异最明显的几十条比如把 The University of Alpha 这类带冠词的写法统一成 alpha university不用追求全部覆盖。通常处理完几十条特殊名称后交集数量就能从惨不忍睹变成可接受。4.2 排名是50或50-52int 转换直接崩现象某榜单的排名列读完是 object 类型里面有的值是 50有的是 50-52直接 astype(int) 抛 ValueError。原因爬虫抓下来或手动录入时并列排名被写成了带等号或区间的文本。pandas 读 CSV 时无法自动识别这种混合格式于是整列都变成字符串。解决用 clean_rank 函数先清洗再转换处理 和 - 之后取区间起始值。如果既要保留原始写法又想做数值运算就在清洗时单独留一列 rank_raw 存原始字符串分析的时候按需取用。4.3 四套榜单的2022根本不是同一时间口径现象把四份 2022 年数据放在一起做横向比较发现某所学校在两个榜单里的位次差了快一百名于是怀疑数据有错。原因四套榜单虽然都标着 2022 版但发布时间和统计周期都不一样。有的在前一年年底就发布了有的到次年中旬才更新权重和样本调整也会让同一所学校在不同时间点的结果产生明显差异。这不是数据错误是由于榜单自带的时滞效应。解决在分析时把榜单版本年份和发布时间分开记录不要默认它们完全对齐。如果论文或报告里要引用排名必须标注是哪套榜单的哪个版本不能只写2022 年排名。4.4 CSV 编码问题read_csv 打不开现象在 Windows 下直接 pd.read_csv(qs_2022.csv) 抛 UnicodeDecodeError但用记事本打开文件却能看到中文正常显示。原因资源包里的 CSV 可能由不同系统导出有的是 UTF-8有的是 GBK还有的带 BOM 头。pandas 默认按 UTF-8 解析遇到 GBK 编码文件就会直接报错。解决读文件时统一加 encodingutf-8-sig它能兼容带 BOM 的 UTF-8 文件如果还报错再尝试 encodinggbk 或 encodinglatin1。最稳妥的写法是写一个小循环逐个尝试编码把成功打开的结果存下来不要手动改文件编码——手动另存为可能会引入新问题。4.5 榜单覆盖学校差异大没上榜不等于差现象A 校在 US News 榜单里没出现某同事直接下结论说这所学校排不进世界前几名。原因US News、软科、QS、THE 四套榜单的覆盖范围差异很大。有的榜单覆盖量大有的榜单本身就有入围筛选规则没上榜的原因可能只是数据来源缺失或学校没参评并不代表真实水平。解决合并时用 left join 或 outer join 保留所有学校的记录不要用 inner join 把缺席的学校静默丢掉。哪怕某所学校只在四榜中出现一次也要保留那一行。最终分析时显式标记哪些榜单缺了这个学校缺席原因本身也可以作为分析维度。5. 进阶验证用排名差散点图找出榜单分歧最大的学校长表和宽表都生成之后最有价值的产品不是排名本身而是四榜之间的位次差。同一所学校的 QS 排名减去 US News 排名如果差值很大说明它在这两套评价体系里受到的待遇截然不同。这个差值就是做研究选题、找案例分析对象的入口。先基于宽表计算两个维度import numpy as np # 计算 QS 与 US News 的位次差 wide_table[qs_usnews_diff] wide_table[qs] - wide_table[usnews] # 计算四榜平均位次作为综合基准 wide_table[rank_mean] wide_table[[usnews, arwu, qs, the]].mean(axis1)qs_usnews_diff 为正说明这所学校在 QS 里相对靠后在 US News 里更受认可为负则相反。rank_mean 给出一所学校的四榜平均位置用来消除单一榜单的极端值。注意 wide_table 里有些学校只在部分榜单出现mean 会默认跳过 NaN但学校样本量不足四榜时rank_mean 的参考价值有限必要时再加一列统计有效榜单数量。画散点图的方式也很直接import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(10, 6)) ax.scatter(wide_table[rank_mean], wide_table[qs_usnews_diff], alpha0.6) ax.axhline(0, colorgray, linestyle--) ax.set_xlabel(四榜平均排名越小越靠前) ax.set_ylabel(QS排名 - US News排名负值代表QS更靠前) plt.show()散点里靠近横轴零线的学校说明两套榜单的看法比较接近离零线越远的点就是评价体系分歧越大的学校。找出这些离群点后回到长表看它们在其他两个榜单里的位次能进一步判断它是被某种评价维度的偏好推上去的还是恰好踩在了某个统计口径的边缘。我通常还会把散点图按国家分组着色用来快速定位某个地区整体被哪套体系更认可。最后提一个我养成的习惯处理此类排名数据时一定先把长表跑通再做任何可视化。第一次做这类分析时我图省事直接拿四张原始表 merge 成宽表结果学校名没统一画出来的图看起来像随机噪声排查了半天才发现是 join key 的问题。从那以后我每次做排名数据分析都强制先走一遍长表流程列名映射、名称归一化、排名清洗、堆叠、透视验证全部跑完再看数。这套流程独立于具体数据源换一年、换一个榜单组合都通用。希望这份资源能直接帮你跳过我最开始反复返工那几天少走几步弯路。希望帮到你。本文还有配套的精品资源点击获取