Python二手车价格预测(一)—— 数据处理

2022-07-13 16:42:37 浏览数 (1)

01

数据获取


我们的数据来源是“人人车”二手车网站,通过Python爬虫获取291个城市所有在售二手车详细数据。

数据爬取的全过程已经发表在CSDN博客上,欢迎大家点赞收藏,扫描下方二维码即可查看博文。

想了解爬取代码的同学可以clone我的git仓库https://gitee.com/hanxianzhe/spider/tree/master/spider_renren

02

数据处理


原始数据维度为:93738*212

获取时间为:2020年7月25日

【写在前面】

数据处理十分重要,一个机器学习模型预测结果的好坏与数据处理有直接关联。每个人处理数据的思维和方式都不一样,因此本文只是依据我的一些学习经验进行数据处理,给大家当个baseline~

【Step 1:导包】

代码语言:javascript复制
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import sys
import seaborn as sns
import warnings
import datetime
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
# 设置可显示的最大行列数量
pd.set_option('display.max_rows', 100,'display.max_columns', 100,"display.max_colwidth",1000,'display.width',1000)

【Step 2:删除无效列与填充空值】

数据的许多列包含大量的空值和单一数据,当这些数据超过一定量时,对模型来说是无效的,因此先将这一部分数据进行删除。

代码语言:javascript复制
# 读取数据
data = pd.read_excel("cars_info.xlsx", na_values=np.nan)

# 每列数据为空的列,数量大于80000,删除该列(无参考价值)
for c in data.columns:
    if data[c].isna().sum() > 80000:
        data.drop([c], axis=1, inplace=True)
        
# 每列数据为“无”的列,数量大于60000,删除该列(无参考价值)
for c in data.columns:
    if data[c].isin(["无"]).sum() > 60000:
        data.drop([c], axis=1, inplace=True)

# 因为数据本身含有长宽高的单独列,因此“长*宽*高(mm)”列删除
data.drop(['长*宽*高(mm)'], axis=1, inplace=True)

# 数据中许多列都包含“标配”,数量大于60000时无参考价值
for c in data.columns:
    if data[c].isin(["标配"]).sum() > 60000:
        print(c, data[c].isin(["标配"]).sum())
        data.drop([c], axis=1, inplace=True)

# 删除 “售价” 和 “排量” 为空的行
data.dropna(axis=0,subset = ["售价", "排量(L)"], inplace=True)

# 该列含有大量范围值,且已有新车售价,删除处理
data.drop(['厂商新车指导价'], axis=1, inplace=True)

# “过户记录”许多为空,我们认为可能无过户记录,因此填充0;“载客/人”按照该列平均值进行填充
data['过户记录'].fillna(0, inplace=True)
data['载客/人'].fillna(int(data['载客/人'].mean()), inplace=True)

【Step 3:数值型数据处理】

通过Excel表我们可以发现,有些列可以完全处理成数值型数据。剔除这些列中的异常数据,并且为空值进行填充,可以使用平均值或众数进行填充。

代码语言:javascript复制
# 筛选出可以转化为数值型数据的列
numerical_col = ['售价', '新车售价', '行驶里程', '过户记录', 
                 '载客/人', '排量(L)', '最高车速(km/h)', '官方0-100km/h加速(s)',
                 '工信部综合油耗(L/100km)', '长度(mm)', '宽度(mm)', '高度(mm)', 
                 '轴距(mm)', '前轮距(mm)', '后轮距(mm)', '车门数', '油箱容积(L)',
                 '整备质量(kg)', '最小离地间隙(mm)', '排量(mL)', '气缸数(个)', 
                 '每缸气门数(个)', '压缩比', '最大马力(Ps)', '最大功率(kW)',
                 '最大扭矩(N·m)'
                ]
numerical_df = data[numerical_col]

# 将非数值型数据替换为np.nan
for c in numerical_col[5:]:
    numerical_df[c] = numerical_df[c].replace("无", np.nan).replace("false", np.nan).replace("未知", np.nan)

# 空值填充
mean_fill_col = ['排量(L)', '最高车速(km/h)', '官方0-100km/h加速(s)',
                 '工信部综合油耗(L/100km)', '长度(mm)', '宽度(mm)', '高度(mm)', 
                 '轴距(mm)', '前轮距(mm)', '后轮距(mm)', '油箱容积(L)',
                 '整备质量(kg)', '最小离地间隙(mm)', '排量(mL)', '压缩比', 
                 '最大马力(Ps)', '最大功率(kW)', '最大扭矩(N·m)'
                ]
many_fill_col = ['车门数', '气缸数(个)', '每缸气门数(个)'] # 多数都为4

# 将dataframe转化成float类型
numerical_df = numerical_df.astype(float)

# 进行填充
for c in mean_fill_col:
    numerical_df[c].fillna(numerical_df[c].mean(), inplace=True)
    
for c in many_fill_col:
    numerical_df[c].fillna(4, inplace=True)

# 将处理完的数据更新至data中
data[ numerical_col ] = numerical_df

# 处理 ['座位数', '行李厢容积(L)', '最大功率转速(rpm)', '最大扭矩转速(rpm)'] 中的异常值
# 异常值处理函数
def pickNum(df, c):
    if '-' in df[c]:
        num_list = df[c].split('-')
        return num_list[0]
    elif '―' in df[c]:
        num_list = df[c].split('―')
        return num_list[0]
    elif '~' in df[c]:
        num_list = df[c].split('~')
        return num_list[0]
    elif '/' in df[c]:
        num_list = df[c].split('/')
        return num_list[0]
    else:
        return df[c]

pickNum_col = ['座位数', '行李厢容积(L)', '最大功率转速(rpm)', '最大扭矩转速(rpm)']

# 转化为str类型
data[pickNum_col] = data[pickNum_col].astype(str)

# 异常值处理
for c in pickNum_col:
    data[c] = data.apply(lambda x:pickNum(x, c), axis=1)

# 将“无”、“false”、“未知” 等数据替换为空
for c in pickNum_col:
    data[c] = data[c].replace("无", np.nan).replace("false", np.nan).replace("未知", np.nan)

data[pickNum_col] = data[pickNum_col].astype(float)

# 众数填充
data['座位数'].fillna(5, inplace=True)

# 均值填充
for c in pickNum_col[1:]:
    data[c].fillna(data[c].mean(), inplace=True)

【Step 4:日期型数据处理】

数据中包含许多日期数据,我将它们转换成天数差,即用数据获取的时间减去对应的时间。

代码语言:javascript复制
# 处理日期型数据
date_col = ['商业险过期日期','交强险过期日期', '注册日期', '出厂日期', '车船税过期日期']

data['数据获取日期'] = '2020-07-25'

date_col.append('数据获取日期')

# 处理日期型数据函数
def calDate(df, c):
    if pd.isnull(df['出厂日期']):
        return np.nan
    else:
        d1=datetime.datetime.strptime('2020-07-25',"%Y-%m-%d")
        d2=datetime.datetime.strptime(df[c],"%Y-%m-%d")
        diff_days=d1-d2
        # print(diff_days)
        return diff_days.days

# 处理数据中的异常值
for c in date_col[:-1]:
    data[c] = data[c].replace("--", np.nan)
    
# 生成时间差的列
for c in date_col[:-1]:
    data[c '差(天)'] = data.apply(lambda x:calDate(x, c), axis=1)

new_date_col = ['商业险过期日期差(天)','交强险过期日期差(天)', '注册日期差(天)', '出厂日期差(天)', '车船税过期日期差(天)']

# 均值填充
for c in new_date_col:
    data[c].fillna(data[c].mean(), inplace=True)

# 删除之前的日期列
data.drop(date_col, axis=1, inplace=True

【Step 5:二值型数据处理】

许多列的数据要么为"有",要么为"无"。类似这种的数据我们称为"二值型"数据,可以将其转化为0-1的形式。

代码语言:javascript复制
# 处理0-1型数据
zero_one_col_names = ['前排侧气囊', '无钥匙启动系统', 'TRC牵引力控制系统', '上坡辅助', '电动天窗', 
                      '真皮方向盘', '日间行车灯', '自动头灯', '后视镜加热', '后雨刷', '后座出风口', 
                      '4S店保养', '原始购车/过户发票', '车辆购置税完税证明']

# 异常值替换及空值填充
for c in zero_one_col_names:
    data[c] = data[c].replace("无", 0).replace("false", 0).replace("true", 1).replace("标配", 1).replace("false", 0).replace("否", 0).replace("是", 1).replace("有(已见发票)", 1).replace("有(未见发票)", 0).replace("已缴税(未见证明)", 0).replace("已缴税(已见证明)", 1).replace("K请问欺负我测了没人粉粉嫩嫩妇女。。佛方法v。。", 0)
    data[c].fillna(0, inplace=True)

【Step 6:One-Hot型数据处理】

当一列值可以被分成多个类别时,我们可以将数据处理成独热编码(One-Hot)的形式,建议类别的个数超过10的时候就不要使用独热编码了,因为会导致数据过于稀疏,它的详细作用就不介绍了,朋友们自行百度。

代码语言:javascript复制
one_hot_col_names = ['进气形式', '气缸排列形式', '配气机构', '燃油标号', '供油方式', '缸盖材料', 
                     '缸体材料', '燃油形式', '变速箱类型', '驱动方式', '助力类型', '车体结构', 
                     '前制动', '后制动', '驻车制动类型', '备胎规格', '定速巡航', '真皮座椅', 
                     '变速器类型', '燃料类型', '车身颜色', '挡位个数']

# 这些是在excel筛选时发现的一些异常值,进行替换
data['挡位个数'] = data['挡位个数'].replace("无", "无级变速")
data['车身颜色'] = data['车身颜色'].replace("--", np.nan)
data['真皮座椅'] = data['真皮座椅'].replace(",", np.nan)
data['定速巡航'] = data['定速巡航'].replace("/", np.nan).replace("田......看", np.nan)
data['助力类型'] = data['助力类型'].replace("无助力", "无")

for c in one_hot_col_names:
    data[c] = data[c].replace("false", "无")
    data[c].fillna("无", inplace=True)

# 使用pandas中的get_dummies方法,直接将想要转换成独热编码额数据进行转换
one_hot_data = pd.get_dummies(data[one_hot_col_names])

# 合并独热编码数据,并删除之前的列
data = pd.concat([data,one_hot_data],axis = 1)
data.drop(one_hot_col_names, axis=1, inplace=True)

# 获取当前数据类型为数值型的列
final_col = list(data.describe().columns)
final_data = data[final_col]

# 有22列数据形式较为复杂,在这里就不进行处理了
# 有兴趣的同学可以自己尝试进行处理

# 保存处理后的数据
final_data.to_excel("final_data.xlsx", index=False)

03

结语


经过以上步骤的数据处理,我们将原始数据转换成可以放入机器学习模型的数据了。这里的数据处理过程比较简单,其中的一些操作也可以简单的理解成特征工程的过程(毕竟只是baseline),有能力的同学也可以按照自己的想法进行数据处理和特征工程。

最后的数据维度为:93738*190

下一期将进行机器学习的模型训练,以及实验结果的分析。

最后,整理不易,朋友们请点个赞、转个发

· END ·

最近是懒吉吉

0 人点赞