One-hot编码也叫独热编码,又称一位有效编码。方法就是用N位对N个状态进行表示,但是其中只有一位为1,剩下N-1位全为0。例如,特征为性别,就需要变成10和01。
在回归,分类,聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的。而常用的距离或相似度的计算都是在欧式空间的相似度计算,计算余弦相似性,基于的就是欧式空间。使用独热编码(One-Hot Encoding),将离散特征的取值扩展到了欧式空间,离散特征的某个取值就对应欧式空间的某个点。将离散型特征使用独热编码(One-Hot Encoding),会让特征之间的距离计算更加合理。
举个例子,如果我们现在一个特征有五个取值,如果不采用One-Hot编码,则分别表示为演员=0,厨师=1,公务员=2,教师=3,律师=4,那么教师和厨师的距离为2,律师与厨师的距离为3,这显然是不合理的。比较合理的做法是将两个工作之间的距离表示为sqrt(2),即两个工作之间的距离是一样的。
总结一下,如果特征的大小表示如果有意义的话,比如我们用1-9表示年龄,这种特征的大小表示就是有意义的,所以这种情况下就不需要进行One-Hot编码;如果特征的大小没有意义,仅仅表示状态时则使用One-Hot编码。
pandas实现:首先对f3这个特征做One-Hot编码,接着将生成的df和原来的df进行拼接,最后删除原来的f3特征
df = pd.DataFrame(
[
[1000, "male", 23],
[1001, "female", 22],
[1002, "male", 69]
],
columns=['id', 'gender', 'age']
)
# step 1: using get_dummies to encode feature
dummy_df = pd.get_dummies(df["gender"])
# step 2: concat dummy_df with original df
df = pd.concat((df, dummy_df), axis=1)
# step 3: remove original feature
df = df.drop("gender", axis=1)
# simplified form
# df = pd.concat((df, pd.get_dummies(df["gender"])), axis=1)
# df = df.drop("gender", axis=1)
存在问题:
- 采用这种方法实现会存在问题,比如将性别划分成gender_male和gender_female时,这两种特征其实是冗余的,这个问题在数学中叫做多重共线性(Multicollinearity),在pandas处理时也有人称之为虚拟变量陷阱(Dummy Varialble Trap)。解决办法就是加入参数drop_first=True,作用是除去第一个虚拟变量,然转化后的虚拟变量从k和变成k-1个。
df = pd.DataFrame(
[
[1000, "male", 23],
[1001, "female", 22],
[1002, "male", 69]
],
columns=['id', 'gender', 'age']
)
# step 1: using get_dummies to encode feature
dummy_df = pd.get_dummies(df["gender"], drop_first=True)
# step 2: concat dummy_df with original df
df = pd.concat((df, dummy_df), axis=1)
# step 3: remove original feature
df = df.drop("gender", axis=1)
# simplified form
# df = pd.concat((df, pd.get_dummies(df["gender"], drop_first=True)), axis=1)
# df = df.drop("gender", axis=1)
- 测试集中可能出现训练集中没出现过的变量。解决办法:①将训练集和测试集一起get_dummies,然后再切分;②检测不一致的列,然后在缺少这些列的数据集中加入取值全为0的同名列。③使用sklearn的OneHotEncoder
train = pd.DataFrame(
[
[1000, "male", 23],
[1001, "female", 22],
[1002, "male", 69]
],
columns=['id', 'gender', 'age']
)
test = pd.DataFrame(
[
[1000, "male", 23],
[1001, "female", 22],
[1002, "male", 69],
[1003, "unknown", 88]
],
columns=['id', 'gender', 'age']
)
train = pd.concat((train, pd.get_dummies(train["gender"], drop_first=True)), axis=1)
train.drop("gender", axis=1)
test = pd.concat((test, pd.get_dummies(test["gender"], drop_first=True)), axis=1)
test.drop("gender", axis=1)
for col in list(test.columns.difference(train.columns)):#train没有的列
df_obj = pd.DataFrame({col:np.squeeze(np.zeros((1,train.shape[0])))})
train = pd.concat([train,df_obj], axis=1)
for col in list(train.columns.difference(test.columns)):#test没有的列
df_obj = pd.DataFrame({col:np.squeeze(np.zeros((1,test.shape[0])))})
test = pd.concat([test,df_obj], axis=1)
sklearn实现:
from sklearn.preprocessing import OneHotEncoder
train = pd.DataFrame(
[
[1000, "male", 23],
[1001, "female", 22],
[1002, "male", 69]
],
columns=['id', 'gender', 'age']
)
test = pd.DataFrame(
[
[1000, "male", 23],
[1001, "female", 22],
[1002, "male", 69],
[1003, "unknown", 88]
],
columns=['id', 'gender', 'age']
)
# drop:k-1 to represent k; handle_unknown:for missing value
encoder = OneHotEncoder(sparse=False, handle_unknown='ignore', drop="first")
# fit
enc = encoder.fit(train[["gender"]])
# 获取新列名
columns = enc.get_feature_names_out(["gender"])
# 转换测试数据
enc_arr = enc.transform(test[["gender"]])
# 生成dataframe
enc_df = pd.DataFrame(enc_arr, columns=columns)
new_data = pd.concat([test, enc_df], axis=1)
new_data.drop(["gender"], axis=1, inplace=True)
new_data