二手车交易价格预测-Task2 数据分析
本阶段我们主要是通过EDA等方式来了解数据,探索数据的特征。关于资料有挺多疑问的地方,均在文中用删除线加以注释,并总结放在了最后
一、载入各种库和数据
使用库pandas
来便捷的载入数据,其中path
标识了我们训练集和测试集所在的路径,为了之后的读取更为便捷。
# 导入warnings包,利用过滤器来实现忽略警告语句。
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as msno
path = 'E:/学习资料/car/'
Train_data = pd.read_csv(path + 'used_car_train.csv', sep=' ') # 文件以空字符分隔数据,若以','分隔数据,则sep=','或省略(源文件都将数据存到了一列里面)
Test_data = pd.read_csv(path + 'used_car_testA.csv', sep=' ')
代码详解:我们通过观察源文件.csv
可以知,所有特征均存储在同一列中,且特征之间以空字符分隔,因此我们在使用pandas.read_csv
读入的时候需要使用关键字sep=' '
实现。sep
默认的是以逗号','
分隔,以后我们读取源文件时必须显示的使用sep=' '
。
下面我们来简略的观察一下源数据。
print(Train_data.head().append(Train_data.tail()))
代码详解:其中Train_data.head()
是获取训练集的前几行特征(默认前5行,如果想调用前10行可用Train_data.head(10)
来实现),Train_data.tail()
是或许训练集的最后几行特征,默认值同样也是后5行。因此我们可以使用上面代码观察原数据集前5行和后5行的数据。其结果如下:
SaleID name regDate model ... v_11 v_12 v_13 v_14
0 0 736 20040402 30.0 ... 2.804097 -2.420821 0.795292 0.914762
1 1 2262 20030301 40.0 ... 2.096338 -1.030483 -1.722674 0.245522
2 2 14874 20040403 115.0 ... 1.803559 1.565330 -0.832687 -0.229963
3 3 71865 19960908 109.0 ... 1.285940 -0.501868 -2.438353 -0.478699
4 4 111080 20120103 110.0 ... 0.910783 0.931110 2.834518 1.923482
149995 149995 163978 20000607 121.0 ... -2.983973 0.589167 -1.304370 -0.302592
149996 149996 184535 20091102 116.0 ... -2.774615 2.553994 0.924196 -0.272160
149997 149997 147587 20101003 60.0 ... -1.630677 2.290197 1.891922 0.414931
149998 149998 45907 20060312 34.0 ... -2.633719 1.414937 0.431981 -1.659014
149999 149999 177672 19990204 19.0 ... -3.179913 0.031724 -1.483350 -0.342674
[10 rows x 31 columns]
我们在用shape
函数观察一下整个训练集数据的“形状”:
print(Train_data.shape)
结果如下所示:
(150000, 31)
综上我们可知,训练集*有150,000
条数据,每条数据31
个特征值。
我们用同样的方法看一下测试集数据的“形状”,结果如下:
(50000, 30)
因此测试集共有50,000
条数据,每条数据30
个特征值。细心的同学可能发现了测试集的特征数比训练集少了一个,原因是训练集中多的那个特征就是该数据的标签price
。
二、总览数据概况
1. 熟悉数据的相关统计量
通过describe()
来熟悉数据的相关统计量,看max是否有异常值,如99,999
等。
print(Train_data.describe())
结果如下所示:
SaleID name ... v_13 v_14
count 150000.000000 150000.000000 ... 150000.000000 150000.000000
mean 74999.500000 68349.172873 ... 0.000313 -0.000688
std 43301.414527 61103.875095 ... 1.288988 1.038685
min 0.000000 0.000000 ... -4.153899 -6.546556
25% 37499.750000 11156.000000 ... -1.057789 -0.437034
50% 74999.500000 51638.000000 ... -0.036245 0.141246
75% 112499.250000 118841.250000 ... 0.942813 0.680378
max 149999.000000 196812.000000 ... 11.147669 8.658418
[8 rows x 30 columns]
很显然,这不是我们想要的结果(我用的是pycharm
),好多特征都被折叠了,于是我们需要想办法展开被折叠的内容,我们只需要在之前加入以下设置即可:
pd.set_option('display.max_columns',1000)
pd.set_option('display.width', 1000)
print(Train_data.describe())
于是我们得到了全部特征的相关统计量。
SaleID name regDate model brand bodyType fuelType gearbox power kilometer regionCode seller offerType creatDate price v_0 v_1 v_2 v_3 v_4 v_5 v_6 v_7 v_8 v_9 v_10 v_11 v_12 v_13 v_14
count 150000.000000 150000.000000 1.500000e+05 149999.000000 150000.000000 145494.000000 141320.000000 144019.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.0 1.500000e+05 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000 150000.000000
mean 74999.500000 68349.172873 2.003417e+07 47.129021 8.052733 1.792369 0.375842 0.224943 119.316547 12.597160 2583.077267 0.000007 0.0 2.016033e+07 5923.327333 44.406268 -0.044809 0.080765 0.078833 0.017875 0.248204 0.044923 0.124692 0.058144 0.061996 -0.001000 0.009035 0.004813 0.000313 -0.000688
std 43301.414527 61103.875095 5.364988e+04 49.536040 7.864956 1.760640 0.548677 0.417546 177.168419 3.919576 1885.363218 0.002582 0.0 1.067328e+02 7501.998477 2.457548 3.641893 2.929618 2.026514 1.193661 0.045804 0.051743 0.201410 0.029186 0.035692 3.772386 3.286071 2.517478 1.288988 1.038685
min 0.000000 0.000000 1.991000e+07 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.000000 0.000000 0.0 2.015062e+07 11.000000 30.451976 -4.295589 -4.470671 -7.275037 -4.364565 0.000000 0.000000 0.000000 0.000000 0.000000 -9.168192 -5.558207 -9.639552 -4.153899 -6.546556
25% 37499.750000 11156.000000 1.999091e+07 10.000000 1.000000 0.000000 0.000000 0.000000 75.000000 12.500000 1018.000000 0.000000 0.0 2.016031e+07 1300.000000 43.135799 -3.192349 -0.970671 -1.462580 -0.921191 0.243615 0.000038 0.062474 0.035334 0.033930 -3.722303 -1.951543 -1.871846 -1.057789 -0.437034
50% 74999.500000 51638.000000 2.003091e+07 30.000000 6.000000 1.000000 0.000000 0.000000 110.000000 15.000000 2196.000000 0.000000 0.0 2.016032e+07 3250.000000 44.610266 -3.052671 -0.382947 0.099722 -0.075910 0.257798 0.000812 0.095866 0.057014 0.058484 1.624076 -0.358053 -0.130753 -0.036245 0.141246
75% 112499.250000 118841.250000 2.007111e+07 66.000000 13.000000 3.000000 1.000000 0.000000 150.000000 15.000000 3843.000000 0.000000 0.0 2.016033e+07 7700.000000 46.004721 4.000670 0.241335 1.565838 0.868758 0.265297 0.102009 0.125243 0.079382 0.087491 2.844357 1.255022 1.776933 0.942813 0.680378
max 149999.000000 196812.000000 2.015121e+07 247.000000 39.000000 7.000000 6.000000 1.000000 19312.000000 15.000000 8120.000000 1.000000 0.0 2.016041e+07 99999.000000 52.304178 7.320308 19.035496 9.854702 6.829352 0.291838 0.151420 1.404936 0.160791 0.222787 12.357011 18.819042 13.847792 11.147669 8.658418
2. 观察是否有缺省值
需要注意的是除了Nan
以外,还有其他的异常值表达,比如999,9999
和-1
等值其实都是Nan
的另外一种表达方式。
我们首先看一下训练集每个特征Nan
的情况:
print(Train_data.isnull().sum())
其结果如下:
SaleID 0
name 0
regDate 0
model 1
brand 0
bodyType 4506
fuelType 8680
gearbox 5981
power 0
kilometer 0
notRepairedDamage 0
regionCode 0
seller 0
offerType 0
creatDate 0
price 0
v_0 0
v_1 0
v_2 0
v_3 0
v_4 0
v_5 0
v_6 0
v_7 0
v_8 0
v_9 0
v_10 0
v_11 0
v_12 0
v_13 0
v_14 0
dtype: int64
测试集的特征Nan
情况也与训练集类似
SaleID 0
name 0
regDate 0
model 0
brand 0
bodyType 1413
fuelType 2893
gearbox 1910
power 0
...
v_14 0
dtype: int64
由此我们可以很清晰的观察到特征bodyType、fuelType
和gearbox
存在大量的Nan
值。Nan
存在的个数如果很小一般选择填充,如使用lgb
等树模型可以直接空缺,让树自己去优化;但如果Nan
存在的过多、可以考虑删掉。
3.查看是否有异常值
查看特征值的数据类型。
print(Train_data.info())
结果如下:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150000 entries, 0 to 149999
Data columns (total 31 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 SaleID 150000 non-null int64
1 name 150000 non-null int64
2 regDate 150000 non-null int64
3 model 149999 non-null float64
4 brand 150000 non-null int64
5 bodyType 145494 non-null float64
6 fuelType 141320 non-null float64
7 gearbox 144019 non-null float64
8 power 150000 non-null int64
9 kilometer 150000 non-null float64
10 notRepairedDamage 150000 non-null object
11 regionCode 150000 non-null int64
12 seller 150000 non-null int64
13 offerType 150000 non-null int64
14 creatDate 150000 non-null int64
15 price 150000 non-null int64
16 v_0 150000 non-null float64
17 v_1 150000 non-null float64
18 v_2 150000 non-null float64
19 v_3 150000 non-null float64
20 v_4 150000 non-null float64
21 v_5 150000 non-null float64
22 v_6 150000 non-null float64
23 v_7 150000 non-null float64
24 v_8 150000 non-null float64
25 v_9 150000 non-null float64
26 v_10 150000 non-null float64
27 v_11 150000 non-null float64
28 v_12 150000 non-null float64
29 v_13 150000 non-null float64
30 v_14 150000 non-null float64
dtypes: float64(20), int64(10), object(1)
memory usage: 35.5+ MB
可以发现除了notRepairedDamage
为object
类型其他都为数字(int
或者float
)。这里不难会产生疑问,notRepairedDamage
是车是否损坏,只有0和1两种情况,为什么不是int
类型呢?
我们将notRepairedDamage
具体的值和数量显示一下看看:
print(Train_data['notRepairedDamage'].value_counts())
其结果如下所示:
0.0 111361
- 24324
1.0 14315
Name: notRepairedDamage, dtype: int64
我们可以看到,除了和我们想的有0和1之外,还有一个'-'
值,我们可以理解为不知道该车是否损坏,因此我们可以将其当做Nan
值处理。
下面我们'-'
替换为Nan
,并显示处理后的情况:
Train_data['notRepairedDamage'].replace('-', np.nan, inplace=True)
print(Train_data['notRepairedDamage'].value_counts())
其结果如下:
0.0 111361
1.0 14315
Name: notRepairedDamage, dtype: int64
为了再次确认,我们再次查看训练集中缺省值的数目:
print(Train_data.isnull().sum())
结果如下:
SaleID 0
name 0
regDate 0
model 1
brand 0
bodyType 4506
fuelType 8680
gearbox 5981
power 0
kilometer 0
notRepairedDamage 24324
regionCode 0
...
v_14 0
dtype: int64
由此可见,notRepairedDamage
的缺省值已经从0变成了24324
,我们已经成功替换为了缺省值nan
。
我们用相同的操作处理测试集中的notRepairedDamage
,以下不再赘述。
4.删除对预测结果无帮助的特征
由二、1
中的统计量我们观察到有两个特征值可能出现了严重的倾斜。(部分截图如下)
因此我们需要进一步验证一下这两个特征是否真的是无效的,代码如下所示:
print(Train_data['seller'].value_counts())
print()
print(Train_data['offerType'].value_counts())
结果如下:
0 149999
1 1
Name: seller, dtype: int64
0 150000
Name: offerType, dtype: int64
通过以上的计数显示,我们确定了我们的猜想,这两个特征值出现了严重的倾斜,对结果无帮助,因此删除。
del Train_data["seller"]
del Train_data["offerType"]
del Test_data["seller"]
del Test_data["offerType"]
print(Train_data.shape)
删除后我们再次看一下训练集的形状:(150000, 29)
,由此可见,我们已经完成了删除操作。
5.了解预测值的分布
我们使用库seaborn
和scipy
来拟合预测值price
的分布情况,我们分别尝试了正态分布、*约翰逊分布和对数正态分布。
y = Train_data['price']
plt.figure(1); plt.title('Johnson SU')
sns.distplot(y, kde=False, fit=st.johnsonsu)
plt.figure(2); plt.title('Normal')
sns.distplot(y, kde=False, fit=st.norm)
plt.figure(3); plt.title('Log Normal')
sns.distplot(y, kde=False, fit=st.lognorm)
plt.show()
结果如下图所示:
由图我们易知,最拟合预测值price
的是*约翰逊分布。
为了进一步处理预测值,我们先进一步看一下各个price的频数分布。
y = Train_data['price']
plt.hist(Train_data['price'], orientation = 'vertical',histtype = 'bar', color ='blue')
plt.show()
print('the highest price is:',max(y))
结果如下所示:the highest price is: 99,999
由结果我们可知,价格的最大值为99,999
,但是绝大多数的价格都不超过20,000
,因此我们这里也可以把这些当作异常值删掉或者替换为20,000
。
?关于取对数的小trick还没明白具体怎么用,所以存疑,之后再补充?
6.特征分类和数字特征分析
这里也存疑,这特征不是手工分的吗?之前介绍的根据对象是否是object来输出有何意义?
特征分为类别特征和数字特征,本题中数字特征有:numeric_features = ['power', 'kilometer', 'v_0', 'v_1', 'v_2', 'v_3', 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10', 'v_11', 'v_12', 'v_13','v_14' ]
,类别特征有:categorical_features = ['name', 'model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'notRepairedDamage', 'regionCode']
。
而且我们知道预测值price
也属于数字特征,因此将price
加入numeric_features
中。
numeric_features.append('price')
数字特征的相关性分析
直接调用corr()
函数进行相关性分析
price_numeric = Train_data[numeric_features]
correlation = price_numeric.corr()
print(correlation['price'].sort_values(ascending = False),'\n')
结果如下:
price 1.000000
v_12 0.692823
v_8 0.685798
v_0 0.628397
power 0.219834
v_5 0.164317
v_2 0.085322
v_6 0.068970
v_1 0.060914
v_14 0.035911
v_13 -0.013993
v_7 -0.053024
v_4 -0.147085
v_9 -0.206205
v_10 -0.246175
v_11 -0.275320
kilometer -0.440519
v_3 -0.730946
Name: price, dtype: float64
相关性的可视化操作:
f , ax = plt.subplots(figsize = (7, 7))
plt.title('Correlation of Numeric Features with Price',y=1,size=16)
sns.heatmap(correlation,square = True, vmax=0.8)
结果如下所示:
分析相关性确实是很不错的思路,但是之后呢,是否应该对某些相关性绝对值大于或者小于某些值的特征做处理呢?而且我认为,相关性取绝对值来观察可能会更好一点
数字特征之间的关系可视化:
sns.set()
columns = ['price', 'v_12', 'v_8' , 'v_0', 'power', 'v_5', 'v_2', 'v_6', 'v_1', 'v_14']
sns.pairplot(Train_data[columns],size = 2 ,kind ='scatter',diag_kind='kde')
plt.show()
我们不再一一展示所有的特征之间的关系,但是从结果中我们可以观察到匿名特征'v_1'
和'v_6'
之间关系比较特殊,如下图所示:
由图我们可知匿名特征'v_1'
和'v_6'
之间有非常强的线性相关性,因此是否可以将两者之一删掉。(存疑 )
7.类别特征分析
懂了几种可视图操作,只能说大概了解到各类别取值的频数,并未得到更多知识,期待后续对数据进一步的挖掘和处理。
三、学习总结
1.收获
①.通过观察数据集的形状并观察前几行来大概观测数据特征
②.通过相关统计量来大致了解数据的特征
③.关于缺省值和异常值的查找(通过显示特征的类型和各取值的频数)和处理(改为nan或者填充)
④.删除取值严重倾斜的特征
⑤.使用库seaborn
和scipy
来拟合预测值分布情况
2.存疑
①.关于预测值你和那个地方取对数后近似正态分布的小tirck不懂,有什么好处么?
②.关于特征的分类,最开始说的是按照对象是否是object类来分,到最后却是手工分的,前面按照object类是笔误还是有其他什么原因?
③.分析相关性确实是很不错的思路,但是之后呢?是否应该对某些相关性绝对值大于或者小于某些值的特征做处理呢?比起按照相关性,是不是将其取绝对值来观察可能会更好一点?
④.相关性特别强的特征该如何处理,能否删除掉其中一个?比如本题中的v_1
和v_6
.
④.类别特征分析中只是介绍了几种可视图的操作,具体对之后的模型建立有何用处呢,期待后续对数据进一步的挖掘和处理。