1. 为什么我要整理这份数据:从“下个表”到“造个表”
1.1 需求远比“找一个xlsx”复杂
做数据分析这几年,我整理过不少乱七八糟的Excel表格,但印象最深的还是2000年到2024年企业员工数、裁员数这份数据。起因是要写一份跨年度的企业用工趋势分析,需要把员工总数、裁员规模、裁员率这几个数字从2000年一路拉到2024年。那时候我第一时间打开搜索引擎,看到一堆“xxx数据xlsx”的下载链接,以为下载下来就能用。事实是,真正可用的现成表格几乎没有,能找到的都是散落在年报、年鉴、数据库里的原始片段。类似鸢尾花数据集那种一打包就整整齐齐的xlsx,在企业员工数据领域根本不存在。
这也是第一版方案失败的根因:我在网上找来三四个年份不齐、字段混乱的Excel,用一个小时拼在一起,结果做出来的趋势图前一年还是平稳的,后一年突然断崖式下跌。后来排查发现,不是数据出了问题,而是2001年用的“员工人数”口径是年末在册数,2002年用的却是“全年平均人数”,两个数值差了一大截。从那一刻起,我就意识到这份活儿不是“下个表”,而是“造个表”。
“造表”意味着要做几个关键决策:数据覆盖范围多大?年份是否每年都有?行业怎么分类?员工数和裁员数的定义到底是什么?数据来源能不能追溯?整张表要支持哪些分析场景?先把这些问题回答完,后面才能动手。
当时也有人劝我,数据量又不大,直接用CSV存数据更简单。但CSV有三个硬伤:没有多sheet能力,数据字典和汇总表没地方放;字段增减时所有协作者都要手工同步;发给别人后还得让人重新设置列宽、筛选、格式。对于一份要经常更新和分发的数据来说,xlsx是更稳妥的载体。
1.2 数据来源与口径的取舍:宁可少一个数,不能错一个口径
既然现成xlsx不存在,我就把目标定成“自建一张可持续更新的标准表”。数据来源上,我结合了四类渠道。
一是上市公司年报和招股说明书。这类数据披露相对规范,每年有固定审计流程,员工数在“员工情况”章节能直接查到,部分公司还会披露劳务派遣人数。缺点是只覆盖上市公司,行业分布偏制造业和互联网,中小型企业很少。
二是统计年鉴和宏观经济数据库。这类数据胜在时间长、口径统一,能拿到从2000年至今连续二十多年的就业总量。但年鉴里的“城镇单位就业人员”和“企业员工数”并不是一个概念,很多年份还包含机关单位,需要自己拆分。
三是行业协会和公开招聘信息。行业协会会发布细分行业的用工数据,招聘平台通过投递量、offer量、入离职数也能看出趋势,但覆盖面有限,只能作为补充。
四是研究机构整理的面板数据。有些机构会发布已经清洗过的跨年度数据,格式和字段都比较接近需求,适合拿来做基准和交叉验证。但要确认数据来源是否允许二次处理,避免发布时踩授权坑。
数据来源确定后,口径成了最头疼的问题。我踩过的坑主要有三个。
第一个是员工总数的口径,用“期末员工人数”还是“全年平均人数”。很多年报写的是“在职员工数量”,但企业会在年末突击调整,不同公司可比性不高。我规定,能拿到期末数就用期末数,拿不到就用年报里的“全年平均人数”,并且单独加一列记录口径类型。
第二个是裁员数的定义。“裁员”两个字太模糊了。接到辞退通知、合同到期不续签、业务线整体裁撤、协商解除劳动补偿,在实操里都会被称为裁员,但统计渠道完全不一样。有些企业年报里写“人员优化”“组织架构调整”“转岗”,根本不会直接出现“裁员”二字。用文本匹配很难抓全,必须结合新闻稿、年报上下文一起判断。我当时的方法是先设定关键词库,再人工抽查,宁可漏掉部分,也要保证已统计的数值来源可靠。最终我选择“公司决策导致的主动解除劳动关系人数”作为主口径,同时保留原始字段值。
第三个是行业分类。2000年用的是旧国民经济行业分类,2011年和2017年又各调整过一次,直接拼接会导致行业名称对不上。我采用“行业代码+历史行业名称+现行行业名称”的三段式结构,让每条数据都能在现行标准与历史标准之间切换。
这些口径决策如果不提前做,后面所有年份的合并都是白干活。这也是为什么遇到跨年度数据,我会建议先花一天时间写数据字典,再开始采集。
2. 表格结构设计:xlsx不是“一格一个数”
2.1 多表设计,一次规划不返工
很多人在整理数据时喜欢“一张sheet塞满所有东西”,结果就是列数几十个、行数上万、不同年份的字段还混在最右边。这种表用起来非常痛苦,你根本不知道哪些列是真实的、哪些列只是某个年份临时加的。
我这次采用经典的多sheet结构,一共四张主表:
- 明细表:一行一条记录,记录“年份+行业+企业规模分层+员工数+裁员数+裁员率+数据来源+口径说明”
- 汇总表:按年份和行业口径汇总,方便快速做趋势图
- 数据字典:说明每一个字段名称、取值、单位、缺失值标注方式
- 版本记录:记录每次更新的时间、范围和改动内容
明细表是主体,所有原始数据都在这里。汇总表不要手工填,我用脚本自动从明细表计算生成,避免两边数字对不上。数据字典是很多人不写的,但跨几十年、多人协作时它比数据本身还重要,不然过两个月你自己都得猜“emp_cnt”到底有没有除以1000。
除了四张主表,我还在明细表前面加了一张“README”表,把核心口径用一句话写在顶部。比如“员工数默认期末人数,使用年均人数时会在员工数口径列标注”“裁员数仅统计公司决策导致的主动解除,不含辞职、退休”等。这张表的存在,能解决大部分合作方拿到表格却不知道我做了什么手脚的问题。
2.2 字段设计:让每一条数据都能“自解释”
设计字段时,我重点考虑三件事:这条数据属于谁?数字是怎么来的?能不能跟其他表对齐?
当时定下的核心字段如下:
| 字段名 | 说明 | 示例 | 备注 |
|---|---|---|---|
| year | 年份 | 2020 | 必填,用于时间序列 |
| industry_code | 行业代码 | C27 | 沿用统计用行业分类 |
| industry_name | 行业名称 | 医药制造业 | 使用现行国标名称 |
| company_scale | 企业规模分层 | 大型/中型/小微型 | 部分年份没有分规模 |
| emp_count | 员工数 | 125000 | 单位:人 |
| emp_count_type | 员工数口径 | 期末数/全年平均 | 关键字段 |
| layoff_count | 裁员数 | 3500 | 单位:人 |
| layoff_definition | 裁员定义 | 公司主动解除 | 关键字段 |
| layoff_rate | 裁员率 | 2.8 | 计算列,单位:% |
| data_source | 数据来源 | 某年年报/年鉴 | 可追溯 |
| remark | 备注 | 含子公司合并口径 | 自由文本 |
有两个地方特别容易翻车。一个是员工数和裁员数的逻辑矛盾:裁员率如果超过30%或为负数,一定是数据有问题。另一个是单位混乱,有些来源给的是“千人”,有些给的是“人”,我明确规定统一成“人”,不保留第二种单位。
另外,我还加了一个“年报披露月份”字段。因为企业年报基本在次年上半年发布,不同公司披露时间不一样,直接使用“报告年度”作为year字段时,可能会把不同披露时间的数值放混。加了这个字段后,做分析时可以按披露月份做二次校正。
2.3 数据字典与口径注释,写给未来的自己
数据字典表一般四列就够:字段名、中文解释、单位/取值、备注。下面是我挂在汇总表旁边的真实写法:
| 字段名 | 说明 | 取值 | 备注 |
|---|---|---|---|
| layoff_definition | 裁员定义 | 公司主动解除 | 不含辞职、退休、内部调动 |
| emp_count_type | 员工数口径 | 期末数 | 优先期末数,缺失时用年均 |
| layoff_rate | 裁员率 | 百分比 | layoff_count / emp_count * 100 |
| data_source | 数据来源 | 年报/年鉴/协会 | 主页写明具体出处 |
需要特别强调“缺失值标注”问题:我用空值表示“未知”,用0表示“真实为0”,这两个含义绝对不能混。很多Excel函数会把空值和0都当成0处理,但当你做筛选排序时,空值会被放在一边,0会被正常参与计算,两者的语义完全不同。我会把所有“数据缺失”明确写成文本“NA”,凡是数值列不填数字,避免它和真正的0混在一起。
数据字典做完后,整个表的结构骨架就有了,之后往里填数据,再乱都能回到统一格式。
3. 实操过程:从原始数据到干净xlsx
3.1 清洗链路:不靠肉眼,靠脚本
很多人清洗数据的方法是打开Excel,肉眼盯着改,这种方式在几十行的表里没问题,但遇到跨度二十多年、来源四五种的数据就会改到怀疑人生。我的流程分为四步。
第一步,把纸质年报、PDF表格统一转成结构化csv。无论用OCR、手动录入还是PDF提取工具,最终都要落到“一列一个字段”的规范格式。这里最大的坑是PDF表格经常合并单元格,提取后出现大量空值,需要先做横向填充。
第二步,统一列名和单位。原始数据有“员工总人数”“在职员工”“雇员数量”等十几种叫法,我先做一个列名映射表,把它们统一成字段设计里的规范名。常见映射长得像这样:
| 原始列名 | 规范字段 |
|---|---|
| 员工总人数 | emp_count |
| 在职员工 | emp_count |
| 平均从业人数 | emp_count |
| 裁员人数/裁减员工 | layoff_count |
| 离职人数 | 需人工判断是否为裁员 |
第三步,处理缺失值和异常值。员工数明显小于裁员数、裁员率为负数、年份不在2000到2024年范围内,这些记录单独标记出来,不做自动删除。
第四步,去重和逻辑校验。同一个来源、同一年、同一行业,出现两条不一致数据时,以数据质量更高的一条为准,并在备注里写明冲突原因。
清洗脚本一定要写注释并保存中间文件。不要一个脚本从头处理到尾,每处理一个来源就保存一个“clean_xxx.csv”,这样万一某个来源需要重新更新,不用把之前所有处理都重跑一遍。
3.2 Python生成多表xlsx:几行代码解决重复劳动
数据清洗成规范csv后,下一步就是生成多sheet的xlsx。这一步我强烈推荐用pandas和openpyxl,不要手动复制粘贴,更不要在Excel里逐个设置格式。
下面是生成xlsx的基础框架,包含读取明细、计算汇总、生成表格和设置自动筛选:
import pandas as pd from openpyxl import Workbook from openpyxl.utils.dataframe import dataframe_to_rows df = pd.read_csv("clean_detail.csv") # 计算裁员率 df["layoff_rate"] = (df["layoff_count"] / df["emp_count"] * 100).round(2) # 按年+行业汇总 summary = df.groupby(["year", "industry_name"]).agg( emp_sum=("emp_count", "sum"), layoff_sum=("layoff_count", "sum") ).reset_index() summary["layoff_rate"] = (summary["layoff_sum"] / summary["emp_sum"] * 100).round(2) wb = Workbook() ws_detail = wb.active ws_detail.title = "明细表" for row in dataframe_to_rows(df, index=False, header=True): ws_detail.append(row) ws_detail.freeze_panes = "A3" ws_detail.auto_filter.ref = ws_detail.dimensions ws_summary = wb.create_sheet("汇总表") for row in dataframe_to_rows(summary, index=False, header=True): ws_summary.append(row) ws_summary.freeze_panes = "A3" ws_summary.auto_filter.ref = ws_summary.dimensions # 加一个简单的使用说明sheet ws_readme = wb.create_sheet("README") ws_readme.append(["字段说明详见‘数据字典’sheet"]) ws_readme.append(["员工数默认期末人数,使用年均人数时会在员工数口径列标注"]) ws_readme.append(["裁员数仅统计公司决策导致的主动解除,不含辞职、退休"]) wb.save("企业员工数_裁员数_2000-2024.xlsx")除了自动筛选和冻结窗格,我还会用openpyxl单独给表头设置浅灰色背景、加粗字体、列宽自适应。这些格式可以让文件一打开就有“值得看”的第一印象。
比较稳妥的做法是写一个函数,把所有格式设置集中放在里面,而不是在生成表后手工返工。因为这份表要反复更新,人工步骤越少,越不容易遗漏。
3.3 发布前校验清单:不跑一遍心里不踏实
发布xlsx之前,我会跑一遍脚本,把下面几类问题一次性查出来,避免发出去后被对方发现低级错误。
- 年份覆盖是否完整:2000到2024年之间,某一年某行业整年没数据,到底是有意空出还是漏了?
- 明细表和汇总表数字是否一致:加总明细应与汇总表一致,不一致一定是脚本或清洗环节有问题。
- 裁员率是否异常:超过50%或为负数的记录,必须逐条查看。
- 来源覆盖比例:如果某年80%的数据来自同一个来源,说明该年份的代表性存疑,需要在README里提示。
我还会检查单元格格式:数字列必须是数字类型,不能有隐藏为文本的数字。比如员工数这一列,如果有一部分是文本格式,做合计时会被跳过,直接导致汇总数错误。这类问题肉眼很难发现,但用脚本判断value_if_string很容易暴露。
校验不通过就先不要发布。这也是为什么我坚持加“版本记录”sheet,每次做完一轮清洗、生成、校验,就把“改了哪些、删了哪些、哪些来源有更新”写进去,后面追问题能省大量时间。
4. xlsx格式坑:存储膨胀、xlsm跳变、打开乱掉
4.1 文件越来越大,不是数据多的错
我第一版xlsx只有8MB,后来只是调整几次格式、加了几列公式,体积直接飙到50MB以上。很多人以为这是数据量大,其实不是,这是xlsx“存储膨胀”的典型症状。
xlsx本质上是一个zip压缩包,里面是xml和资源文件。当你整列整行设置格式,哪怕这个区域根本没有数据,这些空单元格的样式也会被记录在styles.xml里,文件体积就会不正常增加。条件格式叠加过多、隐藏图表没清理、旧版本缓存数据没删除,都会让文件越来越臃肿。有的表明明只有几万行数据,却能撑到200MB,多半是这种原因。
检查方法很直接:用Python直接打开xlsx看它内部装了些什么。
import zipfile zf = zipfile.ZipFile("企业员工数_裁员数_2000-2024.xlsx", "r") for name in zf.namelist(): size = zf.getinfo(name).file_size if size > 100_000: print(name, size) zf.close()如果发现styles.xml、sharedStrings.xml异常大,基本可以断定是格式冗余。解决思路有三个。
第一,只给有数据的区域设置格式,不要全选整列设置字体边框。第二,删掉没用的条件格式规则和隐藏sheet。第三,用openpyxl打开文件后,检查sheet的max_row和max_column有没有膨胀到几万甚至几十万,如果max_column是几百列但实际只有几列,那要手动清除多余列。
最后还有一招:把内容复制到一个新建工作簿,用“值+格式”粘贴后重新保存,往往文件体积能缩小一半以上。
4.2 一打开就变成xlsm,到底哪出了问题
有段时间我把生成好的xlsx发给同事,对方回我一句“你是不是发成xlsm了”,我先是一愣,后来发现是WPS的问题。
“xlsm”通常是两种来源。一种是我在Excel文件里写了宏模块,哪怕没真正使用宏,只要文件里存在VBA项目,另存或重新保存时WPS和Excel都可能提示另存为xlsm格式。另一种是WPS打开xlsx后,在保存时自动添加了宏项目结构,把扩展名悄悄改成了xlsm。
排查方法不复杂。点击“开发工具”或“宏”按钮,查看VBAProject是否存在;如果不需要宏,打开VBA编辑器删除模块,再另存为xlsx。如果只是WPS自动转换,没有真正的宏,更稳妥的办法是在发送文件前用Excel打开另存一次,确保扩展名确实是.xlsx,再检查文件大小和格式是否正常。
xlsm和xlsx的核心区别就是能不能包含宏,xlsx不支持宏,xlsm支持。安全方面,别人发来的xlsm文件不要随便启用宏,这不是效率问题,是安全底线。尤其是我这种会把数据分享给多个协作方的人,更要防止有脚本被误触。
4.3 别人打开后格式乱了的三大原因
自己电脑上看着好好的表,发到别人那边字体变了、数字变成科学计数法、小数位丢了,这是xlsx分发最常见的坑。大概率是下面三个原因之一。
第一,字体兼容性。用了某些系统专有字体,对方电脑没有这个字体时会被替换成默认字体。第二,数字格式依赖区域设置。对方系统的区域设置和你不同,小数点、千位分隔符规则不一样,显示就会乱。第三,条件格式和筛选依赖区域引用,如果文件复制时引用区域乱了,筛选和格式就会失效。
解决方法是发布前统一字体为常见字体,把数字格式设置成固定的小数位,不要依赖全球通用区域设置;条件格式规则重新整理一遍,清除没用的引用区域。
还有个容易被忽略的点:不要把整个工作表设置为“适合内容列宽”。这个设置会随着字体和分辨率变化。发布前最好手动设定固定列宽,比如8到12这样的宽度,确保在别人的屏幕上看起来跟你自己这边基本一致。
4.4 数据口径对不齐,全在散点图上露馅
数据清洗时最容易漏掉的问题不是“缺数”,而是“单位错位”。当我把2000年和2015年的数据放在同一张表里,发现“员工数”这一列的量级差得离谱,检查后才发现2000年用的是“千人”,2015年用的是“人”。这种单位混乱如果不及时发现,趋势图基本就是断崖式突变。
排查异常最有效的工具不是Excel筛选,而是画一张简单的散点图。把每年员工总数画出来,凡是出现不合理突变的年份,基本都能一眼看出来。再用“按年和行业分组求均值”,把均值明显偏离的趋势点标出来逐条查。不要只盯着最大值和最小值,单位错误往往隐藏在中间几年。
应对措施其实就一条:数据字典里把单位约定写得清清楚楚,清洗脚本里再强制校验每个来源的数值范围。如果某个来源的数据平均在10万附近,突然来了个9位数的记录,先停下来查清楚,再继续往下跑。
5. 实操总结与个人体会
5.1 长跨度数据表的核心经验
把2000到2024年的企业员工数、裁员数xlsx从无到有做出来,我最想分享的经验不是某个具体函数,而是流程。
第一,先写数据字典,再碰原始数据。我能少走一半弯路,就是靠这份字典兜底。第二,所有清洗处理保留原始文件和中间文件,不要只留最终结果,谁也不知道哪个来源需要重新处理。第三,发布表格前再跑一遍校验清单,远比一遍遍解释“这里为什么是这样的”有效。
有一段时间我图省事,直接用Excel手工把几列数据拼在一起,当时看起来很高效,结果每次更新都要手动重做,最后熬不住还是回去写了Python脚本。长跨度数据更新频率高,流程化建设比一次性完成重要得多。
5.2 这份数据还能怎么扩展
如果后续要再往上走,我会做几件事。
一是把产业结构数据和员工数做联动,看看哪些行业的用工变化趋势联动性更强。二是补充企业注册数据,和裁员数形成更完整的对比视角。三是将xlsx与BI报表结合,做成自动刷新的可视化看板。技术上其实不难,核心还是前面说的:把数据源、口径、字段设计先搞清楚再前进。
在这次整理过程中我还有一个体会:越是看起来简单的Excel表格,越需要投入精力做设计。数据本身不会说话,整理数据的方式决定了它能不能被信任。你要做类似的长跨度数据表时,我的建议是不要急着下载和堆数据,先花时间把口径和结构定下来。这个收益会在你后面的每一次更新和查询中持续放大,短期内可能看不出差别,长期看能把你的数据习惯带到另一个水平。