Python机器学习实战:如何处理非数值特征

机器学习实战:这里没有艰深晦涩的数学理论,我们将用简单的案例和大量的示例代码,向大家介绍机器学习的核心概念。我们的目标是教会大家用Python构建机器学习模型,解决现实世界的难题。

机器学习模型要求输入为数值变量,特征必须是大小为(n_samples, n_features)的数值矩阵,目标是(n_samples, 1)的数值向量。但现实世界的数据集有可能包含非数值数据,例如分类变量,文本数据和图像。

这时候需要进行数据预处理(data preprocessing),即采用一些技巧将非数值变量转换为数值变量。

1. 分类变量

分类变量通常分为两种:有序和无序。

  • 有序:类别可以相互比较,有大小之分,可以直接转化为数值变量。假设代表收入的变量’income’有3个类别,‘low’,‘medium’,‘high’,分别代表低收入,中等收入,高收入,类别是可以直接比较的,并且可以排序,‘low’ < ‘medium’ < ‘high’。
  • 无序:类别不可以比较,需要引入虚拟变量(dummy variable),常用编码技术是独热编码(one-hot encoding)。例如代表性别的变量’gender’有两个类别,‘male’和’female’,但’male’ < 'female’的关系并不成立。

将分类变量转化为数值变量有两种常用方法:标签编码和独热编码。

1.1 标签编码

标签编码(label encoding): 将分类变量的类别编码为数字。

用sklearn.preprocessing.LabelEncoder实现,将包含k个类别的分类变量编码为 0 , 1 , 2 , . . . ( k − 1 ) 0,1,2,...(k-1) 0,1,2,...(k1)

标签编码一般不用于特征,而是用于目标变量。

假设一个代表性别的特征’gender’,包含两个类别:‘male’,‘female’。标签编码将类别编码为整数,0代表男性,1代表女性。但这不符合模型背后的假设,因为机器学习模型认为数据有算数含义,例如0 < 1,这意味着男性 < 女性,但这种关系不成立。一般会使用独热编码处理分类特征,标签编码仅用于分类目标

接下来说明如何使用标签编码,假设目标变量代表股票价格趋势,有3种可能的类别,‘up’,‘down’,‘range’.

from sklearn.preprocessing import LabelEncoder

# 目标变量
y = ["up", "up", "down", "range", "up", "down", "range", "range", "down"]

# 创建LabelEncoder对象
le = LabelEncoder()

# 拟合数据
le.fit(y)

# 查看包含哪些类别
print("classes: ", le.classes_)

# 编码为数字
print("encoded labels: ", le.transform(y))

# 调用inverse_transform实现反向操作
print("inverse encoding: ", le.inverse_transform([0, 1, 2]))
classes:  ['down' 'range' 'up']
encoded labels:  [2 2 0 1 2 0 1 1 0]
inverse encoding:  ['down' 'range' 'up']

1.2 独热编码

独热编码(one hot encoding): 将包含m个类别的分类变量转化为 n ∗ m n*m nm的二元矩阵,n是观测值数量,m是类别数量。

假设分类变量’car_type’,表示汽车类型,包含类别(BMW, Tesla, Audi),独热编码将产生下图的结果,每一个类别都成为一个新的变量/特征,1表示观测值包含该类别,0表示不包含。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z5sdurJ2-1600075236801)(pictures/one_hot_encoding.png)]

sklearn提供OneHotEncoder类实现独热编码。

import numpy as np
import pandas as pd
from sklearn.preprocessing import OneHotEncoder

# 分类特征
car_types = np.array(["BMW", "Tesla", "Audi", "BMW", "Audi"])

# 创建编码器
oe = OneHotEncoder()

# OneHotEncoder要求输入为二维数组,用reshape重排结构
oe.fit(car_types.reshape(-1, 1))

# 查看类别
print("classes: ", oe.categories_)

# 调用transform获得编码结果
# transform默认返回稀疏矩阵,当分类变量包含很多类别时非常有用,进一步调用toarray可获得熟悉的numpy二维数组
# 创建OneHotEncoder实例时,如果设置sparse=False,调用transform会得到二维数组
encoded_labels = oe.transform(car_types.reshape(-1, 1)).toarray()
print(encoded_labels)

# 用数据框展现最终的结果,便于理解
encoded_labels_df = pd.DataFrame(encoded_labels, columns=oe.categories_)
print(encoded_labels_df)
classes:  [array(['Audi', 'BMW', 'Tesla'], dtype='

调用pd.get_dummies实现独热编码,这比sklearn的接口更方便,因为它允许我们直接操作数据框。

pd.get_dummies默认将数据类型为’object’的变量视为分类变量,也可以提供要编码的变量名称。

import pandas as pd

car_types = pd.DataFrame({"car_type": ["BMW", "Tesla", "Audi", "BMW", "Audi"]})
print(car_types)

car_types_encoded = pd.get_dummies(car_types)
print(car_types_encoded)
  car_type
0      BMW
1    Tesla
2     Audi
3      BMW
4     Audi
   car_type_Audi  car_type_BMW  car_type_Tesla
0              0             1               0
1              0             0               1
2              1             0               0
3              0             1               0
4              1             0               0

1.3 虚拟变量陷阱

独热编码会引入K个新特征,分别表示分类变量的K个类别。但如果我们使用回归模型,则不能这么做,因为这会导致多重共线性。

首先我们要理解两个新的概念:虚拟变量陷阱多重共线性

虚拟变量陷阱:在回归模型中,虚拟变量(dummy variable)用于表示分类变量的类别,通常用1和0表示。例如性别变量’gender’有两个类别,分别是男性和女性,那么可以引入一个虚拟变量D,如果观测值是男性记为1,否则记为0。如果分类变量有 k k k个类别,引入 ( k − 1 ) (k-1) (k1)个虚拟变量,有一个类别作为基准组/参照组。如果引入 k k k个虚拟变量,会导致多重共线性。

多重共线性:如果1个自变量可以表示为其它自变量的线性组合,就是完全多重共线性,无法估计回归方程的斜率系数。简单理解就是预测变量(特征)之间高度相关。

假设变量 X X X有3个类别,引入3个虚拟变量 X a , X b , X c X_a, X_b, X_c Xa,Xb,Xc,每个变量的取值都是1或0,则必然满足以下关系: X a + X b + X c = 1 X_a + X_b + X_c = 1 Xa+Xb+Xc=1,那么任何一个变量都可以表示为其余两个虚拟变量的线性组合,结果就是完全多重共线性。

独热编码和多重共线性

使用OneHotEncoder创建(k-1)个虚拟变量。

car_types = np.array(["BMW", "Tesla", "Audi", "BMW", "Audi"])

# 将第一个类别作为参照组
oe = OneHotEncoder(drop="first")

# 拟合数据
oe.fit(car_types.reshape(-1, 1))

# 编码
encoded_labels = oe.transform(car_types.reshape(-1, 1)).toarray()

# 查看剔除的特征
# oe.categories_保存了所有的类别,包括剔除的类别
# oe.drop_idx_保存了categories_中被剔除类别的索引
features_drop = oe.categories_[0][oe.drop_idx_.astype(int)]
features_retain = np.delete(oe.categories_[0], oe.drop_idx_.astype(int))
print("features drop: ", features_drop)
print("features retain: ", features_retain)

# 查看结果
encoded_labels_df = pd.DataFrame(encoded_labels, columns=features_retain)
encoded_labels_df["class"] = car_types
print(encoded_labels_df)
features drop:  ['Audi']
features retain:  ['BMW' 'Tesla']
   BMW  Tesla  class
0  1.0    0.0    BMW
1  0.0    1.0  Tesla
2  0.0    0.0   Audi
3  1.0    0.0    BMW
4  0.0    0.0   Audi

使用pd.get_dummies创建(k-1)个虚拟变量。

car_types = pd.DataFrame({"car_type": ["BMW", "Tesla", "Audi", "BMW", "Audi"]})

# drop_first=True: 将第一个类别作为参照组
car_types_encode = pd.get_dummies(car_types, drop_first=True)

# 查看结果
car_types_join = pd.concat([car_types, car_types_encode], axis=1)
print(car_types_join)
  car_type  car_type_BMW  car_type_Tesla
0      BMW             1               0
1    Tesla             0               1
2     Audi             0               0
3      BMW             1               0
4     Audi             0               0

2. 文本数据

有时候模型的输入是文本数据,例如新闻,社交媒体发言等,需要把文本转化为数值矩阵。

常用处理方法有两种:

  1. 单词统计(word counts): 统计每个单词出现的次数。
  2. TF-IDF(Term Frequency-Inverse Document Frequency): 统计单词出现的“频率”。

单词统计法的缺陷是当单词出现次数较多,模型会赋予更多的权重,Tfidf可以规避这个缺陷。

2.1 word counts

行代表观测值,列(特征)是所有文档中出现过的单词,特征值表示单词在当前文档中出现的次数。

from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd

# 准备3个观测值,文本变量
sample = ['problem of evil',
          'evil queen',
          'horizon problem']

# 创建Vectorizer对象,调用fit_transform方法,返回稀疏矩阵(sparse matrix)
vec = CountVectorizer()
X = vec.fit_transform(sample)

# 将结果转化为数据框,以更直观的方式显示结果
df = pd.DataFrame(X.toarray(), columns=vec.get_feature_names())
df["text"] = sample
df
evil horizon of problem queen text
0 1 0 1 1 0 problem of evil
1 1 0 0 0 1 evil queen
2 0 1 0 1 0 horizon problem

2.2 Tfidf

行代表观测值,列(特征)是所有文档出现过的单词,分数是文档中词的频率乘以所有文档的逆频率,分数越高显示单词在当前文档中使用越多,而在其它文档中使用得越少。

from sklearn.feature_extraction.text import TfidfVectorizer

vec = TfidfVectorizer()
X = vec.fit_transform(sample)

df = pd.DataFrame(X.toarray(), columns=vec.get_feature_names())
df["text"] = sample
df
evil horizon of problem queen text
0 0.517856 0.000000 0.680919 0.517856 0.000000 problem of evil
1 0.605349 0.000000 0.000000 0.000000 0.795961 evil queen
2 0.000000 0.795961 0.000000 0.605349 0.000000 horizon problem

如果喜欢我们的文章,记得点赞和收藏哦,我们每天都会为大家带来Python,数据科学和量化交易的精品内容。

【关于我们】

蜂鸟数据:国内领先的金融数据API提供商。

蜂鸟数据团队由业界顶尖的数据工程师,数据科学家和宽客组成,我们正努力构建一个强大的金融数据库,并提供API接口,目标是令金融数据开源化和平民化。

浏览并测试我们接口吧,目前覆盖股票,外汇,商品期货,数字货币和宏观经济领域,包括实时报价(tick)和历史数据(分钟),提供REST API和Websocket两种接入方式,能够满足金融分析师,量化交易和理财app的需求。

需要金融数据?利用蜂鸟API将数据整合到您的应用

如果您准备好了,请登录蜂鸟官网,注册免费获取API密钥,然后开始探索我们的金融数据库吧。

你可能感兴趣的:(python,机器学习,大数据,python,机器学习,数据分析,人工智能)