统计年鉴Excel版数据清洗与批量合并实战:从混乱到整洁
简介2021年统计年鉴Excel版是一套以Excel表格为主的年度统计数据包覆盖人口、GDP、工业、农业、财政、价格等常见统计主题适用于经济研究者、高校师生和数据分析人员可用于学术研究、行业报告与年度趋势对比。压缩包共2000个文件整体约29.77MB主要文件类型包括xls数据表、txt统计说明、htm索引导航、jpg/png趋势图片等不同格式相互配合便于数据检索、指标解读与二次编辑。目前已有184人学习/下载。内容聚焦2021年国民经济与社会发展各项核心数据按地区、行业和专题分类组织既可直接用Excel做筛选、透视和汇总也能通过htm目录快速定位所需表格txt说明有助于核对统计口径图片则直观呈现变化趋势。该数据集结构清晰且体积紧凑适合课题研究、毕业论文写作及市场分析时作为基础统计素材免去逐页翻阅和转录纸质年鉴的繁冗。1. 统计年鉴Excel版拿到先别急着合并先搞懂它到底装了什么做数据清洗和行业分析的人大概率都经历过这样的场景拿到一份《2021年统计年鉴Excel版》文件夹里躺着几十个文件每个文件里面又是几十个Sheet满心以为打开就能用结果光是搞清楚哪个Sheet对应哪个指标就花掉一下午。这份资源真正解决的问题不是“有没有数据”而是“怎么把散落在不同Sheet和不同文件里的年鉴数据变成一张能直接进透视表、能跑趋势分析的干净表格”。它适合做课题报告、行业研究、课程设计或者任何需要引用年度统计数据的场景。如果你只是想要某个单一指标直接翻PDF更快但如果你要做多指标对比、跨区域汇总或者近五年走势Excel版配合脚本处理效率会高一个量级。2. 年鉴数据骨架表头、Sheet分布和编码决定你后面的清洗效率2.1 文件清单与Sheet分布先画一张数据地图建议拿到资源的第一件事不是急着用代码批量读取而是先把整个目录结构看一遍。常见做法是先用资源管理器或者命令行把文件全部列出来形成一张“数据地图”。这一步花五分钟能帮你省掉后面好几个小时的试错。典型的年鉴Excel版文件组织方式是这样的根目录下按章节拆分成多个工作簿比如“人口与就业.xlsx”“工业与能源.xlsx”“固定资产投资.xlsx”每个工作簿内部再按指标拆成多个Sheet。有些版本还会有一个“总目录.xlsx”或者“主要指标汇总.xlsx”这个文件通常包含了全书的主要指标和对应的Sheet名称是你建立索引的第一个抓手。看地图的时候重点确认三件事第一工作簿的命名规律是否一致是统一用章节号开头还是纯中文名称第二Sheet的命名是否有规律有些版本用指标名命名有些用表号命名第三有没有“综合”“附录”“说明”这类特殊Sheet里面往往藏着指标口径解释和单位说明后面清洗时你一定会回来翻它。我一般会顺手把目录输出到一个文本文件里方便后续检索。2.2 表头结构的两种常见形态单行表头与多级表头打开任意一个Sheet先别急着看数据把前五行从头到尾扫一遍确认表头占了几行。年鉴Excel版的表头形态大致分两种一种是比较规整的单行表头第一行就是指标名第二行直接是数据这种最省事另一种是多级表头比如“绝对数”和“比上年增长”两个一级分类下各自带着多个二级指标或者年份跨两列、指标名跨两行。多级表头处理起来要麻烦得多因为如果直接拿第一行当列名后面的列名就会变成Unnamed之类的占位符合并多个Sheet时会对不上。处理多级表头的常见做法是把前两行甚至前三行拼接成新的列名比如把“绝对数-第一产业”这种组合作为最终列名。注意拼接时要处理空单元格可以用向上填充的方式把空的父级单元格补上否则拼出来是“纯文本-NaN”。另外有些Sheet会在表头下方加一行单位说明比如“单位亿元”这一行读数据的时候要跳过但它又很重要因为单位信息往往只出现在这一行里后面做单位归一会用到。2.3 编码与软件兼容性GBK、UTF-8 和打开报错的真相年鉴Excel版的编码问题主要出现在读取环节而不是打开环节。因为Excel文件在磁盘上是二进制结构不存在文本编码的困扰但Excel文件内部可能保存着不同的代码页信息导致用不同工具读取时出现乱码或字符集错乱。如果你是直接用Excel或WPS打开一般不会遇到乱码顶多是某些老版本生成的.xls文件在较新版本的Excel里打开时提示“格式与扩展名不匹配”这时候不要直接点“是”建议先做一个副本再尝试用导入向导读取。如果你用Python的pandas读取遇到的典型问题是默认引擎读xlsx没问题但部分文件实际上是老式的xls格式虽然扩展名改成了xlsx直接读取会报错。常见做法是指定engine参数xls用xlrdxlsx用openpyxl。还有一个低频但麻烦的问题某些文件里混入了从网页复制过来的特殊空格或换行符导致字符串匹配失败这类问题在清洗表头时特别容易碰到处理方式是先把列名做一次strip和编码归一化。2.4 为什么选Excel版而不是PDF版机器可读性的差距统计年鉴的PDF版通常是大几百页的扫描影印件或者排版导出文件即便文字层可以复制表格的结构也会在复制时丢失比如多列错位、换行符嵌入单元格、小数点变成句号等。Excel版的优势在于保留了表格的行列结构数据是真正的单元格值而不是图上像素这意味着你可以直接用脚本批量处理。另一个优势是单位信息通常保留在单元格里而不是像PDF那样被拆分到页眉或注释里。但Excel版也有自己的问题——它同样可能“长得不规整”。不同年份的年鉴甚至同一年鉴的不同章节表头格式都可能不统一。所以严格来说Excel版解决的是“能不能程序化处理”的问题至于处理过程是否顺利取决于你对数据结构的理解深度。这也是为什么本章前面的地图绘制和表头检查那么重要。3. 批量合并21个Sheetopenpyxl 遍历、表头统一与单位归一化3.1 准备工作建立项目目录与读取测试搞清楚了结构接下来进入正题——把散落在多个工作簿里的Sheet合并成一张能直接用的总表。我习惯把整个处理过程分成三个阶段探测、清洗、输出。先建一个项目目录把原始文件放在raw_data子目录下处理脚本放在根目录避免污染原始数据。脚本第一步是遍历目录下所有xlsx文件并对每个文件做读取测试确认文件没有损坏、编码可以正常解析。下面是我常用的一段探测代码用来输出每个工作簿的Sheet名称和每个Sheet的行列数方便你和数据地图做对照。import openpyxl from pathlib import Path raw_dir Path(raw_data) for xlsx_file in raw_dir.glob(*.xlsx): wb openpyxl.load_workbook(xlsx_file, read_onlyTrue, data_onlyTrue) print(f文件: {xlsx_file.name}) for ws_name in wb.sheetnames: ws wb[ws_name] print(f Sheet: {ws_name}, 行数: {ws.max_row}, 列数: {ws.max_column}) wb.close()这段代码做的事情很简单遍历raw_data目录下所有xlsx文件用openpyxl读取工作簿然后输出每个Sheet的名字、行数和列数。注意load_workbook时我加了两个参数read_onlyTrue表示只读模式占用内存小对大文件友好data_onlyTrue表示读取单元格的缓存值而不是公式这一步很关键因为年鉴里有些单元格是公式计算的如果直接读公式字符串后面的数值清洗就全乱了。参数解释一下read_only模式在遍历Sheet时必须用for循环逐行读取如果你想直接访问某个具体单元格性能会差一些但探测场景下完全够用。data_only模式的前提是文件之前被Excel或WPS计算过并保存了缓存值如果文件从来没人打开过缓存可能为空那时候你会拿到None值遇到这种情况要么先手动打开一遍另存要么改用full_load模式。3.2 遍历工作簿并定位每个Sheet的表头行有了Sheet清单下一步要对每个Sheet做表头定位。年鉴这种数据源表头行数不固定是常态所以不能用硬编码的“第一行就是表头”而是要写一个探测函数逐行读取前N行统计每行非空单元格的数量当非空数量从少变多、并且出现“地区/指标/年份”这类关键词时就判定为表头行起点。我对年鉴数据积累的经验是表头行通常出现在前五行的范围内且表头行的非空单元格数量明显多于前面的说明行。import openpyxl KEYWORDS [地区, 指标, 单位, 年份, 合计, 总计] def detect_header_row(ws, max_scan10): for row_idx in range(1, min(max_scan, ws.max_row) 1): row_values [] for cell in ws[row_idx]: if cell.value is not None: row_values.append(str(cell.value).strip()) non_empty len(row_values) keyword_hits sum(1 for v in row_values if any(k in v for k in KEYWORDS)) # 判定条件非空数量大于3且命中关键词或者非空数量足够多 if non_empty 3 and (keyword_hits 2 or non_empty 5): return row_idx return 1这个函数的判定逻辑是“非空数量加上关键词命中”双重条件。为什么非空数量阈值定在3因为年鉴Sheet里常有“注”“续表”这类说明行非空数量一般是1到2可以过滤掉。关键词命中的设计是为了处理表头行数不固定的情况有的表头是“地区-指标-单位”三列结构有的表头是“指标-绝对数-比上年增长”多级结构通过关键词命中来兜底。如果扫描完10行还没找到就回退到第1行避免函数报错返回None影响后续流程。3.3 统一表头、追加年度列与地区列定位到表头行之后清洗逻辑的核心是“三统一”统一列名、统一类型、统一维度。第一步是提取表头行把多级表头拼接成一级列名——我处理过最简单的情况是表头只有一行直接取第一行作为列名稍复杂的情况是表头有两行前一行是分类名后一行是指标名这种用“分类-指标”拼接。第二步是给每个Sheet补充“年份”和“地区”两个维度列年份可以根据文件命名规则推断地区可以看列名里是否包含“全省”“全国”“城镇”这类词也可以从文件来源判断。import openpyxl import pandas as pd YEAR_MAP {2021: 2021, 2020: 2020} # 实际使用时按文件命名规则构造 def sheet_to_df(ws, header_rows2, year2021): rows list(ws.iter_rows(min_rowheader_rows 1, values_onlyTrue)) # 拼接多级表头 headers [] header_cells list(ws.iter_rows(min_row1, max_rowheader_rows, values_onlyTrue)) for col_idx in range(len(header_cells[0])): parts [] for row_idx in range(header_rows): val header_cells[row_idx][col_idx] if val is not None and str(val).strip(): parts.append(str(val).strip()) headers.append(-.join(parts) if parts else f列{col_idx}) df pd.DataFrame(rows, columnsheaders) df.insert(0, 年份, year) return df这里header_rows参数可以直接传你探测到的行数也可以传一个固定值然后根据实际报错去调整。拼接表头时用了两层循环外层是列索引内层是表头行索引把每一列的多个层级用“-”连接起来这样处理避免了多个列头重名的问题。但要注意如果某个列的所有表头层级都是空的代码会补一个“列索引”的名字这部分后面要单独检查。插列时用了insert(0)把年份放在第一列这样合并后所有Sheet的列顺序一致。3.4 数值清洗与单位换算的自动处理年鉴里最麻烦的是数字格式不干净千分位逗号、中文“%”、括号备注比如“上年100”、甚至“一”“—”“…”这些表示数据缺失或统计误差的符号都会混在数值列里。直接pd.to_numeric一定会报错所以要写一个清洗函数把这些噪声处理掉。另一个要做的是单位归一化常见的单位有“亿元”“万元”“%”“万人”等不同Sheet的单位可能不同但指标本身是同类的如果不统一后面做跨Sheet汇总时会得到完全错误的结果。import pandas as pd import re UNIT_MAP {亿元: 1e8, 万元: 1e4, 元: 1.0} UNIT_PATTERN re.compile(r(亿元|万元|元|%)) def clean_numeric(value, unit元): if value is None: return pd.NA s str(value).replace(,, ).strip() s re.sub(r[\(].*?[\)], , s) if s in {, —, -, …, ...}: return pd.NA try: num float(s) except ValueError: num pd.NA if num is pd.NA: return num multiplier UNIT_MAP.get(unit, 1.0) return num * multiplier def apply_unit_from_header(df): for col in df.columns: if any(u in col for u in UNIT_MAP.keys()): unit next(u for u in UNIT_MAP.keys() if u in col) df[col] df[col].apply(lambda x: clean_numeric(x, unit)) elif 率 in col or % in col: df[col] df[col].apply(lambda x: clean_numeric(x, %)) return df这段代码有两层逻辑。第一层clean_numeric做单元格级清洗先去掉千分位逗号再用正则删掉括号里的注释比如“上年100”然后把“—”“…”统一替换成pd.NA。第二层apply_unit_from_header做列级清洗从列名里匹配单位关键词把单位数值乘以对应的倍数比如“万元”列自动乘以1万。这里要注意单位映射是硬编码的如果你的年鉴里出现“万美元”“千瓦时”这种带复合单位的列名需要扩展UNIT_MAP字典否则会按“元”处理导致数值错误。3.5 输出合并总表与按指标拆分文件清洗完成后最后一步是把所有Sheet的DataFrame纵向拼接成一张大表同时按指标拆分成多个小文件。大表的用途是做全量检索和透视分析小文件的用途是给不同业务方提供可以直接引用的单项数据。拼接时用pandas的concat因为每个Sheet的列名经过统一后基本一致不一致的列会变成NaN需要统计一下哪些列是稀疏的。import pandas as pd from pathlib import Path def generate_splits(df, key_cols[年份, 地区], out_diroutput): out_path Path(out_dir) out_path.mkdir(exist_okTrue) df.to_csv(out_path / merged_all.csv, indexFalse, encodingutf-8-sig) indicator_cols [c for c in df.columns if c not in key_cols] for col in indicator_cols: subset df[key_cols [col]].dropna(subset[col]) file_name col.replace(/, _).replace(\\, _) .csv subset.to_csv(out_path / file_name, indexFalse, encodingutf-8-sig)这里有两个容易出错的细节。第一合并导出的编码统一用utf-8-sig因为Excel打开无BOM的UTF-8文件会乱码加BOM之后双击打开就正常。第二拆分文件时列名里如果带“/”在Windows下会直接报错所以做了replace替换。dropna这一步很重要因为很多指标在部分地区或部分年份就是没有数据不删除的话拆出来的文件里全是空行。输出的merged_all.csv就是你后续分析的主数据表。4. 实战避坑年鉴Excel最常见的五个坑从乱码到零值都在这4.1 打开文件提示“格式损坏”或自动修复现象双击xlsx文件Excel提示文件格式与扩展名不匹配或者自动进入修复模式修复后部分Sheet的数据变成科学计数法小数点丢失。原因部分版本的年鉴Excel文件是把多个xls老文件直接改了扩展名或者用了非标准编码生成文件内部结构并非标准的OpenXML格式却被放到xlsx后缀下。解决先复制原始文件到工作目录用WPS或老版本Excel打开确认内容完整再用LibreOffice另存为标准xlsx如果只依赖pandas读取用engine指定xlrd打开xls格式再另存。从那以后我拿到这类资源都会先做一次“复制另存”的消毒动作不直接对原始文件操作。4.2 第一行不是表头是制表说明现象用pandas读取某个Sheet第一行数据是“本表数据来源于……”“单位亿元”之类的说明文字列名变成“列1、列2”合并后数据错位。原因年鉴的编制人员把表头固定在第二行或第三行第一行留作说明行这在分页很多的章节尤其常见。解决在读取流程里强制加入表头行探测也就是前面detect_header_row函数做的事即使你确定大多数Sheet都是第一行表头探测一下成本很低但能避免偶尔几个Sheet带来的全面错位。4.3 数字列读出来全是NaN或科学计数法现象有些列明明在Excel里显示的是“12,345”read_excel读出来却是NaN或者读出来变成1.2345E04精度丢失。原因数字单元格被存储为文本格式Excel用文本型数据保存了带千分位的字符串pandas默认不会自动转换科学计数法的情况通常是数字超过了单元格显示宽度底层存的是浮点数。解决读取后用pd.to_numeric配合errorscoerce强制转换转换前先去掉千分位逗号和空格。经验是不要直接to_numeric因为带“%”的列会全部变NaN要分开处理。4.4 年份列变浮点数单位列却夹在数据里现象合并后年份列显示为2021.0而不是2021或者“单位”列出现在数据列中间而不是表头的后缀里。原因pandas在读入混合类型列时自动推断为float64特别是存在NaN值时整数列会被提升为浮点列单位列混入数据通常是表头行定位不准导致把单位行当成了数据行。解决读取完成后对年份这类整型维度列做astype(Int64)用pandas的nullable整数类型来保留NaN同时保持整数形式单位问题回到表头处理流程里检查确认单位信息在表头行内而不是单独一行数据。这一坑最容易出现在批量处理多个文件时前面文件都正常突然一个文件多了个“单位行”整个merged表就废了。4.5 同一指标不同Sheet的统计口径不一致现象比如“居民消费价格指数”在“人民生活”章节里是上年100的定基指数在“价格指数”章节里是月度同比数据数值差了几十甚至上百。原因年鉴不同章节由不同业务部门提供统计口径和基期定义不一致但Sheet名称却可能相近。解决合并之前专门检查每一列列名里是否带年份基期说明比如“(上年100)”字样如果带拆出来单独存储不要直接和其他Sheet合并。跨口径对比是年鉴分析里最容易出结论性错误的地方宁可拆开也不要强行拼接。我一般会在合并后的表里加一列“指标口径”字段记录原始Sheet名后续分析时按口径字段筛选。5. 验证与进阶交叉核对、透视表与多年拼接的收尾习惯5.1 抽样交叉验证用汇总页核对明细页数据清洗全部做完了你可能以为大功告成但还有一个关键步骤——验证。常见做法是抽取三个指标做交叉核对年鉴末尾通常会有一个“主要统计指标”汇总表和前面分章节的明细Sheet是同一指标的两份数据如果清洗后这两份对不上说明前面的清洗流程有系统性错误。具体操作是从merged_all.csv里筛出目标指标再打开汇总版本的Sheet单独读取一次用sum或mean对比。数值不一致时回头看单位是否归一化了或者是否有多级表头拼接错位。验证通过之后这个数据表才值得被用来做分析否则后面全是在错误数据上盖楼。5.2 数据透视表看突变快速定位异常验证的第二个技巧是直接对merged_all.csv做数据透视表行为地区、列为年份、值为某个主要指标。透视表的价值在于快速暴露“突变”比如某地区2021年人口总量比2020年下降超过5%这在正常年份是不太可能的问题大概率出在单位换算或者数据缺失被填零。我习惯用Pandas的pivot_table加上diff函数计算相邻年份的变化率把变化率超过阈值人口类超过2%、经济类超过30%的行筛选出来逐一排查。这一步能过滤掉清洗过程中遗漏的脏数据。5.3 跨年度拼接处理指标改名与口径跃迁如果你手上不只有2021年一本年鉴而是连续好几年的Excel版最终的进阶目标是拼成一张多年度面板数据。跨年拼接的坑主要在两个地方一是同一指标在不同年份的表头写法不同比如“地区生产总值”在某一年简写成“GDP”需要建立指标别名映射二是统计口径发生过调整比如某年执行了新的行业划分标准前后两年数据不可直接对比这时要保留原始的年份列和口径说明字段不要盲目做比率计算。处理方法是先按指标别名做列名归一化再检查每个指标的年份范围若发现年份断档或数值阶跃单独标记出来。5.4 收尾习惯生成数据字典与缺失清单最后一个技巧是输出一份数据字典文件内容包括合并后的列名、原始Sheet名、单位、口径说明、缺失值统计。这份数据字典的价值在于三个月后你回头用这份数据写报告时不需要重新打开原始Excel去回忆某个列名到底代表什么如果发给同事或组员使用更是省掉了大量口头沟通。缺失清单同样重要年鉴Excel里标识为“—”或“…”的单元格被清洗成了NaN这部分数据占比比较高的表在做趋势分析时会影响结果提前暴露出来总比分析到一半才发现好。我做这类数据集的习惯是每次合并完强制自己先跑一遍抽样交叉验证再生成数据字典然后才允许自己往下做分析。坚持这个流程之后返工率明显降下来了。希望帮到你。本文还有配套的精品资源点击获取