Python数据分析实战:公务员职位表清洗与可视化全流程
把各省公务员招录职位表拉下来做一遍Python数据分析与可视化这活儿听起来像是个典型的课程项目但真正做完之后你对数据清洗、结构化字段处理和图表表达的理解会上一个台阶。每年各省发布的招录职位表少则几千行多则上万行有Excel、有Word、还有网页表格字段口径不统一备注内容五花八门直接拿Excel透视表也能凑合看但想从“看一眼涨了还是跌了”升级到“哪些专业最吃香、哪些地区门槛最高、学历要求变化的趋势是什么”就必须用Python把流程固化下来。这篇文章从一个实际项目的角度完整复盘如何把各省公务员招录职位表从原始文件变成干净的数据集再做统计分析并用可视化呈现。既适合正在做大数据课程设计的同学参考也适合想用Python做真实数据分析练习的初学者还包括那些想通过历史职位数据辅助报考决策的人。下面按项目推进顺序拆开讲每一步都会说明为什么这么做、坑在哪里、代码怎么写。1. 项目整体设计与思路拆解1.1 分析公务员招录职位表到底能解决什么问题很多人看到“公务员招录职位表”会下意识觉得这只是个Excel表格不值得分析。但换一个角度它其实是一份含金量很高的公共数据每个职位都带地区、部门层级、学历要求、专业要求、政治面貌、基层工作年限、招录人数等结构化字段。把多个省份、多个年度的职位表汇总起来能回答很多实际的问题哪个省份招录规模最稳定哪个年份扩招明显学历门槛是在提高还是放宽硕士岗位比例变化趋势如何哪些专业在招录中出现的频次最高哪些专业几乎是“天选之子”各城市、各系统税务、公安、乡镇等的招录结构有什么差异限制应届生、限制基层经验的职位占比有多大。这些结论对个人报考策略有直接参考价值也适合作为大数据可视化的选题素材。更关键的是数据本身是公开可获取的不涉及隐私问题处理流程完整能把爬取、清洗、聚合、可视化全链路练一遍。1.2 技术选型与整体流程设计整个项目我选的路线是Python 3 pandas做数据处理pyecharts做交互式可视化同时用matplotlib出静态图用于报告插入。为什么不用纯Excel因为几十个省份的表格格式不统一Excel透视表处理跨年跨地区合并非常痛苦而pandas的DataFrame操作可以写一套清洗函数批量跑完所有文件。完整流程分四步数据获取从各省人事考试网或公务员局官网下载历年职位表附件整理到本地文件夹数据清洗统一字段名、处理缺失值、拆分“专业要求”这类长文本、从“学历要求”中提取学历层次数据分析按省份、年份、系统、学历、专业等维度做分组聚合计算占比、同比变化等指标数据可视化用柱状图、折线图、地图、饼图等方式呈现结论输出为HTML或图片。这里有一个很重要的设计决定一开始不要追求自动化爬取所有省份先手动下载三到五个省份近两年的职位表跑通流程再扩展到更多年份和地区。因为各省表格的字段命名和格式差异很大一上来就写通用爬虫光处理反爬和表格结构就够折腾的。1.3 字段标准化的核心思路不同省份的职位表字段虽然大同小异但细节差异很大。比如同样是学历要求有的写“本科及以上”有的写“本科”有的写“本科或硕士研究生”还有的写“大专及以上”。同样是专业要求有的按学科门类写“计算机类”有的写具体专业“计算机科学与技术、软件工程、网络工程”有的干脆写“不限”。所以项目的第一步不是急着做图而是先定义一份你心里的“标准字段字典”标准字段常见原始字段写法招录机关招录单位、用人单位、部门名称职位名称职位、岗位、招录职位招录人数名额、计划招录人数、人数学历要求学历、学位要求、最低学历专业要求专业、专业类别、专业条件政治面貌政治面貌要求、是否党员基层工作年限基层工作最低年限、工作经历要求地区工作地点、考区、所属辖区在实际操作中我建议每拿到一个新省份的文件先打印出列名人工写一个字段映射字典再交给pandas统一重命名。这个步骤看起来笨实际最省时间因为完全自动识别列名往往要写一堆正则和模糊匹配小数据量没必要。2. 数据获取与预处理实操2.1 原始数据从哪里来、怎么保存各省公务员招录职位表一般以附件形式发布在省人事考试网、省公务员局官网文件名通常是“职位表.xls”“招录职位一览表.xlsx”这类。部分省份会在公告页直接提供网页表格需要自行另存或复制。实际操作时我建议按这样的目录结构存放原始文件data/ 2022/ 某东部省份_职位表.xlsx 某中部省份_职位表.xlsx 2023/ 某东部省份_职位表.xlsx 某中部省份_职位表.xlsx文件名规范很重要因为后面合并数据时年份和省份信息可以直接从文件名中解析出来省掉很多手工标注的麻烦。我就是因为一开始文件名随意导致后面不得不返工补了一轮省份字段。下载环节有几个注意点很多政府网站访问速度慢不要用多线程并发请求容易被限制访问用单线程加随机延时就好部分网站不支持wget或requests直接下载文件可能需要在浏览器里手动点击下载有些文件下载下来是网页跳转链接而不是真实文件需要检查扩展名和文件大小避免存了一堆空壳文件。2.2 用pandas读取Excel文件的常见坑读取Excel文件pandas提供read_excel函数但实际用起来要小心几个问题。第一条是文件格式兼容性老版本文件可能是.xls格式需要安装xlrd库新版本.xlsx格式需要openpyxl库。建议直接全部安装pip install pandas openpyxl xlrd第二条是工作表位置不固定。有的职位表把数据放在第一个sheet有的放在第二个还有的在同一个sheet里上下堆了两张表。稳妥的做法是先用ExcelFile对象查看sheet名再决定读取哪个import pandas as pd xls pd.ExcelFile(data/2023/某省份_职位表.xlsx) print(xls.sheet_names) df pd.read_excel(xls, sheet_name0, header0)第三条是表头不一定在第一行。有些省份会在表格上方放几行说明文字比如“附件XX省2023年度考试录用公务员职位表”然后才是真正的表头。这时需要手动指定header参数或者先读取几行看看内容再调整。我习惯先打印前五行做个快速体检df_preview pd.read_excel(file, headerNone, nrows5) print(df_preview)2.3 缺失值和脏数据处理技巧职位表里的缺失值比想象中多。常见的情况有某个职位没有专业限制专业要求字段可能填“不限”没有政治面貌要求可能直接留空基层工作年限可能写“无限制”。处理时不能一刀切地填充要结合字段含义判断df[专业要求] df[专业要求].fillna(不限) df[政治面貌] df[政治面貌].fillna(不限) df[基层工作年限] df[基层工作年限].replace({无限制: 0, 不限: 0, 无: 0})还要注意数据里混入全角空格、中文括号、不间断空格等。比如“本科 及以上”“本科及以上”这类写法直接用字符串匹配会出问题。统一做法是先把字符串标准化import re def clean_text(s): if pd.isna(s): return s s str(s) s s.replace(\u3000, ).replace(\xa0, ) # 去掉全角空格和不间断空格 s re.sub(r[\s()], , s) # 去掉空白和括号 return s这个函数看起来简单却能解决大多数因空格和括号导致的匹配失败问题。3. 核心分析维度与可视化实现3.1 招录人数与地区分布分析第一个要做的分析维度肯定是招录总人数的地区分布。把所有省份的职位表合并后按省份分组求和招录人数画一个横向柱状图能直观看出哪些地区招录规模大哪些地区相对缩招。可视化我用pyecharts因为它生成的HTML图表交互性好鼠标悬停有提示还可以缩放、保存图片放在报告里或者浏览器里看都很方便。核心代码如下from pyecharts.charts import Bar from pyecharts import options as opts # df_all是合并后的总表province和num是标准字段 province_sum df_all.groupby(province)[num].sum().sort_values() bar Bar() bar.add_xaxis(province_sum.index.tolist()) bar.add_yaxis(招录人数, province_sum.values.tolist()) bar.set_global_opts( title_optsopts.TitleOpts(title各省公务员招录总人数对比), xaxis_optsopts.AxisOpts(name省份), yaxis_optsopts.AxisOpts(name人数) ) bar.render(output/各省招录人数对比.html)这里有一个经验如果省份数量多横向柱状图比纵向柱状图更适合阅读。pyecharts的Bar支持bar_category_gap等参数调整柱间距可以留到后面慢慢调。3.2 学历要求分布与逐年趋势学历要求是职位表里最有分析价值的数据之一。把学历字段做标准化后统计各学历层次职位数量占比再按年份看趋势变化就能回答“门槛是否在提高”这个问题。我实际处理时发现学历要求字段往往不是单值比如“本科及以上”表示最低学历是本科。更复杂的还有“本科或硕士研究生”这种表示本科和硕士都符合条件。为了简化分析第一版项目只提取最低学历也就是从字符串里截取出“大专”“本科”“硕士研究生”“博士研究生”中最基础的一个层次。虽然会损失部分信息但足够反映整体趋势。def extract_min_degree(text): if pd.isna(text): return 未知 text clean_text(text) if 博士 in text: return 博士研究生 if 硕士 in text: return 硕士研究生 if 本科 in text: return 本科 if 大专 in text or 专科 in text: return 大专 return 其他画学历分布饼图时要注意饼图适合占比展示但如果类别过多图例堆在一起很难看。建议只保留前五大类别其他归为“其他”再用堆叠柱状图看年份趋势。3.3 专业要求的关键词挖掘专业要求字段是职位表里最复杂的因为一个职位可能同时允许十几个专业报考用顿号、逗号、斜杠分隔。分析思路有两个方向一是统计“专业限制频次”即把所有职位要求的专业字符串拼接起来按专业关键词计数二是统计“不限专业”职位的占比这个数字越高说明专业门槛越宽松。关键词挖掘用简单的字符串计数就能实现不需要上复杂的NLP模型。比如把每行的专业要求按分隔符拆开再统计词频from collections import Counter def split_major(text): if pd.isna(text) or text 不限: return [] text clean_text(text) parts re.split(r[,、/;], text) return [p for p in parts if p] major_counter Counter() for major_text in df_all[专业要求]: for m in split_major(major_text): major_counter[m] 1 top_majors major_counter.most_common(20)这里有个细节容易被忽略拆分出来的词条长度差异很大有“计算机科学与技术”这种具体专业也有“计算机类”这种学科门类还有“法学类、法律”这类相近词。直接计数会把这些相近表达拆散。简单解法是做一个同义词替换表把“计算机科学与技术”“软件工程”“网络工程”这类归并到“计算机类相关”或者只统计到类别关键词层级。第一版可以朴素计数但要清楚结果只是粗略参考。3.4 限制条件与职位层级透视除了学历和专业职位表里还有大量二元属性是否限制应届生、是否要求党员、是否有基层工作年限要求、是否只招男性或女性。这些字段可以用0/1编码统计比例。职位层级一般可以从“单位名称”或“职位代码”中提取常见层级有省级、市级、县级、乡镇。很多省份的职位代码前几位就包含层级信息比如代码中某一位是1代表省级、2代表市级、3代表县级、4代表乡镇。我的做法是先按代码解析出层级再和招录单位名称做交叉验证。如果代码规则不清楚也可以直接用单位名称关键词匹配比如出现“乡镇”“街道”的归为基层。这个维度做出来之后可以画一个“层级-学历”的堆叠图往往能发现一个现象基层岗位更多面向本科学历省级岗位硕士比例显著更高。这类结论比单纯罗列数字更能说明问题。3.5 可视化图表的选型心得图表选型比很多人想象的重要。做数据分析项目最忌讳的是每张图都用饼图或者都用柱状图。我的选型标准很简单比较大小和排名用柱状图看时间变化趋势用折线图看占比构成用饼图或堆叠柱状图看地理分布用地图看两个指标的关系用散点图。pyecharts的地图组件做省份分布很合适但要注意首次使用地图需要加载地图数据文件这一步特别容易踩坑。用新版本pyecharts2.x时地图数据包和主库是分开发布的需要单独安装pip install pyecharts如果地图渲染出来是空白多半是地图JS文件没有正确加载检查一下HTML文件里的地图注册脚本是否存在或者尝试更换render模式。4. 完整实操流程记录4.1 第一步合并多个Excel文件整个项目最核心的代码段是批量读取文件夹里的所有Excel并合并。我实际写的合并函数长这样import os import pandas as pd field_map { 省份: province, 年份: year, 招录单位: dept, 职位名称: position, 招录人数: num, 学历: degree, 学位: degree_level, 专业: major, 政治面貌: political, 基层工作年限: work_year, 工作地点: city, } def parse_filename(filename): # 文件名规范年份_省份_职位表.xlsx parts filename.replace(.xlsx, ).replace(.xls, ).split(_) year parts[0] province parts[1] return year, province def load_files(data_dir): frames [] for root, dirs, files in os.walk(data_dir): for file in files: if not (file.endswith(.xls) or file.endswith(.xlsx)): continue filepath os.path.join(root, file) year, province parse_filename(file) df pd.read_excel(filepath) df df.rename(columnsfield_map) df[year] year df[province] province frames.append(df) return pd.concat(frames, ignore_indexTrue)合并过程中最常见的问题是各表的字段数量不一致。有的省份多了“备注”列有的少了“学位”列直接用concat合并时缺失的列会自动变成NaN这倒不影响大局但要留意总列数。4.2 第二步统一字段内容格式合并完成后的下一步是统一内容格式。除了前面讲的学历、专业处理还有一个容易被忽视的字段招录人数。招录人数在Excel里看起来是数字但读进来之后可能是字符串、浮点数甚至带中文。比如“2人”“两”“1—2名”这类写法。处理招录人数我写了这个逻辑def parse_num(s): if pd.isna(s): return 0 s str(s).strip() s re.sub(r人|名|个, , s) s s.replace(—, -).replace(--, -) if - in s: parts s.split(-) # 区间值取中位数 return (int(parts[0]) int(parts[1])) / 2 try: return int(float(s)) except: return 0这里取区间中位数只是一个简化策略实际项目中如果职位要求“1-2名”取2会更符合保守估算具体看分析目的。如果你之后要报考应该按上限人数算更稳妥。4.3 第三步生成关键可视化图表完成数据清洗后我用几段代码分别生成图表每一张图对应一个分析结论。省份分布地图的代码需要注意注册地图from pyecharts.charts import Map china_map Map() china_map.add( 招录人数, [list(z) for z in zip(province_names, province_values)], maptypechina ) china_map.set_global_opts( title_optsopts.TitleOpts(title各省招录人数分布), visualmap_optsopts.VisualMapOpts(max_max(province_values)) ) china_map.render(output/招录人数地图.html)折线图用于多年度趋势对比关键参数是is_smooth可以让曲线更平滑视觉上更舒服。学历占比堆叠图用Bar的stack参数实现多个学历层次叠加在一根柱子上既能看总体规模变化又能看结构变化。4.4 第四步输出分析结论图做出来之后还需要配文字结论。这一步往往是学生项目最薄弱的地方大家容易只贴图不解读。我在项目里会为每张图写三句话图上最明显的现象、数据佐证的数字、可能的原因分析。举个例子某省2022到2023年招录总人数下降了18%但本科学历职位占比从63%涨到71%。结合该省当年对基层岗位的学历政策调整可以推断学历门槛有放宽趋势。写结论时不要求做因果断言更多是提出假设这反而更符合数据分析的思维习惯。5. 常见问题与排查技巧实录5.1 Excel文件读取乱码或数据为空read_excel读出来全是NaN或者中文乱码通常是引擎问题。检查是不是没安装openpyxl或xlrd。另一个容易忽略的点是有些“Excel”文件其实是CSV文件改了扩展名这时候read_excel会失败改用read_csv并指定encodingutf-8或gbk就能解决。字符编码问题在Windows环境尤其常见一部分省级网站下载的CSV文件是GBK编码直接用默认参数读会乱码。我的排查习惯是遇到任何读进来中文乱码的文件先用文本编辑器打开原始文件确认编码再用对应的encoding参数读取。5.2 学历、专业字段取值混乱前面提到的clean_text函数能解决大部分格式问题但有些情况需要单独处理。比如“本科及以上”和“本科以上”是同义表达“大专及以上”和“专科及以上”也是同义表达。我的做法是先建立一个同义映射表degree_map { 本科以上: 本科, 本科及以上: 本科, 硕士研究生以上: 硕士研究生, 硕士研究生及以上: 硕士研究生, 大专以上: 大专, 大专及以上: 大专, 专科以上: 大专, }然后统一用映射表做替换再做最低学历提取这样统计结果稳定得多。5.3 pyecharts地图不显示地图不显示的问题90%出在pyecharts版本和地图数据包不匹配上。我遇到过的情况是HTML打开后只有标题图表区域空白浏览器控制台报找不到地图脚本的错误。解决方案是检查pyecharts版本并确认是否安装了地图扩展包。另一个新版本才有的问题pyecharts 2.x默认从CDN加载JS资源如果分析环境没有外网访问权限图表会加载不出来。解决办法是把项目设置为离线模式或者把生成的HTML文件里的JS链接替换为本地文件。做课程汇报或写报告时最好在本地生成HTML后再嵌入文档不要在无网环境现场渲染。5.4 分析结论“为做图而做图”这个坑不是技术问题是思维问题。很多项目收集了一堆数据做了八九张图但每张图之间没有逻辑关系读者看完不知道整体结论是什么。我的改进方法是先写分析提纲再决定做哪些图。比如分析提纲是“招录规模画像-学历门槛变化-专业需求集中度-限制条件分布”然后每个主题只做一到两张最能说明问题的图。图和图之间保持递进关系先看总量再看结构再看细节。这样最后生成的报告有了完整叙事线不再是一堆孤立图表的拼盘。写在最后的一点体会这类“职位表数据分析”的项目技术门槛其实不高难的是把一份又一份格式乱七八糟的官方表格整理成能分析的结构化数据。我在做这个项目时最深的体会是数据分析工作里清洗和标准化占用了七成以上的时间真正跑分析画图反而很快。也正因如此这个项目特别适合用来训练自己对脏数据的耐心和处理能力。如果你之后想扩展可以尝试加入报考热度数据、历年分数线等进行综合分析或者用爬虫定时抓取新增职位自动提醒。顺着这个方向往下做这个看似简单的课程项目完全可以变成一个能持续使用的个人报考辅助工具。