简介:《浙江省统计年鉴2004》是一部系统记录浙江省2004年度经济社会发展状况的统计资料,面向经济研究者、政策制定者、高校师生及企业分析人员,可用于区域经济研究、数据查询与趋势分析。资源以rar压缩包形式提供,内含“浙江统计年鉴2004光盘版”电子数据,包体大小约4.13MB,便于本地检索与长期保存。年鉴内容覆盖综合、农业、工业、建筑业与房地产业、投资与交通、贸易、财政与金融、消费与物价、人民生活以及教育科技文化卫生等十大篇章,收录地区生产总值、人口就业、居民收入、主要工农业产品产量、固定资产投资、进出口总额、财政信贷、价格指数等关键指标,能够帮助读者全面把握浙江省2004年的经济结构、产业运行与社会发展水平。目前已有187人学习下载,适合需要权威年度统计数据支撑论文写作、课题研究或经营决策的读者使用。
1. 浙江省统计年鉴2004:一本二十年前的数据集,为什么今天还有人翻
如果你手里正好有一份《浙江省统计年鉴2004》的 PDF 或者光盘镜像,想把它变成能跑 SQL、能画图、能做回归的干净表格,那你来对地方了。这不是一本“过时的书”,而是一份被严重低估的结构化数据源:它覆盖 2003 年浙江省及下辖 11 个地市、数十个县市的社会经济指标,从 GDP、三次产业增加值、规模以上工业主要指标,到固定资产投资、消费品零售总额、进出口、财政收支、城乡居民收入、价格指数,颗粒度细到分行业、分市县。做区域经济长时序研究、做县域面板数据、做历史数据回填的人,绕不开这类年鉴。
麻烦在于,它天生不是给机器读的。影印版 PDF 里全是跨页大表、合并单元格、中文表头、脚注符号,还有“续表”“计量单位:万元”这种半结构化信息。直接复制粘贴到 Excel,列会错位,数字会串行,负号会丢。我见过太多人在这第一步翻车,然后放弃。这篇笔记就干一件事:把《浙江省统计年鉴2004》从 PDF 到可分析数据集的完整路径拆开,包括工具选型、表格识别参数、清洗规则和校验方法。适合做区域经济、产业研究、数据新闻的从业者,也适合想练手真实脏数据的新手。
2. 先搞清楚这本年鉴的数据结构,再决定怎么抽
2.1 年鉴的表格组织逻辑:篇、章、表三级
《浙江省统计年鉴2004》的正文按“篇—章—表”组织。常见篇目包括综合、人口与劳动力、固定资产投资、对外经济贸易、能源、财政金融、物价、人民生活、农业、工业、建筑业、交通运输邮电、批发零售贸易餐饮业、科技教育文化卫生等。每一篇下面若干章,每章下面若干张表。表号通常是“1-1”“1-2”这种格式,续表会标“续表1-1”。
理解这个层级很重要,因为抽取时你要保留三个定位字段:篇名、章名、表号。没有这三个字段,后面把几十张表合并成一张宽表时,你根本不知道某列“工业增加值”到底属于哪个统计口径。我的做法是在 PDF 目录页先人工建一张映射表,把表号和表名对应起来,后续所有抽取结果都带上这个映射。
2.2 三类表格的抽取难度分级
不是所有表都一样难抽。按我的经验分三级:
| 难度 | 特征 | 典型表 | 处理策略 |
|---|---|---|---|
| 低 | 规则网格,无合并单元格,表头单行 | 主要年份 GDP 总量 | 直接表格识别 |
| 中 | 表头多行,有合并单元格,脚注少 | 分行业工业总产值 | 识别后手工校正表头 |
| 高 | 跨页续表,列宽极窄,数字带括号负号 | 市县财政收入分项 | 分页抽取后按表号拼接 |
先分级再动手,能省掉大量返工。低难度表可以批量跑,高难度表必须逐张盯。
2.3 工具选型:为什么我最终用 Camelot 加人工校验
试过几种方案。纯 PDF 文本层提取(pdfplumber)对有线表格还行,对无线表格基本废掉。OCR 方案(Tesseract)对中文数字混排识别率不稳定,尤其是“0”和“8”、“1”和“7”。最后稳定下来的组合是:Camelot 的 lattice 模式处理有线表,stream 模式处理无线表,输出 CSV 后用 pandas 做规则清洗,再人工抽查。
import camelot import pandas as pd # lattice 模式:适合有完整边框线的表格 tables = camelot.read_pdf( "zj_yearbook_2004.pdf", pages="45-60", # 先小范围试,确认页码 flavor="lattice", # 有线表用 lattice line_scale=40, # 线宽阈值,默认15,影印版调大 shift_text=["l", "t"] # 文本左对齐、上对齐,减少错位 ) # 逐表导出,保留表号 for i, table in enumerate(tables): df = table.df df.to_csv(f"raw/table_{i:03d}.csv", index=False, header=False) print(f"表{i} 形状: {df.shape}, 精度: {table.parsing_report['accuracy']}")这段代码的关键参数是line_scale。影印版 PDF 的表格线往往断裂或过粗,默认值 15 会漏检,调到 40 左右能显著提升有线表的识别率。shift_text控制文本对齐方向,中文年鉴表头通常左上对齐,设成["l", "t"]比默认值稳。parsing_report里的 accuracy 低于 80 的表,直接标记为需人工处理,不要硬跑。
3. 从 PDF 到干净 CSV:抽取、清洗、校验三步走
3.1 表头重建:多行表头怎么压成单行
年鉴表头经常是两行甚至三行。比如第一行“工业总产值”,第二行“轻工业”“重工业”,第三行“合计”“国有”“集体”。直接识别出来是三个独立行,你需要把它们拼成“工业总产值_轻工业_合计”这种单行表头。
def flatten_header(header_rows): """把多行表头压成单行,用下划线连接""" max_cols = max(len(r) for r in header_rows) flat = [] for col in range(max_cols): parts = [] for row in header_rows: if col < len(row): val = str(row[col]).strip() # 跳过空值和纯数字(数字是数据不是表头) if val and not val.replace(".", "").isdigit(): parts.append(val) # 去重相邻重复,比如"合计_合计" deduped = [] for p in parts: if not deduped or deduped[-1] != p: deduped.append(p) flat.append("_".join(deduped) if deduped else f"col_{col}") return flat # 假设前3行是表头 header = flatten_header([df.iloc[0], df.iloc[1], df.iloc[2]]) df.columns = header df = df.iloc[3:].reset_index(drop=True)逻辑说明:逐列扫描每一行表头,把非空、非数字的文本按顺序拼起来。去重是为了处理合并单元格被识别成重复值的情况。参数上,header_rows的行数需要你打开 PDF 目测确认,一般 2 到 4 行。压完之后一定要打印列名人工过一遍,这是整个流程里最不能省的一步。
3.2 数值清洗:负号、括号、千分位、脚注符号
年鉴里的数字有几种脏法:负数用括号表示,如“(123)”;千分位逗号;脚注符号“①”“②”粘在数字后面;还有“—”表示缺失。这些不处理,后面astype(float)必报错。
import re def clean_number(val): """把年鉴里的脏数字转成 float""" if pd.isna(val): return None s = str(val).strip() # 空、横线、省略号都视为缺失 if s in ["", "—", "-", "…", "...", "-"]: return None # 去掉脚注符号(圆圈数字、星号、上标) s = re.sub(r"[①②③④⑤⑥⑦⑧⑨⑩\*\u2010-\u2015]", "", s) # 括号负数 neg = False if s.startswith("(") and s.endswith(")"): neg = True s = s[1:-1] if s.startswith("(") and s.endswith(")"): neg = True s = s[1:-1] # 去千分位逗号和空格 s = s.replace(",", "").replace(" ", "") try: num = float(s) return -num if neg else num except ValueError: return None # 转不了的标记为缺失,后续人工查 # 对数据列批量应用 for col in df.columns[1:]: # 第一列通常是地区名,跳过 df[col] = df[col].apply(clean_number)这段的核心是“宁可标缺失,不要瞎猜”。转不了的值返回 None,导出后单独列一张问题清单,人工回查 PDF 原页。参数上,脚注符号的正则范围要按你手上版本实际出现的符号调整,我这份里圆圈数字和星号最常见。
3.3 地区名标准化:市县名称对齐与代码映射
年鉴里的地区名写法不统一,有的带“市”,有的不带,有的用旧称。做面板数据必须统一。我的做法是建一张映射表,把年鉴里的写法映射到标准行政区划代码。
# 地区名映射示例(部分) region_map = { "杭州市": "330100", "杭州": "330100", "宁波市": "330200", "宁波": "330200", "温州市": "330300", "温州": "330300", # ... 其余地市 "萧山区": "330109", "萧山": "330109", # ... 其余县区 } def standardize_region(name): s = str(name).strip().replace(" ", "") return region_map.get(s, None) df["region_code"] = df["地区"].apply(standardize_region) # 映射失败的单独输出 unmapped = df[df["region_code"].isna()]["地区"].unique() print("未映射地区:", unmapped)映射表需要你根据当年的行政区划手工整理,注意 2003 年之后有些县市改区了,年鉴用的是当年名称。这一步没有捷径,但一次建好可以复用到其他年份的年鉴。
4. 避坑与排查:那些让我返工三次的细节
4.1 跨页续表拼接错位
现象:表 3-5 在第一页末尾,续表在第二页开头,两段拼起来后行数对不上,中间多了一行表头。原因:续表页顶部重复了表头,识别时被当成数据行。解决:按表号分组后,检测每段第一行是否与表头高度相似,相似则丢弃。我一般写个简单规则:如果某行所有单元格都是非数字文本,且与已知表头重合度超过 60%,判为重复表头。
4.2 数字“0”和“—”混淆
现象:清洗后某些指标出现大量 0,但原表里明明是“—”。原因:OCR 或表格识别把短横线识别成了 0,或者反过来。解决:在清洗函数里,先把“—”映射为 None,再处理数字。同时抽查:如果某列 0 的占比超过 40%,大概率是识别问题,回原表核对。这个坑我在财政收入表上踩过,整整一列“—”(表示该县无该项收入)被识别成 0,导致均值严重偏低。
4.3 计量单位不统一
现象:合并多张表后,同一指标列里有的行是“万元”,有的是“亿元”。原因:年鉴不同表用的单位不同,表头或脚注里写了,但抽取时没保留。解决:在表级元数据里强制记录单位字段,合并前先统一换算。我的做法是建一张table_meta表,字段包括表号、单位、倍数,清洗时按表号 join 进去做换算。
4.4 脚注符号粘在数字尾部导致类型错误
现象:float("1234①")报 ValueError。原因:脚注符号和数字之间没有空格,识别后连在一起。解决:清洗正则里提前剥离所有非数字非小数点非负号的字符。注意不要误伤科学计数法的“e”,虽然年鉴里基本不会出现。
4.5 表头列名重复导致 pandas 自动加后缀
现象:df.columns里出现“工业总产值.1”“工业总产值.2”。原因:多行表头压平后,不同列拼出了相同名称。解决:压平函数里加列名去重逻辑,重复的追加序号,但更重要的是回看原表——列名重复往往意味着你的表头行数判断错了,漏掉了区分维度。
5. 进阶:把年鉴数据接进时序库和可视化流水线
数据清洗完只是半成品。真正产生价值,要把它接进可查询、可更新的流水线。我现在的做法是:清洗后的 CSV 先入 SQLite(轻量、单文件、方便分发),再用 SQL 做跨表关联和长时序拼接。如果你要做 2000 到 2020 年的长面板,把各年年鉴都按同一套 region_code 和 indicator_code 标准化,就能直接 union。
import sqlite3 conn = sqlite3.connect("zj_yearbook.db") # 宽表转长表:地区+指标+年份+值 df_long = df.melt( id_vars=["region_code", "地区"], var_name="indicator", value_name="value" ) df_long["year"] = 2003 # 2004年鉴数据年份为2003 df_long.to_sql("indicators", conn, if_exists="append", index=False) # 查询示例:某市某指标时序 query = """ SELECT year, value FROM indicators WHERE region_code = '330100' AND indicator LIKE '%GDP%' ORDER BY year """ print(pd.read_sql(query, conn))长表结构的好处是加新年份只需 append,不用改表结构。indicator字段建议再拆成indicator_code和indicator_name,方便和外部指标库对齐。可视化环节,我一般用 matplotlib 或 plotly 直接读 SQLite,按 region_code 分组画小多图,快速看异常值。
一个具体技巧:校验数据质量时,别只看总量。拿“分县 GDP 之和”和“全省 GDP”对一下,差距在 2% 以内算正常(统计口径差异),超过 5% 说明某张表抽错了。这个交叉校验帮我抓到过两次续表漏抽。做历史数据这行,后悔药是没有的,唯一能信的就是交叉验证。希望帮到你。
本文还有配套的精品资源,点击获取