简介:这份压缩包整合了1946—2023年基于联合国投票记录估计的国家理想点距离数据,面向国际关系、政治学及计量研究领域的师生与研究者,可用于分析国家偏好距离、外交立场演变及多边合作趋势,无需自行爬取和处理投票数据。压缩包共含7个文件,约46.95MB,设计较为完整:两份CSV数据文件适合直接导入常见统计软件,R数据文件和Stata数据文件分别对应R与Stata环境,另附数据来源说明网页、变量说明书Word文档和估算方法论文PDF,方便不同工具链用户读取与复现。已有59人学习浏览,适合需要快速获取可用数据集完成论文、报告或课堂项目的用户。其中“与中国理想点距离”的Stata数据文件可直接用于中外双边关系研究;Codebook与配套论文详述了变量定义、样本范围和估算逻辑,帮助用户规范引用并快速上手。
1. 理想点距离(1946-2023年).zip 是什么:一个能直接当决策依据的时间序列数据包
做过多指标评价的人看到这个名字应该会很眼熟:理想点距离是 TOPSIS(逼近理想解排序法)里的核心中间结果,每个评价对象到正理想解和负理想解的欧氏距离,决定了它最终的综合得分和排名。而这个压缩包把 1946 到 2023 年共 78 个年份的理想点距离按年组织成了一组数据文件,等于把几十年评价计算的底稿直接递到了你手上。它的价值在于不用重新找原始指标、不用重复跑规范化流程,拿到手就能做趋势分析、排名复核和年报出数。适合正在做综合评价、指标体系建设、历史数据回溯或者需要给报告配“得分走势”的从业者,时间序列研究者也可以把它当作现成的面板数据样本。
2. 拿包先别解压:校验、伪加密与正确解压
2.1 为什么压缩包先校验再解压
很多人的习惯是双击压缩包直接“解压到当前文件夹”,遇到几百 MB 的数据包更是懒得等校验。但理想点距离这种跨 78 年的数据包,里面有大量 csv、xlsx 或 dbf 文件,任何一个文件在传输过程中损坏,后续按年拼接时都会出现“某一年数据对不上”的诡异问题。usb 拷贝、网盘下载、邮件附件,三种常见途径都可能导致 zip 的中央目录和本地文件头不一致。解压后再发现缺文件,回去重新下载整个包的成本更高。
所以我的固定动作是:先看压缩包完整性,再看包内文件清单,最后才真正释放文件。这一步在 Linux 和 Windows 上都有现成工具,但命令和参数不一样,下面分开说。
2.2 用 unzip 与 zipinfo 做三项体检的命令
在 Linux 或 macOS 终端里,拿到“理想点距离(1946-2023年).zip”之后,我一般会依次跑三条命令:
# 1. 测试压缩包完整性,不释放文件 unzip -t "理想点距离(1946-2023年).zip" # 2. 查看包内目录结构和文件总数 unzip -l "理想点距离(1946-2023年).zip" | head -40 # 3. 查看压缩方式、加密标志和文件时间戳 zipinfo -v "理想点距离(1946-2023年).zip" | grep -E "^[-a-z]+$|file security status|compression method"第一条unzip -t是测试模式,逐文件读出 CRC32 和压缩包里的记录值做比对,任何一条“bad CRC”都会在这里暴露。第二条unzip -l列出包内所有文件路径,先看有没有多余的临时文件、空目录或者异常命名,避免解压出预期之外的内容。第三条zipinfo -v是体检细节,重点看file security status那一行:显示encrypted说明文件有密码保护,显示not encrypted才是干净的普通 zip;compression method显示deflated是常规压缩,stored表示未压缩存储。
参数说明:-t是 test 的缩写,不写输出文件;-l是 list,管道到head -40是因为年份表文件可能很多,只看前 40 行够了;grep的正则里^[-a-z]+$匹配以字母开头的小节标题行,file security status和compression method是 zipinfo 输出里的固定字段名。
Windows 用户如果没有 Linux 环境,用 7-Zip 的文件管理器打开包后按Shift+L可以测试压缩包,或者打开命令行工具7z t "理想点距离(1946-2023年).zip",效果一致。注意不要用系统自带的“压缩文件夹”功能做测试,它只验证能否列出文件,不校验 CRC。
2.3 伪加密与“密码移除”的误解
zip 伪加密是流传很广的坑,理想点距离这类公开数据包偶尔也会中招。现象是解压时提示输入密码,但包里全是 CSV 数据表,发布方也没提过加密。原因在于 zip 格式的加密标志位有两种:一种是真正的 AES 或 ZipCrypto 加密,另一种只是把全局加密标志位置 1,数据本身并没有加密。后者就是伪加密,解压工具看到标志位就会停下来要密码。
遇到伪加密,不需要做什么“密码移除”,改标志位就行。Linux 下没有现成的单命令工具,我常用的办法是先用zipinfo -v看每个文件的file security status,如果确认是伪加密,用 Python 直接改文件头里的加密标志字节:
import struct with open("理想点距离(1946-2023年).zip", "r+b") as f: data = bytearray(f.read()) count = 0 for i in range(len(data) - 4): # 定位本地文件头 0x504B0304 if data[i:i+4] == b"PK\x03\x04": flag_offset = i + 6 flags = struct.unpack("<H", data[flag_offset:flag_offset+2])[0] if flags & 0x0001: # 加密位为 1 flags &= ~0x0001 struct.pack_into("<H", data, flag_offset, flags) count += 1 with open("理想点距离(1946-2023年)-fixed.zip", "wb") as f: f.write(bytes(data)) print(f"修复了 {count} 个本地文件头的加密标志位")逻辑说明:zip 每个本地文件头的第 6、7 字节是通用标志位,最低位是加密标志。代码遍历整个文件找PK\x03\x04签名,把加密位置 0 后写回新文件。参数说明:r+b是读写二进制模式,<H是小端无符号短整型,0x0001是加密位的掩码,struct.pack_into直接修改指定偏移的字节,避免重建整个文件。
需要提醒的是,修改标志位只适用于你确认这份数据应当公开、没有版权争议的情况。如果发布方明确声明了密码,那属于正规加密,不该绕过,你要做的是联系数据提供方索取授权。另外,真正 AES 加密的 zip,改标志位没有任何作用,解压时依然会报错,那种情况要么有密码,要么就放弃这份数据换个来源。
3. 把包内数据读成可用结构:目录认识与 Python 标准读入
3.1 包内“一年一表”的常见组织方式
跨年度的理想点距离数据包,目录结构通常遵循“按年份分文件、按区域或对象分行、按指标分列”的约定。我经手过的类似数据包,典型布局是这样:
理想点距离(1946-2023年)/ ├── 1946.csv ├── 1947.csv ├── ... ├── 2022.csv ├── 2023.csv ├── indicator_definition.xlsx ├── weight_config.json └── README.txtREADME 里一般写清楚了理想点距离的定义和计算口径:正理想点距离 D+ 表示评价对象距离最优虚拟方案的距离,负理想点距离 D- 表示距离最劣虚拟方案的距离,综合贴近度 C = D- / (D+ + D-)。CSV 文件里除了 D+ 和 D- 两列,往往还有评价对象的 ID、名称,以及规范化后的各指标值。
这一节的关键是:解压后不要急着把 CSV 丢进 Excel 里看,先确认列名、分隔符和编码。跨年份数据最容易翻车的地方就是早期年份用 GBK 编码,近几年用 UTF-8,直接读会报编码错误或中文乱码。
3.2 用 pandas 读入并按年拼接
我一般会用一个 Python 脚本把解压后的数据全部读进来,统一编码、统一列名,拼成一张长表:
import pandas as pd from pathlib import Path data_dir = Path("理想点距离(1946-2023年)") frames = [] for csv_path in sorted(data_dir.glob("*.csv")): year = int(csv_path.stem) # 文件名就是年份 # 优先按 UTF-8 读,失败就回退 GBK try: df = pd.read_csv(csv_path, encoding="utf-8-sig") except UnicodeDecodeError: df = pd.read_csv(csv_path, encoding="gbk") df.insert(0, "year", year) # 每一行打上年份标记 frames.append(df) full_data = pd.concat(frames, ignore_index=True) full_data.to_parquet("ideal_point_distance_full.parquet") print(full_data.groupby("year").size().head())逻辑说明:脚本的核心是用年份文件名作为主键,把所有 CSV 纵向拼接成一张“长表”,之后按年份筛选、按对象追踪、画趋势线都在这张表上完成。utf-8-sig能自动吃掉带 BOM 的头部,gbk是中文环境老数据最常见的编码。参数说明:csv_path.stem取文件名的去后缀部分,int()强转成年份数字,df.insert(0, "year", year)把年份插到第 0 列而不是追加到末尾,方便一眼看到;to_parquet是给后续分析用的,没有 parquet 环境可以换成to_csv("full.csv", index=False)。
读完之后马上做两个检查:一是full_data.isna().sum()看有没有整列空值,二是full_data.groupby("year").size()看每年行数是否在合理区间。如果某一年行数为 0,说明那年的 CSV 可能是空文件,回头用 2.2 的unzip -l对照包内文件列表确认是源数据缺失还是解压失败。
3.3 复算贴近度验证数据完整性
光能读进来还不够,还要验证包里的理想点距离算得对不对。这是最容易被跳过、但最能发现问题的一步。TOPSIS 的贴近度 C 在每一年内部独立计算,所以我可以从 CSV 里取出 D+ 和 D- 两列复算 C 值,和包里给出的 C 列做比对:
import numpy as np for year, grp in full_data.groupby("year"): d_plus = grp["D+"].to_numpy() d_minus = grp["D-"].to_numpy() with np.errstate(divide="ignore", invalid="ignore"): c_calc = d_minus / (d_plus + d_minus) c_given = grp["C"].to_numpy() diff = np.nanmax(np.abs(c_calc - c_given)) if diff > 1e-10: print(f"{year} 年贴近度与包内值不一致,最大差 {diff:.2e}")逻辑说明:C 的定义是负理想距离占正负距离之和的比例,范围恒在 0 到 1 之间。用每行的 D+ 和 D- 直接相除,再与包里现成的 C 列对比,理论上浮点误差在 1e-10 量级。如果某年差值到 1e-5 以上,基本可以断定那个年份文件的数据在生成或拷贝过程中出了问题。参数说明:np.errstate是防止某行 D+ 和 D- 同时为 0 时除零报警,np.nanmax过滤掉 NaN 后取最大偏差,1e-10是浮点容差阈值,实际使用时可以放宽到 1e-8。
这一步跑通之后,你就能确认:这包数据的 D+、D-、C 三列内部自洽。接下来无论是直接把 C 值作为评价得分出报告,还是把原始 D 值接进自己的排名逻辑重算,都有了可信的基础。顺带说一句,如果包里没有 C 列而只有 D+ 和 D-,这个脚本就变成了补算 C 列的工具,逻辑完全一致。
4. 从压缩包到业务数据源:Linux 与 Windows 的落位方案
4.1 Linux 离线环境的 zip 工具准备
内网离线环境里解压数据包,经常碰到“没装 unzip”的尴尬。Debian/Ubuntu 系用apt install unzip,CentOS/RHEL 系用yum install unzip,但在离线环境这两条命令都无效。我一般提前备好 deb 或 rpm 安装包,或者更轻量地,直接用 Python 的 zipfile 模块做解压,省掉系统依赖:
# 离线环境用 Python 解压,不依赖系统 unzip python3 - <<'EOF' import zipfile z = zipfile.ZipFile("理想点距离(1946-2023年).zip") z.extractall("ideal_point_output") z.close() EOF逻辑说明:zipfile 是 Python 标准库,有 Python 3 就有它,不需要额外安装。extractall会把压缩包内所有文件释放到指定目录,目录不存在会自动创建。参数说明:第一个参数是压缩包路径,第二个是输出目录名;z.close()是显式释放文件句柄,避免 Windows 下文件被占用导致后续操作失败。这个办法的代价是没有 CRC 校验输出,所以释放完最好用 2.2 节的方法检查一遍。
如果环境里连 Python 都没有,那就只能在有网的机器上提前把 unzip 的离线安装包装好。确认方法很简单:which zipinfo和which unzip两条命令都返回路径,才说明工具链齐全。另外,某些精简版 Linux 只装了zip(压缩命令)没装unzip(解压命令),别看到zip -?能执行就以为解压也没问题。
4.2 Windows 下解压、便携化与右键压缩干扰
Windows 用户拿到理想点距离数据包,最常见的路径是右键 → 全部解压缩。系统原生 zip 功能对小文件没问题,但 78 个 CSV 加配置文件一起解压时,经常出现两个问题:一是解压进度条走完但文件不完整,二是右键菜单里误点了“压缩为 zip 文件夹”,把原本正确的数据目录二次压缩,导致文件层级多套了一层。
我的建议是弃用系统自带功能,装一个便携版解压工具。CrystalDiskInfo 那种“便携版 zip、免安装、解压即用”的思路套到压缩工具上同样适用:7-Zip 便携版解压后直接运行7z.exe,不需要管理员权限、不写入注册表,适合在客户机器和管制环境里临时使用。命令示例:
:: 在 Windows 命令行中用 7-Zip 测试并解压 "C:\path\7z.exe" t "理想点距离(1946-2023年).zip" "C:\path\7z.exe" x "理想点距离(1946-2023年).zip" -o"D:\data\ideal_point" -y参数说明:t是测试,x是解压,-o指定输出目录(冒号后紧跟路径,不能有空格),-y是全自动覆盖文件。不加-y时遇到已存在的同名文件会交互式询问,在脚本化批处理里会造成卡住。注意-o的语法比较特殊,-oD:\data\ideal_point是一个整体,如果写成-o D:\data会被 7-Zip 当成两个参数,解压位置就不对了。
解压后如果想在 Windows 下“便携化”使用——也就是不让文件散落在临时目录、不依赖 Excel 宏、双击即用——常见的做法是建一个run.bat放在数据根目录里,把 Python 或数据分析脚本的调用固定下来,让数据包变成一个可重复执行的分析单元。这种方式在团队内部流转时特别好用,别人拿到数据包不用问“怎么打开”,直接双击即可。
4.3 把解压目录注册为服务数据源
数据解压只是第一步,真正要让它发挥作用,是把这 78 年数据接进周期性的评价流程。一个常见的做法是把解压目录当作一个只读数据源挂到服务或者数据库里,类似 MySQL 的 zip 包在 Windows 上手动设置成本地服务那样:先解压到固定目录,再写配置文件指向它,最后注册成服务或定时任务。
在 Linux 上,我一般会用 systemd 的定时器或 cron 来处理“每月一次数据刷新”的诉求。下面是一个最小可用的 cron 配合脚本:
# /etc/cron.d/refresh_ideal_point 0 2 1 * * root /opt/scripts/refresh_ideal_point.sh#!/usr/bin/env bash # /opt/scripts/refresh_ideal_point.sh set -euo pipefail DATA_DIR="/srv/ideal_point" ZIP_FILE="/data/upload/理想点距离(1946-2023年).zip" # 校验后解压,解压到临时目录再原子替换 unzip -t "$ZIP_FILE" >/dev/null TMP_DIR=$(mktemp -d) unzip -q "$ZIP_FILE" -d "$TMP_DIR" rm -rf "$DATA_DIR" mv "$TMP_DIR" "$DATA_DIR" echo "$(date '+%Y-%m-%d %H:%M:%S') 数据已更新" >> /var/log/ideal_point.log逻辑说明:脚本先把新上传的 zip 做完整性测试,测试通过才解压到临时目录,最后用mv做原子替换,避免数据库中读到一半的文件。set -euo pipefail是三个保险:任何命令失败立刻退出、未定义变量报错、管道中前一个命令失败也报错。参数说明:mktemp -d生成系统临时目录,rm -rf清空旧数据目录,mv是原子操作,整个替换过程对外部访问者不可见,这点在数据服务化之后很重要。
Windows 上做同样的效果,用任务计划程序定时跑一个 bat 脚本,核心逻辑一样:先7z t测试,再7z x -y覆盖解压。区别只是没有 Linux 的原子替换语义,所以我会解压到一个带版本号的目录(如ideal_point_v2024),然后更新一个current快捷方式指向它,业务系统只读current路径,这样更新时不会出现“文件正在被占用”的报错。
5. 避坑:理想点距离数据从 zip 到分析的 5 个典型翻车现场
5.1 解压提示输入密码:伪加密拦路
现象:双击解压“理想点距离(1946-2023年).zip”,弹出密码框,但 README 和数据说明里都没有密码。 原因:zip 的加密标志位被置 1,数据本身没有加密,常见的伪加密手法,也可能是发布方打包工具异常写入。 解决:用 2.3 节的脚本检查file security status,确认无真正加密后,把标志位清零重新保存。如果修改后仍要求密码,说明数据确被真实加密,不应破解,去和提供方确认授权或索取密码。
5.2 文件名乱码:ICU 编码与本地编码打架
现象:解压出来的 CSV 文件名是“1946.csv”这类数字命名,但indicator_definition.xlsx变成了乱码;或者 README.txt 打开是乱码。 原因:zip 的文件名和文件内容各有一套编码。包内文件内容常见 GBK,但文件名用的是 UTF-8,在中文版 Windows 上解压时系统按 GBK 解释 UTF-8 编码的文件名,部分字符就错位了。 解决:Linux 下用unzip -O gbk指定文件名编码(部分 unzip 版本支持-O参数),Windows 下用 7-Zip 解压并在设置里选“使用 UTF-8 文件名”或直接开启“自动检测”。文件内容乱码则用 3.2 节的编码回退方案处理。
5.3 Excel 打开 CSV 后 D+ 与 D- 列变成“科学计数法”
现象:用 Excel 打开某个年份的 CSV,D+ 列显示成 1.23E+07,原本的数值精度丢失,对不上。 原因:CSV 里的距离数值较大,Excel 默认把超过一定位数的数字显示成科学计数法,这只影响显示,不影响底层数值。可怕的是用户手动另存为 xlsx 后,部分精度被真正截断。 解决:不要用 Excel 直接编辑原始 CSV,分析全程走 Python 或数据工具。如果必须用 Excel 查看,导入时选择“数据 → 自文本/CSV”,在向导里把 D+、D- 列设置为“文本”格式。用 pandas 读入后检查dtype,确认是float64而不是object。
5.4 missing zip entry:中间文件缺失导致整体解压失败
现象:执行unzip -t时报missing zip entry或file not found,指向的是某个早期年份的 CSV。 原因:压缩包本身在制作时遗漏了文件条目,或者下载过程中文件被截断,中央目录与本地文件头不一致。这属于物理损坏,不是解压工具的锅。 解决:先重新下载一次,看问题是否复现。如果复现,用zip -FF 理想点距离(1946-2023年).zip --out fixed.zip做修复尝试,-FF是 unzip 系列的修复模式,通过扫描本地文件头重建中央目录。修复后一定要重新跑unzip -t确认所有文件条目完整。如果修复后仍缺特定年份,那年的数据只能联系提供方补发。
5.5 把 jpg 文件改成 zip 扩展名的伪文件
现象:拿到一个名为“理想点距离(1946-2023年)_补充说明.zip”的文件,解压工具报“不是有效的压缩文件”。 原因:有人为了绕过上传限制,把图片或文档直接改扩展名伪装成 zip,文件真实格式可能是 jpg、pdf 或 docx。 解决:不要双击,先看文件头。用xxd 文件.zip | head看前几个字节:zip 的起始签名是50 4B 03 04(即 PK 两个字符),jpg 是FF D8 FF,pdf 是25 50 44 46。不是50 4B开头的就不是 zip。真遇到这种情况,按真实扩展名改回去再打开。
6. 进阶:让 1946-2023 年的数据包滚动起来——增量校验小脚本
静态数据包的价值有限,真正让理想点距离数据变成长期资产的方式是把它接到每年的评价流程里,定期补一个年份、自动校验、输出变更报告。我给自己留了一个小脚本,每年新数据公布后跑一遍,它负责三件事:确认新 zip 和已有数据同源、把新年份的数据拼接进长表、给出上一年的排名变动清单。
import hashlib import pandas as pd from pathlib import Path NEW_ZIP = Path("理想点距离(1946-2023年).zip") OLD_PARQUET = Path("ideal_point_distance_full.parquet") # 计算 zip 的 SHA256,用于记录版本指纹 digest = hashlib.sha256(NEW_ZIP.read_bytes()).hexdigest() print(f"新包 SHA256: {digest[:16]}...") # 解压并读取新一年的数据(复用 3.2 节逻辑,省略展开) new_frame = pd.read_csv("2024.csv", encoding="utf-8-sig") new_frame.insert(0, "year", 2024) # 拼接进历史长表 old_frame = pd.read_parquet(OLD_PARQUET) merged = pd.concat([old_frame, new_frame], ignore_index=True) merged.to_parquet(OLD_PARQUET) # 只看最近两年排名变化 pivot = merged[merged["year"].isin([2023, 2024])] rank_last = pivot[pivot["year"] == 2023].sort_values("C", ascending=False)["对象名称"].tolist() rank_now = pivot[pivot["year"] == 2024].sort_values("C", ascending=False)["对象名称"].tolist() changes = [(i, o) for i, o in enumerate(rank_now) if rank_last[i] != o] print(f"对比 2023 年,2024 年排名变动位置数:{len(changes)}")逻辑说明:脚本用 SHA256 给每次入包的压缩文件做指纹,记录在案,以后任何人问“今年这份和去年那份是不是同一份数据”,比对指纹即可。拼接逻辑和第 3 章一致,区别是这里只增量读取新一年文件,避免每年全量重读 78 个 CSV。排名变动部分把两年各自按 C 值降序排列,逐位比对,输出变动位置数量,方便快速判断这一年评价结果是否出现大洗牌。
参数说明:hashlib.sha256返回的是字节摘要,hexdigest()转成十六进制字符串;read_bytes()一次性读取整个 zip,对于大文件会吃内存,替代方案是分块读取并逐步更新摘要;to_parquet覆盖写入历史表,写之前确认旧表已有多余备份;列表推导式里的i是排名位次,o是该位次上的对象名称,只要两年排序在相同位置出现不同对象,就计入变动。
这个脚本跑完之后,我会固定把当年的2024.csv单独留档,压缩包本身则按年份归档。因为 zip 里保存的是 1946-2023 年全量数据,每年新数据出来时,如果发布方给的还是一个全量包,我倾向于只取增量文件入库,而不是每次用新包覆盖旧包——全量包一旦在传输中出错,连带历史数据一起被污染的风险太大,这个教训来自一次我直接用新包替换旧表、结果发现新包某年 CSV 损坏、历史数据也跟着回不去的经历。文件都留在原地,新数据逐条追加上去,zip 只是运输工具,分析底座永远是那张校验过的 parquet 长表,希望帮到你。
本文还有配套的精品资源,点击获取