环境:Pycharm、python3.7
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import mean_absolute_error
# 导入训练集
file = open("used_car_train.csv", encoding = 'UTF-8')
x_data = pd.read_csv(file, sep = ' ')
X = x_data
# 'notRepairedDamage' 是 object 类型,在实际运算中需要强制转换为 float
# 'notRepairedDamage' 其中有 '-' 符号,这里将其替换为数值 0.5
X['notRepairedDamage'] = X['notRepairedDamage'].replace('-', '0.5')
X['notRepairedDamage'] = X['notRepairedDamage'].astype('float')
# 'bodyType' 有缺失值,根据特征值特点,用数值 8 代替其他未知
# 'bodyType' 由于数值不代表大小或先后顺序,因此进行独热编码增加维度
X['bodyType'] = X['bodyType'].fillna(value = 8)
X['bodyType'] = X['bodyType'].astype('int')
OneHotEncoder(sparse = False).fit_transform(X[['bodyType']])
# 'fuelType' 有缺失值,根据特征值特点,用数值 7 代替其他未知
# 'fuelType' 由于数值不代表大小或先后顺序,因此进行独热编码增加维度
X['fuelType'] = X['fuelType'].fillna(value = 7)
X['fuelType'] = X['fuelType'].astype('int')
OneHotEncoder(sparse = False).fit_transform(X[['fuelType']])
# 'gearbox' 有缺失值,根据特征值特点,用数值 2 代替其他未知
# 'gearbox' 由于数值不代表大小或先后顺序,因此进行独热编码增加维度
X['gearbox'] = X['gearbox'].fillna(value = 2)
X['gearbox'] = X['gearbox'].astype('int')
OneHotEncoder(sparse = False).fit_transform(X[['gearbox']])
# 'seller' 由于数值不代表大小或先后顺序,因此进行独热编码增加维度
OneHotEncoder(sparse = False).fit_transform(X[['seller']])
# 'offerType' 由于数值不代表大小或先后顺序,因此进行独热编码增加维度
OneHotEncoder(sparse = False).fit_transform(X[['offerType']])
# 'model' 在训练集中有一个未知值,在测试集中没有未知值
# 因此可以将训练集中的未知值删去,不影响整体数据
X = X.dropna(how = "any", subset = ['model'])
# 'power' 取值范围为 0 ~ 600,数据分析中可以发现有越界值
# 因此对 'power' 范围进行约束
X['power'] = X['power'].map(lambda x: 600 if x > 600 else x)
# 使用时间:data['creatDate'] - data['regDate'],反应汽车使用时间,一般来说价格与使用时间成反比
# 不过要注意,数据里有时间出错的格式,所以我们需要 errors = 'coerce'
X['used_time'] = (pd.to_datetime(X['creatDate'], format = '%Y%m%d', errors = 'coerce') - pd.to_datetime(X['regDate'], format = '%Y%m%d', errors = 'coerce')).dt.days
# 从邮编中提取城市信息,相当于加入了先验知识
X['city'] = X['regionCode'].apply(lambda x : str(x)[:-3])
# 数据分桶,以 'power' 为例
# 为什么要做数据分桶呢,原因有很多
# 1. 离散后稀疏向量内积乘法运算速度更快,计算结果也方便存储,容易扩展;
# 2. 离散后的特征对异常值更具鲁棒性,如 age > 30 为 1 否则为 0,对于年龄为 200 的也不会对模型造成很大的干扰;
# 3. LR 属于广义线性模型,表达能力有限,经过离散化后,每个变量有单独的权重,这相当于引入了非线性,能够提升模型的表达能力,加大拟合;
# 4. 离散后特征可以进行特征交叉,提升表达能力,由 M + N 个变量编程 M * N 个变量,进一步引入非线形,提升了表达能力;
# 5. 特征离散后模型更稳定,如用户年龄区间,不会因为用户年龄长了一岁就变化
# 当然还有很多原因,LightGBM 在改进 XGboost 时就增加了数据分桶,增强了模型的泛化性
bin = [i * 10 for i in range(31)]
X['power_bin'] = pd.cut(X['power'], bin, labels = False)
X[['power_bin', 'power']].head()
# 删除不需要的数据
X = X.drop(['creatDate', 'regDate', 'regionCode'], axis = 1)
# 对 'kilometer' 直接做归一化
X['kilometer'] = ((X['kilometer'] - np.min(X['kilometer'])) /
(np.max(X['kilometer']) - np.min(X['kilometer'])))
# 根据数据分析的特征相关性分析,删除以下特征相关性较高的特征
X = X.drop(['v_6', 'v_4', 'v_13', 'v_2'], axis = 1)
# 将 y 划分为 label 标签值
y = X.price
# 将 X 划分为所有的特征值
X = X.drop(['price'], axis = 1)
X = X.drop_duplicates()
X = X.iloc[ : , 1 : ]
# 划分用于训练的数据和用于检测的数据
# 按 90% 训练样本、 10% 测验样本
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.1, random_state = 4)
"""
导入训练模型
………………
………………(此处省略)
………………
训练模型完成
"""
# 导入预测集
file = open("used_car_testA.csv", encoding = 'UTF-8')
y_data = pd.read_csv(file, sep = ' ')
y_test = data.iloc[ : , 1 : ]
"""
预测集的处理方法同训练集的处理方法(此处省略)
"""
数据的特征工程就到这里,接下来将进行建模与调参等一系列模型构造处理,使用机器学习算法核心。
如有错误,请多指正。