2020链家杭州二手房数据分析(截止到2020年09月07日)

2020链家杭州二手房数据分析(截止到2020年09月07日)

1 项目背景

通过python爬去链家杭州二手房的数据,网址为:https://hz.lianjia.com/ershoufang/。可以发现,链家把杭州分为15个区域。其中,截止到2020年09月07日,桐庐和建德区域数据为0条,大江东数据为2两条,淳安数据共32条。和其他区域相比,这几个区域数据量太小,因此在后续爬去过去中略去。

通过requestsBeautifulSoup库爬取和解析,代码就不放了,很简陋。注意每次爬取后设置随机间隔时间,做个简单的反反爬虫,不然会被封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行数据。结果如下:dx7J4f.png

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()wCcmLt.png

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()wQwHpR.png

从数据来看,各项值基本正常。

4 数据可视化

4.1 探索变量相关性

看看几个列的相关性。注意,pandas中默认的相关是皮尔逊相关系数,要求数据是连续变量。而我们的数据中,只有Size, totalPrice, unitPrice是连续变量,所以这个图就作为一个参考吧。这几列数据的相关性挺高。

corr = df.corr()
plt.figure(figsize=(8,6))
sns.heatmap(corr, cmap='GnBu')
plt.show()

wQ0hvt.png

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万一套。不得不说,杭州人民真有钱。wQB8xI.png

使用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()

wQ68D1.png

接下来,可以看看各个区域的直方图和核密度图,以便了解每个区域平均总房价的分布。

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)

箱线图:wJyS0J.png

通过以上,我们可以发现,大部分区域二手房价格的中位数都在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()

结果图:wDcvCV.png

可以看出,大多数区域房屋单价都在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()

结果如下:w3RR5n.png

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()

结果如下。wJMvUs.png

看来大家比较倾向于中低楼层。但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()

结果如图:wJNNJe.png

看来大家都比较喜欢精装的房子。

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()

看看结果图:wDsRA0.png

可以看出:

  • 绝大多数房屋面积都在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,数据可视化完成。

版权声明:本文为zjjoebloggs原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。