测试集NaN值处理:策略与实践指南
2024-12-31 11:49:04
测试集中 NaN 值的处理:策略与实践
数据预处理是机器学习模型开发的关键步骤,其中一个常见挑战便是处理数据中的缺失值,通常表示为 NaN (Not a Number)。 特别是在构建训练和测试集时,NaN 值的处理方式会显著影响模型性能。本文将探讨在测试集中是否应保留 NaN 值,以及应该如何有效处理。
问题剖析:为什么测试集会有 NaN 值?
在实际场景中,测试集中存在 NaN 值的原因多种多样:
- 数据采集问题 :传感器故障、人工录入错误、API 返回异常等,均会导致数据集中出现 NaN。
- 数据转换丢失 :在数据处理和清洗流程中,某些数据变换可能产生 NaN 值。 例如,字符串转数值、缺失值填充算法引入错误等。
- 与训练数据不一致 : 训练集和测试集来自不同的数据分布,有可能训练集没有出现缺失值,但是测试集中出现了。
出现 ValueError: Found unknown categories [nan] in column ... during transform
通常表明你的训练数据在执行 One-Hot Encoding (独热编码) 时没有 NaN 类别,而在测试集转换的时候出现了 NaN,这意味着模型未学习如何处理 NaN 这一个“类别”,自然会抛出错误。 这凸显了一个核心问题, 测试集的数据特征和格式要尽可能和训练集一致,特别是分类特征。
解决思路:多种方法处理 NaN
面对测试集中的 NaN,以下是一些处理方案。
1. 训练集测试集同步 Imputation(填充)
方法 : 在训练阶段,通过某种策略对训练集中的缺失值进行填充。 测试集使用相同的填充策略,以确保测试集中没有缺失值, 且处理方式与训练集一致。
操作步骤 :
* 选择一种合适的 Imputation 方法:如均值填充、中位数填充、众数填充、KNN 填充、特定值填充。 选择方法需要参考数据本身特性以及业务场景需求。
* 在训练集上使用.fit()
计算所需的统计量(如均值、中位数)。
* 训练集和测试集使用相同的 .transform()
填充。
代码示例 (Python/pandas)
```python
import pandas as pd
from sklearn.impute import SimpleImputer
# 模拟训练集和测试集
train_data = {'feature1': [1, 2, float('nan'), 4, 5],
'feature2': ['A', 'B', float('nan'), 'A', 'C']}
test_data = {'feature1': [6, float('nan'), 8, 9, 10],
'feature2': ['B', 'C', float('nan'), 'A', 'B']}
train_df = pd.DataFrame(train_data)
test_df = pd.DataFrame(test_data)
# 数值特征填充中位数
numerical_imputer = SimpleImputer(strategy='median')
train_df[['feature1']] = numerical_imputer.fit_transform(train_df[['feature1']])
test_df[['feature1']] = numerical_imputer.transform(test_df[['feature1']])
# 分类特征填充众数
categorical_imputer = SimpleImputer(strategy='most_frequent')
train_df[['feature2']] = categorical_imputer.fit_transform(train_df[['feature2']])
test_df[['feature2']] = categorical_imputer.transform(test_df[['feature2']])
print("填充后的训练集:\n", train_df)
print("填充后的测试集:\n", test_df)
```
**操作步骤**
1. 使用pandas 创建一个简单的含有 `NaN` 的DataFrame 用来模拟训练集和测试集。
2. 使用 sklearn 提供的 `SimpleImputer` 对象创建不同的 Imputer (填充器),数值特征采用中位数填充,分类特征使用众数填充。
3. 在训练集 `feature1` 列 使用 `fit_transform` 方法完成计算并填充, 并用相同的imputer 使用 `transform` 方法处理测试集的`feature1`。`fit_transform` 等效于先 `fit()` 然后再`transform()`。 `fit()` 计算需要的统计值, `transform()` 根据统计值完成填充。 分类特征同理。
4. 输出填充后的训练集和测试集。
注意: 需确保Imputation 的填充逻辑的一致性, 使用训练集的数据信息 fit()
计算,测试集使用相同的逻辑transform()
填充。
2. 引入新的“未知”类别
方法 :对于类别型特征,当测试集中出现训练集未出现的 NaN 时,可以将其视为一个新的类别,例如 “Unknown” 或 “Missing”。 在进行 One-Hot Encoding 前,将测试集和训练集的缺失值都替换成同一个特定字符串。
操作步骤 :
* 将训练集和测试集的 NaN 值替换为 “Unknown” 或类似的占位符。
* 使用训练集执行One-Hot Encoding时,需保留缺失值的这个类别。
* 使用训练好的 One-Hot Encoding 模型在测试集上执行 transform.
代码示例(Python/pandas):
```python
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
# 模拟训练集和测试集
train_data = {'feature': ['A', 'B', float('nan'), 'A']}
test_data = {'feature': ['B', 'C', float('nan'], 'A', float('nan')]}
train_df = pd.DataFrame(train_data)
test_df = pd.DataFrame(test_data)
# 缺失值替换为Unknown
train_df['feature'].fillna("Unknown", inplace=True)
test_df['feature'].fillna("Unknown", inplace=True)
# 创建OneHotEncoder 对象
ohe = OneHotEncoder(handle_unknown='ignore', sparse_output=False)
train_encoded = ohe.fit_transform(train_df[['feature']]) # 先训练
test_encoded = ohe.transform(test_df[['feature']]) # 在测试集上 transform
print("编码后的训练集:\n", train_encoded)
print("编码后的测试集:\n", test_encoded)
```
**操作步骤**
1. 创建模拟的包含 `NaN`的dataframe 作为训练集和测试集。
2. 使用 `pandas.fillna` 将训练集和测试集中的缺失值 `NaN` 统一替换为 `Unknown`。
3. 使用`OneHotEncoder` 创建 One-Hot 编码器对象,并指定`handle_unknown='ignore'` 处理未知类别(训练集未出现的类别)。 `sparse_output=False` 设置输出为 `numpy.array`而非稀疏矩阵。
4. 使用 `fit_transform` 在训练集上学习转换,同时输出训练集编码后的结果。
5. 在测试集上直接使用 `transform` 完成测试集的编码。
6. 输出训练集和测试集的编码结果。
**注意:** 设置 `handle_unknown='ignore'` 在执行`transform()`操作时会忽略掉训练集不存在的类别, 可能会导致一些信息丢失,但可以保证程序顺利执行。另外可以使用 `ohe.categories_` 查看 `fit()` 得到的类别。
3. 特征工程: 增加指示缺失的特征
方法描述: 除直接替换或忽略,还可以使用一个额外二进制特征,标识该条数据在这个特征上是否存在缺失,然后在后续进行 Imputation 或者保留缺失值不处理。 这种方法,可以让模型知道那些地方是缺失值。
操作步骤:
* 为包含缺失值的特征添加一个指示器列(0 代表未缺失,1 代表缺失)。
* 针对新添加的缺失指示器特征做独热编码(如有需要)
* 按照既有的方式, 对缺失特征做 imputation 或 保留
代码示例(Python/pandas):
```python
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
# 模拟训练集和测试集
train_data = {'feature1': [1, 2, float('nan'), 4, 5],
'feature2': ['A', 'B', float('nan'), 'A', 'C']}
test_data = {'feature1': [6, float('nan'), 8, 9, 10],
'feature2': ['B', 'C', float('nan'), 'A', float('nan')]}
train_df = pd.DataFrame(train_data)
test_df = pd.DataFrame(test_data)
for feature in ['feature1','feature2']:
train_df[feature + '_missing'] = train_df[feature].isnull().astype(int) # 生成指示特征
test_df[feature + '_missing'] = test_df[feature].isnull().astype(int) #生成指示特征
#对缺失指示器独热编码
ohe = OneHotEncoder(sparse_output = False)
ohe.fit(pd.concat([train_df[[col + '_missing' for col in ['feature1','feature2'] ]], test_df[[col + '_missing' for col in ['feature1','feature2'] ]]], axis=0))
train_indicator_encoded = ohe.transform(train_df[[col + '_missing' for col in ['feature1','feature2'] ]])
test_indicator_encoded = ohe.transform(test_df[[col + '_missing' for col in ['feature1','feature2'] ]])
train_df = pd.concat([train_df,pd.DataFrame(train_indicator_encoded)], axis = 1)
test_df = pd.concat([test_df, pd.DataFrame(test_indicator_encoded)],axis = 1 )
print("包含指示特征的训练集:\n", train_df)
print("包含指示特征的测试集:\n", test_df)
```
操作步骤:
1. 创造模拟含有NaN
的DataFrame 用来表示训练集和测试集。
2. 对训练集和测试集中存在的特征 feature1
和feature2
, 增加对应的缺失指示列(形如: feature1_missing
) 。 使用 pandas的 isnull()
判断缺失,使用astype(int)
将 bool
类型转换成 int
, 方便后续处理。
3. 使用 OneHotEncoder 对所有数据里的缺失指示列做 OneHotEncode
4. 将独热编码后的新列合并到原始的数据集。
5. 打印训练集和测试集
**注意:** 指示列本身也变成模型的一个特征,允许模型学习到缺失对结果可能带来的影响。 使用 `ohe.fit()`的时候一定要把训练集和测试集拼接起来, 确保 `OneHotEncoder` 的行为一致。
结论
在机器学习流程中,处理测试集的 NaN 值需要综合考虑多个方面。 没有一种通用的最佳方法,最佳实践往往依赖于具体的数据、模型、以及业务场景。通常,同步 Imputation, 新增未知类别, 添加缺失指示特征, 这三种方法都是常见的应对测试集中出现 NaN 的策略。处理数据需要保持耐心细致,根据实际情况灵活调整处理方法,保证模型学习效果的最大化。