问题:旅游业指标在控制经济社会变量后,对近岸海域水质的净影响方向。 落成的口径:山东省 7 个沿海城市(青岛、烟台、威海、潍坊、日照、东营、滨州)2021–2023; 自变量 = 旅游接待人次 / 旅游收入;控制变量 = 常住人口 + 第二产业增加值(含年份固定效应); 目标变量 = 近岸海域优良(一、二类)水质比例。 这一轮真跑了:数据采集 → 逐条回原文核对 → 残差交互分析 → 零模型 5000 次 × 3 个种子 × 两种置换方案 → 按城市整块重抽 2000 次 × 3 个种子 → 留一城市 → 正负对照与检出率模拟。 纯 CPU,七个步骤合计约 14 秒机时;时间几乎全花在找数据上。
三句话:
这一轮跑之前写下的预测(P1:在这个样本量上测不出任何关联)被推翻了,两种零模型方案 都给出 p < 0.05、三个种子一致。我们按预注册如实报告这个推翻。但同一份预注册里的 另一条判据也触发了:按城市重抽的偏相关 95% 区间横跨 −1.00 到 1.00,方向并没有被钉住。 p 值和区间说的不是一回事,两个都得看。
7 个城市 × 3 年 = 21 个格子。逐格找下来:
| 字段 | 拿到 | 缺哪些 |
|---|---|---|
| 年末常住人口 | 21/21 | — |
| 第二产业增加值 | 20/21 | 威海 2023 |
| 近岸海域优良水质比例 | 14/21 | 烟台 2022/2023、潍坊 2022、日照 2021/2022、东营 2022、滨州 2022 |
| 旅游接待人次 | 15/21 | 滨州三年全缺、潍坊 2021/2022、威海 2023 |
| 旅游收入 | 15/21 | 同上 |
五项同时齐全的只有 10 个:青岛 2021/2022/2023、烟台 2021、威海 2021/2022、 潍坊 2023、日照 2023、东营 2021/2023。
几个具体的坑,都是这类研究会反复撞上的:
跑之前写死的规则是:同一个城市在进入分析的那些年份里,指标口径必须一致;不一致的, 该城市该指标按缺失处理,另做一次把它们加回来的敏感性分析,两个结果都报。
对上真实数据之后:
work/logs/analyze_strict.log。这个口径差到底有多大,我们量了: 威海 2021 年公报同一段里同时给了三套数 —— 接待旅游总人数 4407.3 万人次、接待国内游客 4403.93 万人次、接待入境游客 3.37 万人次。 入境占比 0.076%;旅游总收入与国内旅游收入在公报里是同一个数(都是 533.52 亿元)。 我们又把 20 份公报正文全量扫了「入境 / 境外 / 外汇收入」,只有威海 2021 这一处单列入境, 其余 19 份零命中。2021–2023 这三年边境基本关闭,两种口径差在千分之一量级。
所以敏感性分析(10 个观测全进)在数值上是可信的,本报告后面的数字都出自它。 但口径规则本身没有放宽、也没有回头改预注册,两条路径的结果都在包里: work/results/analysis_strict.json(n=3,跳过)与 work/results/analysis.json(n=10)。
目标变量这一侧的口径更麻烦,而且没法用同样的办法化解:
| 口径 | 城市 |
|---|---|
| 点位法(国控点位优良率) | 威海 2021/2022/2023 |
| 面积法(优良水质面积比例) | 青岛 2021/2022/2023、烟台 2021、潍坊 2021、东营 2021/2023、滨州 2021 |
| 原文未写明点位还是面积 | 潍坊 2023、日照 2023、滨州 2023 |
预注册里对这种情况的处置是:主结论降级为仅方向性提示,并点名是哪几个城市。 本报告照此执行。这也是为什么第 0 节说「方向性提示」而不是「结论」。
10 个观测,6 个城市,扣掉常数、两个控制变量与两个年份哑变量后,残差自由度 4。
| 旅游接待人次 | 旅游收入 | |
|---|---|---|
| 偏相关 r | 0.896 | 0.829 |
| 每翻一倍对应的水质变化 | +29.1 个百分点 | +18.2 个百分点 |
| 零模型(置换旅游指标)p,三个种子 | 0.0148 / 0.0156 / 0.0130 | 0.0404 / 0.0350 / 0.0346 |
| 第二种零模型(Freedman–Lane)p | 0.0010 / 0.0006 / 0.0006 | 0.0032 / 0.0028 / 0.0036 |
| 比只有控制变量的平凡基线多解释 | ΔR² = 0.486 | ΔR² = 0.416 |
| 按城市重抽的偏相关 95% 区间(种子 0) | [−1.00, 1.00] | [−1.00, 1.00] |
| 重抽里方向为负的比例 | 10.5% | 9.9% |
「3 个种子方向一致」这句话旁边必须放着那个区间——这是跑之前就写进判据的要求。 p 值说:假如这 10 行彼此独立,这个方向和随机打乱能分开。但这 10 行只来自 6 个城市、水质几乎只在城市之间变;复核补做的模拟显示,在同样的城市聚集结构下、旅游与水质毫无关系时,这个检验有约 14% 的概率给出 p<0.05(名义 5%),改成按城市整块置换则 p=0.50。所以 p=0.015 不能当作「信号和随机能分开」的证据。区间说:只有 6 个城市可抽, 换一组城市,方向可能整个翻过来(约十分之一的重抽确实翻了)。两句都是真的,别只引一句。
为什么不能读成「旅游改善水质」,第 0 节那个 0.48 是关键:
| 城市 | 年份 | 水质(%) | 城市内部波动 | 旅游人次最大/最小 |
|---|---|---|---|---|
| 青岛 | 2021/2022/2023 | 99.0 / 99.0 / 99.3 | 0.14 个百分点 | 1.98 倍 |
| 威海 | 2021/2022 | 100 / 100 | 0 | 1.24 倍 |
| 东营 | 2021/2023 | 76.1 / 78.7 | 1.30 个百分点 | 1.37 倍 |
同一座城市三年里旅游人次能差一倍,水质几乎不动;而城市之间的水质差距有 10.5 个百分点 (青岛 99、威海 100,对潍坊 43.8–75.6、东营 76–79)。这条关联住在城市之间,不住在城市内部。 城市之间的差别里,最不像旅游造成的那一条是地理:青岛、威海、日照面向黄海开阔海域, 潍坊、东营、滨州在渤海/莱州湾一侧,本身就是半封闭海湾、受入海河流总氮影响大。 旅游体量也沿着同一条线分布。这种情况下,正向系数最自然的解释是反向因果加地理混杂 ——水好所以旅游旺,不是旅游旺所以水好。 我们在预注册里就把反向因果列为已声明混杂, 这一轮的数据规模不足以把它拆开。
本来能拆开它的是城市固定效应(只看城市内部的变化)。10 个观测、6 个城市, 加上城市哑变量之后自由度归零,这条路在本轮样本量下走不通,代码如实记为 insufficient_df,没有硬跑一个没有自由度的模型出来。
跑之前写死的作废开关:正对照测不出、或负对照虚警超过 10%,主结论一并作废。
检出率曲线(n = 10,200 次模拟 × 每次 399 次置换):
| 真实偏相关 | 0.0 | 0.2 | 0.4 | 0.6 | 0.8 |
|---|---|---|---|---|---|
| 被测出来的比例 | 7% | 6% | 22% | 40% | 86% |
在这个样本量上,真实偏相关得达到 0.8 才有八成把握看得见。 反过来说: 这一轮观测到的 0.896 之所以被检出,正是因为它大得离谱——而一个大到 0.9 的偏相关, 在只有 6 个城市、且城市之间本身就按地理分成两组的情况下,更像是分组效应,不是净效应。
去掉任意一个城市重算,旅游接待人次的偏相关仍在 0.786–0.986,方向没有被单个城市带着走。 但旅游收入那一侧不同:去掉东营之后偏相关从 0.829 掉到 0.159,说明收入口径那条结论 对单个城市敏感得多。两条都在 work/results/analysis.json 的 loco 段里。
work/data/data_provenance.json,每条都带: 城市、年份、指标原文、数值、单位、实际下载到的 URL、随包交付的来源文件、 以及那句话的原文片段。work/data/sources/(36 份,1.1 MB),可以直接打开核。audit_delivery.py 会把面板里的每一个数字回到来源文件里重新 grep 一遍,对不上就报错。.gov.cn 原文 (青岛 2021 那份是官方统计公报 PDF,本机连不上官网,经服务端抓取取回); 潍坊、滨州、东营的水质数字来自第三方统计公报转载库(tjgb.hongheiku.com、etmoc.com), 数字已在本地文件里核到原文,但没能与官方原始页面二次比对。 每条记录的 source_type 字段都标了这一点。如果这批数据要进正式研究,这 6 条建议复核。``bash python3 work/code/00_assemble_provenance.py # 回原文核对 85 条记录 python3 work/code/01_build_panel.py # 生成 panel.csv 与 panel_strict.csv python3 work/code/02_analyze.py panel.csv analysis.json # 主分析(需 numpy) python3 work/code/02_analyze.py panel_strict.csv analysis_strict.json python3 work/code/03_recheck.py panel.csv recheck.json # 独立复算(纯标准库) python3 work/code/05_diagnostics.py # 城市内 / 城市间分解 python3 work/code/04_figs.py # 出图(需 matplotlib) python3 audit_delivery.py # 包内自检,只用标准库 ``
全部随机性由显式种子控制(置换、重抽、对照噪声一律走标准库 random.Random(seed)), 同机重跑逐位一致。主分析走 numpy,独立复算是另写的纯标准库实现,两者在 β、偏相关、ΔR²、 三个种子的置换 p、重抽区间、正负对照 p 上逐项对齐到 1e-8 以内。 说明白一点:两条实现共用同一套抽样与数值规范(同一 PRNG、同一 Gram–Schmidt 秩容差), 所以它抓的是公式与索引错误,不是数值库之间的差异。

figs/ 目录。