衍生变量
有时候原生变量不能完全满足预测的要求,那么可以根据一个或者多个原生变量衍生出其它可用或不可用的变量,这一过程称为“变量衍生”。变量衍生是特征工程一个比较重要的组成部分,它需要花费比较多的精力和时间尽量去认识原生变量和具体的预测命题,然后根据原生变量的特点构造出一些衍生变量。比如拿到一个“手机号码”,根据这个变量我们可以衍生出“运营商”,“归属地省份”,“归属地城市”,根据“归属地城市”又可以衍生出“是否一级城市”,“是否偏远城市”等等,总之,如何从原生数据中挖出有预测能力的变量是一项需要脑洞和脑力的工程。
对原始数据加工、生成有商业意义的变量
如通过每次登录的相关信息登陆表衍生出”最近30天的登录次数“这个变量。
商业加工,直接使用特征生成有价值的数据
数据加工后,得到对结果有帮助的变量如FM,FFM。
下面,我们以kaggle泰坦尼克数据为例,来提供几种变量衍生的思路,发现有效的隐藏变量:
1)Name:
乘客name的数量可能与其社会地位有关从而影响登船率:
#name的数量
df['Names'] = df['Name'].map(lambda x: len(re.split(' ', x)))
名字称呼的不同(与性别,年龄,地位有关)可能也是不错的点:
# 取每个人的Title
df['Title'] = df['Name'].map(lambda x: re.compile(", (.*?).").findall(x)[0])
# 将一些较特殊或可以合并的Title进行处理
df['Title'][df.Title == 'Jonkheer'] = 'Master'
df['Title'][df.Title.isin(['Ms','Mlle'])] = 'Miss'
df['Title'][df.Title == 'Mme'] = 'Mrs'
df['Title'][df.Title.isin(['Capt', 'Don', 'Major', 'Col', 'Sir'])] = 'Sir'
df['Title'][df.Title.isin(['Dona', 'Lady', 'the Countess'])] = 'Lady'
# Dummy化
df = pd.concat([df, pd.get_dummies(df['Title']).rename(columns=lambda x: 'Title_' + str(x))], axis=1)
同时,对于name,也可以对乘客的姓氏进行变量衍生。 2)Cabin Cabin变量有两重内容。前面的字母代表甲板号,后面的数字代表房间号。所以二者既能在一定程度上代表乘客所在的位置,也能反映社会地位。
# 缺失值填充
df['Cabin'][df.Cabin.isnull()] = 'U0'
# 取甲板号
df['Deck'] = df['Cabin'].map( lambda x : re.compile("([a-zA-Z]+)").search(x).group())
df['Deck'] = pd.factorize(df['Deck'])[0]
# Dummy化
decks = pd.get_dummies(df['Deck']).rename(columns=lambda x: 'Deck_' + str(x))
df = pd.concat([df, decks], axis=1)
# 取房间号
df['Room'] = df['Cabin'].map( lambda x : re.compile("([0-9]+)").search(x).group()).astype(int) + 1
3)Ticket 这个变量缺失包含了部分信息,但又不是很明显,我们需要对此作出一些大胆的猜测,发现的几条线索如下: 1、大约四分之一的数据包含字母前缀,剩下的则全部只含数字;
2、只看前缀,有45类,但将“.”“/”字符去掉后有29类;
3、数字部分有一定的规律,“1”,“2”,“3”开头的数字较多,“4-9”开头的则很少,可以考虑将其归为一类;
4、数字的位数也是分几种,分别是4、5、6,这方面也是一个不错点;
5、有部分样本有共用一张Ticket单号的情况,这意味共用单号的人应该互相之间有一定的关系,和familyID有点相似;
具体代码如下:
def processTicket():
global df
# 将“Ticket”的前缀抽出并处理
df['TicketPrefix'] = df['Ticket'].map( lambda x : getTicketPrefix(x.upper()))
df['TicketPrefix'] = df['TicketPrefix'].map( lambda x: re.sub('[.?/?]', '', x) )
df['TicketPrefix'] = df['TicketPrefix'].map( lambda x: re.sub('STON', 'SOTON', x) )
# Dummy化
prefixes = pd.get_dummies(df['TicketPrefix']).rename(columns=lambda x: 'TicketPrefix_' + str(x))
df = pd.concat([df, prefixes], axis=1)
# factorizing
df['TicketPrefixId'] = pd.factorize(df['TicketPrefix'])[0]
# 将数字抽出
df['TicketNumber'] = df['Ticket'].map( lambda x: getTicketNumber(x) )
# 衍生一个“数字位数 ”变量
df['TicketNumberDigits'] = df['TicketNumber'].map( lambda x: len(x) ).astype(np.int)
# 取开头的数字衍生一个变量
df['TicketNumberStart'] = df['TicketNumber'].map( lambda x: x[0:1] ).astype(np.int)
# 去掉抽出的前缀列
df.drop(['TicketPrefix', 'TicketNumber'], axis=1, inplace=True)
def getTicketPrefix(ticket):
match = re.compile("([a-zA-Z./]+)").search(ticket)
if match:
return match.group()
else:
return 'U'
def getTicketNumber(ticket):
match = re.compile("([d]+$)").search(ticket)
if match:
return match.group()
else:
return '0'
特征构造
- 组合几个原特征来构建适合分析的新特征
我们可以根据原始变量进行变量衍生,也可以根据变量之间的关系进行变量衍生,比如两个变量:距离和时间,我们可以通过二者相除得到一个速度变量。也可以通过一些数学运算,如四则运算,ln,sqrt等,快速衍生出新的变量。
numerics = df.loc[:, ['Age_scaled', 'Fare_scaled', 'Pclass_scaled', 'Parch_scaled', 'SibSp_scaled',
'Names_scaled', 'CabinNumber_scaled', 'Age_bin_id_scaled', 'Fare_bin_id_scaled']]
# 基本的四则运算示例
for i in range(0, numerics.columns.size-1):
for j in range(0, numerics.columns.size-1):
col1 = str(numerics.columns.values[i])
col2 = str(numerics.columns.values[j])
# 乘
if i <= j:
name = col1 + "*" + col2
df = pd.concat([df, pd.Series(numerics.iloc[:,i] * numerics.iloc[:,j], name=name)], axis=1)
# 加
if i < j:
name = col1 + "+" + col2
df = pd.concat([df, pd.Series(numerics.iloc[:,i] + numerics.iloc[:,j], name=name)], axis=1)
# 除
if not i == j:
name = col1 + "/" + col2
df = pd.concat([df, pd.Series(numerics.iloc[:,i] / numerics.iloc[:,j], name=name)], axis=1)
name = col1 + "-" + col2
df = pd.concat([df, pd.Series(numerics.iloc[:,i] - numerics.iloc[:,j], name=name)], axis=1)
通过这种方式,我们可以迅速衍生出大量的特征,如上面的代码为例,仅9个变量就衍生出176个新的特征,而如果有上千个原始变量,那衍生的特征数量也会变得相当庞大,这种衍生方式极有可能衍生出相关性很强的变量集合,那么对线性模型来讲,极有可能产生多重共线性的问题,对于非线性模型,共线性对其不会造成太大影响,不过去掉相关性很强的变量总是百利而无害的,这里使用斯皮尔曼相关系数作为指标去剔除强相关变量,当然你们也可以用其他相关性指标。
# 计算斯皮尔曼相关系数(矩阵)
df_corr = df.drop(['Survived', 'PassengerId'],axis=1).corr(method='spearman')
# 将对角线变为0
mask = np.ones(df_corr.columns.size) - np.eye(df_corr.columns.size)
df_corr = mask * df_corr
drops = []
# 循环
for col in df_corr.columns.values:
# if we've already determined to drop the current variable, continue
if np.in1d([col],drops):
continue
# 找出高相关的变量
corr = df_corr[abs(df_corr[col]) > 0.98].index
drops = np.union1d(drops, corr)
print "nDropping", drops.shape[0], "highly correlated features...n", drops
df.drop(drops, axis=1, inplace=True)
参考资料
8.小波变换教程
9.离散小波变换
10.维基百科-近似熵
11.维基百科-样本熵
12.时间序列的复杂度和熵