文章目錄
- 一、數據集介紹
- 二、缺失值處理
- 三、重復值處理
- 四、薪資數據格式處理
- 五、技能格式處理
- 六、拆分薪資列并處理異常值
- 七、拆分工作區域列
- 八、清洗后的數據集
- 九、完整代碼
一、數據集介紹
這份昆明職位數據集源自 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,數據集中已不存在缺失值,處理達到預期效果。
三、重復值處理
在數據清洗階段,需要識別并處理完全重復的記錄,即兩行數據的所有字段值均相同的情況。這類重復數據可能源于數據采集過程中的冗余或系統誤差,會導致后續分析結果出現偏差。因此,采用以下策略進行處理:
處理邏輯:
- 檢測數據集中完全重復的行數
- 若存在重復行,則刪除重復記錄,僅保留其中一行
- 驗證處理結果,確保數據集中無重復記錄
重復值處理代碼如下所示:
# 重復數據檢測及處理
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”(千)為單位的格式。
處理邏輯:
- 檢測非標準格式:通過正則表達式識別不符合"數字-數字K"或"面議"格式的薪資數據
- 單位轉換:將不同時間單位的薪資數據統一轉換為月薪(K)表示
- 元/月:直接除以1000轉換為K
- 元/天:乘以30天再除以1000轉換為K
- 元/時:乘以8小時/天 × 22天/月再除以1000轉換為K
- 元/周:乘以4周/月再除以1000轉換為K
- 特殊格式處理:
- 帶"*"的格式:提取數字部分并轉換為K
- 帶"·“的格式:提取”·"前的部分
- 處理"面議"情況:用非面議薪資的眾數替換"面議"值,以保留數據統計特性
薪資數據格式處理代碼如下所示:
# 統一薪資數據格式
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” 的簡潔格式,讓薪資數據更規整,便于后續分析。
五、技能格式處理
技能數據在原始數據集中存在格式不統一的問題,主要表現為技能標簽之間存在連續的逗號(如","),以及字符串首尾可能存在多余的逗號。這種不規范的格式會影響后續對技能數據的分詞、統計和分析。因此,需要對技能數據進行格式標準化處理。
處理邏輯:
- 連續逗號處理:使用正則表達式將連續的多個逗號替換為單個逗號
- 首尾逗號處理:去除字符串首尾的逗號,確保技能標簽的獨立性
技能格式處理代碼如下所示:
# 技能統一格式處理
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
,則該值被認為是極端異常值。計算步驟:
- 計算Q1、Q2、Q3:首先對數據進行排序,并找到Q1、Q2和Q3。
- 計算IQR:使用公式
IQR = Q3 - Q1
。- 確定界限:計算下限
Lower Bound = Q1 - 1.5 * IQR
和上限Upper Bound = Q3 + 1.5 * IQR
。- 識別異常值:任何小于下限或大于上限的數據點都視為異常值。
在數據分析過程中,原始的薪資數據通常以范圍形式存在(如"8-15K"),這種格式不利于進行數值計算和統計分析。因此,需要將薪資列拆分為最低薪資和最高薪資兩列,并對其中的異常值進行處理。
處理邏輯:
- 數據拆分:將標準化后的薪資字符串(如"8-15K")拆分為最低薪資和最高薪資兩列
- 類型轉換:將拆分后的薪資數據轉換為整數類型,便于后續計算
- 異常值檢測:使用箱線圖方法(IQR準則)檢測薪資數據中的異常值
- 計算下四分位數(Q1)、上四分位數(Q3)和四分位距(IQR)
- 定義正常范圍為 [Q1-1.5IQR, Q3+1.5IQR]
- 異常值處理:對超出正常范圍的異常值進行邊界修正
- 小于下界的值調整為下界值
- 大于上界的值調整為上界值
- 數據清理:刪除原始薪資列和輔助標記列,保留處理后的結果
拆分薪資列并處理異常值的代碼如下:
# 拆分薪資列
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(薪資上限)兩列已規整呈現,數值經清洗后更具分析價值,后續可基于這些標準化數據開展薪資分布、行業對比等分析。
七、拆分工作區域列
在招聘數據中,工作區域信息常以復合格式存儲(如"城市·行政區"),為便于后續分析崗位的區域分布特征,需對工作區域列進行拆分處理。同時,針對拆分后行政區字段的缺失值,結合行業與區域的關聯性進行填充。
處理邏輯:
- 區域拆分:按固定分隔符(·)將工作區域(
job_area
)中的行政區(district
)拆分出來 - 行業關聯填充:
- 按公司行業(
company_industry
)分組 - 計算每組內行政區的眾數,用眾數填充對應行業的缺失值
- 若某行業無有效眾數,填充"未知"
- 按公司行業(
- 數據清理:刪除原始工作區域列,保留拆分后的行政區字段
拆分工作區域列代碼如下:
# 拆分工作區域列
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')