
简介这份资源面向希望入门或进阶Python数据分析的职场学习者以数据分析师岗位招聘数据为实战对象完整演示从数据清洗到可视化呈现的分析流程。包内共10个文件涵盖ipynb交互式笔记本、py脚本、csv原始数据、html分析报告、ttf中文字体及jpg配图等压缩包约40.89MB兼顾代码运行与结果查阅。项目围绕Pandas数据加载与缺失值处理、描述性统计、探索性分析EDA以及Matplotlib图表绘制展开重点呈现薪资分布、工作经验与薪资关系等职场关注维度并涉及重复值检测、异常值处理与图表可读性优化等细节。源码约300行结构清晰适合对照学习如何组织一个完整的数据分析项目。目前已有372人学习可作为课程作业、简历项目或自学练手的参考素材。1. 从一份 300 行的数据分析师岗位分析源码说起招聘网站上的数据分析师岗位薪资跨度能从 8K 拉到 40K要求里有的写“精通 Excel 即可”有的列了 Python、SQL、Tableau、机器学习一长串。想搞清楚这个岗位到底在招什么人、哪些技能是硬门槛、哪些只是加分项靠一页页翻招聘信息不现实。这份约 300 行的 Python 源码做的就是抓一批数据分析师岗位的招聘数据用 pandas 做探索性分析再用 matplotlib 把薪资分布、技能词频、城市差异画出来。它不依赖爬虫框架不依赖数据库一个 CSV 加两个库就能跑通全流程。适合刚学完 pandas 基础、想找一个完整项目练手的入门者也适合需要快速搭一个岗位分析原型、再往里面填自己业务数据的从业者。代码量不大但覆盖了数据分析项目最核心的几步读数据、清洗、分组聚合、可视化输出。2. 数据探索分析从原始 CSV 到可用 DataFrame2.1 先搞清楚数据长什么样拿到任何一份招聘数据第一步不是急着画图而是把 DataFrame 的 shape、dtypes、head 和缺失值情况看一遍。这份源码里对应的就是数据探索分析部分通常集中在文件前 80 行左右。常见做法是先读 CSV然后立刻做一次结构体检。import pandas as pd import numpy as np # 读取招聘数据注意编码中文 CSV 常见 utf-8 或 gbk df pd.read_csv(data.csv, encodingutf-8) # 结构体检四件套 print(df.shape) # 行数、列数 print(df.dtypes) # 每列类型 print(df.head(3)) # 前 3 行 print(df.isnull().sum()) # 每列缺失值数量逻辑说明shape让你知道样本量够不够支撑后续分组dtypes决定哪些列需要转数值head帮你确认列名和内容是否对得上isnull().sum()直接暴露哪些字段需要清洗。参数上encoding如果报UnicodeDecodeError换成gbk或gb18030再试这是中文数据最常见的翻车点。2.2 薪资字段的清洗是整份代码的分水岭招聘数据里最脏的往往是薪资列格式可能是“15-25K·13薪”“1.5-2万/月”“面议”。这份源码的核心处理逻辑就是把薪资区间拆成最低、最高、中位数三个数值列。如果这一步不做后面所有按薪资分组的分析都是空的。import re def parse_salary(s): if pd.isna(s) or 面议 in str(s): return pd.Series([np.nan, np.nan, np.nan]) s str(s).lower().replace(·13薪, ).replace(·14薪, ) # 匹配 15-25k 或 1.5-2万 两种常见格式 m re.search(r(\d\.?\d*)-(\d\.?\d*)(k|万), s) if not m: return pd.Series([np.nan, np.nan, np.nan]) low, high, unit float(m.group(1)), float(m.group(2)), m.group(3) if unit 万: low, high low * 10, high * 10 # 统一成 K return pd.Series([low, high, (low high) / 2]) df[[salary_low, salary_high, salary_mid]] df[salary].apply(parse_salary)逻辑说明parse_salary返回一个 Seriesapply之后会自动展开成三列。正则(\d\.?\d*)-(\d\.?\d*)(k|万)覆盖了“15-25K”和“1.5-2万”两种写法单位统一成 K 是为了后续比较。参数上\d\.?\d*允许整数和小数·13薪这类后缀直接去掉不影响月薪区间。清洗完记得df[[salary_low,salary_mid]].describe()看一眼分布如果中位数明显偏离常识说明正则漏了某种格式。2.3 技能关键词的拆分与统计岗位要求字段通常是一长串文本比如“Python、SQL、Excel、Tableau”。要统计哪些技能出现频率最高得先把这列拆成独立的词再展开计数。from collections import Counter # 假设技能列叫 skills用顿号或逗号分隔 all_skills [] for row in df[skills].dropna(): parts re.split(r[、,/], row) all_skills.extend([p.strip() for p in parts if p.strip()]) skill_count Counter(all_skills).most_common(15) skill_df pd.DataFrame(skill_count, columns[skill, count]) print(skill_df)逻辑说明re.split用多个分隔符同时切分strip去掉空格Counter直接出词频。参数上分隔符集合[、,/]可以根据实际数据增减如果发现“Python”和“python”被算成两个词加一步.lower()统一大小写。这一步的输出skill_df就是后面画条形图的输入。3. matplotlib 可视化把分析结果画成能看的图3.1 薪资分布直方图与中位数标注数据探索完最有信息量的第一张图是薪资分布。源码里通常用直方图加一条中位数竖线一眼看出市场集中区间。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 中文显示 plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(8, 5)) ax.hist(df[salary_mid].dropna(), bins20, color#4C72B0, edgecolorwhite) median df[salary_mid].median() ax.axvline(median, colorred, linestyle--, labelf中位数 {median:.1f}K) ax.set_xlabel(月薪中位值K) ax.set_ylabel(岗位数量) ax.set_title(数据分析师薪资分布) ax.legend() plt.tight_layout() plt.savefig(salary_dist.png, dpi150) plt.show()逻辑说明hist的bins20是经验值样本几百条时 15 到 25 之间比较合适太少看不出分布太多锯齿明显。axvline画中位数参考线legend显示数值。参数上font.sans-serif设成SimHei是 Windows 下的常见做法Mac 可以换Arial Unicode MSLinux 需要确认系统装了中文字体否则中文全是方块。dpi150保证保存的图够清晰。3.2 技能词频横向条形图技能统计结果用横向条形图最直观因为技能名长短不一横着放不用旋转标签。fig, ax plt.subplots(figsize(8, 6)) skill_df_sorted skill_df.sort_values(count) ax.barh(skill_df_sorted[skill], skill_df_sorted[count], color#55A868) ax.set_xlabel(出现次数) ax.set_title(数据分析师岗位技能要求 Top 15) for i, v in enumerate(skill_df_sorted[count]): ax.text(v 0.5, i, str(v), vacenter, fontsize9) plt.tight_layout() plt.savefig(skill_bar.png, dpi150) plt.show()逻辑说明sort_values升序排列后barh从下往上画最长的在最上面。ax.text在每根柱子末尾标数值v 0.5是偏移量避免文字压住柱子。参数上figsize高度给 6 是因为 15 个技能需要足够行距如果技能更多按每行 0.4 英寸往上加。3.3 城市薪资对比分组箱线图不同城市的薪资差异是求职者最关心的维度之一。箱线图能同时展示中位数、四分位和异常值。top_cities df[city].value_counts().head(6).index df_city df[df[city].isin(top_cities)] fig, ax plt.subplots(figsize(9, 5)) df_city.boxplot(columnsalary_mid, bycity, axax, gridFalse) ax.set_xlabel(城市) ax.set_ylabel(月薪中位值K) ax.set_title(主要城市数据分析师薪资对比) plt.suptitle() # 去掉 pandas 自动加的标题 plt.tight_layout() plt.savefig(city_salary.png, dpi150) plt.show()逻辑说明先取岗位数量前 6 的城市避免小样本城市干扰。boxplot的by参数按城市分组gridFalse去掉网格线让图更干净。plt.suptitle()是必须的否则 pandas 会自动加一个多余的总标题。参数上如果某个城市数据点太少箱体会压成一条线这时候要么合并城市要么在图上标注样本量。4. 避坑与常见问题排查4.1 中文显示成方块现象图上所有中文标签变成一个个小方块英文和数字正常。原因matplotlib 默认字体不含中文字形。解决在绘图前设置plt.rcParams[font.sans-serif] [SimHei]Mac 用[Arial Unicode MS]Linux 先fc-list :langzh确认有哪些中文字体再填对应名称。设完还不行就清一下 matplotlib 缓存删掉~/.matplotlib/fontlist-*.json重启内核。4.2 薪资正则匹配不到“万”字格式现象salary_mid大量为 NaNdescribe 出来 count 只有个位数。原因正则只写了k没写万或者“万”前面是“1.5-2万”这种小数。解决正则改成(\d\.?\d*)-(\d\.?\d*)(k|万)匹配到“万”后乘 10 统一成 K。改完用df[df[salary_mid].isna()][salary].head(20)抽查没匹配上的原始值看还漏了什么格式。4.3 apply 返回多列时报错现象df[[a,b,c]] df[x].apply(func)报ValueError: Columns must be same length as key。原因函数在某些行返回了标量而不是 Series导致展开失败。解决确保函数每条分支都return pd.Series([...])包括异常分支。调试时先df[x].apply(func).head()看返回结构确认每行都是等长 Series 再赋值。4.4 箱线图某个城市只有一条线现象箱线图里某个城市的箱子变成一条横线看不出分布。原因该城市样本量太少或者薪资值全部相同。解决先df[city].value_counts()看各城市样本量少于 10 条的考虑合并到“其他”或直接剔除。如果值确实相同检查是不是薪资清洗时把区间压成了一个点。4.5 保存图片空白现象savefig出来的图片是空白或者被裁切。原因savefig在show之后调用或者tight_layout没生效。解决把savefig放在show之前加bbox_inchestight参数自动裁掉多余白边。如果用了tight_layout还是裁切试试plt.subplots_adjust手动留边距。5. 进阶技巧让这份源码适配你自己的数据这份代码跑通之后最有价值的动作是把它套到你手头真实的招聘数据上。我一般会先改三个地方列名映射、薪资解析、技能词典。列名映射是因为不同来源的 CSV 列名五花八门与其改代码逻辑不如在读入后加一层 rename。col_map { 岗位名称: title, 公司: company, 薪资范围: salary, 城市: city, 技能要求: skills } df df.rename(columnscol_map)逻辑说明把中文列名统一成代码里用的英文名后面所有分析逻辑不用动。参数上col_map的键必须和 CSV 实际列名完全一致包括空格建议先print(df.columns.tolist())确认。技能词典是第二个要改的。原始代码用正则切分但真实数据里“机器学习”和“ML”、“自然语言处理”和“NLP”会被算成不同词。常见做法是加一个归一化字典在Counter之前做一次替换。alias { ml: 机器学习, nlp: 自然语言处理, etl: ETL, bi: BI } all_skills [alias.get(s.lower(), s) for s in all_skills]逻辑说明alias.get命中就替换没命中保留原词。参数上字典的键统一小写因为前面已经.lower()过。这一步做完再统计词频表会干净很多。最后一个技巧是给图表加一个统一的样式函数避免每张图重复写字体和尺寸设置。def setup_style(): plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False plt.rcParams[figure.dpi] 120 plt.rcParams[savefig.bbox] tight setup_style()逻辑说明把字体、负号、DPI、保存边距一次性设好后面每张图直接plt.subplots就行。参数上figure.dpi设 120 在屏幕上看和保存都够用要出版级再单独给savefig传dpi300。从那以后我每次拿到新的招聘数据都强制先跑一遍shape、dtypes、isnull三件套再动任何分析逻辑。这份 300 行的源码最大的价值不是它画了什么图而是它把“读数据 → 清洗 → 分组 → 可视化”这条链路完整走了一遍你可以在任何一个环节替换成自己的处理方式。希望帮到你。本文还有配套的精品资源点击获取