赞
踩
本文数据集来自阿里天池:https://tianchi.aliyun.com/competition/entrance/231784/information
主要参考了Datawhale的整个操作流程:https://tianchi.aliyun.com/notebook/95501
小编也是第一次接触数据挖掘,所以先跟着Datawhale写的教程操作了一遍,不懂的地方加了一点点自己的理解,感谢Datawhale!
# 导入数据
train_data=pd.read_csv(train_path,sep=' ')
test_data=pd.read_csv(test_path,sep=' ')
# 查看数据
train_data.head()
# 查看数据形状
train_data.shape
# 查看数据的列
train_data.columns
# 这里包装了一个异常值处理的代码,可以随便调用。 def outliers_proc(data, col_name, scale=3): """ 用于清洗异常值,默认用 box_plot(scale=3)进行清洗 :param data: 接收 pandas 数据格式 :param col_name: pandas 列名 :param scale: 尺度 :return: """ def box_plot_outliers(data_ser, box_scale): """ 利用箱线图去除异常值 :param data_ser: 接收 pandas.Series 数据格式 :param box_scale: 箱线图尺度, :return: """ # 计算四分位距,并通过box_scale进行缩放 iqr = box_scale * (data_ser.quantile(0.75) - data_ser.quantile(0.25)) # 计算下边缘值,即下四分位数减去四分位距 val_low = data_ser.quantile(0.25) - iqr # 计算上边缘值,即上四分位数加上四分位距 val_up = data_ser.quantile(0.75) + iqr # 创建一个布尔序列,指示哪些数据点低于下边缘值 rule_low = (data_ser < val_low) # 创建一个布尔序列,指示哪些数据点高于上边缘值 rule_up = (data_ser > val_up) return (rule_low, rule_up), (val_low, val_up) data_n = data.copy() data_series = data_n[col_name] rule, value = box_plot_outliers(data_series, box_scale=scale) # 使用布尔数组来选择索引,返回满足条件的索引值数组,即找到异常值所在对的索引位置 index = np.arange(data_series.shape[0])[rule[0] | rule[1]] print("Delete number is: {}".format(len(index))) data_n = data_n.drop(index) # reset_index重新设置 DataFrame 的索引 # 参数 drop=True 表示重置索引后丢弃原来的索引列 # inplace 参数用于指定是否在原始 DataFrame 上进行修改 data_n.reset_index(drop=True, inplace=True) print("Now column number is: {}".format(data_n.shape[0])) # 查看低于下界的数据 index_low = np.arange(data_series.shape[0])[rule[0]] outliers = data_series.iloc[index_low] print("Description of data less than the lower bound is:") print(pd.Series(outliers).describe()) # 查看高于上界的数据 index_up = np.arange(data_series.shape[0])[rule[1]] outliers = data_series.iloc[index_up] print("Description of data larger than the upper bound is:") print(pd.Series(outliers).describe()) # 创建一个包含两个子图的画布。参数 1, 2 表示创建一个1行2列的子图网格,figsize=(10, 7) 表示画布的大小为宽10英寸,高7英寸 # fig 是整个画布对象,ax 是一个包含两个子图对象的数组 fig, ax = plt.subplots(1, 2, figsize=(10, 7)) # 画出去除异常值前后的子图 sns.boxplot(y=data[col_name], data=data, palette="Set1", ax=ax[0]) sns.boxplot(y=data_n[col_name], data=data_n, palette="Set1", ax=ax[1]) return data_n
结果展示:
train_data['train']=1
test_data['train']=0
data = pd.concat([train_data, test_data], ignore_index=True)
# 注意,数据里有时间出错的格式,所以我们需要 errors='coerce',表示如果出现无法转换的情况,将会将其转换为 NaT(Not a Time)值,而不是抛出错误
data['used_time'] = (pd.to_datetime(data['creatDate'], format='%Y%m%d', errors='coerce') - pd.to_datetime(data['regDate'], format='%Y%m%d', errors='coerce')).dt.days
# 看一下空数据,发现有 15k 个样本的时间是有问题的,可以选择删除,也可以选择放着。
# 但是这里不建议删除,因为删除缺失数据占总样本量过大,7.5%
# 如果使用 XGBoost 之类的决策树,其本身就能处理缺失值,所以可以不用管;
data['used_time'].isnull().sum()
data['city'] = data['regionCode'].apply(lambda x : str(x)[:-3])
data = data
# 对数据按brand分组,结果是每个分组结果组成的结果 train_gb = train_data.groupby("brand") all_info = {} # 对分组进行循环 for kind, kind_data in train_gb: info = {} kind_data = kind_data[kind_data['price'] > 0] # 计算每个品牌汽车的数量、最高价、中位数、最低价、价格总和、价格方差、价格平均值 info['brand_amount'] = len(kind_data) info['brand_price_max'] = kind_data.price.max() info['brand_price_median'] = kind_data.price.median() info['brand_price_min'] = kind_data.price.min() info['brand_price_sum'] = kind_data.price.sum() info['brand_price_std'] = kind_data.price.std() info['brand_price_average'] = round(kind_data.price.sum() / (len(kind_data) + 1), 2) all_info[kind] = info # 因为字典的键对应的是dataframe的列,所以我们要进行转置 brand_fe = pd.DataFrame(all_info).T.reset_index().rename(columns={"index": "brand"}) # 将结果合并到我们的原数据中,这里采用的是左连接,连接条件是brand # 左连接就是说,data表显示所有,brand_fe表只显示brand值相匹配的行 data = data.merge(brand_fe, how='left', on='brand')
同理,我们还可以加入不同车型、不同燃油类型、不同变速箱、不同地区的销售统计量,这里不再一一列举
为什么要做数据分桶呢?
- 离散后稀疏向量内积乘法运算速度更快,计算结果也方便存储,容易扩展;
- 离散后的特征对异常值更具鲁棒性,如 age>30 为 1 否则为 0,对于年龄为 200 的也不会对模型造成很大的干扰;
- LR 属于广义线性模型,表达能力有限,经过离散化后,每个变量有单独的权重,这相当于引入了非线性,能够提升模型的表达能力,加大拟合;
- 离散后特征可以进行特征交叉,提升表达能力,由 M+N 个变量编程 M*N 个变量,进一步引入非线形,提升了表达能力;
- 特征离散后模型更稳定,如用户年龄区间,不会因为用户年龄长了一岁就变化
- 当然还有很多原因,LightGBM 在改进 XGBoost 时就增加了数据分桶,增强了模型的泛化性
# 创建了一个包含31个元素的列表,每个元素是按照10的倍数递增的数字。这个列表将用作分桶的边界
bin = [i*10 for i in range(31)]
# 第一个参数是要分箱的数据,
# 第二个参数是分箱的边界,
# 第三个参数labels=False表示我们希望返回的结果是分箱后每个样本所在的箱的索引而不是标签。
# 这一行代码的结果将是一个新的Series,其中每个元素表示对应样本所在的箱的索引。
data['power_bin'] = pd.cut(data['power'], bin, labels=False)
# 删除不需要的数据
data = data.drop(['creatDate', 'regDate', 'regionCode'], axis=1)
# 目前的数据已经可以给树模型使用了,所以先导出一下
data.to_csv('data_for_tree.csv', index=0)
注:不同模型对数据集的要求不同所以分开构造
data['power'].plot.hist()
可以发现,直方图只有0-2500这一列,且该列的值很大,猜测数据可能分布不均匀,这里我们可以先log(数据平滑处理)一下再归一化:
data['power'] = np.log(data['power'] + 1)
data['power'] = ((data['power'] - np.min(data['power'])) / (np.max(data['power']) - np.min(data['power'])))
data['power'].plot.hist()
其他列这里不再一一列举
这里可以定义一个归一化函数,将其他列传入即可
def max_min(x):
return (x - np.min(x)) / (np.max(x) - np.min(x))
# get_dummies是pandas库中用于进行独热编码(One-Hot Encoding)的函数
data = pd.get_dummies(data, columns=['model', 'brand', 'bodyType', 'fuelType',
'gearbox', 'notRepairedDamage', 'power_bin'])
# 参数
# data: 这是一个DataFrame或者类似于DataFrame的数据结构,包含需要进行独热编码的数据。
# columns: 这是一个列表,包含需要进行独热编码的列名。函数将对这些列进行独热编码处理
查看一下形状,发现多了很多列,这里需要理解一下get_dummies是怎么进行one-hot编码的,比如brand有A,B两个类型,那么data里面就会把brand转化为A,B两个列,该行数据属于A类型,那么A类型这一列就为1否则为0
print(data.shape)
print(data.columns)
data.to_csv('data_for_lr.csv', index=0)
# index=0表示不在文件中写入行索引
# 相关性分析
print(data['power'].corr(data['price'], method='spearman'))
print(data['kilometer'].corr(data['price'], method='spearman'))
print(data['brand_amount'].corr(data['price'], method='spearman'))
print(data['brand_price_average'].corr(data['price'], method='spearman'))
print(data['brand_price_max'].corr(data['price'], method='spearman'))
print(data['brand_price_median'].corr(data['price'], method='spearman'))
data_numeric = data[['power', 'kilometer', 'brand_amount', 'brand_price_average',
'brand_price_max', 'brand_price_median']]
# corr() 方法会计算特征之间的皮尔逊相关系数,生成一个相关性矩阵
correlation = data_numeric.corr()
f , ax = plt.subplots(figsize = (7, 7))
# 这行代码给图形添加了标题,标题内容为 'Correlation of Numeric Features with Price',
# y=1 参数将标题的位# 置设置在图形顶部,size=16 参数指定了标题的字体大小为16号
plt.title('Correlation of Numeric Features with Price',y=1,size=16)
#使用Seaborn库中的 heatmap 函数绘制了热力图。热力图展示了相关性矩阵 correlation 中各特征之间的相关性。
# square=True 参数确保热力图是正方形的,vmax=0.8 参数设置了颜色映射的最大值,这有助于突出显示高相关性
sns.heatmap(correlation,square = True, vmax=0.8)
可以看出上述对角线颜色是最浅的,因为对角线是特征自己跟自己的相关性,power跟brand_price_average, brand_price_max, brand_price_median之间的相关性比跟kilometer, brand_amount之间的相关性更强
小编这里这一部分没有跑通,暂时没有排查出是哪里出问题了???
from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from sklearn.linear_model import LinearRegression
sfs = SFS(LinearRegression(),
k_features=10,
forward=True,
floating=False,
scoring = 'r2',
cv = 0)
x = data.drop(['price'], axis=1)
x = x.fillna(0)
y = data['price']
sfs.fit(x, y)
sfs.k_feature_names_
# 画出来,可以看到边际效益
from mlxtend.plotting import plot_sequential_feature_selection as plot_sfs
import matplotlib.pyplot as plt
fig1 = plot_sfs(sfs.get_metric_dict(), kind='std_dev')
plt.grid()
plt.show()
待补充——
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。