章节概述

本章聚焦综合指数构建(Composite Index Construction),介绍如何将多个来源、量纲和方向不同的指标,经过指标选择、变量重构、标准化、权重设定与聚合计算,转化为可比较、可解释的综合评价结果。

本章包含两个专题研究。专题研究 1 使用美国 ACS 1-year estimates 构建主要城市社会剥夺指数,比较 2012-2024 年间城市社会剥夺水平的动态变化。专题研究 2 使用 2024 年中国超一线与新一线城市专利数据,构建专利创新指数(PII),比较城市专利创新表现及其维度结构。

本章学习内容

学习导航

本章学习内容

欢迎开始 第 8 章 的学习。请点击 下方导航卡 进入相应小节:

💡 提示:学习完一个小节后,请再次点击 屏幕右下角的章节主页按钮回到本导航页

1.0 综合指数构建导论

1.0 综合指数构建导论

1.1 什么是综合指数?

基本概念

综合指数(Composite Index)是指在明确概念框架下,将多个单一指标经过方向统一、标准化、加权与聚合等步骤,整合为一个综合数值尺度的方法。在社会科学与城市研究中,许多研究对象并不能由单一变量充分刻画,例如社会剥夺社区脆弱性生活质量城市宜居性公共服务可达性可持续发展水平。这类概念通常具有多维结构,需要借助综合指数将分散的观测指标组织为可比较、可解释、可追踪的综合尺度。

从变量到尺度
综合指数构建并不是简单地将若干变量相加,而是一个从概念界定指标操作化、再到数值聚合的分析过程。一个相对严谨的指数通常需要回答以下问题:指数试图衡量的核心概念是什么?该概念可以拆分为哪些维度?每个维度应由哪些可观测指标代理?不同指标的方向是否一致?是否需要进行标准化、加权或变换?最终得到的指数结果应如何解释、验证与检验其稳定性?

从方法论角度看,综合指数更接近一种简化模型(simplified model),而不是对现实状况的直接复制。OECDEuropean Commission JRC 的综合指标构建手册指出,综合指数有助于对复杂现象进行比较与沟通,但其结果同时受到概念界定、变量选择、标准化方法、权重设定和聚合方式等一系列研究决策的影响。因此,一个具有解释力的指数不仅应当能够被计算出来,还需要说明为什么这样构建结果应如何解释,以及主要结论是否会因方法选择而发生明显变化

资料提示
OECD(Organisation for Economic Co-operation and Development,经济合作与发展组织)与 European Commission JRC(Joint Research Centre,欧盟委员会联合研究中心)曾联合发布 Handbook on Constructing Composite Indicators: Methodology and User Guide
该手册是综合指数构建领域常用的方法参考,系统讨论了理论框架、数据处理、标准化、权重、聚合、不确定性分析与敏感性分析等问题。


为什么需要综合指数?

许多社会与城市现象都具有多维度(multi-dimensional)特征。例如,“贫困”并不只意味着收入不足,也可能伴随教育机会有限、就业不稳定、住房拥挤、交通资源不足或健康风险累积。若仅使用收入中位数或贫困率等单一变量,往往难以呈现这些维度之间的叠加关系。

综合指数的主要价值在于,将分散在多个指标中的信息重新组织为一个相对简洁的综合尺度,使其更便于横向比较纵向追踪空间制图政策沟通。需要注意的是,综合指数通常并不是研究结论本身,而是用于识别差异、提出问题并支持进一步解释的诊断工具

综合指数在社会科学与日常决策中的典型应用
领域 指数名称 核心维度 典型用途
人类发展 HDI 人类发展指数 预期寿命、教育、收入 比较国家或地区的发展水平
空气环境 AQI 空气质量指数 PM2.5、O3、SO2、NO2 等污染物 日常健康防护与环境治理
社会不平等 IMD 多重剥夺指数 收入、就业、教育、健康、住房、犯罪等 资源分配与贫困地区识别
公共健康 SVI 社会脆弱性指数 社会经济、家庭结构、少数族裔、住房交通 灾害应急与健康风险评估
城市治理 城市宜居指数 稳定性、医疗、文化、教育、基础设施 城市比较与跨国人才配置
金融市场 股票市场指数 一篮子股票价格 宏观市场走势观察

综合指数的基本张力

综合指数的优势在于能够将多维信息压缩为一个便于比较的综合尺度;其局限也来自这种压缩过程。以某个“城市贫困指数”为例,指数值较高并不意味着该地区在所有维度上都处于不利状态,它可能主要由住房拥挤、失业率、收入不足或交通资源缺乏中的某一项推动。相反,两个地区的综合指数接近,也不一定意味着其内部结构相同。因此,综合指数通常需要结合分维度解释敏感性分析以及地图和趋势图共同理解。

综合指数构建可以概括为三个相互衔接的层次:首先是概念层(Conceptual Layer),即明确指数希望衡量的社会或城市现象;其次是指标层(Indicator Layer),即选择可观测变量来代理不同维度;最后是计算层(Computational Layer),即完成方向统一、标准化、加权与聚合。若概念界定不清晰,后续计算即使技术上成立,也可能削弱指数结果的解释意义。

Figure 1. 构建综合指数的三个层次

Figure 1. 构建综合指数的三个层次


1.2 经典案例:社会剥夺指数的演进

经典案例

综合指数在社会政策、公共健康与城市研究中具有较长的应用传统。其中,社会剥夺指数(Deprivation Index)是一类具有代表性的综合指数。它通常借助人口普查或社会调查变量,衡量地区在经济资源、就业机会、住房条件与社会参与等方面的相对不利程度。

1.2.1 Carstairs Index

常见

Carstairs Index(卡斯泰尔斯指数)由 Vera Carstairs 和 Russell Morris 基于苏格兰人口普查资料提出,主要用于衡量地区层面的物质剥夺程度,并服务于健康不平等研究。该指数的特点是结构简洁、变量数量较少、计算过程透明:选取若干能够反映物质剥夺的普查变量,统一方向后进行标准化,再以等权方式加总。

原始 Carstairs Index 通常包含四个维度:

原始 Carstairs Index 的四个核心维度
指标 含义 方向
无汽车家庭比例 经济资源与交通资源不足的代理 越高表示剥夺越严重
男性失业率 劳动市场机会不足 越高表示剥夺越严重
过度拥挤住房比例 住房条件不足 越高表示剥夺越严重
低社会阶层人口比例 半技术或非技术工种户主比例 越高表示剥夺越严重

Carstairs Index(下文简称 CI)的优点在于结构简洁计算透明可解释性较强,因此常被用于社会剥夺研究和方法演示。不过,CI 也提示了综合指数构建中的一个重要问题:同一指标在不同地理语境下可能具有不同含义。例如,在汽车依赖程度较高的地区,无车家庭比例可能反映交通资源不足;但在公共交通较发达、居住密度较高的中心城市,无车并不必然意味着社会剥夺。

1.2.2 Index of Multiple Deprivation

常见

多重剥夺指数(Index of Multiple Deprivation, IMD)是英国公共政策中长期使用的综合指数体系,常用于识别相对不利地区、支持公共资源分配与社会政策评估。英格兰 IMD 通常以较小的统计地理单元为基础发布,例如 2010、2015 和 2019 等版本均被广泛用于学术研究与政策分析。

与 CI 相比,IMD 更接近一个指数之指数(Index of Indices):它并不是直接把所有变量放在一起加总,而是先在收入、就业、教育、健康、犯罪、住房与服务、生活环境等领域内部构建分指数,再按照既定权重汇总为总体剥夺指数。这种结构使 IMD 能够覆盖更广泛的社会剥夺维度,但也对指标选择、权重设定和结果解释提出了更高要求。

Figure 2. UK 2025 IMD 构成示意图

Figure 2. UK 2025 IMD 构成示意图

相较于 CI 的四变量简化结构,IMD 更适合用于正式的政策监测与资源分配,因为它能够覆盖收入、就业、教育、健康、犯罪、住房与环境等多个领域,并在较细空间尺度上呈现地区差异。但这种复杂性也意味着,指数构建过程需要更严格地说明每个领域的理论依据、指标来源、数据质量、权重设定以及结果验证方式。

英国最新 IMD 2025: (https://www.gov.uk/government/statistics/english-indices-of-deprivation-2025)

1.2.3 CDC/ATSDR Social Vulnerability Index

Social Vulnerability Index(SVI)是美国公共健康与灾害管理领域常用的综合指数,主要用于识别在灾害、公共健康事件或其他外部冲击中可能需要额外支持的地区。CDC/ATSDR SVI 基于美国人口普查与 ACS 数据构建,将社会脆弱性组织为若干主题,例如社会经济地位、家庭结构、少数族裔身份、住房与交通等,并进一步形成总体脆弱性排名。

SVI 的特点在于,它并不以一般意义上的“发展水平”或“贫困程度”评价为主要目标,而是服务于风险识别资源配置应急准备。这说明综合指数的用途并不局限于地区排序,也可以用于识别在特定风险情境下更需要关注的空间单元。

1.2.4 Area Deprivation Index

Area Deprivation Index(ADI)是美国公共健康和社会流行病学研究中常用的地区剥夺指数,主要用于衡量社区层面的社会经济不利程度。ADI 通常基于人口普查或 ACS 中的收入、教育、就业、住房质量等变量构建,并将小区域按照相对剥夺程度进行排序。

ADI 的应用场景与健康研究联系较为紧密。例如,研究者常将 ADI 与疾病风险、医疗服务利用、死亡率或健康不平等等问题结合起来,分析社区社会经济条件如何影响个体健康结果。与 SVI 相比,ADI 更集中于邻里社会经济剥夺;与 IMD 相比,它的领域结构通常更聚焦,主要服务于美国社区尺度的健康与社会不平等研究。

1.2.5 Multidimensional Poverty Index

Multidimensional Poverty Index(MPI)是国际发展研究中常见的多维贫困指数,通常用于识别个体或家庭在多个生活维度上的同时匮乏。与只依据收入或消费水平衡量贫困的方法不同,MPI 通常将贫困理解为健康、教育和生活条件等多个维度的叠加结果。

MPI 的意义在于,它强调贫困并不只是收入不足,而是一组生活机会和基本能力的缺失。例如,一个家庭即使收入略高于贫困线,也可能在教育、营养、住房或基本设施方面面临明显不足。由此可见,综合指数能够帮助研究者从单一经济指标之外理解更复杂的社会不平等。

1.2.6 小结:不同指数服务于不同问题

CI、IMD、SVI、ADI 与 MPI 都属于综合指数,但它们的概念目标、指标体系与应用场景并不相同。CI 更强调结构简洁与可解释性,适合展示物质剥夺的基本空间差异;IMD 覆盖领域更广,更适合政策监测与资源分配;SVI 更关注灾害和公共健康事件中的社会脆弱性识别;ADI 侧重社区社会经济剥夺与健康不平等;MPI 则强调个体或家庭层面的多维贫困。

因此,综合指数并不存在单一的“最佳形式”。更关键的问题是:指数所衡量的概念是否清楚,指标选择是否与研究目标匹配,计算方法是否透明,以及最终结果是否能够被合理解释。

知识小卡片:三类常见标准化方法

综合指数构建几乎总是需要标准化。原因很简单:不同指标的量纲、范围和分布形态不同,若直接相加,量级更大的变量会不成比例地主导结果。

综合指数构建中的常见标准化方法
方法 公式或思想 适用场景 主要风险
Z-score (x - mean) / sd 强调相对均值的偏离程度,适合近似连续且希望保留距离信息的变量 受极端值影响较大,结果可能为负,不够直观
Min-Max (x - min) / (max - min) 需要 0-1 直观区间,便于可视化和向公众沟通 极端值会压缩大多数观测的差异
Rank / Percentile 将数值转化为排名或百分位 强偏态、极端值多或官方指数强调稳健性时常用 会损失实际距离信息

1.3 综合指数构建的一般流程

方法框架

综合指数构建通常可以概括为若干相互衔接的步骤。不同指数在技术细节上会有所差异,但较为通用的流程包括:明确研究目标、建立概念框架、选择指标、处理数据、标准化与赋权、聚合计算,以及结果解释与稳健性检验。这样的流程有助于检查一个指数是否具有基本的理论一致性技术可复现性结果可解释性

  1. 明确用途、尺度与分析单元
    首先需要判断指数的主要用途:是用于地区排名、政策识别、风险预警、资源分配,还是用于学术解释。同时还需要明确分析单元,例如国家、省、市、社区、普查区或功能区。空间尺度和分析单元会直接影响结果,也可能引入 MAUP(Modifiable Areal Unit Problem,可变面域单元问题)

  2. 建立概念框架
    明确指数希望衡量的核心概念,并将其拆分为若干维度。例如,社会剥夺可以包含就业、住房、贫困和交通资源等维度;社会脆弱性可以包含社会经济、家庭结构、住房条件和交通可达性等维度。概念框架决定了后续指标选择的方向。

  3. 选择指标与获取数据
    每个维度都需要由可观测指标进行代理。指标选择应尽量满足数据可得、口径清晰、空间匹配、时间一致和方向明确等条件。若指数需要跨地区或跨年份比较,数据口径的一致性尤其重要。

  4. 数据预处理与指标重构
    原始数据通常不能直接进入指数计算。常见处理包括缺失值检查、异常值处理、计数值转化为比率或密度、变量方向调整,以及必要时进行对数变换或截尾处理。对于人口普查数据,许多变量需要先重构为比例指标,才能用于不同地区之间的比较。

  5. 标准化与权重设定
    不同指标往往具有不同量纲和分布范围,因此需要通过 Z-score、Min-Max、排名或百分位等方法转化到可比较尺度。同时,还需要设定不同指标或维度的权重。常见权重方案包括等权、专家权重、AHP、PCA 或因子分析权重。权重不仅是技术选择,也体现了对概念结构的判断。

  6. 指标聚合与指数计算
    聚合是将多个标准化指标合成为综合指数的过程。最常见的方法是加法线性聚合,即对各指标加权求和;也可以使用几何平均等方法,以减少不同维度之间的完全补偿效应。聚合方式应与指数的理论含义保持一致。

  7. 结果解释、可视化与稳健性检验
    指数结果通常需要通过地图、排名、趋势图和分维度分解图进行解释。与此同时,还应检查结果是否对指标选择、标准化方法、权重方案或空间尺度变化过于敏感。敏感性分析(sensitivity analysis)不确定性分析(uncertainty analysis)可以帮助判断主要结论是否稳定。

需要注意的是,综合指数构建并不一定是一次性完成的线性流程。若结果难以解释、某些指标主导性过强,或不同权重方案下结论差异明显,通常需要回到概念框架、指标选择或标准化方法中重新检查。指数构建的关键不只是得到一个数值结果,而是形成一套透明、可解释且能够接受检验的分析过程。

Figure 3. 综合指数构建的一般流程:从研究目标到结果解释与稳健性检验

Figure 3. 综合指数构建的一般流程:从研究目标到结果解释与稳健性检验

方法提示:权重是技术问题,也是理论问题

在指数构建中,权重设定经常是最容易引发争议的环节。

  • 等权:最透明,适合教学演示和指标数量较少的指数,但隐含了“每个维度同等重要”的假设。
  • 专家权重 / AHP:适合政策目标明确、需要专家共识的场景,但可能引入主观判断差异。
  • PCA / 因子分析权重:由数据的协方差结构决定权重,适合探索性研究,但“统计上解释方差多”并不等于“政策上更重要”。

因此,在综合指数研究中,最重要的不是找到一个永远正确的权重,而是明确说明:为什么这样设定权重,以及如果更换权重方案,主要结论是否仍然稳定。

延伸阅读

以下资料可用于进一步了解综合指数构建的基本方法、经典剥夺指数以及社会脆弱性指数的实际应用:

2.0 综合指数构建实战 1

2.0 专题研究 1

专题研究 1: 美国主要城市 CI 年度动态分析

专题导航

美国主要城市社会剥夺指数动态分析

“从多年份 ACS 指标到城市剥夺指数轨迹”

本专题基于美国 ACS 1-year 数据,构建主要城市的社会剥夺指数(CI),并比较其年度变化。主要流程包括研究对象界定、变量获取、指标重构、标准化、指数计算与动态比较。

点击下方卡片,快速跳转至相应模块:

💡 提示:学习完一个小节后,请再次点击 屏幕右下角的专题导航按钮回到本导航页

研究课题
2.1 研究课题与分析目标

实战教学

本专题关注美国主要城市社会剥夺水平的年度动态变化。核心问题是:如何利用 ACS 1-year estimates 中可持续获取的城市数据,将多个社会经济与居住条件指标整合为一个能够跨城市、跨年份比较的综合指数?

城市社会剥夺通常具有多维特征。贫困、失业、住房拥挤和交通资源不足可能在不同城市中以不同方式组合出现。单一指标难以充分反映这种差异,也不利于比较不同城市在较长时期内的相对变化。因此,本专题以 ACS place 地理单元为基础,将纽约(New York)、洛杉矶(Los Angeles)、芝加哥(Chicago)、休斯敦(Houston)、凤凰城(Phoenix)、费城(Philadelphia)、圣安东尼奥(San Antonio)、圣迭戈(San Diego)、达拉斯(Dallas)、圣何塞(San Jose)等主要城市纳入同一分析框架,并进一步扩展到 ACS 1-year 可持续观测的全美主要城市。

本专题的重点是构建一个城市社会剥夺指数面板:以城市为分析单元,以年份为时间维度,观察哪些城市长期处于较高社会剥夺水平,哪些城市出现明显变化,以及这些变化主要来自贫困、失业、住房拥挤还是无车家庭比例等具体维度。


示例课题

研究课题
美国主要城市社会剥夺指数的年度动态变化:基于 ACS 1-year estimates 的 Carstairs-style CI 构建

研究背景与目标
城市社会剥夺并不只表现为贫困率高低。住房拥挤、就业脆弱、交通资源不足和贫困状态可能共同影响城市居民的生活机会。通过构建一个结构清晰、计算透明的综合指数,可以围绕以下问题展开分析:

  1. 城市间差异
    在全美主要城市中,哪些城市长期处于较高社会剥夺水平?哪些城市相对较低?
  2. 年度动态变化
    从 2012 到 2024 年,不同城市的 CI 是否呈现改善、恶化或相对稳定状态?
  3. 城市排名与轨迹比较
    纽约、洛杉矶、芝加哥、休斯敦等大城市之间的 CI 轨迹是否趋同?是否存在明显分化?
  4. 指数构成诊断
    某个城市 CI 偏高,主要来自贫困、失业、住房拥挤,还是无车家庭比例较高?

本专题的分析流程包括:将社会剥夺概念拆分为若干维度,将 ACS 计数变量重构为比例指标,进行跨年份标准化与指数聚合,并通过地图、趋势图和维度分解图解释指数结果。

为什么使用 ACS 1-year?

ACS 1-year estimates 提供按年度更新的社会经济与人口统计数据,适合用于城市层面的时间序列比较。需要注意的是,ACS 1-year 通常只覆盖人口规模较大的地理单元,因此本专题的研究对象并不是美国所有城市,而是ACS 1-year 中可持续观测的主要城市

截至本章撰写时,美国人口普查局已经发布 2024 ACS 1-year estimates。本专题的分析年份设置为 2012:20192021:2024。其中,2020 年 ACS 1-year estimates 未以常规形式发布,因此不纳入本章的年度序列。

指数定义

本专题构建的 CI 是一个基于 Carstairs Index 思路改写的城市社会剥夺指数。为保证城市层面跨年份计算的可复现性,CI 采用四个可由 ACS 直接重构的维度:

本专题 CI 的四个核心维度与 ACS 变量来源
维度 操作化定义 ACS表格 方向
住房拥挤 >1人/房间的住户比例 B25014 越高表示剥夺越严重
就业剥夺 民用劳动力中的失业人口比例 B23025 越高表示剥夺越严重
无车家庭 无可用车辆家庭比例 B08201 越高通常表示剥夺越严重
贫困 贫困线以下人口比例 B17001 越高表示剥夺越严重

说明
原始 Carstairs Index 使用“男性失业率”作为就业剥夺指标。本专题采用 B23025 构造总体失业率,主要是因为该表结构更简洁,便于在城市层面进行多年份批量重构。若需要更严格地复刻原始 Carstairs Index,可进一步使用 B23001,并按性别与年龄分组加总男性劳动力与男性失业人口。

变量选择与数据获取
2.2 变量选择与数据获取

环境配置

本专题使用 tidycensus 批量获取 ACS 数据,使用 sftigris 处理空间边界,使用 tidyverse 完成数据整理与可视化。

# 如首次使用,请先在 Console 中安装:
# install.packages(c("tidycensus", "tidyverse", "sf", "tigris", "ggspatial", "patchwork", "scales", "forcats"))

library(tidycensus)
library(tidyverse)
library(sf)
library(tigris)
library(ggspatial)
library(patchwork)
library(scales)
library(forcats)

options(tigris_use_cache = TRUE)

API Key 设置

使用 Census API 建议申请免费 key。若已经在本机设置过,可跳过以下代码。

# 访问 https://api.census.gov/data/key_signup.html 申请免费 API Key
# 将 YOUR_API_KEY_HERE 替换为自己的 key
census_api_key("YOUR_API_KEY_HERE", install = TRUE, overwrite = TRUE)

# 设置后建议重启 R Session,然后检查:
Sys.getenv("CENSUS_API_KEY")

定义年份、城市样本与变量

本专题使用 ACS 1-year estimates 构建城市年度面板,并在时间序列中排除 2020 年。分析单元采用 Census place 地理单元;样本筛选时保留名称中包含 city、最新年份人口不少于 100,000 人,且在所有研究年份中均可观测的城市。

# ============================================================
# 1. 研究参数设定
# ============================================================
years <- c(2012:2019, 2021:2024)
latest_year <- max(years)
first_year <- min(years)
min_city_population <- 100000

# 为后续示例折线图预设若干主要城市
focus_cities <- c(
  "New York, New York",
  "Los Angeles, California",
  "Chicago, Illinois",
  "Houston, Texas",
  "Phoenix, Arizona",
  "Philadelphia, Pennsylvania",
  "San Antonio, Texas",
  "San Diego, California",
  "Dallas, Texas",
  "San Jose, California"
)

# ============================================================
# 2. ACS 变量设定
# ============================================================
acs_vars <- c(
  # 人口规模:用于城市样本筛选
  total_pop = "B01003_001",
  
  # 住房拥挤:B25014 Occupants per Room
  occ_total = "B25014_001",
  occ_owner_101_150 = "B25014_005",
  occ_owner_151_200 = "B25014_006",
  occ_owner_201_plus = "B25014_007",
  occ_renter_101_150 = "B25014_011",
  occ_renter_151_200 = "B25014_012",
  occ_renter_201_plus = "B25014_013",
  
  # 总失业率:B23025 Employment Status
  civilian_labor = "B23025_003",
  unemployed = "B23025_005",
  
  # 无车家庭:B08201 Household Size by Vehicles Available
  hh_vehicle_total = "B08201_001",
  hh_no_vehicle = "B08201_002",
  
  # 贫困率:B17001 Poverty Status in the Past 12 Months
  poverty_universe = "B17001_001",
  poverty_below = "B17001_002"
)

本专题设定的分析年份为 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2021, 2022, 2023, 2024,共 12 个 ACS 1-year 年份。需要注意的是,2020 年 ACS 1-year 数据受到 COVID-19 疫情对调查采集过程的影响,美国人口普查局没有发布标准的 2020 ACS 1-year estimates,而是发布了 experimental estimates。因此,为保持时间序列口径的一致性,本专题在分析中跳过 2020 年。城市样本门槛设为最新年份人口不少于 100,000 人;后续将从 ACS 中提取 14 个原始表项字段,并重构为 4 个 CI 维度指标


本地缓存与重试机制

国内网络环境下,访问 Census API 或下载美国地理边界时可能遇到连接中断、响应超时或单次请求失败。为减少这类问题对课堂练习的影响,本专题将数据下载拆分为“属性数据”和“地理边界”两部分,并把成功下载的结果保存到 data/ci_cache/。只要本地 RDS 文件存在,后续 knit 会优先读取缓存,不必每次重新访问远程服务器。

# ============================================================
# 本地 RDS 缓存与网络重试工具
# ============================================================
ci_cache_dir <- file.path("data", "ci_cache")
dir.create(ci_cache_dir, recursive = TRUE, showWarnings = FALSE)

# 如需强制重新下载,可临时改为 TRUE
ci_refresh_cache <- FALSE
ci_download_retries <- 3
ci_retry_wait <- 5

ci_cache_file <- function(name) {
  file.path(ci_cache_dir, paste0(name, ".rds"))
}

ci_download_or_cache <- function(cache_file,
                                 label,
                                 downloader,
                                 refresh = ci_refresh_cache,
                                 retries = ci_download_retries,
                                 wait = ci_retry_wait,
                                 stop_on_error = TRUE) {
  # 1. 优先读取本地缓存
  if (file.exists(cache_file) && !refresh) {
    return(readRDS(cache_file))
  }

  # 2. 缓存缺失或强制刷新时,尝试联网下载
  last_error <- NULL
  for (attempt in seq_len(retries)) {
    value <- tryCatch(
      downloader(),
      error = function(e) {
        last_error <<- e
        NULL
      }
    )

    if (!is.null(value)) {
      saveRDS(value, cache_file)
      return(value)
    }

    # 网络短时波动时,稍等后再试
    if (attempt < retries) {
      Sys.sleep(wait * attempt)
    }
  }

  # 3. 强制刷新失败时,若旧缓存仍在,则回退到旧缓存
  if (file.exists(cache_file)) {
    warning("下载失败,已改用已有本地缓存:", label, call. = FALSE)
    return(readRDS(cache_file))
  }

  error_text <- if (is.null(last_error)) {
    "未知错误"
  } else {
    conditionMessage(last_error)
  }

  message_text <- paste0(
    "无法获取 ", label,
    "。可稍后重新运行,或请老师提供 data/ci_cache/ 中对应的 RDS 缓存文件。最后错误:",
    error_text
  )

  if (stop_on_error) {
    stop(message_text, call. = FALSE)
  }

  warning(message_text, call. = FALSE)
  NULL
}

为什么需要本地 RDS 缓存?

R Markdown 的 cache=TRUE 可以减少重复运行代码块,但它仍然依赖当前文档的缓存状态;一旦学生清理缓存、修改代码块或换电脑运行,就可能重新触发远程下载。本专题额外保存 data/ci_cache/*.rds,相当于把已经成功下载的数据固定到项目文件夹中。下载相关代码块会在 knit 时运行,但本地缓存存在时只读取 RDS,不再访问远程服务器。这样既保留了通过代码自动下载的可重复性,也降低了网络波动导致整章 knit 失败的概率。


批量下载 ACS 1-year 数据

下面的代码会对每个年份分别调用一次 get_acs(),并把每一年的结果单独保存为 RDS 文件。若某些年份已经下载成功,后续运行会直接读取对应缓存;即使某次网络中断,也不需要从头重新下载所有年份。

需要注意的是,如果修改了分析年份、ACS 变量、地理单元或样本筛选条件,应删除 data/ci_cache/ 中对应的 RDS 文件,或将 ci_refresh_cache 临时设为 TRUE 后重新运行。

# ============================================================
# 3. 多年份 ACS 1-year place 属性数据下载
# ============================================================
raw_city_acs <- map_dfr(years, function(yr) {
  # 每个年份对应一个 RDS 文件,避免单次失败后全部重下
  year_cache <- ci_cache_file(paste0("acs_place_", yr))

  ci_download_or_cache(
    cache_file = year_cache,
    label = paste0("ACS 1-year place 属性数据 ", yr),
    downloader = function() {
      get_acs(
        geography = "place",
        variables = acs_vars,
        year = yr,
        survey = "acs1",
        output = "wide",
        geometry = FALSE
      ) %>%
        mutate(year = yr) # 保留年份字段,便于构造城市面板
    }
  )
})

下载完成后,原始 ACS 数据包含 7,406 行、31 列,覆盖 12 个年份。由于 ACS 1-year 主要覆盖人口规模较大的地理单元,不同年份中可获取的 place 数量可能并不完全一致。为了进行跨年份比较,后续需要进一步筛选出在所有研究年份中均可观测的城市样本。

下方代码对原始数据按年份进行汇总,不仅统计每一年下载到的 place 记录数,也进一步区分名称中包含 city 的记录、达到人口门槛的城市记录和 place 人口中位数。这个表格可用于检查各年份的数据规模是否一致,并为后续构建平衡城市面板提供依据。

raw_city_acs %>%
  group_by(year) %>%
  summarise(
    # 区分原始 place 记录、城市记录与人口门槛初筛结果
    `全部 place 记录` = n_distinct(GEOID),
    `名称含 city 记录` = sum(str_detect(NAME, " city,"), na.rm = TRUE),
    `city 且人口>=10万` = sum(
      str_detect(NAME, " city,") & total_popE >= min_city_population,
      na.rm = TRUE
    ),
    `place 人口中位数` = round(median(total_popE, na.rm = TRUE)),
    .groups = "drop"
  ) %>%
  mutate(across(-year, comma)) %>%
  kable(
    caption = "表:各年份 ACS 1-year place 数据下载与初筛概况",
    align = "crrrr",
    booktabs = TRUE
  ) %>%
  kable_styling(full_width = FALSE, position = "center", font_size = 12)
表:各年份 ACS 1-year place 数据下载与初筛概况
year 全部 place 记录 名称含 city 记录 city 且人口>=10万 place 人口中位数
2012 568 506 280 104,070
2013 582 519 284 103,784
2014 591 525 284 103,486
2015 596 530 290 104,945
2016 605 540 291 105,171
2017 614 546 293 105,052
2018 630 559 298 104,411
2019 634 563 301 103,376
2021 634 563 317 105,419
2022 646 572 317 104,932
2023 649 574 321 104,884
2024 657 581 330 104,912
数据预处理
2.3 数据预处理:比率重构与城市样本筛选

从计数到比率

ACS 详细表通常以计数值形式返回数据,例如某类人口数量、某类家庭数量或某类住房数量。由于不同城市的人口规模差异很大,原始计数值不能直接用于城市之间比较。因此,在构建指数之前,需要先将相关计数值重构为比率变量。

例如,贫困率可以由贫困线以下人口数除以具有贫困状态判定的人口总数得到;无车家庭比例可以由无可用车辆家庭数除以家庭总数得到。通过这种处理,不同规模城市之间的社会经济特征才具有更好的可比性。

# ============================================================
# 4. 原始计数转化为四个 CI 比率变量
# ============================================================
ci_rates_all <- raw_city_acs %>%
  mutate(
    # 解析城市名与州名
    city = str_remove(NAME, " city,.*$"),
    state_name = str_remove(NAME, "^.*, "),
    city_state = paste0(city, ", ", state_name),
    
    # 人口用于最新年份样本门槛与表格展示
    population = total_popE,
    
    # 维度 1:住房拥挤,>1 人/房间
    overcrowd_num = occ_owner_101_150E + occ_owner_151_200E + occ_owner_201_plusE +
      occ_renter_101_150E + occ_renter_151_200E + occ_renter_201_plusE,
    overcrowd_rate = overcrowd_num / occ_totalE,
    
    # 维度 2:失业率
    unemployment_rate = unemployedE / civilian_laborE,
    
    # 维度 3:无车家庭比例
    no_vehicle_rate = hh_no_vehicleE / hh_vehicle_totalE,
    
    # 维度 4:贫困率
    poverty_rate = poverty_belowE / poverty_universeE
  ) %>%
  mutate(
    # 清理分母为 0 或缺失导致的无效比率
    across(
      c(overcrowd_rate, unemployment_rate, no_vehicle_rate, poverty_rate),
      ~ if_else(is.nan(.x) | is.infinite(.x), NA_real_, .x)
    )
  ) %>%
  select(
    GEOID, NAME, city, state_name, city_state, year, population,
    overcrowd_rate, unemployment_rate, no_vehicle_rate, poverty_rate
  )

# ============================================================
# 5. 城市样本筛选
# ============================================================
# 以最新年份人口门槛界定候选城市
latest_city_ids <- ci_rates_all %>%
  filter(
    year == latest_year,
    str_detect(NAME, " city,"),
    population >= min_city_population
  ) %>%
  pull(GEOID)

# 只保留所有研究年份都可观测的城市
complete_city_ids <- ci_rates_all %>%
  filter(GEOID %in% latest_city_ids) %>%
  count(GEOID, name = "n_years") %>%
  filter(n_years == length(years)) %>%
  pull(GEOID)

ci_rates <- ci_rates_all %>%
  filter(GEOID %in% complete_city_ids) %>%
  drop_na(overcrowd_rate, unemployment_rate, no_vehicle_rate, poverty_rate)

完成筛选后,数据中保留 319 个美国主要城市,形成 3,382 个城市-年份观测。由于每个城市都覆盖 12 个研究年份,该数据集可以被视为适合开展年度趋势比较的平衡城市面板(balanced city panel)


多年份数据预览

在正式计算 CI 之前,可以先查看若干代表性年份和主要城市的原始比率指标。下表展示的是尚未标准化的四个底层指标,包括贫困率、失业率、住房拥挤率和无车家庭比例。该预览有助于理解:综合指数并不是直接从原始计数生成,而是建立在一组经过重构的可比较比例指标之上。

preview_years <- c(first_year, 2016, 2019, 2021, latest_year)
preview_cities <- focus_cities[1:6]

ci_rates %>%
  filter(year %in% preview_years, city_state %in% preview_cities) %>%
  arrange(city_state, year) %>%
  transmute(
    City = city_state,
    Year = year,
    Population = comma(population),
    Overcrowding = percent(overcrowd_rate, accuracy = 0.1),
    Unemployment = percent(unemployment_rate, accuracy = 0.1),
    `No vehicle` = percent(no_vehicle_rate, accuracy = 0.1),
    Poverty = percent(poverty_rate, accuracy = 0.1)
  ) %>%
  kable(
    caption = "表:部分主要城市在多个年份的 CI 原始维度比率预览",
    align = "lcrrrrr",
    booktabs = TRUE
) %>%
  kable_styling(full_width = FALSE, position = "center", font_size = 11)
表:部分主要城市在多个年份的 CI 原始维度比率预览
City Year Population Overcrowding Unemployment No vehicle Poverty
Chicago, Illinois 2012 2,714,844 4.5% 13.7% 27.9% 23.9%
Chicago, Illinois 2016 2,704,965 3.8% 8.1% 27.5% 19.1%
Chicago, Illinois 2019 2,693,959 3.4% 6.3% 27.8% 16.4%
Chicago, Illinois 2021 2,696,561 3.4% 10.1% 24.9% 17.1%
Chicago, Illinois 2024 2,721,326 3.6% 7.3% 27.4% 16.6%
Houston, Texas 2012 2,161,686 6.6% 9.7% 10.1% 23.5%
Houston, Texas 2016 2,304,388 7.2% 6.5% 8.1% 20.8%
Houston, Texas 2019 2,316,797 6.3% 5.4% 9.3% 19.7%
Houston, Texas 2021 2,287,047 7.7% 8.4% 10.3% 19.4%
Houston, Texas 2024 2,387,910 7.4% 6.4% 10.8% 21.2%
Los Angeles, California 2012 3,857,786 13.3% 12.2% 13.6% 23.3%
Los Angeles, California 2016 3,976,324 13.2% 6.7% 12.2% 19.5%
Los Angeles, California 2019 3,979,537 12.9% 5.3% 12.1% 16.7%
Los Angeles, California 2021 3,849,306 12.6% 10.7% 11.6% 17.1%
Los Angeles, California 2024 3,878,718 11.5% 6.7% 12.0% 16.0%
New York, New York 2012 8,336,697 8.8% 10.6% 56.5% 21.2%
New York, New York 2016 8,537,673 9.3% 6.8% 54.4% 18.9%
New York, New York 2019 8,336,817 8.7% 5.2% 55.4% 16.0%
New York, New York 2021 8,467,513 9.1% 11.8% 53.9% 18.0%
New York, New York 2024 8,478,072 9.2% 6.9% 56.7% 18.0%
Philadelphia, Pennsylvania 2012 1,547,607 2.7% 15.9% 32.6% 26.9%
Philadelphia, Pennsylvania 2016 1,567,872 2.8% 9.6% 29.5% 25.7%
Philadelphia, Pennsylvania 2019 1,584,064 3.2% 8.2% 29.4% 23.3%
Philadelphia, Pennsylvania 2021 1,576,251 3.2% 10.5% 27.9% 22.8%
Philadelphia, Pennsylvania 2024 1,573,916 2.3% 5.9% 27.9% 19.7%
Phoenix, Arizona 2012 1,488,759 6.3% 9.7% 8.5% 24.1%
Phoenix, Arizona 2016 1,615,041 7.2% 5.9% 8.4% 20.3%
Phoenix, Arizona 2019 1,680,988 6.3% 4.4% 7.3% 15.6%
Phoenix, Arizona 2021 1,624,539 6.3% 5.9% 6.1% 14.9%
Phoenix, Arizona 2024 1,673,122 5.8% 4.1% 7.4% 12.0%

原始指标动态可视化

下图进一步展示四个底层指标的年度变化。图中彩色实线表示选定主要城市的指标轨迹,灰色虚线表示样本中所有城市的年度平均水平。需要注意的是,这里展示的仍然是 CI 构建之前的原始比率指标,尚未经过标准化与聚合。不同指标在不同城市中的变化方向和幅度并不完全一致,这也是后续需要构建综合指数的重要原因。

# ============================================================
# 原始指标趋势图:选定城市轨迹 + 全样本城市均值参考线
# ============================================================

rate_trend_data <- ci_rates %>%
  filter(city_state %in% preview_cities) %>%
  select(city_state, year, overcrowd_rate, unemployment_rate, no_vehicle_rate, poverty_rate) %>%
  pivot_longer(
    cols = ends_with("_rate"),
    names_to = "indicator",
    values_to = "value"
  ) %>%
  mutate(
    indicator = recode(
      indicator,
      overcrowd_rate = "Overcrowding",
      unemployment_rate = "Unemployment",
      no_vehicle_rate = "No vehicle households",
      poverty_rate = "Poverty"
    ),
    city_label = str_remove(city_state, ",.*$")
  )

# 计算所有样本城市的年度均值,作为参考基准线
rate_reference <- ci_rates %>%
  select(year, overcrowd_rate, unemployment_rate, no_vehicle_rate, poverty_rate) %>%
  pivot_longer(
    cols = ends_with("_rate"),
    names_to = "indicator",
    values_to = "value"
  ) %>%
  mutate(
    indicator = recode(
      indicator,
      overcrowd_rate = "Overcrowding",
      unemployment_rate = "Unemployment",
      no_vehicle_rate = "No vehicle households",
      poverty_rate = "Poverty"
    )
  ) %>%
  group_by(year, indicator) %>%
  summarise(
    mean_value = mean(value, na.rm = TRUE),
    .groups = "drop"
  )

# 提取每条城市线的最新年份位置,用于右侧直接标注
rate_labels <- rate_trend_data %>%
  filter(year == latest_year)

city_cols <- c(
  "New York, New York" = "#1b9e77",
  "Los Angeles, California" = "#d95f02",
  "Chicago, Illinois" = "#7570b3",
  "Houston, Texas" = "#e7298a",
  "Phoenix, Arizona" = "#66a61e",
  "Philadelphia, Pennsylvania" = "#e6ab02"
)

ggplot() +
  # 2020 年 ACS 1-year 常规数据缺口
  annotate(
    "rect",
    xmin = 2019.5, xmax = 2020.5,
    ymin = -Inf, ymax = Inf,
    fill = "grey92", alpha = 0.65
  ) +
  
  # 全样本城市年度均值参考线
  geom_line(
    data = rate_reference,
    aes(x = year, y = mean_value),
    color = "grey35",
    linetype = "dashed",
    linewidth = 0.85
  ) +
  
  # 选定城市年度轨迹
  geom_line(
    data = rate_trend_data,
    aes(x = year, y = value, color = city_state, group = city_state),
    linewidth = 0.85,
    alpha = 0.90
  ) +
  geom_point(
    data = rate_trend_data,
    aes(x = year, y = value, color = city_state),
    size = 1.7,
    alpha = 0.90
  ) +
  
  # 最新年份城市名称直接标注
  geom_text(
    data = rate_labels,
    aes(x = latest_year + 0.25, y = value, label = city_label, color = city_state),
    hjust = 0,
    size = 3.0,
    show.legend = FALSE
  ) +
  
  facet_wrap(~ indicator, ncol = 2, scales = "free_y") +
  scale_color_manual(values = city_cols, name = "Selected city") +
  scale_x_continuous(
    breaks = c(2012, 2014, 2016, 2018, 2020, 2022, 2024),
    limits = c(first_year, latest_year + 1.4)
  ) +
  scale_y_continuous(labels = label_percent(accuracy = 1)) +
  coord_cartesian(clip = "off") +
  labs(
    title = "Raw Indicator Trends before CI Construction",
    subtitle = "Colored lines show selected cities; dashed grey line shows the annual mean across all sample cities",
    x = "Year",
    y = "Rate",
    caption = "Grey band indicates the omitted 2020 ACS 1-year period"
  ) +
  theme_minimal(base_size = 11) +
  theme(
    plot.title = element_text(face = "bold", size = 14, hjust = 0),
    plot.subtitle = element_text(size = 9.5, color = "grey45", margin = margin(b = 10)),
    plot.caption = element_text(size = 8, color = "grey45", hjust = 0),
    axis.title = element_text(face = "bold", size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1, size = 8),
    legend.position = "bottom",
    legend.title = element_text(face = "bold", size = 9),
    legend.text = element_text(size = 8),
    panel.grid.minor = element_blank(),
    strip.text = element_text(face = "bold", size = 10),
    plot.margin = margin(10, 40, 10, 10)
  )

为什么筛选平衡城市面板?

本专题关注城市 CI 的年度动态变化。若某些城市只在部分年份出现,趋势图和变化量比较可能同时受到真实变化与样本进出影响,进而削弱跨年份解释的清晰度。因此,本专题保留在所有研究年份中均可观测、且最新年份人口不少于 100,000 人的城市,构建平衡城市面板(balanced city panel)

这种处理有助于保持年度比较的一致性,但也会排除部分新近超过人口门槛、或只在部分年份满足 ACS 1-year 发布条件的城市。因此,后续结果应理解为对“ACS 1-year 可持续观测的美国主要城市”的分析,而不是对美国全部城市的完整覆盖。

关于无车家庭指标的解释边界

需要特别说明的是,no vehicle 或“无私家车家庭占比”并不总是可以直接解释为交通剥夺。在纽约市等公共交通系统高度发达、城市密度较高的地区,较高的无车比例可能更多反映居民对公共交通、步行和共享出行的依赖,而不一定意味着出行能力不足。

因此,在后续构建 CI 指数时,应避免将该指标机械地等同于 deprivation。对于公共交通供给较强的城市,需要结合交通基础设施、通勤方式结构和城市密度进行解释;必要时,也可以在指数计算中进行敏感性检验,或对纽约市等典型城市作单独说明。

综合指数构建
2.4 综合指数构建:Pooled Z-score 与等权聚合

关键方法

本专题关注城市 CI 在研究期内的年度变化,因此标准化过程需要采用统一的比较基准。若按年份分别计算 Z-score,每一年的均值都会被重新设为 0,标准差被重新设为 1,所得结果主要反映某城市在当年样本中的相对位置。这种方法适合比较同一年城市之间的差异,但不利于判断城市相对于整个研究期总体分布的变化。

因此,本节采用 Pooled Z-score。具体而言,将平衡城市面板中所有城市、所有年份的观测值合并,统一计算每个维度的总体均值和总体标准差,再对每一个“城市-年份”观测值进行标准化。这样可以使不同年份的得分处在同一尺度下,从而更适合用于指数构建和年度动态比较。

\[Z_{ict} = \frac{X_{ict} - \bar{X}_{c, pooled}}{s_{c, pooled}}\]

其中,\(i\) 表示城市,\(c\) 表示指标维度,\(t\) 表示年份。这样得到的 Z 值表示某城市在某一年相对于全时期美国主要城市总体分布的偏离程度,更适合做年度趋势比较。

# ============================================================
# 6. Pooled Z-score 标准化
# ============================================================
z_ref <- ci_rates %>%
  # 用全时期样本统一估计每个维度的均值和标准差
  summarise(
    across(
      c(overcrowd_rate, unemployment_rate, no_vehicle_rate, poverty_rate),
      list(mean = ~ mean(.x, na.rm = TRUE), sd = ~ sd(.x, na.rm = TRUE)),
      .names = "{.col}_{.fn}"
    )
  )

ci_final <- ci_rates %>%
  mutate(
    # 将四个比率映射到同一 pooled z-score 尺度
    z_overcrowd = (overcrowd_rate - z_ref$overcrowd_rate_mean) / z_ref$overcrowd_rate_sd,
    z_unemployment = (unemployment_rate - z_ref$unemployment_rate_mean) / z_ref$unemployment_rate_sd,
    z_no_vehicle = (no_vehicle_rate - z_ref$no_vehicle_rate_mean) / z_ref$no_vehicle_rate_sd,
    z_poverty = (poverty_rate - z_ref$poverty_rate_mean) / z_ref$poverty_rate_sd,
    
    # 等权加总:四个维度方向均为越高越剥夺
    CI = z_overcrowd + z_unemployment + z_no_vehicle + z_poverty
  ) %>%
  group_by(year) %>%
  mutate(
    # 排名仍按年份内部计算,便于年度横向比较
    CI_rank = rank(desc(CI), ties.method = "min"),
    CI_percentile = percent_rank(CI)
  ) %>%
  ungroup()

完成 pooled Z-score 标准化与等权聚合后,ci_final 共包含 3,382 个城市-年份观测。CI 的取值范围为 -5.38 至 12.43。在各维度指标方向已统一的前提下,CI 数值越高,表示该城市-年份在所选四个维度上的相对综合剥夺水平越高。


指数结果预览

ci_final %>%
  filter(year == latest_year) %>%
  arrange(desc(CI)) %>%
  slice_head(n = 15) %>%
  transmute(
    排名 = CI_rank,
    城市 = city_state,
    人口 = comma(population),
    CI = round(CI, 2),
    住房拥挤 = round(z_overcrowd, 2),
    失业 = round(z_unemployment, 2),
    无车 = round(z_no_vehicle, 2),
    贫困 = round(z_poverty, 2)
  ) %>%
  kable(
    caption = paste0("表:", latest_year, " 年 CI 最高的 15 个美国主要城市"),
    align = "rlrrrrrr",
    booktabs = TRUE
) %>%
  kable_styling(full_width = FALSE, position = "center", font_size = 11)
表:2024 年 CI 最高的 15 个美国主要城市
排名 城市 人口 CI 住房拥挤 失业 无车 贫困
1 Newark, New Jersey 317,303 8.54 2.78 1.67 3.26 0.83
2 New York, New York 8,478,072 7.59 0.97 0.04 6.32 0.26
3 Hartford, Connecticut 122,136 6.42 0.16 1.72 2.84 1.69
4 Salinas, California 160,779 5.99 7.13 -0.71 -0.41 -0.02
5 New Haven, Connecticut 137,556 4.91 -0.10 1.56 2.11 1.34
6 Jersey City, New Jersey 302,822 4.71 0.93 -0.78 4.58 -0.03
7 Detroit, Michigan 645,702 4.64 -0.66 1.22 1.31 2.76
8 Paterson, New Jersey 160,446 4.26 1.54 0.25 2.16 0.30
9 Providence, Rhode Island 194,710 4.26 0.57 1.07 1.15 1.46
10 Santa Maria, California 111,347 4.20 3.57 0.29 -0.33 0.68
11 Elizabeth, New Jersey 140,401 3.79 2.45 0.07 1.10 0.17
12 El Monte, California 104,621 3.66 4.41 -0.56 -0.06 -0.14
13 Washington, District of Columbia 702,250 3.32 -0.09 -0.36 3.61 0.16
14 Waterbury, Connecticut 115,902 3.12 0.30 0.04 1.32 1.46
15 Syracuse, New York 146,091 3.10 -0.49 0.16 1.95 1.48

指数构建后的初步动态

在进入地图和排名热力图之前,可以先查看部分主要城市的 CI 年度轨迹。与前一节的原始比率折线图相比,这里每条线已经综合了住房拥挤、失业、无车家庭和贫困四个维度。

ci_preliminary <- ci_final %>%
  filter(city_state %in% preview_cities) %>%
  ggplot(aes(x = year, y = CI, color = city_state, group = city_state)) +
  annotate(
    "rect",
    xmin = 2019.5, xmax = 2020.5,
    ymin = -Inf, ymax = Inf,
    fill = "grey90", alpha = 0.45
  ) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "grey60") +
  geom_line(linewidth = 1, alpha = 0.85) +
  geom_point(size = 2, alpha = 0.9) +
  scale_x_continuous(breaks = years) +
  scale_y_continuous(labels = label_number(accuracy = 0.1)) +
  theme_bw(base_size = 11) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5, size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1),
    legend.position = "bottom",
    legend.title = element_blank(),
    panel.grid.minor = element_blank()
  ) +
  labs(
    title = "Preliminary CI Trajectories for Selected Cities",
    subtitle = "CI combines four pooled z-score indicators with equal weights",
    x = "Year",
    y = "City Deprivation Index (CI)"
  )

ci_preliminary

年度变化量

ci_change <- ci_final %>%
  filter(year %in% c(first_year, latest_year)) %>%
  select(GEOID, city_state, year, CI) %>%
  pivot_wider(names_from = year, values_from = CI, names_prefix = "CI_") %>%
  mutate(
    CI_change = .data[[paste0("CI_", latest_year)]] - .data[[paste0("CI_", first_year)]],
    change_type = case_when(
      CI_change >= 1 ~ "上升明显",
      CI_change <= -1 ~ "下降明显",
      TRUE ~ "相对稳定"
    )
  )

ci_change %>%
  arrange(desc(CI_change)) %>%
  slice_head(n = 10) %>%
  transmute(
    城市 = city_state,
    `起始年CI` = round(.data[[paste0("CI_", first_year)]], 2),
    `最新年CI` = round(.data[[paste0("CI_", latest_year)]], 2),
    `变化量` = round(CI_change, 2),
    类型 = change_type
  ) %>%
  kable(
    caption = paste0("表:", first_year, "-", latest_year, " 年 CI 上升幅度最大的城市"),
    align = "lrrrl",
    booktabs = TRUE
) %>%
  kable_styling(full_width = FALSE, position = "center", font_size = 11)
表:2012-2024 年 CI 上升幅度最大的城市
城市 起始年CI 最新年CI 变化量 类型
Salinas, California 4.57 5.99 1.43 上升明显
Odessa, Texas -2.17 -1.19 0.98 相对稳定
Cambridge, Massachusetts 0.48 1.14 0.67 相对稳定
Baton Rouge, Louisiana 1.47 1.72 0.25 相对稳定
Naperville, Illinois -3.75 -3.52 0.23 相对稳定
Bellevue, Washington -2.50 -2.34 0.16 相对稳定
McKinney, Texas -3.78 -3.75 0.03 相对稳定
Irvine, California -1.75 -1.84 -0.09 相对稳定
Seattle, Washington -0.52 -0.62 -0.10 相对稳定
Torrance, California -1.70 -1.84 -0.15 相对稳定
空间分布与趋势分析
2.5 空间分布与趋势分析

空间分布图

下面首先绘制最新年份的 CI 空间分布。为了避免城市边界图形过度拥挤,本图将城市多边形转换为代表点,并叠加到美国本土州界上。颜色越红,表示 CI 越高,社会剥夺程度越高;颜色越蓝,表示 CI 越低。

地理边界下载提示
本节不再把属性数据与边界数据放在同一次 get_acs() 请求中,而是单独使用 tigris::places() 下载城市边界、使用 tigris::states() 下载州界。边界文件通常比属性表更大,受网络波动影响也更明显;因此代码会优先读取 data/ci_cache/ 中的本地 RDS 文件,只有缓存不存在时才联网下载。

# ============================================================
# 7. 单独获取最新年份城市空间边界与州界
# ============================================================
# 城市边界:只在缺少本地缓存时联网下载
place_boundaries <- ci_download_or_cache(
  cache_file = ci_cache_file(paste0("us_place_boundaries_", latest_year)),
  label = paste0(latest_year, " 年美国 Census place 边界"),
  downloader = function() {
    tigris::places(cb = TRUE, year = latest_year, class = "sf")
  },
  stop_on_error = FALSE
)

# 州界:与城市边界分开缓存,减少单次下载压力
state_boundaries <- ci_download_or_cache(
  cache_file = ci_cache_file(paste0("us_state_boundaries_", latest_year)),
  label = paste0(latest_year, " 年美国州界"),
  downloader = function() {
    tigris::states(cb = TRUE, year = latest_year, class = "sf")
  },
  stop_on_error = FALSE
)

ci_map_data_available <- !is.null(place_boundaries) && !is.null(state_boundaries)

if (ci_map_data_available) {
  city_geo <- place_boundaries %>%
    # 仅保留进入 CI 样本的城市,减少后续空间处理量
    filter(GEOID %in% unique(ci_final$GEOID)) %>%
    select(GEOID, geometry) %>%
    left_join(
      ci_final %>% filter(year == latest_year),
      by = "GEOID"
    ) %>%
    st_transform(5070)

  # 转为代表点,避免城市多边形在全国图上过度拥挤
  city_points <- city_geo %>%
    filter(!state_name %in% c("Alaska", "Hawaii", "Puerto Rico")) %>%
    st_point_on_surface()

  # 只保留美国本土州界,排除海外领地与非本土州
  us_states <- state_boundaries %>%
    filter(!STUSPS %in% c("AK", "HI", "PR", "VI", "GU", "MP", "AS")) %>%
    st_transform(5070)

  ci_map_data_available <- nrow(city_points) > 0 && nrow(us_states) > 0
} else {
  city_geo <- NULL
  city_points <- NULL
  us_states <- NULL
}

处理策略
如果边界缓存已经存在,上方代码不会访问远程服务器;如果缓存不存在但联网下载失败,后续地图代码会给出提示图,而不是让整章 knit 中断。这样可以保证趋势图、排名热力图和维度分解等非地图结果仍然能够继续生成。

if (!isTRUE(ci_map_data_available)) {
  # 边界不可用时输出提示图,避免整章 knit 中断
  ci_map <- ggplot() +
    annotate(
      "text",
      x = 0,
      y = 0.1,
      label = "地理边界暂不可用,已跳过 CI 空间分布图",
      size = 5,
      fontface = "bold",
      color = "#333333"
    ) +
    annotate(
      "text",
      x = 0,
      y = -0.1,
      label = "请检查网络连接,或将已下载的边界 RDS 文件放入 data/ci_cache/ 后重新 knit。",
      size = 3.6,
      color = "#666666"
    ) +
    xlim(-1, 1) +
    ylim(-1, 1) +
    theme_void(base_size = 11) +
    theme(
      plot.background = element_rect(fill = "#F8F9FA", color = NA),
      plot.margin = margin(20, 20, 20, 20)
    )
} else {
# 经纬网:先按经纬度生成,再转换到 EPSG 5070
us_graticule <- sf::st_graticule(
  sf::st_transform(us_states, 4326),
  lon = seq(-130, -65, by = 10),
  lat = seq(25, 50, by = 5)
) %>%
  sf::st_transform(5070)

# 发散色带以 0 为中心,保证正负方向视觉对称
ci_limit <- max(abs(city_points$CI), na.rm = TRUE)
ci_limit <- ifelse(is.finite(ci_limit) && ci_limit > 0, ci_limit, 1)

# 光晕强度:保留基础可见性,并随 |CI| 增强
city_points_plot <- city_points %>%
  mutate(
    ci_abs_scaled = pmin(abs(CI) / ci_limit, 1),
    ci_abs_scaled = dplyr::coalesce(ci_abs_scaled, 0),
    glow_outer_size = 5.4 + 2.2 * ci_abs_scaled,
    glow_inner_size = 3.0 + 1.3 * ci_abs_scaled,
    glow_outer_alpha = 0.05 + 0.08 * ci_abs_scaled,
    glow_inner_alpha = 0.08 + 0.10 * ci_abs_scaled
  )

ci_map <- ggplot() +
  geom_sf(
    data = us_graticule,
    color = "grey35",
    linewidth = 0.22,
    linetype = "dashed",
    alpha = 0.55
  ) +
  geom_sf(
    data = us_states,
    fill = "#111111",
    color = "grey30",
    linewidth = 0.25
  ) +
  # 外层光晕:弱背景增强,随 |CI| 增强
  geom_sf(
    data = city_points_plot,
    aes(
      color = CI,
      size = I(glow_outer_size),
      alpha = I(glow_outer_alpha)
    ),
    show.legend = FALSE
  ) +
  # 内层光晕:加强高偏离值城市的可见性
  geom_sf(
    data = city_points_plot,
    aes(
      color = CI,
      size = I(glow_inner_size),
      alpha = I(glow_inner_alpha)
    ),
    show.legend = FALSE
  ) +
  # 主数据层:颜色表示 CI,大小表示人口规模
  geom_sf(
    data = city_points_plot,
    aes(color = CI, size = population),
    alpha = 0.88
  ) +
  scale_color_gradient2(
    low = "#38BDF8",
    mid = "#F8FAFC",
    high = "#EF4444",
    midpoint = 0,
    limits = c(-ci_limit, ci_limit),
    oob = scales::squish,
    na.value = "grey65",
    name = "CI",
    labels = label_number(accuracy = 0.1),
    guide = guide_colorbar(
      barheight = grid::unit(4.2, "cm"),
      barwidth = grid::unit(0.35, "cm"),
      frame.colour = "grey45",
      ticks.colour = "grey70"
    )
  ) +
  scale_size_continuous(
    name = "Population",
    labels = label_number(scale_cut = cut_short_scale()),
    range = c(1.3, 5.8),
    guide = guide_legend(
      override.aes = list(color = "grey85", alpha = 0.85)
    )
  ) +
  annotation_scale(
    location = "bl",
    width_hint = 0.25,
    style = "ticks",
    text_cex = 0.75,
    text_col = "grey80",
    line_col = "grey80",
    pad_x = grid::unit(0.3, "cm"),
    pad_y = grid::unit(0.3, "cm")
  ) +
  annotation_north_arrow(
    location = "br",
    which_north = "true",
    height = grid::unit(0.9, "cm"),
    width = grid::unit(0.9, "cm"),
    pad_x = grid::unit(0.35, "cm"),
    pad_y = grid::unit(0.35, "cm"),
    style = north_arrow_fancy_orienteering(
      fill = c("grey60", "#222222"),
      line_col = "grey70",
      text_col = "grey85"
    )
  ) +
  coord_sf(crs = 5070, datum = NA) +
  theme_void(base_size = 11) +
  theme(
    plot.background = element_rect(fill = "#111111", color = NA),
    panel.background = element_rect(fill = "#111111", color = NA),
    panel.border = element_rect(color = "grey35", fill = NA, linewidth = 0.35),
    legend.position = "right",
    legend.background = element_rect(fill = "#111111", color = NA),
    legend.key = element_rect(fill = "#111111", color = NA),
    legend.title = element_text(color = "white", face = "bold", size = 9),
    legend.text = element_text(color = "grey80", size = 8),
    plot.title = element_text(color = "white", face = "bold", hjust = 0.5, size = 15),
    plot.subtitle = element_text(color = "grey65", hjust = 0.5, size = 10),
    plot.caption = element_text(color = "grey50", size = 8, hjust = 1),
    plot.margin = margin(10, 12, 8, 12)
  ) +
  labs(
    title = paste0("City Deprivation Index across Major U.S. Cities (", latest_year, ")"),
    subtitle = "Color shows CI; glow strength increases with distance from CI = 0; point size represents population",
    caption = "Data: U.S. Census Bureau ACS 1-year estimates | CRS: EPSG 5070 | Alaska, Hawaii and territories excluded"
  )
}

ci_map


主要城市趋势比较

# 1. 数据整理:仅保留重点城市,并按最新年份 CI 排序
focus_ci <- ci_final %>%
  filter(city_state %in% focus_cities)

city_order <- focus_ci %>%
  filter(year == latest_year) %>%
  arrange(desc(CI)) %>%
  pull(city_state)

focus_ci_plot <- focus_ci %>%
  mutate(
    city_state = factor(city_state, levels = city_order),
    city_short = stringr::str_remove(as.character(city_state), ", .*"),
    # 主折线分段绘制,避免把 2019 与 2021 当作连续观测直接连接
    period_segment = if_else(year <= 2019, "2012-2019", "2021-2024")
  )

# 2. 2019-2021 虚线连接:只作为视觉引导,不表示 2020 有观测值
ci_gap_bridge <- focus_ci_plot %>%
  filter(year %in% c(2019, 2021))

# 3. 末端标签:只标注最新年份,减少图例依赖
ci_end_labels <- focus_ci_plot %>%
  filter(year == latest_year)

# 4. 配色:克制的多类别学术配色
ci_base_cols <- c(
  "#4E79A7", "#F28E2B", "#E15759", "#76B7B2", "#59A14F",
  "#EDC948", "#B07AA1", "#FF9DA7", "#9C755F", "#79706E"
)

ci_city_cols <- setNames(
  grDevices::colorRampPalette(ci_base_cols)(length(city_order)),
  city_order
)

ci_trend <- ggplot(
  focus_ci_plot,
  aes(
    x = year,
    y = CI,
    color = city_state,
    group = interaction(city_state, period_segment)
  )
) +
  # 2020 缺测区间
  annotate(
    "rect",
    xmin = 2019.5, xmax = 2020.5,
    ymin = -Inf, ymax = Inf,
    fill = "grey90", alpha = 0.70
  ) +
  annotate(
    "text",
    x = 2020, y = Inf,
    label = "2020 omitted",
    vjust = 1.4,
    size = 3,
    color = "grey45"
  ) +
  # pooled Z-score 的共同基准线
  geom_hline(
    yintercept = 0,
    linetype = "dashed",
    color = "grey45",
    linewidth = 0.45
  ) +
  annotate(
    "label",
    x = first_year, y = 0,
    label = "CI = 0",
    hjust = 0,
    vjust = -0.7,
    size = 3,
    label.size = 0.15,
    label.padding = grid::unit(0.12, "lines"),
    color = "grey35",
    fill = "white"
  ) +
  # 缺测段视觉连接:继承城市颜色,但使用虚线和较低透明度
  geom_line(
    data = ci_gap_bridge,
    aes(group = city_state),
    linewidth = 0.85,
    linetype = "dashed",
    alpha = 0.55
  ) +
  # 实际观测段:2012-2019 与 2021-2024 分段实线
  geom_line(
    linewidth = 0.95,
    alpha = 0.88,
    lineend = "round"
  ) +
  geom_point(
    size = 2.1,
    alpha = 0.92
  ) +
  # 末端标签,减少图例与线条之间的来回对照
  ggrepel::geom_text_repel(
    data = ci_end_labels,
    aes(label = city_short),
    nudge_x = 0.55,
    direction = "y",
    hjust = 0,
    size = 3.1,
    segment.color = "grey70",
    segment.linewidth = 0.25,
    box.padding = 0.25,
    point.padding = 0.15,
    max.overlaps = Inf,
    show.legend = FALSE
  ) +
  scale_color_manual(values = ci_city_cols) +
  scale_x_continuous(
    breaks = years,
    limits = c(first_year, latest_year + 1.7),
    expand = expansion(mult = c(0.01, 0.02))
  ) +
  scale_y_continuous(
    labels = label_number(accuracy = 0.1),
    breaks = scales::pretty_breaks(n = 7)
  ) +
  coord_cartesian(clip = "off") +
  theme_minimal(base_size = 11) +
  theme(
    legend.position = "none",
    plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5, size = 10),
    plot.caption = element_text(color = "grey50", size = 8, hjust = 1),
    axis.title = element_text(face = "bold", size = 10),
    axis.text.x = element_text(angle = 45, hjust = 1, size = 8),
    panel.grid.minor = element_blank(),
    panel.grid.major.x = element_blank(),
    panel.grid.major.y = element_line(color = "grey88", linetype = "dotted"),
    panel.border = element_rect(color = "grey75", fill = NA, linewidth = 0.35),
    plot.margin = margin(10, 55, 8, 10)
  ) +
  labs(
    title = "Annual CI Trajectories for Selected Major U.S. Cities",
    subtitle = "CI is based on pooled Z-score standardization and equal-weight aggregation",
    x = "Year",
    y = "City Deprivation Index (CI)",
    caption = "Higher CI indicates higher relative composite deprivation; dashed 2019-2021 segments are visual guides only, not observed 2020 estimates."
  )

ci_trend


高 CI 城市的历史热力图

下图选取最新年份 CI 最高的 20 个城市,并回溯它们在整个研究期内的 CI 变化。每个格子的颜色表示对应“城市-年份”的 CI 数值:颜色越红,说明该城市在该年份的相对综合剥夺水平越高;颜色越蓝,则表示相对水平较低。

这张图主要用于观察高 CI 城市的持续性。如果某些城市在多数年份都保持偏红,说明其较高 CI 水平具有一定延续性;如果颜色随年份变化明显,则说明该城市在研究期内的相对位置或综合剥夺水平发生了较大变化。

# 1. 选取最新年份 CI 最高的 20 个城市,并保留最新年份排名
top_latest_cities <- ci_final %>%
  filter(year == latest_year) %>%
  slice_max(CI, n = 20, with_ties = FALSE) %>%
  arrange(desc(CI)) %>%
  mutate(
    latest_rank = row_number(),
    city_label = paste0(stringr::str_pad(latest_rank, 2, pad = "0"), ". ", city_state)
  ) %>%
  select(city_state, latest_rank, city_label)

# 2. 构造完整年份序列;显式加入 2020,用灰色表示 ACS 1-year 缺测
heatmap_years <- first_year:latest_year

ci_heatmap_data <- tidyr::crossing(
  top_latest_cities,
  year = heatmap_years
) %>%
  left_join(
    ci_final %>% select(city_state, year, CI),
    by = c("city_state", "year")
  ) %>%
  mutate(
    year_label = factor(year, levels = heatmap_years),
    # y 轴因子反向设置,使最新年份 CI 最高的城市排在最上方
    city_label = factor(city_label, levels = rev(top_latest_cities$city_label))
  )

# 3. 发散色带以 CI = 0 为中心,并保持正负两侧范围对称
ci_heat_limit <- max(abs(ci_heatmap_data$CI), na.rm = TRUE)
ci_heat_limit <- ifelse(is.finite(ci_heat_limit) && ci_heat_limit > 0, ci_heat_limit, 1)

# 4. 最新年份单元格标签:帮助读者看清筛选依据
latest_ci_labels <- ci_heatmap_data %>%
  filter(year == latest_year) %>%
  mutate(
    ci_label = sprintf("%.1f", CI),
    label_color = if_else(CI > 1.1, "white", "grey20")
  )

ci_heatmap <- ggplot(
  ci_heatmap_data,
  aes(x = year_label, y = city_label, fill = CI)
) +
  geom_tile(
    color = "white",
    linewidth = 0.35,
    width = 0.96,
    height = 0.88
  ) +
  # 最新年份列加边框,提示这些城市是按该年份 CI 排名前 20 选出的
  geom_tile(
    data = ci_heatmap_data %>% filter(year == latest_year),
    fill = NA,
    color = "grey25",
    linewidth = 0.45,
    width = 0.96,
    height = 0.88
  ) +
  geom_text(
    data = latest_ci_labels,
    aes(label = ci_label, color = label_color),
    size = 2.5,
    fontface = "bold",
    show.legend = FALSE
  ) +
  scale_color_identity() +
  scale_fill_gradient2(
    low = "#2166AC",
    mid = "#F7F7F7",
    high = "#B2182B",
    midpoint = 0,
    limits = c(-ci_heat_limit, ci_heat_limit),
    oob = scales::squish,
    na.value = "grey88",
    name = "CI",
    labels = label_number(accuracy = 0.1),
    guide = guide_colorbar(
      title.position = "top",
      title.hjust = 0.5,
      barheight = grid::unit(4.6, "cm"),
      barwidth = grid::unit(0.38, "cm"),
      frame.colour = "grey60",
      ticks.colour = "grey50"
    )
  ) +
  scale_x_discrete(expand = c(0, 0)) +
  scale_y_discrete(expand = c(0, 0)) +
  theme_minimal(base_size = 11) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5, size = 10),
    plot.caption = element_text(color = "grey50", size = 8, hjust = 1),
    axis.title = element_blank(),
    axis.text.x = element_text(angle = 45, hjust = 1, size = 8),
    axis.text.y = element_text(size = 8.2),
    panel.grid = element_blank(),
    legend.position = "right",
    legend.title = element_text(face = "bold", size = 9),
    legend.text = element_text(size = 8),
    plot.margin = margin(10, 12, 8, 10)
  ) +
  labs(
    title = paste0("Historical CI Heatmap for Top-CI Cities in ", latest_year),
    subtitle = "Rows are the top 20 latest-year cities; fill encodes city-year CI relative to the pooled mean",
    caption = "Rows ordered by latest-year CI. Grey cells indicate the omitted 2020 ACS 1-year period; latest-year cells are outlined and labeled."
  )

ci_heatmap


CI 构成维度诊断

在查看总 CI 之后,还需要进一步回到其组成维度,判断高 CI 主要由哪些指标推动。CI 是四个已同向化 pooled Z-score 维度的等权平均;因此,总分只能概括综合水平,但不能直接说明具体来源。

下图将若干高 CI 城市的总指数重新展开为住房拥挤、失业、无车家庭和贫困四个标准化维度。这样可以帮助我们区分不同城市的剥夺结构:有些城市可能主要受贫困或失业影响,有些城市则可能在住房拥挤或无车家庭维度上更突出。需要注意的是,无车家庭比例在公共交通高度发达的城市中应谨慎解释,不能简单等同于交通剥夺。

# 1. 选取最新年份 CI 最高的 6 个城市
decomp_city_info <- ci_final %>%
  filter(year == latest_year) %>%
  slice_max(CI, n = 6, with_ties = FALSE) %>%
  arrange(desc(CI)) %>%
  transmute(
    city_state,
    city_short = stringr::str_remove(city_state, ", .*"),
    city_label = paste0(city_short, "\nCI = ", sprintf("%.2f", CI))
  )

# 2. 整理四个标准化维度
dimension_levels <- c("Overcrowding", "Unemployment", "No vehicle households", "Poverty")

ci_decomp_long <- ci_final %>%
  filter(city_state %in% decomp_city_info$city_state) %>%
  select(city_state, year, z_overcrowd, z_unemployment, z_no_vehicle, z_poverty) %>%
  pivot_longer(
    cols = starts_with("z_"),
    names_to = "dimension",
    values_to = "z_value"
  ) %>%
  mutate(
    dimension = recode(
      dimension,
      z_overcrowd = "Overcrowding",
      z_unemployment = "Unemployment",
      z_no_vehicle = "No vehicle households",
      z_poverty = "Poverty"
    )
  ) %>%
  left_join(decomp_city_info, by = "city_state") %>%
  mutate(
    city_label = factor(city_label, levels = rev(decomp_city_info$city_label)),
    dimension = factor(dimension, levels = dimension_levels)
  )

# 3. 为历史热力图显式加入 2020 缺测列
decomp_years <- first_year:latest_year

ci_decomp_heat <- tidyr::crossing(
  decomp_city_info,
  year = decomp_years,
  dimension = factor(dimension_levels, levels = dimension_levels)
) %>%
  left_join(
    ci_decomp_long %>% select(city_state, year, dimension, z_value),
    by = c("city_state", "year", "dimension")
  ) %>%
  mutate(
    year_label = factor(year, levels = decomp_years),
    city_short = factor(city_short, levels = decomp_city_info$city_short)
  )

# 4. 色带以 0 为中心;正值表示该维度高于 pooled mean
z_limit <- max(abs(ci_decomp_long$z_value), na.rm = TRUE)
z_limit <- ifelse(is.finite(z_limit) && z_limit > 0, z_limit, 1)

dim_cols <- c(
  "Overcrowding" = "#7A5195",
  "Unemployment" = "#EF5675",
  "No vehicle households" = "#FFA600",
  "Poverty" = "#003F5C"
)

# 5. 上图:最新年份维度诊断
latest_dim_plot <- ci_decomp_long %>%
  filter(year == latest_year) %>%
  ggplot(aes(x = z_value, y = dimension, color = dimension)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "grey55", linewidth = 0.45) +
  geom_segment(
    aes(x = 0, xend = z_value, yend = dimension),
    linewidth = 0.9,
    alpha = 0.85
  ) +
  geom_point(size = 2.8, alpha = 0.95) +
  geom_text(
    aes(
      label = sprintf("%.1f", z_value),
      hjust = if_else(z_value >= 0, -0.25, 1.25)
    ),
    size = 2.7,
    show.legend = FALSE
  ) +
  facet_wrap(~ city_label, ncol = 3) +
  scale_color_manual(values = dim_cols, guide = "none") +
  scale_x_continuous(
    limits = c(-z_limit - 0.35, z_limit + 0.35),
    labels = label_number(accuracy = 0.1)
  ) +
  coord_cartesian(clip = "off") +
  theme_minimal(base_size = 11) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 13.5),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5, size = 9.5),
    axis.title.y = element_blank(),
    axis.text.y = element_text(size = 8.5),
    panel.grid.minor = element_blank(),
    panel.grid.major.y = element_blank(),
    strip.text = element_text(face = "bold", size = 9),
    plot.margin = margin(8, 18, 6, 8)
  ) +
  labs(
    title = paste0("Dimension Profiles of High-CI Cities in ", latest_year),
    subtitle = "Positive values indicate dimensions above the pooled sample mean",
    x = "Pooled Z-score"
  )

# 6. 下图:历史维度热力图
history_dim_heatmap <- ggplot(
  ci_decomp_heat,
  aes(x = year_label, y = city_short, fill = z_value)
) +
  geom_tile(color = "white", linewidth = 0.28, width = 0.96, height = 0.86) +
  facet_wrap(~ dimension, ncol = 2) +
  scale_fill_gradient2(
    low = "#2166AC",
    mid = "#F7F7F7",
    high = "#B2182B",
    midpoint = 0,
    limits = c(-z_limit, z_limit),
    oob = scales::squish,
    na.value = "grey88",
    name = "Pooled\nZ-score",
    labels = label_number(accuracy = 0.1)
  ) +
  theme_minimal(base_size = 11) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5, size = 13.5),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5, size = 9.5),
    axis.title = element_blank(),
    axis.text.x = element_text(angle = 45, hjust = 1, size = 8),
    axis.text.y = element_text(size = 8.5),
    panel.grid = element_blank(),
    strip.text = element_text(face = "bold", size = 9),
    legend.position = "right",
    legend.title = element_text(face = "bold", size = 9),
    legend.text = element_text(size = 8),
    plot.margin = margin(6, 8, 8, 8)
  ) +
  labs(
    title = "Historical Patterns of Standardized CI Dimensions",
    subtitle = "Grey cells indicate the omitted 2020 ACS 1-year period"
  )

# 7. 组合输出:上图看结构来源,下图看历史持续性
ci_decomp_plot <- latest_dim_plot / history_dim_heatmap +
  patchwork::plot_layout(heights = c(1.05, 1)) +
  patchwork::plot_annotation(
    caption = "CI is the equal-weighted average of four pooled Z-score dimensions. Dimension plots diagnose which standardized indicators drive high CI values."
  ) &
  theme(
    plot.caption = element_text(color = "grey50", size = 8, hjust = 1)
  )

ci_decomp_plot


结果解释提示

完成上述图表后,结果解读应围绕三个层面展开:

  • 持续性:哪些城市在多数年份中保持较高 CI?如果某一城市长期处于高值区间,说明其相对综合剥夺水平可能具有较强延续性。
  • 变化性:哪些城市的 CI 在研究期内明显上升或下降?这类变化可能与经济结构调整、人口迁移、住房压力变化或数据发布条件变化有关,需要结合具体城市背景进一步解释。
  • 构成性:高 CI 主要由哪些维度推动?贫困、失业、住房拥挤和无车家庭比例分别对应不同的社会含义,因此不能只依据总指数作出笼统判断。

其中,无车家庭比例需要特别谨慎解释。在高密度且公共交通较完善的城市,无车家庭占比较高可能反映出公共交通、步行或共享出行的替代作用,并不必然意味着交通剥夺;而在低密度、汽车依赖较强的城市,无车则更可能与出行机会受限相关。

因此,CI 应被理解为一种用于比较和诊断的综合指标,而不是对城市社会问题的单一结论。对结果的解释需要同时结合时间变化、维度来源和城市具体语境。

3.0 专题研究 1: 回顾与反思

3.0 专题研究 1:回顾与反思

重要

本节对前述专题研究 1“美国主要城市社会剥夺指数年度动态分析”进行回顾与反思。该案例以 ACS 1-year estimates 为数据基础,围绕城市社会剥夺这一概念,构建 Carstairs-style 城市社会剥夺指数。

需要说明的是,本专题属于方法演示型案例。其目的不是提出一个可直接作为官方标准使用的城市剥夺指数,而是展示综合指数构建的一套可复现流程,包括研究对象界定指标选择比例变量重构pooled Z-score 标准化等权聚合时空可视化,以及对指数构成维度的解释。


3.1 专题研究 1 回顾

回顾专题研究 1,本案例基于美国 ACS 1-year estimates,面向可持续观测的美国主要城市构建了一个 Carstairs-style 城市社会剥夺指数(City Deprivation Index),并追踪其 2012–2024 年间的年度变化。核心工作流可概括为以下四个环节:

  • 明确比较对象:ACS 1-year 可持续观测的美国主要城市
    本专题以 Census place 为城市单元,并保留在所有研究年份中均可观测、且最新年份人口不少于 10 万人的城市。这样的样本设定有助于开展稳定的年度比较,但其解释对象应限定为“ACS 1-year 可持续观测的美国主要城市”,而不是美国全部城市。

  • 构建平衡城市面板以支持年度动态比较
    由于本专题关注的是城市 CI 的时间变化,因此案例采用平衡面板策略,保证每个城市在研究期内具有相同的年度观测结构。这样可以减少样本进入和退出对趋势判断的干扰,使地图、折线图和热力图中的年度比较具有一致的样本基础。

  • 基于四个维度构建 Carstairs-style CI
    指数包含住房拥挤、失业、无车家庭和贫困四个维度。每个维度先由 ACS 计数变量重构为比例指标,再通过 pooled Z-score 转换到统一尺度,最后采用等权聚合得到总 CI。pooled Z-score 的作用在于为不同年份提供共同基准,使 CI 更适合用于跨年份动态比较。

  • 从总指数回到组成维度进行解释
    本专题不仅展示 CI 的空间分布、年度轨迹和历史热力图,也进一步检查四个标准化维度的结构来源。这样可以避免把 CI 简单理解为一个黑箱分数,并帮助判断高 CI 城市主要由贫困、失业、住房拥挤还是无车家庭维度推动。


3.2 专题研究 1 反思

重要

综合指数的优势在于将多维信息压缩为可比较、可制图、可追踪的单一尺度;其风险也来自这种压缩。CI 可以帮助我们识别城市之间的相对差异和年度变化,但不能替代对指标含义、数据质量和城市语境的具体解释。

以下问题可作为对本案例的进一步反思:

  • “城市”边界并不等同于功能性城市区域
    本专题使用 Census place 作为分析单元。该边界适合进行城市行政辖区层面的比较,但并不等同于都市区、通勤区或功能性城市区域。例如,洛杉矶市与洛杉矶都会区、纽约市与纽约都会区的空间范围和社会结构都存在明显差异。若改用 metropolitan statistical area (MSA),CI 的空间格局和城市排序可能发生变化。

  • ACS 1-year 适合动态观察,但仍受抽样误差影响
    ACS 1-year estimates 具有较好的年度时效性,适合观察主要城市的短期变化。但它本质上仍是抽样估计,边际误差不可忽略。对于人口规模较小、或某些分母较小的指标,年度波动可能部分来自抽样误差,而不完全代表社会结构的真实变化。正式研究中应进一步结合 MOE (Margin of Error) 或置信区间进行稳健性判断。

  • 标准化策略决定趋势解释口径
    本案例采用 pooled Z-score,是因为研究目标是比较城市在整个研究期共同分布中的相对位置变化。若改用逐年 Z-score,结果更适合解释同一年城市之间的相对排序;若改用固定基准年标准化,则更强调相对于某一基准年的变化。不同标准化策略服务于不同问题,不能在解释时混用。

  • 无车家庭比例需要结合城市交通语境解释
    无车家庭比例在汽车依赖型城市中可能反映交通机会不足,但在高密度、公共交通较完善的城市中,较高的无车比例可能更多体现出公共交通、步行或共享出行的替代作用。纽约、波士顿、旧金山、华盛顿 DC 等城市尤其需要结合公共交通供给、居住密度和停车成本解释,不能将无车家庭比例机械等同于剥夺。

  • 等权聚合透明,但隐含同等重要性的假设
    等权聚合的优点是清晰、可复现,也便于教学展示。但它同时假设四个维度对 CI 具有相同重要性。若研究目标转向住房政策、公共卫生、交通公平或贫困治理,权重设定可能需要重新讨论。后续可以比较等权、PCA 权重、专家权重或政策权重下的城市排序是否稳定。

  • 从指数识别走向机制解释仍需更多变量后续研究
    CI 可以帮助识别哪些城市相对剥夺水平较高、哪些城市变化明显、以及高值主要来自哪些维度。但它不能直接解释这些差异为何产生。若要进一步进入机制分析,需要引入产业结构、住房市场、人口迁移、族裔隔离、城市治理、交通系统和区域经济等变量,构建更完整的解释框架。

延伸阅读:综合指数的敏感性分析

敏感性分析(Sensitivity Analysis)是综合指数研究中重要的稳健性检查。它关注的问题是:当某些合理但并非唯一的设定发生变化时,主要结论是否仍然保持一致。

对本专题而言,可以从以下几个方向开展最小可行的敏感性分析:

  1. 更换标准化方法:比较 pooled Z-score、逐年 Z-score、固定基准年 Z-score 或百分位标准化下的城市排序是否稳定。
  2. 更换权重方案:比较等权、PCA 权重、专家权重或政策权重下的高 CI 城市名单是否发生明显变化。
  3. 去除单一维度:采用 leave-one-out 方法,每次删除一个维度后重新计算 CI,检查结果是否高度依赖某一指标。
  4. 调整城市样本门槛:将人口门槛从 100,000 调整为 65,000 或 250,000,观察结论是否受样本范围影响。
  5. 更换空间分析单元:将 Census place 替换为 MSA、county 或其他空间单元,比较“城市剥夺”的空间解释是否发生变化。

如果某些城市在多种设定下都稳定处于高 CI 位置,说明这一结果具有较强的稳健性,值得进一步解释;如果排名明显依赖某一指标、权重或标准化方法,则应在结果表述中保留相应限制。

4.0 综合指数构建实战 2

4.0 专题研究 2

专题研究 2:中国超一线与新一线城市专利创新指数

专题导航

中国城市专利创新指数

“从专利明细数据到城市创新综合评价”

本专题基于 2024 年中国超一线城市新一线城市专利申请明细和常住人口数据,构建城市层面的专利创新指数(PII)。主要流程包括研究设计、数据清洗、指标体系、EWM-TOPSIS 指数计算与结果解释。

点击下方卡片,快速跳转至相应模块:

💡 提示:学习完一个小节后,请再次点击屏幕右下角的专题导航按钮返回本导航页


4.1 研究设计:从城市专利活动到创新综合指数

研究课题

本专题的研究课题是:基于 2024 年中国城市专利申请明细数据,构建超一线新一线城市的专利创新指数(Patent Innovation Index, PII),并比较不同城市在专利创新规模、创新质量、创新主体结构、创新合作和技术多样性方面的综合表现。

数据与分析对象

本专题以城市为分析单元,研究对象包括 2024 年中国超一线与新一线城市。主要使用两类数据:一是 2024 年专利申请明细数据,用于识别每个城市的专利数量、专利类型、申请主体、合作关系、IPC 技术分类等信息;二是2024年城市常住人口数据,用于将专利总量进一步转化为可跨城市比较的人均创新强度指标。

相关数据已经在网站上开源:点击访问

通过将专利明细表城市人口表进行匹配,本专题将原始的专利记录汇总为城市层面的指标矩阵,并在此基础上构建综合指数。

为什么用专利研究城市创新?

城市创新可以理解为城市将知识、技术、人才和产业基础转化为新产品、新工艺或新服务的能力。相关研究常关注知识溢出创新集群区域创新系统等机制,即创新活动往往会在企业、高校、科研机构和专业服务部门集聚的城市空间中更容易发生。

专利数据常被用作技术创新产出的代理变量,因为它记录了较为明确的发明活动、申请主体和技术领域信息。但专利并不等同于创新本身,它难以完整反映商业模式创新、组织创新、非专利技术诀窍等内容。

参考阅读


研究目的

本专题旨在将 2024 年城市专利申请明细数据整理为城市层面的创新评价数据,并据此构建专利创新指数(Patent Innovation Index, PII)。研究目的主要包括三个方面:

  1. 明确城市样本与数据口径:通过城市名称标准化和人口数据匹配,确定可进入比较的城市样本,保证专利指标与人口指标在同一城市口径下计算。
  2. 构建多维专利创新指标:从专利规模、人口标准化强度、专利类型、申请主体、合作申请和技术分类等信息中提取城市创新特征。
  3. 形成可解释的综合评价结果:采用熵权法和 TOPSIS 将多项指标整合为 PII,用于比较城市之间的相对表现,并分析不同城市的创新结构差异。

方法路线

本专题采用从数据整理、指标构建到综合评价的技术路线:

  1. 数据检查与城市匹配
    读取城市常住人口表和专利申请明细表,统一城市名称格式,检查目标城市的专利数据覆盖情况。

  2. 专利去重与字段重构
    申请号 为基础识别同一件专利,避免同一申请因不同法律状态或记录类型而被重复计数;同时整理专利类型、申请主体、合作申请、IPC 分类等字段。

  3. 城市级指标构建
    将专利明细汇总到城市层面,围绕专利规模创新质量主体结构合作程度技术多样性五个维度构建指标体系。

  4. 熵权法确定权重
    根据各指标在城市之间的离散程度计算权重。城市间差异越明显的指标,通常包含更高的信息量,在综合评价中的权重也相应提高。

  5. TOPSIS 综合评价
    计算各城市与“理想创新城市”和“负理想城市”的相对距离,得到 PII 综合得分,并据此开展城市排名与分维度解释。

专题研究 2 的技术路线:从专利明细到城市专利创新指数 PII
环节 核心任务 输出结果
样本与口径 读取专利明细与城市常住人口数据,统一城市名称并检查样本覆盖情况 城市样本覆盖表
去重与重构 按申请号识别同一件专利,整理专利类型、申请主体、合作申请、IPC 分类与引用字段 专利申请级数据表
指标构建 围绕规模、质量、主体、合作和技术多样性五个维度构建城市级指标 城市指标矩阵
权重设定 使用熵权法根据指标离散程度计算客观权重 指标权重表
综合评价 使用 TOPSIS 计算城市与理想解的相对贴近度 PII 排名与分维度得分
4.2 数据清洗:城市匹配、专利去重与字段重构

读取数据

本节使用本章数据文件中的三份 CSV 数据:2024 年超一线与新一线城市常住人口表超一线城市专利明细表新一线城市专利明细表。其中,人口表用于确定城市样本与人口标准化分母,专利明细表用于提取专利数量、专利类型、申请主体、合作申请、IPC 分类和引用等信息。

为提高代码的可移植性,下方代码将优先查找 data/ 文件夹中的数据文件;若该路径不存在,则读取当前 Rmd 文件同目录下的数据文件。这样可以兼容不同的本地项目组织方式,减少因文件存放位置不同导致的读取错误。

# 读取依赖包,保证本代码块可独立运行
library(tidyverse)
library(scales)
library(forcats)
library(patchwork)
library(knitr)
library(kableExtra)

# 在候选路径中返回第一个存在的数据文件
find_first_existing <- function(paths) {
  existing <- paths[file.exists(paths)]
  if (length(existing) == 0) {
    stop("未找到数据文件:", paste(paths, collapse = " / "))
  }
  existing[1]
}

# 统一城市名称口径,便于人口表与专利表匹配
normalize_city <- function(x) {
  x %>%
    stringr::str_squish() %>%
    stringr::str_remove("市$")
}

# 后续专利去重时,用于保留非缺失字段
first_non_missing <- function(x) {
  x <- x[!is.na(x) & x != ""]
  if (length(x) == 0) NA_character_ else x[1]
}

# 定位专利明细文件
patent_files <- c(
  "超一线城市专利明细" = find_first_existing(c(
    "data/patent_bj_sh_sz_gz_2024_raw.csv",
    "patent_bj_sh_sz_gz_2024_raw.csv"
  )),
  "新一线城市专利明细" = find_first_existing(c(
    "data/patent_new_tier_2024_raw.csv",
    "patent_new_tier_2024_raw.csv"
  ))
)

# 定位城市常住人口文件
population_file <- find_first_existing(c(
  "data/2024年超一线及新一线城市常住人口.csv",
  "2024年超一线及新一线城市常住人口.csv"
))

# 读取并合并两类城市专利明细
patent_raw <- purrr::imap_dfr(
  patent_files,
  ~ readr::read_csv(.x, show_col_types = FALSE) %>%
    mutate(data_source = .y)
)

# 读取城市人口表,并生成用于匹配的城市键
city_population <- readr::read_csv(population_file, show_col_types = FALSE) %>%
  mutate(
    city_key = normalize_city(城市),
    常住人口_人 = readr::parse_number(as.character(常住人口_人))
  )

# 清洗专利明细,并限定为本专题目标城市与 2024 年申请
patent_clean <- patent_raw %>%
  mutate(
    # 标准化文本空白,并把空字符串转为 NA
    across(where(is.character), ~ na_if(str_squish(.x), "")),
    city_key = normalize_city(申请人城市),
    申请年份 = readr::parse_integer(as.character(申请年份)),
    他引次数 = replace_na(readr::parse_number(as.character(他引次数)), 0),
    被引证次数 = replace_na(readr::parse_number(as.character(被引证次数)), 0)
  ) %>%
  filter(
    # 限定 2024 年、有效申请号和目标城市样本
    申请年份 == 2024,
    !is.na(申请号),
    city_key %in% city_population$city_key
  )

城市样本覆盖检查

正式计算 PII 之前,需要先核对人口表与专利明细表的城市覆盖情况。若某个城市出现在人口表中,但没有匹配到专利明细记录,应标记为专利明细缺失,而不宜直接解释为“该城市没有专利申请”。这一步有助于明确本次指数计算的样本范围。

城市样本覆盖检查:人口表与专利明细表的匹配情况
排序 城市等级 城市 常住人口(万人) 匹配到的申请号数 数据状态
1 超一线城市 上海 2480 109,585 已匹配专利明细
2 超一线城市 北京 2183 167,109 已匹配专利明细
3 超一线城市 深圳 1799 140,629 已匹配专利明细
4 超一线城市 广州 1898 86,764 已匹配专利明细
5 新一线城市 成都 2147 55,983 已匹配专利明细
6 新一线城市 杭州 1262 77,317 已匹配专利明细
7 新一线城市 重庆 3190 42,792 已匹配专利明细
8 新一线城市 苏州 1299 75,191 已匹配专利明细
9 新一线城市 武汉 1381 55,271 已匹配专利明细
10 新一线城市 西安 1317 46,796 已匹配专利明细
11 新一线城市 南京 958 55,827 已匹配专利明细
12 新一线城市 长沙 1062 29,599 已匹配专利明细
13 新一线城市 天津 1364 36,790 已匹配专利明细
14 新一线城市 郑州 1309 24,826 已匹配专利明细
15 新一线城市 东莞 1057 41,736 已匹配专利明细
16 新一线城市 无锡 751 32,590 已匹配专利明细
17 新一线城市 宁波 978 42,224 已匹配专利明细
18 新一线城市 青岛 1044 37,134 已匹配专利明细
19 新一线城市 合肥 1000 44,787 已匹配专利明细

本次读取的人口表包含 19 个城市,其中 19 个城市匹配到 2024 年专利明细记录。若表中出现“专利明细缺失”的城市,说明当前文件尚不足以支持这些城市进入 PII 计算;后续补齐同口径专利明细后,可再将其纳入指数计算。


专利申请级去重

原始专利明细表以记录行为单位,同一 申请号 可能因专利状态更新而出现多条记录。例如,同一发明专利可能同时包含“发明申请”和“发明授权”记录。若直接按行数统计,将高估城市专利数量。

因此,本专题先将数据整理为城市-申请号层级:同一城市内同一 申请号 只保留一条记录;若同一申请号对应多个专利类型,则按照“发明授权、发明申请、实用新型、外观设计”的顺序确定代表性类型。同时,保留申请主体、IPC 分类、引用次数等后续指标构建所需字段。

patent_app <- patent_clean %>%
  group_by(申请号, city_key) %>%
  summarise(
    # 同一申请号可能对应多个状态,优先保留更高阶段的专利类型
    patent_type = case_when(
      any(专利类型 == "发明授权", na.rm = TRUE) ~ "发明授权",
      any(专利类型 == "发明申请", na.rm = TRUE) ~ "发明申请",
      any(专利类型 == "实用新型", na.rm = TRUE) ~ "实用新型",
      any(专利类型 == "外观设计", na.rm = TRUE) ~ "外观设计",
      TRUE ~ first_non_missing(专利类型)
    ),

    # 汇总同一申请号下的申请主体与主体类型
    applicant_type_text = paste(sort(unique(na.omit(申请人类型))), collapse = ","),
    applicant_text = paste(sort(unique(na.omit(申请人))), collapse = "; "),

    # 提取后续指标构建所需字段
    ipc_group = first_non_missing(IPC_Group),
    other_citations = max(他引次数, na.rm = TRUE),
    cited_count = max(被引证次数, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(
    # IPC 前 4 位用于刻画技术领域
    ipc_group = str_sub(ipc_group, 1, 4),

    # 识别不同类型的申请主体
    has_enterprise = str_detect(applicant_type_text, "企业"),
    has_school = str_detect(applicant_type_text, "学校"),
    has_research = str_detect(applicant_type_text, "科研单位"),
    has_individual = str_detect(applicant_type_text, "个人"),
    has_government = str_detect(applicant_type_text, "机关团体"),
    has_other = str_detect(applicant_type_text, "其他"),

    # 统计同一专利涉及的主体类型数量
    applicant_type_count = rowSums(
      across(
        c(has_enterprise, has_school, has_research, has_individual, has_government, has_other),
        as.integer
      ),
      na.rm = TRUE
    ),

    # 构建专利层面的辅助判断变量
    is_collaboration = str_detect(applicant_text, ";|;") | applicant_type_count >= 2,
    is_industry_academia = has_enterprise & (has_school | has_research),
    is_invention = patent_type %in% c("发明申请", "发明授权"),
    has_other_citation = other_citations > 0,
    emerging_tech = str_detect(ipc_group, "^(G06N|H04W|B60L|H01M|C12N)")
  )

# 对比去重前后的记录数量
application_check <- tibble(
  项目 = c("原始明细行数", "去重后城市-申请号数", "重复记录减少量"),
  数值 = c(
    nrow(patent_clean),
    nrow(patent_app),
    nrow(patent_clean) - nrow(patent_app)
  )
)

kable(
  application_check %>% mutate(数值 = comma(数值)),
  align = "c",
  caption = "专利申请级去重结果",
  booktabs = TRUE,
  linesep = ""
) %>%
  kable_styling(full_width = FALSE, position = "center", font_size = 12)
专利申请级去重结果
项目 数值
原始明细行数 1,238,708
去重后城市-申请号数 1,202,950
重复记录减少量 35,758

上表用于检查去重处理的影响。其中,“原始明细行数”表示清洗后专利表中的记录总数;“去重后城市-申请号数”表示进入后续指标计算的专利申请数量;“重复记录减少量”反映因同一申请号重复出现而被合并的记录数。

4.3 指标体系:规模、质量、主体、合作与技术多样性

指标框架

在完成专利申请级去重后,需要将专利明细进一步汇总为城市层面的指标体系。本专题将城市专利创新划分为五个维度,用于刻画不同城市在创新活动中的结构性差异:

  1. 创新规模:衡量城市专利申请的总体规模及人口标准化后的创新强度。
  2. 创新质量:关注发明类专利占比、授权情况以及外部他引等能够反映专利技术含量或影响力的指标。
  3. 主体结构:考察企业、高校、科研机构等创新主体的参与情况,以及申请主体类型的多元程度。
  4. 创新合作:识别多申请人合作和产学研合作,反映城市创新活动中的协同研发特征。
  5. 技术多样性:基于 IPC 分类刻画技术领域覆盖范围、分布均衡程度和新兴技术布局。

需要说明的是,专利综合指数研究中还常使用 PCT 专利、高价值专利、跨城市合作网络、技术专业化 LQ 等指标。但这些指标对数据字段、识别规则和时间窗口有更高要求,在当前数据中并不能稳定构建。因此,本专题仅保留口径清楚、字段可识别、计算过程可复现的指标,保证 PII 的教学可操作性和结果可解释性。

indicator_meta <- tribble(
  ~indicator, ~display, ~dimension, ~direction, ~meaning,
  "patent_total_log", "专利总量(log)", "创新规模", "positive", "反映城市专利产出规模;使用 log1p 降低极端规模差异影响",
  "patents_per_10k_log", "每万人专利量(log)", "创新规模", "positive", "以常住人口标准化后的专利强度;使用 log1p 后进入指数",

  "invention_share", "发明类专利占比", "创新质量", "positive", "发明申请和发明授权占全部专利的比例",
  "cited_share", "有他引专利占比", "创新质量", "positive", "他引次数大于 0 的专利比例,作为早期技术影响力代理",

  "enterprise_share", "企业申请占比", "主体结构", "positive", "企业参与的专利占比,反映市场化创新活跃度",
  "university_research_share", "高校科研占比", "主体结构", "positive", "学校或科研单位参与的专利占比,反映知识创新力量",
  "applicant_type_entropy", "主体类型 Shannon 熵", "主体结构", "positive", "不同申请主体类型分布越均衡,主体结构越多元",
  "applicant_hhi", "申请人集中度 HHI", "主体结构", "negative", "申请人越集中,创新活动越依赖少数主体;作为负向指标",

  "collaboration_rate", "合作申请率", "创新合作", "positive", "多申请人或多主体类型参与的专利比例",
  "industry_academia_rate", "产学研合作率", "创新合作", "positive", "企业与学校或科研单位共同参与的专利比例",

  "ipc_coverage_log", "IPC 覆盖数(log)", "技术多样性", "positive", "城市涉及的 IPC 技术组数量;使用 log1p 后进入指数",
  "ipc_entropy", "IPC Shannon 熵", "技术多样性", "positive", "专利在不同 IPC 技术组之间分布越均衡,熵值越高",
  "emerging_tech_share", "新兴技术占比", "技术多样性", "positive", "G06N、H04W、B60L、H01M、C12N 等前沿专利占比"
)

indicator_table <- indicator_meta %>%
  transmute(
    维度 = dimension,
    指标 = display,
    方向 = case_when(
      direction == "positive" ~ "正向(+)\n数值越高,评价越高",
      direction == "negative" ~ "负向(-)\n数值越高,评价越低",
      TRUE ~ direction
    ),
    指标含义 = meaning
  )

kable(
  indicator_table,
  align = "c",
  caption = "本专题 PII 指标体系",
  booktabs = TRUE,
  linesep = ""
) %>%
  kable_styling(full_width = FALSE, position = "center", font_size = 12) %>%
  column_spec(1, width = "3.0cm", bold = TRUE) %>%
  column_spec(2, width = "5.5cm") %>%
  column_spec(3, width = "7.0cm") %>%
  column_spec(4, width = "11cm") %>%
  collapse_rows(columns = 1, valign = "middle")
本专题 PII 指标体系
维度 指标 方向 指标含义
创新规模 专利总量(log) 正向(+) 数值越高,评价越高 反映城市专利产出规模;使用 log1p 降低极端规模差异影响
每万人专利量(log) 正向(+) 数值越高,评价越高 以常住人口标准化后的专利强度;使用 log1p 后进入指数
创新质量 发明类专利占比 正向(+) 数值越高,评价越高 发明申请和发明授权占全部专利的比例
有他引专利占比 正向(+) 数值越高,评价越高 他引次数大于 0 的专利比例,作为早期技术影响力代理
主体结构 企业申请占比 正向(+) 数值越高,评价越高 企业参与的专利占比,反映市场化创新活跃度
高校科研占比 正向(+) 数值越高,评价越高 学校或科研单位参与的专利占比,反映知识创新力量
主体类型 Shannon 熵 正向(+) 数值越高,评价越高 不同申请主体类型分布越均衡,主体结构越多元
申请人集中度 HHI 负向(-) 数值越高,评价越低 申请人越集中,创新活动越依赖少数主体;作为负向指标
创新合作 合作申请率 正向(+) 数值越高,评价越高 多申请人或多主体类型参与的专利比例
产学研合作率 正向(+) 数值越高,评价越高 企业与学校或科研单位共同参与的专利比例
技术多样性 IPC 覆盖数(log) 正向(+) 数值越高,评价越高 城市涉及的 IPC 技术组数量;使用 log1p 后进入指数
IPC Shannon 熵 正向(+) 数值越高,评价越高 专利在不同 IPC 技术组之间分布越均衡,熵值越高
新兴技术占比 正向(+) 数值越高,评价越高 G06N、H04W、B60L、H01M、C12N 等前沿专利占比

IPC 编号与新兴技术识别

IPC 是国际专利分类(International Patent Classification),用于按照技术领域对专利进行分类。IPC 编号通常由部、大类、小类和组构成,例如 G06NH04W 等前 4 位代码可用于快速识别较宽泛的技术方向。

本专题中的“新兴技术占比”仅作教学型代理指标,示例性选取若干与数字技术、通信、新能源和生物技术相关的 IPC 小类:

  • G06N:基于特定计算模型的计算机系统,常见于人工智能、机器学习等相关专利;
  • H04W:无线通信网络,常见于移动通信、网络连接等相关专利;
  • B60L:电动车辆的电力装备与推进系统,常见于新能源汽车相关专利;
  • H01M:电池、燃料电池及其制造,常见于储能与动力电池相关专利;
  • C12N:微生物、酶、基因工程等,常见于生物技术相关专利。

需要注意的是,IPC 代码与技术主题之间并非一一对应。正式研究中应根据研究问题、产业背景和完整 IPC 定义进一步细化识别规则。


城市级指标汇总

下方代码将申请级专利数据汇总到城市层面,并计算比例型指标、人口标准化指标、分布均衡指标和集中度指标。其中,比例型指标用于刻画发明类专利、企业申请、合作申请等结构特征;人口标准化指标用于减少城市人口规模差异对专利总量比较的影响;分布均衡与集中度指标则用于观察创新活动是否过度依赖单一主体或少数技术领域。

本专题使用 Shannon 熵 衡量申请主体类型和 IPC 技术领域的分布均衡程度,使用 HHI(Herfindahl-Hirschman Index) 衡量申请人集中度。前者数值越高,表示分布越均衡、多样性越强;后者数值越高,表示专利申请越集中于少数主体,因此在后续指数计算中作为负向指标处理。

拓展阅读:Shannon 熵与 HHI

Shannon 熵最初来自信息论,常用于衡量一个分布的不确定性或均衡程度。在本专题中,可将其理解为“多样性”指标:若某城市的专利较均匀地分布在多个申请主体类型或多个 IPC 技术领域中,熵值较高;若专利高度集中在少数类型或少数技术领域中,熵值较低。

其常见形式为:

\[ H = -\sum_{i=1}^{k} p_i \ln(p_i) \]

其中,\(p_i\) 表示第 \(i\) 类在总体中的占比,\(k\) 表示类别数量。

HHI(Herfindahl-Hirschman Index)常用于产业组织和市场集中度研究,也可用于衡量专利申请人集中度。若一个城市的大量专利集中在少数申请人手中,HHI 较高;若专利分布在较多申请人之间,HHI 较低。

其常见形式为:

\[ HHI = \sum_{i=1}^{k} s_i^2 \]

其中,\(s_i\) 表示第 \(i\) 个申请人在该城市专利申请中的占比。HHI 越接近 1,集中度越高;越接近 0,说明申请人结构越分散。

# 1. 汇总城市层面的基础比例指标
city_patent_summary <- patent_app %>%
  group_by(city_key) %>%
  summarise(
    # 基础规模、质量、主体和合作指标
    patent_total = n(),
    invention_share = mean(is_invention),
    cited_share = mean(has_other_citation),
    mean_other_citation = mean(other_citations),
    enterprise_share = mean(has_enterprise),
    university_research_share = mean(has_school | has_research),
    collaboration_rate = mean(is_collaboration),
    industry_academia_rate = mean(is_industry_academia),
    emerging_tech_share = mean(replace_na(emerging_tech, FALSE)),
    .groups = "drop"
  )

# 2. 拆分多申请人字段,用于计算申请人集中度
applicant_names <- patent_app %>%
  select(city_key, applicant_text) %>%
  mutate(applicant_name = str_split(applicant_text, "\\s*[;;]\\s*")) %>%
  tidyr::unnest(applicant_name) %>%
  mutate(applicant_name = str_squish(applicant_name)) %>%
  filter(!is.na(applicant_name), applicant_name != "")

# 3. 计算申请人数量与 HHI 集中度
applicant_hhi <- applicant_names %>%
  count(city_key, applicant_name, name = "applicant_patents") %>%
  group_by(city_key) %>%
  mutate(p = applicant_patents / sum(applicant_patents)) %>%
  summarise(
    applicant_count = n_distinct(applicant_name),
    applicant_hhi = sum(p^2),
    high_productive_applicant_share = mean(applicant_patents > 5),
    .groups = "drop"
  )

# 4. 计算申请主体类型的 Shannon 熵
applicant_type_entropy <- patent_app %>%
  transmute(
    city_key,
    企业 = has_enterprise,
    学校 = has_school,
    科研单位 = has_research,
    个人 = has_individual,
    机关团体 = has_government,
    其他 = has_other
  ) %>%
  pivot_longer(-city_key, names_to = "applicant_type", values_to = "present") %>%
  filter(present) %>%
  count(city_key, applicant_type) %>%
  group_by(city_key) %>%
  mutate(p = n / sum(n)) %>%
  summarise(
    applicant_type_entropy = -sum(p * log(p)),
    .groups = "drop"
  )

# 5. 计算 IPC 技术覆盖数与技术领域 Shannon 熵
ipc_stats <- patent_app %>%
  filter(!is.na(ipc_group), ipc_group != "") %>%
  count(city_key, ipc_group) %>%
  group_by(city_key) %>%
  mutate(p = n / sum(n)) %>%
  summarise(
    ipc_coverage = n_distinct(ipc_group),
    ipc_entropy = -sum(p * log(p)),
    .groups = "drop"
  )

# 6. 合并人口数据与各类城市级专利指标
city_index_base <- city_population %>%
  # 逐步合并城市人口、申请人结构和 IPC 多样性指标
  left_join(city_patent_summary, by = "city_key") %>%
  left_join(applicant_hhi, by = "city_key") %>%
  left_join(applicant_type_entropy, by = "city_key") %>%
  left_join(ipc_stats, by = "city_key") %>%
  mutate(has_patent_data = !is.na(patent_total))

# 7. 生成进入 PII 计算的最终指标数据
city_indicators <- city_index_base %>%
  filter(has_patent_data) %>%
  mutate(
    across(
      c(
        patent_total, invention_share, cited_share, mean_other_citation,
        enterprise_share, university_research_share,
        collaboration_rate, industry_academia_rate, emerging_tech_share,
        applicant_count, applicant_hhi, high_productive_applicant_share,
        applicant_type_entropy, ipc_coverage, ipc_entropy
      ),
      ~ replace_na(.x, 0)
    ),
    # 对规模型指标做人口标准化和 log1p 压缩
    patents_per_10k = patent_total / 常住人口_人 * 10000,
    patent_total_log = log1p(patent_total),
    patents_per_10k_log = log1p(patents_per_10k),
    ipc_coverage_log = log1p(ipc_coverage)
  )
城市级核心专利指标预览
城市等级 城市 专利总量 每万人专利量 发明类占比 合作申请率 IPC 覆盖数
超一线城市 北京 167,109 76.6 83.4% 20.2% 577
超一线城市 深圳 140,629 78.2 52.4% 4.3% 556
超一线城市 上海 109,585 44.2 66.9% 9.5% 569
超一线城市 广州 86,764 45.7 64.4% 12.7% 556
新一线城市 杭州 77,317 61.3 69.4% 11.4% 562
新一线城市 苏州 75,191 57.9 60.2% 5.8% 560
新一线城市 成都 55,983 26.1 65.8% 8.7% 543
新一线城市 南京 55,827 58.3 79.0% 11.9% 547
新一线城市 武汉 55,271 40.0 74.1% 11.6% 540
新一线城市 西安 46,796 35.5 75.2% 10.0% 529
新一线城市 合肥 44,787 44.8 64.9% 10.4% 533
新一线城市 重庆 42,792 13.4 65.8% 8.4% 529
新一线城市 宁波 42,224 43.2 39.4% 6.8% 531
新一线城市 东莞 41,736 39.5 36.2% 2.6% 504
新一线城市 青岛 37,134 35.6 53.7% 13.6% 533
新一线城市 天津 36,790 27.0 60.0% 12.1% 532
新一线城市 无锡 32,590 43.4 61.7% 5.5% 513
新一线城市 长沙 29,599 27.9 73.0% 9.8% 504
新一线城市 郑州 24,826 19.0 59.2% 12.1% 498
4.4 指数计算:熵权法与 TOPSIS 综合评价

为什么需要加权综合评价?

城市专利创新指数由多个指标共同构成。不同指标的量纲、分布范围和区分能力并不相同,因此在综合评价之前,需要完成三个步骤:第一,统一指标方向;第二,确定各指标权重;第三,将多项指标合成为一个综合得分

本节采用 EWM-TOPSIS 方法。其中,熵权法(Entropy Weight Method, EWM)用于确定指标权重,TOPSIS(Technique for Order Preference by Similarity to Ideal Solution)用于计算城市与理想状态之间的相对接近程度。二者结合后,可以在保持计算过程透明的同时,减少完全主观赋权带来的影响。

熵权法:为什么需要给指标加权?

熵权法

在综合指数中,不同指标对城市差异的刻画能力可能不同。若某个指标在所有城市之间差异很小,它对区分城市创新表现的贡献较弱;若某个指标在城市之间差异明显,它包含的区分信息较多。

熵权法正是基于这一思想确定权重。其基本逻辑是:指标分布越分散,信息量越高,权重越大;指标分布越接近,信息量越低,权重越小。常见计算形式为:

\[ e_j = -k \sum_{i=1}^{n} p_{ij}\ln(p_{ij}) \]

\[ w_j = \frac{1-e_j}{\sum_{j=1}^{m}(1-e_j)} \]

其中,\(e_j\) 表示第 \(j\) 个指标的信息熵,\(w_j\) 表示该指标的权重。需要注意的是,熵权法得到的是一种数据驱动权重,反映的是样本内部差异程度,并不等同于理论重要性。

指标方向统一与熵权计算

在计算熵权和 TOPSIS 得分之前,需要先统一指标方向。正向指标表示数值越高评价越高,例如专利总量、发明类专利占比和技术多样性;负向指标表示数值越高评价越低,例如申请人集中度 HHI。下方代码先通过 0-1 标准化完成方向统一,再计算各指标的熵权。

# 0-1 标准化函数:同时处理正向指标与负向指标
range01 <- function(x, direction = "positive") {
  value_range <- range(x, na.rm = TRUE)

  # 若指标没有有效差异,统一赋为 0.5,避免除以 0
  if (all(is.na(x)) || diff(value_range) == 0) {
    return(rep(0.5, length(x)))
  }

  z <- (x - value_range[1]) / diff(value_range)

  # 负向指标需要反向处理,使标准化后仍然满足“越大越好”
  if (direction == "negative") 1 - z else z
}

# 提取进入 PII 计算的指标名称与方向
indicator_vars <- indicator_meta$indicator
direction_lookup <- setNames(indicator_meta$direction, indicator_meta$indicator)

# 对全部指标进行方向统一和 0-1 标准化
ewm_standardized <- city_indicators %>%
  mutate(
    across(
      all_of(indicator_vars),
      ~ range01(.x, direction_lookup[[cur_column()]])
    )
  )

# 转换为矩阵,便于执行熵权法计算
std_mat <- as.matrix(ewm_standardized[, indicator_vars])
n_city <- nrow(std_mat)

# 计算各城市在每个指标上的占比 p_ij
col_sums <- colSums(std_mat, na.rm = TRUE)

p_mat <- sweep(
  std_mat,
  2,
  ifelse(col_sums == 0, NA_real_, col_sums),
  "/"
)

# 若某个指标标准化后总和为 0,则赋予均等占比,避免无效计算
p_mat[, col_sums == 0] <- 1 / n_city

# 计算指标熵值、差异系数与熵权
entropy <- -1 / log(n_city) * colSums(
  ifelse(p_mat > 0, p_mat * log(p_mat), 0),
  na.rm = TRUE
)

divergence <- 1 - entropy

weights <- if (sum(divergence) == 0) {
  rep(1 / length(divergence), length(divergence))
} else {
  divergence / sum(divergence)
}

# 整理权重结果表
weight_table <- indicator_meta %>%
  mutate(
    entropy = entropy,
    divergence = divergence,
    weight = weights
  ) %>%
  arrange(desc(weight))

weight_display <- weight_table %>%
  transmute(
    维度 = dimension,
    指标 = display,
    方向 = case_when(
      direction == "positive" ~ "正向(+)",
      direction == "negative" ~ "负向(-)",
      TRUE ~ direction
    ),
    熵值 = number(entropy, accuracy = 0.001),
    差异系数 = number(divergence, accuracy = 0.001),
    权重 = percent(weight, accuracy = 0.1)
  )

kable(
  weight_display,
  align = "c",
  caption = "熵权法计算得到的指标权重",
  booktabs = TRUE,
  linesep = ""
) %>%
  kable_styling(full_width = FALSE, position = "center", font_size = 12) %>%
  column_spec(1, width = "2.0cm", bold = TRUE) %>%
  column_spec(2, width = "4.0cm") %>%
  column_spec(3, width = "3.6cm") %>%
  column_spec(4, width = "2.5cm") %>%
  column_spec(5, width = "2.8cm") %>%
  column_spec(6, width = "2.6cm") %>%
  collapse_rows(columns = 1, valign = "middle")
熵权法计算得到的指标权重
维度 指标 方向 熵值 差异系数 权重
创新质量 有他引专利占比 正向(+) 0.922 0.078 11.2%
创新规模 专利总量(log) 正向(+) 0.927 0.073 10.5%
创新合作 产学研合作率 正向(+) 0.928 0.072 10.2%
主体结构 高校科研占比 正向(+) 0.932 0.068 9.8%
技术多样性 IPC 覆盖数(log) 正向(+) 0.938 0.062 8.9%
创新合作 合作申请率 正向(+) 0.945 0.055 7.8%
主体结构 企业申请占比 正向(+) 0.946 0.054 7.8%
主体类型 Shannon 熵 正向(+) 0.948 0.052 7.4%
技术多样性 新兴技术占比 正向(+) 0.954 0.046 6.5%
创新质量 发明类专利占比 正向(+) 0.957 0.043 6.2%
创新规模 每万人专利量(log) 正向(+) 0.962 0.038 5.4%
主体结构 申请人集中度 HHI 负向(-) 0.966 0.034 4.8%
技术多样性 IPC Shannon 熵 正向(+) 0.975 0.025 3.5%

熵值越高,说明该指标在城市之间的分布越接近,区分度相对较低;差异系数越高,说明该指标提供的区分信息越多,因此在熵权法中获得更高权重。需要注意,熵权反映的是当前样本中的数据差异,并不直接代表指标在理论上的重要性。


TOPSIS 综合得分

在完成指标标准化和熵权计算后,本节使用 TOPSIS(Technique for Order Preference by Similarity to Ideal Solution) 计算城市专利创新指数。TOPSIS 是多指标综合评价中常用的方法,常见于城市竞争力、生态环境、公共服务、选址决策和绩效评估等研究场景。

其基本思想是:综合表现较好的城市,应当更接近“理想解”,并更远离“负理想解”。在本专题中,“理想解”表示各项指标均达到样本中较优水平的城市状态;“负理想解”表示各项指标均处于较弱水平的城市状态。PII 定义为城市到负理想解的距离占两类距离之和的比例:

\[ PII_i = \frac{D_i^-}{D_i^+ + D_i^-} \]

其中,\(D_i^+\) 表示城市 \(i\) 到理想解的距离,\(D_i^-\) 表示城市 \(i\) 到负理想解的距离。\(PII_i\) 越接近 1,说明该城市在当前样本和指标体系下越接近理想创新状态。

方法提示:如何理解 TOPSIS 得分?

TOPSIS 得分是一种相对评价结果。它依赖当前样本中的城市集合、指标选择、标准化方法和权重设定。因此,PII 适合用于比较本专题样本内部不同城市的相对表现,并分析其分维度结构差异;若更换城市样本或调整指标体系,PII 得分和排名都可能发生变化。

# 1. 对标准化矩阵进行向量归一化
vector_norm <- sqrt(colSums(std_mat^2, na.rm = TRUE))

norm_mat <- sweep(
  std_mat,
  2,
  ifelse(vector_norm == 0, 1, vector_norm),
  "/"
)

# 2. 将熵权应用到归一化矩阵
weighted_mat <- sweep(norm_mat, 2, weights, "*")

# 3. 确定理想解与负理想解
ideal_best <- apply(weighted_mat, 2, max, na.rm = TRUE)
ideal_worst <- apply(weighted_mat, 2, min, na.rm = TRUE)

# 4. 计算每个城市到理想解和负理想解的欧氏距离
d_best <- sqrt(rowSums((sweep(weighted_mat, 2, ideal_best, "-"))^2, na.rm = TRUE))
d_worst <- sqrt(rowSums((sweep(weighted_mat, 2, ideal_worst, "-"))^2, na.rm = TRUE))

# 5. 计算 TOPSIS 相对贴近度,即 PII 综合得分
pii <- d_worst / (d_best + d_worst)

# 6. 计算五个维度的平均标准化得分,用于辅助解释 PII 结构
dimension_scores <- map_dfr(unique(indicator_meta$dimension), function(dim_name) {
  dim_vars <- indicator_meta %>%
    filter(dimension == dim_name) %>%
    pull(indicator)

  ewm_standardized %>%
    transmute(
      city_key,
      dimension = dim_name,
      dimension_score = rowMeans(across(all_of(dim_vars)), na.rm = TRUE)
    )
})

# 7. 合并 PII、排名和分维度得分
city_scores <- ewm_standardized %>%
  mutate(
    PII = pii,
    PII_rank = rank(-PII, ties.method = "min")
  ) %>%
  arrange(PII_rank)

city_dimension_wide <- dimension_scores %>%
  tidyr::pivot_wider(names_from = dimension, values_from = dimension_score)

city_scores <- city_scores %>%
  left_join(city_dimension_wide, by = "city_key")

# 8. 展示城市 PII 排名与分维度表现
kable(
  city_scores %>%
    transmute(
      排名 = PII_rank,
      城市等级,
      城市 = 城市简称,
      PII = number(PII, accuracy = 0.001),
      `专利总量` = comma(patent_total),
      `每万人专利量` = number(patents_per_10k, accuracy = 0.1),
      `创新规模` = number(`创新规模`, accuracy = 0.001),
      `创新质量` = number(`创新质量`, accuracy = 0.001),
      `主体结构` = number(`主体结构`, accuracy = 0.001),
      `创新合作` = number(`创新合作`, accuracy = 0.001),
      `技术多样性` = number(`技术多样性`, accuracy = 0.001)
    ),
  align = "c",
  caption = "2024 年城市专利创新指数 PII 排名",
  booktabs = TRUE,
  linesep = ""
) %>%
  kable_styling(full_width = FALSE, position = "center", font_size = 10)
2024 年城市专利创新指数 PII 排名
排名 城市等级 城市 PII 专利总量 每万人专利量 创新规模 创新质量 主体结构 创新合作 技术多样性
1 超一线城市 北京 0.698 167,109 76.6 0.994 0.636 0.723 0.905 0.667
2 新一线城市 南京 0.651 55,827 58.3 0.628 0.902 0.651 0.756 0.703
3 新一线城市 杭州 0.623 77,317 61.3 0.727 0.744 0.588 0.538 0.684
4 新一线城市 武汉 0.569 55,271 40.0 0.517 0.595 0.610 0.710 0.706
5 新一线城市 成都 0.569 55,983 26.1 0.398 0.814 0.644 0.515 0.521
6 超一线城市 广州 0.566 86,764 45.7 0.673 0.495 0.648 0.522 0.656
7 新一线城市 天津 0.545 36,790 27.0 0.298 0.730 0.621 0.647 0.573
8 新一线城市 合肥 0.541 44,787 44.8 0.494 0.652 0.612 0.557 0.725
9 超一线城市 上海 0.531 109,585 44.2 0.725 0.394 0.636 0.414 0.726
10 新一线城市 西安 0.521 46,796 35.5 0.439 0.627 0.544 0.587 0.515
11 新一线城市 长沙 0.502 29,599 27.9 0.250 0.816 0.484 0.598 0.541
12 新一线城市 苏州 0.498 75,191 57.9 0.704 0.736 0.514 0.128 0.644
13 新一线城市 青岛 0.487 37,134 35.6 0.379 0.543 0.587 0.544 0.584
14 超一线城市 深圳 0.465 140,629 78.2 0.955 0.227 0.550 0.079 0.648
15 新一线城市 郑州 0.437 24,826 19.0 0.096 0.389 0.635 0.772 0.405
16 新一线城市 重庆 0.432 42,792 13.4 0.143 0.438 0.599 0.438 0.664
17 新一线城市 无锡 0.419 32,590 43.4 0.402 0.717 0.562 0.220 0.519
18 新一线城市 宁波 0.322 42,224 43.2 0.468 0.094 0.538 0.175 0.479
19 新一线城市 东莞 0.281 41,736 39.5 0.439 0.000 0.488 0.000 0.328
4.5 结果展示:排名、维度画像与城市类型比较

PII 排名图

下图展示各城市的 PII 综合得分与相对排名。PII 是基于当前样本、指标体系、熵权和 TOPSIS 方法得到的相对评价结果,适合用于比较样本城市之间的专利创新表现,但不应解释为城市创新能力的绝对数值。

# 整理排名图数据:按 PII 从低到高排列,使高排名城市显示在图形上方
rank_plot_data <- city_scores %>%
  arrange(PII) %>%
  mutate(
    rank_city_label = factor(
      paste0(str_pad(PII_rank, width = 2, pad = "0"), ". ", 城市简称),
      levels = paste0(str_pad(PII_rank, width = 2, pad = "0"), ". ", 城市简称)
    )
  )

city_rank_plot <- ggplot(
  rank_plot_data,
  aes(x = PII, y = rank_city_label, color = 城市等级)
) +
  # 用线段表示从 0 到 PII 的得分距离,减少柱状图的视觉压迫感
  geom_segment(
    aes(x = 0, xend = PII, yend = rank_city_label),
    linewidth = 3.2,
    alpha = 0.28,
    lineend = "round"
  ) +
  # 用点突出每个城市的最终 PII 得分
  geom_point(size = 3.8, alpha = 0.95) +
  # 在点右侧标注 PII 数值,便于直接读取
  geom_text(
    aes(label = number(PII, accuracy = 0.001)),
    hjust = -0.18,
    size = 3.2,
    color = "grey25"
  ) +
  scale_color_manual(
    values = c(
      "超一线城市" = "#B23A48",
      "新一线城市" = "#2F6F73"
    )
  ) +
  scale_x_continuous(
    limits = c(0, max(rank_plot_data$PII, na.rm = TRUE) * 1.14),
    labels = number_format(accuracy = 0.1),
    expand = expansion(mult = c(0.01, 0.02))
  ) +
  theme_minimal(base_size = 11) +
  theme(
    legend.position = "top",
    legend.justification = "center",
    plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5, size = 10),
    plot.caption = element_text(color = "grey50", size = 8, hjust = 1),
    axis.title.y = element_blank(),
    axis.title.x = element_text(face = "bold", size = 10),
    axis.text.y = element_text(color = "grey20", size = 9),
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_rect(color = "grey82", fill = NA, linewidth = 0.35)
  ) +
  labs(
    title = "2024 年中国超一线与新一线城市专利创新指数",
    subtitle = "PII 基于熵权法与 TOPSIS 相对贴近度计算",
    caption = "注:PII 为样本内部相对评价结果,得分受城市样本、指标体系和权重方案影响。",
    x = "Patent Innovation Index (PII)",
    color = NULL
  )

city_rank_plot


PII 空间分布图

排名图能够展示城市之间的相对顺序,但不能呈现这些城市在全国空间格局中的分布位置。下图将城市 PII 按 Fisher-Jenks 自然断点法划分为若干等级,并用固定大小的城市点展示其空间分布,用于观察高 PII 城市是否集中在特定区域。

中国地图底图使用说明

中国全图制图必须使用符合国家规范的底图数据。建议使用自然资源部标准地图服务系统或已通过审核、带有审图号的中国地图数据,并在图注中标明底图来源和审图号。涉及中国全图时,应完整表示中国疆域,并规范处理南海诸岛附图、台湾岛、钓鱼岛及其附属岛屿等要素。

参考:自然资源部《公开地图内容表示规范》自然资源部标准地图服务系统

library(sf)
library(ggspatial)
library(cowplot)
library(classInt)
library(scales)
library(ggrepel)

# 1. 读取中国底图
# 教学演示版使用第 4 章同款在线底图;正式发表时请替换为带审图号的标准地图数据
china_base <- sf::read_sf("https://geojson.cn/api/china/100000.topo.json") %>%
  sf::st_set_crs(4326) %>%
  sf::st_transform(4490)

map_source_note <- "底图来源:GeoJSON.cn 开源接口;仅用于课堂代码演示,正式发表请替换为审图底图。"

# 2. 内置目标城市中心点坐标
city_coords <- tibble::tribble(
  ~城市, ~lon, ~lat,
  "上海", 121.4737, 31.2304,
  "北京", 116.4074, 39.9042,
  "深圳", 114.0579, 22.5431,
  "广州", 113.2644, 23.1291,
  "成都", 104.0665, 30.5723,
  "杭州", 120.1551, 30.2741,
  "重庆", 106.5516, 29.5630,
  "苏州", 120.5853, 31.2989,
  "武汉", 114.3054, 30.5931,
  "西安", 108.9398, 34.3416,
  "南京", 118.7969, 32.0603,
  "长沙", 112.9388, 28.2282,
  "天津", 117.2000, 39.0842,
  "郑州", 113.6254, 34.7466,
  "东莞", 113.7518, 23.0207,
  "无锡", 120.3119, 31.4912,
  "宁波", 121.5503, 29.8746,
  "青岛", 120.3826, 36.0671,
  "合肥", 117.2272, 31.8206
) %>%
  mutate(city_key = normalize_city(城市))

# 3. 城市点数据
city_points <- city_scores %>%
  left_join(city_coords, by = "city_key") %>%
  filter(!is.na(lon), !is.na(lat)) %>%
  sf::st_as_sf(coords = c("lon", "lat"), crs = 4490)

# 4. Fisher-Jenks 自然断点分级
pii_values <- city_points$PII
n_class <- min(5, length(unique(na.omit(pii_values))))

pii_breaks <- classInt::classIntervals(
  pii_values,
  n = n_class,
  style = "fisher",
  warnSmall = FALSE
)$brks %>%
  unique()

pii_labels <- paste0(
  ifelse(seq_along(pii_breaks[-1]) == 1, "[", "("),
  number(pii_breaks[-length(pii_breaks)], accuracy = 0.001),
  ", ",
  number(pii_breaks[-1], accuracy = 0.001),
  "]"
)

city_points <- city_points %>%
  mutate(
    PII_class = cut(
      PII,
      breaks = pii_breaks,
      labels = pii_labels,
      include.lowest = TRUE
    )
  ) %>%
  arrange(PII)

label_points <- city_points %>%
  arrange(PII_rank) %>%
  slice_head(n = 8)

# 5. 地图范围与经纬网
bb_main <- c(xmin = 73,  xmax = 135.5, ymin = 18, ymax = 54)
bb_scs  <- c(xmin = 105, xmax = 125,   ymin = 3,  ymax = 26)

main_bbox <- sf::st_as_sfc(sf::st_bbox(bb_main, crs = sf::st_crs(4490)))
scs_bbox  <- sf::st_as_sfc(sf::st_bbox(bb_scs, crs = sf::st_crs(4490)))

main_graticule <- sf::st_graticule(
  main_bbox,
  lon = seq(80, 130, 10),
  lat = seq(20, 50, 10)
) %>%
  sf::st_transform(4490)

scs_graticule <- sf::st_graticule(
  scs_bbox,
  lon = seq(110, 125, 5),
  lat = seq(5, 25, 5)
) %>%
  sf::st_transform(4490)

# 6. 主图
main_map <- ggplot() +
  geom_sf(
    data = main_graticule,
    color = "#D8D3CA",
    linewidth = 0.22,
    linetype = "dashed"
  ) +
  geom_sf(
    data = china_base,
    fill = "#F8F5EF",
    color = "#BDB6AA",
    linewidth = 0.28
  ) +
  geom_sf(
    data = city_points,
    aes(fill = PII_class),
    shape = 21,
    size = 5.6,
    alpha = 0.93,
    color = "grey25",
    stroke = 0.45
  ) +
  ggrepel::geom_label_repel(
    data = label_points,
    aes(label = 城市简称, geometry = geometry),
    stat = "sf_coordinates",
    size = 3,
    color = "grey20",
    fill = alpha("white", 0.78),
    label.size = 0.15,
    label.padding = grid::unit(0.12, "lines"),
    segment.color = "grey55",
    segment.linewidth = 0.25,
    min.segment.length = 0,
    box.padding = 0.35,
    max.overlaps = 20
  ) +
  ggspatial::annotation_scale(
    location = "bl",
    width_hint = 0.22,
    style = "ticks",
    text_cex = 0.65,
    line_col = "grey35",
    text_col = "grey35"
  ) +
  ggspatial::annotation_north_arrow(
    location = "tl",
    which_north = "true",
    height = grid::unit(0.9, "cm"),
    width = grid::unit(0.9, "cm"),
    style = ggspatial::north_arrow_fancy_orienteering(
      fill = c("grey20", "white"),
      line_col = "grey20"
    )
  ) +
  scale_fill_brewer(
    palette = "YlOrRd",
    direction = 1,
    name = "PII\n(Fisher-Jenks)",
    guide = guide_legend(reverse = TRUE)
  ) +
  scale_x_continuous(
    breaks = seq(80, 130, 10),
    labels = function(x) paste0(x, "°E")
  ) +
  scale_y_continuous(
    breaks = seq(20, 50, 10),
    labels = function(y) paste0(y, "°N")
  ) +
  coord_sf(
    crs = 4490,
    xlim = c(bb_main["xmin"], bb_main["xmax"]),
    ylim = c(bb_main["ymin"], bb_main["ymax"]),
    expand = FALSE
  ) +
  theme_minimal(base_size = 11) +
  theme(
    panel.background = element_rect(fill = "#FCFBF8", color = NA),
    panel.grid = element_blank(),
    panel.border = element_rect(color = "grey70", fill = NA, linewidth = 0.45),
    axis.title = element_blank(),
    axis.text = element_text(color = "grey45", size = 8),
    axis.ticks = element_line(color = "grey70", linewidth = 0.25),
    legend.position = "right",
    legend.key.height = grid::unit(0.58, "cm"),
    legend.key.width = grid::unit(0.58, "cm"),
    legend.title = element_text(face = "bold", size = 9),
    legend.text = element_text(size = 8),
    plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5, size = 10),
    plot.caption = element_text(color = "grey50", size = 8, hjust = 1)
  ) +
  labs(
    title = "2024 年中国超一线与新一线城市专利创新指数空间分布",
    subtitle = "城市点大小固定;颜色表示 PII 的 Fisher-Jenks 自然断点分组",
    caption = paste0(map_source_note, " | CRS: CGCS2000 (EPSG:4490)")
  )

# 7. 南海诸岛附图
scs_map <- ggplot() +
  geom_sf(
    data = scs_graticule,
    color = "#D8D3CA",
    linewidth = 0.16,
    linetype = "dashed"
  ) +
  geom_sf(
    data = china_base,
    fill = "#F8F5EF",
    color = "#BDB6AA",
    linewidth = 0.16
  ) +
  coord_sf(
    crs = 4490,
    xlim = c(bb_scs["xmin"], bb_scs["xmax"]),
    ylim = c(bb_scs["ymin"], bb_scs["ymax"]),
    expand = FALSE,
    datum = NA
  ) +
  theme_void() +
  theme(
    panel.background = element_rect(fill = "white", color = NA),
    panel.border = element_rect(color = "grey55", fill = NA, linewidth = 0.35)
  )

# 8. 合成主图与南海诸岛附图
china_bubble_map <- cowplot::ggdraw(main_map) +
  cowplot::draw_plot(
    scs_map,
    x = 0.71,
    y = 0.075,
    width = 0.18,
    height = 0.24
  )

china_bubble_map


分维度热力图

PII 综合排名可以展示城市之间的相对顺序,但不能直接解释不同城市的得分来源。为进一步观察指数结构,下图将 PII 拆解为五个维度的标准化得分,并以城市-维度矩阵形式展示。

纵轴按照 PII 排名排序,横轴表示五个指标维度,颜色表示对应维度的 0-1 标准化得分。该图可用于识别城市创新表现的结构差异:若某城市在多个维度上均保持较高得分,说明其专利创新表现较为均衡;若仅少数维度较高,则说明综合得分可能主要由特定维度推动。

# 1. 固定城市排序:按 PII 排名从高到低展示
city_order <- city_scores %>%
  arrange(PII_rank) %>%
  transmute(
    city_key,
    city_label = paste0(stringr::str_pad(PII_rank, width = 2, pad = "0"), ". ", 城市简称)
  )

# 2. 固定维度顺序,保证图形阅读逻辑与指标体系一致
dimension_order <- c("创新规模", "创新质量", "主体结构", "创新合作", "技术多样性")

# 3. 整理热力图长表数据
dimension_long <- dimension_scores %>%
  left_join(city_order, by = "city_key") %>%
  mutate(
    city_label = factor(city_label, levels = rev(city_order$city_label)),
    dimension = factor(dimension, levels = dimension_order),
    score_label = scales::number(dimension_score, accuracy = 0.01),
    label_color = if_else(dimension_score <= 0.30, "white", "grey20")
  )

# 4. 绘制城市-维度热力图
dimension_heatmap <- ggplot(
  dimension_long,
  aes(x = dimension, y = city_label, fill = dimension_score)
) +
  geom_tile(
    color = "white",
    linewidth = 0.65,
    width = 0.96,
    height = 0.92
  ) +
  geom_text(
    aes(label = score_label, color = label_color),
    size = 2.75,
    fontface = "bold"
  ) +
  scale_color_identity() +
  scale_fill_viridis_c(
    option = "E",
    direction = 1,
    begin = 0.05,
    end = 0.95,
    limits = c(0, 1),
    breaks = seq(0, 1, 0.25),
    labels = scales::number_format(accuracy = 0.01),
    name = "维度得分\n(0-1)",
    guide = guide_colorbar(
      barheight = grid::unit(4.2, "cm"),
      barwidth = grid::unit(0.45, "cm"),
      ticks.colour = "grey35",
      frame.colour = "grey70"
    )
  ) +
  scale_x_discrete(position = "top", expand = expansion(add = 0.02)) +
  scale_y_discrete(expand = expansion(add = 0.02)) +
  theme_minimal(base_size = 11) +
  theme(
    legend.position = "right",
    legend.title = element_text(face = "bold", size = 9),
    legend.text = element_text(size = 8),
    plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5, size = 10),
    plot.caption = element_text(color = "grey50", size = 8, hjust = 1),
    axis.title = element_blank(),
    axis.text.x = element_text(face = "bold", color = "grey20", size = 10, margin = margin(b = 6)),
    axis.text.y = element_text(color = "grey25", size = 8.5),
    panel.grid = element_blank(),
    panel.border = element_rect(color = "grey82", fill = NA, linewidth = 0.35),
    plot.margin = margin(8, 12, 8, 8)
  ) +
  labs(
    title = "城市专利创新指数的分维度画像",
    subtitle = "Rows ordered by PII ranking; fill color shows standardized dimension score",
    caption = "注:维度得分为对应维度内标准化指标的平均值,仅用于解释 PII 的结构来源。"
  )

dimension_heatmap


规模与强度的差异

专利总量反映城市专利活动的绝对规模,每万人专利量则反映人口标准化后的创新强度。二者具有不同含义:前者容易受到城市人口规模、产业体量和企业数量影响,后者更适合比较不同城市单位人口对应的专利产出水平。

下图以专利总量为横轴、每万人专利量为纵轴,并加入两条中位数参考线。参考线将城市大致划分为“高规模-高强度”“高规模-低强度”“低规模-高强度”和“低规模-低强度”四类,有助于识别城市专利创新表现的结构差异。

library(ggrepel)

# 1. 构造参考线:使用中位数划分规模与强度的相对高低
scale_median <- median(city_scores$patent_total, na.rm = TRUE)
intensity_median <- median(city_scores$patents_per_10k, na.rm = TRUE)

# 2. 标注对象:优先标注 PII 排名前列城市,以及规模或强度特别突出的城市
label_cities <- city_scores %>%
  mutate(
    label_flag = PII_rank <= 8 |
      patent_total >= quantile(patent_total, 0.85, na.rm = TRUE) |
      patents_per_10k >= quantile(patents_per_10k, 0.85, na.rm = TRUE)
  ) %>%
  filter(label_flag)

# 3. 绘制规模-强度散点图
scale_intensity_plot <- ggplot(
  city_scores,
  aes(x = patent_total, y = patents_per_10k)
) +
  # 中位数参考线,用于辅助识别四类城市位置
  geom_vline(
    xintercept = scale_median,
    linetype = "dashed",
    linewidth = 0.35,
    color = "grey55"
  ) +
  geom_hline(
    yintercept = intensity_median,
    linetype = "dashed",
    linewidth = 0.35,
    color = "grey55"
  ) +

  # 四象限提示文字,保持低饱和度,避免干扰主体数据
  annotate(
    "label",
    x = max(city_scores$patent_total, na.rm = TRUE) * 0.75,
    y = max(city_scores$patents_per_10k, na.rm = TRUE) * 0.94,
    label = "高规模-高强度",
    size = 3,
    label.size = 0,
    fill = alpha("white", 0.78),
    color = "grey35"
  ) +
  annotate(
    "label",
    x = min(city_scores$patent_total, na.rm = TRUE) * 1.35,
    y = max(city_scores$patents_per_10k, na.rm = TRUE) * 0.94,
    label = "低规模-高强度",
    size = 3,
    label.size = 0,
    fill = alpha("white", 0.78),
    color = "grey35"
  ) +

  # 城市点:颜色表示城市等级,大小表示 PII 综合得分
  geom_point(
    aes(fill = 城市等级, size = PII),
    shape = 21,
    color = "grey25",
    stroke = 0.45,
    alpha = 0.88
  ) +

  # 使用 repel 标签减少重叠
  ggrepel::geom_label_repel(
    data = label_cities,
    aes(label = 城市简称),
    size = 3,
    color = "grey20",
    fill = alpha("white", 0.82),
    label.size = 0.15,
    label.padding = grid::unit(0.12, "lines"),
    segment.color = "grey55",
    segment.linewidth = 0.25,
    min.segment.length = 0,
    box.padding = 0.35,
    max.overlaps = 30
  ) +
  scale_x_log10(
    labels = label_number(big.mark = ","),
    breaks = scales::breaks_log(n = 5)
  ) +
  scale_y_continuous(
    labels = label_number(accuracy = 1),
    expand = expansion(mult = c(0.04, 0.12))
  ) +
  scale_size_area(
    max_size = 8.5,
    labels = number_format(accuracy = 0.01),
    name = "PII"
  ) +
  scale_fill_manual(
    values = c(
      "超一线城市" = "#B23A48",
      "新一线城市" = "#2F6F73"
    ),
    name = NULL
  ) +
  theme_minimal(base_size = 11) +
  theme(
    legend.position = "right",
    legend.title = element_text(face = "bold", size = 9),
    legend.text = element_text(size = 8),
    plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5, size = 10),
    plot.caption = element_text(color = "grey50", size = 8, hjust = 1),
    axis.title = element_text(face = "bold", size = 10),
    axis.text = element_text(color = "grey35"),
    panel.grid.minor = element_blank(),
    panel.border = element_rect(color = "grey82", fill = NA, linewidth = 0.35)
  ) +
  labs(
    title = "专利规模与人口标准化强度的城市差异",
    subtitle = "Dashed lines indicate sample medians; point size represents PII",
    caption = "注:横轴使用 log10 标度,以降低专利总量长尾分布对读图的影响。",
    x = "专利申请总量(log10 标度)",
    y = "每万人专利申请量",
    fill = NULL
  )

scale_intensity_plot

结果解释

PII 的解释应同时关注综合得分与分维度结构。综合得分较高的城市,可能在多个维度上均衡领先,也可能主要由某一优势维度推动。因此,类型识别不宜仅依据排名,还应结合五个维度得分之间的差异。

下方代码根据每个城市得分最高的维度识别其优势方向,并进一步计算最高维度与第二高维度之间的差距。若差距较小,则将其标记为“相对均衡型”,避免把轻微差异过度解释为明确类型。

基于分维度得分的城市创新类型初步识别
排名 城市等级 城市 PII 优势维度 优势得分 领先幅度 初步类型
1 超一线城市 北京 0.698 创新规模 0.994 0.089 规模优势型
2 新一线城市 南京 0.651 创新质量 0.902 0.146 质量优势型
3 新一线城市 杭州 0.623 创新质量 0.744 0.017 相对均衡型
4 新一线城市 武汉 0.569 创新合作 0.710 0.004 相对均衡型
5 新一线城市 成都 0.569 创新质量 0.814 0.170 质量优势型
6 超一线城市 广州 0.566 创新规模 0.673 0.017 相对均衡型
7 新一线城市 天津 0.545 创新质量 0.730 0.084 质量优势型
8 新一线城市 合肥 0.541 技术多样性 0.725 0.073 技术广度型
9 超一线城市 上海 0.531 技术多样性 0.726 0.002 相对均衡型
10 新一线城市 西安 0.521 创新质量 0.627 0.040 相对均衡型
11 新一线城市 长沙 0.502 创新质量 0.816 0.219 质量优势型
12 新一线城市 苏州 0.498 创新质量 0.736 0.032 相对均衡型
13 新一线城市 青岛 0.487 主体结构 0.587 0.003 相对均衡型
14 超一线城市 深圳 0.465 创新规模 0.955 0.307 规模优势型
15 新一线城市 郑州 0.437 创新合作 0.772 0.137 合作网络型
16 新一线城市 重庆 0.432 技术多样性 0.664 0.065 技术广度型
17 新一线城市 无锡 0.419 创新质量 0.717 0.155 质量优势型
18 新一线城市 宁波 0.322 主体结构 0.538 0.059 主体多元型
19 新一线城市 东莞 0.281 主体结构 0.488 0.049 相对均衡型

5.0 专题研究 2: 回顾与反思

5.0 专题研究 2:回顾与反思

专题回顾

本节对专题研究 2:“中国城市专利创新指数”进行回顾。该专题以 2024 年专利申请明细数据为基础,以超一线与新一线城市为目标样本,构建城市层面的专利创新指数(Patent Innovation Index, PII)。PII 将专利规模、人口标准化强度、发明类专利占比、他引情况、申请主体结构、合作申请和 IPC 技术多样性整合为一个综合评价框架,用于比较不同城市的专利创新表现及其结构差异。

本专题主要完成了五个环节:首先,检查城市人口表与专利明细表的样本覆盖情况,明确可进入指数计算的城市范围;其次,按 申请号 将原始专利明细整理为申请级数据,减少状态更新导致的重复计数;再次,从规模、质量、主体、合作与技术多样性五个维度构建城市级指标;随后,使用熵权法根据指标离散程度确定权重;最后,使用 TOPSIS 计算各城市相对于理想创新状态的贴近度,并通过排名图、空间分布图、维度热力图和规模-强度散点图解释结果。

PII 排名前列城市回顾
排名 城市等级 城市 PII 专利总量 每万人专利量
1 超一线城市 北京 0.698 167,109 76.6
2 新一线城市 南京 0.651 55,827 58.3
3 新一线城市 杭州 0.623 77,317 61.3
4 新一线城市 武汉 0.569 55,271 40.0
5 新一线城市 成都 0.569 55,983 26.1
6 超一线城市 广州 0.566 86,764 45.7
7 新一线城市 天津 0.545 36,790 27.0
8 新一线城市 合肥 0.541 44,787 44.8

方法总结

本专题的方法价值主要体现在三个方面:

  • 申请级数据重构:专利明细表并不天然满足“每行一件专利”的统计口径。按 申请号 去重并重构字段,可以减少重复记录对城市专利规模的影响。
  • 多维指标体系构建:城市专利创新表现不宜仅由专利总量表示。人口标准化强度、发明类占比、主体结构、合作关系和技术领域分布共同构成 PII 的解释基础。
  • EWM-TOPSIS 综合评价:熵权法根据样本内部差异确定指标权重,TOPSIS 通过理想解和负理想解计算综合贴近度,适合用于横截面城市比较。

专题研究 2 反思

局限与风险

综合指数能够压缩复杂信息,也会带来解释风险。PII 有助于快速比较城市专利创新表现,但不能直接等同于城市真实创新能力。在正式研究中,至少需要注意以下问题。

  1. 专利指标的覆盖边界
    专利更适合衡量可被知识产权制度记录的技术产出,难以充分覆盖商业模式创新、组织创新、开源技术、基础科学突破或服务业创新。不同产业的专利申请倾向也存在差异,因此高专利数量不必然对应更高创新质量。

  2. 引用与授权的时间滞后
    专利从申请到公开、审查、授权和被引用都需要时间。对于 2024 年截面数据,被引证次数他引次数 对新近专利并不完全公平。引用类指标更适合在多年窗口中观察。

  3. 熵权法的解释边界
    熵权法根据指标离散程度分配权重,可以减少主观赋权,但并不代表理论重要性。若某个指标差异较大主要来自数据噪声、口径偏差或极端值,也可能获得较高权重。因此,权重结果仍需结合理论与数据质量进行解释。

  4. TOPSIS 的样本依赖性
    TOPSIS 的理想解和负理想解来自当前样本。若加入更多城市、补齐缺失城市,或调整指标体系,同一城市的 PII 和排名都可能变化。因此,PII 只能在明确样本边界和指标口径的前提下解释。

  5. 行政边界与创新功能区不完全一致
    专利申请地址通常与企业总部、研发机构注册地或申请人地址有关,未必等同于真实研发活动发生地。若研究目标转向区域创新网络,可能需要采用都市圈、城市群或跨城市合作网络作为分析框架。

稳健性分析建议

若将本专题扩展为更正式的研究,可以进一步开展以下敏感性检验:

  • 更换权重方案:比较熵权法、等权法、PCA 权重或专家权重下的 PII 排名是否一致。
  • 更换标准化方法:比较 min-max、Z-score、百分位排名等方法对城市排序的影响。
  • 移除单一维度:采用 leave-one-dimension-out 方法,每次删除一个维度重新计算 PII,观察排名是否稳定。
  • 调整质量指标:将他引指标替换为授权占比、发明授权占比,或使用多年份引用窗口。
  • 补充空间与网络视角:若能获取申请人地址或合作城市信息,可进一步分析城市创新网络、技术专业化和跨区域知识联系。

本专题的教学意义

本专题的重点在于展示综合指数构建的完整思路:明确概念框架,选择可复现指标;处理数据口径,完成标准化与方向统一;说明权重来源,计算综合评价结果;最后回到指标、方法和样本边界中反思结论的适用范围。

综合指数不是结论本身,而是一种组织证据、比较差异和提出进一步问题的分析工具。

6.0 本章练习

6.0 动手实战

提醒

专题章节中的 6.0 部分通常不再提供完整的可复现模板。请先回顾本章两个专题研究中的研究设计、指标构建、标准化、赋权和结果解释流程,再完成下列练习。

课堂练习

本部分不限定具体主题,也不要求必须构建创新指数。请结合本章综合指数构建方法,以及前几章已经学习的数据清洗、可视化、空间分析、点数据分析、网络分析或人口普查数据分析等内容,独立完成一个小型综合指数研究。


6.1 任务说明

请选择一个具有明确解释对象的城市或区域议题,构建一个可复现、可解释的综合指数。研究主题可以来自本章内容,也可以结合前几章专题继续拓展。例如:

  1. 基于本章数据改进已有指数:调整 CI 或 PII 的指标体系、标准化方法或权重方案,比较新旧结果是否一致。
  2. 构建新的城市或区域指数:例如城市公共服务指数、宜居性指数、交通可达性指数、商业活力指数、社会剥夺指数、数字活动活跃度指数、创新生态指数等。
  3. 结合前几章方法开展指数研究:利用空间点数据、人口普查数据、POI 数据、网络数据或社交媒体数据,构建面向特定问题的综合评价指标。
  4. 更换空间尺度或样本范围:在区县、街区、地级市、省份、都市圈或网格尺度下构建指数,并比较不同尺度下结果的差异。

报告需要清楚说明:指数衡量的核心概念是什么,分析单元是什么,为什么选择这些指标,数据是否支持这些指标,以及最终结果应如何解释。


6.2 硬性要求(Checklist)

你的 R Markdown 报告至少应包含以下内容:

  1. 研究问题与分析单元
    说明指数衡量什么概念,分析单元是什么,主要用于比较、诊断、分类还是解释。

  2. 数据来源与样本覆盖检查
    说明数据来源、时间范围、空间尺度和样本覆盖情况,并检查缺失值、重复记录或口径不一致问题。

  3. 指标体系设计
    至少包含 3 个维度、8 个指标。每个指标都需要说明含义、计算方式、方向和数据来源。

  4. 数据清洗与指标重构
    展示如何从原始字段构建分析单元层面的指标。若使用明细型数据,应说明去重、聚合和异常值处理规则。

  5. 标准化与权重设定
    至少使用一种标准化方法,并说明处理理由。权重方案可以选择等权、熵权法、PCA 权重、专家权重或自定义权重。

  6. 综合指数计算
    至少输出一张综合指数结果表。若使用 TOPSIS,需要说明理想解和负理想解的含义。

  7. 结果可视化
    至少输出 2 张图,例如排名图、分维度热力图、散点图、地图、雷达图或分组比较图。

  8. 结果解释与方法反思
    讨论指数结果的主要发现,并至少完成一种敏感性分析,例如更换权重、删除某个维度、更换标准化方法或调整样本范围。


6.3 建议选题

方向 A:综合指数稳健性检验
选择本章 CI 或 PII,分别使用等权法、熵权法或其他权重方案重新计算指数,比较排名和主要结论是否稳定。

方向 B:城市公共服务指数
结合教育、医疗、交通、公园、商业设施等数据,构建城市或街区尺度的公共服务水平指数。

方向 C:城市宜居性指数
综合人口密度、住房条件、通勤、公共服务、绿地、商业便利度或环境指标,评价不同区域的宜居性差异。

方向 D:交通可达性指数
使用地铁站、公交站、道路网络或出行数据,构建街区、社区或网格尺度的交通可达性综合指数。

方向 E:社会剥夺或社会脆弱性指数
基于人口普查或统计数据,从收入、就业、住房、教育、年龄结构等维度构建区域社会剥夺或社会脆弱性指数。

方向 F:商业活力或数字活动指数
结合 POI、社交媒体点、评论数据或夜间灯光等数据,构建城市内部活动活跃度或商业活力指数。


6.4 提交内容

最终提交一个可以运行的 R Markdown 报告,建议结构如下:

  • 研究问题与指标框架
  • 数据来源与样本覆盖检查
  • 数据清洗与指标重构
  • 描述统计与指标诊断
  • 标准化、赋权与指数计算
  • 排名、可视化与结果解释
  • 方法反思与敏感性分析

评价重点不在于得到一个“唯一正确”的排名,而在于是否能够清楚说明:指数如何定义、指标如何选择、数据如何处理、权重如何设定,以及结论在多大程度上稳定。