用Python分析北京市蛋殼公寓租房數(shù)據(jù)
前言
大家好,我是寶器。?
近期,蛋殼公寓“爆雷”事件持續(xù)發(fā)酵,期間因拖欠房東房租與租客退款,蛋殼公寓陷入討債風(fēng)波,全國多地蛋殼公寓辦公區(qū)域出現(xiàn)大規(guī)模解約事件,而作為蛋殼公寓總部所在地北京,自然首當(dāng)其沖。
為了應(yīng)對大規(guī)模的解約,北京在全市已經(jīng)設(shè)立了100多個(gè)蛋殼公寓矛盾糾紛接待點(diǎn),包含了蛋殼公寓涉及到的12個(gè)區(qū),這些接待點(diǎn)下沉到了街道甚至社區(qū),以方便涉及蛋殼公寓事件的房東和租客咨詢和處理糾紛。
長租公寓暴雷,不少年輕人不得不流離失所,構(gòu)成疫情下的另一個(gè)經(jīng)濟(jì)寫照,事態(tài)何去何從,值得關(guān)注。本文從數(shù)據(jù)角度出發(fā),爬取了蛋殼公寓北京區(qū)域共6025條公寓數(shù)據(jù),清洗數(shù)據(jù),并進(jìn)行可視化分析,為大家了解蛋殼公寓提供一個(gè)新的視角。
數(shù)據(jù)獲取
蛋殼公寓網(wǎng)頁結(jié)構(gòu)相對簡單,數(shù)據(jù)結(jié)構(gòu)統(tǒng)一,簡單的url翻頁構(gòu)造即可。需要注意的是極少數(shù)網(wǎng)頁會返回404,需要添加判斷過濾掉。本文用request請求到數(shù)據(jù),用xpath對返回的數(shù)據(jù)進(jìn)行解析,最后以追加模式將數(shù)據(jù)存儲為csv文件。爬蟲核心代碼如下:
def get_danke(href):
time.sleep(random.uniform(0, 1)) #設(shè)置延時(shí),避免對服務(wù)器產(chǎn)生壓力
response = requests.get(url=href, headers=headers)
if response.status_code == 200: #部分網(wǎng)頁會跳轉(zhuǎn)404,需要做判斷
res = response.content.decode('utf-8')
div = etree.HTML(res)
items = div.xpath("/html/body/div[3]/div[1]/div[2]/div[2]")
for item in items:
house_price=item.xpath("./div[3]/div[2]/div/span/div/text()")[0]
house_area=item.xpath("./div[4]/div[1]/div[1]/label/text()")[0].replace('建筑面積:約','').replace('㎡(以現(xiàn)場勘察為準(zhǔn))','')
house_id=item.xpath("./div[4]/div[1]/div[2]/label/text()")[0].replace('編號:','')
house_type=item.xpath("./div[4]/div[1]/div[3]/label/text()")[0].replace('\n','').replace(' ','').replace('戶型:','')
house_floor=item.xpath("./div[4]/div[2]/div[3]/label/text()")[0].replace('樓層:','')
house_postion_1=item.xpath("./div[4]/div[2]/div[4]/label/div/a[1]/text()")[0]
house_postion_2=item.xpath("./div[4]/div[2]/div[4]/label/div/a[2]/text()")[0]
house_postion_3=item.xpath("./div[4]/div[2]/div[4]/label/div/a[3]/text()")[0]
house_subway=item.xpath("./div[4]/div[2]/div[5]/label/text()")[0]
else:
house_price = None
house_area = None
house_id = None
house_type = None
house_floor = None
house_postion_1 = None
house_postion_2 = None
house_postion_3 = None
house_subway = None
......
由于代碼運(yùn)行過程中中斷了幾次,最終將數(shù)據(jù)保存為以下幾個(gè)csv文件中:
數(shù)據(jù)處理
導(dǎo)入數(shù)據(jù)分析包
import pandas as pd
import numpy as np
from pathlib import Path
import re
導(dǎo)入數(shù)據(jù)并合并
找到文件夾中的所有csv文件,遍歷讀取數(shù)據(jù),最后用concat方法合并所有數(shù)據(jù)。
files = Path(r"D:\菜J學(xué)Python\數(shù)據(jù)分析\蛋殼公寓").glob("*.csv")
dfs = [pd.read_csv(f) for f in files]
df = pd.concat(dfs)
df.head()

數(shù)據(jù)去重
數(shù)據(jù)爬取過程中有中斷,因此可能存在重復(fù)爬取的情況,需要去重處理。
df = df.drop_duplicates()
查看數(shù)據(jù)
用df.info()方法查看整體數(shù)據(jù)信息,結(jié)合預(yù)覽的數(shù)據(jù),我們可以很容易發(fā)現(xiàn),價(jià)格和面積字段不是數(shù)字類型,需要轉(zhuǎn)換處理。樓層字段可以提取出所在樓層和總樓層。
df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 6026 entries, 0 to 710
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 價(jià)格 6025 non-null object
1 面積 6025 non-null object
2 編號 6025 non-null object
3 戶型 6025 non-null object
4 樓層 6025 non-null object
5 位置1 6025 non-null object
6 位置2 6025 non-null object
7 小區(qū) 6025 non-null object
8 地鐵 6025 non-null object
dtypes: object(9)
memory usage: 470.8+ KB
數(shù)據(jù)類型轉(zhuǎn)換
在字段類型轉(zhuǎn)換時(shí)報(bào)錯(cuò),檢查發(fā)現(xiàn)是數(shù)據(jù)存在一行臟數(shù)據(jù),因此先刪除臟數(shù)據(jù)再做轉(zhuǎn)換即可。數(shù)據(jù)類型轉(zhuǎn)換用到astype()方法,提取所在樓層和總樓層時(shí)根據(jù)字符"/"分列即可,采用split()方法。
#刪除包含臟數(shù)據(jù)的行
jg = df['價(jià)格'] != "價(jià)格"
df = df.loc[jg,:]
#將價(jià)格字段轉(zhuǎn)為數(shù)字類型
df["價(jià)格"] = df["價(jià)格"].astype("float64")
#將面積字段轉(zhuǎn)為數(shù)字類型
df["面積"] = df["面積"].astype("float64")
#提取所在樓層
df = df[df['樓層'].notnull()]
df['所在樓層']=df['樓層'].apply(lambda x:x.split('/')[0])
df['所在樓層'] = df['所在樓層'].astype("int32")
#提取總樓層
df['總樓層']=df['樓層'].apply(lambda x:x.split('/')[1])
df['總樓層'] = df['總樓層'].str.replace("層","").astype("int32")
地鐵字段清洗
地鐵字段可以提取出地鐵數(shù)和距離地鐵距離。地鐵數(shù)通過統(tǒng)計(jì)字符"號線”的數(shù)量來計(jì)算,而距離地鐵距離通過正則表達(dá)式匹配出字符"米"前面的數(shù)字即可。為方便理解,這里直接構(gòu)造函數(shù)進(jìn)行清洗。
def get_subway_num(row):
subway_num=row.count('號線')
return subway_num
def get_subway_distance(row):
distance=re.search(r'\d+(?=米)',row)
if distance==None:
return-1
else:
return distance.group()
df['地鐵數(shù)']=df['地鐵'].apply(get_subway_num)
df['距離地鐵距離']=df['地鐵'].apply(get_subway_distance)
df['距離地鐵距離']=df['距離地鐵距離'].astype("int32")
保存數(shù)據(jù)
數(shù)據(jù)清洗完畢后,用df.to_excel()將數(shù)據(jù)保存為excel文件。
df.to_excel(r"\菜J學(xué)Python\數(shù)據(jù)分析\蛋殼公寓.xlsx")
df.head()

數(shù)據(jù)可視化
導(dǎo)入可視化相關(guān)包
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
plt.rcParams['font.sans-serif'] = ['SimHei'] # 設(shè)置加載的字體名
plt.rcParams['axes.unicode_minus'] = False# 解決保存圖像是負(fù)號'-'顯示為方塊的問題
import jieba
from pyecharts.charts import *
from pyecharts import options as opts
from pyecharts.globals import ThemeType
import stylecloud
from IPython.display import Image
各行政區(qū)公寓數(shù)量
根據(jù)清洗后的數(shù)據(jù)繪制北京蛋殼公寓分布地圖,我們可以很清晰的看到蛋殼公寓的布局,朝陽區(qū)和通州區(qū)是蛋殼公寓主要分布區(qū)域,延慶、密云、懷柔、平谷和門頭溝地區(qū)蛋殼公寓分布極少。
從各行政區(qū)數(shù)量上來看,朝陽區(qū)和通州區(qū)蛋殼公寓數(shù)量均超過1000個(gè),朝陽區(qū)遙遙領(lǐng)先其他地區(qū),共計(jì)1877個(gè),通州區(qū)緊隨其后,為1027個(gè)。
df7 = df["位置1"].value_counts()[:10]
df7 = df7.sort_values(ascending=True)
df7 = df7.tail(10)
print(df7.index.to_list())
print(df7.to_list())
c = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK))
.add_xaxis(df7.index.to_list())
.add_yaxis("",df7.to_list()).reversal_axis() #X軸與y軸調(diào)換順序
.set_global_opts(title_opts=opts.TitleOpts(title="各行政區(qū)公寓數(shù)量",subtitle="數(shù)據(jù)來源:蛋殼公寓 \t制圖:菜J學(xué)Python",pos_left = 'left'),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=13)), #更改橫坐標(biāo)字體大小
yaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=13)), #更改縱坐標(biāo)字體大小
)
.set_series_opts(label_opts=opts.LabelOpts(font_size=16,position='right'))
)
c.render_notebook()

小區(qū)公寓數(shù)量TOP10
從小區(qū)數(shù)量來看,新建村小區(qū)、花香東苑和連心園西區(qū)蛋殼公寓數(shù)量最多,均超過50個(gè)。這也意味著,這些小區(qū)的租戶受蛋殼風(fēng)波的影響相較于其他小區(qū)更大。
df7 = df["小區(qū)"].value_counts()[:10]
df7 = df7.sort_values(ascending=True)
df7 = df7.tail(10)
print(df7.index.to_list())
print(df7.to_list())
c = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK,width="1100px",height="600px"))
.add_xaxis(df7.index.to_list())
.add_yaxis("",df7.to_list()).reversal_axis() #X軸與y軸調(diào)換順序
.set_global_opts(title_opts=opts.TitleOpts(title="小區(qū)公寓數(shù)量TOP10",subtitle="數(shù)據(jù)來源:蛋殼公寓 \t制圖:菜J學(xué)Python",pos_left = 'left'),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=11)), #更改橫坐標(biāo)字體大小
yaxis_opts=opts.AxisOpts(axislabel_opts={"rotate":30}), #更改縱坐標(biāo)字體大小
)
.set_series_opts(label_opts=opts.LabelOpts(font_size=16,position='right'))
)
c.render_notebook()

蛋殼公寓租金分布
對租金進(jìn)行區(qū)間分段,我們發(fā)現(xiàn),北京蛋殼公寓的租金還是相當(dāng)有吸引力的,超過一半的公寓租金在2000-3000元/月。2000元/月以下的公寓數(shù)量占比也高達(dá)26.13%。
#租金分段
df['租金分段'] = pd.cut(df['價(jià)格'],[0,1000,2000,3000,4000,1000000],labels=['1000元以下','1000-2000元','2000-3000元','3000-4000元','4000元以上'],right=False)
df11 = df["租金分段"].value_counts()
df11 = df11.sort_values(ascending=False)
df11 = df11.round(2)
print(df11)
c = (
Pie(init_opts=opts.InitOpts(theme=ThemeType.DARK))
.add(
"",
[list(z) for z in zip(df11.index.to_list(),df11.to_list())],
radius=["20%", "80%"], #圓環(huán)的粗細(xì)和大小
rosetype='area'
)
.set_global_opts(legend_opts = opts.LegendOpts(is_show = False),title_opts=opts.TitleOpts(title="蛋殼公寓租金分布",subtitle="數(shù)據(jù)來源:蛋殼公寓\n制圖:菜J學(xué)Python",pos_top="0.5%",pos_left = 'left'))
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}:go7utgvlrp%",font_size=16))
)
c.render_notebook()

各行政區(qū)租金分布
我們繼續(xù)將地區(qū)因素引入租金分析中,發(fā)現(xiàn),不同行政區(qū)內(nèi)的租金分布也存在較大差異。以朝陽區(qū)為例,2000-3000元/月的公寓占比最多,而通州區(qū)1000-2000元/月的公寓占比更多。這也很容易理解,畢竟所處的區(qū)位和經(jīng)濟(jì)發(fā)展?fàn)顩r差異較大。
h = pd.pivot_table(df,index=['租金分段'],values=['價(jià)格'],
columns=['位置1'],aggfunc=['count'])
k = h.droplevel([0,1],axis=1) #刪除指定的索引/列級別
c = (
Polar(init_opts=opts.InitOpts(theme=ThemeType.DARK))
.add_schema(angleaxis_opts=opts.AngleAxisOpts(data=k.columns.tolist(), type_="category"))
.add("1000以下",h.values.tolist()[0], type_="bar", stack="stack0")
.add("1000-2000元",h.values.tolist()[1], type_="bar", stack="stack0")
.add("2000-3000元", h.values.tolist()[2], type_="bar", stack="stack0")
.add("3000-4000元", h.values.tolist()[3], type_="bar", stack="stack0")
.add("4000元以上", h.values.tolist()[4], type_="bar", stack="stack0")
.set_global_opts(title_opts=opts.TitleOpts(title="各行政區(qū)租金情況",subtitle="數(shù)據(jù)來源:蛋殼公寓\n制圖:菜J學(xué)Python"))
)
c.render_notebook()

蛋殼公寓樓層分布
從北京蛋殼公寓的樓層分布來看,10層以下占比高達(dá)73.92,高層和超高層不是蛋殼公寓的理想選擇。
# 漏斗圖
df['樓層分段'] = pd.cut(df['所在樓層'],[0,10,20,30,40,1000000],labels=['10層以下','10-20層','20-30層','30-40層','40層以上'],right=False)
count = df['樓層分段'].value_counts() # pd.Series
print(count)
job = list(count.index)
job_count = count.values.tolist()
from pyecharts.charts import Funnel
c = (
Funnel(init_opts=opts.InitOpts(theme=ThemeType.DARK))
.add("", [list(i) for i in zip(job,job_count)])
.set_global_opts(
title_opts=opts.TitleOpts(title="蛋殼公寓樓層分布",subtitle="數(shù)據(jù)來源:蛋殼公寓\n制圖:菜J學(xué)Python",pos_top="0.1%",pos_left = 'left'),legend_opts = opts.LegendOpts(is_show = False))
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}:go7utgvlrp%",font_size=16))
)
c.render_notebook()

蛋殼公寓戶型分布
從北京蛋殼公寓的戶型分布來看,3室1衛(wèi)為主,共計(jì)2783個(gè),其次才是4室1衛(wèi)。這與深圳蛋殼公寓以4室1衛(wèi)為主的情況存在較大差異。
df2 = df.groupby('戶型')['價(jià)格'].count()
df2 = df2.sort_values(ascending=False)[:10]
# print(df2)
bar = Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK))
bar.add_xaxis(df2.index.to_list())
bar.add_yaxis("",df2.to_list()) #X軸與y軸調(diào)換順序
bar.set_global_opts(title_opts=opts.TitleOpts(title="蛋殼公寓戶型分布",subtitle="數(shù)據(jù)來源:蛋殼公寓\t制圖:菜J學(xué)Python",pos_top="2%",pos_left = 'center'),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=16)), #更改橫坐標(biāo)字體大小
yaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=16)), #更改縱坐標(biāo)字體大小
)
bar.set_series_opts(label_opts=opts.LabelOpts(font_size=16,position='top'))
bar.render_notebook()

蛋殼公寓面積分布
從北京蛋殼公寓的面積分布來看,86.77%的公寓面積不足20㎡。北京10㎡以下的蛋殼公寓占比達(dá)到了21.2%,即便如此,這個(gè)數(shù)字仍不足深圳的一半。
df['面積分段'] = pd.cut(df['面積'],[0,10,20,30,40,1000000],labels=['10㎡以下','10-20㎡','20-30㎡','30-40㎡','40㎡以上'],right=False)
df2 = df["面積分段"].astype("str").value_counts()
print(df2)
df2 = df2.sort_values(ascending=False)
regions = df2.index.to_list()
values = df2.to_list()
c = (
Pie(init_opts=opts.InitOpts(theme=ThemeType.DARK))
.add("", list(zip(regions,values)))
.set_global_opts(legend_opts = opts.LegendOpts(is_show = False),title_opts=opts.TitleOpts(title="蛋殼公寓面積分布",subtitle="數(shù)據(jù)來源:蛋殼公寓\n制圖:菜J學(xué)Python",pos_top="0.5%",pos_left = 'left'))
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}:go7utgvlrp%",font_size=14))
)
c.render_notebook()

蛋殼公寓商圈分布
通過對北京幾個(gè)主要行政區(qū)商圈進(jìn)行詞云統(tǒng)計(jì)(字體越大表示蛋殼公寓數(shù)量最多),朝陽區(qū)的管莊、望京,通州區(qū)的北關(guān),豐臺區(qū)的樊羊路、方莊和角門,昌平區(qū)的天通苑,海淀區(qū)的永豐和西二旗,大興區(qū)的黃村和亦莊,是蛋殼公寓主要選擇的商圈。
# 繪制詞云圖
text1 = get_cut_words(content_series=df1['位置2'])
stylecloud.gen_stylecloud(text=' '.join(text1), max_words=100,
collocations=False,
font_path=r'C:\WINDOWS\FONTS\MSYH.TTC',
icon_name='fas fa-home',
size=653,
palette='cartocolors.diverging.ArmyRose_2',
output_name='./1.png')
Image(filename='./1.png')

相關(guān)性分析
從相關(guān)系數(shù)表可以看出,北京蛋殼公寓的面積、周邊地鐵數(shù)對公寓的價(jià)格有較大的的影響,相關(guān)系數(shù)分別為0.81和0.36。蛋殼公寓在進(jìn)行房屋定價(jià)時(shí),對公寓的面積以及公寓的地鐵配套有較大權(quán)重的考慮。由于北京蛋殼公寓距離地鐵都很近,因此,距離的遠(yuǎn)近對公寓的價(jià)格影響有限。另外,所在樓層也不是北京蛋殼公寓租金高低的重要影響因素。
color_map = sns.light_palette('orange', as_cmap=True) #light_palette調(diào)色板
df.corr().style.background_gradient(color_map)

完整版數(shù)據(jù)集和代碼:
鏈接:
https://pan.baidu.com/s/1MoXH47f7S7jtwlgWTz97_Q?
提取碼:y6f4

推薦閱讀
歡迎長按掃碼關(guān)注「數(shù)據(jù)管道」
