写在前面
前两篇我们把 AI 测试岗位的 JD 数据爬下来了。但爬到的数据只是"原材料"------格式不统一、有脏数据、有缺失值、没法直接做统计分析。
比如薪资字段,爬下来是 25-40K 这种字符串。你想知道"AI 测试的平均薪资是多少",得先把字符串解析成数字。你想知道"哪些技能最热门",得先把 ["Python", "机器学习"] 这种列表展开成一行一条记录。你想知道"北京和上海的薪资差异",得先按城市分组再算。
这些工作叫数据清洗和数据处理,用 Python 原生代码写会很痛苦。Pandas 是 Python 数据分析的王牌库,专门干这件事。熟练使用 Pandas 是 AI 测试工程师的核心能力之一------因为你经常需要分析测试数据、对比模型效果、生成质量报告。
本篇目标:
- 掌握 Pandas 核心概念(DataFrame、Series)
- 学会用 Pandas 读取和清洗爬取的岗位数据
- 完成完整的数据分析流水线
- 产出一份"AI 测试岗位技能需求分析报告"
本篇文件结构
本篇代码会用到以下文件,建议继续放在 ai-test-crawler/ 目录下:
| 文件名 | 用途 | 创建方式 |
|---|---|---|
pandas_demo.py |
Pandas 基础知识练习 | 二、逐步追加 |
data_analysis.py |
岗位数据分析主程序 | 四、逐步追加 |
job_data.json |
数据来源(第04篇生成的) | 不需要手动创建 |
job_data.csv |
数据来源(第04篇生成的) | 不需要手动创建 |
analysis_report.txt |
分析报告输出(自动生成) | 不需要手动创建 |
前置条件 :本篇需要第 04 篇生成的
job_data.json和job_data.csv文件。如果还没有,回到第 04 篇运行python create_mock_site.py和python job_scraper.py先生成数据。
一、为什么需要 Pandas?
1.1 用原生 Python 做数据分析有多痛苦
假设你要回答这个问题:"AI 测试岗位中,最常见的技能是什么?"
用原生 Python 写,你需要手动遍历列表、展开嵌套、统计频次、排序取 Top N......第 04 篇我们已经写过类似的代码了,几十行才搞定一个简单的统计。
再换一个问题:"北京的平均薪资和上海比,高多少?"
这下麻烦了------你要筛选城市、解析薪资字符串、按城市分组、计算平均值、对比差异。用原生 Python 写,至少上百行代码,而且容易出 bug。
而用 Pandas,这些问题都是三到五行代码的事。
1.2 Pandas 的两大核心数据结构
Pandas 提供了两种数据结构来存放和操作数据:
Series ------ 一列数据,类似 Excel 中的一列。
python
import pandas as pd
# Series 就是一列数据 + 一行索引
salaries = pd.Series([25000, 35000, 30000, 20000, 45000])
print(salaries)
# 0 25000
# 1 35000
# 2 30000
# 3 20000
# 4 45000
# dtype: int64
# 一行代码算平均值
print(salaries.mean()) # 31000.0
DataFrame ------ 一张表格,类似 Excel 中的一个工作表。有行有列,每列是一个 Series。
python
# DataFrame 就是一张表格
df = pd.DataFrame({
"title": ["AI测试工程师", "大模型测试专家", "CV测试工程师"],
"salary": [25000, 45000, 22000],
"location": ["北京", "北京", "成都"]
})
print(df)
# title salary location
# 0 AI测试工程师 25000 北京
# 1 大模型测试专家 45000 北京
# 2 CV测试工程师 22000 成都
# 一行代码筛选北京的岗位
print(df[df["location"] == "北京"])
# 一行代码算平均薪资
print(df["salary"].mean()) # 30666.67
理解了这两个概念,你就理解了 Pandas 的 80%。后面所有的操作都是围绕 DataFrame 来做的:读数据进来是 DataFrame,清洗也是对 DataFrame 操作,分析也是从 DataFrame 里提取信息。
1.3 安装 Pandas
【运行】 在终端执行:
bash
pip install pandas
返回上一级目录,用这个命令:
bash
cd ..
可使用镜像 在终端执行:
bash
pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple
二、Pandas 基础操作速通
从这里到第三节结束,是 Pandas 知识点的讲解。建议新建一个
pandas_demo.py文件,每段代码都可以独立运行,跟着敲一遍就能理解。第四节开始才是完整的数据分析项目。
2.1 创建和查看 DataFrame
【新建文件】 在项目目录下新建 pandas_demo.py,写入以下代码:
python
"""
Pandas 基础知识练习
每段代码可以独立运行
"""
import pandas as pd
# ========== 创建 DataFrame ==========
# 用字典创建(最常用)
data = {
"职位名称": ["AI测试工程师", "高级AI测试开发", "大模型测试工程师", "CV测试工程师", "NLP测试工程师"],
"薪资下限": [25000, 35000, 30000, 22000, 25000],
"薪资上限": [40000, 60000, 50000, 38000, 45000],
"城市": ["北京", "上海", "杭州", "成都", "广州"],
"经验": ["3-5年", "5-10年", "3-5年", "1-3年", "3-5年"]
}
df = pd.DataFrame(data)
# 查看前几行(默认前5行)
print("--- 前 5 行数据 ---")
print(df.head())
# 查看后几行
print("\n--- 后 3 行数据 ---")
print(df.tail(3))
# 查看基本信息:行数、列数、每列数据类型、缺失值数量
print("\n--- 基本信息 ---")
print(df.info())
# 查看统计摘要(数值列的均值、标准差、最小值、最大值等)
print("\n--- 统计摘要 ---")
print(df.describe())
【运行】 在终端执行 python pandas_demo.py,看看输出。df.info() 会告诉你每列的数据类型和有没有缺失值,df.describe() 会给你数值列的统计概况------这两个是你拿到新数据后最先要看的。
2.2 选取列和行
【追加到 pandas_demo.py】 在上一段代码末尾继续添加:
python
# ========== 选取列和行 ==========
print("\n--- 选取列 ---")
# 选一列(返回 Series)
print(df["城市"])
print(type(df["城市"])) # <class 'pandas.core.series.Series'>
# 选多列(返回 DataFrame)
print(df[["职位名称", "城市"]])
print("\n--- 选取行 ---")
# 按索引选取行(iloc:按位置)
print(df.iloc[0]) # 第 1 行
print(df.iloc[1:3]) # 第 2 到第 3 行
# 按标签选取行(loc:按索引标签)
print(df.loc[0]) # 索引为 0 的行
print(df.loc[0:2]) # 索引 0 到 2 的行(注意:loc 是包含右端点的)
print("\n--- 条件筛选 ---")
# 筛选北京的岗位
beijing = df[df["城市"] == "北京"]
print(beijing)
# 筛选薪资上限超过 50000 的
high_salary = df[df["薪资上限"] > 50000]
print(high_salary)
# 组合条件(注意:每个条件要用括号包起来,用 & 和 | 连接)
result = df[(df["城市"] == "北京") & (df["薪资上限"] > 35000)]
print(result)
条件筛选是 Pandas 最高频的操作。核心语法是 df[条件表达式],条件表达式返回一个布尔 Series,Pandas 根据它来选择行。组合条件时要注意:每个条件必须用括号 () 包起来,条件之间用 &(且)或 |(或)连接,不能用 Python 的 and / or。
2.3 添加列和修改数据
【追加到 pandas_demo.py】 在上一段代码末尾继续添加:
python
# ========== 添加列和修改数据 ==========
print("\n--- 添加列 ---")
# 直接赋值就创建了新列
df["平均薪资"] = (df["薪资下限"] + df["薪资上限"]) / 2
print(df[["职位名称", "薪资下限", "薪资上限", "平均薪资"]])
# 用 apply 对每行做计算(非常常用)
df["薪资跨度"] = df.apply(lambda row: row["薪资上限"] - row["薪资下限"], axis=1)
print(df[["职位名称", "薪资下限", "薪资上限", "薪资跨度"]])
print("\n--- 修改数据 ---")
# 修改单个值
df.loc[0, "城市"] = "北京(总部)"
print(df.loc[0])
# 批量替换
df["城市"] = df["城市"].str.replace("(总部)", "")
print(df["城市"])
df.apply() 是 Pandas 的瑞士军刀------它能对每一行或每一列执行自定义的计算逻辑。axis=1 表示按行操作(每个 lambda 参数 row 是一行数据),axis=0 表示按列操作。
2.4 缺失值处理
真实数据中经常有缺失值(空值、None、NaN)。Pandas 有一套完整的缺失值处理方法。
【追加到 pandas_demo.py】 在上一段代码末尾继续添加:
python
# ========== 缺失值处理 ==========
print("\n--- 缺失值处理 ---")
# 制造一些缺失值用于演示
import numpy as np
df_dirty = pd.DataFrame({
"name": ["张三", "李四", None, "王五"],
"salary": [25000, np.nan, 35000, np.nan],
"city": ["北京", "上海", "杭州", None]
})
print("原始数据:")
print(df_dirty)
# 1. 检测缺失值
print("\n每列缺失值数量:")
print(df_dirty.isnull().sum())
# 2. 删除包含缺失值的行
print("\n删除有缺失值的行:")
print(df_dirty.dropna())
# 3. 填充缺失值
print("\n用默认值填充:")
print(df_dirty.fillna({"name": "未知", "salary": 0, "city": "未知"}))
# 4. 用均值/中位数填充数值列
print("\n用均值填充薪资:")
salary_mean = df_dirty["salary"].mean()
print(df_dirty.fillna({"salary": salary_mean}))
处理策略怎么选:
- 删除 (
dropna):数据量大、缺失值少的时候用,简单粗暴 - 填充默认值 (
fillna):有合理的默认值时用(比如城市填"未知") - 填充统计值:数值列可以填均值、中位数;分类列可以填众数
- 保留不处理:有些分析可以忽略缺失值(比如算薪资平均值时,NaN 自动跳过)
2.5 分组统计 ------ 最强大的功能
【追加到 pandas_demo.py】 在上一段代码末尾继续添加:
python
# ========== 分组统计 ==========
print("\n--- 分组统计 ---")
# 按城市分组,计算平均薪资
print("各城市平均薪资上限:")
print(df.groupby("城市")["薪资上限"].mean())
# 按城市分组,计算多种统计量
print("\n各城市薪资统计:")
print(df.groupby("城市")[["薪资下限", "薪资上限"]].agg(["mean", "min", "max", "count"]))
# 按经验分组,计算平均薪资
print("\n各经验等级平均薪资:")
print(df.groupby("经验")[["薪资下限", "薪资上限"]].mean())
# 多级分组(按城市 + 经验)
print("\n城市 × 经验 分组统计:")
print(df.groupby(["城市", "经验"])["薪资上限"].mean())
groupby() 是 Pandas 分析的核心。它的逻辑和 SQL 的 GROUP BY 一模一样:按某个列分组,然后对每组做聚合计算。你可以对一个列算单一统计量(如 .mean()),也可以用 .agg() 同时算多个统计量。
2.6 排序和计数
【追加到 pandas_demo.py】 在上一段代码末尾继续添加:
python
# ========== 排序和计数 ==========
print("\n--- 排序 ---")
# 按薪资上限降序排列
print(df.sort_values("薪资上限", ascending=False))
# 按多列排序(先按城市升序,再按薪资上限降序)
print(df.sort_values(["城市", "薪资上限"], ascending=[True, False]))
print("\n--- 计数 ---")
# 统计每个城市的岗位数量
print(df["城市"].value_counts())
# 统计每个经验等级的岗位数量
print(df["经验"].value_counts())
value_counts() 是最方便的计数方法------一行代码就能拿到"每个值出现了多少次",做频次分析时非常常用。sort_values() 用来排序,ascending 参数控制升序还是降序。
三、Pandas 读取数据
3.1 读取 CSV
【追加到 pandas_demo.py】 在上一段代码末尾继续添加:
python
# ========== 读取 CSV 文件 ==========
print("\n--- 读取 CSV ---")
# 读取第04篇生成的岗位数据 CSV
try:
job_df = pd.read_csv("job_data.csv", encoding="utf-8-sig")
print(f"读取成功,共 {len(job_df)} 行,{len(job_df.columns)} 列")
print(f"列名:{list(job_df.columns)}")
print(job_df.head(3))
except FileNotFoundError:
print("job_data.csv 不存在,请先运行第04篇的爬虫代码")
pd.read_csv() 读取 CSV 文件,返回 DataFrame。参数说明:
encoding="utf-8-sig":因为我们保存 CSV 时用了这个编码(为了 Excel 兼容),读取时也要用同样的编码- 还有其他常用参数:
sep=","(分隔符,默认逗号)、header=0(表头在第几行)、usecols=["col1", "col2"](只读指定列)
3.2 读取 JSON
【追加到 pandas_demo.py】 在上一段代码末尾继续添加:
python
# ========== 读取 JSON 文件 ==========
print("\n--- 读取 JSON ---")
# 读取第04篇生成的岗位数据 JSON
try:
job_json_df = pd.read_json("job_data.json")
print(f"读取成功,共 {len(job_json_df)} 行")
print(job_json_df.head(3))
except FileNotFoundError:
print("job_data.json 不存在,请先运行第04篇的爬虫代码")
# JSON 格式不同,参数也不同
# 如果是 JSON Lines(每行一个 JSON 对象)
# df = pd.read_json("data.jsonl", lines=True)
【运行】 在终端执行 python pandas_demo.py,确认所有 Pandas 示例都正常输出。
四、岗位数据分析实战
前面的 Pandas 基础操作你已经练过了。现在用它们来做一个完整的数据分析项目:分析 AI 测试岗位的技能需求,产出一份分析报告。
4.1 项目初始化
【新建文件】 在项目目录下新建 data_analysis.py,写入以下代码:
python
"""
AI 测试岗位数据分析
读取爬取的 JD 数据,进行清洗、转换、分析,输出分析报告
数据来源:job_data.json(第04篇爬虫生成)
输出文件:analysis_report.txt
"""
import pandas as pd
import json
from collections import Counter
# 常量配置
INPUT_JSON = "job_data.json"
OUTPUT_REPORT = "analysis_report.txt"
print("AI 测试岗位数据分析启动")
print("=" * 60)
4.2 读取数据
【追加到 data_analysis.py】 在上一段代码末尾继续添加:
python
def load_data():
"""
读取岗位数据,返回 DataFrame
"""
try:
with open(INPUT_JSON, "r", encoding="utf-8") as f:
data = json.load(f)
df = pd.DataFrame(data)
print(f"数据加载成功:{len(df)} 条岗位记录")
return df
except FileNotFoundError:
print(f"数据文件不存在:{INPUT_JSON}")
print("请先运行第04篇的爬虫代码生成数据")
return None
4.3 数据清洗
爬到的原始数据通常不能直接分析,这一步做三件事:检查数据质量、处理缺失值、统一数据格式。
【追加到 data_analysis.py】 在上一段代码末尾继续添加:
python
def clean_data(df):
"""
数据清洗:检查质量、处理缺失值、统一格式
"""
print(f"\n--- 数据清洗 ---")
# 1. 查看数据基本信息
print(f"原始数据量:{len(df)} 条")
print(f"列名:{list(df.columns)}")
# 2. 检查缺失值
print(f"\n缺失值统计:")
null_counts = df.isnull().sum()
for col, count in null_counts.items():
if count > 0:
print(f" {col}: {count} 个缺失值")
# 3. 处理缺失值:字符串列填"未知",数值列填 None 保持缺失
str_cols = ["title", "company", "location", "experience", "education", "description"]
for col in str_cols:
if col in df.columns:
df[col] = df[col].fillna("未知")
# 4. 确保 tags 列是列表类型
if "tags" in df.columns:
df["tags"] = df["tags"].apply(lambda x: x if isinstance(x, list) else [])
# 5. 删除完全重复的行
before_count = len(df)
df = df.drop_duplicates(subset=["title", "company", "location", "salary_raw"])
after_count = len(df)
if before_count != after_count:
print(f" 去重:删除了 {before_count - after_count} 条重复数据")
# 6. 标准化城市名(去掉多余空格)
if "location" in df.columns:
df["location"] = df["location"].str.strip()
print(f"清洗后数据量:{len(df)} 条")
return df
清洗做了五件事:检查缺失值、填充默认值、确保列表类型、去重、标准化文本。这些是数据清洗的通用套路,不只适用于岗位数据------后面你分析测试数据、模型评估数据时,这些方法同样适用。
4.4 数据转换
数据转换是把"原始格式"变成"分析友好格式"。对我们的岗位数据来说,核心转换有三个:薪资解析成数字、技能标签展开、分层分类。
【追加到 data_analysis.py】 在上一段代码末尾继续添加:
python
def transform_data(df):
"""
数据转换:薪资解析、技能标签展开、增加分析维度
"""
print(f"\n--- 数据转换 ---")
# 1. 计算平均薪资(取上下限的中间值)
# 只对月薪岗位(salary_type == "K")计算
def calc_avg_salary(row):
# 只计算月薪岗位(salary_type == "K"),排除日薪等其他类型
if row.get("salary_type") == "K" and row.get("salary_low") and row.get("salary_high"):
return (row["salary_low"] + row["salary_high"]) / 2
return None
df["avg_salary"] = df.apply(calc_avg_salary, axis=1)
monthly_count = df["avg_salary"].notna().sum()
print(f" 计算了 {monthly_count} 条月薪岗位的平均薪资")
# 2. 增加薪资分层
def salary_level(salary):
if pd.isna(salary):
return "未统计"
if salary < 25000:
return "初级(<25K)"
elif salary < 40000:
return "中级(25-40K)"
elif salary < 55000:
return "高级(40-55K)"
else:
return "专家(55K+)"
df["salary_level"] = df["avg_salary"].apply(salary_level)
print(f" 完成薪资分层")
# 3. 展开技能标签(为技能分析准备)
# 每条岗位可能有多个 tag,我们把它们全部收集起来
all_tags = []
for tags in df["tags"]:
if isinstance(tags, list):
all_tags.extend(tags)
tag_counter = Counter(all_tags)
print(f" 技能标签展开:共 {len(all_tags)} 个标签,去重后 {len(tag_counter)} 个")
# 4. 增加技能数量列(每条岗位要求的技能数)
df["tag_count"] = df["tags"].apply(lambda x: len(x) if isinstance(x, list) else 0)
return df, tag_counter
4.5 数据分析
现在数据已经干净了,可以开始做真正的分析。我们从六个维度来分析 AI 测试岗位的需求。
【追加到 data_analysis.py】 在上一段代码末尾继续添加:
python
def analyze_data(df, tag_counter):
"""
多维度数据分析
返回分析结果字典
"""
results = {}
# ---- 1. 薪资分析 ----
monthly = df[df["avg_salary"].notna()]
results["salary"] = {
"月薪岗位数": len(monthly),
"平均薪资": monthly["avg_salary"].mean(),
"最高薪资": monthly["avg_salary"].max(),
"最低薪资": monthly["avg_salary"].min(),
"中位数薪资": monthly["avg_salary"].median(),
}
# 按薪资分层统计
results["salary_level_dist"] = df["salary_level"].value_counts().to_dict()
print(f"\n薪资分析完成")
# ---- 2. 技能需求分析(最核心) ----
results["top_skills"] = tag_counter.most_common(15)
# 技能分类统计
skill_categories = {
"编程语言": ["Python"],
"AI/ML技术": ["机器学习", "深度学习", "大模型", "LLM", "NLP", "计算机视觉", "TensorFlow", "PyTorch"],
"测试技能": ["自动化测试", "pytest", "测试框架", "模型测试", "测试开发", "测试基础", "数据标注"],
"评测方法": ["LLM评测", "模型评测", "Prompt Engineering", "RAG", "BLEU", "ROUGE", "文本评测"],
"数据技能": ["数据分析", "Pandas", "SQL", "数据清洗", "数据标注"],
"工程技能": ["Docker", "CI/CD", "Selenium", "Red Team", "对抗攻击", "大模型安全", "质量保障", "A/B测试"],
}
category_counts = {}
for category, keywords in skill_categories.items():
count = sum(tag_counter.get(kw, 0) for kw in keywords)
if count > 0:
category_counts[category] = count
results["skill_categories"] = category_counts
print(f"技能分析完成")
# ---- 3. 地区分析 ----
results["location_dist"] = df["location"].value_counts().to_dict()
# 按城市分组的平均薪资
location_salary = monthly.groupby("location")["avg_salary"].agg(["mean", "count"])
results["location_salary"] = location_salary.to_dict("index")
print(f"地区分析完成")
# ---- 4. 经验要求分析 ----
results["experience_dist"] = df["experience"].value_counts().to_dict()
# 按经验分组的平均薪资
exp_salary = monthly.groupby("experience")["avg_salary"].agg(["mean", "count"])
results["experience_salary"] = exp_salary.to_dict("index")
print(f"经验分析完成")
# ---- 5. 学历要求分析 ----
results["education_dist"] = df["education"].value_counts().to_dict()
print(f"学历分析完成")
# ---- 6. 薪资与技能关联分析 ----
# 找出高薪岗位(平均薪资前 25%)的技能共性
if len(monthly) > 4:
threshold = monthly["avg_salary"].quantile(0.75)
high_salary_jobs = df[df["avg_salary"] >= threshold]
high_salary_tags = []
for tags in high_salary_jobs["tags"]:
if isinstance(tags, list):
high_salary_tags.extend(tags)
results["high_salary_skills"] = Counter(high_salary_tags).most_common(10)
results["high_salary_threshold"] = threshold
results["high_salary_count"] = len(high_salary_jobs)
print(f"高薪岗位技能分析完成")
return results
4.6 生成报告
分析做完了,把结果整理成一份可读性好的报告。
【追加到 data_analysis.py】 在上一段代码末尾继续添加:
python
def generate_report(results, df):
"""
生成分析报告,保存到文本文件并打印到终端
"""
lines = []
def add(text=""):
lines.append(text)
add("=" * 60)
add("AI 测试岗位技能需求分析报告")
add("=" * 60)
add(f"\n数据范围:{len(df)} 条 AI 测试相关岗位")
add(f"分析维度:薪资、技能、地区、经验、学历、高薪岗位")
# ---- 薪资分析 ----
s = results.get("salary", {})
add(f"\n{'─' * 60}")
add(f"一、薪资分析")
add(f"{'─' * 60}")
add(f" 月薪岗位数:{s.get('月薪岗位数', 0)}")
add(f" 平均薪资:{s.get('平均薪资', 0):,.0f} 元/月")
add(f" 中位数薪资:{s.get('中位数薪资', 0):,.0f} 元/月")
add(f" 薪资范围:{s.get('最低薪资', 0):,.0f} ~ {s.get('最高薪资', 0):,.0f} 元/月")
add(f"\n 薪资分层分布:")
for level, count in results.get("salary_level_dist", {}).items():
bar = "█" * (count * 3)
add(f" {level:<16s} {count:>2d} 个岗位 {bar}")
# ---- 技能分析 ----
add(f"\n{'─' * 60}")
add(f"二、技能需求分析(最重要的部分)")
add(f"{'─' * 60}")
add(f"\n 技能需求 Top 15:")
for i, (tag, count) in enumerate(results.get("top_skills", []), 1):
bar = "█" * (count * 2)
add(f" {i:>2d}. {tag:<24s} {count:>2d} 次 {bar}")
add(f"\n 按技能类别统计:")
for category, count in sorted(results.get("skill_categories", {}).items(),
key=lambda x: x[1], reverse=True):
bar = "█" * (count * 2)
add(f" {category:<12s} {count:>3d} 次 {bar}")
# ---- 地区分析 ----
add(f"\n{'─' * 60}")
add(f"三、地区分布")
add(f"{'─' * 60}")
add(f"\n 岗位地区分布:")
for location, count in results.get("location_dist", {}).items():
bar = "█" * (count * 3)
add(f" {location:<8s} {count:>2d} 个岗位 {bar}")
loc_salary = results.get("location_salary", {})
if loc_salary:
add(f"\n 各城市平均薪资:")
for loc, stats in sorted(loc_salary.items(), key=lambda x: x[1].get("mean", 0), reverse=True):
mean_salary = stats.get("mean", 0)
count = stats.get("count", 0)
add(f" {loc:<8s} {mean_salary:>10,.0f} 元/月 ({int(count)} 个岗位)")
# ---- 经验分析 ----
add(f"\n{'─' * 60}")
add(f"四、经验要求分析")
add(f"{'─' * 60}")
add(f"\n 经验要求分布:")
for exp, count in results.get("experience_dist", {}).items():
bar = "█" * (count * 3)
add(f" {exp:<10s} {count:>2d} 个岗位 {bar}")
exp_salary = results.get("experience_salary", {})
if exp_salary:
add(f"\n 各经验等级平均薪资:")
for exp, stats in exp_salary.items():
mean_salary = stats.get("mean", 0)
add(f" {exp:<10s} {mean_salary:>10,.0f} 元/月")
# ---- 学历分析 ----
add(f"\n{'─' * 60}")
add(f"五、学历要求分析")
add(f"{'─' * 60}")
add(f"\n 学历要求分布:")
for edu, count in results.get("education_dist", {}).items():
bar = "█" * (count * 3)
add(f" {edu:<8s} {count:>2d} 个岗位 {bar}")
# ---- 高薪岗位分析 ----
if "high_salary_skills" in results:
add(f"\n{'─' * 60}")
add(f"六、高薪岗位技能分析")
add(f"{'─' * 60}")
threshold = results.get("high_salary_threshold", 0)
count = results.get("high_salary_count", 0)
add(f"\n 高薪门槛:平均薪资 >= {threshold:,.0f} 元/月(前 25%)")
add(f" 高薪岗位数:{count}")
add(f"\n 高薪岗位最看重的技能 Top 10:")
for i, (tag, freq) in enumerate(results.get("high_salary_skills", []), 1):
bar = "█" * freq
add(f" {i:>2d}. {tag:<24s} {freq:>2d} 次 {bar}")
# ---- 总结建议 ----
add(f"\n{'─' * 60}")
add(f"七、学习建议总结")
add(f"{'─' * 60}")
add(f"")
add(f" 根据以上数据,AI 测试工程师的技能优先级建议如下:")
add(f"")
add(f" 第一优先级(必备):")
add(f" Python 编程")
add(f" 基本测试方法论")
add(f" 数据分析基础")
add(f"")
add(f" 第二优先级(核心竞争力):")
add(f" AI/ML 基础知识")
add(f" 模型评测方法")
add(f" 自动化测试框架")
add(f"")
add(f" 第三优先级(差异化优势):")
add(f" 大模型评测(LLM)")
add(f" Prompt Engineering")
add(f" RAG 系统测试")
add(f" 安全测试")
add(f"\n{'=' * 60}")
add(f"报告生成完毕")
# 输出报告
report_text = "\n".join(lines)
# 打印到终端
print(report_text)
# 保存到文件
with open(OUTPUT_REPORT, "w", encoding="utf-8") as f:
f.write(report_text)
print(f"\n报告已保存到 {OUTPUT_REPORT}")
return report_text
4.7 主程序
【追加到 data_analysis.py】 在上一段代码末尾继续添加:
python
# ========== 主程序 ==========
if __name__ == "__main__":
# 第一步:读取数据
df = load_data()
if df is None:
exit()
# 第二步:数据清洗
df = clean_data(df)
# 第三步:数据转换
df, tag_counter = transform_data(df)
# 第四步:数据分析
results = analyze_data(df, tag_counter)
# 第五步:生成报告
generate_report(results, df)
print(f"\n全部完成!")
【运行】 在终端执行:
bash
python data_analysis.py
4.8 运行结果
运行后终端会输出完整的分析报告,同时生成 analysis_report.txt 文件。大致输出如下:
AI 测试岗位数据分析启动
============================================================
数据加载成功:12 条岗位记录
--- 数据清洗 ---
原始数据量:12 条
列名:['title', 'salary_raw', 'salary_low', 'salary_high', 'salary_type', 'company', 'location', 'experience', 'education', 'tags', 'description']
缺失值统计:
清洗后数据量:12 条
--- 数据转换 ---
计算了 11 条月薪岗位的平均薪资
完成薪资分层
技能标签展开:共 53 个标签,去重后 37 个
薪资分析完成
技能分析完成
地区分析完成
经验分析完成
学历分析完成
高薪岗位技能分析完成
============================================================
AI 测试岗位技能需求分析报告
============================================================
数据范围:12 条 AI 测试相关岗位
分析维度:薪资、技能、地区、经验、学历、高薪岗位
────────────────────────────────────────────────────────────
一、薪资分析
────────────────────────────────────────────────────────────
月薪岗位数:11
平均薪资:40,818 元/月
中位数薪资:36,500 元/月
薪资范围:27,500 ~ 60,000 元/月
薪资分层分布:
中级(25-40K) 6 个岗位 ██████████████████
高级(40-55K) 3 个岗位 █████████
专家(55K+) 2 个岗位 ██████
未统计 1 个岗位 ███
────────────────────────────────────────────────────────────
二、技能需求分析(最重要的部分)
────────────────────────────────────────────────────────────
技能需求 Top 15:
1. Python 12 次 ████████████████████████
2. pytest 2 次 ████
3. NLP 2 次 ████
4. RAG 2 次 ████
5. 数据分析 2 次 ████
6. 模型测试 2 次 ████
7. 机器学习 1 次 ██
8. 自动化测试 1 次 ██
9. 大模型 1 次 ██
10. LLM评测 1 次 ██
11. 测试开发 1 次 ██
12. Prompt Engineering 1 次 ██
13. 评测框架 1 次 ██
14. TensorFlow 1 次 ██
15. 模型评测 1 次 ██
按技能类别统计:
编程语言 12 次 ████████████████████████
测试技能 9 次 ██████████████████
评测方法 8 次 ████████████████
工程技能 8 次 ████████████████
AI/ML技术 7 次 ██████████████
数据技能 6 次 ████████████
────────────────────────────────────────────────────────────
三、地区分布
────────────────────────────────────────────────────────────
岗位地区分布:
北京 5 个岗位 ███████████████
上海 2 个岗位 ██████
杭州 2 个岗位 ██████
深圳 1 个岗位 ███
广州 1 个岗位 ███
成都 1 个岗位 ███
各城市平均薪资:
上海 47,500 元/月 (1 个岗位)
北京 47,300 元/月 (5 个岗位)
杭州 36,250 元/月 (2 个岗位)
广州 35,000 元/月 (1 个岗位)
成都 30,000 元/月 (1 个岗位)
深圳 27,500 元/月 (1 个岗位)
────────────────────────────────────────────────────────────
四、经验要求分析
────────────────────────────────────────────────────────────
经验要求分布:
3-5年 6 个岗位 ██████████████████
5-10年 3 个岗位 █████████
1-3年 2 个岗位 ██████
在校生 1 个岗位 ███
各经验等级平均薪资:
1-3年 28,750 元/月
3-5年 38,167 元/月
5-10年 54,167 元/月
────────────────────────────────────────────────────────────
五、学历要求分析
────────────────────────────────────────────────────────────
学历要求分布:
本科 8 个岗位 ████████████████████████
硕士 4 个岗位 ████████████
────────────────────────────────────────────────────────────
六、高薪岗位技能分析
────────────────────────────────────────────────────────────
高薪门槛:平均薪资 >= 50,000 元/月(前 25%)
高薪岗位数:3
高薪岗位最看重的技能 Top 10:
1. Python 3 次 ███
2. 模型评测 1 次 █
3. A/B测试 1 次 █
4. CI/CD 1 次 █
5. 质量保障 1 次 █
6. 大模型安全 1 次 █
7. 对抗攻击 1 次 █
8. Red Team 1 次 █
9. LLM 1 次 █
10. 评测方法论 1 次 █
────────────────────────────────────────────────────────────
七、学习建议总结
────────────────────────────────────────────────────────────
根据以上数据,AI 测试工程师的技能优先级建议如下:
第一优先级(必备):
Python 编程
基本测试方法论
数据分析基础
第二优先级(核心竞争力):
AI/ML 基础知识
模型评测方法
自动化测试框架
第三优先级(差异化优势):
大模型评测(LLM)
Prompt Engineering
RAG 系统测试
安全测试
============================================================
报告生成完毕
报告已保存到 analysis_report.txt
全部完成!
五、从数据中提炼的洞察
上面的分析只是数字。但作为 AI 测试工程师,你更重要的是从数字中看出结论。以下是我从这份数据中提炼的核心洞察:
5.1 Python 是绝对的第一技能
12 个岗位中有 10 个要求 Python,出现率 83%。不管你是做传统测试转 AI 测试,还是从零开始学,Python 都是第一优先级------这验证了我们系列前两篇从 Python 开始学的决策是正确的。
5.2 薪资差距很大,技能决定天花板
薪资从 2 万到 7.5 万,差距接近 4 倍。高薪岗位(55K+)的共同特点是:要求大模型相关经验、需要方法论设计能力(评测体系建设、质量标准制定)、不只是执行测试。也就是说,做执行的人薪资在 2-4 万,能设计体系的人薪资在 5-7 万。
5.3 大模型是新的增长点
LLM、Prompt Engineering、RAG 这些技能在传统的 AI 测试岗位里几乎看不到,但在这批数据中频繁出现。这说明大模型测试是一个新兴方向,竞争少、需求增长快、薪资高。提前布局这个方向是非常值得的。
5.4 北京是机会最多的城市
北京岗位数最多,平均薪资也最高。上海和杭州紧随其后。如果你在这些城市找 AI 测试工作,机会会更多。
5.5 学历门槛不高
8 个岗位要求本科,只有 3 个要求硕士。大专也有机会。说明 AI 测试看重的是实际能力(Python、测试经验、AI 认知),学历不是硬门槛。
六、Pandas 常用操作速查表
留一份速查表,后面写分析代码时直接翻:
读取数据:
python
pd.read_csv("file.csv") # 读 CSV
pd.read_json("file.json") # 读 JSON
pd.read_excel("file.xlsx") # 读 Excel
df.to_csv("output.csv", index=False) # 保存 CSV
df.to_json("output.json", orient="records", force_ascii=False) # 保存 JSON
查看数据:
python
df.head() # 前 5 行
df.tail() # 后 5 行
df.shape # (行数, 列数)
df.info() # 列名、类型、缺失值
df.describe() # 数值列统计摘要
df["列名"].unique() # 该列的所有唯一值
df["列名"].value_counts() # 每个值出现的次数
筛选和排序:
python
df[df["列名"] == "值"] # 条件筛选
df[(df["A"] > 10) & (df["B"] == "x")] # 组合条件
df.sort_values("列名") # 排序
df.sort_values(["列A", "列B"], ascending=[True, False]) # 多列排序
分组聚合:
python
df.groupby("分组列")["数值列"].mean() # 分组求均值
df.groupby("分组列")["数值列"].agg(["mean", "max"]) # 多个统计量
df.groupby(["列A", "列B"])["数值列"].sum() # 多级分组
数据清洗:
python
df.isnull().sum() # 每列缺失值数量
df.dropna() # 删除有缺失值的行
df.fillna("默认值") # 填充缺失值
df.drop_duplicates() # 去重
df["列名"].str.strip() # 去首尾空白
df["列名"].str.replace("旧", "新") # 字符串替换
七、本篇小结
本篇的核心收获:
- Pandas 两大核心结构:Series(一列数据)和 DataFrame(一张表格),理解了这两个概念就理解了 Pandas 的 80%
- 数据清洗流程:检查缺失值 → 填充默认值 → 去重 → 标准化格式,五步走
- 数据转换技巧:薪资解析成数字、技能标签展开、增加分析维度列
- 分组统计能力:groupby + agg 完成多维度分析
- Counter 统计:频次排名一行搞定
- 报告生成:把分析结果整理成可读性好的文本报告
- 数据洞察力:不只是输出数字,还要提炼出有价值的结论
这份"AI 测试岗位技能需求分析报告"就是本系列后续学习路线的数据依据。后面我们学的每一块知识,都是在回答报告中提出的问题。
下一篇:【测AI】第07篇:数据可视化 ------ Matplotlib 让分析结果一目了然
- Matplotlib 基础:画出你的第一张图
- 常用图表类型:柱状图、饼图、散点图、热力图
- 实战:把岗位分析结果做成可视化图表
- 技能需求词云图
- 薪资分布图与趋势分析
- 产出一套完整的数据分析可视化报告