前言
最近在自学Python,通过学习大家的分享案例,看到使用Python进行较多的主要4个方面:爬虫,数据处理,数据可视化以及机器学习建模。对我来说目标就是:
- 熟练使用numpy pandas 进行数据清洗和预处理;
- 熟练使用pandas进行数据统计;
- 熟练使用matplotlib seaborn进行数据可视化;
- 了解机器学习常用算法,并能够做一个项目。
我将1-3归为Phase1,也就是这篇文章的主体内容。本篇以模仿为主,俗话说输出是最好的学习方式。希望通过这篇文章可以增加自己对Python基础的熟练程度,内化常规的数据分析思路。
分析思路
分析目标:查看北京二手居民住房的分布价格情况Part 1- 数据读取和预处理
理解变量、数据选取、重复值缺失值处理Part 2 - 北京市房源分布
数量、单价、总价Part 3 - 各城区房源分布Part 4 - 各城区房价分布
单价分布、总价分布、高价Top15小区、低价Top15小区Part 5 - 各城区房源面积分布
全市平均面积分布、各城区平均面积分布、各城区总面积分布Part 6 - 房价与房源特性的关系
房价与户型、楼层、朝向、建筑年代的关系
Part 1 - 数据读取和预处理
- 获取数据
本篇使用参考资料文章3的数据:https://pan.baidu.com/s/1_n3TkvESgOXfl5TUoqlk7w。
#导入常用包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns#导入数据
filepath='./lianjia_beijing.csv'
rawdata=pd.read_csv(filepath,header=0)#查看数据
rawdata.head()
可以看到一共有12个变量,包括:
- Direction: 房屋朝向;
- Region/District/Gadern: 城区/街道/小区地址或者名称
- Id: 链家编码
- Elevator: 楼是否有电梯
- Floor:楼层
- Layout: 房屋户型
- Renovation: 装修情况
- Size: 房屋大小,单位平米
- Year: 房屋建筑年代
- Price: 房屋总价
2. 查看缺失值以及变量类型
rawdata.info()
可以看到-
- 变量类型都符合预期,其中数值型变量的类型均为int64,不需要进行进一步处理;
- 只有Elevator有缺失值,并且缺失数量并不少,之后需要进一步处理。
3. 查看重复值
在查看重复值时,发现真的有,但是打印出来查看的时候没有发现真的重复;没有找到原因,但是还是将重复的删掉了。
4. 处理缺失值
发现有6种情况:‘NaN’'无电梯', '有电梯', '毛坯', '精装', '简装';因为数据抓取时有串行,导致数据不正确,所以删除 '毛坯', '精装', '简装';同时考虑楼房6层以下的无电梯,高层有电梯对缺失值进行填补,不过考虑Floor显示的只是此房源楼层而不是小区楼层,可能有误差。分析结果应该谨慎参考。
#缺失值处理
df.loc[(df['Elevator'].isnull())&(df['Floor']>6),'Elevator']='有电梯'
df.loc[(df['Elevator'].isnull())&(df['Floor']<=6),'Elevator']='无电梯'
del_row=df[(df['Elevator'] == '毛坯')|(df['Elevator'] == '简装')|(df['Elevator'] == '精装')].index.tolist()
len(del_row)
df=df.drop(del_row).reset_index(drop=True)
df.info()
5. 查看数据的一般描述统计值
df.describe()
可以看到:
- 楼层分布在1到57层,75%集中在20层以下;
- Id没有实际意义,可以去掉;
- 每套房子总价在60W-6000W之间;75%价格小于710万,所以6000W有些异常;
- 房子面积在15平-1019平之间;75%的面积小于118平,所以最高面积1019平也有些异常;
- 房子建造年代从1950到2017年。
6. 异常值处理
考虑要分析普通居民住宅,所以具体查看房屋面积进行处理。
#查看区域面积分布
f, ax1=plt.subplots(1,figsize=(15,10))
sns.boxplot(x="Region",y="Size",data=df,ax=ax1)
ax1.set_title("北京各大区二手房面积分布")
ax1.set_xlabel('区域')
ax1.set_ylabel('二手房面积')
plt.xticks(fontsize=9)
plt.show()
可以看到:
- 怀柔的房屋面积分布范围明显比其他城区广,具体查看数据,的确是建筑的居民楼面积较大。考虑其地理位置,数据情况和实际相符;
- 面积小于20的查看了,无明显异常;
- 面积大于800的几个房源很明显。单独查看发现其中“新华联科技大厦”1房间0卫有1019平,明显不是居民住宅,需要删除;同时考虑“X房间0卫”也看起来也不像是居民住宅;
进一步查看,户型分布,从逻辑上看是否有需要删除的异常值。发现有“X房间0卫”和“X室0厅”有很多;分布查看数据后发现“X室0厅”符合居民住宅情况,最后决定删除所有的“X房间0卫”。
#户型面积分布
f, ax1=plt.subplots(1,figsize=(10,10))
sns.boxplot(y="Layout",x="Size",data=df,ax=ax1)
ax1.set_title("北京二手房户型面积分布")
ax1.set_xlabel('户型')
ax1.set_ylabel('二手房面积')
plt.xticks(fontsize=9)
plt.show()
#找到需要删除的行
del_list=df.loc[(df['Layout']=='5房间0卫')|(df['Layout']=='3房间0卫')|(df['Layout']=='2房间0卫')|(df['Layout']=='1房间0卫')].index.tolist()
del_list
df=df.drop(del_list) #删除x房间0卫
7. 变量选取
- 删除变量“Id”因为没有实际意义;
- 增加每平米单价“PerPrice”便于之后分析;
- 重新设置变量位置,方便查看。
#选取子集
df=df.drop(['Id'],axis=1).reset_index(drop=True)
# 添加新特征房屋均价
df['PerPrice'] =df['Price']/df['Size']
# 重新摆放列位置
columns = ['Region', 'District', 'Garden', 'Layout', 'Floor', 'Year', 'Size', 'Elevator', 'Direction', 'Renovation', 'PerPrice', 'Price']
df = pd.DataFrame(df, columns = columns)
df.head()
df.describe()
Part 2 - 北京市房源分布
# 北京二手房分布情况
%matplotlib inline
sns.set_style({'font.sans-serif':['simhei','Arial']})
f, [ax1,ax2,ax3] = plt.subplots(3,1, figsize=(15, 5))
sns.distplot(df['Size'], bins=30, ax=ax1, color='r')
sns.kdeplot(df['Size'], shade=True, ax=ax1)
sns.distplot(df['Price'], bins=30, ax=ax2, color='r')
sns.kdeplot(df['Price'], shade=True, ax=ax2)
sns.distplot(df['PerPrice'], bins=30, ax=ax3, color='r')
sns.kdeplot(df['PerPrice'], shade=True, ax=ax3)
plt.show()
- 房间面积集中在0-200平以内,更大面积的房源面积变化范围大但数量很少;
- 二手房总价集中在1000W以内;
- 二手房每平米均价在4W左右最多,但均价的分布明显更加分散从3W-10W的房源数量都不算少。
Part 3 - 各城区房源分布
# 对二手房区域分组对比二手房数量
df_house_count = df.groupby('Region')['Price'].count().sort_values(ascending=False).to_frame().reset_index()#正确显示中文以及负号
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
sns.set_style({'font.sans-serif':['SimHei','Arial']})f,ax=plt.subplots(1,1,figsize=(20,10))
sns.barplot(x='Region', y='Price',palette="Greens_d", data=df_house_count)
ax.set_title('北京各大区二手房数量对比')
ax.set_xlabel('区域')
ax.set_ylabel('数量')
plt.xticks(fontsize=9)#减小字体以免变成方框
plt.show()
- 丰台、海淀、朝阳和昌平数量相近,在第一梯队;西城和大兴数量相近,在第二梯队;平谷、怀柔和密云数量非常少;
Part 4 - 各城区房价分布
- 各区域总价以及每平米均价分布
#各城区房平均总价分布
df_price_mean=df.groupby("Region")["Price"].mean().sort_values(ascending=False).to_frame().reset_index()
f,[ax1,ax2]=plt.subplots(2,1,figsize=(20,15))
sns.barplot(x="Region",y="Price",palette="Blues_d",data=df_price_mean,ax=ax1)
ax1.set_title("北京各大区二手房总价对比")
ax1.set_xlabel('区域')
ax1.set_ylabel('总价(区域均值)')sns.boxplot(x="Region",y="Price",data=df,ax=ax2)
ax2.set_title("北京各大区二手房总价分布")
ax2.set_xlabel('区域')
ax2.set_ylabel('总价')
plt.xticks(fontsize=9)
plt.show()
#各城区房每平米单价分布
df_perprice_mean=df.groupby("Region")["PerPrice"].mean().sort_values(ascending=False).to_frame().reset_index()
f,[ax1,ax2]=plt.subplots(2,1,figsize=(20,15))
sns.barplot(x="Region",y="PerPrice",palette="Blues_d",data=df_perprice_mean,ax=ax1)
ax1.set_title("北京各大区二手房每平米单价对比")
ax1.set_xlabel('区域')
ax1.set_ylabel('每平米单价')sns.boxplot(x="Region",y="PerPrice",palette="Blues_d",data=df,ax=ax2)
ax2.set_title("北京各大区二手房每平米单价分布")
ax2.set_xlabel('区域')
ax2.set_ylabel('每平米单价')
plt.xticks(fontsize=9)
plt.show()
- 东西城、海淀、怀柔和朝阳的平均每套二手房总价相近,在第一梯队;除怀柔外,每平米单价也保持和总价一样的排名;怀柔的均价并不在第一梯队,结合之前的房屋面积,因为怀柔二手房的房屋面积较大,造成总价较高;
- 东西城、海淀和朝阳的二手房总价分布也非常接近,怀柔的价格分布范围较广;
- 平谷、密云、门头沟和房山4个区域的房子总价和单价都很低,尤其是平谷和密云;为什么怀柔的二手房情况要远远好于它们,有待考证。
2. 房价Top15小区
#查看房屋总价最贵的小区
totalp_village = df.groupby(['Garden','Region'])['Price'].mean().sort_values(ascending = False).reset_index().head(15)
totalp_village
#城区分布
Topdis_check=totalp_village.groupby('Region')['Price'].count().sort_values(ascending = False).reset_index()
Topdis_check
#查看单价最贵的小区
Perp_village = df.groupby(['Garden','Region'])['PerPrice'].mean().sort_values(ascending = False).reset_index().head(15)
Perp_village
#对应城区分布
Perdis_check=Perp_village.groupby('Region')['PerPrice'].count().sort_values(ascending = False).reset_index()
Perdis_check
- Top结果与区域价格分析一致,贵的小区集中在西城东城,如果看总价的话,还有城六区外的顺义和昌平有小区入选。
Part 5 - 各区域房屋面积分布
按照前面对于Size的查看,以区间[0,50)、[50,100)、[100,150)、[150,200)、[200,+∞)为划分标准,将面积划分为Mini small、small、medium、big、huge五个等级,分别对应极小户型、小户型、中等户型、大户型和巨大户型。
df.loc[(df['Size']>=0)&(df['Size']<50),'Size_level']="Mini Small"
df.loc[(df['Size']>=50)&(df['Size']<100),'Size_level']="Small"
df.loc[(df['Size']>=100)&(df['Size']<150),'Size_level']="Mediumn"
df.loc[(df['Size']>=150)&(df['Size']<200),'Size_level']="Big"
df.loc[(df['Size']>=200),'Size_level']="Huge"
df_sizelevel_count=df.groupby('Size_level')['Price'].count().sort_values(ascending=False).to_frame().reset_index()# 对二手房面积分类后对比二手房数量,总价和每平米房价
df_house_count1 = df.groupby('Size_level')['Price'].count().sort_values(ascending=False).to_frame().reset_index()
df_house_mean1 = df.groupby('Size_level')['PerPrice'].mean().sort_values(ascending=False).to_frame().reset_index()f, [ax1,ax2,ax3] = plt.subplots(1,3,figsize=(15,5))sns.barplot(x='Size_level', y='Price', palette="Greens_d", data=df_house_count1, ax=ax1)
ax1.set_title('北京各类别二手房数量对比')
ax1.set_xlabel('类别')
ax1.set_ylabel('数量')
sns.boxplot(x='Size_level', y='Price', data=df, ax=ax2)
ax2.set_title('北京类别二手房房屋总价')
ax2.set_xlabel('类别')
ax2.set_ylabel('房屋总价')
sns.barplot(x='Size_level', y='PerPrice', palette="Blues_d", data=df_house_mean1, ax=ax3)
ax3.set_title('北京各类别二手房每平米单价对比')
ax3.set_xlabel('类别')
ax3.set_ylabel('每平米单价')
plt.show()
- 市场上最多的二手房面积在[50,100)内,[100,150)次之;
- 从房屋总价来看,超小户型<小户型<中等户型,并且三类价格比较集中;
- 从每平米单价来看,超小户型最高,其余几类差别不算太大,结合房屋总价,可能跟供需关系有关,总价低的市场需求量大,而相对供给量较低。
Part 6 - 房价与房源特性的关系
- 房价与户型分布
考虑之前的户型非常多,写法不一,根据全部的分布情况,只选取数量大于10套的户型进行可视化查看。因为户型分类较多,没有看到特别明显的和房价的关系。
#房每平米均价/数量和户型
df_layout_count1=df.groupby('Layout')['PerPrice'].count().sort_values(ascending=False).to_frame().reset_index().head(26)
sel_layout=df_layout_count1['Layout'].tolist()
df2=df.loc[df['Layout'].isin(sel_layout)]
df_layout_perprice1=df2.groupby('Layout')['PerPrice'].mean().sort_values(ascending=False).to_frame().reset_index().head(26)
plt.figure(figsize=(15,5))
x=np.arange(df_layout_perprice1.shape[0])
plt.bar(x*3+1, df_layout_perprice1['PerPrice'])
plt.title('房屋户型均价')
plt.xticks(x*3+1.5,df_layout_perprice1['Layout'],fontsize=8)
plt.xlabel('房屋户型')
plt.ylabel('均价')
plt.show()
2. 房价与朝向分布
统计发现朝向的写法非常乱,所以选取朝向套数>100的进行可视化查看。发现房屋朝向与房屋价格并没有什么明显的关系。
#房均价/数量和朝向
sel_direct=df['Direction'].value_counts().to_frame()
sel_direct_list=sel_direct.loc[sel_direct['Direction']>100].index.tolist()
df_direct=df.loc[df['Direction'].isin(sel_direct_list)]
df_direct['Direction'].value_counts()# 对二手房朝向
df_house_count2 = df_direct.groupby('Direction')['Price'].count().sort_values(ascending=False).to_frame().reset_index()
df_house_mean2 = df_direct.groupby('Direction')['PerPrice'].mean().sort_values(ascending=False).to_frame().reset_index()f, [ax1,ax2,ax3] = plt.subplots(3,1,figsize=(10,15))sns.barplot(x='Direction', y='Price', palette="Greens_d", data=df_house_count2, ax=ax1)
ax1.set_title('北京各朝向二手房数量对比')
ax1.set_xlabel('朝向')
ax1.set_ylabel('数量')
sns.boxplot(x='Direction', y='Price', data=df_direct, ax=ax2)
ax2.set_title('北京各朝向二手房房屋总价')
ax2.set_xlabel('朝向')
ax2.set_ylabel('房屋总价')
sns.barplot(x='Direction', y='PerPrice', palette="Blues_d", data=df_house_mean2, ax=ax3)
ax3.set_title('北京各朝向二手房每平米单价对比')
ax3.set_xlabel('朝向')
ax3.set_ylabel('每平米单价')
plt.show()
3. 房价与装修分布
- 在售二手房以精装和简装为主;
- 毛坯房数量虽少,却在每平单价和总价上均超过了精装房。查看毛坯房源分布的区域和面积都很分散,没有明显特征,原因有待考证。
#房均价/数量和装修
df['Renovation'].value_counts()
# 画幅设置
f, [ax1,ax2,ax3] = plt.subplots(1, 3, figsize=(15, 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()
4. 房价与楼层
- 6层房源最多;
- 1层、42和57层的每平米单价明显较高;其余层差异不大;
- 2层、3层的房屋总价分布与每平米单价表现不一致,平均总价明显高于其他楼层。
#房均价/数量和楼层
df['Floor'].value_counts()f, [ax1,ax2,ax3] = plt.subplots(3, 1, figsize=(20, 10))
sns.countplot(df['Floor'], ax=ax1)
sns.barplot(x='Floor', y='PerPrice', data=df, ax=ax2)
sns.boxplot(x='Floor', y='Price', data=df, ax=ax3)
plt.show()
5. 房价与电梯
电梯的数值在前面进行过填充,此处显示有电梯的房源数量和总价都高于没有电梯的,符合常识。但是填充的结果是否正确,也有待考证。
#房均价/数量和有无电梯
f, [ax1,ax2] = plt.subplots(1, 2, figsize=(20, 10))
sns.countplot(df['Elevator'], ax=ax1)
ax1.set_title('有无电梯数量对比',fontsize=15)
ax1.set_xlabel('是否有电梯')
ax1.set_ylabel('数量')
sns.barplot(x='Elevator', y='Price', data=df, ax=ax2)
ax2.set_title('有无电梯房价对比',fontsize=15)
ax2.set_xlabel('是否有电梯')
ax2.set_ylabel('总价')
plt.show()
6. 房价与建筑年限
y1=df.groupby('Year').Price.mean().reset_index()
y2=df.groupby('Year').PerPrice.count().reset_index()
x=y1.Year
plt.plot(x,y1.Price,label='Total Price')
plt.plot(x,y2.PerPrice,'r--',label='Total Number')
plt.legend(loc='best')
plt.show()
plt.figure(figsize=(15,5))
df.groupby('Year').PerPrice.mean().plot()
plt.title('建筑时间-每平米单价')
#不同城区的建筑情况
plt.figure(figsize=(15,10))
sns.swarmplot(x="Year",y="Region",data=df)
plt.title('建筑时间-数量')
plt.show()
- 2000-2010年10年间北京大规模增加住宅,房屋总价同时增长,但整体房屋单价下降;结合不同年限各区域的住宅分布,可知这段实际增加了非内城区的其他区域的住宅,所以单价没有增加;
- 西城的住宅建设开始的最早,1980年之后各大城区才开始建设;建设时间和城区位置相符。
小结
通过分析可以看到,二手房市场的房源数量和房价表现出的北京特征非常明显:
- 中心城区东西城和学区房海淀朝阳房源多同时房价高;
- 房源面积集中在50-150平之内,总价均值在500-600万,<50平的房源因为房屋数量少和总价低,反而单价最高;
- 房屋的建筑时间和北京各区域发展的时间一致;
- 房价更多与区域位置和面积相关,与户型、装修和电梯等等相关性不明显。
本篇侧重于锻炼python实现数据清洗和可视化的能力,不足之处:
- 对于变量电梯、朝向、户型等的处理非常粗糙;
- 分析的问题不够明确,导致分析结论不明确。
参考资料:
- 第二篇数据分析项目实战:链家二手房分析
- 乐天:Python进行电影数据分析及可视化
- 路远:入门Python数据分析最好的实战项目(一)分析篇
小感悟:当目标不同时,即使数据源相同,分析的呈现思路也是完全不一样的:1和2都是对数据有预设问题,带着问题去看数据找到结论;3更突出探索性分析的特点,目标是为了理解完数据之后选取特征建模。