实战演练------数据分析师岗位分析知识点详解
一、数据分析的流程
1.1 知识点概述
数据分析的标准流程一般包含以下五个阶段:
| 阶段 | 说明 |
|---|---|
| 明确目标 | 确定分析的业务问题和分析方向 |
| 数据收集 | 通过爬虫、数据库、API、公开数据集等方式获取原始数据 |
| 数据预处理 | 对原始数据进行清洗、转换、整合,使其满足分析需求 |
| 数据分析与展现 | 运用统计方法和可视化手段探索数据规律 |
| 结论与建议 | 总结发现,输出分析报告 |
1.2 数据分析流程示意图代码(pyecharts 基础铺垫)
python
# ============================================
# 使用 pyecharts 绘制数据分析流程图(Sankey 桑基图)
# ============================================
# 导入 pyecharts 中的图表类型和配置项模块
from pyecharts.charts import Sankey # 导入桑基图类
from pyecharts import options as opts # 导入全局配置项模块
# ---------- 定义节点(nodes) ----------
# 每个节点代表数据分析流程中的一个阶段
# "name" 是节点的显示名称
nodes = [
{"name": "明确目标"}, # 第1阶段:明确分析目标
{"name": "数据收集"}, # 第2阶段:收集原始数据
{"name": "数据预处理"}, # 第3阶段:清洗与转换数据
{"name": "数据分析"}, # 第4阶段:统计分析与建模
{"name": "数据可视化"}, # 第5阶段:图表呈现结果
{"name": "结论与建议"}, # 第6阶段:输出分析报告
]
# ---------- 定义链接(links) ----------
# 每条链接表示从一个阶段流向下一个阶段
# "source":起始节点名称
# "target":目标节点名称
# "value": 流量大小(此处表示流程的先后顺序权重)
links = [
{"source": "明确目标", "target": "数据收集", "value": 10}, # 目标 -> 收集
{"source": "数据收集", "target": "数据预处理", "value": 10}, # 收集 -> 预处理
{"source": "数据预处理", "target": "数据分析", "value": 10}, # 预处理 -> 分析
{"source": "数据分析", "target": "数据可视化", "value": 8}, # 分析 -> 可视化
{"source": "数据可视化", "target": "结论与建议", "value": 8}, # 可视化 -> 结论
]
# ---------- 创建桑基图 ----------
sankey = (
Sankey() # 实例化桑基图对象
.add( # 添加数据系列
series_name="数据分析流程", # 系列名称(图例显示)
data_opt=nodes, # 传入节点数据
link_opt=links, # 传入链接数据
linestyle_opt=opts.LineStyleOpts( # 设置线条样式
opacity=0.2, # 线条透明度(0-1)
curve=0.5, # 线条弯曲程度(0-1)
color="source", # 线条颜色跟随源节点颜色
),
label_opts=opts.LabelOpts( # 设置标签样式
position="right", # 标签显示在节点右侧
font_size=14, # 字体大小14px
),
)
.set_global_opts( # 设置全局配置
title_opts=opts.TitleOpts( # 标题配置
title="数据分析流程图", # 主标题
title_textstyle_opts=opts.TextStyleOpts( # 标题文字样式
font_size=20, # 标题字号
color="#333", # 标题颜色
),
),
)
)
# ---------- 渲染输出为 HTML 文件 ----------
sankey.render("data_analysis_flow.html") # 生成本地HTML文件
print("数据分析流程图已生成:data_analysis_flow.html") # 提示输出完成
二、使用 pyecharts 绘制图表
2.1 pyecharts 简介与安装
bash
# ============================================
# pyecharts 安装命令(在终端/命令行中执行)
# ============================================
# 安装 pyecharts 库(Echarts 的 Python 版本)
pip install pyecharts
# 查看安装版本
pip show pyecharts
2.2 pyecharts 核心架构
python
# ============================================
# pyecharts 核心架构演示
# ============================================
# pyecharts 的两个核心导入
from pyecharts.charts import Bar, Line, Pie, Map, Grid, Page # 导入图表类型
from pyecharts import options as opts # 导入配置项
# pyecharts 的核心架构:
# 1. 图表类型(Charts):Bar, Line, Pie, Map, Scatter 等
# 2. 配置项(Options):全局配置 + 系列配置
# 3. 渲染(Render):输出为 HTML 或图片
# ----- 配置项分类 -----
# 全局配置项(set_global_opts):
# - TitleOpts 标题配置
# - ToolboxOpts 工具箱配置
# - LegendOpts 图例配置
# - VisualMapOpts 视觉映射配置
# - DataZoomOpts 数据缩放配置
# - TooltipOpts 提示框配置
# 系列配置项(.add() 中使用):
# - LabelOpts 标签配置
# - ItemStyleOpts 图元样式配置
# - linestyle_opts 线样式配置
# - areastyle_opts 区域填充样式配置
2.3 pyecharts 基本使用模式
python
# ============================================
# pyecharts 基本使用模式------以柱状图为例
# ============================================
from pyecharts.charts import Bar # 导入柱状图类
from pyecharts import options as opts # 导入配置项
# 第一步:准备数据
# x 轴数据(类目轴):城市名称列表
x_data = ["北京", "上海", "深圳", "杭州", "广州"]
# y 轴数据(数值轴):各城市数据分析师岗位数量
y_data = [1200, 980, 850, 620, 580]
# 第二步:创建图表对象并链式调用
bar = (
Bar() # 实例化柱状图对象
.add_xaxis(x_data) # 添加 x 轴数据
.add_yaxis( # 添加 y 轴数据系列
series_name="岗位数量", # 系列名称
y_axis=y_data, # y 轴数据
label_opts=opts.LabelOpts( # 标签配置
is_show=True, # 是否显示标签
position="top", # 标签位置:柱体顶部
color="#333", # 标签字体颜色
),
)
.set_global_opts( # 全局配置
title_opts=opts.TitleOpts( # 标题
title="数据分析师岗位数量 Top5 城市", # 主标题
subtitle="数据来源:某招聘平台", # 副标题
),
xaxis_opts=opts.AxisOpts( # x 轴配置
name="城市", # 轴名称
axislabel_opts=opts.LabelOpts(rotate=30), # 标签旋转30度防重叠
),
yaxis_opts=opts.AxisOpts( # y 轴配置
name="岗位数量", # 轴名称
),
tooltip_opts=opts.TooltipOpts( # 提示框配置
trigger="axis", # 触发方式:坐标轴触发
axis_pointer_type="shadow", # 指示器类型:阴影
),
)
)
# 第三步:渲染输出
bar.render("bar_basic_demo.html") # 生成HTML文件
print("柱状图已生成:bar_basic_demo.html")
2.4 常用图表类型详细案例
2.4.1 折线图(Line)
python
# ============================================
# 折线图案例------数据分析师岗位需求趋势
# ============================================
from pyecharts.charts import Line # 导入折线图类
from pyecharts import options as opts # 导入配置项
from pyecharts.globals import ThemeType # 导入主题类型
# 准备数据
# x 轴:月份
months = ["2024-01", "2024-02", "2024-03", "2024-04",
"2024-05", "2024-06", "2024-07", "2024-08",
"2024-09", "2024-10", "2024-11", "2024-12"]
# y 轴:每月新增岗位数量
job_counts = [3200, 2800, 4100, 4500, 4800, 5200,
5000, 4600, 5800, 6100, 5500, 6800]
# 创建折线图对象,使用 DARK 主题
line = (
Line(init_opts=opts.InitOpts( # 初始化配置
theme=ThemeType.DARK, # 使用暗色主题
width="1000px", # 图表宽度
height="500px", # 图表高度
))
.add_xaxis(months) # 添加 x 轴数据(月份)
.add_yaxis( # 添加 y 轴数据系列
series_name="新增岗位数", # 系列名称
y_axis=job_counts, # y 轴数据
is_smooth=True, # 平滑曲线(默认为折线)
symbol="circle", # 数据点标记形状:圆形
symbol_size=8, # 数据点标记大小
linestyle_opts=opts.LineStyleOpts( # 线条样式配置
width=3, # 线宽3px
color="#e74c3c", # 线条颜色:红色
),
label_opts=opts.LabelOpts( # 标签配置
is_show=True, # 是否显示标签
position="top", # 标签位于数据点上方
font_size=10, # 标签字号
),
# 添加区域填充样式(面积图效果)
areastyle_opts=opts.AreaStyleOpts( # 区域填充配置
opacity=0.3, # 填充透明度
color="#e74c3c", # 填充颜色
),
# 标记最大值和最小值
markpoint_opts=opts.MarkPointOpts( # 标记点配置
data=[
opts.MarkPointItem( # 标记最大值
type_="max", # 标记类型:最大值
name="最大值", # 标记名称
),
opts.MarkPointItem( # 标记最小值
type_="min", # 标记类型:最小值
name="最小值", # 标记名称
),
],
symbol_size=50, # 标记点大小
),
# 添加平均线
markline_opts=opts.MarkLineOpts( # 标记线配置
data=[
opts.MarkLineItem( # 平均线
type_="average", # 线类型:平均值
name="平均值", # 线名称
),
],
linestyle_opts=opts.LineStyleOpts( # 标记线样式
type_="dashed", # 虚线
color="#f39c12", # 颜色:橙色
),
),
)
.set_global_opts( # 全局配置
title_opts=opts.TitleOpts( # 标题配置
title="数据分析师岗位月度需求趋势", # 主标题
subtitle="2024年全年数据", # 副标题
pos_left="center", # 标题水平居中
),
xaxis_opts=opts.AxisOpts( # x 轴配置
name="月份", # 轴名称
axislabel_opts=opts.LabelOpts(rotate=45), # 标签旋转45度
),
yaxis_opts=opts.AxisOpts( # y 轴配置
name="岗位数量", # 轴名称
splitline_opts=opts.SplitLineOpts( # 分割线配置
is_show=True, # 显示分割线
),
),
tooltip_opts=opts.TooltipOpts( # 提示框配置
trigger="axis", # 触发方式:坐标轴
),
datazoom_opts=[ # 数据缩放配置
opts.DataZoomOpts( # 滑动条型
type_="slider", # 类型:滑动条
range_start=0, # 起始范围百分比
range_end=100, # 结束范围百分比
),
],
)
)
# 渲染输出
line.render("line_job_trend.html")
print("折线图已生成:line_job_trend.html")
2.4.2 饼图(Pie)
python
# ============================================
# 饼图案例------数据分析师岗位学历要求分布
# ============================================
from pyecharts.charts import Pie # 导入饼图类
from pyecharts import options as opts # 导入配置项
# 准备数据------学历要求及对应的岗位数量
# 数据格式:[(类别1, 值1), (类别2, 值2), ...]
edu_data = [
("本科", 4500), # 本科学历要求:4500个岗位
("硕士", 1200), # 硕士学历要求:1200个岗位
("大专", 800), # 大专学历要求:800个岗位
("博士", 150), # 博士学历要求:150个岗位
("不限", 350), # 学历不限:350个岗位
]
# 创建饼图对象
pie = (
Pie() # 实例化饼图对象
.add( # 添加数据系列
series_name="学历要求", # 系列名称
data_pair=edu_data, # 数据对(类别-值)
radius=["40%", "70%"], # 环形图半径 [内半径, 外半径]
center=["50%", "55%"], # 饼图中心位置 [水平, 垂直]
label_opts=opts.LabelOpts( # 标签配置
is_show=True, # 显示标签
formatter="{b}: {c}\n{d}%", # 标签格式:名称:值\n百分比
font_size=12, # 字体大小
),
# 每个扇区的样式
itemstyle_opts=opts.ItemStyleOpts( # 图元样式配置
border_color="#fff", # 边框颜色:白色
border_width=2, # 边框宽度:2px
),
)
.set_global_opts( # 全局配置
title_opts=opts.TitleOpts( # 标题配置
title="数据分析师岗位学历要求分布", # 主标题
pos_left="center", # 标题居中
),
legend_opts=opts.LegendOpts( # 图例配置
orient="vertical", # 图例排列方向:垂直
pos_left="left", # 图例位置:左侧
pos_top="middle", # 图例垂直居中
),
tooltip_opts=opts.TooltipOpts( # 提示框配置
trigger="item", # 触发方式:数据项
formatter="{a} <br/>{b}: {c} ({d}%)", # 提示框格式
),
)
.set_colors( # 设置自定义颜色列表
["#2ecc71", "#3498db", "#e67e22", "#e74c3c", "#9b59b6"]
)
)
# 渲染输出
pie.render("pie_edu_distribution.html")
print("饼图已生成:pie_edu_distribution.html")
2.4.3 地图(Map)
python
# ============================================
# 地图案例------全国数据分析师岗位城市分布
# ============================================
from pyecharts.charts import Map # 导入地图类
from pyecharts import options as opts # 导入配置项
# 准备数据------省份对应的岗位数量
# 注意:省份名称要与 pyecharts 内置的中国地图名称匹配
province_data = [
("北京", 1200), # 北京市:1200个岗位
("上海", 980), # 上海市:980个岗位
("广东", 1500), # 广东省(含深圳、广州):1500个岗位
("浙江", 720), # 浙江省:720个岗位
("江苏", 560), # 江苏省:560个岗位
("四川", 380), # 四川省:380个岗位
("湖北", 320), # 湖北省:320个岗位
("山东", 280), # 山东省:280个岗位
("福建", 250), # 福建省:250个岗位
("湖南", 200), # 湖南省:200个岗位
]
# 创建地图对象
map_chart = (
Map() # 实例化地图对象
.add( # 添加数据系列
series_name="岗位数量", # 系列名称
data_pair=province_data, # 数据对
maptype="china", # 地图类型:中国地图
is_map_symbol_show=True, # 是否显示地图标记点
symbol_size=15, # 标记点大小
label_opts=opts.LabelOpts( # 标签配置
is_show=True, # 显示省份名称
font_size=10, # 字体大小
),
)
.set_global_opts( # 全局配置
title_opts=opts.TitleOpts( # 标题配置
title="数据分析师岗位全国分布图", # 主标题
pos_left="center", # 标题居中
),
visualmap_opts=opts.VisualMapOpts( # 视觉映射配置(颜色渐变)
is_show=True, # 显示视觉映射组件
min_=100, # 映射最小值
max_=1500, # 映射最大值
is_piecewise=True, # 分段显示(非连续)
pieces=[ # 自定义分段
{"min": 1000, "label": "1000以上", "color": "#7f1100"}, # 深红
{"min": 500, "max": 999, "label": "500-999", "color": "#cc3311"}, # 红
{"min": 200, "max": 499, "label": "200-499", "color": "#ee6644"}, # 橙
{"min": 0, "max": 199, "label": "0-199", "color": "#ffaa55"}, # 浅橙
],
pos_left="left", # 组件位置:左侧
pos_bottom="20%", # 组件距底部20%
),
)
)
# 渲染输出
map_chart.render("map_city_distribution.html")
print("地图已生成:map_city_distribution.html")
2.4.4 组合图表(Grid 多图组合)
python
# ============================================
# Grid 组合图案例------柱状图 + 折线图组合
# ============================================
from pyecharts.charts import Bar, Line, Grid # 导入柱状图、折线图、网格组合图
from pyecharts import options as opts # 导入配置项
from pyecharts.globals import ThemeType # 导入主题
# 准备数据
cities = ["北京", "上海", "深圳", "杭州", "广州", "成都"]
avg_salary = [18000, 17500, 16800, 15500, 14000, 12500] # 平均薪资(元/月)
job_count = [1200, 980, 850, 620, 580, 380] # 岗位数量
# ---------- 创建柱状图 ----------
bar = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.MACARONS)) # 使用马卡龙主题
.add_xaxis(cities) # 添加 x 轴数据
.add_yaxis( # 添加薪资系列
"平均薪资(元/月)", # 系列名称
avg_salary, # y 轴数据
yaxis_index=0, # 使用第0个 y 轴(左侧)
label_opts=opts.LabelOpts(is_show=True, position="top"), # 标签在顶部
)
.extend_axis( # 扩展一个新的 y 轴(右侧)
yaxis=opts.AxisOpts( # 右侧 y 轴配置
name="岗位数量", # 轴名称
position="right", # 位置:右侧
axislabel_opts=opts.LabelOpts(formatter="{value}"), # 标签格式
)
)
.set_global_opts( # 全局配置
title_opts=opts.TitleOpts(title="薪资与岗位数量对比"), # 标题
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="shadow"),
yaxis_opts=opts.AxisOpts( # 左侧 y 轴
name="平均薪资(元)", # 轴名称
position="left", # 位置:左侧
),
)
)
# ---------- 创建折线图 ----------
line = (
Line() # 实例化折线图
.add_xaxis(cities) # 添加 x 轴数据
.add_yaxis( # 添加岗位数量系列
"岗位数量", # 系列名称
job_count, # y 轴数据
yaxis_index=1, # 使用第1个 y 轴(右侧)
symbol="circle", # 数据点形状
symbol_size=10, # 数据点大小
linestyle_opts=opts.LineStyleOpts(width=3, color="#e74c3c"), # 线条样式
)
)
# ---------- 使用 Grid 组合两个图表 ----------
# bar + line 两个图表绘制在同一画布中
bar.overlap(line) # 将折线图叠加到柱状图上
# 创建 Grid 布局
grid = (
Grid(init_opts=opts.InitOpts(width="1000px", height="500px")) # 创建网格
.add( # 添加组合图表
bar, # 叠加后的图表对象
grid_opts=opts.GridOpts( # 网格配置
pos_left="10%", # 左边距10%
pos_right="10%", # 右边距10%
pos_top="15%", # 上边距15%
),
)
)
# 渲染输出
grid.render("grid_bar_line_combo.html")
print("组合图已生成:grid_bar_line_combo.html")
三、数据收集
3.1 知识点概述
数据收集是数据分析的第一步,常见方式包括:
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 爬虫 | 使用 requests + BeautifulSoup / Scrapy 爬取网页数据 | 招聘网站、电商网站 |
| API | 调用平台提供的数据接口 | Twitter、微博、公开数据平台 |
| 数据库 | 从 MySQL、MongoDB 等读取数据 | 企业内部数据 |
| 文件读取 | 读取 CSV、Excel、JSON 等文件 | 已有数据文件 |
| 公开数据集 | Kaggle、UCI 等平台下载 | 学习与研究 |
3.2 使用 requests 爬取招聘数据
python
# ============================================
# 数据收集------使用 requests + BeautifulSoup 爬取招聘数据
# ============================================
import requests # 导入HTTP请求库
from bs4 import BeautifulSoup # 导入HTML解析库
import pandas as pd # 导入数据处理库
import time # 导入时间模块(用于设置请求间隔)
import random # 导入随机数模块
# ---------- 设置请求头(模拟浏览器访问) ----------
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36", # 浏览器标识
"Accept": "text/html,application/xhtml+xml", # 接受的响应类型
"Accept-Language": "zh-CN,zh;q=0.9", # 接受的语言:中文
}
# ---------- 构建请求URL ----------
# 示例:某招聘网站数据分析师岗位搜索页面
# 参数说明:query=搜索关键词, city=城市, page=页码
base_url = "https://www.example.com/jobs"
params = {
"query": "数据分析师", # 搜索关键词
"city": "全国", # 城市范围
"page": 1, # 起始页码
}
# ---------- 存储爬取结果的列表 ----------
all_jobs = [] # 存储所有岗位信息
# ---------- 循环爬取多页数据 ----------
for page in range(1, 6): # 爬取第1页到第5页
params["page"] = page # 更新页码参数
print(f"正在爬取第 {page} 页...") # 打印当前页码
try:
# 发送 GET 请求
response = requests.get( # 发送HTTP GET请求
url=base_url, # 请求URL
params=params, # URL参数
headers=headers, # 请求头
timeout=10, # 超时时间:10秒
)
# 判断请求是否成功
response.raise_for_status() # 如果状态码不是200,抛出异常
# 解析HTML内容
soup = BeautifulSoup( # 创建BeautifulSoup对象
response.text, # HTML文本内容
"html.parser" # 使用内置HTML解析器
)
# 查找所有岗位卡片(根据实际网页结构调整选择器)
job_cards = soup.find_all("div", class_="job-card") # 查找所有class为job-card的div
# 遍历每个岗位卡片,提取信息
for card in job_cards:
# 提取岗位名称
job_title = card.find("h3", class_="job-title")
title_text = job_title.get_text().strip() if job_title else "N/A"
# .get_text() 获取标签内文本, .strip() 去除首尾空白字符
# 提取公司名称
company = card.find("span", class_="company-name")
company_text = company.get_text().strip() if company else "N/A"
# 提取薪资
salary = card.find("span", class_="salary")
salary_text = salary.get_text().strip() if salary else "N/A"
# 提取工作地点
location = card.find("span", class_="location")
location_text = location.get_text().strip() if location else "N/A"
# 提取学历要求
education = card.find("span", class_="education")
edu_text = education.get_text().strip() if education else "N/A"
# 提取经验要求
experience = card.find("span", class_="experience")
exp_text = experience.get_text().strip() if experience else "N/A"
# 将提取的信息存入字典
job_info = {
"岗位名称": title_text, # 岗位名称
"公司名称": company_text, # 公司名称
"薪资": salary_text, # 薪资范围
"工作地点": location_text, # 工作地点
"学历要求": edu_text, # 学历要求
"经验要求": exp_text, # 经验要求
}
all_jobs.append(job_info) # 添加到总列表中
# 随机等待1-3秒,避免请求过于频繁
time.sleep(random.uniform(1, 3)) # 随机休眠
except requests.exceptions.RequestException as e: # 捕获请求异常
print(f"第 {page} 页爬取失败:{e}") # 打印错误信息
continue # 跳过当前页,继续下一页
# ---------- 将数据保存为CSV文件 ----------
df = pd.DataFrame(all_jobs) # 将列表转换为DataFrame
df.to_csv("data_analyst_jobs.csv", # CSV文件名
index=False, # 不保存行索引
encoding="utf-8-sig") # 编码格式(支持中文Excel打开)
print(f"共爬取 {len(all_jobs)} 条岗位数据,已保存为 data_analyst_jobs.csv")
3.3 读取本地数据文件
python
# ============================================
# 数据收集------读取本地CSV/Excel文件
# ============================================
import pandas as pd # 导入pandas库
# ---------- 读取 CSV 文件 ----------
df_csv = pd.read_csv( # 读取CSV文件
"data_analyst_jobs.csv", # 文件路径
encoding="utf-8-sig", # 文件编码格式
header=0, # 第0行作为列名(默认值)
usecols=["岗位名称", "公司名称", "薪资", # 只读取指定列
"工作地点", "学历要求"],
dtype={ # 指定列的数据类型
"岗位名称": str, # 字符串类型
"薪资": str, # 字符串类型(后续需转换)
},
)
# ---------- 读取 Excel 文件 ----------
df_excel = pd.read_excel( # 读取Excel文件
"data_analyst_jobs.xlsx", # 文件路径
sheet_name="Sheet1", # 读取的工作表名称
header=0, # 第0行作为列名
engine="openpyxl", # 使用openpyxl引擎(支持.xlsx)
)
# ---------- 查看数据基本信息 ----------
print("数据形状(行数, 列数):", df_csv.shape) # 输出 (行数, 列数)
print("\n前5行数据预览:") # 分隔提示
print(df_csv.head()) # 查看前5行
print("\n数据类型:") # 分隔提示
print(df_csv.dtypes) # 查看每列数据类型
print("\n基本统计信息:") # 分隔提示
print(df_csv.describe()) # 查看数值列的统计摘要
print("\n缺失值统计:") # 分隔提示
print(df_csv.isnull().sum()) # 查看每列缺失值数量
四、数据预处理
4.1 知识点概述
数据预处理是将原始数据转换为可分析形式的关键步骤,主要包括:
| 步骤 | 说明 |
|---|---|
| 缺失值处理 | 删除或填充缺失数据 |
| 重复值处理 | 删除重复记录 |
| 数据类型转换 | 字符串转数值、日期格式转换等 |
| 异常值处理 | 检测并处理不合理的数据 |
| 数据标准化 | 统一格式(如薪资统一为月薪) |
| 特征工程 | 从现有数据中提取新特征 |
4.2 缺失值处理
python
# ============================================
# 数据预处理------缺失值处理
# ============================================
import pandas as pd # 导入pandas库
import numpy as np # 导入numpy库
# ---------- 创建包含缺失值的示例数据 ----------
data = {
"岗位名称": ["数据分析师", "数据分析师", "数据分析师", "BI分析师", "数据分析师"],
"薪资": ["15-25K", None, "20-30K", "18-28K", None],
"工作地点": ["北京", "上海", None, "深圳", "杭州"],
"学历要求": ["本科", "本科", "硕士", None, "大专"],
"经验要求": ["3-5年", "1-3年", None, "3-5年", "1-3年"],
}
df = pd.DataFrame(data) # 创建DataFrame
print("原始数据:")
print(df) # 打印原始数据
print(f"\n缺失值统计:\n{df.isnull().sum()}") # 统计缺失值
# ----- 方法1:删除缺失值 -----
# 删除包含任意缺失值的行
df_drop_rows = df.dropna( # 删除缺失值
axis=0, # 按行删除(默认值)
how="any", # 只要该行有任意一个缺失值就删除
subset=["薪资", "工作地点"], # 只检查指定列的缺失值
)
print("\n方法1-删除含缺失值的行:")
print(df_drop_rows)
# ----- 方法2:用固定值填充缺失值 -----
df_fill_fixed = df.copy() # 创建副本,避免修改原始数据
df_fill_fixed["薪资"] = df_fill_fixed["薪资"].fillna("面议") # 薪资缺失用"面议"填充
df_fill_fixed["工作地点"] = df_fill_fixed["工作地点"].fillna("未知") # 地点缺失用"未知"填充
print("\n方法2-固定值填充:")
print(df_fill_fixed)
# ----- 方法3:用众数填充缺失值 -----
df_fill_mode = df.copy() # 创建副本
# 获取学历要求列的众数(出现次数最多的值)
edu_mode = df_fill_mode["学历要求"].mode()[0] # mode()返回Series,取第一个
df_fill_mode["学历要求"] = df_fill_mode["学历要求"].fillna(edu_mode) # 用众数填充
print(f"\n方法3-众数填充(众数为'{edu_mode}'):")
print(df_fill_mode)
# ----- 方法4:用前一个有效值填充(前向填充) -----
df_ffill = df.copy() # 创建副本
df_ffill["经验要求"] = df_ffill["经验要求"].ffill() # 用前一个非缺失值填充
print("\n方法4-前向填充:")
print(df_ffill)
# ----- 方法5:插值法填充(适用于数值型数据) -----
# 示例:对数值型数据进行线性插值
numeric_data = pd.Series([10, np.nan, np.nan, 40, 50]) # 包含缺失值的数值序列
interpolated = numeric_data.interpolate(method="linear") # 线性插值
print(f"\n方法5-线性插值:{list(interpolated)}")
# 输出:[10.0, 20.0, 30.0, 40.0, 50.0](均匀插值)
4.3 重复值处理
python
# ============================================
# 数据预处理------重复值处理
# ============================================
import pandas as pd # 导入pandas库
# ---------- 创建包含重复值的示例数据 ----------
data = {
"岗位名称": ["数据分析师", "数据分析师", "算法工程师", "数据分析师", "BI分析师"],
"公司名称": ["A公司", "A公司", "B公司", "C公司", "D公司"],
"薪资": ["15-25K", "15-25K", "25-40K", "20-30K", "18-28K"],
"工作地点": ["北京", "北京", "上海", "深圳", "广州"],
}
df = pd.DataFrame(data) # 创建DataFrame
print("原始数据:")
print(df)
# ----- 检测重复值 -----
# 查看是否有完全重复的行
duplicated_mask = df.duplicated( # 检测重复行
subset=["岗位名称", "公司名称"], # 根据指定列判断是否重复
keep="first", # 保留第一次出现的行,标记后续重复行为True
)
print(f"\n重复行标记:\n{duplicated_mask}") # 打印布尔标记
# ----- 删除重复值 -----
df_deduped = df.drop_duplicates( # 删除重复行
subset=["岗位名称", "公司名称"], # 根据指定列判断重复
keep="first", # 保留第一次出现的记录
ignore_index=True, # 重置行索引
inplace=False, # 不修改原DataFrame,返回新对象
)
print(f"\n去重后数据({len(df_deduped)}条):")
print(df_deduped)
# ----- 统计重复情况 -----
dup_count = df.duplicated( # 检测重复行
subset=["岗位名称", "公司名称"],
keep=False, # 标记所有重复行(包括首次出现的)
).sum() # 对True求和,得到重复行总数
print(f"\n重复记录总数:{dup_count} 条")
4.4 薪资数据清洗与转换(核心预处理)
python
# ============================================
# 数据预处理------薪资字段清洗与数值转换
# (这是本章实战中最核心的预处理步骤)
# ============================================
import pandas as pd # 导入pandas库
import re # 导入正则表达式模块
# ---------- 创建包含各种薪资格式的示例数据 ----------
data = {
"岗位名称": ["数据分析师", "数据分析师", "数据分析师", "数据分析师", "数据分析师", "数据分析师"],
"薪资": [
"15-25K", # 格式1:标准格式 15K-25K
"20K-30K", # 格式2:带大写K
"1.5万-2.5万/月", # 格式3:万元/月格式
"18-25K·14薪", # 格式4:含薪数(14个月工资)
"15000-25000", # 格式5:纯数字格式(元/月)
"面议", # 格式6:面议
],
"工作地点": ["北京", "上海", "深圳", "杭州", "广州", "成都"],
}
df = pd.DataFrame(data) # 创建DataFrame
print("原始薪资数据:")
print(df["薪资"]) # 打印薪资列
# ---------- 定义薪资解析函数 ----------
def parse_salary(salary_str):
"""
解析各种格式的薪资字符串,返回统一的最低薪资和最高薪资(单位:千元/月)
参数:
salary_str (str): 原始薪资字符串
返回:
tuple: (最低薪资K, 最高薪资K),无法解析返回 (None, None)
"""
# 如果薪资为空或"面议",返回空值
if pd.isna(salary_str) or salary_str == "面议": # 判断空值或面议
return (None, None) # 返回两个空值
salary_str = str(salary_str).strip() # 转为字符串并去除空白
# ----- 情况1:处理"XX-XXK"或"XXK-XXK"格式 -----
# 正则表达式:匹配 "数字-数字K" 或 "数字K-数字K" 格式
pattern_k = re.compile(r'(\d+\.?\d*)\s*[Kk]\s*[-~]\s*(\d+\.?\d*)\s*[Kk]?')
match_k = pattern_k.search(salary_str) # 搜索匹配
if match_k: # 如果匹配成功
low = float(match_k.group(1)) # 提取最低薪资(已经是K)
high = float(match_k.group(2)) # 提取最高薪资
return (low, high) # 返回结果
# ----- 情况2:处理"X.X万-X.X万/月"格式 -----
pattern_wan = re.compile(r'(\d+\.?\d*)\s*万?\s*[-~]\s*(\d+\.?\d*)\s*万')
match_wan = pattern_wan.search(salary_str) # 搜索匹配
if match_wan: # 如果匹配成功
low = float(match_wan.group(1)) * 10 # 万转K(1万 = 10K)
high = float(match_wan.group(2)) * 10 # 万转K
return (low, high) # 返回结果
# ----- 情况3:处理纯数字格式 "15000-25000" -----
pattern_num = re.compile(r'(\d+)\s*[-~]\s*(\d+)')
match_num = pattern_num.search(salary_str) # 搜索匹配
if match_num: # 如果匹配成功
low = float(match_num.group(1)) / 1000 # 元转K(除以1000)
high = float(match_num.group(2)) / 1000 # 元转K
return (low, high) # 返回结果
# ----- 无法解析的情况 -----
return (None, None) # 返回空值
# ---------- 应用薪资解析函数 ----------
# apply() 对薪资列的每个元素应用 parse_salary 函数
salary_parsed = df["薪资"].apply(parse_salary) # 解析每条薪资数据
# 将解析结果拆分为两列
df["最低薪资(K)"] = salary_parsed.apply(lambda x: x[0]) # 提取最低薪资
df["最高薪资(K)"] = salary_parsed.apply(lambda x: x[1]) # 提取最高薪资
# 计算平均薪资
df["平均薪资(K)"] = ( # 计算平均值
df["最低薪资(K)"] + df["最高薪资(K)"] # 最低 + 最高
) / 2 # 除以2
# ---------- 处理含薪数的情况(如14薪) ----------
# 提取薪资月数
def extract_months(salary_str):
"""
从薪资字符串中提取薪资月数(如14薪、13薪)
默认为12薪
"""
if pd.isna(salary_str): # 判断空值
return 12 # 默认12个月
pattern = re.compile(r'(\d+)\s*薪') # 匹配"X薪"格式
match = pattern.search(str(salary_str)) # 搜索
return int(match.group(1)) if match else 12 # 返回匹配结果或默认12
df["薪资月数"] = df["薪资"].apply(extract_months) # 提取薪资月数
# 计算年总薪资(万元)
df["年薪(万)"] = df["平均薪资(K)"] * df["薪资月数"] / 10 # K * 月数 / 10 = 万元
print("\n薪资解析结果:")
print(df[["岗位名称", "薪资", "最低薪资(K)", "最高薪资(K)", "平均薪资(K)", "薪资月数", "年薪(万)"]])
4.5 城市数据清洗
python
# ============================================
# 数据预处理------城市名称标准化清洗
# ============================================
import pandas as pd # 导入pandas库
# ---------- 创建包含不规范城市名称的数据 ----------
data = {
"工作地点": [
"北京", "北京市", "北京朝阳区", "北京-朝阳",
"上海", "上海市", "上海浦东新区",
"深圳", "深圳市", "深圳南山区",
"杭州", "杭州市",
"广州", "广州市",
"成都", "成都市",
],
"薪资": ["15K"] * 14, # 简化的薪资数据
}
df = pd.DataFrame(data)
# ---------- 方法1:使用字符串截取 ----------
# 取城市名称的前2个字符(适用于双字城市名)
df["城市_截取"] = df["工作地点"].str[:2] # 截取前2个字符
print("方法1-字符串截取:")
print(df[["工作地点", "城市_截取"]].head(8))
# ---------- 方法2:使用正则表达式提取 ----------
# 提取中文城市名称(2-4个中文字符)
df["城市_正则"] = df["工作地点"].str.extract(r'([\u4e00-\u9fa5]{2,4})')
# [\u4e00-\u9fa5] 是中文字符的Unicode范围
# {2,4} 匹配2到4个中文字符
print("\n方法2-正则表达式提取:")
print(df[["工作地点", "城市_正则"]].head(8))
# ---------- 方法3:使用映射字典标准化 ----------
# 定义城市名称映射字典(原始名称 -> 标准名称)
city_mapping = {
"北京": "北京", "北京市": "北京", "北京朝阳区": "北京", "北京-朝阳": "北京",
"上海": "上海", "上海市": "上海", "上海浦东新区": "上海",
"深圳": "深圳", "深圳市": "深圳", "深圳南山区": "深圳",
"杭州": "杭州", "杭州市": "杭州",
"广州": "广州", "广州市": "广州",
"成都": "成都", "成都市": "成都",
}
df["城市_映射"] = df["工作地点"].map(city_mapping) # 使用map方法进行映射
print("\n方法3-字典映射标准化:")
print(df[["工作地点", "城市_映射"]].head(8))
# ---------- 方法4:使用 replace 方法批量替换 ----------
df["城市_replace"] = df["工作地点"].replace( # 使用replace方法
to_replace=r'(市|区|新区|-.*)', # 正则匹配需要替换的内容
value="", # 替换为空字符串
regex=True, # 启用正则表达式模式
)
print("\n方法4-replace批量替换:")
print(df[["工作地点", "城市_replace"]].head(8))
4.6 数据类型转换
python
# ============================================
# 数据预处理------数据类型转换
# ============================================
import pandas as pd # 导入pandas库
# ---------- 创建示例数据 ----------
data = {
"岗位名称": ["数据分析师", "数据分析师", "数据分析师"],
"薪资": ["15-25K", "20-30K", "18-28K"],
"发布日期": ["2024-03-15", "2024-03-16", "2024-03-17"],
"浏览量": ["1200", "850", "620"], # 字符串类型的数字
"是否急聘": ["是", "否", "是"],
}
df = pd.DataFrame(data) # 创建DataFrame
print("转换前的数据类型:")
print(df.dtypes) # 打印原始数据类型
# ----- 1. 字符串转数值型 -----
# 使用 astype() 转换
df["浏览量_int"] = df["浏览量"].astype(int) # 字符串转整数
# 或使用 pd.to_numeric() 更安全(可处理异常值)
df["浏览量_numeric"] = pd.to_numeric( # 更安全的数值转换
df["浏览量"], # 要转换的列
errors="coerce", # 无法转换的设为NaN
)
# ----- 2. 字符串转日期型 -----
df["发布日期_dt"] = pd.to_datetime( # 字符串转日期
df["发布日期"], # 要转换的列
format="%Y-%m-%d", # 日期格式
)
# 从日期中提取年、月、日、星期
df["发布年"] = df["发布日期_dt"].dt.year # 提取年份
df["发布月"] = df["发布日期_dt"].dt.month # 提取月份
df["发布日"] = df["发布日期_dt"].dt.day # 提取日
df["星期"] = df["发布日期_dt"].dt.day_name() # 提取星期名称
# ----- 3. 字符串转分类类型 -----
df["是否急聘_cat"] = df["是否急聘"].astype("category") # 转为分类类型
# 分类类型的优势:占用内存更小,支持排序
# ----- 4. 使用 map 进行编码转换 -----
# 将"是/否"映射为 1/0
df["是否急聘_num"] = df["是否急聘"].map({"是": 1, "否": 0}) # 映射为数值
print("\n转换后的数据:")
print(df)
print("\n转换后的数据类型:")
print(df.dtypes)
五、数据分析与展现
5.1 分析展现数据分析师岗位的需求趋势
python
# ============================================
# 分析目标1:数据分析师岗位的需求趋势
# 使用折线图展现月度/季度岗位数量变化趋势
# ============================================
import pandas as pd # 导入pandas库
from pyecharts.charts import Line # 导入折线图
from pyecharts import options as opts # 导入配置项
from pyecharts.globals import ThemeType # 导入主题类型
# ---------- 加载数据(模拟已预处理的数据) ----------
# 假设数据已预处理完毕,包含"发布日期"列
# 使用示例数据
data = {
"发布日期": pd.date_range( # 生成日期序列
start="2023-01-01", # 起始日期
periods=24, # 生成24个数据点
freq="M" # 频率:月末
),
"岗位数量": [
2800, 2600, 3500, 3800, 4200, 4600, # 2023年上半年
4400, 4100, 5000, 5300, 4800, 5500, # 2023年下半年
3200, 2900, 4100, 4500, 4800, 5200, # 2024年上半年
5000, 4600, 5800, 6100, 5500, 6800, # 2024年下半年
],
}
df = pd.DataFrame(data) # 创建DataFrame
# ---------- 数据聚合:按月份统计岗位数量 -----
# 将日期转换为月份字符串格式
df["月份"] = df["发布日期"].dt.strftime("%Y-%m") # 格式化为"年-月"字符串
# 如果有重复月份,使用 groupby 聚合
monthly_data = df.groupby("月份")["岗位数量"].sum().reset_index()
# groupby("月份"):按月份分组
# ["岗位数量"].sum():对每组的岗位数量求和
# reset_index():将分组索引还原为普通列
# ---------- 提取绘图数据 ----------
x_months = monthly_data["月份"].tolist() # 月份列表(x轴)
y_counts = monthly_data["岗位数量"].tolist() # 岗位数量列表(y轴)
# ---------- 计算同比增长率 ----------
# pct_change() 计算相邻两个数据点的变化率
growth_rate = monthly_data["岗位数量"].pct_change() * 100 # 百分比增长率
growth_rate = growth_rate.round(1).fillna(0).tolist() # 保留1位小数,空值填0
# ---------- 绘制折线图 ----------
line = (
Line(init_opts=opts.InitOpts( # 初始化配置
theme=ThemeType.VINTAGE, # 复古主题
width="1200px", # 图表宽度
height="500px", # 图表高度
page_title="数据分析师需求趋势分析", # 浏览器标签页标题
))
.add_xaxis(x_months) # 添加x轴数据(月份)
.add_yaxis( # 添加岗位数量系列
series_name="岗位数量", # 系列名称
y_axis=y_counts, # y轴数据
is_smooth=True, # 平滑曲线
symbol="circle", # 数据点形状
symbol_size=6, # 数据点大小
linestyle_opts=opts.LineStyleOpts( # 线条样式
width=3, # 线宽
color="#5470c6", # 线条颜色
),
itemstyle_opts=opts.ItemStyleOpts( # 数据点样式
color="#5470c6", # 点颜色
border_color="#fff", # 点边框颜色
border_width=2, # 点边框宽度
),
label_opts=opts.LabelOpts( # 标签配置
is_show=False, # 不显示标签(数据点多时避免拥挤)
),
# 标记最大值和最小值
markpoint_opts=opts.MarkPointOpts( # 标记点配置
data=[
opts.MarkPointItem(type_="max", name="最大值"), # 标记最大值
opts.MarkPointItem(type_="min", name="最小值"), # 标记最小值
],
),
# 添加平均线
markline_opts=opts.MarkLineOpts( # 标记线配置
data=[opts.MarkLineItem(type_="average", name="平均值")],
),
# 区域填充(面积图效果)
areastyle_opts=opts.AreaStyleOpts( # 区域填充配置
opacity=0.15, # 透明度
),
)
.add_yaxis( # 添加增长率系列(第二条线)
series_name="环比增长率(%)", # 系列名称
y_axis=growth_rate, # y轴数据
is_smooth=True, # 平滑曲线
symbol="diamond", # 数据点形状:菱形
symbol_size=8, # 数据点大小
linestyle_opts=opts.LineStyleOpts( # 线条样式
width=2, # 线宽
color="#91cc75", # 线条颜色:绿色
type_="dashed", # 虚线样式
),
yaxis_index=1, # 使用第二个y轴(右侧)
)
.extend_axis( # 扩展第二个y轴
yaxis=opts.AxisOpts( # 右侧y轴配置
name="增长率(%)", # 轴名称
position="right", # 位置:右侧
axislabel_opts=opts.LabelOpts( # 轴标签配置
formatter="{value}%" # 格式:数值后加%
),
splitline_opts=opts.SplitLineOpts(is_show=False), # 不显示分割线
)
)
.set_global_opts( # 全局配置
title_opts=opts.TitleOpts( # 标题配置
title="数据分析师岗位需求趋势分析", # 主标题
subtitle="2023年1月 - 2024年12月", # 副标题
pos_left="center", # 居中
),
tooltip_opts=opts.TooltipOpts( # 提示框配置
trigger="axis", # 坐标轴触发
),
legend_opts=opts.LegendOpts( # 图例配置
pos_top="8%", # 距顶部8%
),
xaxis_opts=opts.AxisOpts( # x轴配置
name="月份", # 轴名称
axislabel_opts=opts.LabelOpts(rotate=45), # 标签旋转45度
),
yaxis_opts=opts.AxisOpts( # 左侧y轴配置
name="岗位数量", # 轴名称
splitline_opts=opts.SplitLineOpts(is_show=True), # 显示分割线
),
datazoom_opts=[ # 数据缩放配置
opts.DataZoomOpts(type_="slider"), # 滑动条型缩放
],
)
)
# 渲染输出
line.render("01_job_demand_trend.html")
print("需求趋势图已生成:01_job_demand_trend.html")
5.2 分析展现数据分析师岗位的热门城市 Top10
python
# ============================================
# 分析目标2:数据分析师岗位热门城市Top10
# 使用柱状图+标签展现排名
# ============================================
import pandas as pd # 导入pandas库
from pyecharts.charts import Bar, Pie, Page # 导入柱状图、饼图、页面组合
from pyecharts import options as opts # 导入配置项
from pyecharts.globals import ThemeType # 导入主题类型
# ---------- 模拟预处理后的数据 ----------
# 假设已对"工作地点"列进行了城市标准化处理
data = {
"工作地点": (["北京"] * 1200 + ["上海"] * 980 + # 北京1200条、上海980条
["深圳"] * 850 + ["杭州"] * 620 + # 深圳850条、杭州620条
["广州"] * 580 + ["成都"] * 380 + # 广州580条、成都380条
["南京"] * 320 + ["武汉"] * 300 + # 南京320条、武汉300条
["西安"] * 260 + ["长沙"] * 220 + # 西安260条、长沙220条
["重庆"] * 180 + ["苏州"] * 150), # 重庆180条、苏州150条
}
df = pd.DataFrame(data) # 创建DataFrame
# ---------- 数据聚合:统计各城市岗位数量 ----------
city_counts = ( # 统计各城市出现次数
df["工作地点"] # 选取城市列
.value_counts() # 统计每个城市的出现次数(自动降序排列)
.head(10) # 取前10名
.reset_index() # 重置索引,将原索引变成列
)
city_counts.columns = ["城市", "岗位数量"] # 重命名列名
# 提取绘图数据
x_cities = city_counts["城市"].tolist() # 城市列表
y_job_counts = city_counts["岗位数量"].tolist() # 岗位数量列表
# 计算占比
total = sum(y_job_counts) # 总数
percentages = [round(c / total * 100, 1) for c in y_job_counts] # 百分比列表
# ---------- 绘制柱状图 ----------
bar = (
Bar(init_opts=opts.InitOpts( # 初始化配置
theme=ThemeType.CHALK, # 粉笔主题
width="1000px",
height="500px",
))
.add_xaxis(x_cities) # 添加x轴数据(城市)
.add_yaxis( # 添加y轴数据系列
series_name="岗位数量", # 系列名称
y_axis=y_job_counts, # y轴数据
category_gap="40%", # 柱体间距(百分比)
label_opts=opts.LabelOpts( # 标签配置
is_show=True, # 显示标签
position="top", # 标签在柱体顶部
formatter="{c}", # 标签格式:只显示数值
font_size=12, # 字体大小
),
# 设置每根柱体的渐变颜色
itemstyle_opts=opts.ItemStyleOpts( # 图元样式配置
color=opts.JsCode( # 使用JavaScript代码设置渐变色
"new echarts.graphic.LinearGradient(0, 0, 0, 1, " # 从上到下线性渐变
"[{offset: 0, color: '#e74c3c'}, " # 顶部颜色:红色
"{offset: 1, color: '#f39c12'}])" # 底部颜色:橙色
),
),
)
.reversal_axis() # 翻转坐标轴(水平柱状图)
.set_global_opts( # 全局配置
title_opts=opts.TitleOpts( # 标题
title="数据分析师岗位热门城市 Top 10", # 主标题
subtitle="基于招聘平台数据分析", # 副标题
),
xaxis_opts=opts.AxisOpts( # x轴(翻转后变成纵向)
name="岗位数量", # 轴名称
),
yaxis_opts=opts.AxisOpts( # y轴(翻转后变成横向)
axislabel_opts=opts.LabelOpts( # 标签配置
font_size=12, # 字体大小
),
),
tooltip_opts=opts.TooltipOpts( # 提示框
trigger="axis", # 触发方式
axis_pointer_type="shadow", # 阴影指示器
),
visualmap_opts=opts.VisualMapOpts( # 视觉映射
is_show=False, # 隐藏视觉映射组件
min_=min(y_job_counts), # 映射最小值
max_=max(y_job_counts), # 映射最大值
dimension=0, # 映射维度(0=第一个数据维度)
range_color=["#f39c12", "#e74c3c"], # 颜色范围:橙色到红色
),
)
.set_series_opts( # 系列配置
markline_opts=opts.MarkLineOpts( # 标记线
data=[opts.MarkLineItem(type_="average", name="平均值")],
),
)
)
# 渲染输出
bar.render("02_top10_cities_bar.html")
print("热门城市柱状图已生成:02_top10_cities_bar.html")
# ---------- 同时绘制饼图(辅助展示占比) ----------
# 构建饼图数据
pie_data = list(zip(x_cities, y_job_counts)) # 将城市和数量配对
pie = (
Pie(init_opts=opts.InitOpts(width="800px", height="500px"))
.add(
series_name="岗位占比",
data_pair=pie_data, # 饼图数据
radius=["30%", "65%"], # 环形图
rosetype="area", # 玫瑰图模式(面积模式:半径表示数值)
label_opts=opts.LabelOpts(
formatter="{b}: {d}%", # 标签格式:城市名:百分比
),
)
.set_global_opts(
title_opts=opts.TitleOpts(
title="Top10城市岗位占比",
pos_left="center",
),
legend_opts=opts.LegendOpts(
orient="vertical", # 垂直排列图例
pos_right="2%", # 右侧
pos_top="15%", # 顶部偏下
),
)
)
pie.render("02_top10_cities_pie.html")
print("热门城市饼图已生成:02_top10_cities_pie.html")
5.3 分析展现不同城市数据分析师岗位的薪资水平
python
# ============================================
# 分析目标3:不同城市数据分析师岗位的薪资水平
# 使用箱线图/柱状图/地图综合展现薪资分布
# ============================================
import pandas as pd # 导入pandas库
import numpy as np # 导入numpy库
import random # 导入随机数模块
from pyecharts.charts import Bar, Map, Boxplot, Page # 导入多种图表
from pyecharts import options as opts # 导入配置项
from pyecharts.globals import ThemeType # 导入主题类型
# ---------- 模拟各城市薪资数据 ----------
# 设置随机种子,保证结果可复现
random.seed(42) # Python随机种子
np.random.seed(42) # numpy随机种子
# 定义各城市的薪资参数:(城市名, 平均薪资K, 标准差, 数据条数)
city_salary_params = [
("北京", 20, 5, 200), # 北京:均值20K,标准差5,200条数据
("上海", 19, 4.5, 180), # 上海:均值19K,标准差4.5
("深圳", 18, 4, 160), # 深圳:均值18K,标准差4
("杭州", 16, 4, 120), # 杭州:均值16K,标准差4
("广州", 15, 3.5, 110), # 广州:均值15K,标准差3.5
("成都", 13, 3, 80), # 成都:均值13K,标准差3
("南京", 14, 3.5, 70), # 南京:均值14K,标准差3.5
("武汉", 13, 3, 65), # 武汉:均值13K,标准差3
("西安", 12, 2.5, 55), # 西安:均值12K,标准差2.5
("长沙", 11, 2.5, 50), # 长沙:均值11K,标准差2.5
]
# 生成模拟数据
all_data = [] # 存储所有数据
for city, mean, std, count in city_salary_params: # 遍历每个城市
# 使用正态分布生成薪资数据,并取整
salaries = np.random.normal(mean, std, count) # 正态分布生成
salaries = np.clip(salaries, 5, 50) # 裁剪到5K-50K范围
salaries = np.round(salaries, 1) # 保留1位小数
for s in salaries: # 遍历每个薪资值
all_data.append({"城市": city, "薪资(K)": s}) # 添加到列表
df = pd.DataFrame(all_data) # 创建DataFrame
# ---------- 计算各城市薪资统计信息 ----------
salary_stats = df.groupby("城市")["薪资(K)"].agg([ # 分组聚合
"mean", # 平均值
"median", # 中位数
"min", # 最小值
"max", # 最大值
"std", # 标准差
"count", # 数据条数
]).round(1) # 保留1位小数
# 按平均薪资降序排列
salary_stats = salary_stats.sort_values("mean", ascending=False) # 降序
salary_stats.columns = ["平均薪资", "中位数薪资", "最低薪资", # 重命名列
"最高薪资", "标准差", "样本量"]
print("各城市薪资统计:")
print(salary_stats)
# ---------- 绘制柱状图:各城市平均薪资对比 ----------
x_cities = salary_stats.index.tolist() # 城市列表
y_avg_salary = salary_stats["平均薪资"].tolist() # 平均薪资列表
y_median_salary = salary_stats["中位数薪资"].tolist() # 中位数薪资列表
bar = (
Bar(init_opts=opts.InitOpts( # 初始化
theme=ThemeType.WESTEROS, # 权游主题
width="1000px",
height="500px",
))
.add_xaxis(x_cities) # 添加x轴数据
.add_yaxis( # 平均薪资系列
"平均薪资(K)", # 系列名称
y_avg_salary, # 数据
gap="20%", # 同一类目柱体间距
label_opts=opts.LabelOpts(position="top"), # 标签在顶部
itemstyle_opts=opts.ItemStyleOpts( # 样式
color=opts.JsCode( # 渐变色
"new echarts.graphic.LinearGradient(0, 0, 0, 1, "
"[{offset: 0, color: '#667eea'}, "
"{offset: 1, color: '#764ba2'}])"
),
),
)
.add_yaxis( # 中位数薪资系列
"中位数薪资(K)", # 系列名称
y_median_salary, # 数据
gap="20%", # 间距
label_opts=opts.LabelOpts(position="top"), # 标签在顶部
itemstyle_opts=opts.ItemStyleOpts( # 样式
color=opts.JsCode(
"new echarts.graphic.LinearGradient(0, 0, 0, 1, "
"[{offset: 0, color: '#f093fb'}, "
"{offset: 1, color: '#f5576c'}])"
),
),
)
.set_global_opts(
title_opts=opts.TitleOpts( # 标题
title="不同城市数据分析师薪资对比", # 主标题
subtitle="平均薪资 vs 中位数薪资", # 副标题
),
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="shadow"),
legend_opts=opts.LegendOpts(pos_top="8%"),
xaxis_opts=opts.AxisOpts(
axislabel_opts=opts.LabelOpts(rotate=30), # 标签旋转
),
yaxis_opts=opts.AxisOpts(name="薪资(K/月)"),
)
)
bar.render("03_city_salary_bar.html")
print("\n薪资对比柱状图已生成:03_city_salary_bar.html")
# ---------- 绘制地图:全国薪资热力图 ----------
# 准备地图数据(省份-平均薪资)
map_data = [
("北京", 20.0), ("上海", 19.0), ("广东", 18.0), # 一线城市
("浙江", 16.0), ("江苏", 14.0), ("四川", 13.0), # 新一线/二线城市
("湖北", 13.0), ("陕西", 12.0), ("湖南", 11.0),
("山东", 12.5), ("福建", 13.5), ("重庆", 12.0),
]
map_chart = (
Map(init_opts=opts.InitOpts(width="900px", height="500px"))
.add(
series_name="平均薪资(K)",
data_pair=map_data,
maptype="china",
is_map_symbol_show=True,
symbol_size=12,
)
.set_global_opts(
title_opts=opts.TitleOpts(
title="数据分析师薪资全国热力图",
pos_left="center",
),
visualmap_opts=opts.VisualMapOpts(
is_show=True,
min_=10, # 最小值
max_=22, # 最大值
is_piecewise=True, # 分段显示
pieces=[
{"min": 18, "label": "18K以上(高薪)", "color": "#7f1100"},
{"min": 15, "max": 17.9, "label": "15-18K", "color": "#cc3311"},
{"min": 12, "max": 14.9, "label": "12-15K", "color": "#ee6644"},
{"min": 0, "max": 11.9, "label": "12K以下", "color": "#ffaa55"},
],
),
)
)
map_chart.render("03_salary_map.html")
print("薪资热力地图已生成:03_salary_map.html")
5.4 分析展现数据分析师岗位的学历要求
python
# ============================================
# 分析目标4:数据分析师岗位的学历要求分析
# 使用饼图、柱状图、环形图等多种图表展现
# ============================================
import pandas as pd # 导入pandas库
from pyecharts.charts import Pie, Bar, Page, Liquid, WordCloud # 导入多种图表
from pyecharts import options as opts # 导入配置项
from pyecharts.globals import ThemeType # 导入主题
# ---------- 模拟学历要求数据 ----------
data = {
"学历要求": (["本科"] * 4500 + ["硕士"] * 1200 + # 本科4500条、硕士1200条
["大专"] * 800 + ["博士"] * 150 + # 大专800条、博士150条
["不限"] * 350), # 不限350条
"城市": (["北京"] * 800 + ["上海"] * 700 + # 各城市的分布
["深圳"] * 600 + ["杭州"] * 500 +
["广州"] * 400 + ["成都"] * 300 +
["南京"] * 250 + ["武汉"] * 250 +
["西安"] * 200 + ["长沙"] * 200 +
["北京"] * 500 + ["上海"] * 400 +
["深圳"] * 350 + ["杭州"] * 300 +
["广州"] * 250 + ["成都"] * 200 +
["南京"] * 150 + ["武汉"] * 150 +
["西安"] * 100 + ["长沙"] * 100),
# 注意:这里的city分布与edu对应仅做演示,实际应一一对应
}
df = pd.DataFrame(data)
# ---------- 1. 学历要求总体分布(环形饼图) ----------
edu_counts = df["学历要求"].value_counts() # 统计各学历出现次数
edu_data = list(zip(edu_counts.index.tolist(), # 学历名称列表
edu_counts.values.tolist())) # 数量列表
# 自定义颜色
colors = ["#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de"]
pie = (
Pie(init_opts=opts.InitOpts( # 初始化
theme=ThemeType.MACARONS, # 马卡龙主题
width="800px",
height="500px",
))
.add( # 添加数据
series_name="学历要求", # 系列名称
data_pair=edu_data, # 数据
radius=["35%", "65%"], # 环形图半径(内35%,外65%)
center=["50%", "55%"], # 中心位置
# 设置标签引导线
label_opts=opts.LabelOpts( # 标签配置
is_show=True, # 显示标签
formatter="{b}\n{c}个岗位\n占比{d}%", # 格式:名称\n数量\n百分比
font_size=11, # 字体大小
),
# 引导线配置
label_linelength=20, # 引导线第一段长度
label_linelength2=30, # 引导线第二段长度
itemstyle_opts=opts.ItemStyleOpts( # 图元样式
border_color="#fff", # 边框白色
border_width=2, # 边框宽度
),
)
.set_colors(colors) # 设置颜色列表
.set_global_opts(
title_opts=opts.TitleOpts( # 标题
title="数据分析师岗位学历要求分布", # 主标题
subtitle="占比分析", # 副标题
pos_left="center",
),
legend_opts=opts.LegendOpts( # 图例
orient="vertical", # 垂直排列
pos_left="left",
pos_top="middle",
),
tooltip_opts=opts.TooltipOpts( # 提示框
trigger="item",
formatter="{a} <br/>{b}: {c} ({d}%)", # 格式
),
)
)
pie.render("04_edu_pie.html")
print("学历要求饼图已生成:04_edu_pie.html")
# ---------- 2. 各城市学历要求交叉分析(分组柱状图) ----------
# 创建城市-学历交叉表
cross_table = pd.crosstab( # 创建交叉表
df["城市"], # 行:城市
df["学历要求"], # 列:学历
)
# 按本科学历数量降序排列
cross_table = cross_table.sort_values("本科", ascending=False)
# 提取数据
cities = cross_table.index.tolist() # 城市列表
edu_levels = ["本科", "硕士", "大专", "博士", "不限"] # 学历级别列表
# 创建分组柱状图
bar = (
Bar(init_opts=opts.InitOpts( # 初始化
theme=ThemeType.ROMA, # 罗马主题
width="1100px",
height="500px",
))
.add_xaxis(cities) # 添加x轴数据(城市)
)
# 为每个学历级别添加一个系列
for i, edu in enumerate(edu_levels): # 遍历每个学历
if edu in cross_table.columns: # 如果该学历存在
bar.add_yaxis( # 添加系列
series_name=edu, # 系列名:学历名称
y_axis=cross_table[edu].tolist(), # 数据
gap="10%", # 柱体间距
label_opts=opts.LabelOpts(is_show=False), # 不显示标签(避免拥挤)
)
bar.set_global_opts( # 全局配置
title_opts=opts.TitleOpts(
title="各城市数据分析师岗位学历要求分布",
subtitle="分组对比",
),
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="shadow"),
legend_opts=opts.LegendOpts(pos_top="8%"),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=30)),
yaxis_opts=opts.AxisOpts(name="岗位数量"),
datazoom_opts=[opts.DataZoomOpts(type_="slider")],
)
bar.render("04_edu_city_bar.html")
print("城市学历分组柱状图已生成:04_edu_city_bar.html")
# ---------- 3. 本科占比水球图 ----------
# 计算本科学历占比
total_jobs = len(df) # 总岗位数
bachelor_count = (df["学历要求"] == "本科").sum() # 本科岗位数
bachelor_ratio = round(bachelor_count / total_jobs, 2) # 计算占比(保留2位小数)
liquid = (
Liquid(init_opts=opts.InitOpts(width="500px", height="400px"))
.add(
series_name="本科占比", # 系列名称
data=[bachelor_ratio], # 数据(0-1之间的比例值)
shape="circle", # 水球形状:圆形
is_outline_show=True, # 显示外边框
outline_itemstyle_opts=opts.ItemStyleOpts( # 外边框样式
border_color="#5470c6", # 边框颜色
border_width=2, # 边框宽度
),
label_opts=opts.LabelOpts( # 标签配置
font_size=30, # 字体大小
formatter=f"本科要求\n{bachelor_ratio:.0%}", # 格式化显示百分比
color="#333", # 字体颜色
),
)
.set_global_opts(
title_opts=opts.TitleOpts(
title="本科及以上学历要求占比",
pos_left="center",
),
)
)
liquid.render("04_edu_liquid.html")
print("水球图已生成:04_edu_liquid.html")
# ---------- 4. 学历要求词云图 ----------
wordcloud = (
WordCloud(init_opts=opts.InitOpts(width="600px", height="400px"))
.add(
series_name="学历要求", # 系列名称
data_pair=edu_data, # 数据:[(词, 频率), ...]
word_size_range=[20, 100], # 字体大小范围
shape="circle", # 词云形状:圆形
textstyle_opts=opts.TextStyleOpts( # 文字样式
font_family="Microsoft YaHei", # 字体
),
)
.set_global_opts(
title_opts=opts.TitleOpts(title="学历要求词云"),
)
)
wordcloud.render("04_edu_wordcloud.html")
print("词云图已生成:04_edu_wordcloud.html")
六、综合实战------完整数据分析报告
6.1 使用 Page 组合多图成一页报告
python
# ============================================
# 综合实战:使用 Page 将多个图表组合为一个完整的分析报告页面
# ============================================
from pyecharts.charts import Page # 导入页面组合类
from pyecharts import options as opts # 导入配置项
# 创建 Page 对象
page = Page( # 实例化页面对象
page_title="数据分析师岗位分析报告", # 浏览器标签页标题
layout=Page.SimplePageLayout, # 页面布局方式:简单布局
)
# ---------- 将之前创建的所有图表添加到页面中 ----------
# 注意:此处需要引用前面已创建的图表对象
# 这里用简化方式演示,实际使用时需确保图表对象已被创建
# 方式一:逐个添加(add方法)
# page.add(chart1, chart2, chart3, ...) # 逐个添加图表
# 方式二:使用 Tab 选项卡布局
# page = Page(layout=Page.DraggablePageLayout) # 可拖拽布局
# 为了演示完整性,这里重新创建简化的图表对象组合
from pyecharts.charts import Bar, Line, Pie, Map
from pyecharts.globals import ThemeType
# ----- 图表1:需求趋势(简化版) -----
line_chart = (
Line(init_opts=opts.InitOpts(theme=ThemeType.VINTAGE, width="1000px", height="400px"))
.add_xaxis(["1月", "2月", "3月", "4月", "5月", "6月"])
.add_yaxis("岗位数量", [3200, 2800, 4100, 4500, 4800, 5200], is_smooth=True)
.set_global_opts(title_opts=opts.TitleOpts(title="需求趋势"))
)
# ----- 图表2:热门城市(简化版) -----
bar_chart = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.VINTAGE, width="1000px", height="400px"))
.add_xaxis(["北京", "上海", "深圳", "杭州", "广州"])
.add_yaxis("岗位数量", [1200, 980, 850, 620, 580])
.reversal_axis()
.set_global_opts(title_opts=opts.TitleOpts(title="热门城市Top5"))
)
# ----- 图表3:学历分布(简化版) -----
pie_chart = (
Pie(init_opts=opts.InitOpts(theme=ThemeType.VINTAGE, width="1000px", height="400px"))
.add("学历要求", [("本科", 4500), ("硕士", 1200), ("大专", 800), ("博士", 150), ("不限", 350)])
.set_global_opts(title_opts=opts.TitleOpts(title="学历要求分布"))
)
# ----- 图表4:薪资地图(简化版) -----
map_chart = (
Map(init_opts=opts.InitOpts(theme=ThemeType.VINTAGE, width="1000px", height="400px"))
.add("平均薪资", [("北京", 20), ("上海", 19), ("广东", 18), ("浙江", 16)], maptype="china")
.set_global_opts(
title_opts=opts.TitleOpts(title="薪资分布"),
visualmap_opts=opts.VisualMapOpts(min_=10, max_=22, is_piecewise=True),
)
)
# ---------- 将所有图表添加到页面 ----------
page.add( # 添加图表到页面
line_chart, # 图表1:需求趋势
bar_chart, # 图表2:热门城市
pie_chart, # 图表3:学历分布
map_chart, # 图表4:薪资地图
)
# ---------- 渲染页面 ----------
page.render("05_full_report.html") # 生成完整报告HTML
print("完整分析报告已生成:05_full_report.html")
6.2 数据导出与保存
python
# ============================================
# 分析结果导出------保存为Excel和CSV文件
# ============================================
import pandas as pd # 导入pandas库
# ---------- 假设已有分析结果数据 ----------
# 汇总分析结果
summary_data = {
"分析维度": ["需求趋势", "热门城市", "薪资水平", "学历要求"],
"核心发现": [
"岗位需求同比增长25%,Q4为招聘旺季",
"北京、上海、深圳位居前三,占比超40%",
"一线城市平均薪资18-20K,新一线城市13-16K",
"本科学历要求占64%,硕士占17%",
],
"数据来源": ["招聘平台"] * 4,
"分析日期": ["2024-12"] * 4,
}
summary_df = pd.DataFrame(summary_data) # 创建汇总DataFrame
# ---------- 保存为CSV ----------
summary_df.to_csv( # 保存为CSV
"analysis_summary.csv", # 文件名
index=False, # 不保存行索引
encoding="utf-8-sig", # 编码(兼容中文Excel)
)
print("CSV文件已保存:analysis_summary.csv")
# ---------- 保存为Excel ----------
# 使用 ExcelWriter 可以保存多个工作表到同一个Excel文件
with pd.ExcelWriter( # 创建Excel写入器
"data_analyst_analysis_report.xlsx", # 文件名
engine="openpyxl", # 使用openpyxl引擎
mode="w", # 写入模式:覆盖写入
) as writer:
# 工作表1:分析汇总
summary_df.to_excel( # 写入工作表
writer, # Excel写入器
sheet_name="分析汇总", # 工作表名称
index=False, # 不保存行索引
)
# 工作表2:城市薪资统计(示例)
salary_df = pd.DataFrame({
"城市": ["北京", "上海", "深圳", "杭州", "广州"],
"平均薪资(K)": [20, 19, 18, 16, 15],
"中位数薪资(K)": [19, 18, 17, 15, 14],
"岗位数量": [1200, 980, 850, 620, 580],
})
salary_df.to_excel(writer, sheet_name="城市薪资统计", index=False)
# 工作表3:学历要求统计(示例)
edu_df = pd.DataFrame({
"学历": ["本科", "硕士", "大专", "博士", "不限"],
"岗位数量": [4500, 1200, 800, 150, 350],
"占比": ["64.3%", "17.1%", "11.4%", "2.1%", "5.0%"],
})
edu_df.to_excel(writer, sheet_name="学历要求统计", index=False)
print("Excel报告已保存:data_analyst_analysis_report.xlsx")
print("包含3个工作表:分析汇总、城市薪资统计、学历要求统计")
七、本章小结------知识点总结
┌──────────────────────────────────────────────────────────────┐
│ 第8章 核心知识点总结 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. 数据分析流程 │
│ 目标 → 收集 → 预处理 → 分析展现 → 结论 │
│ │
│ 2. pyecharts 核心图表 │
│ ├── Bar() 柱状图 │
│ ├── Line() 折线图 │
│ ├── Pie() 饼图 │
│ ├── Map() 地图 │
│ ├── Boxplot() 箱线图 │
│ ├── Liquid() 水球图 │
│ ├── WordCloud() 词云图 │
│ ├── Grid() 网格组合 │
│ ├── Page() 页面组合 │
│ └── Timeline() 时间轴 │
│ │
│ 3. pyecharts 配置体系 │
│ ├── 全局配置 set_global_opts() │
│ │ ├── TitleOpts 标题 │
│ │ ├── LegendOpts 图例 │
│ │ ├── TooltipOpts 提示框 │
│ │ ├── VisualMapOpts 视觉映射 │
│ │ ├── DataZoomOpts 数据缩放 │
│ │ └── AxisOpts 坐标轴 │
│ └── 系列配置 │
│ ├── LabelOpts 标签 │
│ ├── ItemStyleOpts 图元样式 │
│ ├── MarkPointOpts 标记点 │
│ └── MarkLineOpts 标记线 │
│ │
│ 4. 数据预处理要点 │
│ ├── 缺失值:dropna() / fillna() / interpolate() │
│ ├── 重复值:duplicated() / drop_duplicates() │
│ ├── 类型转换:astype() / to_datetime() / to_numeric() │
│ ├── 字符串处理:str.extract() / str.replace() / map() │
│ └── 薪资解析:正则表达式 + apply()自定义函数 │
│ │
│ 5. 数据分析展现方法 │
│ ├── 趋势分析:折线图(Line) │
│ ├── 排名分析:柱状图(Bar)+ 水平翻转 │
│ ├── 分布分析:饼图(Pie)+ 环形图 │
│ ├── 地域分析:地图(Map)+ 视觉映射 │
│ ├── 对比分析:分组柱状图 + 组合图 │
│ └── 综合报告:Page 组合多图 │
│ │
│ 6. 关键 pandas 方法 │
│ ├── value_counts() 统计频次 │
│ ├── groupby().agg() 分组聚合 │
│ ├── crosstab() 交叉表 │
│ ├── sort_values() 排序 │
│ ├── head() / tail() 预览数据 │
│ └── to_csv() / to_excel() 导出数据 │
│ │
└──────────────────────────────────────────────────────────────┘