特征工程:独热码

675 阅读6分钟

公众号:尤而小屋
作者:Peter
编辑:Peter

大家好,我是Peter~

本文给大家介绍一种数据挖掘中常见的特征处理方式:基于独热码的哑变量生成

哑变量又叫做虚拟变量虚设变量或者名义变量,是人为设定的用于将分类变量引入回归模型中的方法。

比如学历、职业、性别等分类变量的数据是本身不能量化的,但是可以通过构造0和1的哑变量可以考察定性因素(分类变量)对因变量的影响。

哑变量一般在回归问题的相关模型中经常使用。在虚拟变量的设置中:表示的基础类型、肯定类型取值为1;如果是比较类型,否定类型则取值为0。

在实际的数据处理中,通过独热码one-hot来实现哑变量。Pandas中的get_dummies函数能够实现此功能。

下面这张图很形象地解释了哑变量的生成过程:

get_dummies使用

Pandas的get_dummies可以用来生成哑变量,下面是函数的参数解释与应用:

pandas.get_dummies(data,  # 待处理数据
                   prefix=None, # 前缀
                   prefix_sep='_', # 和前缀的连接符 
                   dummy_na=False,  # 是否显示控制
                   columns=None, # 指定字段
                   sparse=False, # 是否表示为稀疏矩阵
                   drop_first=False, # 是否删除生成后的第一个字段
                   dtype=None)  # 指定字段类型
import pandas as pd
import numpy as np

from sklearn.preprocessing import OneHotEncoder
s = pd.Series(list("abadc"))
s
0    a
1    b
2    a
3    d
4    c
dtype: object
pd.get_dummies(s)
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
a b c d
0 1 0 0 0
1 0 1 0 0
2 1 0 0 0
3 0 0 0 1
4 0 0 1 0

可以看到上列名展开成了4个,也就是s中的4个取值。

前缀处理-prefix

pd.get_dummies(s)  # 默认
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
a b c d
0 1 0 0 0
1 0 1 0 0
2 1 0 0 0
3 0 0 0 1
4 0 0 1 0

统一加上一个前缀col

pd.get_dummies(s, prefix="col")  # 统一加上前缀col,默认连接符是_
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
col_a col_b col_c col_d
0 1 0 0 0
1 0 1 0 0
2 1 0 0 0
3 0 0 0 1
4 0 0 1 0

连接符

默认的连接符是_,指定为.

pd.get_dummies(s, prefix="col", prefix_sep=".")  # 统一加上前缀col,连接符是.
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
col.a col.b col.c col.d
0 1 0 0 0
1 0 1 0 0
2 1 0 0 0
3 0 0 0 1
4 0 0 1 0

空值处理

s1 = pd.Series(["a","b",np.nan,"c"])
s1
0      a
1      b
2    NaN
3      c
dtype: object
pd.get_dummies(s1, dummy_na=False)  # 默认是False
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
a b c
0 1 0 0
1 0 1 0
2 0 0 0
3 0 0 1

默认空值是不显示的,下面改成显示空值:

pd.get_dummies(s1, dummy_na=True) # 显示空值
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
a b c NaN
0 1 0 0 0
1 0 1 0 0
2 0 0 0 1
3 0 0 1 0

删除第一个字段

pd.get_dummies(s1)  
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
a b c
0 1 0 0
1 0 1 0
2 0 0 0
3 0 0 1
pd.get_dummies(s1, drop_first=True)  
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
b c
0 0 0
1 1 0
2 0 0
3 0 1

指定字段类型dtype

pd.get_dummies(s1, dtype="float")  
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
a b c
0 1.0 0.0 0.0
1 0.0 1.0 0.0
2 0.0 0.0 0.0
3 0.0 0.0 1.0

案例1-两种分类

df = pd.DataFrame({
    "id":["ID1","ID2","ID3","ID4","ID5","ID6"],
    "sex":["Female","Male","Female","Male","Male","Female"],
    "amount":[2000,2500,1800,1900,2000,2600]
})

df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id sex amount
0 ID1 Female 2000
1 ID2 Male 2500
2 ID3 Female 1800
3 ID4 Male 1900
4 ID5 Male 2000
5 ID6 Female 2600

sex字段中存在两种取值:

pd.get_dummies(df["sex"]) 
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Female Male
0 1 0
1 0 1
2 1 0
3 0 1
4 0 1
5 1 0

结果:从sex变量延伸出两个变量Female和Male,这两个变量就是sex中的不同取值。

当原数据中出现了Female,则哑变量Female取值为1,否则为0;Male是一样的

pd.get_dummies(df["sex"], prefix="sex") 
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
sex_Female sex_Male
0 1 0
1 0 1
2 1 0
3 0 1
4 0 1
5 1 0
# 指定对sex执行独热码

pd.get_dummies(df, columns=["sex"])  
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id amount sex_Female sex_Male
0 ID1 2000 1 0
1 ID2 2500 0 1
2 ID3 1800 1 0
3 ID4 1900 0 1
4 ID5 2000 0 1
5 ID6 2600 1 0

当指定columns,会自动加上前缀。

案例2-多种分类

df1 = pd.DataFrame({
    "id":["ID1","ID2","ID3","ID4","ID5","ID6"],
    "sex":["Female","Male","Female","Male","Male","Female"],
    "education":["高中","本科","本科","研究生","本科","研究生"],
    "amount":[1000,2500,1800,4900,2000,3600]
})

df1
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id sex education amount
0 ID1 Female 高中 1000
1 ID2 Male 本科 2500
2 ID3 Female 本科 1800
3 ID4 Male 研究生 4900
4 ID5 Male 本科 2000
5 ID6 Female 研究生 3600
pd.get_dummies(df1["education"]) 
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
本科 研究生 高中
0 0 0 1
1 1 0 0
2 1 0 0
3 0 1 0
4 1 0 0
5 0 1 0
pd.get_dummies(df1, columns=["education"]) 
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id sex amount education_本科 education_研究生 education_高中
0 ID1 Female 1000 0 0 1
1 ID2 Male 2500 1 0 0
2 ID3 Female 1800 1 0 0
3 ID4 Male 4900 0 1 0
4 ID5 Male 2000 1 0 0
5 ID6 Female 3600 0 1 0

案例3-多个字段

df1
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id sex education amount
0 ID1 Female 高中 1000
1 ID2 Male 本科 2500
2 ID3 Female 本科 1800
3 ID4 Male 研究生 4900
4 ID5 Male 本科 2000
5 ID6 Female 研究生 3600
pd.get_dummies(df1, columns=["sex","education"])
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id amount sex_Female sex_Male education_本科 education_研究生 education_高中
0 ID1 1000 1 0 0 0 1
1 ID2 2500 0 1 1 0 0
2 ID3 1800 1 0 1 0 0
3 ID4 4900 0 1 0 1 0
4 ID5 2000 0 1 1 0 0
5 ID6 3600 1 0 0 1 0

OneHotEncoder()使用

官网案例

enc = OneHotEncoder()  
enc.fit([[0,0,3],
         [1,1,0],
         [0,2,1],
         [1,0,2]])  #这里一共有4个数据,3种特征

array = enc.transform([[0,1,3]]).toarray()  

print(array)
[[1. 0. 0. 1. 0. 0. 0. 0. 1.]]

上面结果的解释:原数据是4行3列:4行记录,3个特征

特征1特征2特征3
1003
2110
3021
4102

可以看到:

  • 特征1存在两种取值,独热码是2位
  • 特征2存在两种取值,独热码是3位
  • 特征3存在4种取值,独热码是4位

分析新的测试数据:0,1,3

  1. 0:是第一个特征的取值,出现在第一位,表示为10
  2. 1:是第二个特征的取值,出现在第二位,表示为010
  3. 3:是第三个特征的取值,出现在第四位,表示为0001

总体表示为:100100001

再来一个测试案例:

array = enc.transform([[1,2,1]]).toarray()  

print(array)
[[0. 1. 0. 0. 1. 0. 1. 0. 0.]]

实际案例

df1
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id sex education amount
0 ID1 Female 高中 1000
1 ID2 Male 本科 2500
2 ID3 Female 本科 1800
3 ID4 Male 研究生 4900
4 ID5 Male 本科 2000
5 ID6 Female 研究生 3600
enc = OneHotEncoder()  

enc.fit(df1[["sex", "education"]])
OneHotEncoder()
enc.categories_
[array(['Female', 'Male'], dtype=object),
 array(['本科', '研究生', '高中'], dtype=object)]
# 测试案例

enc.transform([["Male","研究生"]]).toarray()
array([[0., 1., 0., 1., 0.]])

解释为:

  • Male在第二位:编码为01
  • 研究生在第二位:编码为010

总体编码为01010