# Kaggle 官方教程：机器学习中级3 分类变量
> 原文：[Intermediate Machine Learning](https://www.kaggle.com/learn/intermediate-machine-learning) > [Categorical Variables](https://www.kaggle.com/alexisbcook/categorical-variables)
> 
> 译者：[Leytton](https://github.com/Leytton)
> 
> 协议：[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)

PS：水平有限，欢迎交流指正（Leytton@126.com）

在本教程中，你将了解什么是分类变量，以及处理这类数据的三种方法。
## 1、介绍
分类变量类似于枚举，拥有特定数量的值类型。

 - 比如一项调查，询问你多久吃一次早餐，并提供四个选项:“从不”、“很少”、“大多数日子”或“每天”。在本例中，数据是分类的，因为答案属于一组固定的类别。
    
 - 如果对人们所拥有的汽车品牌进行调查，回答可以分为“本田”、“丰田”和“福特”。在本例中，数据也是分类的。

如果您没有预处理这些分类变量，就将这些变量应用于机器学习模型中，大多数情况您将得到一个错误结果。在本教程中，我们将比较三种预处理分类数据的方法。

## 2、三种方法
**1) 删除分类变量**
处理分类变量最简单的方法是从数据集中删除它们。这种方法适用于该列中不包含有用信息的情况。

**2) 标签编码**
标签编码将每个变量类型标记为不同的整数。

![在这里插入图片描述](/img/learn/intermediate-machine-learning/3.1.png)

这种方法假设类别的顺序为:“Never”(0)<“rare”(1)<“Most days”(2)<“Every day”(3)。

在本例中，这个假设是有意义的，因为对类别有个唯一的排名。 并不是所有的分类变量在值中都有一个明确的顺序，但是我们将那些有顺序的变量称为`有序变量`。对于基于树的模型(如决策树和随机森林)，有序变量的标签编码可能效果不错。

**3) One-Hot 编码**

“One-hot”编码创建新列，表明原始数据中每个可能值的存在(或不存在)。为了说明这点，我们举个例子：

![在这里插入图片描述]((/img/learn/intermediate-machine-learning/3.2.png))

在原始数据集中，“Color”是一个分类变量，包含“Red”、“Yellow”和“Green”三个类别。对应的`one-hot编码`是每个可能的值各自作为一列，原始数据集中的每一行作为一行。当原始值为“Red”时，我们在“Red”列中放入1;如果原始值是“Yellow”，则在“Yellow”列中放入1，以此类推。

与`标签编码`不同，`one-hot编码`不假定类别的顺序。因此，如果在分类数据中没有明确的顺序(例如，“红色”既不比“黄色”多也不比“黄色”少)，这种方法可能会特别有效。我们把没有内在排序的分类变量称为`名义变量`。

如果分类变量具有大量不同的值（超过15个），效果将不会很好。

## 3、案例
像前一篇教程一样，我们将使用 [Melbourne Housing数据集](https://www.kaggle.com/dansbecker/melbourne-housing-snapshot/home)。

我们不会关注数据加载步骤。假设您已经拥有了`X_train`、`X_valid`、`y_train`和`y_valid`中的训练和验证数据。

```python
import pandas as pd
from sklearn.model_selection import train_test_split

# Read the data
data = pd.read_csv('../input/melbourne-housing-snapshot/melb_data.csv')

# Separate target from predictors
y = data.Price
X = data.drop(['Price'], axis=1)

# Divide data into training and validation subsets
X_train_full, X_valid_full, y_train, y_valid = train_test_split(X, y, train_size=0.8, test_size=0.2,
                                                                random_state=0)

# Drop columns with missing values (simplest approach)
cols_with_missing = [col for col in X_train_full.columns if X_train_full[col].isnull().any()] 
X_train_full.drop(cols_with_missing, axis=1, inplace=True)
X_valid_full.drop(cols_with_missing, axis=1, inplace=True)

# "Cardinality" means the number of unique values in a column
# Select categorical columns with relatively low cardinality (convenient but arbitrary)
low_cardinality_cols = [cname for cname in X_train_full.columns if X_train_full[cname].nunique() < 10 and 
                        X_train_full[cname].dtype == "object"]

# Select numerical columns
numerical_cols = [cname for cname in X_train_full.columns if X_train_full[cname].dtype in ['int64', 'float64']]

# Keep selected columns only
my_cols = low_cardinality_cols + numerical_cols
X_train = X_train_full[my_cols].copy()
X_valid = X_valid_full[my_cols].copy()
```
我们使用下面的`head()`方法查看训练数据。
```python
X_train.head()
```
输出结果：
```
	Type 	Method 	Regionname 	Rooms 	Distance 	Postcode 	Bedroom2 	Bathroom 	Landsize 	Lattitude 	Longtitude 	Propertycount
12167 	u 	S 	Southern Metropolitan 	1 	5.0 	3182.0 	1.0 	1.0 	0.0 	-37.85984 	144.9867 	13240.0
6524 	h 	SA 	Western Metropolitan 	2 	8.0 	3016.0 	2.0 	2.0 	193.0 	-37.85800 	144.9005 	6380.0
8413 	h 	S 	Western Metropolitan 	3 	12.6 	3020.0 	3.0 	1.0 	555.0 	-37.79880 	144.8220 	3755.0
2919 	u 	SP 	Northern Metropolitan 	3 	13.0 	3046.0 	3.0 	1.0 	265.0 	-37.70830 	144.9158 	8870.0
6043 	h 	S 	Western Metropolitan 	3 	13.3 	3020.0 	3.0 	1.0 	673.0 	-37.76230 	144.8272 	4217.0
```
接下来，我们获得训练数据中所有分类变量的列。

我们通过检查每个列的数据类型(或`dtype`)来做到这一点。`object` 类型表示改列存在文本(理论上它还可以是其他东西，但对于我们的目的来说并不重要)。对于这个数据集，带有文本的列表示分类变量。
```python
# Get list of categorical variables
s = (X_train.dtypes == 'object')
object_cols = list(s[s].index)

print("Categorical variables:")
print(object_cols)
```
输出结果：
```
Categorical variables:
['Type', 'Method', 'Regionname']
```

**定义函数来评估每种方法的效果**

我们定义了一个函数`score_dataset()`来比较处理分类变量的不同方法。该函数计算`随机森林模型`的`平均绝对误差(MAE)`。一般来说，我们希望`MAE`越低越好！

**方法1的得分(删除分类变量)**

我们使用`select_dtypes()`方法删除对象列。
```python
drop_X_train = X_train.select_dtypes(exclude=['object'])
drop_X_valid = X_valid.select_dtypes(exclude=['object'])

print("MAE from Approach 1 (Drop categorical variables):")
print(score_dataset(drop_X_train, drop_X_valid, y_train, y_valid))
```
```
MAE from Approach 1 (Drop categorical variables):
175703.48185157913
```
**方法2的得分(标签编码)**

`Scikit-learn`有一个`LabelEncoder`类，可以用来获取标签编码。我们循环遍历分类变量，并将标签编码器分别应用于每一列。
```python
from sklearn.preprocessing import LabelEncoder

# 复制一份数据防止改变源数据 
label_X_train = X_train.copy()
label_X_valid = X_valid.copy()

# 将标签编码器分别应用于每一列
label_encoder = LabelEncoder()
for col in object_cols:
    label_X_train[col] = label_encoder.fit_transform(X_train[col])
    label_X_valid[col] = label_encoder.transform(X_valid[col])

print("MAE from Approach 2 (Label Encoding):") 
print(score_dataset(label_X_train, label_X_valid, y_train, y_valid))
```
```
MAE from Approach 2 (Label Encoding):
165936.40548390493
```
在上面的代码中，对于每个列，我们随机分配一个唯一整数。这是一种比提供自定义标签更简单的常见方法；然而，如果我们为所有有序变量提供更好的信息标签，效果会更好。

**方法3的得分((One-Hot编码)**

我们使用`scikit-learn`的`OneHotEncoder`类来获得`one-hot编码`。有许多参数可定义。
- 设置`handle_unknown='ignore'`，以避免在验证数据包含训练数据中没有包括的值时发生错误
- 设置`sparse=False`可以确保将已编码的列作为`numpy数组`(而不是稀疏矩阵)返回。

为了使用这个编码器，我们提供了只有分类变量的数据列。举例来说，为了对训练数据进行编码，我们提供了`X_train[object_cols]`(代码中的`object_cols`表示分类变量名称，`X_train[object_cols]`包含了训练数据的所有分类变量)。
```python
from sklearn.preprocessing import OneHotEncoder

# 将one-hot编码器分别应用于每一列分类变量
OH_encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
OH_cols_train = pd.DataFrame(OH_encoder.fit_transform(X_train[object_cols]))
OH_cols_valid = pd.DataFrame(OH_encoder.transform(X_valid[object_cols]))

# One-hot编码时移除了index;补回来
OH_cols_train.index = X_train.index
OH_cols_valid.index = X_valid.index

# 删除分类列(将替换为One-hot编码),留下编码列
num_X_train = X_train.drop(object_cols, axis=1)
num_X_valid = X_valid.drop(object_cols, axis=1)

# 向数值特征添加One-hot编码列
OH_X_train = pd.concat([num_X_train, OH_cols_train], axis=1)
OH_X_valid = pd.concat([num_X_valid, OH_cols_valid], axis=1)

print("MAE from Approach 3 (One-Hot Encoding):") 
print(score_dataset(OH_X_train, OH_X_valid, y_train, y_valid))
```
输出结果：
```
MAE from Approach 3 (One-Hot Encoding):
166089.4893009678
```
## 4、哪种方法最好?
在本案例中，`删除分类变量`(方法1)的性能最差，因为它有最高的`MAE`分数。至于另外两种方法，由于返回的`MAE`分数值非常接近，没有太大差异。

通常，`one-hot编码`(方法3)的效果最好，`删除分类变量`(方法1)的效果最差，但还得视情况而定。

## 5、结论
这个世界充满了分类数据。如果您知道如何使用这种常见的数据类型，您将成为一个更高效的数据科学家！

## 6、去吧，皮卡丘
把你的新技能运用到下面的[练习中](https://www.kaggle.com/kernels/fork/3370279)！