本文为和鲸python 特征工程入门与实践·闯关训练营资料整理而来,加入了自己的理解(by GPT4o)
原作者:云中君,大厂后端研发工程师
目录
0、 总结
在本节中,讨论了针对数值数据和文本数据进行特征构建的方法。以下是本节的主要内容和关键学习点的总结:
数值数据的特征构建:
填充分类特征:对于分类特征,可能需要处理缺失值。学习如何进行填充以确保数据的完整性和准确性。
编码分类变量:将分类变量转化为机器学习模型可以理解的形式。
扩展数值特征:通过使用现有数值特征创建新的特征,以提供更多信息给模型。
文本数据的特征构建:
词袋法:将文本表示为单词的出现频率,转化为向量形式,可用于机器学习算法。
CountVectorizer:将文本数据转换为其向量表示的最常用办法,和虚拟变量类似。
TF-IDF:考虑单词在文档中的重要性,通过计算权重进行特征构建,用于文本分类等任务。
下一步展望:
学习如何选择合适的特征用于机器学习。
特征选择是优化模型性能的关键一步,可以通过过滤法、包装法和嵌入法等方法进行选择。
通过本节的学习,你已经掌握了数值数据和文本数据特征构建的基本方法,为下一步选择合适的特征奠定了基础。在进入下一关之前,确保对特征构建的概念有清晰的理解,并准备好学习如何进行特征选择。祝你在学习的过程中取得更大的进步!
1、前言
本节,我们使用现有特征构建全新的特征,主要从以下几个方面进行讲解
- 检查数据集
- 填充分类特征
- 编码分类变量
- 扩展数值特征
- 针对文本的特征构建
2、基础知识
2.1数据集
自己创建数据集,展示不同的数据等级和类型
import pandas as pd
X = pd.DataFrame({'city':['tokyo', None, 'london', 'seattle', 'san francisco', 'tokyo'],
'boolean':['yes', 'no', None, 'no', 'no', 'yes'],
'ordinal_column':['somewhat like', 'like', 'somewhat like', 'like', 'somewhat like', 'dislike'],
'quantitative_column':[1, 11, -.5, 10, None, 20]})
print(X)
city boolean ordinal_column quantitative_column
0 tokyo yes somewhat like 1.0
1 None no like 11.0
2 london None somewhat like -0.5
3 seattle no like 10.0
4 san francisco no somewhat like NaN
5 tokyo yes dislike 20.0
- boolean(布尔值):二元分类数据(是/否),定类等级
- city(城市):分类数据,也是定类等级
- ordinal_column(顺序列):顺序数据,定序等级
- quantitative_column(定量列):是整数,定比等级。
2.2填充分类特征
用isnull和sum方法查看缺失值
X.isnull().sum()
city 1
boolean 1
ordinal_column 0
quantitative_column 1
dtype: int64
scikit-learn的Imputer类有一个most_frequent方法可以用在定性数据上,但是只能处理整数型的分类数据。
对于数值数据,可以通过计算均值的方法填充缺失值;而对于分类数据,可以计算出最常见的类别用于填充。
# 寻找city列中最常见的元素
most_frequent = X['city'].value_counts().index[0]
most_frequent
'tokyo'
# 用最常见值填充city列
X['city'].fillna(most_frequent)
0 tokyo
1 tokyo
2 london
3 seattle
4 san francisco
5 tokyo
Name: city, dtype: object
自定义分类填充器
机器学习流水线:
- 我们可以用流水线按顺序应用转换和最终的预测器
- 流水线的中间步骤只能是转换,这意味着它们必须实现fit和transform方法
- 最终的预测器只需要实现fit方法
流水线的目的是将几个可以交叉验证的步骤组装在一起,并设置不同的参数。在为每个需要填充的列构建好自定义转换器后,就可以把它们传入流水线,一口气转换好数据。
# 创建自定义分类填充器
from sklearn.base import TransformerMixin
class CustomCategoryImputer(TransformerMixin):
def __init__(self, cols=None):
self.cols = cols
def transform(self, df):
X = df.copy()
for col in self.cols:
X[col].fillna(X[col].value_counts().index[0], inplace=True)
return X
def fit(self, *_):
return self
# 在列上应用自定义分类填充器
cci = CustomCategoryImputer(cols=['city', 'boolean'])
cci.fit_transform(X)
city | boolean | ordinal_column | quantitative_column | |
---|---|---|---|---|
0 | tokyo | yes | somewhat like | 1.0 |
1 | tokyo | no | like | 11.0 |
2 | london | no | somewhat like | -0.5 |
3 | seattle | no | like | 10.0 |
4 | san francisco | no | somewhat like | NaN |
5 | tokyo | yes | dislike | 20.0 |
自定义定量填充器
# 自定义定量填充器
from sklearn.impute import SimpleImputer
from sklearn.base import TransformerMixin
class CustomQuantitativeImputer(TransformerMixin):
def __init__(self, cols=None, strategy='mean'):
self.cols = cols
self.strategy = strategy
def transform(self, df):
X = df.copy()
impute = SimpleImputer(strategy=self.strategy)
for col in self.cols:
X[col] = impute.fit_transform(X[[col]])
return X
def fit(self, *_):
return self
cqi = CustomQuantitativeImputer(cols=['quantitative_column'], strategy='mean')
cqi.fit_transform(X)
city | boolean | ordinal_column | quantitative_column | |
---|---|---|---|---|
0 | tokyo | yes | somewhat like | 1.0 |
1 | None | no | like | 11.0 |
2 | london | None | somewhat like | -0.5 |
3 | seattle | no | like | 10.0 |
4 | san francisco | no | somewhat like | 8.3 |
5 | tokyo | yes | dislike | 20.0 |
流水线调用
# 从sklearn导入Pipeline
from sklearn.pipeline import Pipeline
imputer = Pipeline([('quant', cqi), ('category', cci)])
imputer.fit_transform(X)
city | boolean | ordinal_column | quantitative_column | |
---|---|---|---|---|
0 | tokyo | yes | somewhat like | 1.0 |
1 | tokyo | no | like | 11.0 |
2 | london | no | somewhat like | -0.5 |
3 | seattle | no | like | 10.0 |
4 | san francisco | no | somewhat like | 8.3 |
5 | tokyo | yes | dislike | 20.0 |
2.3编码分类变量
将分类数据转换为数值数据,以供机器学习模型使用
定类等级的编码
将分类数据转换为虚拟变量(dummy variable)
- 用Pandas自动找到分类变量并进行编码
- 创建自定义虚拟变量编码器,在流水线中工作
pd.get_dummies(X,
columns = ['city', 'boolean'], # 要虚拟化的列
prefix_sep='__') # 前缀(列名)和单元格值之间的分隔符
ordinal_column | quantitative_column | city__london | city__san francisco | city__seattle | city__tokyo | boolean__no | boolean__yes | |
---|---|---|---|---|---|---|---|---|
0 | somewhat like | 1.0 | 0 | 0 | 0 | 1 | 0 | 1 |
1 | like | 11.0 | 0 | 0 | 0 | 0 | 1 | 0 |
2 | somewhat like | -0.5 | 1 | 0 | 0 | 0 | 0 | 0 |
3 | like | 10.0 | 0 | 0 | 1 | 0 | 1 | 0 |
4 | somewhat like | NaN | 0 | 1 | 0 | 0 | 1 | 0 |
5 | dislike | 20.0 | 0 | 0 | 0 | 1 | 0 | 1 |
# 自定义虚拟变量编码器
class CustomDummifier(TransformerMixin):
def __init__(self, cols=None):
self.cols = cols
def transform(self, X):
return pd.get_dummies(X, columns=self.cols)
def fit(self, *_):
return self
cd = CustomDummifier(cols=['boolean', 'city'])
cd.fit_transform(X)
ordinal_column | quantitative_column | boolean_no | boolean_yes | city_london | city_san francisco | city_seattle | city_tokyo | |
---|---|---|---|---|---|---|---|---|
0 | somewhat like | 1.0 | 0 | 1 | 0 | 0 | 0 | 1 |
1 | like | 11.0 | 1 | 0 | 0 | 0 | 0 | 0 |
2 | somewhat like | -0.5 | 0 | 0 | 1 | 0 | 0 | 0 |
3 | like | 10.0 | 1 | 0 | 0 | 0 | 1 | 0 |
4 | somewhat like | NaN | 1 | 0 | 0 | 1 | 0 | 0 |
5 | dislike | 20.0 | 0 | 1 | 0 | 0 | 0 | 1 |
定序等级的编码
为了保持顺序,我们使用标签编码器。标签编码器是指,顺序数据的每个标签都会有一个相关数值。在我们的例子中,这意味着顺序列的值(dislike、somewhat like和like)会用0、1、2来表示。
# 创建一个列表,顺序数据对应于列表索引
ordering = ['dislike', 'somewhat like', 'like'] # 0是dislike,1是somewhat like,2是like
# 在将ordering排序映射到顺序列之前,先看一下列
print(X['ordinal_column'])
0 somewhat like
1 like
2 somewhat like
3 like
4 somewhat like
5 dislike
Name: ordinal_column, dtype: object
# 将ordering映射到顺序列
print(X['ordinal_column'].map(lambda x: ordering.index(x)))
0 1
1 2
2 1
3 2
4 1
5 0
Name: ordinal_column, dtype: int64
# 将自定义标签编码器放进流水线中
class CustomEncoder(TransformerMixin):
def __init__(self, col, ordering=None):
self.ordering = ordering
self.col = col
def transform(self, df):
X = df.copy()
X[self.col] = X[self.col].map(lambda x: self.ordering.index(x))
return X
def fit(self, *_):
return self
ce = CustomEncoder(col='ordinal_column', ordering = ['dislike', 'somewhat like', 'like'])
ce.fit_transform(X)
city | boolean | ordinal_column | quantitative_column | |
---|---|---|---|---|
0 | tokyo | yes | 1 | 1.0 |
1 | None | no | 2 | 11.0 |
2 | london | None | 1 | -0.5 |
3 | seattle | no | 2 | 10.0 |
4 | san francisco | no | 1 | NaN |
5 | tokyo | yes | 0 | 20.0 |
将连续特征分箱
用cut函数将数据分箱(binning),亦称为分桶(bucketing)。意思就是,它会创建数据的范围。
# 默认的类别名是分箱
pd.cut(X['quantitative_column'], bins=3)
0 (-0.52, 6.333]
1 (6.333, 13.167]
2 (-0.52, 6.333]
3 (6.333, 13.167]
4 NaN
5 (13.167, 20.0]
Name: quantitative_column, dtype: category
Categories (3, interval[float64, right]): [(-0.52, 6.333] < (6.333, 13.167] < (13.167, 20.0]]
# 不使用标签
pd.cut(X['quantitative_column'], bins=3, labels=False)
0 0.0
1 1.0
2 0.0
3 1.0
4 NaN
5 2.0
Name: quantitative_column, dtype: float64
class CustomCutter(TransformerMixin):
def __init__(self, col, bins, labels=False):
self.labels = labels
self.bins = bins
self.col = col
def transform(self, df):
X = df.copy()
X[self.col] = pd.cut(X[self.col], bins=self.bins, labels=self.labels)
return X
def fit(self, *_):
return self
cc = CustomCutter(col='quantitative_column', bins=3)
cc.fit_transform(X)
city | boolean | ordinal_column | quantitative_column | |
---|---|---|---|---|
0 | tokyo | yes | somewhat like | 0.0 |
1 | None | no | like | 1.0 |
2 | london | None | somewhat like | 0.0 |
3 | seattle | no | like | 1.0 |
4 | san francisco | no | somewhat like | NaN |
5 | tokyo | yes | dislike | 2.0 |
创建流水线
流水线的顺序是:
- 用imputer填充缺失值
- 用虚拟变量填充分类列(one-hot编码)
- 对ordinal_column进行编码
- 将quantitative_column分箱
from sklearn.pipeline import Pipeline
pipe = Pipeline([("imputer", imputer), ('dummify', cd), ('encode', ce), ('cut', cc)])
# 进入流水线前的数据
print(X)
city boolean ordinal_column quantitative_column
0 tokyo yes somewhat like 1.0
1 None no like 11.0
2 london None somewhat like -0.5
3 seattle no like 10.0
4 san francisco no somewhat like NaN
5 tokyo yes dislike 20.0
# 拟合流水线
pipe.fit(X)
Pipeline(steps=[('imputer',Pipeline(steps=[('quant', <__main__.CustomQuantitativeImputer object at 0x00000211553EBA00>), ('category', <__main__.CustomCategoryImputer object at 0x000002113D3443A0>)])), ('dummify', <__main__.CustomDummifier object at 0x00000211559CEA60>), ('encode', <__main__.CustomEncoder object at 0x00000211559D1BB0>), ('cut', <__main__.CustomCutter object at 0x00000211559EB520>)])</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class="sk-container" hidden><div class="sk-item sk-dashed-wrapped"><div class="sk-label-container"><div class="sk-label sk-toggleable"><input class="sk-toggleable__control sk-hidden--visually" id="sk-estimator-id-1" type="checkbox" ><label for="sk-estimator-id-1" class="sk-toggleable__label sk-toggleable__label-arrow">Pipeline</label><div class="sk-toggleable__content"><pre>Pipeline(steps=[('imputer', Pipeline(steps=[('quant', <__main__.CustomQuantitativeImputer object at 0x00000211553EBA00>), ('category', <__main__.CustomCategoryImputer object at 0x000002113D3443A0>)])), ('dummify', <__main__.CustomDummifier object at 0x00000211559CEA60>), ('encode', <__main__.CustomEncoder object at 0x00000211559D1BB0>), ('cut', <__main__.CustomCutter object at 0x00000211559EB520>)])</pre></div></div></div><div class="sk-serial"><div class="sk-item"><div class="sk-label-container"><div class="sk-label sk-toggleable"><input class="sk-toggleable__control sk-hidden--visually" id="sk-estimator-id-2" type="checkbox" ><label for="sk-estimator-id-2" class="sk-toggleable__label sk-toggleable__label-arrow">imputer: Pipeline</label><div class="sk-toggleable__content"><pre>Pipeline(steps=[('quant', <__main__.CustomQuantitativeImputer object at 0x00000211553EBA00>), ('category', <__main__.CustomCategoryImputer object at 0x000002113D3443A0>)])</pre></div></div></div><div class="sk-serial"><div class="sk-item"><div class="sk-estimator sk-toggleable"><input class="sk-toggleable__control sk-hidden--visually" id="sk-estimator-id-3" type="checkbox" ><label for="sk-estimator-id-3" class="sk-toggleable__label sk-toggleable__label-arrow">CustomQuantitativeImputer</label><div class="sk-toggleable__content"><pre><__main__.CustomQuantitativeImputer object at 0x00000211553EBA00></pre></div></div></div><div class="sk-item"><div class="sk-estimator sk-toggleable"><input class="sk-toggleable__control sk-hidden--visually" id="sk-estimator-id-4" type="checkbox" ><label for="sk-estimator-id-4" class="sk-toggleable__label sk-toggleable__label-arrow">CustomCategoryImputer</label><div class="sk-toggleable__content"><pre><__main__.CustomCategoryImputer object at 0x000002113D3443A0></pre></div></div></div></div></div><div class="sk-item"><div class="sk-estimator sk-toggleable"><input class="sk-toggleable__control sk-hidden--visually" id="sk-estimator-id-5" type="checkbox" ><label for="sk-estimator-id-5" class="sk-toggleable__label sk-toggleable__label-arrow">CustomDummifier</label><div class="sk-toggleable__content"><pre><__main__.CustomDummifier object at 0x00000211559CEA60></pre></div></div></div><div class="sk-item"><div class="sk-estimator sk-toggleable"><input class="sk-toggleable__control sk-hidden--visually" id="sk-estimator-id-6" type="checkbox" ><label for="sk-estimator-id-6" class="sk-toggleable__label sk-toggleable__label-arrow">CustomEncoder</label><div class="sk-toggleable__content"><pre><__main__.CustomEncoder object at 0x00000211559D1BB0></pre></div></div></div><div class="sk-item"><div class="sk-estimator sk-toggleable"><input class="sk-toggleable__control sk-hidden--visually" id="sk-estimator-id-7" type="checkbox" ><label for="sk-estimator-id-7" class="sk-toggleable__label sk-toggleable__label-arrow">CustomCutter</label><div class="sk-toggleable__content"><pre><__main__.CustomCutter object at 0x00000211559EB520></pre></div></div></div></div></div></div></div>
pipe.transform(X)
ordinal_column | quantitative_column | boolean_no | boolean_yes | city_london | city_san francisco | city_seattle | city_tokyo | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
1 | 2 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
2 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
3 | 2 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
4 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
5 | 0 | 2 | 0 | 1 | 0 | 0 | 0 | 1 |
2.4扩展数值特征
根据胸部加速度计识别动作的数据集
数据集按参与者划分,包含以下内容:
- 序号
- x轴加速度
- y轴加速度
- z轴加速度
- 标签。标签是数字,每个数字代表一种动作(activity):1在电脑前工作;2站立、走路和上下楼梯;3站立;4走路;5上下楼梯;6与人边走边聊;7站立着讲话。
path = './data/activity_recognizer.csv'
df = pd.read_csv(path, header=None)
df.columns = ['index', 'x', 'y', 'z', 'activity']
df.head()
index | x | y | z | activity | |
---|---|---|---|---|---|
0 | 0.0 | 1502 | 2215 | 2153 | 1 |
1 | 1.0 | 1667 | 2072 | 2047 | 1 |
2 | 2.0 | 1611 | 1957 | 1906 | 1 |
3 | 3.0 | 1601 | 1939 | 1831 | 1 |
4 | 4.0 | 1643 | 1965 | 1879 | 1 |
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 162501 entries, 0 to 162500
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 index 162501 non-null float64
1 x 162501 non-null int64
2 y 162501 non-null int64
3 z 162501 non-null int64
4 activity 162501 non-null int64
dtypes: float64(1), int64(4)
memory usage: 6.2 MB
查看空准确率
df['activity'].value_counts(normalize=True)
7 0.515369
1 0.207242
4 0.165291
3 0.068793
5 0.019637
6 0.017951
2 0.005711
0 0.000006
Name: activity, dtype: float64
空准确率是51.54%,意味着如果我们猜7(站立着讲话),正确率就超过一半了
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
import warnings
warnings.filterwarnings('ignore')
X = df[['x', 'y', 'z']]
# 删除响应变量,建立特征矩阵
y = df['activity']
# 网格搜索所需的变量和实例
# 需要试验的KNN模型参数
knn_params = {'n_neighbors':[3, 4, 5, 6]}
knn = KNeighborsClassifier()
grid = GridSearchCV(knn, knn_params)
grid.fit(X, y)
print(grid.best_score_, grid.best_params_)
0.7357680039194061 {'n_neighbors': 5}
使用5个邻居作为参数时,KNN模型准确率达到了72.08%,比51.54%的空准确率高得多
多项式特征
使用Polynomial-Features创建新的列,它们是原有列的乘积,用于捕获特征交互。
- degree是多项式特征的阶数,默认值是2。
- interaction_only是布尔值:如果为True(默认False),表示只生成互相影响/交互的特征,也就是不同阶数特征的乘积。
- include_bias也是布尔值:如果为True(默认),会生成一列阶数为0的偏差列,也就是说列中全是数字1。
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2, include_bias=False, interaction_only=False)
X_poly = poly.fit_transform(X)
X_poly.shape
(162501, 9)
pd.DataFrame(X_poly, columns=poly.get_feature_names_out()).head()
x | y | z | x^2 | x y | x z | y^2 | y z | z^2 | |
---|---|---|---|---|---|---|---|---|---|
0 | 1502.0 | 2215.0 | 2153.0 | 2256004.0 | 3326930.0 | 3233806.0 | 4906225.0 | 4768895.0 | 4635409.0 |
1 | 1667.0 | 2072.0 | 2047.0 | 2778889.0 | 3454024.0 | 3412349.0 | 4293184.0 | 4241384.0 | 4190209.0 |
2 | 1611.0 | 1957.0 | 1906.0 | 2595321.0 | 3152727.0 | 3070566.0 | 3829849.0 | 3730042.0 | 3632836.0 |
3 | 1601.0 | 1939.0 | 1831.0 | 2563201.0 | 3104339.0 | 2931431.0 | 3759721.0 | 3550309.0 | 3352561.0 |
4 | 1643.0 | 1965.0 | 1879.0 | 2699449.0 | 3228495.0 | 3087197.0 | 3861225.0 | 3692235.0 | 3530641.0 |
探索性数据分析
%matplotlib inline
import seaborn as sns
sns.heatmap(pd.DataFrame(X_poly, columns=poly.get_feature_names_out()).corr())
<AxesSubplot:>
# 将interaction_only被设置成了True
poly = PolynomialFeatures(degree=2, include_bias=False, interaction_only=True)
X_poly = poly.fit_transform(X)
print(X_poly.shape)
(162501, 6)
pd.DataFrame(X_poly, columns=poly.get_feature_names_out()).head()
x | y | z | x y | x z | y z | |
---|---|---|---|---|---|---|
0 | 1502.0 | 2215.0 | 2153.0 | 3326930.0 | 3233806.0 | 4768895.0 |
1 | 1667.0 | 2072.0 | 2047.0 | 3454024.0 | 3412349.0 | 4241384.0 |
2 | 1611.0 | 1957.0 | 1906.0 | 3152727.0 | 3070566.0 | 3730042.0 |
3 | 1601.0 | 1939.0 | 1831.0 | 3104339.0 | 2931431.0 | 3550309.0 |
4 | 1643.0 | 1965.0 | 1879.0 | 3228495.0 | 3087197.0 | 3692235.0 |
sns.heatmap(pd.DataFrame(X_poly, columns=poly.get_feature_names_out()).corr())
<AxesSubplot:>
# 流水线
from sklearn.pipeline import Pipeline
pipe_params = {'poly_features__degree':[1, 2, 3], 'poly_features__interaction_only':[True, False], 'classify__n_neighbors':[3, 4, 5, 6]}
pipe = Pipeline([('poly_features', poly), ('classify', knn)])
grid = GridSearchCV(pipe, pipe_params)
grid.fit(X, y)
print(grid.best_score_, grid.best_params_)
0.7395217705490719 {'classify__n_neighbors': 5, 'poly_features__degree': 2, 'poly_features__interaction_only': True}
2.5文本专用特征构建
词袋法
通过单词的出现来描述文档,完全忽略单词在文档中的位置。词袋的3个步骤是:
- 分词(tokenizing)
- 计数(counting)
- 归一化(normalizing)
twitter_path = './data/twitter_sentiment.csv'
tweets = pd.read_csv(twitter_path, encoding='latin1')
tweets.head()
ItemID | Sentiment | SentimentText | |
---|---|---|---|
0 | 1 | 0 | is so sad for my APL frie... |
1 | 2 | 0 | I missed the New Moon trail... |
2 | 3 | 1 | omg its already 7:30 :O |
3 | 4 | 0 | .. Omgaga. Im sooo im gunna CRy. I'... |
4 | 5 | 0 | i think mi bf is cheating on me!!! ... |
del tweets['ItemID']
tweets.head()
Sentiment | SentimentText | |
---|---|---|
0 | 0 | is so sad for my APL frie... |
1 | 0 | I missed the New Moon trail... |
2 | 1 | omg its already 7:30 :O |
3 | 0 | .. Omgaga. Im sooo im gunna CRy. I'... |
4 | 0 | i think mi bf is cheating on me!!! ... |
from sklearn.feature_extraction.text import CountVectorizer
X = tweets['SentimentText']
y = tweets['Sentiment']
vect = CountVectorizer()
_ = vect.fit_transform(X)
print(_.shape)
(99989, 105849)
CountVectorizer的参数
CountVectorizer将文本列转换为矩阵,其中列是词项,单元值是每个文档中每个词项的出现次数
- stop_words:停用词
- min_df:忽略在文档中出现频率低于阈值的词,减少特征的数量
- max_df:忽略在文档中出现频率高于阈值的词,减少特征的数量
- ngram_range:接收一个元组,表示n值的范围(代表要提取的不同n-gram的数量)上下界
- analyzer:设置分析器作为参数,以判断特征是单词还是短语。默认是单词
vect = CountVectorizer(stop_words='english') # 删除英语停用词(if、a、the, 等等)
_ = vect.fit_transform(X)
print(_.shape)
(99989, 105545)
vect = CountVectorizer(min_df=.05) # 只保留至少在5%文档中出现的单词
# 减少特征数
_ = vect.fit_transform(X)
print(_.shape)
(99989, 31)
vect = CountVectorizer(max_df=.8) # 只保留至多在80%文档中出现的单词
# “推断”停用词
_ = vect.fit_transform(X)
print(_.shape)
(99989, 105849)
vect = CountVectorizer(ngram_range=(1, 5)) # 包括最多5个单词的短语
_ = vect.fit_transform(X)
print(_.shape) # 特征数爆炸
(99989, 3219557)
vect = CountVectorizer(analyzer='word') # 默认分析器,划分为单词
_ = vect.fit_transform(X)
print(_.shape)
(99989, 105849)
词干提取(stemming)是一种常见的自然语言处理方法,可以将词汇中的词干提取出来,也就是把单词转换为其词根,从而缩小词汇量
from nltk.stem.snowball import SnowballStemmer
stemmer = SnowballStemmer('english')
stemmer.stem('interesting')
'interest'
# 将文本变成词根的函数
def word_tokenize(text, how='lemma'):
words = text.split(' ') # 按词分词
return [stemmer.stem(word) for word in words]
word_tokenize("hello you are very interesting")
['hello', 'you', 'are', 'veri', 'interest']
vect = CountVectorizer(analyzer=word_tokenize)
_ = vect.fit_transform(X)
print(_.shape) # 单词变小,特征少了
(99989, 154397)
TF-IDF矢量化
-
TF(term frequency,词频):衡量词在文档中出现的频率。由于文档的长度不同,词在长文中的出现次数有可能比在短文中出现的次数多得多。因此,一般会对词频进行归一化,用其除以文档长度或文档的总词数。
-
IDF(inverse document frequency,逆文档频率):衡量词的重要性。在计算词频时,我们认为所有的词都同等重要。但是某些词(如is、of和that)有可能出现很多次,但这些词并不重要。因此,我们需要减少常见词的权重,加大稀有词的权重。
再次强调,TfidfVectorizer和CountVectorizer相同,都从词项构造了特征,但是TfidfVectorizer进一步将词项计数按照在语料库中出现的频率进行了归一化
from sklearn.feature_extraction.text import TfidfVectorizer
# 用CountVectorizer生成文档-词矩阵
vect = CountVectorizer()
_ = vect.fit_transform(X)
print(_.shape, _[0,:].mean())
(99989, 105849) 6.613194267305311e-05
# TfidfVectorizer
vect = TfidfVectorizer()
_ = vect.fit_transform(X)
print(_.shape, _[0,:].mean()) # 行列数相同,内容不同
(99989, 105849) 2.1863060975751192e-05
在机器学习流水线中使用文本
from sklearn.naive_bayes import MultinomialNB
# 取空准确率
y.value_counts(normalize=True)
1 0.564632
0 0.435368
Name: Sentiment, dtype: float64
要让准确率超过56.5%。我们分两步创建流水线:
- 用CountVectorizer将推文变成特征
- 用朴素贝叶斯模型MultiNomialNB进行正负面情绪的分类
# 设置流水线参数
pipe_params = {'vect__ngram_range':[(1, 1), (1, 2)], 'vect__max_features':[1000, 10000], 'vect__stop_words':[None, 'english']}
# 实例化流水线
pipe = Pipeline([('vect', CountVectorizer()), ('classify', MultinomialNB())])
# 实例化网格搜索
grid = GridSearchCV(pipe, pipe_params)
# 拟合网格搜索对象
grid.fit(X, y)
# 取结果
print(grid.best_score_, grid.best_params_)
0.7558931564507154 {'vect__max_features': 10000, 'vect__ngram_range': (1, 2), 'vect__stop_words': None}
scikit-learn有一个FeatureUnion模块,可以水平(并排)排列特征。这样,在一个流水线中可以使用多种类型的文本特征构建器。
from sklearn.pipeline import FeatureUnion
# 单独的特征构建器对象
featurizer = FeatureUnion([('tfidf_vect', TfidfVectorizer()),
('count_vect', CountVectorizer())])
_ = featurizer.fit_transform(X)
print(_.shape) # 行数相同,但列数为2倍
(99989, 211698)
# 改变featurizer对象的参数,看看效果
featurizer.set_params(tfidf_vect__max_features=100, count_vect__ngram_range=(1, 2), count_vect__max_features=300)
# TfidfVectorizer只保留100个单词,而CountVectorizer保留300个1~2个单词的短语
_ = featurizer.fit_transform(X)
print(_.shape) # 行数相同,但列数为2倍
(99989, 400)
# 完整的流水线
pipe_params = {'featurizer__count_vect__ngram_range':[(1, 1), (1, 2)],
'featurizer__count_vect__max_features':[1000, 10000],
'featurizer__count_vect__stop_words':[None, 'english'],
'featurizer__tfidf_vect__ngram_range':[(1, 1), (1, 2)],
'featurizer__tfidf_vect__max_features':[1000, 10000],
'featurizer__tfidf_vect__stop_words':[None, 'english']}
pipe = Pipeline([('featurizer', featurizer), ('classify', MultinomialNB())])
grid = GridSearchCV(pipe, pipe_params)
grid.fit(X, y)
print(grid.best_score_, grid.best_params_)
0.7580933924767184 {'featurizer__count_vect__max_features': 10000, 'featurizer__count_vect__ngram_range': (1, 2), 'featurizer__count_vect__stop_words': None, 'featurizer__tfidf_vect__max_features': 10000, 'featurizer__tfidf_vect__ngram_range': (1, 1), 'featurizer__tfidf_vect__stop_words': 'english'}
3、代码解析
3.1 自定义分类填充器代码解析
这段代码实现了一个自定义的分类填充器,用于填充数据集中某些列中的缺失值。下面是对代码的详细解析:
1. 导入必要的库
from sklearn.base import TransformerMixin
TransformerMixin
是 Scikit-learn 中的一个混入类(Mixin),它允许你方便地创建自定义的转换器(Transformer)。通过继承TransformerMixin
,你可以轻松实现fit
和transform
方法,并且让你的自定义类兼容 Scikit-learn 的流水线(Pipeline)。
2. 定义 CustomCategoryImputer
类
class CustomCategoryImputer(TransformerMixin):
def __init__(self, cols=None):
self.cols = cols
- 这是一个自定义的类,用于填充指定列的缺失值。
__init__
方法是类的构造函数,用来初始化对象。当你创建CustomCategoryImputer
的实例时,cols
参数允许你指定需要填充的列。如果没有指定列,cols
默认值为None
。
3. 定义 transform
方法
def transform(self, df):
X = df.copy()
for col in self.cols:
X[col].fillna(X[col].value_counts().index[0], inplace=True)
return X
transform
方法用于执行实际的转换操作。- 首先,它创建了数据框
df
的副本X
,以确保不修改原始数据。 - 接下来,它对指定的列(即
self.cols
中的每一列)进行遍历,并使用fillna
方法填充缺失值。填充值是该列中最常见的值(通过value_counts().index[0]
获取)。 - 最后,
transform
方法返回经过填充处理后的数据框。
4. 定义 fit
方法
def fit(self, *_):
return self
fit
方法通常用于在转换器中进行一些拟合操作,比如计算平均值、标准差等,但在这个类中,fit
方法什么都不做,直接返回self
,表示这个转换器不需要进行拟合操作。- 在流水线中,
fit
方法必须存在,因此尽管它在这个例子中什么也不做,依然需要定义。
5. 使用自定义填充器
cci = CustomCategoryImputer(cols=['city', 'boolean'])
cci.fit_transform(X)
cci
是CustomCategoryImputer
类的一个实例,指定了要填充的列为['city', 'boolean']
。fit_transform(X)
方法首先调用fit
方法(在这个例子中并未做任何操作),然后调用transform
方法对X
数据框中的city
和boolean
列进行缺失值填充。
总结:
这段代码定义了一个简单的自定义填充器,用于填充数据框中指定列的缺失值,填充值为该列中出现频率最高的值。这种方法常用于分类特征的数据预处理,特别是在数据清理的阶段。
3.2 自定义定量填充器代码解析
这段代码扩展了之前的自定义填充器概念,创建了一个针对定量(数值)数据的自定义填充器,并将其与之前的分类填充器结合起来,通过 Pipeline
进行流水线处理。以下是详细解析:
1. 导入必要的库
from sklearn.impute import SimpleImputer
from sklearn.base import TransformerMixin
SimpleImputer
是 Scikit-learn 中的一个类,用于处理缺失值填充。它提供了多种策略(如均值、中位数、众数等)来填充缺失值。TransformerMixin
允许我们方便地创建自定义的转换器,继承这个类可以让自定义类与 Scikit-learn 的其他组件兼容。
2. 定义 CustomQuantitativeImputer
类
class CustomQuantitativeImputer(TransformerMixin):
def __init__(self, cols=None, strategy='mean'):
self.cols = cols
self.strategy = strategy
CustomQuantitativeImputer
是一个自定义的类,用于填充数值列的缺失值。cols
参数指定要填充的列,如果没有指定,默认值为None
。strategy
参数指定填充策略,默认是'mean'
,即用均值填充。这个参数可以设置为'mean'
、'median'
(中位数)、'most_frequent'
(众数)等。
3. 定义 transform
方法
def transform(self, df):
X = df.copy()
impute = SimpleImputer(strategy=self.strategy)
for col in self.cols:
X[col] = impute.fit_transform(X[[col]])
return X
transform
方法用于执行实际的填充操作。- 首先,创建数据框
df
的副本X
,以确保不修改原始数据。 - 然后,初始化
SimpleImputer
对象impute
,并使用指定的strategy
进行填充。 - 接下来,遍历指定的列(
self.cols
),并对每一列应用SimpleImputer
的fit_transform
方法进行缺失值填充。 - 最后,返回经过填充处理后的数据框。
4. 定义 fit
方法
def fit(self, *_):
return self
fit
方法通常用于在转换器中进行拟合操作,但在这个类中,fit
方法什么都不做,直接返回self
。这是为了与 Scikit-learn 的流水线兼容。
5. 使用自定义定量填充器
cqi = CustomQuantitativeImputer(cols=['quantitative_column'], strategy='mean')
cqi.fit_transform(X)
cqi
是CustomQuantitativeImputer
类的一个实例,指定要填充的列为['quantitative_column']
,填充策略为'mean'
(均值)。fit_transform(X)
方法首先调用fit
方法(未做任何操作),然后调用transform
方法对X
数据框中的quantitative_column
列进行缺失值填充。
6. 创建和使用 Pipeline
from sklearn.pipeline import Pipeline
imputer = Pipeline([('quant', cqi), ('category', cci)])
imputer.fit_transform(X)
Pipeline
是 Scikit-learn 提供的一种工具,用于将多个数据预处理步骤按顺序组合在一起。Pipeline
的参数是一个包含步骤的列表,每个步骤都是一个二元组(名称, 转换器)。在这个例子中,Pipeline
包含两个步骤:第一个是cqi
(自定义定量填充器),第二个是cci
(自定义分类填充器)。fit_transform(X)
方法会按顺序调用Pipeline
中每个步骤的fit_transform
方法。首先,使用cqi
对数值列进行填充,然后使用cci
对分类列进行填充。
总结
这段代码通过定义自定义的定量填充器,并将其与之前定义的分类填充器结合,创建了一个完整的预处理流水线。这个流水线可以处理数据框中的数值列和分类列,填充它们的缺失值,使得数据可以进一步用于建模或分析。使用 Pipeline
使得预处理步骤的组织更加清晰,并且能够很方便地扩展和调整。
3.3 自定义虚拟变量编码器
这段代码实现了一个自定义的虚拟变量编码器,用于将指定的分类变量转换为虚拟变量(即one-hot编码)。下面是详细解析:
1. 使用 pd.get_dummies
进行虚拟变量编码
pd.get_dummies(X,
columns=['city', 'boolean'], # 要虚拟化的列
prefix_sep='__') # 前缀(列名)和单元格值之间的分隔符
pd.get_dummies
是 Pandas 中用于进行虚拟变量编码的函数。它将指定的列(在columns
参数中指定)转换为多个二进制列。columns=['city', 'boolean']
指定了要进行编码的列,这两列中的每个独立值都会变成一个新的二进制列。prefix_sep='__'
设置了新列名的前缀和实际值之间的分隔符。例如,如果city
列中有一个值NewYork
,则对应的虚拟变量列名可能为city__NewYork
。
2. 定义 CustomDummifier
类
class CustomDummifier(TransformerMixin):
def __init__(self, cols=None):
self.cols = cols
CustomDummifier
是一个自定义类,用于实现虚拟变量编码。cols
参数用于指定要进行虚拟变量编码的列,如果不指定,默认值为None
。
3. 定义 transform
方法
def transform(self, X):
return pd.get_dummies(X, columns=self.cols)
transform
方法用于执行虚拟变量编码操作。- 直接调用
pd.get_dummies
函数,将指定的列(self.cols
)进行虚拟变量编码,并返回转换后的数据框。
4. 定义 fit
方法
def fit(self, *_):
return self
fit
方法在这个类中没有实际操作,仅返回self
。这是为了使这个类与 Scikit-learn 的流水线兼容。
5. 使用自定义虚拟变量编码器
cd = CustomDummifier(cols=['boolean', 'city'])
cd.fit_transform(X)
cd
是CustomDummifier
类的一个实例,指定要进行编码的列为['boolean', 'city']
。- 调用
fit_transform(X)
方法会对X
数据框中的boolean
和city
列进行虚拟变量编码,返回转换后的数据框。
总结
- 这个自定义虚拟变量编码器 (
CustomDummifier
) 使用了 Pandas 的pd.get_dummies
函数来实现虚拟变量编码。它通过 Scikit-learn 的TransformerMixin
使得该类能够与 Scikit-learn 的流水线兼容。这样可以将数据预处理的各个步骤(如缺失值填充、虚拟变量编码等)组合在一起,形成一个完整的流水线,以便于数据的预处理和建模。
3.4 自定义定序等级编码器
这段代码实现了一个自定义的定序等级编码器,用于将顺序变量(ordinal variable)按照指定的顺序映射为整数值。以下是详细解析:
1. 创建顺序映射
ordering = ['dislike', 'somewhat like', 'like'] # 0是dislike,1是somewhat like,2是like
- 这里定义了一个顺序列表
ordering
,表示某个顺序变量(如用户喜好)的等级顺序。 - 在这个列表中,
'dislike'
被映射为0
,'somewhat like'
被映射为1
,'like'
被映射为2
。这个列表的顺序决定了最终映射的编码。
2. 将顺序映射应用到数据
print(X['ordinal_column'].map(lambda x: ordering.index(x)))
- 这行代码使用
map
函数将ordering
列表中的顺序映射到X
数据框的ordinal_column
列。 lambda x: ordering.index(x)
表示对每个元素x
,找到它在ordering
列表中的索引,从而实现顺序编码。- 这个过程将原始的顺序变量转换为对应的整数值,并打印结果。
3. 定义 CustomEncoder
类
class CustomEncoder(TransformerMixin):
def __init__(self, col, ordering=None):
self.ordering = ordering
self.col = col
CustomEncoder
是一个自定义类,用于将顺序变量按照指定的顺序进行编码。col
参数指定要进行编码的列。ordering
参数用于指定顺序映射列表。如果没有指定,默认值为None
。
4. 定义 transform
方法
def transform(self, df):
X = df.copy()
X[self.col] = X[self.col].map(lambda x: self.ordering.index(x))
return X
transform
方法用于执行顺序编码操作。- 首先,创建数据框
df
的副本X
,以确保不修改原始数据。 - 然后,使用
map
函数和lambda
函数将self.col
列中的值映射为对应的整数值。 - 最后,返回经过编码处理后的数据框。
5. 定义 fit
方法
def fit(self, *_):
return self
fit
方法在这个类中没有实际操作,仅返回self
。这是为了使这个类与 Scikit-learn 的流水线兼容。
3.5 使用自定义定序等级编码器
ce = CustomEncoder(col='ordinal_column', ordering=['dislike', 'somewhat like', 'like'])
ce.fit_transform(X)
ce
是CustomEncoder
类的一个实例,指定要进行编码的列为'ordinal_column'
,并且使用ordering=['dislike', 'somewhat like', 'like']
作为顺序映射。- 调用
fit_transform(X)
方法会对X
数据框中的ordinal_column
列进行顺序编码,返回转换后的数据框。
总结
- 这个自定义定序等级编码器 (
CustomEncoder
) 是一个简单而有效的方法,可以将顺序变量(ordinal variables)映射为整数值。通过定义顺序映射列表ordering
,你可以灵活地控制编码的顺序。 - 该编码器通过继承
TransformerMixin
,可以与 Scikit-learn 的流水线兼容,因此可以方便地与其他数据预处理步骤组合在一起使用。这种编码方法适用于顺序变量的处理,使得这些变量可以直接用于模型训练。
4、闯关题
Q1. (判断题)对分类数据进行编码,可以转化为数值数据?
Q2. (判断题)使用词袋法可以将文本特征转化为数值特征?
Q3. (判断题)处理数值数据时,使用多项式方法不能创造新的特征?
#填入你的答案并运行,注意大小写
a1 = 'T' # 如 a1= 'T/F'
a2 = 'T' # 如 a2= 'T/F'
a3 = 'F' # 如 a3= 'T/F'