ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python实现联合国SDG可持续发展指数:SDRPI、SDGI与SDCI计算复现

Python实现联合国SDG可持续发展指数:SDRPI、SDGI与SDCI计算复现 简介这是一份面向可持续发展、环境政策与国际发展领域研究人员的数据分析资源围绕联合国17个SDGs构建SDRPI、SDGI与SDCI三大指数完整复现2000-2020年全球115国可持续发展绩效、目标间不平衡性与协调性的量化评估流程。包体为1个PDF文件大小674KB内含论文方法解读、指数计算原理及Python可运行代码覆盖基尼系数计算、模糊逻辑隶属函数、网络相关性分析等关键环节并附代码逐段解释与数据预处理注意事项。配套说明还梳理了全球SDRPI均值从28.5升至39.8、SDGI从0.43降至0.33等实证结论的推导逻辑对比低收入国家与东欧地区的时空演变辨析弱、强及综合可持续性等理论背景有助于理解区域不平衡格局与跨目标协调机制。已有41人学习下载适合具备一定数据分析基础、希望掌握SDG综合评估框架或开展跨国比较研究的高校师生与决策者使用。1. 三个指数的核心逻辑从SDG数据里读出“绩效、失衡、协调”三层信息用一份标准化后的联合国SDG指标数据表就能同时算出三个不同维度的国家可持续发展指数这大概是这套方法最吸引人的地方。这篇资源复现的是《Assessing global sustainability performance, imbalance, and coordination over space and time》一文的计算框架核心产物就是三个指数SDRPI看一个国家17个SDG目标的总体表现SDGI仿照经济学里的基尼系数衡量一国内部目标之间的不平衡程度SDCI则评估目标之间是否协同增长。你拿到的是一份可直接运行的Python代码包附带完整的函数定义和注释适合正在做可持续发展定量研究、需要一套可解释的指标体系作为论文或政策分析工具的研究人员。文章全部代码基于numpy和pandas数据结构清晰拿到手改一下文件路径就能跑出结果。2. 均值与基尼系数为什么能撑起绩效评估SDRPI和SDGI的计算原理与基准实现2.1 指标选型为什么用均值而不用加权求和为什么基尼系数比方差更合适SDRPI的官方定义是所有SDG指标标准化后的平均值代码里就是data.mean(axis1)一行。很多第一次接触的人会问为什么不加权实际上论文原文强调SDRPI“基于但超越了SDG指数”它刻意保持等权重是因为在缺乏可靠先验的情况下等权重是最不容易引入主观偏差的做法。加权方案留给下游分析者按政策优先级自行调整而不是在基础指数里就做死。SDGI选择基尼系数而不是标准差或变异系数关键在于基尼系数的取值范围固定。标准差是无界的不同国家之间的可比性差而基尼系数天然落在0到1之间0表示完全平衡1表示极端不平衡。这个性质让SDGI可以直接跨国家、跨年份做横向比较。代码里用的简化公式是(n 1 - 2 * sum(cum_sum) / cum_sum[-1]) / n注意这个公式假设数据已经排序而且要求所有值非负。因为SDG指标标准化到0-100所以不会出现负数这个前提是成立的。2.2 基准代码一份数据表算出SDRPI、SDGI和SDCI全流程先看第一份基准代码。假设你已经有一份CSV格式是每行一个国家每列一个SDG指标数值已经标准化到0-100。import numpy as np import pandas as pd data pd.read_csv(sdg_data.csv, index_col0) print(data.shape) # 预期输出类似 (115, 17) def calculate_sdrpi(data): 计算可持续发展相对绩效指数各国SDG指标的平均值 return data.mean(axis1) def calculate_sdgi(data): 计算可持续发展基尼指数衡量各国内部SDG指标的不平衡性 gini_list [] for _, row in data.iterrows(): sorted_row np.sort(row) n len(row) cum_sum np.cumsum(sorted_row) gini (n 1 - 2 * np.sum(cum_sum) / cum_sum[-1]) / n gini_list.append(gini) return np.array(gini_list) def calculate_sdci(data): 计算可持续发展协调指数SDG指标间的相关系数均值截面简化版 sdci_scores [] for i in range(data.shape[0]): correlations [] row_values data.iloc[i, :] for j in range(data.shape[1]): for k in range(j 1, data.shape[1]): corr np.corrcoef(row_values.iloc[j], row_values.iloc[k])[0, 1] correlations.append(corr) sdci_scores.append(np.nanmean(correlations)) return np.array(sdci_scores) sdrpi calculate_sdrpi(data) sdgi calculate_sdgi(data) sdci calculate_sdci(data) results pd.DataFrame({ Country: data.index, SDRPI: sdrpi, SDGI: sdgi, SDCI: sdci }) print(results.head())这段代码的逻辑说明SDRPI直接调用pandas的mean(axis1)按行求均值结果保留在DataFrame索引上方便下游合并。SDGI部分对每一行先做np.sort排序然后累积求和。基尼系数的本质是洛伦兹曲线与完全平等线之间面积的比值累积求和就是洛伦兹曲线的离散近似。SDCI部分在截面数据上做一个简化处理用单年数据直接计算两两SDG指标之间的相关系数然后取平均。参数说明里最需要注意的坑是np.corrcoef在样本量只有2时行为不稳定。这段代码里np.corrcoef(row_values.iloc[j], row_values.iloc[k])实际上传的是两个标量np.corrcoef会把它们当成两个长度为1的序列处理相关系数要么是1要么是NaN。这个简化处理只在演示逻辑时能跑通真正做研究时SDCI必须基于时间序列数据计算。这也是后面第4章补充代码存在的原因千万不要把这段SDCI结果直接写进论文。3. 模糊逻辑与加权基尼系数当等权重假设不再成立时的升级方案3.1 梯形隶属度函数为什么模糊逻辑能压制主观判断的干扰论文里提到SDRPI使用模糊逻辑模型来消除主观判断。传统做法是给每个指标设定一个固定的评级阈值比如60分以上算及格这种硬边界的毛病在于59分和61分在真实世界里差异微乎其微但评级结果可能从“差”直接跳到“良”。模糊隶属度函数让评级变成一个渐变过程。代码里的梯形隶属度函数定义是fuzzy_membership(x, a, b, c, d)把指标值映射到0到1之间的隶属度。阈值参数[30, 50, 70]的含义是0-30分完全属于“差”30-50分逐渐过渡到“中”50-70分逐渐过渡到“良”70-100完全属于“良”。这是梯形函数的标准形式落在平台区间的值隶属度为1落在斜坡区间的值按线性比例计算。def fuzzy_logic_sdrpi(data, membership_thresholds[30, 50, 70]): 使用模糊逻辑计算SDRPI def fuzzy_membership(x, a, b, c, d): if x a: return 0 elif a x b: return (x - a) / (b - a) elif b x c: return 1 elif c x d: return (d - x) / (d - c) else: return 0 sdrpi_scores [] for _, country_data in data.iterrows(): fuzzy_scores [] for indicator in country_data: poor fuzzy_membership(indicator, 0, 0, membership_thresholds[0], membership_thresholds[1]) fair fuzzy_membership(indicator, membership_thresholds[0], membership_thresholds[1], membership_thresholds[1], membership_thresholds[2]) good fuzzy_membership(indicator, membership_thresholds[1], membership_thresholds[2], membership_thresholds[2], 100) weighted_score poor * 25 fair * 50 good * 75 fuzzy_scores.append(weighted_score) sdrpi_scores.append(np.mean(fuzzy_scores)) return np.array(sdrpi_scores)逻辑说明对每个国家的每个SDG指标分别计算它相对于“差、中、良”三个模糊集合的隶属度。注意poor的梯形参数是(0, 0, 30, 50)意味着0到30分之间poor隶属度为130到50分线性下降到0good的梯形参数是(50, 70, 100, 100)表示50到70分线性上升70分以上完全为1。三个隶属度加起来不一定是1因为梯形函数有重叠区间这是正常的不需要归一化。参数说明weighted_score poor * 25 fair * 50 good * 75里的25、50、75是三个模糊集合的质心值。这个选择直接影响最终SDRPI的数值范围。如果论文复现时发现SDRPI整体偏低或偏高优先调整这几个质心值而不是动隶属度函数的端点。端点决定的是分类界线质心决定的是打分基数。3.2 加权基尼系数权重向量怎么定代码里最容易错在哪一步改进版SDGI的核心是给17个SDG指标分配不同权重然后基于加权后的数值计算基尼系数。论文里没有公开具体的权重向量代码里给的是一个近似值分布范围在0.06到0.07之间所有权重加起来需要等于1。def improved_sdgi(data, weightsNone): 改进的可持续发展基尼指数计算考虑SDG指标的重要性差异 if weights is None: weights np.array([0.06, 0.07, 0.06, 0.06, 0.06, 0.07, 0.06, 0.06, 0.07, 0.06, 0.06, 0.07, 0.06, 0.06, 0.06, 0.06, 0.06]) assert len(weights) data.shape[1], 权重数量必须与SDG列数一致 gini_scores [] for _, row in data.iterrows(): weighted_data row.values * weights sorted_data np.sort(weighted_data) n len(sorted_data) cum_sum np.cumsum(sorted_data) total_sum cum_sum[-1] if total_sum 0: gini_scores.append(0) continue gini (n 1 - 2 * np.sum(cum_sum) / total_sum) / n gini_scores.append(gini) return np.array(gini_scores)逻辑说明先用row.values * weights做逐元素乘法把权重乘到对应SDG指标上然后排序、累积、套用同一个基尼公式。这里有个数学细节——对加权后的数据计算基尼系数其结果不单纯是原始基尼系数的线性变换而是会在高权重指标值较大时放大不平衡程度。这正是论文想要的特性重要的SDG目标如果拖后腿对SDGI的负面影响应该更大。参数说明weights的默认值总和需要检查——我算了一下代码里的这个向量总和是1.02左右不是严格的1。实际使用时建议做一步归一化weights weights / weights.sum()。另外assert那句是我后来补的防御性检查因为最常翻车的情况就是换了数据源之后SDG列数变了权重向量和列数对不上导致广播运算出错或者算出一堆NaN。4. 时空维度的SDCI从截面相关系数到21年动态协调网络4.1 为什么截面版SDCI在方法上站不住脚第2章的SDCI实现只能算占位代码因为它把一个国家在一年的17个指标当成计算相关的样本。从统计学角度看n1计算相关系数没有意义。论文里真正的SDCI是基于时间序列的对一个国家提取某个SDG指标在2000-2020年间的21个观测值再提取另一个SDG指标同期21个观测值计算它们之间的Pearson相关系数。这样每组相关都有21个样本支撑结果才具有统计意义。数据结构也会从二维变成三维[国家数, SDG数, 时间点数]。国家数约115个SDG数是17时间点是21年。这个三维数组如果用纯Python列表嵌套会非常难维护建议直接用numpy的三维数组或者xarray的DataArray。下面给出推荐的数据组织方式。4.2 三维数据下的SDCI计算与滑动窗口参数import numpy as np from scipy.stats import pearsonr def temporal_sdci(data_3d, min_periods5): 考虑时间维度的可持续发展协调指数 data_3d: 三维数组 [国家, SDG指标, 时间点] min_periods: 计算相关所需的最少有效时间点数量 n_countries, n_goals, n_times data_3d.shape sdci_scores [] for i in range(n_countries): country_correlations [] for j in range(n_goals): for k in range(j 1, n_goals): ts1 data_3d[i, j, :] ts2 data_3d[i, k, :] mask ~(np.isnan(ts1) | np.isnan(ts2)) if mask.sum() min_periods: continue if np.std(ts1[mask]) 0 and np.std(ts2[mask]) 0: corr, _ pearsonr(ts1[mask], ts2[mask]) country_correlations.append(corr) if country_correlations: sdci_scores.append(np.mean(country_correlations)) else: sdci_scores.append(np.nan) return np.array(sdci_scores)逻辑说明这版把第2章的伪相关换成了真实的时间序列相关。内层双重循环遍历17个SDG的所有两两组合总共136对。每对提取21个时间点的观测值先做缺失值掩码过滤再检查方差是否为零因为标准差为零时pearsonr会返回NaN并抛出警告。最后把所有相关系数取平均得到该国的SDCI。参数说明min_periods5是我按经验设的下限意思是这对指标在21年中至少有5年是同时有效的才参与计算。如果你的数据缺失严重可以调到3如果数据质量好建议调到10以获得更稳定的相关估计。另外numpy的std默认是总体标准差分母是n而不是n-1这在样本量为21时差异不大但如果追求严谨可以传ddof1。4.3 网络相关性SDCI用networkx把相关矩阵变成协调网络论文提到SDCI采用网络相关性分析所以资源里提供了一个基于networkx的升级版不直接取相关系数的平均值而是把相关系数超过阈值的SDG对连成边构建一个无向图然后用网络密度来反映协调程度。这个做法的好处是能滤掉弱相关和噪声。import networkx as nx def network_sdci(sdrpi_time_series, corr_threshold0.1): 基于网络相关性分析的SDCI计算 sdrpi_time_series: [时间点, 国家, SDG指标] corr_threshold: 边保留的相关系数阈值 n_times, n_countries, n_sdgs sdrpi_time_series.shape sdci_scores np.zeros(n_countries) for country in range(n_countries): correlation_matrix np.zeros((n_sdgs, n_sdgs)) for i in range(n_sdgs): for j in range(n_sdgs): if i ! j: ts_i sdrpi_time_series[:, country, i] ts_j sdrpi_time_series[:, country, j] if np.std(ts_i) 0 and np.std(ts_j) 0: corr, _ pearsonr(ts_i, ts_j) correlation_matrix[i, j] corr G nx.Graph() for i in range(n_sdgs): for j in range(i 1, n_sdgs): if abs(correlation_matrix[i, j]) corr_threshold: G.add_edge(i, j, weightcorrelation_matrix[i, j]) if len(G.nodes()) 1: sdci_scores[country] nx.density(G) else: sdci_scores[country] 0 return sdci_scores逻辑说明三维数组的轴顺序换成[时间点, 国家, SDG指标]内层循环时ts_i提取的是该国家某个SDG在21年间的完整时间序列。相关系数对称填充到相关矩阵后遍历上三角区域绝对值超过0.1就建一条带权重的边。网络密度定义为实际边数与可能边数的比值密度越高说明越多的SDG指标之间存在显著协同联动。参数说明corr_threshold0.1是将弱相关过滤掉的阈值。这个值要按实际数据分布灵活调整我的习惯是先算所有国家所有指标对的相关系数看下25%分位数再定阈值。另外注意len(G.nodes()) 1这个条件它保证只有在至少两个节点时才计算密度否则单个节点的密度无意义。5. 复现避坑指南数据标准化、缺失值和时间窗口的三类典型问题5.1 标准化方向搞反SDRPI排名整体错位现象算出来的SDRPI数值看起来正常范围在0到100之间但排名和论文结论完全相反——论文里说低收入国家进步更快你的结果里反而是高收入国家SDRPI最高。原因SDG指标里有一部分是正向指标数值越高越好另一部分是负向指标数值越低越好比如饥饿人口比例、失业率。如果直接把原始数据塞进CSV不做方向统一SDRPI的计算结果就会失真。标准化时把所有指标都默认当成正向处理负向指标的高分值反而拉低了综合绩效。解决读入数据后先检查每个指标的定义对负向指标做反转处理100 - value或取倒数后再归一化。我自己的习惯是单独维护一个direction列表长度17正向写1、负向写-1在标准化循环里按方向处理而不是手动对每一列改数据。5.2 基尼系数分母为0全零行导致的崩溃现象跑improved_sdgi时某一行数据全是0total_sum变成0Python直接报ZeroDivisionError程序中断。原因数据标准化到0-100之后理论上不该出现整行全0。但实际数据集里某些小岛国在个别年份确实会一整年没有任何SDG观测记录数据填充时用0来占位就造成了全零行。代码里虽然加了if total_sum 0的判定但它是最后一道保险更应该在数据预处理阶段就把这类行标记为缺失值。解决读取CSV后先做行级别的缺失率统计missing_rate data.isnull().mean(axis1)缺失超过50%的国家直接排除不要进指数计算。连续的年份数据再用前向填充和后向填充组合处理data data.ffill().bfill()但要注意pandas的ffill默认按列填充你需要先确认数据结构。如果填充后仍然有零值残留就把0替换为该国的多年均值而不是让全零行进入基尼计算。5.3 SDCI时间序列长度不一致21年数据缺了后半段现象temporal_sdci跑完返回大量NaN国家个数有100多个但有效的SDCI值只剩四五个。原因原始数据集的发布时间不同很多国家的SDG指标只更新到2015年或2018年2020年的观测值缺失。我的代码里min_periods5能过滤掉样本量太短的对但如果你传入的数组直接用np.nan补齐参与计算的对数会大幅减少。解决在temporal_sdci函数里增加一个有效年份比例的检查。我的做法是先用掩码统计每个国家所有136对指标的有效观测年份总数如果某国有效数据量不足总量的40%就直接把这个国家的SDCI记为缺失并输出警告。另一个折中方案是把时间窗口从21年缩短为10年只计算2011-2020年的数据这样能保住更多样本但要牺牲论文中讲的长期趋势分析取舍取决于你的研究目标。5.4 权重向量与列数不匹配引发的静默错位现象improved_sdgi没有报错但结果明显异常——某些表现很好的国家SDGI反而偏高与常识相悖。原因这是我最常遇到的索引错位问题。如果你在读取CSV时没有按固定顺序排列SDG列而权重向量仍按默认顺序对应那么权重就挂错了指标。比如默认权重的第1个0.06对应SDG1但你的数据第1列其实是SDG4权重被张冠李戴。由于计算过程不会报错这种错误很难被发现。解决在数据预处理时给每一列重命名成固定的SDG编号格式然后按编号排序。具体做法是data data[sorted_sdg_list]其中sorted_sdg_list是你定义好的17个标准列名顺序。同时把权重向量也做成一个Series索引与SDG编号一致这样乘法运算时会自动按索引对齐索引不一致会生成NaN而不是静默算错。我复现论文时踩过这个坑后来在代码开头强制跑了断言检查才彻底杜绝。6. 验证方法用构造的模拟数据检验三个指数的计算逻辑拿到代码包后第一件事不是急着跑自己的数据而是先用一份构造好的伪数据集验证三个指数是否符合预期行为。这个习惯帮我挡掉了大量因为数据结构或参数错误引起的无效复现。np.random.seed(42) n_countries, n_goals, n_times 10, 17, 21 # 构造前5个国家协调增长后5个国家互相独立 base np.arange(n_times) / 20.0 mock_data np.zeros((n_countries, n_goals, n_times)) for i in range(n_countries): trend np.random.uniform(0.2, 1.0) for g in range(n_goals): if i 5: mock_data[i, g, :] 30 base * 50 * trend np.random.normal(0, 2, n_times) else: mock_data[i, g, :] 30 np.random.normal(0, 10, n_times) np.random.uniform(0, 20, n_times)模拟数据的逻辑很简单前5个国家的17个SDG指标共享一个时间趋势加上小噪声它们的SDCI应该偏高后5个国家每个指标独立随机游走SDCI应该接近0。跑完network_sdci(mock_transposed)之后如果前5个国家的网络密度明显高于后5个说明SDCI算法的方向没问题。# 把模拟数据转成 [时间点, 国家, 指标] 并计算 mock_3d mock_data.transpose(2, 0, 1) sdci_check network_sdci(mock_3d, corr_threshold0.1) print(前5国SDCI均值:, sdci_check[:5].mean()) print(后5国SDCI均值:, sdci_check[5:].mean())参数说明corr_threshold0.1在这里起作用因为前5国的指标间相关系数通常在0.7以上后5国分布在-0.2到0.2之间这个阈值能干净地区分两类国家。如果打印结果出现前5国密度低于后5国基本可以判定是时间序列轴顺序传错了transpose(2, 0, 1)就是专门用来保证轴顺序正确的。从那以后我每次拿到新的SDG数据集都强制先跑一遍这个模拟验证再进真实数据。它能用不到一分钟的时间检验出代码里90%的轴顺序、缺失值和标准化方向问题省下的排查时间远超写这段验证代码的成本。希望这套验证习惯和指数复现流程能帮到你让你的论文复现少走一些我走过的弯路。本文还有配套的精品资源点击获取
返回列表