2020链家杭州二手房数据分析(截止到2020年09月07日)
1 项目背景
通过python爬去链家杭州二手房的数据,网址为:https://hz.lianjia.com/ershoufang/。可以发现,链家把杭州分为15个区域。其中,截止到2020年09月07日,桐庐和建德区域数据为0条,大江东数据为2两条,淳安数据共32条。和其他区域相比,这几个区域数据量太小,因此在后续爬去过去中略去。
通过requests和BeautifulSoup库爬取和解析,代码就不放了,很简陋。注意每次爬取后设置随机间隔时间,做个简单的反反爬虫,不然会被封IP。我设置的间隔时间为random.randint(0,2) + random.random()。最终,一共获得了31, 230条记录。
2 理解数据
数据含义如下:
| 字段名 | 字段含义 |
|---|---|
| postion | 具体位置 |
| Layout | 房屋布局 |
| Size | 房屋面积 |
| Directioin | 房屋朝向 |
| Renovation | 装修情况 |
| Floor | 楼层 |
| Structure | 房屋结构(板楼、塔楼) |
| totalPrice | 总价格 |
| unitPrice | 单价 |
| District | 所在区县 |
3 探索数据
3.1 数据查看
通过pandas导入数据df = pd.read_excel('lz_hangzhou-copy.xlsx')后,使用df.head(10)简单查看前10行数据。结果如下:
3.2 数据清洗
3.2.1 缺失值
再看看有没有缺失值:df.isnull().sum().sort_values(),没有发现缺失值。数据维护的挺好。
3.2.2 unitPrice列
接下来,处理unitPrice列。需要吧汉字去除和‘/’去掉。观察一下,可以使用正则表达式找出其中的数字。
df['unitPrice'] = df['unitPrice'].apply(lambda x: str(x)).str.findall("(\d+)").str[0].astype("float")
3.2.3 Floor列
然后处理Floor列。在这一列,数据可以分为两部分:a. 楼层位置(高、低、中); b. 层数。两部分可以由’()'分开。我们需要将这两部分新建两列,代替原来的Floor列。
首先,我们看一下不符合这个形式的记录共有多少行。df.shape[0]-df['Floor'].str.contains('\(').sum(),返回结果是1741,占总数据的5.6%。那我们将把这些记录删掉,df = df.loc[df['Floor'].str.contains('\(')]。然后把Floor列拆分成floorType和totalFloor两列,分别表示楼层位置和总层数,再删除原来的Floor列。代码如下:
df['floorType'] = df['Floor'].str.split('(').str[0]
df['totalFloor'] = df['Floor'].str.split('(').str[1].str.\
findall('\d+').str[0].astype('int')
df.drop('Floor', axis=1, inplace=True)
看一下现在的数据:df.head()
3.2.4 Structure列
首先,看看Structure列的数据构成:df['Structure'].unique().
array([' 板楼', ' 塔楼', ' 板塔结合', ' 暂无数据'], dtype=object)
我们需要把每个类型名称中的空格去除,然后删掉’暂无数据’的记录。代码如下:
df['Structure'] = df['Structure'].str.strip()
df = df[df['Structure'] != '暂无数据']
3.2.5 Renovation列
同样,该列的数据中也有空格:
array([' 精装 ', ' 简装 ', ' 毛坯 '], dtype=object)
换一种方式去除空格。
def reno(x):
if '精装' in x: return '精装'
if '简装' in x: return '简装'
if '毛坯' in x: return '毛坯'
df['Renovation'] = df['Renovation'].apply(reno)
3.2.6 Direction列
这一列的数据构成比较多样:
array([' 南 ', ' 东 南 北 ', ' 南 北 ', ' 南 西 ', ' 东 ', ' 北 南 ', ' 东 南 ',
' 东南 ', ' 南 西 北 ', ' 西 ', ' 西南 ', ' 北 ', ' 西 北 ', ' 西北 ',
' 南 西南 北 ', ' 东南 南 '], dtype=object)
仔细观看可以发现以下几种情况:
- 单一朝向:’ 东 ', ‘ 南 ’, ‘ 西 ’, ‘ 北 ’, ‘ 西南 ’, ‘ 西北 ’,‘ 东南 ’
- 两种朝向:’ 南 北 ', ’ 南 西 ', ’ 北 南 ', ’ 东 南 ',
- 三种朝向:’ 东 南 北 ‘,’ 西 西南 南 ‘,’ 南 西 北 ',等等。
针对单一朝向的,直接去除空格。而对于多种朝向,为了后续分析方便,则简化为单种朝向,并将最终值赋予Orientation列。
def direct(x):
if "东南" in x:
if x.count("南") > 1:
if ("西南" in x) & ("南" not in x):
return "东南"
else:
return "南"
else:
return "东南"
elif "西南" in x:
if x.count("南") >1:
return "南"
else:
return "西南"
elif "东北" in x:
return "东北"
elif "西北" in x:
return "西北"
elif "东" in x:
return "东"
elif "西" in x:
return "西"
elif "南" in x:
return "南"
elif "北" in x:
return "北"
df['Orientation'] = df['Direction'].apply(direct)
df.drop('Direction', axis=1, inplace=True)
3.2.6 Size列
观察可以发现,面积都是浮点数,用正则表达式提取即可。
df['Size'] = df['Size'].str.findall('[\d,.]+').str[0].astype('float')
3.2.7 Layout列
这一列的信息为房屋格局,如3室2厅,把室的信息和厅的信息提取出来,分别存储。
df['Room'] = df['Layout'].str.findall('(\d)室(\d)厅').str[0].str[0].astype('int')
df['Hall'] = df['Layout'].str.findall('(\d)室(\d)厅').str[0].str[1].astype('int')
df.drop('Layout', axis=1, inplace=True)
3.2.8 Follow列
关注人数为整数,提取出来。
df['Follow'] = df['Follow'].str.findall('\d+').str[0].astype('int')
3.2.9 异常值处理
先看看有没有异常值:df.describe()
从数据来看,各项值基本正常。
4 数据可视化
4.1 探索变量相关性
看看几个列的相关性。注意,pandas中默认的相关是皮尔逊相关系数,要求数据是连续变量。而我们的数据中,只有Size, totalPrice, unitPrice是连续变量,所以这个图就作为一个参考吧。这几列数据的相关性挺高。
corr = df.corr()
plt.figure(figsize=(8,6))
sns.heatmap(corr, cmap='GnBu')
plt.show()
4.2 杭州市各区域房源平均总价对比
total_district = df.groupby('District')['totalPrice'].mean().\
sort_values(ascending=False).reset_index()
plt.figure(figsize=(12,8))
plt.tick_params(labelsize=16)
ax = sns.barplot(x='District', y='totalPrice', data=total_district, palette='Greens_r')
ax.set_title('杭州市各区房源平均总价对比', fontsize=20)
ax.set_xlabel('区域', fontsize=18)
ax.set_ylabel('总价/万元', fontsize=18)
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
for index, row in total_district.iterrows():
ax.text(row.name,row.totalPrice+10, round(row.totalPrice), ha='center', fontsize=14)
来看看结果,滨江区最高,平均531万一套。不得不说,杭州人民真有钱。
使用pyecharts的Map,把它放到地图上看看。关于pyecharts和相应扩展地图的安装,可以参考这个。注意,地图上没有钱塘新区。
pair1 = [(row["District"], round(row["totalPrice"],2)) for i, row in total_district.iterrows()]
map1 = Map(init_opts=opts.InitOpts(theme='macarons', width='800px', height='400px'))
map1.add('杭州', pair1, '杭州',is_roam=False)
map1.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
map1.set_global_opts(
title_opts=opts.TitleOpts(title='杭州各区域房源平均总价对比'),
legend_opts=opts.LegendOpts(is_show=False),
visualmap_opts=opts.VisualMapOpts(min_=round(total_district['totalPrice'].min()), max_=round(total_district['totalPrice'].max())),
tooltip_opts=opts.TooltipOpts(formatter='{b}:{c}万元') # a.为杭州,b为各区名称,c为价格
)
map1.render_notebook()
接下来,可以看看各个区域的直方图和核密度图,以便了解每个区域平均总房价的分布。
locations = df['District'].unique()
plt.figure(figsize=(18,12))
with sns.axes_style('ticks'): # 通过with,把subplot的风格设置为'ticks'
for i in range(len(locations)):
temp = df[df['District'] == locations[i]]
plt.subplot(3,4,i+1)
plt.title(locations[i])
sns.distplot(temp['totalPrice'])
plt.xlabel('')
plt.show()
可以发现,像钱塘新区,富阳区的价格分布有些正态分布的味了。而价格三巨头,滨江、西湖和上城区,都有些正偏态分布,即平均数大于众数。这说明,这几个地区的总房价里面有坏人,太高了![外链图片转存中…(img-Hh7Ir5yb-1600328097491)]](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWdjaHIuY29tL2kvd3UwUFBJ?x-oss-process=image/format,png)
再来看看箱线图,可能更直观一点:
plt.figure(figsize=(12,8))
plt.tick_params(labelsize=16)
axbox1 = sns.boxplot(x='District', y='totalPrice', data=df,)
axbox1.set_title('杭州市各区房源总价分布', fontsize=20)
axbox1.set_xlabel('区域', fontsize=18)
axbox1.set_ylabel('总价/万元', fontsize=18)
箱线图:
通过以上,我们可以发现,大部分区域二手房价格的中位数都在400万以下。上城区和滨江区的极端值都很高,价格在1400万左右,拉高了均值。
4.3 杭州市各区域房源平均单价对比
我们再来看见平均单价。
ave_district = df.groupby('District')['unitPrice'].mean().sort_values(ascending=False).reset_index()
plt.figure(figsize=(12,8))
plt.tick_params(labelsize=16)
ax2 = sns.barplot(x='District', y='unitPrice', data=ave_district, palette='Blues_r')
ax2.spines['right'].set_visible(False)
ax2.spines['top'].set_visible(False)
ax2.set_title('杭州市各区房源平均单价对比', fontsize=20)
ax2.set_xlabel('区域', fontsize=18)
ax2.set_ylabel('单价/元', fontsize=18)
for index, row in ave_district.iterrows():
ax2.text(row.name, row.unitPrice+500, round(row.unitPrice), ha='center', fontsize=14)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FVrnY2ZT-1600328097496)(https://s1.ax1x.com/2020/09/08/wQWu6g.png)]
看了之后,心哇凉哇凉的O(∩_∩)O哈哈~。再放到地图上看看。
pair2 = [(row.District,round(row.unitPrice,2)) for index, row in ave_district.iterrows()]
map2 = Map(init_opts=opts.InitOpts(theme='macarons', width='800px', height='400px'))
map2.add('杭州', pair2, '杭州', is_roam=False)
map2.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
map2.set_global_opts(
title_opts = opts.TitleOpts(title='杭州市各区房源平均单价对比'),
legend_opts = opts.LegendOpts(is_show=False),
visualmap_opts = opts.VisualMapOpts(min_=round(ave_district['unitPrice'].min()), max_=round(ave_district['unitPrice'].max())),
tooltip_opts = opts.TooltipOpts(formatter='{b}: {c}元')
)
map2.render_notebook()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h89mymKO-1600328097498)(https://s1.ax1x.com/2020/09/08/wQ5n5n.png)]
同样,看看各区域价格分布。可以看出,大多数区域房源单价的分布都是中间多。
plt.figure(figsize=(18, 12))
with sns.axes_style('ticks'):
for i in range(len(locations)):
temp = df[df['District'] == locations[i]]
plt.subplot(3,4, i+1)
plt.title(locations[i])
sns.distplot(temp['unitPrice'])
plt.xlabel('')
plt.suptitle('杭州市各区房源平均单价分布', fontsize=18)
plt.show()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XbVQuvno-1600328097499)(https://s1.ax1x.com/2020/09/08/wl9VNq.png)]
同样看一下箱线图:
plt.figure(figsize=(12, 8))
plt.tick_params(labelsize=16)
axbox2 = sns.boxplot(x='District', y='unitPrice', data=df, palette=sns.color_palette('muted'))
axbox2.set_xlabel('区域', fontsize=16)
axbox2.set_ylabel('单价/元', fontsize=16)
axbox2.set_title('杭州市各区房源单价分布', fontsize=18)
plt.show()
结果图:
可以看出,大多数区域房屋单价都在4万元每平米左右。真的是贵。
4.4 杭州市各区域房源关注度对比
看看关注度对比:
follow_district = df.groupby('District')['Follow'].sum().sort_values(ascending=False).reset_index()
plt.figure(figsize=(12, 8))
plt.tick_params(labelsize=16)
ax3 = sns.barplot(x='District', y='Follow', data=follow_district, palette='Reds_r')
ax3.spines['top'].set_visible(False)
ax3.spines['right'].set_visible(False)
ax3.set_title('杭州市各区房源关注度对比', fontsize=18)
ax3.set_ylabel('关注人数', fontsize=16)
ax3.set_xlabel('区域', fontsize=16)
for index, row in follow_district.iterrows():
ax3.text(row.name, row['Follow']+ 2000, row['Follow'], ha='center', fontsize=14)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZrSgLdYD-1600328097502)(https://s1.ax1x.com/2020/09/09/w1VERA.png)]
滨江区这个关注度一骑绝尘啊。
4.5 不同因素对关注度的影响
4.5.1 不同房型对关注度的影响
在前面,为了做相关,我们把Layout拆分成了Room和Hall两种变量。现在把它组合回来,然后再看看不同户型的关注度。
df['Layout'] = df['Room'].astype('str')+'室'+df['Hall'].astype('str')+'厅'
df['Layout'].unique()
共有以下几种房型:
array(['4室2厅', '2室2厅', '3室2厅', '3室1厅', '5室2厅', '2室1厅', '4室1厅', '1室1厅',
'6室2厅', '1室2厅', '1室0厅', '2室0厅', '5室1厅'], dtype=object)
看看关注度最高的10种房型:
layout_follow = df.groupby('Layout')['Follow'].sum().sort_values(ascending=False).reset_index()
bar1 = Bar(init_opts=opts.InitOpts(theme='wonderland', width='800px', height='600px'))
bar1.add_xaxis(layout_follow['Layout'].head(10).to_list())
bar1.add_yaxis("", layout_follow['Follow'].head(10).to_list())
bar1.set_global_opts(title_opts=opts.TitleOpts(title='杭州市房源关注度TOP10', pos_left='center'))
# bar1.reversal_axis()
bar1.render_notebook()
结果如下:
3室2厅这么受欢迎的么。
4.5.2 不同朝向对关注度的影响
在这一部分,本来想做个玫瑰图,但类别太少了,而且类别差异过大,不太适合,所以就做个饼图吧。
ori_follow = df.groupby('Orientation')['Follow'].sum().sort_values(ascending=False).reset_index()
pie = Pie(init_opts=opts.InitOpts(width='800px', height='450px', theme='light'))
pie.add("", [list(z) for z in zip(ori_follow['Orientation'], ori_follow['Follow'])],
radius=['35%', '75%'],
)
pie.set_global_opts(title_opts=opts.TitleOpts(title='不同朝向关注度对比'),
legend_opts=opts.LegendOpts(is_show=True),
toolbox_opts=opts.ToolboxOpts(is_show=False)
)
pie.set_series_opts(label_opts=opts.LabelOpts(is_show=True, font_size=12,
formatter='{b}:{c}'))
pie.render_notebook()
结果如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OL0gklxM-1600328097506)(https://s1.ax1x.com/2020/09/10/wGGcS1.png)]
房屋朝南关注度最高。
4.5.3 楼层位置对关注度的影响
先观察下楼层位置的范围:
array([ 6, 5, 33, 32, 9, 14, 30, 19, 26, 11, 18, 12, 34, 16, 31, 20, 23,
25, 2, 7, 27, 24, 3, 17, 28, 15, 8, 22, 10, 29, 42, 38, 43, 21,
4, 13], dtype=int64)
最小的2,最大的是42。那么我们就可以从0~45,5层一组,共分为9组。
cut_range = [x for x in range(0,46, 5)]
cut_name = [str(cut_range[i]) + '-' + str(cut_range[i+1]) + '层' for i in range(9)]
df['floorRange'] = pd.cut(df['totalFloor'], cut_range, labels=cut_name)
接下来做可视化:
floor_follow = df.groupby('floorRange')['Follow'].sum().sort_values(ascending=False).reset_index()
bar2 = Bar(init_opts=opts.InitOpts(theme='vintage', width='600px', height='400px'))
bar2.add_xaxis(floor_follow['floorRange'].to_list())
bar2.add_yaxis("关注度", floor_follow['Follow'].to_list())
bar2.set_series_opts(label_opts=opts.LabelOpts(is_show=True))
bar2.set_global_opts(title_opts=opts.TitleOpts(title='不同楼层关注度'),
xaxis_opts=opts.AxisOpts(axislabel_opts={'rotate': 45,}))
bar2.render_notebook()
结果如下。
看来大家比较倾向于中低楼层。但0~5的关注度也不高。
4.5.4 装修情况对关注度的影响
装修有精装、简装和毛坯三种。看看这几种装修情况的关注度如何。
reno_follow = df.groupby('Renovation')['Follow'].sum().sort_values(ascending=False).reset_index()
bar3 = Bar(init_opts=opts.InitOpts(theme='roma', width='700px', height='300px'))
bar3.add_xaxis(reno_follow['Renovation'].to_list())
bar3.add_yaxis("装修情况", reno_follow['Follow'].to_list())
bar3.set_series_opts(label_opts=opts.LabelOpts(is_show=True, position='right'))
bar3.set_global_opts(title_opts=opts.TitleOpts('装修情况对关注度的影响'),
xaxis_opts=opts.AxisOpts(axislabel_opts={'interval':"0"}))
bar3.reversal_axis()
bar3.render_notebook()
结果如图:
看来大家都比较喜欢精装的房子。
4.6 杭州市各区房源面积对比
再看一下面积:
size_district = df.groupby('District')['Size'].mean().sort_values(ascending=True).reset_index()
bar3 = Bar(init_opts=opts.InitOpts(theme='vintage', width='800px', height='600px'))
bar3.add_xaxis(size_district['District'].to_list())
bar3.add_yaxis("平均面积", round(size_district['Size'],2).to_list())
bar3.set_series_opts(label_opts=opts.LabelOpts(is_show=True, position='right'))
bar3.set_global_opts(title_opts=opts.TitleOpts("杭州市各区房源平均面积对比"),
xaxis_opts=opts.AxisOpts(axislabel_opts={'interval': "0"}))
bar3.reversal_axis()
bar3.render_notebook()
结果如图
平均下来之后,都没有超过120平米。再看一下总体面积分布。
f,[ax1, ax2] = plt.subplots(1,2,figsize=(18,6))
sns.distplot(df['Size'],ax=ax1, rug=True, bins=20)
ax1.set_title('杭州市各区房源面积分布', fontsize=18)
ax1.tick_params(labelsize=14)
sns.regplot(x='Size', y='totalPrice', data=df, ax=ax2)
ax2.set_title('房屋总价和面积关系', fontsize=18)
ax2.tick_params(labelsize=14)
ax2.set_xlabel('房屋面积/㎡', fontsize=16)
ax2.set_ylabel('房屋总价/万元', fontsize=16)
plt.show()
看看结果图:
可以看出:
- 绝大多数房屋面积都在100平米左右,最大的在250平米以上。
- 房屋面积和总价格成正相关(一个有点废话的结论,O(∩_∩)O)。
5 总结
5.1 结论
- 杭州二手房市场价格较贵。大多数区域房源总体价格中位数在400万左右,单价中位数在4万元每平方。
- 杭州二手房热度和价格都较高的区域为:滨江区、西湖区,拱墅区和上城区。其中,滨江区在热度和总价方面都最高,而在单价方面却小于其他三区,原因是因为该区二手房平均面积较大。同时,从整体数据来看,房屋价格和面积呈正相关。
- 本次分析主要是想巩固以下python的可视化,主要用到的有条形图、饼图、地图、和密度分布。Python在作图这方面,不管是图类型还是样式,离ggplot2的差距越来越小。
5.2 待改进
- 在处理房屋朝向时,过于简单,有点混乱,后续可以改进。
- 可以加入机器学习的部分,预测二手房价格。
Update Log
- 2020-09-01 17:11:21,爬取数据
- 2020-09-04 20:09:38,完成平均总房价分析
- 2020-09-07 18:53:00,发现房子的“关注度”指标有分析价值,重新爬取数据
- 2020-09-10 17:07:51,数据可视化完成。