在售房源数据可视化
項目要求
探索地理位置、單價、面積、房屋朝向、戶型、樓層位置等因素對購房者關注熱度的影響。數(shù)據(jù)讀取
讀取CSV文件,瀏覽各字段內(nèi)容補充 ① :
忽略警告
import warnings
warnings.filterwarnings(‘ignore’)
| 70年 | 0 | 余杭臨平 | 21015元/平米 | 眾安理想灣 | 2015年建/板樓 | 210.0 | 3室2廳 | 103105013026 | 2019-06-12 | 南 北 | 低樓層/共33層 | 平層/精裝 | 99.93平米 |
| 70年 | 4 | 余杭臨平 | 28416元/平米 | 眾安理想灣 | 2016年建/板塔結合 | 780.0 | 6室2廳 | 103104324906 | 2019-04-04 | 南 | 聯(lián)排/共3層 | 毛坯 | 274.5平米 |
| 70年 | 2 | 余杭臨平 | 17323元/平米 | 眾安理想灣 | 2015年建/板樓 | 220.0 | 3室2廳 | 103102855120 | 2018-09-07 | 南 | 高樓層/共33層 | 精裝 | 127平米 |
| 70年 | 4 | 余杭臨平 | 18249元/平米 | 眾安理想灣 | 2015年建/塔樓 | 250.0 | 3室2廳 | 103102737121 | 2018-08-15 | 南 | 中樓層/共33層 | 簡裝 | 137平米 |
| 70年 | 1 | 余杭臨平 | 24112元/平米 | 眾安理想灣 | 2015年建/板樓 | 215.0 | 3室2廳 | 103104498162 | 2019-04-21 | 南 | 高樓層/共34層 | 精裝 | 89.17平米 |
補充②
d=data[“單價”].apply(lambda x: str(x)).str.findall("(\d+)").str[0].astype(“float”);
Series.str.findall() 與re模塊findall 類似
str[0] 妙用如下:
補充③:
data[“起建時間”]=data[“年限”].str.split("/").str[0]
data[“建筑類型”]=data[“年限”].str.split("/").str[1]
data=data[(data[“起建時間”]!=“未知年建”) & (data[“建筑類型”] != “暫無數(shù)據(jù)”)]
補充④:
data[“面積”].apply(lambda x: str(x)).str.extract("([\d,.]+)")
正則表達式"([\d,.]+)" 中方括號[]中的 點和逗號均代表“點和逗號”自身,無拓展意思。而且用str模塊extract或者findall處理時需要保證數(shù)據(jù)為字符串格式,否則會結果會出現(xiàn)錯誤。如下:
數(shù)據(jù)清洗
df=data.copy() df.dropna(inplace=True) #刪除空值 a=df[df.duplicated(keep=False)] #print("重復數(shù)據(jù):",a) df.drop_duplicates(inplace=True,ignore_index=True) # 刪除重復數(shù)據(jù),ignore_index=True 重置索引 print(df.info()) <class 'pandas.core.frame.DataFrame'>RangeIndex: 30765 entries, 0 to 30764Data columns (total 14 columns):# Column Non-Null Count Dtype --- ------ -------------- ----- 0 產(chǎn)權 30765 non-null object 1 關注 30765 non-null int64 2 區(qū)域 30765 non-null object 3 單價 30765 non-null object 4 小區(qū) 30765 non-null object 5 年限 30765 non-null object 6 總價/萬元 30765 non-null float647 戶型 30765 non-null object 8 房屋編碼 30765 non-null int64 9 掛牌時間 30765 non-null object 10 朝向 30765 non-null object 11 樓層 30765 non-null object 12 裝修情況 30765 non-null object 13 面積 30765 non-null object dtypes: float64(1), int64(2), object(11)memory usage: 3.3+ MBNone```python```python data1=df.copy() data1.head()| 70年 | 0 | 余杭臨平 | 21015元/平米 | 眾安理想灣 | 2015年建/板樓 | 210.0 | 3室2廳 | 103105013026 | 2019-06-12 | 南 北 | 低樓層/共33層 | 平層/精裝 | 99.93平米 |
| 70年 | 4 | 余杭臨平 | 28416元/平米 | 眾安理想灣 | 2016年建/板塔結合 | 780.0 | 6室2廳 | 103104324906 | 2019-04-04 | 南 | 聯(lián)排/共3層 | 毛坯 | 274.5平米 |
| 70年 | 2 | 余杭臨平 | 17323元/平米 | 眾安理想灣 | 2015年建/板樓 | 220.0 | 3室2廳 | 103102855120 | 2018-09-07 | 南 | 高樓層/共33層 | 精裝 | 127平米 |
| 70年 | 4 | 余杭臨平 | 18249元/平米 | 眾安理想灣 | 2015年建/塔樓 | 250.0 | 3室2廳 | 103102737121 | 2018-08-15 | 南 | 中樓層/共33層 | 簡裝 | 137平米 |
| 70年 | 1 | 余杭臨平 | 24112元/平米 | 眾安理想灣 | 2015年建/板樓 | 215.0 | 3室2廳 | 103104498162 | 2019-04-21 | 南 | 高樓層/共34層 | 精裝 | 89.17平米 |
各字段瀏覽
瀏覽個字段信息格式,是否需要做進一步處理,比如取數(shù)值或者取特定意義的字段產(chǎn)權
# 各字段唯一值瀏覽:產(chǎn)權 df.產(chǎn)權.unique() # 產(chǎn)權 字段 # 計算產(chǎn)權“未知”的數(shù)量 df.groupby("產(chǎn)權").size() df[df.產(chǎn)權=="未知"] #查看產(chǎn)權“未知”數(shù)據(jù)是否需要做特殊處理,比如刪除、0值填充初步考慮產(chǎn)權“未知”行只有7行,占比總體數(shù)據(jù)比率較少。但是這部分數(shù)據(jù)的其他字段并無異常,暫時不做刪除或者量化處理
區(qū)域
地域分區(qū):上城,下城,臨安,余杭,富陽,拱墅,江干,濱江,蕭山,西湖,錢塘新區(qū)
# 函數(shù)定義,區(qū)域分區(qū),例如:西湖文三西路,改為西湖;區(qū)域:上城,下城,臨安,余杭,富陽,拱墅,江干,濱江,蕭山,西湖,錢塘新區(qū) import re def qy(data):if re.match("上城",data):return "上城"elif re.match("下城",data):return "下城"elif re.match("臨安",data):return "臨安"elif re.match("余杭",data):return "余杭"elif re.match("富陽",data):return "富陽"elif re.match("拱墅",data):return "拱墅"elif re.match("江干",data):return "江干"elif re.match("濱江",data):return "濱江"elif re.match("蕭山",data):return "蕭山"elif re.match("西湖",data):return "西湖"elif re.match("錢塘新區(qū)",data):return "錢塘新區(qū)" a=df.copy() df["地區(qū)"]=a.區(qū)域.apply(qy) b=df.drop(columns="區(qū)域") data2=b.copy() df=data2.copy()單價
# 提取"單價"中的數(shù)字 def dj(x):b=re.match("\d+",x).group()return b # 或者以下 def dj1(x):b=re.findall(r"\b(\d+)元/平米\b",x)[0]return b # 注意findall 與 match 的區(qū)別 findall 返回的是list match 返回的是string data2["單價dj"]=df.單價.apply(dj1) data2.drop(columns="單價",inplace=True) data2.rename(columns={"單價dj":"單價","地區(qū)":"區(qū)域"},inplace=True) df=data2.copy() df.drop(columns="小區(qū)",inplace=True) data2=df.copy()年限
# 定義函數(shù),提取年限中的有效字段,如果是“未知年限”則提取“未知” def nx(x):b=re.match(r"(.+)年建.+",x).group(1)return b # 刪除部分字段 ,提取年限中的有限字段 data3=data2.copy() data3["nx"]=data2.年限.apply(nx) data3.drop(columns=["年限","戶型","房屋編碼"],inplace=True) data3.rename(columns={"nx":"年限"},inplace=True) df=data3.copy()朝向
# 定于朝向函數(shù),假定數(shù)據(jù)朝向列中的第一個空格前為房屋朝向,例如“東 南 北 東北” 的朝向定義為“東” def cx(x):a=re.findall(r"\b.+?\b",x)[0]return a data4=data3.copy() data4["cx"]=df.朝向.apply(cx) data4.drop(columns="朝向",inplace=True) data4.rename(columns={"cx":"朝向"},inplace=True) df=data4.copy()樓層
# 函數(shù)定義 def lc(x):z=[]if re.match("(.+?)/.*?(\d+).*",x):z.append(re.match("(.+?)/.*?(\d+).*",x).group(1)) # 注意 () 的應用,以及(\d+)前的*?,即最小匹配。如果是采用貪婪匹配會與預期值有出入z.append(re.match("(.+?)/.*?(\d+).*",x).group(2))elif re.match(".*?(\d+).*",x):z=["未知",re.match(".*?(\d+).*",x).group(1)] # 共3層 對于類似情況,只提取樓高 3else:z=["未知","未知"]return z import numpy as np a=df.樓層.apply(lc).to_list() a=np.array(a) data5=data4.copy() data5["Lc"]=a[:,0] data5["Lg"]=a[:,1] data5.drop(columns="樓層",inplace=True) data5.rename(columns={"Lc":"樓層","Lg":"樓高"},inplace=True)裝修情況
data6=data5.copy() data6["zsqk"]=data5.裝修情況.apply(lambda x : x[-2:]) data6.drop(columns="裝修情況",inplace=True) data6.rename(columns={"zsqk":"裝修情況"},inplace=True)掛牌時間
import pandas as pd df=data6.copy() data7=data6.copy() data7["掛牌時間"]=pd.to_datetime(data6.掛牌時間)面積
# 面積數(shù)據(jù)提取函數(shù)定義 def mj(x):d=re.match("\d+",x).group()return d df=data7.copy() data8=data7.copy() data8["面積"]=data7.面積.apply(mj) df=data8.copy()字符轉換成數(shù)值
# 定義數(shù)值轉換函數(shù),如果字符可以轉換成數(shù)值則執(zhí)行轉換,如果字符不可以轉換成字符則返回原字符 # 方法一: def zh(x):try:float(x)except:return xelse :return int(x) # 方法二:如果是 a=pd.Series(["1","2","3","abc"]) 這種情況下以下定義函數(shù)有效,如果是b=pd.Series(["1.0","2.0","3.0","abc"]) 情況以下函數(shù)失效; def zh1(x):if x.isdigit():return float(x)else:return x df=data8.copy() data9=data8.copy() data9[["總價/萬元","面積","單價","樓高"]]=df[["總價/萬元","面積","單價","樓高"]].applymap(zh) data9.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 30765 entries, 0 to 30764 Data columns (total 12 columns):# Column Non-Null Count Dtype --- ------ -------------- ----- 0 產(chǎn)權 30765 non-null object 1 關注 30765 non-null int64 2 總價/萬元 30765 non-null float64 3 掛牌時間 30765 non-null datetime64[ns]4 面積 30765 non-null float64 5 區(qū)域 30765 non-null object 6 單價 30765 non-null float64 7 年限 30765 non-null object 8 朝向 30765 non-null object 9 樓層 30765 non-null object 10 樓高 30765 non-null object 11 裝修情況 30765 non-null object dtypes: datetime64[ns](1), float64(3), int64(1), object(7) memory usage: 2.8+ MB字段相關性
字段標準化
測試標準化是否影響相關系數(shù),初步猜測不影響 # 總價與關注度 zj=df.groupby("總價/萬元").關注.sum() zj=zj.reset_index() zj.corr()| 1.000000 | -0.171617 |
| -0.171617 | 1.000000 |
補充⑤:
通過熱力圖可以更直接的表示各個因素之間的相關程度
corr=df.corr() plt.figure(figsize=(8, 6)) sns.heatmap(corr, cmap='GnBu') plt.show()數(shù)據(jù)之間的關系系數(shù)
cs.corr()| 1.000000 | -0.171617 | 1.000000 | -0.171617 |
| -0.171617 | 1.000000 | -0.171617 | 1.000000 |
| 1.000000 | -0.171617 | 1.000000 | -0.171617 |
| -0.171617 | 1.000000 | -0.171617 | 1.000000 |
注意:數(shù)據(jù)標準化并不影響數(shù)據(jù)之間的關系系數(shù)
df.corr()| 1.000000 | -0.014854 | -0.047961 | 0.018297 |
| -0.014854 | 1.000000 | 0.736337 | 0.557916 |
| -0.047961 | 0.736337 | 1.000000 | -0.016588 |
| 0.018297 | 0.557916 | -0.016588 | 1.000000 |
可視化
分別用pandas、seaborn、pyecharts可視化
產(chǎn)權可視化
# 根據(jù)產(chǎn)權分組計算關注度,將結果可視化 print(df.產(chǎn)權.unique()) cq=df.groupby("產(chǎn)權")["關注"].sum() cq=pd.DataFrame(cq) # 刪除產(chǎn)權不明的數(shù)據(jù) cq=cq[cq.index != "未知"]pandas 可視化
# pandas 庫 plot.bar 可視化 import pandas as pd import matplotlib.pyplot as plt import numpy as npplt.rcParams["font.sans-serif"]=["SimHei"] # 解決中文顯示異常 plt.rcParams["axes.unicode_minus"]=False # 負號正常顯示cq.plot(kind="bar",rot=40,figsize=(10,6)) #繪制柱狀圖 # 添加數(shù)據(jù)標簽 for x ,y in zip(np.arange(len(cq)),cq["關注"].tolist()):plt.text(x,y+10000,y)seaborn 可視化
# seaborn 庫可視化 import seaborn as sns import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"]=["SimHei"] # 解決中文亂碼,注意如果設置seaborn style 則需要在 set_style 中設置中文顯示亂碼問題,此設置失效 plt.rcParams['axes.unicode_minus'] = False # 負號正常顯示sns.set_style("whitegrid",{'font.sans-serif':['simhei','Arial']}) #圖表風格 plt.figure(figsize=(10,6)) x=cq.index y=cq.關注 ax=sns.barplot(x,y) ax.set_xticklabels(x,rotation=45) # 添加數(shù)據(jù)標簽 for x ,y in zip(np.arange(len(cq)),cq["關注"].tolist()):plt.text(x,y+10000,y)pyecharts
from pyecharts.charts import Bar from pyecharts.globals import ThemeType from pyecharts import options as optsfrom pyecharts.globals import WarningType WarningType.ShowWarning = False # 刪除可惡的警報x=cq.index.to_list() y=cq.關注.to_list() bar=(Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK,width="800px",height="500px")).add_xaxis(x).add_yaxis("關注度",y,bar_width=80)) bar.render_notebook()總價與關注度
zj=df[["關注","總價/萬元"]].sort_values(by="總價/萬元",ascending=True) zj.head()| 56 | 29.8 |
| 59 | 35.0 |
| 4 | 36.0 |
| 3 | 38.0 |
| 19 | 39.0 |
利用Kmeans 將總價分層
Kmeans K值確定
利用肘部法則和輪廓系數(shù)法確定Kmeans中的K值
df.info() <class 'pandas.core.frame.DataFrame'> Int64Index: 30765 entries, 0 to 30764 Data columns (total 12 columns):# Column Non-Null Count Dtype --- ------ -------------- ----- 0 產(chǎn)權 30765 non-null object 1 關注 30765 non-null int64 2 總價/萬元 30765 non-null float64 3 掛牌時間 30765 non-null datetime64[ns]4 面積 30765 non-null int64 5 區(qū)域 30765 non-null object 6 單價 30765 non-null int64 7 年限 30765 non-null object 8 朝向 30765 non-null object 9 樓層 30765 non-null object 10 樓高 30765 non-null object 11 裝修情況 30765 non-null object dtypes: datetime64[ns](1), float64(1), int64(3), object(7) memory usage: 4.3+ MB import numpy as np from sklearn.cluster import KMeans import matplotlib.pyplot as plt from sklearn import metrics # SSE K = range(2, 31) SSE = [] # SSE 記錄平方距離和 silhouette_scores=[] # 記錄各個K值的輪廓系數(shù) silhouette_score X=np.array(df["總價/萬元"].to_list()).reshape(-1, 1) #為使用sihouette計算輪廓系數(shù),構建縱坐標為0的二維數(shù)組 y=np.zeros((len(X),1)) z=np.array(list(zip(X,y))).reshape(-1,2) for k in K:kmeanModel = KMeans(n_clusters=k).fit(z)SSE.append(kmeanModel.inertia_)silhouette_scores.append(metrics.silhouette_score(z,kmeanModel.labels_,metric='euclidean'))# SSE 肘部法則 plt.figure(figsize=(10,6)) plt.subplot(2,1,1) plt.plot(K, SSE, 'bx-') plt.xlabel('k') plt.ylabel('SSE') plt.title('SSE 肘部法則')# 輪廓系數(shù) silhouette_scores 特別消耗計算機,運行比較慢 plt.subplot(2,1,2) plt.plot(K,silhouette_scores) plt.xlabel("K") plt.ylabel("silhouette_scores") plt.title('輪廓系數(shù)') plt.show()# 根據(jù)肘部法則能夠確認最佳K 值為 5分組
K值確定后,用Kmeans 模型對“總價”進行分組總計關注度
# 使用Kmeans K=5 將“總價/萬元”分組 X=np.array(df["總價/萬元"].to_list()).reshape(-1, 1) kmeanModel = KMeans(n_clusters=5).fit(X) labels=pd.Series(kmeanModel.labels_,index=df.index) df_k=pd.concat([df[["總價/萬元","關注"]],labels],axis=1) df_k.columns=["總價/萬元","關注","分類"] df1=df_k.groupby(by="分類").agg({"總價/萬元":["mean","min","max","count"],"關注":["sum"]}) df1.columns=["均值","總價_min","總價_max","計數(shù)","關注"] df1["分組"]=df1["總價_min"].astype(str)+" - "+df1["總價_max"].astype(str) zj=df1[["分組","關注"]] zj總價可視化
pandas 可視化
z=zj.plot.bar(figsize=(10,6),y="關注",x="分組",rot=45,title="總價與關注度") for i in np.arange(5):plt.text(i,zj["關注"][i]+5000,zj["關注"][i])seaborn 可視化
# seaborn 庫可視化 import seaborn as sns import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"]=["SimHei"] # 解決中文亂碼,注意如果設置seaborn style 則需要在 set_style 中設置中文顯示亂碼問題,此設置失效 plt.rcParams['axes.unicode_minus'] = False # 負號正常顯示sns.set_style("darkgrid",{'font.sans-serif':['simhei','Arial']}) #圖表風格 plt.figure(figsize=(10,6)) x=zj.分組 y=zj.關注 ax=sns.barplot(x,y) ax.set_xticklabels(x,rotation=45) # 添加數(shù)據(jù)標簽 for x ,y in zip(np.arange(len(zj)),zj["關注"].tolist()):plt.text(x,y+5000,y)pyecharts 可視化
from pyecharts.charts import Bar from pyecharts.globals import ThemeType from pyecharts import options as optsimport numpy as np x=zj.分組.tolist() y=zj.關注.tolist() bar=(Bar(init_opts=opts.InitOpts(theme=ThemeType.MACARONS,width="1000px",height="600px")).add_xaxis(x).add_yaxis("關注度",y,bar_max_width=60)) bar.render_notebook()面積與關注度
mj=df.groupby("面積").關注.sum().reset_index() mjpandas 可視化
x=mj.面積.to_list() y=mj.關注.to_list()mj_max=mj[mj.關注==max(mj.關注)] x_max=mj_max.iloc[0,1] y_max=mj_max.iloc[0,0]mj.plot.line(figsize=(10,6),x="面積",y="關注",title="面積與關注度") plt.text(y_max,x_max-2000,("面積:"+str(y_max)+"\n"+"關注度:"+str(x_max)),fontsize=14)seaborn 可視化
# seaborn 庫可視化 import seaborn as sns import matplotlib.pyplot as pltsns.set_style("dark",{'font.sans-serif':['simhei','Arial']}) #圖表風格,如果不設置san-serif中文會顯示異常 plt.figure(figsize=(10,6))x=mj.面積.to_list() y=mj.關注.to_list()mj_max=mj[mj.關注==max(mj.關注)] x_max=mj_max.iloc[0,1] y_max=mj_max.iloc[0,0]ax=sns.lineplot(x,y)plt.text(y_max,x_max-2000,("面積:"+str(y_max)+"\n"+"關注度:"+str(x_max)),fontsize=14) #對最大值添加數(shù)字標簽pyecharts 可視化
from pyecharts.charts import Line from pyecharts.globals import ThemeType from pyecharts import options as opts import numpy as npx=mj.面積.to_list() y=mj.關注.to_list()line=(Line(init_opts=opts.InitOpts(theme=ThemeType.CHALK)).add_xaxis(x).add_yaxis("關注度",y)) line.render_notebook()補充⑥:
#關于區(qū)域性質(zhì)的數(shù)據(jù),用地區(qū)顯示很直觀pair1 = [(row["地理位置"], row['關注']) for i, row in count_area.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="杭州市各區(qū)房源數(shù)量對比"),legend_opts=opts.LegendOpts(is_show=True),visualmap_opts=opts.VisualMapOpts(min_=count_area["關注"].min(), max_=count_area["關注"].max()))map1.render_notebook()其它因素可視化
單價同前面總價處理流程;
年限、朝向、樓層、裝修情況、樓高與前面的柱狀圖\條形圖 處理方式相似 不再一一贅述;
可以考慮創(chuàng)建一個函數(shù)或者類,直接傳入?yún)?shù)得到自己想要的圖表;
以下定義了一個簡單的函數(shù),直接調(diào)用函數(shù)傳入相應的參數(shù)即可直接顯示該因素與關注度的可視化圖表;
總結
- 上一篇: 2023最新SSM计算机毕业设计选题大全
- 下一篇: 搜索技术【二分搜索】 - 简介 原理