Python数据分析学习

转摘:https://segmentfault.com/a/1190000015440560

一、数据初探

首先导入要使用的科学计算包numpy,pandas,可视化matplotlib,seaborn,以及机器学习包

 1 import pandas as pd
 2 import numpy as np
 3 import seaborn as sns
 4 import matplotlib as mpl
 5 
 6 import matplotlib.pyplot as plt
 7 from IPython.display import display
 8 plt.style.use("fivethirtyeight")
 9 sns.set_style({'font.sans-serif':['simhei','Arial']})
10 %matplotlib inline
11 
12 
13 # 检查Python版本
14 from sys import version_info
15 if version_info.major != 3:
16     raise Exception('请使用Python 3 来完成此项目')
引入模块与包

然后导入数据,并进行初步的观察,这些观察包括了解数据特征的缺失值异常值,以及大概的描述性统计

1 # 导入二手房数据
2 lianjia_df = pd.read_csv('lianjia.csv')
3 display(lianjia_df.head(n=2))
读取CSV数据

初步观察到一共有11个特征变量,Price 在这里是我们的目标变量,然后我们继续深入观察一下。

1 # 检查缺失值情况
2 lianjia_df.info()
检查缺失值情况

发现了数据集一共有23677条数据,其中Elevator特征有明显的缺失值。

lianjia_df.describe()
数据初始描述信息

上面结果给出了特征值是数值的一些统计值,包括平均数标准差中位数最小值最大值25%分位数75%分位数。这些统计结果简单直接,对于初始了解一个特征好坏非常有用,比如我们观察到 Size 特征 的最大值为1019平米,最小值为2平米,那么我们就要思考这个在实际中是不是存在的,如果不存在没有意义,那么这个数据就是一个异常值,会严重影响模型的性能。

 1 # 添加新特征房屋均价
 2 df = lianjia_df.copy()
 3 df['PerPrice'] = lianjia_df['Price']/lianjia_df['Size']
 4 
 5 # 重新摆放列位置
 6 columns = ['Region', 'District', 'Garden', 'Layout', 'Floor', 'Year', 'Size', 'Elevator', 'Direction', 'Renovation', 'PerPrice', 'Price']
 7 df = pd.DataFrame(df, columns = columns)
 8 
 9 # 重新审视数据集
10 display(df.head(n=2))
添加新特征房屋均价

发现 Id 特征其实没有什么实际意义,所以将其移除。由于房屋单价分析起来比较方便,简单的使用总价/面积就可得到,所以增加一个新的特征 PerPrice(只用于分析,不是预测特征)。另外,特征的顺序也被调整了一下,看起

来比较舒服。

二、可视化分析

Region特征分析:对于区域特征,我们可以分析不同区域房价和数量的对比。

 1 # 对二手房区域分组对比二手房数量和每平米房价
 2 df_house_count = df.groupby('Region')['Price'].count().sort_values(ascending=False).to_frame().reset_index()
 3 df_house_mean = df.groupby('Region')['PerPrice'].mean().sort_values(ascending=False).to_frame().reset_index()
 4 
 5 f, [ax1,ax2,ax3] = plt.subplots(3,1,figsize=(20,15))
 6 sns.barplot(x='Region', y='PerPrice', palette="Blues_d", data=df_house_mean, ax=ax1)
 7 ax1.set_title('北京各大区二手房每平米单价对比',fontsize=15)
 8 ax1.set_xlabel('区域')
 9 ax1.set_ylabel('每平米单价')
10 
11 sns.barplot(x='Region', y='Price', palette="Greens_d", data=df_house_count, ax=ax2)
12 ax2.set_title('北京各大区二手房数量对比',fontsize=15)
13 ax2.set_xlabel('区域')
14 ax2.set_ylabel('数量')
15 
16 sns.boxplot(x='Region', y='Price', data=df, ax=ax3)
17 ax3.set_title('北京各大区二手房房屋总价',fontsize=15)
18 ax3.set_xlabel('区域')
19 ax3.set_ylabel('房屋总价')
20 
21 plt.show()
区域特征

使用了pandas的网络透视功能 groupby 分组排序。区域特征可视化直接采用 seaborn 完成,颜色使用调色板 palette 参数,颜色渐变,越浅说明越少,反之越多。
可以观察到:

    • 二手房均价:西城区的房价最贵均价大约11万/平,因为西城在二环以里,且是热门学区房的聚集地。其次是东城大约10万/平,然后是海淀大约8.5万/平,其它均低于8万/平。
    • 二手房房数量:从数量统计上来看,目前二手房市场上比较火热的区域。海淀区和朝阳区二手房数量最多,差不多都接近3000套,毕竟大区,需求量也大。然后是丰台区,近几年正在改造建设,有赶超之势。
    • 二手房总价:通过箱型图看到,各大区域房屋总价中位数都都在1000万以下,且房屋总价离散值较高,西城最高达到了6000万,说明房屋价格特征不是理想的正太分布。

Size特征分析 :

1 f, [ax1,ax2] = plt.subplots(1, 2, figsize=(15, 5))
2 # 建房面积的分布情况
3 sns.distplot(df['Size'], bins=20, ax=ax1, color='r')
4 sns.kdeplot(df['Size'], shade=True, ax=ax1)
5 # 建房面积和出售价格的关系
6 sns.regplot(x='Size', y='Price', data=df, ax=ax2)
7 plt.show()
面积特征分析

Size 分布

      通过 distplot kdeplot 绘制柱状图观察 Size 特征的分布情况,属于长尾类型的分布,这说明了有很多面积很大且超出正常范围的二手房。

Size 与 Price 的关系

     通过 regplot 绘制了 Size 和 Price 之间的散点图,发现 Size 特征基本与Price呈现线性关系,符合基本常识,面积越大,价格越高。但是有两组明显的异常点:1. 面积不到10平米,但是价格超出10000万;2. 一个点面积超过了1000平米,价格很低,需要查看是什么情况。

1 df.loc[df['Size']< 10]
过滤面积小于10

df.loc[df['Size']>1000]
过滤面积大于1000

   经观察这个异常点不是普通的民用二手房,很可能是商用房,所以才有1房间0厅确有如此大超过1000平米的面积,这里选择移除。

df = df[(df['Layout']!='叠拼别墅')&(df['Size']<1000)]
过滤数据

     重新进行可视化发现就没有明显的异常点了

 Layout特征分析:

f, ax1= plt.subplots(figsize=(20,20))
sns.countplot(y='Layout', data=df, ax=ax1)
ax1.set_title('房屋户型',fontsize=15)
ax1.set_xlabel('数量')
ax1.set_ylabel('户型')
plt.show()
房型物征

      这个特征真是不看不知道,各种厅室组合搭配,竟然还有9室3厅,4室0厅等奇怪的结构。其中,2室一厅占绝大部分,其次是3室一厅,2室2厅,3室两厅。但是仔细观察特征分类下有很多不规则的命名,比如2室一厅与2房间1卫,还有别墅,没有统一的叫法。这样的特征肯定是不能作为机器学习模型的数据输入的,需要使用特征工程进行相应的处理。

Renovation 特征分析

1 df['Renovation'].value_counts()
装修特征

   精装 11345

   简装 8497

   其他 3239

   毛坯 576

      发现Renovation装修特征中竟然有南北,它属于朝向的类型,可能是因为爬虫过程中一些信息位置为空,导致“Direction”朝向特征出现在这里,所以需要清除或替

# 去掉错误数据“南北”,因为爬虫过程中一些信息位置为空,导致“Direction”的特征出现在这里,需要清除或替换
df['Renovation'] = df.loc[(df['Renovation'] != '南北'), 'Renovation']

# 画幅设置
f, [ax1,ax2,ax3] = plt.subplots(1, 3, figsize=(20, 5))
sns.countplot(df['Renovation'], ax=ax1)
sns.barplot(x='Renovation', y='Price', data=df, ax=ax2)
sns.boxplot(x='Renovation', y='Price', data=df, ax=ax3)
plt.show()
按装修分类统计图

     观察到,精装修的二手房数量最多,简装其次,也是我们平日常见的。而对于价格来说,毛坯类型却是最高,其次是精装修

Elevator 特征分析

        初探数据的时候,我们发现 Elevator 特征是有大量缺失值的,这对于我们是十分不利的,首先我们先看看有多少缺失值:

misn = len(df.loc[(df['Elevator'].isnull()), 'Elevator'])
print('Elevator缺失值数量为:'+ str(misn))
电梯特征分析

      Elevator 缺失值数量为:8237

      这么多的缺失值怎么办呢?这个需要根据实际情况考虑,常用的方法有平均值/中位数填补法,直接移除,或者根据其他特征建模预测等。

      这里我们考虑填补法,但是有无电梯不是数值,不存在平均值和中位数,怎么填补呢?这里给大家提供一种思路:就是根据楼层 Floor 来判断有无电梯,一般的楼层大于6的都有电梯,而小于等于6层的一般都没有电梯。有了这个标准,那么剩下的就简单了。

 1 # 由于存在个别类型错误,如简装和精装,特征值错位,故需要移除
 2 df['Elevator'] = df.loc[(df['Elevator'] == '有电梯')|(df['Elevator'] == '无电梯'), 'Elevator']
 3 
 4 # 填补Elevator缺失值
 5 df.loc[(df['Floor']>6)&(df['Elevator'].isnull()), 'Elevator'] = '有电梯'
 6 df.loc[(df['Floor']<=6)&(df['Elevator'].isnull()), 'Elevator'] = '无电梯'
 7 
 8 f, [ax1,ax2] = plt.subplots(1, 2, figsize=(20, 10))
 9 sns.countplot(df['Elevator'], ax=ax1)
10 ax1.set_title('有无电梯数量对比',fontsize=15)
11 ax1.set_xlabel('是否有电梯')
12 ax1.set_ylabel('数量')
13 sns.barplot(x='Elevator', y='Price', data=df, ax=ax2)
14 ax2.set_title('有无电梯房价对比',fontsize=15)
15 ax2.set_xlabel('是否有电梯')
16 ax2.set_ylabel('总价')
17 plt.show()
画统计图

         结果观察到,有电梯的二手房数量居多一些,毕竟高层土地利用率比较高,适合北京庞大的人群需要,而高层就需要电梯。相应的,有电梯二手房房价较高,因为电梯前期装修费和后期维护费包含内了(但这个价格比较只是一个平均的概念,比如无电梯的6层豪华小区当然价格更高了)。

Year 特征分析:

1 grid = sns.FacetGrid(df, row='Elevator', col='Renovation', palette='seismic',size=4)
2 grid.map(plt.scatter, 'Year', 'Price')
3 grid.add_legend()
画散点图

在Renovation和Elevator的分类条件下,使用 FaceGrid 分析 Year 特征,观察结果如下:

  • 整个二手房房价趋势是随着时间增长而增长的;
  • 2000年以后建造的二手房房价相较于2000年以前有很明显的价格上涨;
  • 1980年之前几乎不存在有电梯二手房数据,说明1980年之前还没有大面积安装电梯;
  • 1980年之前无电梯二手房中,简装二手房占绝大多数,精装反而很少;

Floor 特征分析:

f, ax1= plt.subplots(figsize=(20,5))
sns.countplot(x='Floor', data=df, ax=ax1)
ax1.set_title('房屋户型',fontsize=15)
ax1.set_xlabel('数量')
ax1.set_ylabel('户型')
plt.show()
楼层特征统计图

        可以看到,6层二手房数量最多,但是单独的楼层特征没有什么意义,因为每个小区住房的总楼层数都不一样,我们需要知道楼层的相对意义。另外,楼层与文化也有很重要联系,比如中国文化七上八下,七层可能受欢迎,房价也贵,而一般也不会有4层或18层。当然,正常情况下中间楼层是比较受欢迎的,价格也高,底层和顶层受欢迎度较低,价格也相对较低。所以楼层是一个非常复杂的特征,对房价影响也比较大。

  转摘:https://segmentfault.com/a/1190000015613967

  本篇将继续上一篇数据分析之后进行数据挖掘建模预测,这两部分构成了一个简单的完整项目。结合两篇文章通过数据分析和挖掘的方法可以达到二手房屋价格预测的效果。

  下面从特征工程开始讲述。

二、特征工程

  特征工程包括的内容很多,有特征清洗,预处理,监控等,而预处理根据单一特征或多特征又分很多种方法,如归一化,降维,特征选择,特征筛选等等。这么多的方法,为的是什么呢?其目的是让这些特征更友好的作为模型的输入,处理数据的好坏会严重的影响模型性能,而好的特征工程有的时候甚至比建模调参更重要。

  下面是继上一次分析之后对数据进行的特征工程,博主将一个一个帮大家解读。

 1 """
 2 特征工程
 3 """
 4 # 移除结构类型异常值和房屋大小异常值
 5 df = df[(df['Layout']!='叠拼别墅')&(df['Size']<1000)]
 6 
 7 # 去掉错误数据“南北”,因为爬虫过程中一些信息位置为空,导致“Direction”的特征出现在这里,需要清除或替换
 8 df['Renovation'] = df.loc[(df['Renovation'] != '南北'), 'Renovation']
 9 
10 # 由于存在个别类型错误,如简装和精装,特征值错位,故需要移除
11 df['Elevator'] = df.loc[(df['Elevator'] == '有电梯')|(df['Elevator'] == '无电梯'), 'Elevator']
12 
13 # 填补Elevator缺失值
14 df.loc[(df['Floor']>6)&(df['Elevator'].isnull()), 'Elevator'] = '有电梯'
15 df.loc[(df['Floor']<=6)&(df['Elevator'].isnull()), 'Elevator'] = '无电梯'
16 
17 # 只考虑“室”和“厅”,将其它少数“房间”和“卫”移除
18 df = df.loc[df['Layout'].str.extract('^\d(.*?)\d.*?') == '']
19 
20 # 提取“室”和“厅”创建新特征
21 df['Layout_room_num'] = df['Layout'].str.extract('(^\d).*', expand=False).astype('int64')
22 df['Layout_hall_num'] = df['Layout'].str.extract('^\d.*?(\d).*', expand=False).astype('int64')
23 
24 # 按中位数对“Year”特征进行分箱
25 df['Year'] = pd.qcut(df['Year'],8).astype('object')
26 
27 # 对“Direction”特征
28 d_list_one = ['','西','','']
29 d_list_two = ['东西','东南','东北','西南','西北','南北']
30 d_list_three = ['东西南','东西北','东南北','西南北']
31 d_list_four = ['东西南北']    
32 df['Direction'] = df['Direction'].apply(direct_func)
33 df = df.loc[(df['Direction']!='no')&(df['Direction']!='nan')]
34 
35 # 根据已有特征创建新特征
36 df['Layout_total_num'] = df['Layout_room_num'] + df['Layout_hall_num']
37 df['Size_room_ratio'] = df['Size']/df['Layout_total_num']
38 
39 # 删除无用特征
40 df = df.drop(['Layout','PerPrice','Garden'],axis=1)
41 
42 # 对于object特征进行onehot编码
43 df,df_cat = one_hot_encoder(df)
特征工程-数据处理

由于一些清洗处理在上一篇文章已经提到,所以从17行代码开始。

Layout  先看看没经处理的Layout特征值是什么样的:

1 df['Layout'].value_counts()
显示房型分类统计信息

  大家也都看到了,特征值并不是像想象中的那么理想。有两种格式的数据,一种是"xx室xx厅",另一种是"xx房间xx卫",但是绝大多数都是xx室xx厅的数据。而对于像"11房间3卫"或者"5房间0卫"这些的Layout明显不是民住的二手房(不在我们的考虑范围之内),因此最后决定将所有"xx房间xx卫"格式的数据都移除掉,只保留"xx室xx厅"的数据。

Layout特征的处理如下:

  第2行的意思是只保留"xx室xx厅"数据,但是保留这种格式的数据也是不能作为模型的输入的,我们不如干脆将"室"和"厅"都提取出来,单独作为两个新特征(如第5和6行),这样效果可能更好。

具体的用法就是使用 str.extract() 方法,里面写的是正则表达式。

1 # 只考虑“室”和“厅”,将其它少数“房间”和“卫”移除
2 df = df.loc[df['Layout'].str.extract('^\d(.*?)\d.*?') == '']
3 
4 # 提取“室”和“厅”创建新特征
5 df['Layout_room_num'] = df['Layout'].str.extract('(^\d).*', expand=False).astype('int64')
6 df['Layout_hall_num'] = df['Layout'].str.extract('^\d.*?(\d).*', expand=False).astype('int64')
提取所需要的数据

Year分析:为建房的年限时间。年限种类很多,分布在1950和2018之间,如果每个不同的 Year 值都作为特征值,我们并不能找出 Year 对 Price 有什么影响,因为年限划分的太细了。因此,我们只有将连续数值型特征 Year 离散化,做分箱处理。

如何分箱还要看实际业务需求,博主为了方便并没有手动分箱,而使用了pandas的 qcut 采用中位数进行分割,分割数为8等份。

1 # 按中位数对“Year”特征进行分箱
2 df['Year'] = pd.qcut(df['Year'],8).astype('object')
按year进行分箱

将 Year 进行分箱的结果:

Direction分析:这个特征没处理之前更乱,原以为是爬虫的问题,但是亲自到链家看过,朝向确实是这样的

  如上所见,像"西南西北北"或者"东东南南"这样的朝向是不符合常识的(反正我是理解不了)。因此,我们需要将这些凌乱的数据进行处理,具体实现方式是博主自己写了一个函数 direct_func,主要思想就是将各种重复但顺序不一样的特征值合并,比如"西南北""南西北",并将不合理的一些值移除,如"西南西北北"等。

  然后通过 apply() 方法将 Direction 数据格式转换,代码如下:

1 # 对“Direction”特征
2 d_list_one = ['','西','','']
3 d_list_two = ['东西','东南','东北','西南','西北','南北']
4 d_list_three = ['东西南','东西北','东南北','西南北']
5 d_list_four = ['东西南北']    
6 df['Direction'] = df['Direction'].apply(direct_func)
7 df = df.loc[(df['Direction']!='no')&(df['Direction']!='nan')]
数据转换

处理完结果如下,所有的内容相同而顺序不同的朝向都合并了,异常朝向也被移除了。

 

创建新特征:

  有时候仅靠已有的一些特征是不够的,需要根据对业务的理解,定义一些的新特征,然后尝试这些新特征对模型的影响,在实战中会经常使用这种方法。

这里尝试将"室"与"厅"的数量相加作为一个总数量特征,然后将房屋大小Size与总数量的比值作为一个新特征,可理解为 "每个房间的平均面积大小"。当然,新特征不是固定的,可根据自己的理解来灵活的定义。

1 # 根据已有特征创建新特征
2 df['Layout_total_num'] = df['Layout_room_num'] + df['Layout_hall_num']
3 df['Size_room_ratio'] = df['Size']/df['Layout_total_num']
4 
5 # 删除无用特征
6 df = df.drop(['Layout','PerPrice','Garden'],axis=1)
创建新特征

      最后删除旧的特征 Layout,PerPrice,Garden 

One-hot coding:

  这部分是 One-hot 独热编码,因为像 Region,Year(离散分箱后),Direction,Renovation,Elevator等特征都是定类的非数值型类型,而作为模型的输入我们需要将这些非数值量化。

在没有一定顺序(定序类型)的情况下,使用独热编码处理定类数据是非常常用的做法,在pandas中非常简单,就是使用 get_dummies() 方法,而对于像Size这样的定比数据则不使用独热,博主这里用了一个自己封装的函数实现了定类数据的自动量化处理。

       对于定类,定序,定距,定比这四个非常重要的数据类型相信加入知识星球的伙伴都非常熟悉了,想要了解的同学可以扫描最后二维码查看。

1 # 对于object特征进行onehot编码
2 df,df_cat = one_hot_encoder(df)
one hot 编码

  以上的特征工程就完成了。

特征相关性

  下面使用 seabornheatmap 方法对特征相关性进行可视化。

1 # data_corr 
2 colormap = plt.cm.RdBu
3 plt.figure(figsize=(20,20))
4 # plt.title('Pearson Correlation of Features', y=1.05, size=15)
5 sns.heatmap(df.corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor='white', annot=True)
可视化

颜色偏红或者偏蓝都说明相关系数较大,即两个特征对于目标变量的影响程度相似,即存在严重的重复信息,会造成过拟合现象。因此,通过特征相关性分析,我们可以找出哪些特征有严重的重叠信息,然后择优选择。

三、数据建模预测

为了方便理解,博主在建模上做了一些精简,模型策略方法如下:

  • 使用Cart决策树的回归模型对二手房房价进行分析预测
  • 使用交叉验证方法充分利用数据集进行训练,避免数据划分不均匀的影响。
  • 使用GridSearchCV方法优化模型参数
  • 使用R2评分方法对模型预测评分

上面的建模方法比较简单,旨在让大家了解建模分析的过程。随着逐渐的深入了解,博主会介绍更多实战内容。

  数据划分

1 # 转换训练测试集格式为数组
2 features = np.array(features)
3 prices = np.array(prices)
4 
5 # 导入sklearn进行训练测试集划分
6 from sklearn.model_selection import train_test_split
7 features_train, features_test, prices_train, prices_test = train_test_split(features, prices, test_size=0.2, random_state=0)
训练测试集

  将以上数据划分为训练集和测试集,训练集用于建立模型,测试集用于测试模型预测准确率。使用sklearn的 model_selection 实现以上划分功能。

   建立模型

 1 from sklearn.model_selection import KFold
 2 from sklearn.tree import DecisionTreeRegressor
 3 from sklearn.metrics import make_scorer
 4 from sklearn.model_selection import GridSearchCV
 5 
 6 # 利用GridSearchCV计算最优解
 7 def fit_model(X, y):
 8     """ 基于输入数据 [X,y],利于网格搜索找到最优的决策树模型"""
 9     
10     cross_validator = KFold(10, shuffle=True)
11     regressor = DecisionTreeRegressor()
12     
13     params = {'max_depth':[1,2,3,4,5,6,7,8,9,10]}
14     scoring_fnc = make_scorer(performance_metric)
15     grid = GridSearchCV(estimator = regressor, param_grid = params, scoring = scoring_fnc, cv = cross_validator)
16 
17     # 基于输入数据 [X,y],进行网格搜索
18     grid = grid.fit(X, y)
19 #     print pd.DataFrame(grid.cv_results_)
20     return grid.best_estimator_
21 
22 # 计算R2分数
23 def performance_metric(y_true, y_predict):
24     """计算并返回预测值相比于预测值的分数"""
25     from sklearn.metrics import r2_score
26     score = r2_score(y_true, y_predict)
27 
28     return score
mode

  使用了 KFold 方法减缓过拟合,GridSearchCV 方法进行最优参数自动搜查,最后使用R2评分来给模型打分。

  调参优化模型

 1 import visuals as vs
 2 
 3 # 分析模型
 4 vs.ModelLearning(features_train, prices_train)
 5 vs.ModelComplexity(features_train, prices_train)
 6 
 7 optimal_reg1 = fit_model(features_train, prices_train)
 8 
 9 # 输出最优模型的 'max_depth' 参数
10 print("最理想模型的参数 'max_depth' 是 {} 。".format(optimal_reg1.get_params()['max_depth']))
11 
12 predicted_value = optimal_reg1.predict(features_test)
13 r2 = performance_metric(prices_test, predicted_value)
14 
15 print("最优模型在测试数据上 R^2 分数 {:,.2f}。".format(r2))
参数调优

  由于决策树容易过拟合的问题,我们这里采取观察学习曲线的方法查看决策树深度,并判断模型是否出现了过拟合现象。以下是观察到的学习曲线图形:

  通过观察,最理想模型的参数"max_depth"是10,此种情况下达到了偏差与方差的最优平衡,最后模型在测试数据上的R2分数,也即二手房房价预测的准确率为:0.81

转摘:https://www.cnblogs.com/palace/p/9598256.html

1. 引言

Pandas是一个开源的Python数据分析库。Pandas把结构化数据分为了三类:

  • Series,1维序列,可视作为没有column名的、只有一个column的DataFrame;

  • DataFrame,同Spark SQL中的DataFrame一样,其概念来自于R语言,为多column并schema化的2维结构化数据,可视作为Series的容器(container);

  • Panel,为3维的结构化数据,可视作为DataFrame的容器;

DataFrame较为常见,因此本文主要讨论内容将为DataFrame。DataFrame的生成可通过读取纯文本、Json等数据来生成,亦可以通过Python对象来生成:

import pandas as pd import numpy as np df = pd.DataFrame({'total_bill': [16.99, 10.34, 23.68, 23.68, 24.59], 'tip': [1.01, 1.66, 3.50, 3.31, 3.61], 'sex': ['Female', 'Male', 'Male', 'Male', 'Female']})

对于DataFrame,我们可以看到其固有属性:

# data type of columns
print df.dtypes # indexes print df.index # return pandas.Index print df.columns # each row, return array[array] print df.values
  • .index,为行索引

  • .columns,为列名称(label)

  • .dtype,为列数据类型

2. SQL操作

官方Doc给出了部分SQL的Pandas实现。在此基础上,本文给出了一些扩充说明。以下内容基于Python 2.7 + Pandas 0.18.1的版本。

select

SQL中的select是根据列的名称来选取;Pandas则更为灵活,不但可根据列名称选取,还可以根据列所在的position选取。相关函数如下:

  • loc,基于列label,可选取特定行(根据行index);

  • iloc,基于行/列的position;

print df.loc[1:3, ['total_bill', 'tip']] print df.loc[1:3, 'tip': 'total_bill'] print df.iloc[1:3, [1, 2]] print df.iloc[1:3, 1: 3]
  • at,根据指定行index及列label,快速定位DataFrame的元素;

  • iat,与at类似,不同的是根据position来定位的;

print df.at[3, 'tip'] print df.iat[3, 1]
  • ix,为loc与iloc的混合体,既支持label也支持position;

print df.ix[1:3, [1, 2]] print df.ix[1:3, ['total_bill', 'tip']]

此外,有更为简洁的行/列选取方式:

print df[1: 3] print df[['total_bill', 'tip']] # print df[1:2, ['total_bill', 'tip']] # TypeError: unhashable type

where

Pandas实现where filter,较为常用的办法为df[df[colunm] boolean expr],比如:

print df[df['sex'] == 'Female'] print df[df['total_bill'] > 20] # or print df.query('total_bill > 20')

在where子句中常常会搭配and, or, in, not关键词,Pandas中也有对应的实现:

# and
print df[(df['sex'] == 'Female') & (df['total_bill'] > 20)] # or print df[(df['sex'] == 'Female') | (df['total_bill'] > 20)] # in print df[df['total_bill'].isin([21.01, 23.68, 24.59])] # not print df[-(df['sex'] == 'Male')] print df[-df['total_bill'].isin([21.01, 23.68, 24.59])] # string function print df = df[(-df['app'].isin(sys_app)) & (-df.app.str.contains('^微信\d+$'))]

对where条件筛选后只有一行的dataframe取其中某一列的值,其两种实现方式如下:

total = df.loc[df['tip'] == 1.66, 'total_bill'].values[0] total = df.get_value(df.loc[df['tip'] == 1.66].index.values[0], 'total_bill')

distinct

drop_duplicates根据某列对dataframe进行去重:

df.drop_duplicates(subset=['sex'], keep='first', inplace=True)

包含参数:

  • subset,为选定的列做distinct,默认为所有列;

  • keep,值选项{'first', 'last', False},保留重复元素中的第一个、最后一个,或全部删除;

  • inplace ,默认为False,返回一个新的dataframe;若为True,则返回去重后的原dataframe

group

group一般会配合合计函数(Aggregate functions)使用,比如:count、avg等。Pandas对合计函数的支持有限,有count和size函数实现SQL的count:

print df.groupby('sex').size() print df.groupby('sex').count() print df.groupby('sex')['tip'].count()

对于多合计函数,

select sex, max(tip), sum(total_bill) as total from tips_tb group by sex;

实现在agg()中指定dict:

print df.groupby('sex').agg({'tip': np.max, 'total_bill': np.sum}) # count(distinct **) print df.groupby('tip').agg({'sex': pd.Series.nunique})

as

SQL中使用as修改列的别名,Pandas也支持这种修改:

# first implementation
df.columns = ['total', 'pit', 'xes'] # second implementation df.rename(columns={'total_bill': 'total', 'tip': 'pit', 'sex': 'xes'}, inplace=True)

其中,第一种方法的修改是有问题的,因为其是按照列position逐一替换的。因此,我推荐第二种方法。

join

Pandas中join的实现也有两种:

# 1.
df.join(df2, how='left'...) # 2.  pd.merge(df1, df2, how='left', left_on='app', right_on='app')

第一种方法是按DataFrame的index进行join的,而第二种方法才是按on指定的列做join。Pandas满足left、right、inner、full outer四种join方式。

order

Pandas中支持多列order,并可以调整不同列的升序/降序,有更高的排序自由度:

print df.sort_values(['total_bill', 'tip'], ascending=[False, True])

top

对于全局的top:

print df.nlargest(3, columns=['total_bill'])

对于分组top,MySQL的实现(采用自join的方式):

select a.sex, a.tip
from tips_tb a where ( select count(*) from tips_tb b where b.sex = a.sex and b.tip > a.tip ) < 2 order by a.sex, a.tip desc;

Pandas的等价实现,思路与上类似:

# 1.
df.assign(rn=df.sort_values(['total_bill'], ascending=False) .groupby('sex') .cumcount()+1)\ .query('rn < 3')\ .sort_values(['sex', 'rn']) # 2. df.assign(rn=df.groupby('sex')['total_bill'] .rank(method='first', ascending=False)) \ .query('rn < 3') \ .sort_values(['sex', 'rn'])

replace

replace函数提供对dataframe全局修改,亦可通过where条件进行过滤修改(搭配loc):

# overall replace
df.replace(to_replace='Female', value='Sansa', inplace=True) # dict replace df.replace({'sex': {'Female': 'Sansa', 'Male': 'Leone'}}, inplace=True) # replace on where condition  df.loc[df.sex == 'Male', 'sex'] = 'Leone'

自定义

除了上述SQL操作外,Pandas提供对每列/每一元素做自定义操作,为此而设计以下三个函数:

  • map(func),为Series的函数,DataFrame不能直接调用,需取列后再调用;

  • apply(func),对DataFrame中的某一行/列进行func操作;

  • applymap(func),为element-wise函数,对每一个元素做func操作

print df['tip'].map(lambda x: x - 1) print df[['total_bill', 'tip']].apply(sum) print df.applymap(lambda x: x.upper() if type(x) is str else x)

3. 实战

环比增长

现有两个月APP的UV数据,要得到月UV环比增长;该操作等价于两个Dataframe left join后按指定列做减操作:

def chain(current, last): df1 = pd.read_csv(current, names=['app', 'tag', 'uv'], sep='\t') df2 = pd.read_csv(last, names=['app', 'tag', 'uv'], sep='\t') df3 = pd.merge(df1, df2, how='left', on='app') df3['uv_y'] = df3['uv_y'].map(lambda x: 0.0 if pd.isnull(x) else x) df3['growth'] = df3['uv_x'] - df3['uv_y'] return df3[['app', 'growth', 'uv_x', 'uv_y']].sort_values(by='growth', ascending=False)

差集

对于给定的列,一个Dataframe过滤另一个Dataframe该列的值;相当于集合的差集操作:

def difference(left, right, on): """  difference of two dataframes  :param left: left dataframe  :param right: right dataframe  :param on: join key  :return: difference dataframe  """ df = pd.merge(left, right, how='left', on=on) left_columns = left.columns col_y = df.columns[left_columns.size] df = df[df[col_y].isnull()] df = df.ix[:, 0:left_columns.size] df.columns = left_columns return df

转摘:https://segmentfault.com/a/1190000015440560

一、数据初探

首先导入要使用的科学计算包numpy,pandas,可视化matplotlib,seaborn,以及机器学习包

 1 import pandas as pd
 2 import numpy as np
 3 import seaborn as sns
 4 import matplotlib as mpl
 5 
 6 import matplotlib.pyplot as plt
 7 from IPython.display import display
 8 plt.style.use("fivethirtyeight")
 9 sns.set_style({'font.sans-serif':['simhei','Arial']})
10 %matplotlib inline
11 
12 
13 # 检查Python版本
14 from sys import version_info
15 if version_info.major != 3:
16     raise Exception('请使用Python 3 来完成此项目')
引入模块与包

然后导入数据,并进行初步的观察,这些观察包括了解数据特征的缺失值异常值,以及大概的描述性统计

1 # 导入二手房数据
2 lianjia_df = pd.read_csv('lianjia.csv')
3 display(lianjia_df.head(n=2))
读取CSV数据

初步观察到一共有11个特征变量,Price 在这里是我们的目标变量,然后我们继续深入观察一下。

1 # 检查缺失值情况
2 lianjia_df.info()
检查缺失值情况

发现了数据集一共有23677条数据,其中Elevator特征有明显的缺失值。

lianjia_df.describe()
数据初始描述信息

上面结果给出了特征值是数值的一些统计值,包括平均数标准差中位数最小值最大值25%分位数75%分位数。这些统计结果简单直接,对于初始了解一个特征好坏非常有用,比如我们观察到 Size 特征 的最大值为1019平米,最小值为2平米,那么我们就要思考这个在实际中是不是存在的,如果不存在没有意义,那么这个数据就是一个异常值,会严重影响模型的性能。

 1 # 添加新特征房屋均价
 2 df = lianjia_df.copy()
 3 df['PerPrice'] = lianjia_df['Price']/lianjia_df['Size']
 4 
 5 # 重新摆放列位置
 6 columns = ['Region', 'District', 'Garden', 'Layout', 'Floor', 'Year', 'Size', 'Elevator', 'Direction', 'Renovation', 'PerPrice', 'Price']
 7 df = pd.DataFrame(df, columns = columns)
 8 
 9 # 重新审视数据集
10 display(df.head(n=2))
添加新特征房屋均价

发现 Id 特征其实没有什么实际意义,所以将其移除。由于房屋单价分析起来比较方便,简单的使用总价/面积就可得到,所以增加一个新的特征 PerPrice(只用于分析,不是预测特征)。另外,特征的顺序也被调整了一下,看起

来比较舒服。

二、可视化分析

Region特征分析:对于区域特征,我们可以分析不同区域房价和数量的对比。

 1 # 对二手房区域分组对比二手房数量和每平米房价
 2 df_house_count = df.groupby('Region')['Price'].count().sort_values(ascending=False).to_frame().reset_index()
 3 df_house_mean = df.groupby('Region')['PerPrice'].mean().sort_values(ascending=False).to_frame().reset_index()
 4 
 5 f, [ax1,ax2,ax3] = plt.subplots(3,1,figsize=(20,15))
 6 sns.barplot(x='Region', y='PerPrice', palette="Blues_d", data=df_house_mean, ax=ax1)
 7 ax1.set_title('北京各大区二手房每平米单价对比',fontsize=15)
 8 ax1.set_xlabel('区域')
 9 ax1.set_ylabel('每平米单价')
10 
11 sns.barplot(x='Region', y='Price', palette="Greens_d", data=df_house_count, ax=ax2)
12 ax2.set_title('北京各大区二手房数量对比',fontsize=15)
13 ax2.set_xlabel('区域')
14 ax2.set_ylabel('数量')
15 
16 sns.boxplot(x='Region', y='Price', data=df, ax=ax3)
17 ax3.set_title('北京各大区二手房房屋总价',fontsize=15)
18 ax3.set_xlabel('区域')
19 ax3.set_ylabel('房屋总价')
20 
21 plt.show()
区域特征

使用了pandas的网络透视功能 groupby 分组排序。区域特征可视化直接采用 seaborn 完成,颜色使用调色板 palette 参数,颜色渐变,越浅说明越少,反之越多。
可以观察到:

    • 二手房均价:西城区的房价最贵均价大约11万/平,因为西城在二环以里,且是热门学区房的聚集地。其次是东城大约10万/平,然后是海淀大约8.5万/平,其它均低于8万/平。
    • 二手房房数量:从数量统计上来看,目前二手房市场上比较火热的区域。海淀区和朝阳区二手房数量最多,差不多都接近3000套,毕竟大区,需求量也大。然后是丰台区,近几年正在改造建设,有赶超之势。
    • 二手房总价:通过箱型图看到,各大区域房屋总价中位数都都在1000万以下,且房屋总价离散值较高,西城最高达到了6000万,说明房屋价格特征不是理想的正太分布。

Size特征分析 :

1 f, [ax1,ax2] = plt.subplots(1, 2, figsize=(15, 5))
2 # 建房面积的分布情况
3 sns.distplot(df['Size'], bins=20, ax=ax1, color='r')
4 sns.kdeplot(df['Size'], shade=True, ax=ax1)
5 # 建房面积和出售价格的关系
6 sns.regplot(x='Size', y='Price', data=df, ax=ax2)
7 plt.show()
面积特征分析

Size 分布

      通过 distplot kdeplot 绘制柱状图观察 Size 特征的分布情况,属于长尾类型的分布,这说明了有很多面积很大且超出正常范围的二手房。

Size 与 Price 的关系

     通过 regplot 绘制了 Size 和 Price 之间的散点图,发现 Size 特征基本与Price呈现线性关系,符合基本常识,面积越大,价格越高。但是有两组明显的异常点:1. 面积不到10平米,但是价格超出10000万;2. 一个点面积超过了1000平米,价格很低,需要查看是什么情况。

1 df.loc[df['Size']< 10]
过滤面积小于10

df.loc[df['Size']>1000]
过滤面积大于1000

   经观察这个异常点不是普通的民用二手房,很可能是商用房,所以才有1房间0厅确有如此大超过1000平米的面积,这里选择移除。

df = df[(df['Layout']!='叠拼别墅')&(df['Size']<1000)]
过滤数据

     重新进行可视化发现就没有明显的异常点了

 Layout特征分析:

f, ax1= plt.subplots(figsize=(20,20))
sns.countplot(y='Layout', data=df, ax=ax1)
ax1.set_title('房屋户型',fontsize=15)
ax1.set_xlabel('数量')
ax1.set_ylabel('户型')
plt.show()
房型物征

      这个特征真是不看不知道,各种厅室组合搭配,竟然还有9室3厅,4室0厅等奇怪的结构。其中,2室一厅占绝大部分,其次是3室一厅,2室2厅,3室两厅。但是仔细观察特征分类下有很多不规则的命名,比如2室一厅与2房间1卫,还有别墅,没有统一的叫法。这样的特征肯定是不能作为机器学习模型的数据输入的,需要使用特征工程进行相应的处理。

Renovation 特征分析

1 df['Renovation'].value_counts()
装修特征

   精装 11345

   简装 8497

   其他 3239

   毛坯 576

      发现Renovation装修特征中竟然有南北,它属于朝向的类型,可能是因为爬虫过程中一些信息位置为空,导致“Direction”朝向特征出现在这里,所以需要清除或替

# 去掉错误数据“南北”,因为爬虫过程中一些信息位置为空,导致“Direction”的特征出现在这里,需要清除或替换
df['Renovation'] = df.loc[(df['Renovation'] != '南北'), 'Renovation']

# 画幅设置
f, [ax1,ax2,ax3] = plt.subplots(1, 3, figsize=(20, 5))
sns.countplot(df['Renovation'], ax=ax1)
sns.barplot(x='Renovation', y='Price', data=df, ax=ax2)
sns.boxplot(x='Renovation', y='Price', data=df, ax=ax3)
plt.show()
按装修分类统计图

     观察到,精装修的二手房数量最多,简装其次,也是我们平日常见的。而对于价格来说,毛坯类型却是最高,其次是精装修

Elevator 特征分析

        初探数据的时候,我们发现 Elevator 特征是有大量缺失值的,这对于我们是十分不利的,首先我们先看看有多少缺失值:

misn = len(df.loc[(df['Elevator'].isnull()), 'Elevator'])
print('Elevator缺失值数量为:'+ str(misn))
电梯特征分析

      Elevator 缺失值数量为:8237

      这么多的缺失值怎么办呢?这个需要根据实际情况考虑,常用的方法有平均值/中位数填补法,直接移除,或者根据其他特征建模预测等。

      这里我们考虑填补法,但是有无电梯不是数值,不存在平均值和中位数,怎么填补呢?这里给大家提供一种思路:就是根据楼层 Floor 来判断有无电梯,一般的楼层大于6的都有电梯,而小于等于6层的一般都没有电梯。有了这个标准,那么剩下的就简单了。

 1 # 由于存在个别类型错误,如简装和精装,特征值错位,故需要移除
 2 df['Elevator'] = df.loc[(df['Elevator'] == '有电梯')|(df['Elevator'] == '无电梯'), 'Elevator']
 3 
 4 # 填补Elevator缺失值
 5 df.loc[(df['Floor']>6)&(df['Elevator'].isnull()), 'Elevator'] = '有电梯'
 6 df.loc[(df['Floor']<=6)&(df['Elevator'].isnull()), 'Elevator'] = '无电梯'
 7 
 8 f, [ax1,ax2] = plt.subplots(1, 2, figsize=(20, 10))
 9 sns.countplot(df['Elevator'], ax=ax1)
10 ax1.set_title('有无电梯数量对比',fontsize=15)
11 ax1.set_xlabel('是否有电梯')
12 ax1.set_ylabel('数量')
13 sns.barplot(x='Elevator', y='Price', data=df, ax=ax2)
14 ax2.set_title('有无电梯房价对比',fontsize=15)
15 ax2.set_xlabel('是否有电梯')
16 ax2.set_ylabel('总价')
17 plt.show()
画统计图

         结果观察到,有电梯的二手房数量居多一些,毕竟高层土地利用率比较高,适合北京庞大的人群需要,而高层就需要电梯。相应的,有电梯二手房房价较高,因为电梯前期装修费和后期维护费包含内了(但这个价格比较只是一个平均的概念,比如无电梯的6层豪华小区当然价格更高了)。

Year 特征分析:

1 grid = sns.FacetGrid(df, row='Elevator', col='Renovation', palette='seismic',size=4)
2 grid.map(plt.scatter, 'Year', 'Price')
3 grid.add_legend()
画散点图

在Renovation和Elevator的分类条件下,使用 FaceGrid 分析 Year 特征,观察结果如下:

  • 整个二手房房价趋势是随着时间增长而增长的;
  • 2000年以后建造的二手房房价相较于2000年以前有很明显的价格上涨;
  • 1980年之前几乎不存在有电梯二手房数据,说明1980年之前还没有大面积安装电梯;
  • 1980年之前无电梯二手房中,简装二手房占绝大多数,精装反而很少;

Floor 特征分析:

f, ax1= plt.subplots(figsize=(20,5))
sns.countplot(x='Floor', data=df, ax=ax1)
ax1.set_title('房屋户型',fontsize=15)
ax1.set_xlabel('数量')
ax1.set_ylabel('户型')
plt.show()
楼层特征统计图

        可以看到,6层二手房数量最多,但是单独的楼层特征没有什么意义,因为每个小区住房的总楼层数都不一样,我们需要知道楼层的相对意义。另外,楼层与文化也有很重要联系,比如中国文化七上八下,七层可能受欢迎,房价也贵,而一般也不会有4层或18层。当然,正常情况下中间楼层是比较受欢迎的,价格也高,底层和顶层受欢迎度较低,价格也相对较低。所以楼层是一个非常复杂的特征,对房价影响也比较大。

  转摘:https://segmentfault.com/a/1190000015613967

  本篇将继续上一篇数据分析之后进行数据挖掘建模预测,这两部分构成了一个简单的完整项目。结合两篇文章通过数据分析和挖掘的方法可以达到二手房屋价格预测的效果。

  下面从特征工程开始讲述。

二、特征工程

  特征工程包括的内容很多,有特征清洗,预处理,监控等,而预处理根据单一特征或多特征又分很多种方法,如归一化,降维,特征选择,特征筛选等等。这么多的方法,为的是什么呢?其目的是让这些特征更友好的作为模型的输入,处理数据的好坏会严重的影响模型性能,而好的特征工程有的时候甚至比建模调参更重要。

  下面是继上一次分析之后对数据进行的特征工程,博主将一个一个帮大家解读。

 1 """
 2 特征工程
 3 """
 4 # 移除结构类型异常值和房屋大小异常值
 5 df = df[(df['Layout']!='叠拼别墅')&(df['Size']<1000)]
 6 
 7 # 去掉错误数据“南北”,因为爬虫过程中一些信息位置为空,导致“Direction”的特征出现在这里,需要清除或替换
 8 df['Renovation'] = df.loc[(df['Renovation'] != '南北'), 'Renovation']
 9 
10 # 由于存在个别类型错误,如简装和精装,特征值错位,故需要移除
11 df['Elevator'] = df.loc[(df['Elevator'] == '有电梯')|(df['Elevator'] == '无电梯'), 'Elevator']
12 
13 # 填补Elevator缺失值
14 df.loc[(df['Floor']>6)&(df['Elevator'].isnull()), 'Elevator'] = '有电梯'
15 df.loc[(df['Floor']<=6)&(df['Elevator'].isnull()), 'Elevator'] = '无电梯'
16 
17 # 只考虑“室”和“厅”,将其它少数“房间”和“卫”移除
18 df = df.loc[df['Layout'].str.extract('^\d(.*?)\d.*?') == '']
19 
20 # 提取“室”和“厅”创建新特征
21 df['Layout_room_num'] = df['Layout'].str.extract('(^\d).*', expand=False).astype('int64')
22 df['Layout_hall_num'] = df['Layout'].str.extract('^\d.*?(\d).*', expand=False).astype('int64')
23 
24 # 按中位数对“Year”特征进行分箱
25 df['Year'] = pd.qcut(df['Year'],8).astype('object')
26 
27 # 对“Direction”特征
28 d_list_one = ['','西','','']
29 d_list_two = ['东西','东南','东北','西南','西北','南北']
30 d_list_three = ['东西南','东西北','东南北','西南北']
31 d_list_four = ['东西南北']    
32 df['Direction'] = df['Direction'].apply(direct_func)
33 df = df.loc[(df['Direction']!='no')&(df['Direction']!='nan')]
34 
35 # 根据已有特征创建新特征
36 df['Layout_total_num'] = df['Layout_room_num'] + df['Layout_hall_num']
37 df['Size_room_ratio'] = df['Size']/df['Layout_total_num']
38 
39 # 删除无用特征
40 df = df.drop(['Layout','PerPrice','Garden'],axis=1)
41 
42 # 对于object特征进行onehot编码
43 df,df_cat = one_hot_encoder(df)
特征工程-数据处理

由于一些清洗处理在上一篇文章已经提到,所以从17行代码开始。

Layout  先看看没经处理的Layout特征值是什么样的:

1 df['Layout'].value_counts()
显示房型分类统计信息

  大家也都看到了,特征值并不是像想象中的那么理想。有两种格式的数据,一种是"xx室xx厅",另一种是"xx房间xx卫",但是绝大多数都是xx室xx厅的数据。而对于像"11房间3卫"或者"5房间0卫"这些的Layout明显不是民住的二手房(不在我们的考虑范围之内),因此最后决定将所有"xx房间xx卫"格式的数据都移除掉,只保留"xx室xx厅"的数据。

Layout特征的处理如下:

  第2行的意思是只保留"xx室xx厅"数据,但是保留这种格式的数据也是不能作为模型的输入的,我们不如干脆将"室"和"厅"都提取出来,单独作为两个新特征(如第5和6行),这样效果可能更好。

具体的用法就是使用 str.extract() 方法,里面写的是正则表达式。

1 # 只考虑“室”和“厅”,将其它少数“房间”和“卫”移除
2 df = df.loc[df['Layout'].str.extract('^\d(.*?)\d.*?') == '']
3 
4 # 提取“室”和“厅”创建新特征
5 df['Layout_room_num'] = df['Layout'].str.extract('(^\d).*', expand=False).astype('int64')
6 df['Layout_hall_num'] = df['Layout'].str.extract('^\d.*?(\d).*', expand=False).astype('int64')
提取所需要的数据

Year分析:为建房的年限时间。年限种类很多,分布在1950和2018之间,如果每个不同的 Year 值都作为特征值,我们并不能找出 Year 对 Price 有什么影响,因为年限划分的太细了。因此,我们只有将连续数值型特征 Year 离散化,做分箱处理。

如何分箱还要看实际业务需求,博主为了方便并没有手动分箱,而使用了pandas的 qcut 采用中位数进行分割,分割数为8等份。

1 # 按中位数对“Year”特征进行分箱
2 df['Year'] = pd.qcut(df['Year'],8).astype('object')
按year进行分箱

将 Year 进行分箱的结果:

Direction分析:这个特征没处理之前更乱,原以为是爬虫的问题,但是亲自到链家看过,朝向确实是这样的

  如上所见,像"西南西北北"或者"东东南南"这样的朝向是不符合常识的(反正我是理解不了)。因此,我们需要将这些凌乱的数据进行处理,具体实现方式是博主自己写了一个函数 direct_func,主要思想就是将各种重复但顺序不一样的特征值合并,比如"西南北""南西北",并将不合理的一些值移除,如"西南西北北"等。

  然后通过 apply() 方法将 Direction 数据格式转换,代码如下:

1 # 对“Direction”特征
2 d_list_one = ['','西','','']
3 d_list_two = ['东西','东南','东北','西南','西北','南北']
4 d_list_three = ['东西南','东西北','东南北','西南北']
5 d_list_four = ['东西南北']    
6 df['Direction'] = df['Direction'].apply(direct_func)
7 df = df.loc[(df['Direction']!='no')&(df['Direction']!='nan')]
数据转换

处理完结果如下,所有的内容相同而顺序不同的朝向都合并了,异常朝向也被移除了。

 

创建新特征:

  有时候仅靠已有的一些特征是不够的,需要根据对业务的理解,定义一些的新特征,然后尝试这些新特征对模型的影响,在实战中会经常使用这种方法。

这里尝试将"室"与"厅"的数量相加作为一个总数量特征,然后将房屋大小Size与总数量的比值作为一个新特征,可理解为 "每个房间的平均面积大小"。当然,新特征不是固定的,可根据自己的理解来灵活的定义。

1 # 根据已有特征创建新特征
2 df['Layout_total_num'] = df['Layout_room_num'] + df['Layout_hall_num']
3 df['Size_room_ratio'] = df['Size']/df['Layout_total_num']
4 
5 # 删除无用特征
6 df = df.drop(['Layout','PerPrice','Garden'],axis=1)
创建新特征

      最后删除旧的特征 Layout,PerPrice,Garden 

One-hot coding:

  这部分是 One-hot 独热编码,因为像 Region,Year(离散分箱后),Direction,Renovation,Elevator等特征都是定类的非数值型类型,而作为模型的输入我们需要将这些非数值量化。

在没有一定顺序(定序类型)的情况下,使用独热编码处理定类数据是非常常用的做法,在pandas中非常简单,就是使用 get_dummies() 方法,而对于像Size这样的定比数据则不使用独热,博主这里用了一个自己封装的函数实现了定类数据的自动量化处理。

       对于定类,定序,定距,定比这四个非常重要的数据类型相信加入知识星球的伙伴都非常熟悉了,想要了解的同学可以扫描最后二维码查看。

1 # 对于object特征进行onehot编码
2 df,df_cat = one_hot_encoder(df)
one hot 编码

  以上的特征工程就完成了。

特征相关性

  下面使用 seabornheatmap 方法对特征相关性进行可视化。

1 # data_corr 
2 colormap = plt.cm.RdBu
3 plt.figure(figsize=(20,20))
4 # plt.title('Pearson Correlation of Features', y=1.05, size=15)
5 sns.heatmap(df.corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor='white', annot=True)
可视化

颜色偏红或者偏蓝都说明相关系数较大,即两个特征对于目标变量的影响程度相似,即存在严重的重复信息,会造成过拟合现象。因此,通过特征相关性分析,我们可以找出哪些特征有严重的重叠信息,然后择优选择。

三、数据建模预测

为了方便理解,博主在建模上做了一些精简,模型策略方法如下:

  • 使用Cart决策树的回归模型对二手房房价进行分析预测
  • 使用交叉验证方法充分利用数据集进行训练,避免数据划分不均匀的影响。
  • 使用GridSearchCV方法优化模型参数
  • 使用R2评分方法对模型预测评分

上面的建模方法比较简单,旨在让大家了解建模分析的过程。随着逐渐的深入了解,博主会介绍更多实战内容。

  数据划分

1 # 转换训练测试集格式为数组
2 features = np.array(features)
3 prices = np.array(prices)
4 
5 # 导入sklearn进行训练测试集划分
6 from sklearn.model_selection import train_test_split
7 features_train, features_test, prices_train, prices_test = train_test_split(features, prices, test_size=0.2, random_state=0)
训练测试集

  将以上数据划分为训练集和测试集,训练集用于建立模型,测试集用于测试模型预测准确率。使用sklearn的 model_selection 实现以上划分功能。

   建立模型

 1 from sklearn.model_selection import KFold
 2 from sklearn.tree import DecisionTreeRegressor
 3 from sklearn.metrics import make_scorer
 4 from sklearn.model_selection import GridSearchCV
 5 
 6 # 利用GridSearchCV计算最优解
 7 def fit_model(X, y):
 8     """ 基于输入数据 [X,y],利于网格搜索找到最优的决策树模型"""
 9     
10     cross_validator = KFold(10, shuffle=True)
11     regressor = DecisionTreeRegressor()
12     
13     params = {'max_depth':[1,2,3,4,5,6,7,8,9,10]}
14     scoring_fnc = make_scorer(performance_metric)
15     grid = GridSearchCV(estimator = regressor, param_grid = params, scoring = scoring_fnc, cv = cross_validator)
16 
17     # 基于输入数据 [X,y],进行网格搜索
18     grid = grid.fit(X, y)
19 #     print pd.DataFrame(grid.cv_results_)
20     return grid.best_estimator_
21 
22 # 计算R2分数
23 def performance_metric(y_true, y_predict):
24     """计算并返回预测值相比于预测值的分数"""
25     from sklearn.metrics import r2_score
26     score = r2_score(y_true, y_predict)
27 
28     return score
mode

  使用了 KFold 方法减缓过拟合,GridSearchCV 方法进行最优参数自动搜查,最后使用R2评分来给模型打分。

  调参优化模型

 1 import visuals as vs
 2 
 3 # 分析模型
 4 vs.ModelLearning(features_train, prices_train)
 5 vs.ModelComplexity(features_train, prices_train)
 6 
 7 optimal_reg1 = fit_model(features_train, prices_train)
 8 
 9 # 输出最优模型的 'max_depth' 参数
10 print("最理想模型的参数 'max_depth' 是 {} 。".format(optimal_reg1.get_params()['max_depth']))
11 
12 predicted_value = optimal_reg1.predict(features_test)
13 r2 = performance_metric(prices_test, predicted_value)
14 
15 print("最优模型在测试数据上 R^2 分数 {:,.2f}。".format(r2))
参数调优

  由于决策树容易过拟合的问题,我们这里采取观察学习曲线的方法查看决策树深度,并判断模型是否出现了过拟合现象。以下是观察到的学习曲线图形:

  通过观察,最理想模型的参数"max_depth"是10,此种情况下达到了偏差与方差的最优平衡,最后模型在测试数据上的R2分数,也即二手房房价预测的准确率为:0.81

转摘:https://www.cnblogs.com/palace/p/9598256.html

1. 引言

Pandas是一个开源的Python数据分析库。Pandas把结构化数据分为了三类:

  • Series,1维序列,可视作为没有column名的、只有一个column的DataFrame;

  • DataFrame,同Spark SQL中的DataFrame一样,其概念来自于R语言,为多column并schema化的2维结构化数据,可视作为Series的容器(container);

  • Panel,为3维的结构化数据,可视作为DataFrame的容器;

DataFrame较为常见,因此本文主要讨论内容将为DataFrame。DataFrame的生成可通过读取纯文本、Json等数据来生成,亦可以通过Python对象来生成:

import pandas as pd import numpy as np df = pd.DataFrame({'total_bill': [16.99, 10.34, 23.68, 23.68, 24.59], 'tip': [1.01, 1.66, 3.50, 3.31, 3.61], 'sex': ['Female', 'Male', 'Male', 'Male', 'Female']})

对于DataFrame,我们可以看到其固有属性:

# data type of columns
print df.dtypes # indexes print df.index # return pandas.Index print df.columns # each row, return array[array] print df.values
  • .index,为行索引

  • .columns,为列名称(label)

  • .dtype,为列数据类型

2. SQL操作

官方Doc给出了部分SQL的Pandas实现。在此基础上,本文给出了一些扩充说明。以下内容基于Python 2.7 + Pandas 0.18.1的版本。

select

SQL中的select是根据列的名称来选取;Pandas则更为灵活,不但可根据列名称选取,还可以根据列所在的position选取。相关函数如下:

  • loc,基于列label,可选取特定行(根据行index);

  • iloc,基于行/列的position;

print df.loc[1:3, ['total_bill', 'tip']] print df.loc[1:3, 'tip': 'total_bill'] print df.iloc[1:3, [1, 2]] print df.iloc[1:3, 1: 3]
  • at,根据指定行index及列label,快速定位DataFrame的元素;

  • iat,与at类似,不同的是根据position来定位的;

print df.at[3, 'tip'] print df.iat[3, 1]
  • ix,为loc与iloc的混合体,既支持label也支持position;

print df.ix[1:3, [1, 2]] print df.ix[1:3, ['total_bill', 'tip']]

此外,有更为简洁的行/列选取方式:

print df[1: 3] print df[['total_bill', 'tip']] # print df[1:2, ['total_bill', 'tip']] # TypeError: unhashable type

where

Pandas实现where filter,较为常用的办法为df[df[colunm] boolean expr],比如:

print df[df['sex'] == 'Female'] print df[df['total_bill'] > 20] # or print df.query('total_bill > 20')

在where子句中常常会搭配and, or, in, not关键词,Pandas中也有对应的实现:

# and
print df[(df['sex'] == 'Female') & (df['total_bill'] > 20)] # or print df[(df['sex'] == 'Female') | (df['total_bill'] > 20)] # in print df[df['total_bill'].isin([21.01, 23.68, 24.59])] # not print df[-(df['sex'] == 'Male')] print df[-df['total_bill'].isin([21.01, 23.68, 24.59])] # string function print df = df[(-df['app'].isin(sys_app)) & (-df.app.str.contains('^微信\d+$'))]

对where条件筛选后只有一行的dataframe取其中某一列的值,其两种实现方式如下:

total = df.loc[df['tip'] == 1.66, 'total_bill'].values[0] total = df.get_value(df.loc[df['tip'] == 1.66].index.values[0], 'total_bill')

distinct

drop_duplicates根据某列对dataframe进行去重:

df.drop_duplicates(subset=['sex'], keep='first', inplace=True)

包含参数:

  • subset,为选定的列做distinct,默认为所有列;

  • keep,值选项{'first', 'last', False},保留重复元素中的第一个、最后一个,或全部删除;

  • inplace ,默认为False,返回一个新的dataframe;若为True,则返回去重后的原dataframe

group

group一般会配合合计函数(Aggregate functions)使用,比如:count、avg等。Pandas对合计函数的支持有限,有count和size函数实现SQL的count:

print df.groupby('sex').size() print df.groupby('sex').count() print df.groupby('sex')['tip'].count()

对于多合计函数,

select sex, max(tip), sum(total_bill) as total from tips_tb group by sex;

实现在agg()中指定dict:

print df.groupby('sex').agg({'tip': np.max, 'total_bill': np.sum}) # count(distinct **) print df.groupby('tip').agg({'sex': pd.Series.nunique})

as

SQL中使用as修改列的别名,Pandas也支持这种修改:

# first implementation
df.columns = ['total', 'pit', 'xes'] # second implementation df.rename(columns={'total_bill': 'total', 'tip': 'pit', 'sex': 'xes'}, inplace=True)

其中,第一种方法的修改是有问题的,因为其是按照列position逐一替换的。因此,我推荐第二种方法。

join

Pandas中join的实现也有两种:

# 1.
df.join(df2, how='left'...) # 2.  pd.merge(df1, df2, how='left', left_on='app', right_on='app')

第一种方法是按DataFrame的index进行join的,而第二种方法才是按on指定的列做join。Pandas满足left、right、inner、full outer四种join方式。

order

Pandas中支持多列order,并可以调整不同列的升序/降序,有更高的排序自由度:

print df.sort_values(['total_bill', 'tip'], ascending=[False, True])

top

对于全局的top:

print df.nlargest(3, columns=['total_bill'])

对于分组top,MySQL的实现(采用自join的方式):

select a.sex, a.tip
from tips_tb a where ( select count(*) from tips_tb b where b.sex = a.sex and b.tip > a.tip ) < 2 order by a.sex, a.tip desc;

Pandas的等价实现,思路与上类似:

# 1.
df.assign(rn=df.sort_values(['total_bill'], ascending=False) .groupby('sex') .cumcount()+1)\ .query('rn < 3')\ .sort_values(['sex', 'rn']) # 2. df.assign(rn=df.groupby('sex')['total_bill'] .rank(method='first', ascending=False)) \ .query('rn < 3') \ .sort_values(['sex', 'rn'])

replace

replace函数提供对dataframe全局修改,亦可通过where条件进行过滤修改(搭配loc):

# overall replace
df.replace(to_replace='Female', value='Sansa', inplace=True) # dict replace df.replace({'sex': {'Female': 'Sansa', 'Male': 'Leone'}}, inplace=True) # replace on where condition  df.loc[df.sex == 'Male', 'sex'] = 'Leone'

自定义

除了上述SQL操作外,Pandas提供对每列/每一元素做自定义操作,为此而设计以下三个函数:

  • map(func),为Series的函数,DataFrame不能直接调用,需取列后再调用;

  • apply(func),对DataFrame中的某一行/列进行func操作;

  • applymap(func),为element-wise函数,对每一个元素做func操作

print df['tip'].map(lambda x: x - 1) print df[['total_bill', 'tip']].apply(sum) print df.applymap(lambda x: x.upper() if type(x) is str else x)

3. 实战

环比增长

现有两个月APP的UV数据,要得到月UV环比增长;该操作等价于两个Dataframe left join后按指定列做减操作:

def chain(current, last): df1 = pd.read_csv(current, names=['app', 'tag', 'uv'], sep='\t') df2 = pd.read_csv(last, names=['app', 'tag', 'uv'], sep='\t') df3 = pd.merge(df1, df2, how='left', on='app') df3['uv_y'] = df3['uv_y'].map(lambda x: 0.0 if pd.isnull(x) else x) df3['growth'] = df3['uv_x'] - df3['uv_y'] return df3[['app', 'growth', 'uv_x', 'uv_y']].sort_values(by='growth', ascending=False)

差集

对于给定的列,一个Dataframe过滤另一个Dataframe该列的值;相当于集合的差集操作:

def difference(left, right, on): """  difference of two dataframes  :param left: left dataframe  :param right: right dataframe  :param on: join key  :return: difference dataframe  """ df = pd.merge(left, right, how='left', on=on) left_columns = left.columns col_y = df.columns[left_columns.size] df = df[df[col_y].isnull()] df = df.ix[:, 0:left_columns.size] df.columns = left_columns return df

1. 引言

Pandas是一个开源的Python数据分析库。Pandas把结构化数据分为了三类:

  • Series,1维序列,可视作为没有column名的、只有一个column的DataFrame;

  • DataFrame,同Spark SQL中的DataFrame一样,其概念来自于R语言,为多column并schema化的2维结构化数据,可视作为Series的容器(container);

  • Panel,为3维的结构化数据,可视作为DataFrame的容器;

DataFrame较为常见,因此本文主要讨论内容将为DataFrame。DataFrame的生成可通过读取纯文本、Json等数据来生成,亦可以通过Python对象来生成:

import pandas as pd import numpy as np df = pd.DataFrame({'total_bill': [16.99, 10.34, 23.68, 23.68, 24.59], 'tip': [1.01, 1.66, 3.50, 3.31, 3.61], 'sex': ['Female', 'Male', 'Male', 'Male', 'Female']})

对于DataFrame,我们可以看到其固有属性:

# data type of columns
print df.dtypes # indexes print df.index # return pandas.Index print df.columns # each row, return array[array] print df.values
  • .index,为行索引

  • .columns,为列名称(label)

  • .dtype,为列数据类型

2. SQL操作

官方Doc给出了部分SQL的Pandas实现。在此基础上,本文给出了一些扩充说明。以下内容基于Python 2.7 + Pandas 0.18.1的版本。

select

SQL中的select是根据列的名称来选取;Pandas则更为灵活,不但可根据列名称选取,还可以根据列所在的position选取。相关函数如下:

  • loc,基于列label,可选取特定行(根据行index);

  • iloc,基于行/列的position;

print df.loc[1:3, ['total_bill', 'tip']] print df.loc[1:3, 'tip': 'total_bill'] print df.iloc[1:3, [1, 2]] print df.iloc[1:3, 1: 3]
  • at,根据指定行index及列label,快速定位DataFrame的元素;

  • iat,与at类似,不同的是根据position来定位的;

print df.at[3, 'tip'] print df.iat[3, 1]
  • ix,为loc与iloc的混合体,既支持label也支持position;

print df.ix[1:3, [1, 2]] print df.ix[1:3, ['total_bill', 'tip']]

此外,有更为简洁的行/列选取方式:

print df[1: 3] print df[['total_bill', 'tip']] # print df[1:2, ['total_bill', 'tip']] # TypeError: unhashable type

where

Pandas实现where filter,较为常用的办法为df[df[colunm] boolean expr],比如:

print df[df['sex'] == 'Female'] print df[df['total_bill'] > 20] # or print df.query('total_bill > 20')

在where子句中常常会搭配and, or, in, not关键词,Pandas中也有对应的实现:

# and
print df[(df['sex'] == 'Female') & (df['total_bill'] > 20)] # or print df[(df['sex'] == 'Female') | (df['total_bill'] > 20)] # in print df[df['total_bill'].isin([21.01, 23.68, 24.59])] # not print df[-(df['sex'] == 'Male')] print df[-df['total_bill'].isin([21.01, 23.68, 24.59])] # string function print df = df[(-df['app'].isin(sys_app)) & (-df.app.str.contains('^微信\d+$'))]

对where条件筛选后只有一行的dataframe取其中某一列的值,其两种实现方式如下:

total = df.loc[df['tip'] == 1.66, 'total_bill'].values[0] total = df.get_value(df.loc[df['tip'] == 1.66].index.values[0], 'total_bill')

distinct

drop_duplicates根据某列对dataframe进行去重:

df.drop_duplicates(subset=['sex'], keep='first', inplace=True)

包含参数:

  • subset,为选定的列做distinct,默认为所有列;

  • keep,值选项{'first', 'last', False},保留重复元素中的第一个、最后一个,或全部删除;

  • inplace ,默认为False,返回一个新的dataframe;若为True,则返回去重后的原dataframe

group

group一般会配合合计函数(Aggregate functions)使用,比如:count、avg等。Pandas对合计函数的支持有限,有count和size函数实现SQL的count:

print df.groupby('sex').size() print df.groupby('sex').count() print df.groupby('sex')['tip'].count()

对于多合计函数,

select sex, max(tip), sum(total_bill) as total from tips_tb group by sex;

实现在agg()中指定dict:

print df.groupby('sex').agg({'tip': np.max, 'total_bill': np.sum}) # count(distinct **) print df.groupby('tip').agg({'sex': pd.Series.nunique})

as

SQL中使用as修改列的别名,Pandas也支持这种修改:

# first implementation
df.columns = ['total', 'pit', 'xes'] # second implementation df.rename(columns={'total_bill': 'total', 'tip': 'pit', 'sex': 'xes'}, inplace=True)

其中,第一种方法的修改是有问题的,因为其是按照列position逐一替换的。因此,我推荐第二种方法。

join

Pandas中join的实现也有两种:

# 1.
df.join(df2, how='left'...) # 2.  pd.merge(df1, df2, how='left', left_on='app', right_on='app')

第一种方法是按DataFrame的index进行join的,而第二种方法才是按on指定的列做join。Pandas满足left、right、inner、full outer四种join方式。

order

Pandas中支持多列order,并可以调整不同列的升序/降序,有更高的排序自由度:

print df.sort_values(['total_bill', 'tip'], ascending=[False, True])

top

对于全局的top:

print df.nlargest(3, columns=['total_bill'])

对于分组top,MySQL的实现(采用自join的方式):

select a.sex, a.tip
from tips_tb a where ( select count(*) from tips_tb b where b.sex = a.sex and b.tip > a.tip ) < 2 order by a.sex, a.tip desc;

Pandas的等价实现,思路与上类似:

# 1.
df.assign(rn=df.sort_values(['total_bill'], ascending=False) .groupby('sex') .cumcount()+1)\ .query('rn < 3')\ .sort_values(['sex', 'rn']) # 2. df.assign(rn=df.groupby('sex')['total_bill'] .rank(method='first', ascending=False)) \ .query('rn < 3') \ .sort_values(['sex', 'rn'])

replace

replace函数提供对dataframe全局修改,亦可通过where条件进行过滤修改(搭配loc):

# overall replace
df.replace(to_replace='Female', value='Sansa', inplace=True) # dict replace df.replace({'sex': {'Female': 'Sansa', 'Male': 'Leone'}}, inplace=True) # replace on where condition  df.loc[df.sex == 'Male', 'sex'] = 'Leone'

自定义

除了上述SQL操作外,Pandas提供对每列/每一元素做自定义操作,为此而设计以下三个函数:

  • map(func),为Series的函数,DataFrame不能直接调用,需取列后再调用;

  • apply(func),对DataFrame中的某一行/列进行func操作;

  • applymap(func),为element-wise函数,对每一个元素做func操作

print df['tip'].map(lambda x: x - 1) print df[['total_bill', 'tip']].apply(sum) print df.applymap(lambda x: x.upper() if type(x) is str else x)

3. 实战

环比增长

现有两个月APP的UV数据,要得到月UV环比增长;该操作等价于两个Dataframe left join后按指定列做减操作:

def chain(current, last): df1 = pd.read_csv(current, names=['app', 'tag', 'uv'], sep='\t') df2 = pd.read_csv(last, names=['app', 'tag', 'uv'], sep='\t') df3 = pd.merge(df1, df2, how='left', on='app') df3['uv_y'] = df3['uv_y'].map(lambda x: 0.0 if pd.isnull(x) else x) df3['growth'] = df3['uv_x'] - df3['uv_y'] return df3[['app', 'growth', 'uv_x', 'uv_y']].sort_values(by='growth', ascending=False)

差集

对于给定的列,一个Dataframe过滤另一个Dataframe该列的值;相当于集合的差集操作:

def difference(left, right, on): """  difference of two dataframes  :param left: left dataframe  :param right: right dataframe  :param on: join key  :return: difference dataframe  """ df = pd.merge(left, right, how='left', on=on) left_columns = left.columns col_y = df.columns[left_columns.size] df = df[df[col_y].isnull()] df = df.ix[:, 0:left_columns.size] df.columns = left_columns return df

猜你喜欢

转载自www.cnblogs.com/abdm-989/p/12129135.html