当前位置: 首页 > news >正文

基于Pandas和FineBI的昆明职位数据分析与可视化实现(二)- 职位数据清洗与预处理

文章目录

  • 一、数据集介绍
  • 二、缺失值处理
  • 三、重复值处理
  • 四、薪资数据格式处理
  • 五、技能格式处理
  • 六、拆分薪资列并处理异常值
  • 七、拆分工作区域列
  • 八、清洗后的数据集
  • 九、完整代码


一、数据集介绍

这份昆明职位数据集源自 Boss 直聘,数据量颇为丰富,包含 17731 行、17 列数据。数据集里各个字段都承载着重要信息,具体介绍如下表所示:

字段名含义
province岗位所在省份
city岗位所在城市
category_1岗位的一级分类
category_2岗位的二级分类
position具体职位
job_name职位名称
job_area工作区域
salary薪资待遇
experience工作经验要求
education教育程度要求
company_name招聘公司名称
company_industry招聘公司所属行业
financing_status招聘公司的融资状态
company_size招聘公司的规模
skill岗位所需技能
benefits公司提供的福利待遇
job_url职位详情链接

数据集部分数据如下图所示:

在这里插入图片描述


二、缺失值处理

在开展缺失值处理工作之前,首要步骤是对数据集中各字段的缺失情况进行全面检测,以明确哪些字段存在缺失值及缺失的具体数量。缺失值检测结果如下图所示,通过该结果可直观掌握各字段的缺失状况,为后续有针对性地制定处理策略提供依据。

在这里插入图片描述

缺失值检测结果显示,数据集中多个字段存在缺失值,这可能影响后续分析的准确性。针对不同字段的特点和业务逻辑,采用了多种策略进行缺失值处理。

education(教育程度)字段的缺失值仅 89 条,占数据集总量的 0.5%,删除这些记录对整体数据分布影响甚微,因此采用直接删除策略以保证数据质量。

financing_status(融资状态)字段存在 5762 条缺失值(占比 32.5%),考虑到融资状态与公司行业强相关(如互联网行业多处于 B 轮后融资阶段),可按company_industry分组,计算每组众数作为填充值。例如,科技行业的众数为 “C 轮”,则该行业的缺失值统一填充为 “C 轮”。对于无有效众数的行业(如新兴行业数据稀疏),标记为 “未知”,确保填充逻辑符合行业特性。

company_size(公司规模)字段 存在102 条缺失值(占比 0.6%),占比较少,可采用全局众数填充。经统计,数据集内 “100-499 人” 规模的公司占比最高,因此将缺失值统一填充为该众数。若后续分析发现规模与行业相关性增强,可调整为分组填充策略。

skill(技能要求)字段存在 1267 条缺失值(占比 7.1%)使用业务语义填充。招聘实践中,技能要求缺失通常意味着岗位对技术栈无强制要求,因此统一标注为 “无明确技能要求”。此填充方式保留了职位特性,避免因技术术语差异导致的分析偏差。

benefits(福利待遇)字段存在4684条缺失值(占比26.4%),采用“常规福利”进行填充。

通过上述策略,所有字段的缺失值均被有效处理,确保了数据集的完整性,为后续分析奠定了基础。

缺失值处理代码如下所示:

# 缺失值检测及处理
def handle_missing_values(data):print("缺失值处理前各列缺失值数量:")print(data.isnull().sum())# 处理education字段的缺失值data.dropna(subset=['education'], inplace=True)# financing_status(融资状态)字段# 公司的融资状态可能和公司所处行业有一定联系,不同行业的公司融资情况可能不同。# 因此,我将依据 company_industry(公司行业)分组,用每组内 financing_status 的众数进行填充,以更贴合实际情况。# 遍历数据中公司行业列的唯一值for industry in data['company_industry'].unique():# 从数据中筛选出当前行业的数据industry_df = data[data['company_industry'] == industry]# 计算当前行业融资状态列的众数# 如果众数存在,则取第一个众数;如果众数为空,则使用 '未知' 作为填充值mode_value = industry_df['financing_status'].mode().iloc[0] if not industry_df['financing_status'].mode().empty else '未知'# 使用计算得到的众数填充当前行业融资状态列的缺失值data.loc[data['company_industry'] == industry, 'financing_status'] = data.loc[data['company_industry'] == industry, 'financing_status'].fillna(mode_value)# 计算 company_size 列的众数,如果众数存在则取第一个众数作为填充值,若不存在则使用 '未知' 作为填充值mode_company_size = data['company_size'].mode().iloc[0] if not data['company_size'].mode().empty else '未知'# 使用计算得到的众数填充 company_size 列中的缺失值data['company_size'] = data['company_size'].fillna(mode_company_size)# 使用 '无明确技能要求' 填充 skill 列中的缺失值,inplace=True 表示直接在原数据上修改data['skill'].fillna('无明确技能要求', inplace=True)# 使用 '常规福利' 填充 benefits 列中的缺失值,inplace=True 表示直接在原数据上修改data['benefits'].fillna('常规福利', inplace=True)print("缺失值处理后各列缺失值数量:")print(data.isnull().sum())return data

缺失值处理完成后,再次核查缺失值数据,结果如下图所示。可以看到,各字段的缺失值数量均已降为 0,数据集中已不存在缺失值,处理达到预期效果。

在这里插入图片描述


三、重复值处理

在数据清洗阶段,需要识别并处理完全重复的记录,即两行数据的所有字段值均相同的情况。这类重复数据可能源于数据采集过程中的冗余或系统误差,会导致后续分析结果出现偏差。因此,采用以下策略进行处理:

处理逻辑

  1. 检测数据集中完全重复的行数
  2. 若存在重复行,则删除重复记录,仅保留其中一行
  3. 验证处理结果,确保数据集中无重复记录

重复值处理代码如下所示:

# 重复数据检测及处理
def handle_duplicate_data(data):duplicate_rows = len(data[data.duplicated()])print("重复行数量:", duplicate_rows)if duplicate_rows > 0:data.drop_duplicates(inplace=True)duplicate_rows = len(data[data.duplicated()])print("重复行数量:", duplicate_rows)return data

执行上述重复值处理代码后,结果如下图所示。可以看到,处理前通过data.duplicated()检测,重复行数量为 0 ;处理后再次检测,重复行数量依旧为 0 ,表明数据集中原本就不存在完全重复的记录,无需进行重复值删除操作。

在这里插入图片描述


四、薪资数据格式处理

薪资数据在原始数据集中存在多种格式,如"元/月"、“元/天”、“元/时”、“元/周"等不同时间单位的表示,以及带”·“或”*“的特殊格式,甚至包含"面议"等非数值形式。这种格式不统一会严重影响后续薪资分析的准确性和可比性。因此,需要对薪资数据进行标准化处理,将其统一转换为"K”(千)为单位的格式。

处理逻辑

  1. 检测非标准格式:通过正则表达式识别不符合"数字-数字K"或"面议"格式的薪资数据
  2. 单位转换:将不同时间单位的薪资数据统一转换为月薪(K)表示
    • 元/月:直接除以1000转换为K
    • 元/天:乘以30天再除以1000转换为K
    • 元/时:乘以8小时/天 × 22天/月再除以1000转换为K
    • 元/周:乘以4周/月再除以1000转换为K
  3. 特殊格式处理
    • 带"*"的格式:提取数字部分并转换为K
    • 带"·“的格式:提取”·"前的部分
  4. 处理"面议"情况:用非面议薪资的众数替换"面议"值,以保留数据统计特性

薪资数据格式处理代码如下所示:

# 统一薪资数据格式
def convert_salary(s):if '元/月' in s:parts = s[:-3].split('-')low = int(int(parts[0]) / 1000)high = int(int(parts[1]) / 1000)return f"{low}-{high}K"elif '元/天' in s:parts = s[:-3].split('-')low = int((int(parts[0]) * 30) / 1000)high = int((int(parts[1]) * 30) / 1000)return f"{low}-{high}K"elif '元/时' in s:parts = s[:-3].split('-')low = int((int(parts[0]) * 8 * 22) / 1000)high = int((int(parts[1]) * 8 * 22) / 1000)return f"{low}-{high}K"elif '元/周' in s:parts = s[:-3].split('-')low = int((int(parts[0]) * 4) / 1000)high = int((int(parts[1]) * 4) / 1000)return f"{low}-{high}K"elif '*' in s:parts = s.split('*')[0]low, high = parts[:-1].split('-')low = int(int(low[:-1]))high = int(int(high[:-1]))return f"{low}-{high}K"elif '·' in s:parts = s.split('·')[0]return partsreturn s# 薪资格式标准化处理
def uniform_salary_format(data):# 检测格式不统一的数据的单位# 定义一个正则表达式模式,用于匹配格式为 "数字-数字K" 的字符串# 其中 ^ 表示字符串的开始,\d+ 表示匹配一个或多个数字,- 表示匹配连字符,$ 表示字符串的结束pattern = r'^\d+-\d+K$|^面议$'# 使用 str.match 方法检查 'salary' 列中的每个值是否匹配定义的模式# ~ 是取反操作符,用于选择不匹配模式的行# 最终将不匹配模式的行筛选出来,存储在 filtered_df 中filtered_df = data[~data['salary'].str.match(pattern)]print(filtered_df['salary'].to_csv(na_rep='nan', index=False))# 处理薪资格式不统一的数据data['salary'] = data['salary'].apply(convert_salary)# 计算薪资列的众数salary_mode = data[data['salary'] != '面议']['salary'].mode()if not salary_mode.empty:mode_value = salary_mode[0]# 将面议替换为众数data.loc[data['salary'] == '面议', 'salary'] = mode_valuereturn data

处理前后的部分薪资数据对比如下。可以看到,处理前薪资格式多样,包含 “元 / 天”“・13 薪” 等不同表述;经标准化转换后,统一为 “数字 - 数字 K” 的简洁格式,让薪资数据更规整,便于后续分析。

在这里插入图片描述


五、技能格式处理

技能数据在原始数据集中存在格式不统一的问题,主要表现为技能标签之间存在连续的逗号(如","),以及字符串首尾可能存在多余的逗号。这种不规范的格式会影响后续对技能数据的分词、统计和分析。因此,需要对技能数据进行格式标准化处理。

处理逻辑

  1. 连续逗号处理:使用正则表达式将连续的多个逗号替换为单个逗号
  2. 首尾逗号处理:去除字符串首尾的逗号,确保技能标签的独立性

技能格式处理代码如下所示:

# 技能统一格式处理
def uniform_skill_format(data):# 使用正则表达式将 'skill' 列中连续的逗号替换为单个逗号# 然后去除字符串首尾的逗号data['skill'] = data['skill'].str.replace(',+', ',', regex=True).str.strip(',')return data

六、拆分薪资列并处理异常值

IQR准则,即四分位距(Interquartile Range, IQR)准则,是一种用于识别数据集中异常值的统计方法。它基于数据集的四分位数来确定哪些观测值可以被视为异常值。这种方法特别适用于偏态分布或小样本的数据集,因为它不依赖于正态分布假设。
四分位距(IQR)简介

  • 四分位数:将一组数据按数值大小排序后分成四个等份,处于三个分割点位置的数值称为四分位数。
  • 第一四分位数(Q1):也叫下四分位数,表示有25%的数据小于等于这个值。
  • 第二四分位数(Q2):即中位数,表示中间值,50%的数据小于等于这个值。
  • 第三四分位数(Q3):也叫上四分位数,表示有75%的数据小于等于这个值。
  • 四分位距(IQR):是第三四分位数与第一四分位数之间的差值,即 IQR = Q3 - Q1。IQR反映了中间50%数据的范围。

IQR准则的应用
根据IQR准则,任何低于 Q1 - 1.5 * IQR 或高于 Q3 + 1.5 * IQR 的值都被认为是潜在的异常值(温和异常值)。更极端的情况,如果某个值低于 Q1 - 3 * IQR 或高于 Q3 + 3 * IQR,则该值被认为是极端异常值。

计算步骤:

  1. 计算Q1、Q2、Q3:首先对数据进行排序,并找到Q1、Q2和Q3。
  2. 计算IQR:使用公式 IQR = Q3 - Q1
  3. 确定界限:计算下限 Lower Bound = Q1 - 1.5 * IQR 和上限 Upper Bound = Q3 + 1.5 * IQR
  4. 识别异常值:任何小于下限或大于上限的数据点都视为异常值。

在数据分析过程中,原始的薪资数据通常以范围形式存在(如"8-15K"),这种格式不利于进行数值计算和统计分析。因此,需要将薪资列拆分为最低薪资和最高薪资两列,并对其中的异常值进行处理。

处理逻辑

  1. 数据拆分:将标准化后的薪资字符串(如"8-15K")拆分为最低薪资和最高薪资两列
  2. 类型转换:将拆分后的薪资数据转换为整数类型,便于后续计算
  3. 异常值检测:使用箱线图方法(IQR准则)检测薪资数据中的异常值
    • 计算下四分位数(Q1)、上四分位数(Q3)和四分位距(IQR)
    • 定义正常范围为 [Q1-1.5IQR, Q3+1.5IQR]
  4. 异常值处理:对超出正常范围的异常值进行边界修正
    • 小于下界的值调整为下界值
    • 大于上界的值调整为上界值
  5. 数据清理:删除原始薪资列和辅助标记列,保留处理后的结果

拆分薪资列并处理异常值的代码如下:

# 拆分薪资列
def split_salary_column(data):# 移除 'salary' 列中字符串里的 'K' 字符,不使用正则表达式匹配salary_series = data['salary'].str.replace('K', '', regex=False)# 将移除 'K' 后的字符串按 '-' 进行分割,并将分割结果展开为两列# 分别存储到新的 'salary_lower' 和 'salary_upper' 列中data[['salary_lower', 'salary_upper']] = salary_series.str.split('-', expand=True)# 将'salary_lower' 和'salary_upper' 列的数据类型转换为整数data['salary_lower'] = data['salary_lower'].astype(int)data['salary_upper'] = data['salary_upper'].astype(int)# 1.5 这个数值已经成为了一种通用的标准和行业惯例,在很多数据分析、统计学教材以及实际的数据处理应用中被广泛使用。# 使用箱线图的方法检测异常值# 计算 salary_lower 列的下四分位数(第25百分位数)Q1_min = data['salary_lower'].quantile(0.25)# 计算 salary_lower 列的上四分位数(第75百分位数)Q3_min = data['salary_lower'].quantile(0.75)# 计算 salary_lower 列的四分位距,即上四分位数与下四分位数的差值IQR_min = Q3_min - Q1_min# 计算 salary_lower 列的下限,小于此值的数据可能为异常值lower_bound_min = Q1_min - 1.5 * IQR_min# 计算 salary_lower 列的上限,大于此值的数据可能为异常值upper_bound_min = Q3_min + 1.5 * IQR_min# 计算 salary_upper 列的下四分位数(第25百分位数)Q1_max = data['salary_upper'].quantile(0.25)# 计算 salary_upper 列的上四分位数(第75百分位数)Q3_max = data['salary_upper'].quantile(0.75)# 计算 salary_upper 列的四分位距,即上四分位数与下四分位数的差值IQR_max = Q3_max - Q1_max# 计算 salary_upper 列的下限,小于此值的数据可能为异常值lower_bound_max = Q1_max - 1.5 * IQR_max# 计算 salary_upper 列的上限,大于此值的数据可能为异常值upper_bound_max = Q3_max + 1.5 * IQR_max# 标记 salary_lower 列中的异常值,若值小于下限或大于上限,则标记为 True,否则为 Falsedata['min_salary_outlier'] = (data['salary_lower'] < lower_bound_min) | (data['salary_lower'] > upper_bound_min)# 标记 salary_upper 列中的异常值,若值小于下限或大于上限,则标记为 True,否则为 Falsedata['max_salary_outlier'] = (data['salary_upper'] < lower_bound_max) | (data['salary_upper'] > upper_bound_max)# 调整 salary_lower 列的异常值data['salary_lower'] = data['salary_lower'].apply(lambda x: lower_bound_min if x < lower_bound_min else (upper_bound_min if x > upper_bound_min else x))# 调整 salary_upper 列的异常值data['salary_upper'] = data['salary_upper'].apply(lambda x: lower_bound_max if x < lower_bound_max else (upper_bound_max if x > upper_bound_max else x))# 这里简单选择删除异常值所在的行,只保留 min_salary_outlier 和 max_salary_outlier 均为 False 的行# data = data[(~data['min_salary_outlier']) & (~data['max_salary_outlier'])]# 删除薪资列和辅助列data.drop(['salary', 'min_salary_outlier', 'max_salary_outlier'], axis=1, inplace=True)return data

拆分并处理异常值后,截取部分数据展示如下。可见 salary_lower(薪资下限)和 salary_upper(薪资上限)两列已规整呈现,数值经清洗后更具分析价值,后续可基于这些标准化数据开展薪资分布、行业对比等分析 。

在这里插入图片描述


七、拆分工作区域列

在招聘数据中,工作区域信息常以复合格式存储(如"城市·行政区"),为便于后续分析岗位的区域分布特征,需对工作区域列进行拆分处理。同时,针对拆分后行政区字段的缺失值,结合行业与区域的关联性进行填充。

处理逻辑

  1. 区域拆分:按固定分隔符(·)将工作区域(job_area)中的行政区(district)拆分出来
  2. 行业关联填充
    • 按公司行业(company_industry)分组
    • 计算每组内行政区的众数,用众数填充对应行业的缺失值
    • 若某行业无有效众数,填充"未知"
  3. 数据清理:删除原始工作区域列,保留拆分后的行政区字段

拆分工作区域列代码如下:

# 拆分工作区域列
def split_job_area_column(data):data['district'] = data['job_area'].str.split('·').str[1]for industry in data['company_industry'].unique():# 从数据中筛选出当前行业的数据industry_df = data[data['company_industry'] == industry]# 计算当前行业所在地区列的众数# 如果众数存在,则取第一个众数;如果众数为空,则使用 '未知' 作为填充值mode_value = industry_df['district'].mode().iloc[0] if not industry_df['district'].mode().empty else '未知'# 使用计算得到的众数填充当前行业所在地区列的缺失值data.loc[data['company_industry'] == industry, 'district'] = data.loc[data['company_industry'] == industry, 'district'].fillna(mode_value)# 删除工作区域列# data.drop('job_area', axis=1, inplace=True)return data

拆分工作区域列后,得到的部分数据展示如下。原 job_area 列以 “城市・行政区・具体地点” 等复合格式呈现,经处理后,district 列精准提取出行政区信息(如五华区、官渡区等 ),数据格式更清晰,便于后续开展区域维度的招聘数据分析。

在这里插入图片描述


八、清洗后的数据集

经过缺失值处理(涵盖各字段针对性填充或删除)、重复值检测删除、薪资数据格式标准化(统一转换为 K 单位并拆分)、技能格式规整(清理冗余逗号)、薪资列拆分及异常值修正、工作区域列拆分提取行政区等一系列数据清洗操作后,得到规范可用的数据集。部分数据截图如下,可见各字段格式统一、内容完整,可支撑后续数据分析。

在这里插入图片描述


九、完整代码

from pathlib import Pathimport pandas as pd
from sqlalchemy import create_enginedef load_data(csv_file_path):try:data = pd.read_csv(csv_file_path)return dataexcept FileNotFoundError:print("未找到指定的 CSV 文件,请检查文件路径和文件名。")except Exception as e:print(f"加载数据时出现错误: {e}")# 保存清洗后的数据为csv文件
def save_to_csv(data, csv_file_path):# 使用 pathlib 处理文件路径path = Path(csv_file_path)# 检查文件所在目录是否存在,如果不存在则创建path.parent.mkdir(parents=True, exist_ok=True)data.to_csv(csv_file_path, index=False, encoding='utf-8-sig', mode='w', header=True)print(f'清洗后的数据已保存到 {csv_file_path} 文件')# 读取MySQL中的数据
def load_from_mysql(table_name):# 创建数据库引擎实例engine = create_engine(f'mysql+mysqlconnector://root:zxcvbq@127.0.0.1:3306/position')data = pd.read_sql_table(table_name, engine)return data# 保存清洗后的数据到MySQL数据库
def save_to_mysql(data, table_name):# 创建一个 SQLAlchemy 引擎,用于连接 MySQL 数据库# 使用 mysqlconnector 作为 MySQL 的驱动程序# 数据库连接信息包括用户名 root、密码 zxcvbq、主机地址 127.0.0.1、端口 3306 以及数据库名 positionengine = create_engine(f'mysql+mysqlconnector://root:zxcvbq@127.0.0.1:3306/position')# 将 DataFrame 中的数据写入到 MySQL 数据库中# table_name 是要写入的表名# con 参数指定了数据库连接引擎# index=False 表示不将 DataFrame 的索引写入数据库# if_exists='replace' 表示如果表已经存在,则先删除原表,再创建新表并写入数据data.to_sql(table_name, con=engine, index=False, if_exists='replace')print(f'清洗后的数据已保存到 {table_name} 表')def check_data(data):print('数据基本信息:')data.info()# 查看数据前几行信息print('数据前几行内容信息:')print(data.head().to_csv(na_rep='nan'))# 查看所有列的唯一值print('所有列的唯一值:')for column in data.columns:print(f'{column} 列的唯一值:')print(data[column].unique())# 缺失值检测及处理
def handle_missing_values(data):print("缺失值处理前各列缺失值数量:")print(data.isnull().sum())# 处理education字段的缺失值data.dropna(subset=['education'], inplace=True)# financing_status(融资状态)字段# 公司的融资状态可能和公司所处行业有一定联系,不同行业的公司融资情况可能不同。# 因此,我将依据 company_industry(公司行业)分组,用每组内 financing_status 的众数进行填充,以更贴合实际情况。# 遍历数据中公司行业列的唯一值for industry in data['company_industry'].unique():# 从数据中筛选出当前行业的数据industry_df = data[data['company_industry'] == industry]# 计算当前行业融资状态列的众数# 如果众数存在,则取第一个众数;如果众数为空,则使用 '未知' 作为填充值mode_value = industry_df['financing_status'].mode().iloc[0] if not industry_df['financing_status'].mode().empty else '未知'# 使用计算得到的众数填充当前行业融资状态列的缺失值data.loc[data['company_industry'] == industry, 'financing_status'] = data.loc[data['company_industry'] == industry, 'financing_status'].fillna(mode_value)# 计算 company_size 列的众数,如果众数存在则取第一个众数作为填充值,若不存在则使用 '未知' 作为填充值mode_company_size = data['company_size'].mode().iloc[0] if not data['company_size'].mode().empty else '未知'# 使用计算得到的众数填充 company_size 列中的缺失值data['company_size'] = data['company_size'].fillna(mode_company_size)# 使用 '无明确技能要求' 填充 skill 列中的缺失值,inplace=True 表示直接在原数据上修改data['skill'].fillna('无明确技能要求', inplace=True)# 使用 '常规福利' 填充 benefits 列中的缺失值,inplace=True 表示直接在原数据上修改data['benefits'].fillna('常规福利', inplace=True)print("缺失值处理后各列缺失值数量:")print(data.isnull().sum())return data# 重复数据检测及处理
def handle_duplicate_data(data):duplicate_rows = len(data[data.duplicated()])print("处理前重复行数量:", duplicate_rows)if duplicate_rows > 0:data.drop_duplicates(inplace=True)duplicate_rows = len(data[data.duplicated()])print("处理后重复行数量:", duplicate_rows)return data# 统一薪资数据格式
def convert_salary(s):if '元/月' in s:parts = s[:-3].split('-')low = int(int(parts[0]) / 1000)high = int(int(parts[1]) / 1000)return f"{low}-{high}K"elif '元/天' in s:parts = s[:-3].split('-')low = int((int(parts[0]) * 30) / 1000)high = int((int(parts[1]) * 30) / 1000)return f"{low}-{high}K"elif '元/时' in s:parts = s[:-3].split('-')low = int((int(parts[0]) * 8 * 22) / 1000)high = int((int(parts[1]) * 8 * 22) / 1000)return f"{low}-{high}K"elif '元/周' in s:parts = s[:-3].split('-')low = int((int(parts[0]) * 4) / 1000)high = int((int(parts[1]) * 4) / 1000)return f"{low}-{high}K"elif '*' in s:parts = s.split('*')[0]low, high = parts[:-1].split('-')low = int(int(low[:-1]))high = int(int(high[:-1]))return f"{low}-{high}K"elif '·' in s:parts = s.split('·')[0]return partsreturn s# 薪资格式标准化处理
def uniform_salary_format(data):# 检测格式不统一的数据的单位# 定义一个正则表达式模式,用于匹配格式为 "数字-数字K" 的字符串# 其中 ^ 表示字符串的开始,\d+ 表示匹配一个或多个数字,- 表示匹配连字符,$ 表示字符串的结束pattern = r'^\d+-\d+K$|^面议$'# 使用 str.match 方法检查 'salary' 列中的每个值是否匹配定义的模式# ~ 是取反操作符,用于选择不匹配模式的行# 最终将不匹配模式的行筛选出来,存储在 filtered_df 中filtered_df = data[~data['salary'].str.match(pattern)]print(filtered_df['salary'].to_csv(na_rep='nan', index=False))# 处理薪资格式不统一的数据data['salary'] = data['salary'].apply(convert_salary)# 计算薪资列的众数salary_mode = data[data['salary'] != '面议']['salary'].mode()if not salary_mode.empty:mode_value = salary_mode[0]# 将面议替换为众数data.loc[data['salary'] == '面议', 'salary'] = mode_valuereturn data# 技能统一格式处理
def uniform_skill_format(data):# 使用正则表达式将 'skill' 列中连续的逗号替换为单个逗号# 然后去除字符串首尾的逗号data['skill'] = data['skill'].str.replace(',+', ',', regex=True).str.strip(',')return data# 拆分薪资列
def split_salary_column(data):# 移除 'salary' 列中字符串里的 'K' 字符,不使用正则表达式匹配salary_series = data['salary'].str.replace('K', '', regex=False)# 将移除 'K' 后的字符串按 '-' 进行分割,并将分割结果展开为两列# 分别存储到新的 'salary_lower' 和 'salary_upper' 列中data[['salary_lower', 'salary_upper']] = salary_series.str.split('-', expand=True)# 将'salary_lower' 和'salary_upper' 列的数据类型转换为整数data['salary_lower'] = data['salary_lower'].astype(int)data['salary_upper'] = data['salary_upper'].astype(int)# 1.5 这个数值已经成为了一种通用的标准和行业惯例,在很多数据分析、统计学教材以及实际的数据处理应用中被广泛使用。# 使用箱线图的方法检测异常值# 计算 salary_lower 列的下四分位数(第25百分位数)Q1_min = data['salary_lower'].quantile(0.25)# 计算 salary_lower 列的上四分位数(第75百分位数)Q3_min = data['salary_lower'].quantile(0.75)# 计算 salary_lower 列的四分位距,即上四分位数与下四分位数的差值IQR_min = Q3_min - Q1_min# 计算 salary_lower 列的下限,小于此值的数据可能为异常值lower_bound_min = Q1_min - 1.5 * IQR_min# 计算 salary_lower 列的上限,大于此值的数据可能为异常值upper_bound_min = Q3_min + 1.5 * IQR_min# 计算 salary_upper 列的下四分位数(第25百分位数)Q1_max = data['salary_upper'].quantile(0.25)# 计算 salary_upper 列的上四分位数(第75百分位数)Q3_max = data['salary_upper'].quantile(0.75)# 计算 salary_upper 列的四分位距,即上四分位数与下四分位数的差值IQR_max = Q3_max - Q1_max# 计算 salary_upper 列的下限,小于此值的数据可能为异常值lower_bound_max = Q1_max - 1.5 * IQR_max# 计算 salary_upper 列的上限,大于此值的数据可能为异常值upper_bound_max = Q3_max + 1.5 * IQR_max# 标记 salary_lower 列中的异常值,若值小于下限或大于上限,则标记为 True,否则为 Falsedata['min_salary_outlier'] = (data['salary_lower'] < lower_bound_min) | (data['salary_lower'] > upper_bound_min)# 标记 salary_upper 列中的异常值,若值小于下限或大于上限,则标记为 True,否则为 Falsedata['max_salary_outlier'] = (data['salary_upper'] < lower_bound_max) | (data['salary_upper'] > upper_bound_max)# 调整 salary_lower 列的异常值data['salary_lower'] = data['salary_lower'].apply(lambda x: lower_bound_min if x < lower_bound_min else (upper_bound_min if x > upper_bound_min else x))# 调整 salary_upper 列的异常值data['salary_upper'] = data['salary_upper'].apply(lambda x: lower_bound_max if x < lower_bound_max else (upper_bound_max if x > upper_bound_max else x))# 这里简单选择删除异常值所在的行,只保留 min_salary_outlier 和 max_salary_outlier 均为 False 的行# data = data[(~data['min_salary_outlier']) & (~data['max_salary_outlier'])]# 删除薪资列和辅助列data.drop(['salary', 'min_salary_outlier', 'max_salary_outlier'], axis=1, inplace=True)return data# 拆分公司规模列(company_size)
def split_company_size_column(data):# 查看公司规模列的唯一值company_size_unique = data['company_size'].unique()print(company_size_unique.tolist())# 移除公司规模列中字符串里的 '人以上'或'人' 字符,不使用正则表达式匹配company_size_series = data['company_size'].str.replace('人', '', regex=False)company_size_series = company_size_series.str.replace('以上', '', regex=False)data[['company_size_lower', 'company_size_upper']] = company_size_series.str.split('-', expand=True)return data# 拆分工作区域列
def split_job_area_column(data):data['district'] = data['job_area'].str.split('·').str[1]for industry in data['company_industry'].unique():# 从数据中筛选出当前行业的数据industry_df = data[data['company_industry'] == industry]# 计算当前行业所在地区列的众数# 如果众数存在,则取第一个众数;如果众数为空,则使用 '未知' 作为填充值mode_value = industry_df['district'].mode().iloc[0] if not industry_df['district'].mode().empty else '未知'# 使用计算得到的众数填充当前行业所在地区列的缺失值data.loc[data['company_industry'] == industry, 'district'] = data.loc[data['company_industry'] == industry, 'district'].fillna(mode_value)# 删除工作区域列# data.drop('job_area', axis=1, inplace=True)return dataif __name__ == '__main__':df = load_data('../data/original_data/position_dataset.csv')check_data(df)df = handle_missing_values(df)df = handle_duplicate_data(df)df = uniform_salary_format(df)df = uniform_skill_format(df)df = split_salary_column(df)df = split_job_area_column(df)# df = split_company_size_column(df)check_data(df)save_to_csv(df, '../data/data_cleaning_result/cleaned_position_dataset.csv')# save_to_mysql(df, 'cleaned_position_dataset')
http://www.lqws.cn/news/556687.html

相关文章:

  • 《自动控制原理 》- 第 1 章 自动控制的基本原理与方式
  • Linux基本指令篇 —— more指令
  • PostgreSQL 中,若需显示 不在 `IN` 子句列表中的数据
  • SQL常用命令
  • 阿里云Ubuntu服务器上安装MySQL并配置远程连接
  • 网络缓冲区
  • Solidity学习 - 错误处理
  • ffpaly播放 g711a音频命令
  • 【学习笔记】深入理解Java虚拟机学习笔记——第12章 Java内存模型与线程
  • 设计模式之抽象工厂模式
  • Docker 入门教程(五):Docker 命令思维导图
  • 【分布式机架感知】分布式机架感知能力的主流存储系统与数据库软件
  • 微处理原理与应用篇---STM32寄存器控制GPIO
  • 矩阵的条件数(Condition Number of a Matrix)
  • 华为云Flexus+DeepSeek征文 | 基于华为云ModelArts Studio安装NoteGen AI笔记应用程序
  • Learning PostgresSQL读书笔记: 第11章 Transactions, MVCC, WALs, and Checkpoints
  • 基于Docker的mosquitto安装测试
  • FPGA设计的上板调试
  • python多线程详细讲解
  • Python爬虫实战:研究difflib库相关技术
  • Ubuntu 主机通过 `enp4s0` 向开发板共享网络的完整步骤
  • 默克树技术原理
  • 组成原理--指令指令集寻址方式的介绍
  • ubuntu-server 与 ubuntu-live-server 的区别 笔记250628
  • Java锁机制知识点
  • 网关ARP防护的措施
  • 【开源初探】基于Qwen2.5VL的OCRFlux
  • vue-28(服务器端渲染(SSR)简介及其优势)
  • LNA设计
  • macOS生成密钥对教程