據(jù)預(yù)處理:CSV打散與訓練測試集拆分實戰(zhàn)指南)
簡介面向Python數(shù)據(jù)分析與機器學習初學者這份資源聚焦CSV數(shù)據(jù)集的打散與拆分幫助解決建模前訓練集、測試集劃分不隨機、分布不一致等常見問題。資源包為zip壓縮格式共4個文件、約7KB包含1個Python處理腳本及3份csv樣例數(shù)據(jù)腳本覆蓋pandas讀取、數(shù)據(jù)質(zhì)量檢查、隨機打散、按比例切片生成訓練集與測試集等完整過程樣例數(shù)據(jù)便于反復(fù)驗證不同拆分比例下結(jié)果的變化。目前已有109人學習下載。通過案例可掌握DataFrame的sample和iloc等關(guān)鍵用法理解隨機因子對模型評估可信度的影響并建立一套可復(fù)用的建模前數(shù)據(jù)預(yù)處理路徑幫助后續(xù)特征工程、模型訓練與評估環(huán)節(jié)更順暢。1. 打散與拆分機器學習流水線里最低調(diào)的兩個環(huán)節(jié)Python對CSV數(shù)據(jù)集的處理流程里打散和拆分是我見過最容易被跳過、又最直接影響模型評估可信度的兩步。很多人拿到CSV后直接調(diào)用train_test_split從不關(guān)心原始文件的行順序是不是按類別、按來源或者按時間排好的結(jié)果訓練集和測試集的分布嚴重不一致線下指標虛高模型一上線就翻車。這份資源帶的processdata.py示例和data0數(shù)據(jù)組就是針對這個場景的標準解法先把整個數(shù)據(jù)集隨機打散再按7:3切成訓練集和測試集。適合剛?cè)腴T機器學習、用CSV自己組裝數(shù)據(jù)集的同學也適合被數(shù)據(jù)順序坑過、想把預(yù)處理流程規(guī)范化的從業(yè)者。2. 讀入 CSV 先做三道檢查read_csv 參數(shù)、缺失值與原始副本CSV 文件在磁盤上就是一個文本文件讀進來的第一眼決定后面所有步驟是否靠譜。這一步的核心不是“把數(shù)據(jù)讀進來”而是“確認讀進來的數(shù)據(jù)跟源頭一致”包括列類型、缺失值標記和行數(shù)。我習慣在讀入之后先打印 shape、dtypes 和 head 三件套再決定后續(xù)處理策略。很多人跳過這一步直接進打散遇到亂碼和類型問題再回頭查反而更浪費時間。2.1 用 read_csv 讀入先對齊分隔符、編碼和列類型import pandas as pd df pd.read_csv( data0.csv, encodingutf-8, na_values[, NA, null], keep_default_naTrue, ) print(shape:, df.shape) print(columns:, df.columns.tolist()) print(df.dtypes) print(df.head())邏輯說明pd.read_csv 是 pandas 讀取 CSV 的統(tǒng)一入口返回 DataFrame。這里顯式傳了 encoding 和 na_values 兩個參數(shù)目的是把文件中可能出現(xiàn)的空字符串、NA、null 統(tǒng)一識別為缺失值避免它們被當成普通字符串混進數(shù)值列。shape 和 dtypes 能快速發(fā)現(xiàn)兩類問題行數(shù)比預(yù)期少說明文件里有換行轉(zhuǎn)義問題列類型為 object 說明數(shù)值列被讀成了字符串。參數(shù)說明read_csv 默認分隔符是英文逗號制表符文件需要手動傳 sep\tencoding 在 Windows 上生成的 CSV 經(jīng)常是 gbk如果 utf-8 報 UnicodeDecodeError改成 encodinggbk 再試。na_values 是額外缺失值標記列表keep_default_naTrue 表示保留 NaN、None 等默認標記兩者疊加使用。Excel 導(dǎo)出的 CSV 偶爾帶 UTF-8 BOM讀進來后第一列列名會帶 \ufeff 前綴打印 columns 一眼就能看出來這時候把 encoding 換成 utf-8-sig 就能解決。read_csv 參數(shù)作用常見取值場景sep指定分隔符默認逗號制表符文件用 \tencoding文件編碼中文文件常見 utf-8 / gbk / utf-8-signa_values追加缺失值標記把空串、NA、null 統(tǒng)一收進來dtype指定列類型讀取時就把類別列限成 category2.2 缺失值和重復(fù)值先查清楚再決定怎么清print(df.isnull().sum()) print(df.duplicated().sum())邏輯說明isnull().sum() 逐列統(tǒng)計缺失值數(shù)量duplicated().sum() 統(tǒng)計完全重復(fù)的行數(shù)。這一步的作用是給清洗策略提供依據(jù)缺失值占比高到一定程度的列要么填充要么直接丟棄重復(fù)行可以直接刪除。重點在于清洗動作必須在拆分之前完成否則訓練集和測試集的缺失值比例、總行數(shù)都會各自偏掉。注意dropna 默認刪除包含任何缺失值的整行如果先拆分后清洗訓練集和測試集各自丟掉的行數(shù)不一樣后續(xù)模型訓練和評估的數(shù)據(jù)規(guī)模就不可比。我一般先用 isnull 看分布缺失集中在某一列且占比低于 5% 時直接 dropna高于 20% 時考慮把這個列放進數(shù)據(jù)分析報告里單獨處理而不是盲目刪除。重復(fù)行的處理類似先打印數(shù)量再決定是否調(diào)用 drop_duplicates不要想當然。2.3 留一份原始副本打散拆分前的后悔藥raw_df df.copy()邏輯說明copy() 是對當前 DataFrame 的深拷貝后續(xù)打散、刪除行、修改 dtype 都不會影響 raw_df。數(shù)據(jù)預(yù)處理的流程一旦展開清洗動作是不可逆的留一份原始副本可以讓每一步都回退對比排查問題時也能明確“是清洗改壞了還是模型調(diào)參出了問題”。參數(shù)說明copy(deepTrue) 是默認的深拷貝行為會重新申請內(nèi)存復(fù)制數(shù)據(jù)數(shù)據(jù)量大時內(nèi)存占用翻一倍。如果機器內(nèi)存緊張也可以用淺拷貝先頂著但淺拷貝和原對象共享部分內(nèi)存修改時互相影響。踩過這個坑之后我對于這種小規(guī)模 CSV 數(shù)據(jù)集一律用深拷貝省得給自己挖坑。3. 打散不是在湊隨機感sample(frac1) 的機制與參數(shù)細節(jié)打散的目的是打破原始文件里可能存在的順序規(guī)律。CSV 文件的行順序往往不是隨機的要么按采集時間排列要么按來源分組甚至可能是某個 SQL 查詢結(jié)果直接導(dǎo)出前 300 行是 A 類、后 300 行是 B 類。如果不打散就直接按比例切切出來的訓練集和測試集分布會非常難看。3.1 數(shù)據(jù)順序里藏著的偏見一個具體的反面案例假設(shè) data0.csv 有 600 行前 300 行都是類別 A后 300 行都是類別 B。直接按 7:3 切訓練集就是前 420 行包含 300 個 A 和 120 個 B測試集是后 180 行全部是 B。模型在訓練集里看到的 B 樣本偏少測試時面對的又是清一色 B評估指標完全沒有參考價值。這個例子有點極端但真實場景里按時間段、按設(shè)備、按地區(qū)排序的情況非常普遍危害是一樣的。數(shù)據(jù)順序的問題在于它不會顯式報錯。模型訓練流程能正常跑完損失值能正常下降但評估結(jié)果不可信。很多從業(yè)者遇到線上效果和線下對不上第一反應(yīng)是調(diào)模型很少有人會回頭檢查數(shù)據(jù)拆分的順序。所以打散不僅是流程需要更是一個讓評估結(jié)果具備參考價值的前置條件。3.2 用 sample(frac1) 打散全量無放回抽樣shuffled_df df.sample(frac1, random_state42)邏輯說明DataFrame.sample 是從原表中隨機抽取行的函數(shù)frac1 表示抽取比例是 100%也就是全量抽一遍。由于抽樣結(jié)果保留了原表的所有行但順序是隨機生成的所以等價于一次打散shuffle。抽樣的本質(zhì)是無放回抽樣每一行最多被抽中一次不會有重復(fù)行混進來。參數(shù)說明random_state 是隨機數(shù)種子固定成任意整數(shù)比如 42之后每次運行打散結(jié)果完全一致這是一種“可復(fù)現(xiàn)的隨機”。如果不傳這個參數(shù)每次跑出來的順序都不同模型指標的波動會讓你分不清是數(shù)據(jù)問題還是模型問題。隨機數(shù)種子的取值范圍就是整數(shù)沒有其他限制團隊協(xié)作時統(tǒng)一約定一個固定值即可。sample 內(nèi)部走的是 numpy 的隨機數(shù)機制所以用 numpy.random.seed 全局設(shè)種子也能影響它。但直接傳 random_state 更清晰作用范圍只限定在這一次調(diào)用里不會污染其他隨機過程。如果你習慣 numpy 風格也可以寫成 df.loc[numpy.random.permutation(df.index)]效果類似但要多處理一次索引對齊pandas 的 sample 一行搞定我更推薦后者。這里補充一個邊界frac 可以小于 1比如 frac0.8 表示抽取 80% 的行這在超大數(shù)據(jù)集里可以做降采樣。但打散這個場景必須用 frac1否則會少行后面拆分時行數(shù)對不上問題就很難排查了。3.3 打散后立刻重置索引別讓舊行號坑到你shuffled_df shuffled_df.reset_index(dropTrue)邏輯說明sample 返回的 DataFrame 行索引還是原始 CSV 里的行號比如原表第 300 行在被抽到后仍然帶著索引 300。如果不重置后面用 iloc 按位置切片時不受影響但一旦用 loc 按索引取值、或者做 merge 操作就會發(fā)現(xiàn)取出來的行是“跳著”的跟打印結(jié)果對不上。reset_index(dropTrue) 把索引重新變成從 0 開始的連續(xù)整數(shù)同時丟棄舊索引列。參數(shù)說明dropTrue 的意思是舊索引直接扔掉而不是作為一列保留。如果漏掉這個參數(shù)舊索引會被塞進一個叫 index 的新列后面拆分和導(dǎo)出 CSV 時這個列會混進數(shù)據(jù)屬于很隱蔽的臟數(shù)據(jù)來源。這個問題在數(shù)據(jù)量大時尤其難發(fā)現(xiàn)因為打印前面幾行時 index 列看起來就像正常的數(shù)據(jù)列。4. 拆分訓練集和測試集手動 iloc 切片與 train_test_split 怎么選數(shù)據(jù)打散之后拆分的核心邏輯就只剩“按位置切一刀”。但這一刀怎么切、要不要分層、用 pandas 還是用 sklearn決定了后續(xù)評估的可信度。我的判斷標準很簡單數(shù)據(jù)能隨機打散就用 train_test_split數(shù)據(jù)必須保持順序比如時間序列就手動切片。4.1 手動切片先算整數(shù)邊界再 ilocsplit_idx int(len(shuffled_df) * 0.7) train_df shuffled_df.iloc[:split_idx].copy() test_df shuffled_df.iloc[split_idx:].copy() print(ftrain: {len(train_df)} rows, test: {len(test_df)} rows)邏輯說明先乘比例再取整得到訓練集的邊界下標。iloc[:split_idx] 取前 70% 行iloc[split_idx:] 取剩下的 30%兩者加起來正好等于原始行數(shù)不會重疊也不會漏行。copy() 在這里不是多余的iloc 切片返回的是原對象的視圖如果后續(xù)對測試集做原地修改比如填充缺失值可能連帶影響原 DataFrame加上 copy() 之后兩個集合就徹底獨立了。參數(shù)說明train_ratio0.7 只是一個約定俗成的起點樣本量小時可以提到 0.8樣本量很大時用 0.9 甚至更高都常見但測試集至少要保證 500 行以上否則評估指標的置信區(qū)間太寬。比例的選擇取決于數(shù)據(jù)規(guī)模和問題難度沒有絕對最優(yōu)值。切完之后順手打印兩個集合的行數(shù)確認 train 加 test 等于原始行數(shù)這個對賬動作能提前發(fā)現(xiàn)邊界錯誤。手動切片最典型的適用場景是時間序列。比如 data0.csv 是某個傳感器按小時采集的數(shù)據(jù)必須先按時間升序排序再直接按位置切分保證訓練集里的時間點全部在測試集之前。如果這種數(shù)據(jù)先打散再切未來數(shù)據(jù)混進訓練集模型評估結(jié)果會樂觀到失真上線后真實表現(xiàn)斷崖式下跌。4.2 換成 train_test_split自帶隨機和分層from sklearn.model_selection import train_test_split train_df, test_df train_test_split( shuffled_df, test_size0.3, random_state42, stratifyshuffled_df[label] if label in shuffled_df.columns else None, )邏輯說明sklearn 的 train_test_split 內(nèi)部會先做一次隨機打散再做切分所以這里傳入已經(jīng)打過散的 DataFrame 也沒問題只是多一次無謂的隨機操作結(jié)果不受影響。stratify 參數(shù)是它比手動切片最大的優(yōu)勢傳入類別列之后函數(shù)會按該類別的比例分配訓練集和測試集保證兩邊各類別占比基本一致。參數(shù)說明test_size0.3 與手動切片的 30% 等價random_state 同樣是固定種子。當數(shù)據(jù)類別不平衡比如正樣本只占 5% 時普通隨機切分很容易把測試集里的正樣本丟得只剩幾個stratify 能穩(wěn)妥解決這個問題。對比維度手動 iloc 切片train_test_split適用場景時間序列、保持順序常規(guī)隨機拆分、類別不平衡隨機打散需要自己先 sample內(nèi)部自帶 shuffle分層支持不支持需自行實現(xiàn)stratify 參數(shù)直接傳類別列依賴pandas 自帶需要安裝 scikit-learn5. 避坑打散拆分階段最容易翻車的五個地方這一章是實際跑數(shù)據(jù)時最容易踩的坑覆蓋面主要是順序、種子、索引、分層和編碼這五個維度。每條按“現(xiàn)象、原因、解決”的結(jié)構(gòu)來寫都是我踩過或者幫別人排查過的真實問題。5.1 缺失值處理順序不一致訓練和測試各缺一截現(xiàn)象拆分前沒統(tǒng)計缺失值拆分后分別對訓練集和測試集 dropna結(jié)果訓練集丟掉 30 行、測試集丟掉 18 行兩邊數(shù)據(jù)量不同模型評估時訓練集和測試集的行數(shù)比例變成了 70:28 而不是 70:30。原因缺失值分布并不是按行均勻鋪開的先拆分再清洗等于讓兩個集合各自獨立丟失樣本破壞了原始分布而且兩個集合清洗后的行數(shù)無法對齊。解決在 read_csv 之后、sample 之前完成所有清洗動作把 dropna 或 fillna 的結(jié)果記錄下來確認刪除行數(shù)、剩余行數(shù)后再進入打散拆分。這樣一來訓練集和測試集只是原始清洗后數(shù)據(jù)的兩個切片行數(shù)比例嚴格受控。5.2 random_state 不固定指標對不上號現(xiàn)象同一份數(shù)據(jù)、同一個模型今天跑 AUC 是 0.85明天跑變成 0.83你以為是模型問題其實是數(shù)據(jù)拆分的隨機種子變了訓練集和測試集的內(nèi)容已經(jīng)完全不同。原因sample 和 train_test_split 默認都是隨機過程不固定種子每次打散順序都不同拆分結(jié)果就不同下游所有指標都跟著抖。解決把 random_state42 寫死在處理腳本里或作為函數(shù)參數(shù)傳入保證每一次運行拿到相同的訓練集和測試集。這個習慣在排查模型問題時能省掉很多無謂的懷疑不然你會花大量時間調(diào)參最后發(fā)現(xiàn)是數(shù)據(jù)每次都不同。5.3 打散后沒有重置索引loc 取值“跳行”現(xiàn)象shuffled_df 打印出來順序是正常的但用 loc[100:200] 取出來是亂七八糟的行甚至報 KeyError。原因打散后索引還是原始行號loc 是按索引標簽取值的它根本不知道你期望的是“第 100 到第 200 行”它拿標簽 100 到 200 去匹配匹配到哪一行算哪一行。解決打散后緊跟一行 reset_index(dropTrue)讓索引重新連續(xù)。如果已經(jīng)拆完了才發(fā)現(xiàn)那就回退到打散步驟重新走一遍這種情況沒有捷徑只能從頭跑。5.4 類別不平衡時忽略分層小類樣本在測試集里“失蹤”現(xiàn)象數(shù)據(jù)里正樣本占 3%隨機切分后測試集 5 萬行里只有幾十個正樣本評估曲線抖得沒法看精確率召回率每次跑都差很多。原因隨機切分不保證類別比例少數(shù)類樣本量本來就小隨機分完兩邊數(shù)量波動很大測試集里的小類樣本少到無法支撐穩(wěn)定評估。解決用 train_test_split 的 stratify 參數(shù)按類別列分層抽樣確保訓練集和測試集的每個類別占比都和全量數(shù)據(jù)一致。如果堅持用手動切片那就得按類別分組、各組內(nèi)部按比例切、最后再合并工作量大且容易出錯。5.5 中文 CSV 亂碼與路徑報錯現(xiàn)象read_csv 讀取時拋 UnicodeDecodeError或者 CSV 用 Excel 打開正常但 pandas 讀出來全是亂碼。原因文件本身是 gbk 或 gb2312 編碼pandas 默認按 utf-8 解碼解碼對不上就報錯或出現(xiàn)亂碼。解決讀入時指定 encodinggbk或者讀取前先用文本編輯器把文件另存為 utf-8 編碼一勞永逸。文件路徑盡量用英文Windows 下中文路徑配合 pandas 老版本有時會莫名報錯把數(shù)據(jù)文件放到純英文目錄下能少折騰幾次。這類問題通常在換機器、換數(shù)據(jù)源后隨機出現(xiàn)寫代碼時養(yǎng)成顯式傳 encoding 的習慣能規(guī)避大部分。6. 把流程封裝成 process_csv一致性檢查與批量導(dǎo)出把前面的步驟收攏成一個函數(shù)日常處理新數(shù)據(jù)集時只需要改文件路徑和比例就能穩(wěn)定復(fù)現(xiàn)整個過程。封裝的意義在于把順序固定下來減少手工操作帶來的偏差也能讓團隊其他人拿到腳本后跑出完全一致的結(jié)果。6.1 封裝 function打散、拆分、檢查、導(dǎo)出一次完成import pandas as pd def process_csv(input_path, train_path, test_path, train_ratio0.7, random_state42): df pd.read_csv(input_path, encodingutf-8) df df.drop_duplicates().reset_index(dropTrue) df df.sample(frac1, random_staterandom_state) df df.reset_index(dropTrue) split_idx int(len(df) * train_ratio) train_df df.iloc[:split_idx].copy() test_df df.iloc[split_idx:].copy() numeric_cols df.select_dtypes(includenumber).columns if len(numeric_cols) 0: diff train_df[numeric_cols].mean() - test_df[numeric_cols].mean() print(均值差最大值:, diff.abs().max()) train_df.to_csv(train_path, indexFalse, encodingutf-8) test_df.to_csv(test_path, indexFalse, encodingutf-8) return train_df, test_df邏輯說明函數(shù)先把重復(fù)行清掉并重置索引再打散再按比例切片最后用數(shù)值列均值差做一次快速一致性檢查。均值差絕對值越小說明訓練集和測試集的數(shù)值分布越接近如果這個值明顯偏大優(yōu)先懷疑數(shù)據(jù)沒打散或者數(shù)據(jù)本身就存在按某個字段分組的強規(guī)律。導(dǎo)出的 CSV 都加了 indexFalse避免把行號寫進文件。比例和編碼這類參數(shù)都成了函數(shù)入?yún)⑿聰?shù)據(jù)集換一下路徑就能復(fù)用。數(shù)據(jù)量大時在函數(shù)里把均值檢查換成目標列分布對比比如用 value_counts 計算訓練集和測試集的類別占比差判斷邏輯是一樣的。這一步檢查雖然只有幾行代碼但它能把分布異常在訓練前暴露出來比模型跑完再回頭查數(shù)據(jù)要快得多。我有一次因為偷懶跳過了這個均值檢查測試集 F1 比線上高了十幾個點從特征工程一路排查到數(shù)據(jù)拆分最后發(fā)現(xiàn)是 CSV 按機房來源排序沒打散就切了。從那以后我每次拆完數(shù)據(jù)都強制走一遍分布對比確認訓練集和測試集的差距在合理范圍內(nèi)才繼續(xù)下一步。希望幫到你。本文還有配套的精品資源點擊獲取