简介面向Python数据分析与机器学习初学者这份资源聚焦CSV数据集的打散与拆分帮助解决建模前训练集、测试集划分不随机、分布不一致等常见问题。资源包为zip压缩格式共4个文件、约7KB包含1个Python处理脚本及3份csv样例数据脚本覆盖pandas读取、数据质量检查、随机打散、按比例切片生成训练集与测试集等完整过程样例数据便于反复验证不同拆分比例下结果的变化。目前已有109人学习下载。通过案例可掌握DataFrame的sample和iloc等关键用法理解随机因子对模型评估可信度的影响并建立一套可复用的建模前数据预处理路径帮助后续特征工程、模型训练与评估环节更顺畅。1. 打散与拆分机器学习流水线里最低调的两个环节Python对CSV数据集的处理流程里打散和拆分是我见过最容易被跳过、又最直接影响模型评估可信度的两步。很多人拿到CSV后直接调用train_test_split从不关心原始文件的行顺序是不是按类别、按来源或者按时间排好的结果训练集和测试集的分布严重不一致线下指标虚高模型一上线就翻车。这份资源带的processdata.py示例和data0数据组就是针对这个场景的标准解法先把整个数据集随机打散再按7:3切成训练集和测试集。适合刚入门机器学习、用CSV自己组装数据集的同学也适合被数据顺序坑过、想把预处理流程规范化的从业者。2. 读入 CSV 先做三道检查read_csv 参数、缺失值与原始副本CSV 文件在磁盘上就是一个文本文件读进来的第一眼决定后面所有步骤是否靠谱。这一步的核心不是“把数据读进来”而是“确认读进来的数据跟源头一致”包括列类型、缺失值标记和行数。我习惯在读入之后先打印 shape、dtypes 和 head 三件套再决定后续处理策略。很多人跳过这一步直接进打散遇到乱码和类型问题再回头查反而更浪费时间。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 的统一入口返回 DataFrame。这里显式传了 encoding 和 na_values 两个参数目的是把文件中可能出现的空字符串、NA、null 统一识别为缺失值避免它们被当成普通字符串混进数值列。shape 和 dtypes 能快速发现两类问题行数比预期少说明文件里有换行转义问题列类型为 object 说明数值列被读成了字符串。参数说明read_csv 默认分隔符是英文逗号制表符文件需要手动传 sep\tencoding 在 Windows 上生成的 CSV 经常是 gbk如果 utf-8 报 UnicodeDecodeError改成 encodinggbk 再试。na_values 是额外缺失值标记列表keep_default_naTrue 表示保留 NaN、None 等默认标记两者叠加使用。Excel 导出的 CSV 偶尔带 UTF-8 BOM读进来后第一列列名会带 \ufeff 前缀打印 columns 一眼就能看出来这时候把 encoding 换成 utf-8-sig 就能解决。read_csv 参数作用常见取值场景sep指定分隔符默认逗号制表符文件用 \tencoding文件编码中文文件常见 utf-8 / gbk / utf-8-signa_values追加缺失值标记把空串、NA、null 统一收进来dtype指定列类型读取时就把类别列限成 category2.2 缺失值和重复值先查清楚再决定怎么清print(df.isnull().sum()) print(df.duplicated().sum())逻辑说明isnull().sum() 逐列统计缺失值数量duplicated().sum() 统计完全重复的行数。这一步的作用是给清洗策略提供依据缺失值占比高到一定程度的列要么填充要么直接丢弃重复行可以直接删除。重点在于清洗动作必须在拆分之前完成否则训练集和测试集的缺失值比例、总行数都会各自偏掉。注意dropna 默认删除包含任何缺失值的整行如果先拆分后清洗训练集和测试集各自丢掉的行数不一样后续模型训练和评估的数据规模就不可比。我一般先用 isnull 看分布缺失集中在某一列且占比低于 5% 时直接 dropna高于 20% 时考虑把这个列放进数据分析报告里单独处理而不是盲目删除。重复行的处理类似先打印数量再决定是否调用 drop_duplicates不要想当然。2.3 留一份原始副本打散拆分前的后悔药raw_df df.copy()逻辑说明copy() 是对当前 DataFrame 的深拷贝后续打散、删除行、修改 dtype 都不会影响 raw_df。数据预处理的流程一旦展开清洗动作是不可逆的留一份原始副本可以让每一步都回退对比排查问题时也能明确“是清洗改坏了还是模型调参出了问题”。参数说明copy(deepTrue) 是默认的深拷贝行为会重新申请内存复制数据数据量大时内存占用翻一倍。如果机器内存紧张也可以用浅拷贝先顶着但浅拷贝和原对象共享部分内存修改时互相影响。踩过这个坑之后我对于这种小规模 CSV 数据集一律用深拷贝省得给自己挖坑。3. 打散不是在凑随机感sample(frac1) 的机制与参数细节打散的目的是打破原始文件里可能存在的顺序规律。CSV 文件的行顺序往往不是随机的要么按采集时间排列要么按来源分组甚至可能是某个 SQL 查询结果直接导出前 300 行是 A 类、后 300 行是 B 类。如果不打散就直接按比例切切出来的训练集和测试集分布会非常难看。3.1 数据顺序里藏着的偏见一个具体的反面案例假设 data0.csv 有 600 行前 300 行都是类别 A后 300 行都是类别 B。直接按 7:3 切训练集就是前 420 行包含 300 个 A 和 120 个 B测试集是后 180 行全部是 B。模型在训练集里看到的 B 样本偏少测试时面对的又是清一色 B评估指标完全没有参考价值。这个例子有点极端但真实场景里按时间段、按设备、按地区排序的情况非常普遍危害是一样的。数据顺序的问题在于它不会显式报错。模型训练流程能正常跑完损失值能正常下降但评估结果不可信。很多从业者遇到线上效果和线下对不上第一反应是调模型很少有人会回头检查数据拆分的顺序。所以打散不仅是流程需要更是一个让评估结果具备参考价值的前置条件。3.2 用 sample(frac1) 打散全量无放回抽样shuffled_df df.sample(frac1, random_state42)逻辑说明DataFrame.sample 是从原表中随机抽取行的函数frac1 表示抽取比例是 100%也就是全量抽一遍。由于抽样结果保留了原表的所有行但顺序是随机生成的所以等价于一次打散shuffle。抽样的本质是无放回抽样每一行最多被抽中一次不会有重复行混进来。参数说明random_state 是随机数种子固定成任意整数比如 42之后每次运行打散结果完全一致这是一种“可复现的随机”。如果不传这个参数每次跑出来的顺序都不同模型指标的波动会让你分不清是数据问题还是模型问题。随机数种子的取值范围就是整数没有其他限制团队协作时统一约定一个固定值即可。sample 内部走的是 numpy 的随机数机制所以用 numpy.random.seed 全局设种子也能影响它。但直接传 random_state 更清晰作用范围只限定在这一次调用里不会污染其他随机过程。如果你习惯 numpy 风格也可以写成 df.loc[numpy.random.permutation(df.index)]效果类似但要多处理一次索引对齐pandas 的 sample 一行搞定我更推荐后者。这里补充一个边界frac 可以小于 1比如 frac0.8 表示抽取 80% 的行这在超大数据集里可以做降采样。但打散这个场景必须用 frac1否则会少行后面拆分时行数对不上问题就很难排查了。3.3 打散后立刻重置索引别让旧行号坑到你shuffled_df shuffled_df.reset_index(dropTrue)逻辑说明sample 返回的 DataFrame 行索引还是原始 CSV 里的行号比如原表第 300 行在被抽到后仍然带着索引 300。如果不重置后面用 iloc 按位置切片时不受影响但一旦用 loc 按索引取值、或者做 merge 操作就会发现取出来的行是“跳着”的跟打印结果对不上。reset_index(dropTrue) 把索引重新变成从 0 开始的连续整数同时丢弃旧索引列。参数说明dropTrue 的意思是旧索引直接扔掉而不是作为一列保留。如果漏掉这个参数旧索引会被塞进一个叫 index 的新列后面拆分和导出 CSV 时这个列会混进数据属于很隐蔽的脏数据来源。这个问题在数据量大时尤其难发现因为打印前面几行时 index 列看起来就像正常的数据列。4. 拆分训练集和测试集手动 iloc 切片与 train_test_split 怎么选数据打散之后拆分的核心逻辑就只剩“按位置切一刀”。但这一刀怎么切、要不要分层、用 pandas 还是用 sklearn决定了后续评估的可信度。我的判断标准很简单数据能随机打散就用 train_test_split数据必须保持顺序比如时间序列就手动切片。4.1 手动切片先算整数边界再 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%两者加起来正好等于原始行数不会重叠也不会漏行。copy() 在这里不是多余的iloc 切片返回的是原对象的视图如果后续对测试集做原地修改比如填充缺失值可能连带影响原 DataFrame加上 copy() 之后两个集合就彻底独立了。参数说明train_ratio0.7 只是一个约定俗成的起点样本量小时可以提到 0.8样本量很大时用 0.9 甚至更高都常见但测试集至少要保证 500 行以上否则评估指标的置信区间太宽。比例的选择取决于数据规模和问题难度没有绝对最优值。切完之后顺手打印两个集合的行数确认 train 加 test 等于原始行数这个对账动作能提前发现边界错误。手动切片最典型的适用场景是时间序列。比如 data0.csv 是某个传感器按小时采集的数据必须先按时间升序排序再直接按位置切分保证训练集里的时间点全部在测试集之前。如果这种数据先打散再切未来数据混进训练集模型评估结果会乐观到失真上线后真实表现断崖式下跌。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 内部会先做一次随机打散再做切分所以这里传入已经打过散的 DataFrame 也没问题只是多一次无谓的随机操作结果不受影响。stratify 参数是它比手动切片最大的优势传入类别列之后函数会按该类别的比例分配训练集和测试集保证两边各类别占比基本一致。参数说明test_size0.3 与手动切片的 30% 等价random_state 同样是固定种子。当数据类别不平衡比如正样本只占 5% 时普通随机切分很容易把测试集里的正样本丢得只剩几个stratify 能稳妥解决这个问题。对比维度手动 iloc 切片train_test_split适用场景时间序列、保持顺序常规随机拆分、类别不平衡随机打散需要自己先 sample内部自带 shuffle分层支持不支持需自行实现stratify 参数直接传类别列依赖pandas 自带需要安装 scikit-learn5. 避坑打散拆分阶段最容易翻车的五个地方这一章是实际跑数据时最容易踩的坑覆盖面主要是顺序、种子、索引、分层和编码这五个维度。每条按“现象、原因、解决”的结构来写都是我踩过或者帮别人排查过的真实问题。5.1 缺失值处理顺序不一致训练和测试各缺一截现象拆分前没统计缺失值拆分后分别对训练集和测试集 dropna结果训练集丢掉 30 行、测试集丢掉 18 行两边数据量不同模型评估时训练集和测试集的行数比例变成了 70:28 而不是 70:30。原因缺失值分布并不是按行均匀铺开的先拆分再清洗等于让两个集合各自独立丢失样本破坏了原始分布而且两个集合清洗后的行数无法对齐。解决在 read_csv 之后、sample 之前完成所有清洗动作把 dropna 或 fillna 的结果记录下来确认删除行数、剩余行数后再进入打散拆分。这样一来训练集和测试集只是原始清洗后数据的两个切片行数比例严格受控。5.2 random_state 不固定指标对不上号现象同一份数据、同一个模型今天跑 AUC 是 0.85明天跑变成 0.83你以为是模型问题其实是数据拆分的随机种子变了训练集和测试集的内容已经完全不同。原因sample 和 train_test_split 默认都是随机过程不固定种子每次打散顺序都不同拆分结果就不同下游所有指标都跟着抖。解决把 random_state42 写死在处理脚本里或作为函数参数传入保证每一次运行拿到相同的训练集和测试集。这个习惯在排查模型问题时能省掉很多无谓的怀疑不然你会花大量时间调参最后发现是数据每次都不同。5.3 打散后没有重置索引loc 取值“跳行”现象shuffled_df 打印出来顺序是正常的但用 loc[100:200] 取出来是乱七八糟的行甚至报 KeyError。原因打散后索引还是原始行号loc 是按索引标签取值的它根本不知道你期望的是“第 100 到第 200 行”它拿标签 100 到 200 去匹配匹配到哪一行算哪一行。解决打散后紧跟一行 reset_index(dropTrue)让索引重新连续。如果已经拆完了才发现那就回退到打散步骤重新走一遍这种情况没有捷径只能从头跑。5.4 类别不平衡时忽略分层小类样本在测试集里“失踪”现象数据里正样本占 3%随机切分后测试集 5 万行里只有几十个正样本评估曲线抖得没法看精确率召回率每次跑都差很多。原因随机切分不保证类别比例少数类样本量本来就小随机分完两边数量波动很大测试集里的小类样本少到无法支撑稳定评估。解决用 train_test_split 的 stratify 参数按类别列分层抽样确保训练集和测试集的每个类别占比都和全量数据一致。如果坚持用手动切片那就得按类别分组、各组内部按比例切、最后再合并工作量大且容易出错。5.5 中文 CSV 乱码与路径报错现象read_csv 读取时抛 UnicodeDecodeError或者 CSV 用 Excel 打开正常但 pandas 读出来全是乱码。原因文件本身是 gbk 或 gb2312 编码pandas 默认按 utf-8 解码解码对不上就报错或出现乱码。解决读入时指定 encodinggbk或者读取前先用文本编辑器把文件另存为 utf-8 编码一劳永逸。文件路径尽量用英文Windows 下中文路径配合 pandas 老版本有时会莫名报错把数据文件放到纯英文目录下能少折腾几次。这类问题通常在换机器、换数据源后随机出现写代码时养成显式传 encoding 的习惯能规避大部分。6. 把流程封装成 process_csv一致性检查与批量导出把前面的步骤收拢成一个函数日常处理新数据集时只需要改文件路径和比例就能稳定复现整个过程。封装的意义在于把顺序固定下来减少手工操作带来的偏差也能让团队其他人拿到脚本后跑出完全一致的结果。6.1 封装 function打散、拆分、检查、导出一次完成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逻辑说明函数先把重复行清掉并重置索引再打散再按比例切片最后用数值列均值差做一次快速一致性检查。均值差绝对值越小说明训练集和测试集的数值分布越接近如果这个值明显偏大优先怀疑数据没打散或者数据本身就存在按某个字段分组的强规律。导出的 CSV 都加了 indexFalse避免把行号写进文件。比例和编码这类参数都成了函数入参新数据集换一下路径就能复用。数据量大时在函数里把均值检查换成目标列分布对比比如用 value_counts 计算训练集和测试集的类别占比差判断逻辑是一样的。这一步检查虽然只有几行代码但它能把分布异常在训练前暴露出来比模型跑完再回头查数据要快得多。我有一次因为偷懒跳过了这个均值检查测试集 F1 比线上高了十几个点从特征工程一路排查到数据拆分最后发现是 CSV 按机房来源排序没打散就切了。从那以后我每次拆完数据都强制走一遍分布对比确认训练集和测试集的差距在合理范围内才继续下一步。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?