🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 沃尔玛数据集 > 原文:[https://www.textbook.ds100.org/ch/14/feature ou one_hot.html](https://www.textbook.ds100.org/ch/14/feature ou one_hot.html) ``` # HIDDEN # Clear previously defined variables %reset -f # Set directory for data loading to work properly import os os.chdir(os.path.expanduser('~/notebooks/14')) ``` ``` # HIDDEN import warnings # Ignore numpy dtype warnings. These warnings are caused by an interaction # between numpy and Cython and can be safely ignored. # Reference: https://stackoverflow.com/a/40846742 warnings.filterwarnings("ignore", message="numpy.dtype size changed") warnings.filterwarnings("ignore", message="numpy.ufunc size changed") import numpy as np import matplotlib.pyplot as plt import pandas as pd import seaborn as sns %matplotlib inline import ipywidgets as widgets from ipywidgets import interact, interactive, fixed, interact_manual import nbinteract as nbi sns.set() sns.set_context('talk') np.set_printoptions(threshold=20, precision=2, suppress=True) pd.options.display.max_rows = 7 pd.options.display.max_columns = 8 pd.set_option('precision', 2) # This option stops scientific notation for pandas # pd.set_option('display.float_format', '{:.2f}'.format) ``` 2014 年,沃尔玛发布了一些销售数据,作为预测其商店每周销售额的竞争的一部分。我们已经获取了他们数据的一个子集,并将其加载到下面。 ``` walmart = pd.read_csv('walmart.csv') walmart ``` | | 日期 | 每周销售 | 伊索利德 | 温度 | 燃料价格 | 失业 | 降价 | | --- | --- | --- | --- | --- | --- | --- | --- | | 零 | 2010 年 2 月 5 日 | 24924.50 美元 | 不 | 四十二点三一 | 二点五七二 | 八点一零六 | 无降价 | | --- | --- | --- | --- | --- | --- | --- | --- | | 1 个 | 2010 年 2 月 12 日 | 46039.49 元 | 是的 | 三十八点五一 | 二点五四八 | 8.106 | No Markdown | | --- | --- | --- | --- | --- | --- | --- | --- | | 二 | 2010 年 2 月 19 日 | 41595.55 美元 | No | 三十九点九三 | 二点五一四 | 8.106 | No Markdown | | --- | --- | --- | --- | --- | --- | --- | --- | | …… | …… | ... | ... | ... | ... | ... | ... | | --- | --- | --- | --- | --- | --- | --- | --- | | 一百四十 | 2012 年 10 月 12 日 | 22764.01 年 | No | 六十二点九九 | 三点六零一 | 六点五七三 | 降价 2 | | --- | --- | --- | --- | --- | --- | --- | --- | | 一百四十一 | 2012 年 10 月 19 日 | 24185.27 美元 | No | 六十七点九七 | 三点五九四 | 6.573 | MarkDown2 | | --- | --- | --- | --- | --- | --- | --- | --- | | 一百四十二 | 2012 年 10 月 26 日 | 27390.81 元 | No | 六十九点一六 | 三点五零六 | 6.573 | 降价 1 | | --- | --- | --- | --- | --- | --- | --- | --- | 143 行×7 列 这些数据包含几个有趣的特性,包括一周是否包含假日(`IsHoliday`)、那周的失业率(`Unemployment`),以及商店在那一周提供哪些特价商品(`MarkDown`)。 我们的目标是创建一个模型,使用数据中的其他变量预测`Weekly_Sales`变量。使用线性回归模型,我们可以直接使用`Temperature`、`Fuel_Price`和`Unemployment`列,因为它们包含数值数据。 ## 使用 SciKit Learn[¶](#Fitting-a-Model-Using-Scikit-Learn)拟合模型 在前面的部分中,我们已经了解了如何获取成本函数的梯度,并使用梯度下降来拟合模型。为此,我们必须为模型定义 python 函数、成本函数、成本函数的梯度和梯度下降算法。虽然这对于演示概念如何工作很重要,但在本节中,我们将使用名为[`scikit-learn`](http://scikit-learn.org/)的机器学习库,它允许我们用更少的代码来适应模型。 例如,为了使用沃尔玛数据集中的数值列来拟合多重线性回归模型,我们首先创建一个包含用于预测的变量的二维 numpy 数组和一个包含我们想要预测的值的一维数组: ``` numerical_columns = ['Temperature', 'Fuel_Price', 'Unemployment'] X = walmart[numerical_columns].as_matrix() X ``` ``` array([[ 42.31, 2.57, 8.11], [ 38.51, 2.55, 8.11], [ 39.93, 2.51, 8.11], ..., [ 62.99, 3.6 , 6.57], [ 67.97, 3.59, 6.57], [ 69.16, 3.51, 6.57]]) ``` ``` y = walmart['Weekly_Sales'].as_matrix() y ``` ``` array([ 24924.5 , 46039.49, 41595.55, ..., 22764.01, 24185.27, 27390.81]) ``` 然后,我们从`scikit-learn`([docs](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html#sklearn.linear_model.LinearRegression))导入`LinearRegression`类,实例化它,并使用`X`调用`fit`方法来预测`y`。 请注意,以前我们必须手动将所有$1$的列添加到`X`矩阵中,以便进行截距线性回归。这一次,`scikit-learn`将在幕后处理截获列,为我们节省一些工作。 ``` from sklearn.linear_model import LinearRegression simple_classifier = LinearRegression() simple_classifier.fit(X, y) ``` ``` LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False) ``` 我们完了!当我们调用`.fit`时,`scikit-learn`找到线性回归参数,使最小二乘成本函数最小化。我们可以看到以下参数: ``` simple_classifier.coef_, simple_classifier.intercept_ ``` ``` (array([ -332.22, 1626.63, 1356.87]), 29642.700510138635) ``` 为了计算均方成本,我们可以要求分类器对输入数据`X`进行预测,并将预测值与实际值`y`进行比较。 ``` predictions = simple_classifier.predict(X) np.mean((predictions - y) ** 2) ``` ``` 74401210.603607252 ``` 平均平方误差看起来相当高。这很可能是因为我们的变量(温度、燃料价格和失业率)与每周销售的相关性很弱。 我们的数据中还有两个变量可能对预测更有用:列`IsHoliday`和列`MarkDown`。下面的框线图显示假日可能与周销售额有一定关系。 ``` sns.pointplot(x='IsHoliday', y='Weekly_Sales', data=walmart); ``` ![](https://img.kancloud.cn/01/3d/013d790774c94c0c2121a44874c6da99_412x270.jpg) 不同的降价类别似乎与不同的周销售额密切相关。 ``` markdowns = ['No Markdown', 'MarkDown1', 'MarkDown2', 'MarkDown3', 'MarkDown4', 'MarkDown5'] plt.figure(figsize=(7, 5)) sns.pointplot(x='Weekly_Sales', y='MarkDown', data=walmart, order=markdowns); ``` ![](https://img.kancloud.cn/9a/de/9ade51061da28bc38f375560075d76dd_512x325.jpg) 然而,`IsHoliday`和`MarkDown`列都包含分类数据,而不是数字数据,因此我们不能像回归那样使用它们。 ## 一个热编码 幸运的是,我们可以对这些分类变量执行**一次热编码**转换,将它们转换为数字变量。转换的工作方式如下:为类别变量中的每个唯一值创建一个新列。如果变量最初具有相应的值,则该列包含$1$,否则该列包含$0$。例如,下面的`MarkDown`列包含以下值: ``` # HIDDEN walmart[['MarkDown']] ``` | | MarkDown | | --- | --- | | 0 | No Markdown | | --- | --- | | 1 | No Markdown | | --- | --- | | 2 | No Markdown | | --- | --- | | ... | ... | | --- | --- | | 140 | MarkDown2 | | --- | --- | | 141 | MarkDown2 | | --- | --- | | 142 | MarkDown1 | | --- | --- | 143 行×1 列 此变量包含六个不同的唯一值:“no markdown”、“markdown1”、“markdown2”、“markdown3”、“markdown4”和“markdown5”。我们为每个值创建一列,以获得总共六列。然后,我们按照上面描述的方案用零和一填充列。 ``` # HIDDEN from sklearn.feature_extraction import DictVectorizer items = walmart[['MarkDown']].to_dict(orient='records') encoder = DictVectorizer(sparse=False) pd.DataFrame( data=encoder.fit_transform(items), columns=encoder.feature_names_ ) ``` | | markdown=降价 1 | markdown=降价 2 | markdown=降价 3 | markdown=降价 4 | markdown=降价 5 | 降价=无降价 | | --- | --- | --- | --- | --- | --- | --- | | 0 | 零 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 条 | | --- | --- | --- | --- | --- | --- | --- | | 1 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | | --- | --- | --- | --- | --- | --- | --- | | 2 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | | --- | --- | --- | --- | --- | --- | --- | | ... | ... | ... | ... | ... | ... | ... | | --- | --- | --- | --- | --- | --- | --- | | 140 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | | --- | --- | --- | --- | --- | --- | --- | | 141 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | | --- | --- | --- | --- | --- | --- | --- | | 142 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | | --- | --- | --- | --- | --- | --- | --- | 143 行×6 列 请注意,数据中的第一个值是“无降价”,因此只有转换表中第一行的最后一列标记为$1$。此外,数据中的最后一个值是“markdown1”,这导致第 142 行的第一列标记为$1$。 结果表的每一行将包含一个包含$1$的列;其余的将包含$0$的列。名称“one hot”反映了这样一个事实:只有一列是“hot”(标记为$1$)。 ## SciKit 学习[¶](#One-Hot-Encoding-in-Scikit-Learn)中的一个热编码 要执行一个热编码,我们可以使用`scikit-learn`的[`DictVectorizer`](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.DictVectorizer.html)类。要使用这个类,我们必须将数据框架转换为字典列表。dictvectorizer 类自动对分类数据(需要是字符串)进行一次热编码,并保持数字数据不变。 ``` from sklearn.feature_extraction import DictVectorizer all_columns = ['Temperature', 'Fuel_Price', 'Unemployment', 'IsHoliday', 'MarkDown'] records = walmart[all_columns].to_dict(orient='records') encoder = DictVectorizer(sparse=False) encoded_X = encoder.fit_transform(records) encoded_X ``` ``` array([[ 2.57, 1\. , 0\. , ..., 1\. , 42.31, 8.11], [ 2.55, 0\. , 1\. , ..., 1\. , 38.51, 8.11], [ 2.51, 1\. , 0\. , ..., 1\. , 39.93, 8.11], ..., [ 3.6 , 1\. , 0\. , ..., 0\. , 62.99, 6.57], [ 3.59, 1\. , 0\. , ..., 0\. , 67.97, 6.57], [ 3.51, 1\. , 0\. , ..., 0\. , 69.16, 6.57]]) ``` 为了更好地理解转换后的数据,我们可以用列名显示它: ``` pd.DataFrame(data=encoded_X, columns=encoder.feature_names_) ``` | | Fuel_Price | isholiday=否 | isholiday=是 | MarkDown=MarkDown1 | ... | MarkDown=MarkDown5 | MarkDown=No Markdown | Temperature | Unemployment | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 0 | 2.572 | 1.0 | 0.0 | 0.0 | ... | 0.0 | 1.0 | 42.31 | 8.106 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 1 | 2.548 | 0.0 | 1.0 | 0.0 | ... | 0.0 | 1.0 | 38.51 | 8.106 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2 | 2.514 | 1.0 | 0.0 | 0.0 | ... | 0.0 | 1.0 | 39.93 | 8.106 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 140 | 3.601 | 1.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 62.99 | 6.573 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 141 | 3.594 | 1.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 67.97 | 6.573 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 142 | 3.506 | 1.0 | 0.0 | 1.0 | ... | 0.0 | 0.0 | 69.16 | 6.573 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 143 行×11 列 数值变量(燃油价格、温度和失业率)保留为数字。分类变量(假日和降价)是一个热编码。当我们使用新的数据矩阵来拟合线性回归模型时,我们将为每列数据生成一个参数。因为这个数据矩阵包含 11 列,所以模型将有 12 个参数,因为我们为截距项设置了额外的参数。 ## 用转换后的数据拟合模型 我们现在可以使用`encoded_X`变量进行线性回归。 ``` clf = LinearRegression() clf.fit(encoded_X, y) ``` ``` LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False) ``` 如前所述,我们有十一个列参数和一个截距参数。 ``` clf.coef_, clf.intercept_ ``` ``` (array([ 1622.11, -2.04, 2.04, 962.91, 1805.06, -1748.48, -2336.8 , 215.06, 1102.25, -330.91, 1205.56]), 29723.135729284979) ``` 我们可以比较两个分类器的一些预测,看看两者之间是否有很大的区别。 ``` walmart[['Weekly_Sales']].assign( pred_numeric=simple_classifier.predict(X), pred_both=clf.predict(encoded_X) ) ``` | | Weekly_Sales | pred 数字 | 两者皆可预测 | | --- | --- | --- | --- | | 0 | 24924.50 | 30768.878035 | 30766.790214 年 | | --- | --- | --- | --- | | 1 | 46039.49 | 31992.279504 | 31989.410395 年 | | --- | --- | --- | --- | | 2 | 41595.55 | 31465.220158 号 | 31460.280008 个 | | --- | --- | --- | --- | | ... | ... | ... | ... | | --- | --- | --- | --- | | 140 | 22764.01 | 23492.262649 | 24447.348979 个 | | --- | --- | --- | --- | | 141 | 24185.27 | 21826.414794 个 | 22788.049554 个 | | --- | --- | --- | --- | | 142 | 27390.81 | 21287.928537 | 21409.367463 号 | | --- | --- | --- | --- | 143 行×3 列 这两个模型的预测似乎非常相似。两组预测的散点图证实了这一点。 ``` plt.scatter(simple_classifier.predict(X), clf.predict(encoded_X)) plt.title('Predictions using all data vs. numerical features only') plt.xlabel('Predictions using numerical features') plt.ylabel('Predictions using all features'); ``` ![](https://img.kancloud.cn/68/a8/68a8fe8a01263fcc42818d76c5ce5922_426x287.jpg) ## 模型诊断 为什么会这样?我们可以检查两个模型学习的参数。下表显示了分类器所获得的权重,该分类器只使用数值变量而不使用一个热编码: ``` # HIDDEN def clf_params(names, clf): weights = ( np.append(clf.coef_, clf.intercept_) ) return pd.DataFrame(weights, names + ['Intercept']) clf_params(numerical_columns, simple_classifier) ``` | | 0 | | --- | --- | | Temperature | -332.221180 个 | | --- | --- | | Fuel_Price | 1626.625604 年 | | --- | --- | | Unemployment | 1356.868319 号 | | --- | --- | | 拦截 | 29642.700510 个 | | --- | --- | 下表显示了使用一个热编码的分类器学习的权重。 ``` # HIDDEN pd.options.display.max_rows = 13 display(clf_params(encoder.feature_names_, clf)) pd.options.display.max_rows = 7 ``` | | 0 | | --- | --- | | Fuel_Price | 1622.106239 年 | | --- | --- | | IsHoliday=No | -2.041451 年 | | --- | --- | | IsHoliday=Yes | 2.041451 年 | | --- | --- | | MarkDown=MarkDown1 | 962.908849 号 | | --- | --- | | MarkDown=MarkDown2 | 1805.059613 年 | | --- | --- | | MarkDown=MarkDown3 | -1748.475046 年 | | --- | --- | | MarkDown=MarkDown4 | -2336.799791 | | --- | --- | | MarkDown=MarkDown5 | 215.060616 年 | | --- | --- | | MarkDown=No Markdown | 1102.245760 号 | | --- | --- | | Temperature | -330.912587 号 | | --- | --- | | Unemployment | 1205.564331 年 | | --- | --- | | Intercept | 29723.135729 个 | | --- | --- | 我们可以看到,即使我们使用一个热编码列来拟合线性回归模型,燃油价格、温度和失业率的权重也与以前的值非常相似。与截距项相比,所有权重都很小,这意味着大多数变量仍然与实际销售额略有关联。事实上,`IsHoliday`变量的模型权重非常低,以至于在预测日期是否为假日时几乎没有区别。尽管一些`MarkDown`权重相当大,但许多降价事件在数据集中只出现几次。 ``` walmart['MarkDown'].value_counts() ``` ``` No Markdown 92 MarkDown1 25 MarkDown2 13 MarkDown5 9 MarkDown4 2 MarkDown3 2 Name: MarkDown, dtype: int64 ``` 这表明我们可能需要收集更多的数据,以便模型更好地利用降价事件对销售额的影响。(实际上,这里显示的数据集是沃尔玛发布的[大得多的数据集](https://www.kaggle.com/c/walmart-recruiting-store-sales-forecasting)的一个子集。使用整个数据集而不是一个子集训练模型将是一个有用的练习。) ## 摘要[¶](#Summary) 我们已经学会了使用一种热编码,这是一种对分类数据进行线性回归的有用技术。尽管在这个特定的例子中,转换对我们的模型没有太大的影响,但在实际中,这种技术在处理分类数据时得到了广泛的应用。一个热编码还说明了特征工程的一般原理,它采用原始数据矩阵,并将其转换为一个潜在的更有用的矩阵。