趋近智
机器学习算法,特别是那些基于数学方程的,例如线性回归、逻辑回归、支持向量机,或基于距离计算的,例如k近邻,都作用于数值数据。它们无法直接理解原始数据集中存在的文本标签或类别。因此,将分类特征转换为合适的数值格式是数据准备流程中一项标准且必需的步骤。
分类特征表示属于不同组或类别的数据点。这些可以是:
我们的目标是将这些基于文本的类别转换为机器学习模型能够理解的数字,同时不引入误导信息。让我们来看一下常见策略。
最直接的方法是为每个类别分配一个唯一的整数。这通常被称为标签编码。
例如,如果我们有一个序数特征“大小”:
| 原始值 | 编码值 |
|---|---|
| small | 0 |
| medium | 1 |
| large | 2 |
这对序数型数据效果良好,因为数值顺序(0 < 1 < 2)反映了类别固有的顺序(小 < 中 < 大)。
然而,将标签编码直接应用于名义型数据可能会有问题。考虑一个“颜色”特征:
| 原始值 | 编码值 (任意) |
|---|---|
| red | 0 |
| blue | 1 |
| green | 2 |
在这里,编码暗示了一种顺序(红色 < 蓝色 < 绿色)和实际不存在的关系(例如,蓝色在某种程度上“介于”红色和绿色之间)。这种人为的排序会混淆那些根据量级或距离解释数值的算法。
实现:
你可以使用 Pandas 执行简单的标签编码:
import pandas as pd
data = {'size': ['medium', 'large', 'small', 'medium']}
df = pd.DataFrame(data)
# 为序数数据定义顺序
size_mapping = {'small': 0, 'medium': 1, 'large': 2}
df['size_encoded'] = df['size'].map(size_mapping)
print(df)
# size size_encoded
# 0 medium 1
# 1 large 2
# 2 small 0
# 3 medium 1
# 对于名义数据(如果需要,但不常推荐)
# df['color_encoded'] = df['color'].astype('category').cat.codes
为了将其集成到Scikit-learn管道中,尤其是在处理多个序数型列或需要训练集和测试集之间保持一致性时,OrdinalEncoder 更受推荐。
from sklearn.preprocessing import OrdinalEncoder
import numpy as np
# 假设df[['size', 'education']]包含序数特征
# 为每个特征定义正确的类别顺序
encoder = OrdinalEncoder(categories=[['small', 'medium', 'large'],
['high school', 'bachelor', 'master']])
# 在训练数据上拟合并转换
# train_encoded = encoder.fit_transform(df_train[['size', 'education']])
# 使用*相同*的已拟合编码器转换测试数据
# test_encoded = encoder.transform(df_test[['size', 'education']])
请注意,Scikit-learn的LabelEncoder通常用于编码目标变量 (y),而非输入特征 (X)。对于特征,请使用OrdinalEncoder。
为了避免在名义特征中引入人为顺序,独热编码是标准技术。它将每个类别值转换为一个新的二进制列(0或1)。
考虑带有“红色”、“蓝色”、“绿色”类别的“颜色”特征。独热编码将创建三个新列:
| 原始颜色 | color_red | color_blue | color_green |
|---|---|---|---|
| red | 1 | 0 | 0 |
| blue | 0 | 1 | 0 |
| green | 0 | 0 | 1 |
| blue | 0 | 1 | 0 |
现在,每一行在对应其原始类别的列中有一个“1”,其他列为“0”。这在数值上表示了类别归属,而不暗示任何顺序。
使用独热编码对名义型“颜色”特征进行转换。
潜在问题:维度 如果分类特征有许多唯一值(高基数),独热编码会显著增加数据集的列数。这有时会导致性能问题或某些算法的“维度灾难”。
潜在问题:多重共线性
生成的列是完全多重共线的(例如,如果你知道color_red和color_blue,那么color_green可以被完美预测,因为color_green = 1 - color_red - color_blue)。对于某些模型(如未正则化的线性回归),这会造成问题。一种常见做法是删除其中一个独热编码列(在Pandas/Scikit-learn中使用drop='first'或drop='if_binary')。然而,许多现代算法(特别是基于树的模型或正则化回归)会在内部处理这种多重共线性,因此删除列并非总是必需的,甚至可能会对某些模型略微减少信息。
实现:
Pandas 提供了一个方便的函数 get_dummies:
import pandas as pd
data = {'color': ['red', 'blue', 'green', 'blue'],
'value': [10, 15, 12, 15]}
df = pd.DataFrame(data)
# 创建独热编码列,以原始列名作为前缀
df_encoded = pd.get_dummies(df, columns=['color'], prefix='color', drop_first=False)
print(df_encoded)
# value color_blue color_green color_red
# 0 10 0 0 1
# 1 15 1 0 0
# 2 12 0 1 0
# 3 15 1 0 0
在Scikit-learn管道中使用时,OneHotEncoder是标准工具。它在fit步骤中学习类别,并一致地应用转换。
from sklearn.preprocessing import OneHotEncoder
# 示例数据
data = [['red', 10], ['blue', 15], ['green', 12], ['blue', 15]]
df = pd.DataFrame(data, columns=['color', 'value'])
# 选择分类列
categorical_features = ['color']
# 使用 handle_unknown='ignore' 以避免测试数据中出现未见类别时出错
# 使用 sparse_output=False 以获取密集型 numpy 数组(通常更易于使用)
# 如果模型需要处理多重共线性,请使用 drop='first'
encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore')
# 在训练数据上拟合并转换
# 注意:OneHotEncoder 期望一个二维数组,因此是 df[categorical_features]
# train_encoded_array = encoder.fit_transform(df_train[categorical_features])
# 获取编码后的列名(有助于创建 DataFrame)
# feature_names = encoder.get_feature_names_out(categorical_features)
# train_encoded_df = pd.DataFrame(train_encoded_array, columns=feature_names, index=df_train.index)
# 转换测试数据
# test_encoded_array = encoder.transform(df_test[categorical_features])
# test_encoded_df = pd.DataFrame(test_encoded_array, columns=feature_names, index=df_test.index)
# 示例:拟合和转换样本df
encoded_array = encoder.fit_transform(df[categorical_features])
feature_names = encoder.get_feature_names_out(categorical_features)
df_encoded_sklearn = pd.DataFrame(encoded_array, columns=feature_names, index=df.index)
# 与数值特征组合
df_final = pd.concat([df.drop(columns=categorical_features), df_encoded_sklearn], axis=1)
print(df_final)
# value color_blue color_green color_red
# 0 10 0.0 0.0 1.0
# 1 15 1.0 0.0 0.0
# 2 12 0.0 1.0 0.0
# 3 15 1.0 0.0 0.0
尽管标签/序数编码和独热编码是最常见的,但针对特定情况也存在其他技术:
始终将从训练数据中学习到的编码一致地应用于测试数据。使用Scikit-learn转换器,如OneHotEncoder和OrdinalEncoder,特别是在Pipeline或ColumnTransformer中,有助于确保这种一致性,防止数据泄露和错误。
# 使用 ColumnTransformer 应用不同预处理的示例
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression # 示例模型
import pandas as pd
# 样本数据
data = {
'size': ['medium', 'large', 'small', 'medium', 'large'],
'color': ['red', 'blue', 'green', 'blue', 'red'],
'amount': [100, 150, 80, 120, 200],
'target': [0, 1, 0, 1, 1]
}
df = pd.DataFrame(data)
X = df[['size', 'color', 'amount']]
y = df['target']
# 将数据拆分为训练/测试集
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 定义列类型
ordinal_features = ['size']
nominal_features = ['color']
numerical_features = ['amount']
# 为序数特征定义顺序
ordinal_categories = [['small', 'medium', 'large']]
# 创建预处理器
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), numerical_features),
('ord', OrdinalEncoder(categories=ordinal_categories), ordinal_features),
('nom', OneHotEncoder(handle_unknown='ignore', drop='first'), nominal_features) # drop='first' 可选
],
remainder='passthrough' # 保留其他列(如果有的话,本例中没有)
)
# 创建包含模型的完整管道
model_pipeline = Pipeline(steps=[('preprocessor', preprocessor),
('classifier', LogisticRegression())])
# 现在你可以在训练数据上拟合管道
# model_pipeline.fit(X_train, y_train)
# 并在测试数据上进行预测/评估
# predictions = model_pipeline.predict(X_test)
# score = model_pipeline.score(X_test, y_test)
# 拟合并转换样本数据以查看预处理结果
X_processed = preprocessor.fit_transform(X)
print("处理后的形状:", X_processed.shape)
# 注意:使用 ColumnTransformer 时,get_feature_names_out 可能会很复杂,
# 但列对应于按定义顺序的缩放数值、编码序数、
# 和独热编码的名义特征。
print("处理后的数据(摘录):\n", X_processed[:3])
# Shape after processing: (5, 4)
# Processed Data (excerpt):
# [[-0.56266888 1. 0. 1. ] <--- [缩放后的 amount, 编码后的 size, color_green, color_red] (blue 被删除)
# [ 0.56266888 2. 0. 0. ]
# [-1.12533775 0. 1. 0. ]]
正确处理分类数据是为机器学习准备数据集的基本步骤。通过根据数据性质(序数型与名义型)和类别数量选择合适的编码策略,可以为算法提供有意义的数值输入,从而提高它们有效学习模式的能力。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造