畢業(yè)設計 大數(shù)據(jù)二手房數(shù)據(jù)分析與可視化
?? 這兩年開始畢業(yè)設計和畢業(yè)答辯的要求和難度不斷提升,傳統(tǒng)的畢設題目缺少創(chuàng)新和亮點,往往達不到畢業(yè)答辯的要求,這兩年不斷有學弟學妹告訴學長自己做的項目系統(tǒng)達不到老師的要求。
為了大家能夠順利以及最少的精力通過畢設,學長分享優(yōu)質(zhì)畢業(yè)設計項目,今天要分享的是
?? ?基于大數(shù)據(jù)招聘崗位數(shù)據(jù)分析與可視化系統(tǒng)
??學長這里給一個題目綜合評分(每項滿分5分)
工作量:3分
創(chuàng)新點:5分
畢設幫助,選題指導,技術(shù)解答,歡迎打擾,見B站個人主頁
https://space.bilibili.com/33886978
1 課題背景
首先通過爬蟲采集鏈家網(wǎng)上所有二手房的房源數(shù)據(jù),并對采集到的數(shù)據(jù)進行清洗;然后,對清洗后的數(shù)據(jù)進行可視化分析,探索隱藏在大量數(shù)據(jù)背后的規(guī)律;最后,采用一個聚類算法對所有二手房數(shù)據(jù)進行聚類分析,并根據(jù)聚類分析的結(jié)果,將這些房源大致分類,以對所有數(shù)據(jù)的概括總結(jié)。通過上述分析,我們可以了解到目前市面上二手房各項基本特征及房源分布情況,幫助我們進行購房決策。
2 實現(xiàn)效果
整體數(shù)據(jù)文件詞云

各區(qū)域二手房房源數(shù)量折線圖

二手房房屋用途水平柱狀圖

二手房基本信息可視化分析
各區(qū)域二手房平均單價柱狀圖

各區(qū)域二手房單價和總價箱線圖

二手房單價最高Top20

二手房單價和總價熱力圖

二手房單價熱力圖

二手房總價小于200萬的分布圖

二手房建筑面積分析

二手房建筑面積分布區(qū)間柱狀圖

二手房房屋屬性可視化分析
二手房房屋戶型占比情況
從二手房房屋戶型餅狀圖中可以看出,2室1廳與2室2廳作為標準配置,一共占比接近一半。其中3室2廳和3室1廳的房源也占比不少,其他房屋戶型的房源占比就比較少了。

二手房房屋裝修情況

二手房房屋朝向分布情況

二手房建筑類型占比情況

3 數(shù)據(jù)采集
該部分通過網(wǎng)絡爬蟲程序抓取鏈家網(wǎng)上所有二手房的數(shù)據(jù),收集原始數(shù)據(jù),作為整個數(shù)據(jù)分析的基石。
鏈家網(wǎng)網(wǎng)站結(jié)構(gòu)分析
鏈家網(wǎng)二手房主頁界面如下圖,主頁上面紅色方框位置顯示目前二手房在售房源的各區(qū)域位置名稱,中間紅色方框位置顯示了房源的總數(shù)量,下面紅色方框顯示了二手房房源信息縮略圖,該紅色方框區(qū)域包含了二手房房源頁面的URL地址標簽。圖2下面紅色方框顯示了二手房主頁上房源的頁數(shù)。
鏈家網(wǎng)二手房主頁截圖上半部分:

二手房房源信息頁面如下圖。我們需要采集的目標數(shù)據(jù)就在該頁面,包括基本信息、房屋屬性和交易屬性三大類。各類信息包括的數(shù)據(jù)項如下:
1)基本信息:小區(qū)名稱、所在區(qū)域、總價、單價。
2)房屋屬性:房屋戶型、所在樓層、建筑面積、戶型結(jié)構(gòu)、套內(nèi)面積、建筑類型、房屋朝向、建筑結(jié)構(gòu)、裝修情況、梯戶比例、配備電梯、產(chǎn)權(quán)年限。
3)交易屬性:掛牌時間、交易權(quán)屬、上次交易、房屋用途、房屋年限、產(chǎn)權(quán)所屬、抵押信息、房本備件。


網(wǎng)絡爬蟲程序關鍵問題說明
1)問題1:鏈家網(wǎng)二手房主頁最多只顯示100頁的房源數(shù)據(jù),所以在收集二手房房源信息頁面URL地址時會收集不全,導致最后只能采集到部分數(shù)據(jù)。
解決措施:將所有二手房數(shù)據(jù)分區(qū)域地進行爬取,100頁最多能夠顯示3000套房,該區(qū)域房源少于3000套時可以直接爬取,如果該區(qū)域房源超過3000套可以再分成更小的區(qū)域。
2)問題2:爬蟲程序如果運行過快,會在采集到兩、三千條數(shù)據(jù)時觸發(fā)鏈家網(wǎng)的反爬蟲機制,所有的請求會被重定向到鏈家的人機鑒定頁面,從而會導致后面的爬取失敗。
解決措施:①為程序中每次http請求構(gòu)造header并且每次變換http請求header信息頭中USER_AGENTS數(shù)據(jù)項的值,讓請求信息看起來像是從不同瀏覽器發(fā)出的訪問請求。②爬蟲程序每處理完一次http請求和響應后,隨機睡眠1-3秒,每請求2500次后,程序睡眠20分鐘,控制程序的請求速度。
4 數(shù)據(jù)清洗
對于爬蟲程序采集得到的數(shù)據(jù)并不能直接分析,需要先去掉一些“臟”數(shù)據(jù),修正一些錯誤數(shù)據(jù),統(tǒng)一所有數(shù)據(jù)字段的格式,將這些零散的數(shù)據(jù)規(guī)整成統(tǒng)一的結(jié)構(gòu)化數(shù)據(jù)。
原始數(shù)據(jù)主要需要清洗的部分
主要需要清洗的數(shù)據(jù)部分如下:
1)將雜亂的記錄的數(shù)據(jù)項對齊
2)清洗一些數(shù)據(jù)項格式
3)缺失值處理
3.2.3 數(shù)據(jù)清洗結(jié)果
數(shù)據(jù)清洗前原始數(shù)據(jù)如下圖,

清洗后的數(shù)據(jù)如下圖,可以看出清洗后數(shù)據(jù)已經(jīng)規(guī)整了許多。

5 數(shù)據(jù)聚類分析
該階段采用聚類算法中的k-means算法對所有二手房數(shù)據(jù)進行聚類分析,根據(jù)聚類的結(jié)果和經(jīng)驗,將這些房源大致分類,已達到對數(shù)據(jù)概括總結(jié)的目的。在聚類過程中,我們選擇了面積、總價和單價這三個數(shù)值型變量作為樣本點的聚類屬性。
k-means算法原理
基本原理
k-Means算法是一種使用最普遍的聚類算法,它是一種無監(jiān)督學習算法,目的是將相似的對象歸到同一個簇中。簇內(nèi)的對象越相似,聚類的效果就越好。該算法不適合處理離散型屬性,但對于連續(xù)型屬性具有較好的聚類效果。
聚類效果判定標準
使各個樣本點與所在簇的質(zhì)心的誤差平方和達到最小,這是評價k-means算法最后聚類效果的評價標準。

算法實現(xiàn)步驟
1)選定k值
2)創(chuàng)建k個點作為k個簇的起始質(zhì)心。
3)分別計算剩下的元素到k個簇的質(zhì)心的距離,將這些元素分別劃歸到距離最小的簇。
4)根據(jù)聚類結(jié)果,重新計算k個簇各自的新的質(zhì)心,即取簇中全部元素各自維度下的算術(shù)平均值。
5)將全部元素按照新的質(zhì)心重新聚類。
6)重復第5步,直到聚類結(jié)果不再變化。
7)最后,輸出聚類結(jié)果。
算法缺點
雖然K-Means算法原理簡單,但是有自身的缺陷:
1)聚類的簇數(shù)k值需在聚類前給出,但在很多時候中k值的選定是十分難以估計的,很多情況我們聚類前并不清楚給出的數(shù)據(jù)集應當分成多少類才最恰當。
2)k-means需要人為地確定初始質(zhì)心,不一樣的初始質(zhì)心可能會得出差別很大的聚類結(jié)果,無法保證k-means算法收斂于全局最優(yōu)解。
3)對離群點敏感。
4)結(jié)果不穩(wěn)定(受輸入順序影響)。
5)時間復雜度高O(nkt),其中n是對象總數(shù),k是簇數(shù),t是迭代次數(shù)。
算法實現(xiàn)關鍵問題說明
K值的選定說明
根據(jù)聚類原則:組內(nèi)差距要小,組間差距要大。我們先算出不同k值下各個SSE(Sum of squared errors)值,然后繪制出折線圖來比較,從中選定最優(yōu)解。從圖中,我們可以看出k值到達5以后,SSE變化趨于平緩,所以我們選定5作為k值。

初始的K個質(zhì)心選定說明
初始的k個質(zhì)心選定是采用的隨機法。從各列數(shù)值最大值和最小值中間按正太分布隨機選取k個質(zhì)心。
關于離群點
離群點就是遠離整體的,非常異常、非常特殊的數(shù)據(jù)點。因為k-means算法對離群點十分敏感,所以在聚類之前應該將這些“極大”、“極小”之類的離群數(shù)據(jù)都去掉,否則會對于聚類的結(jié)果有影響。離群點的判定標準是根據(jù)前面數(shù)據(jù)可視化分析過程的散點圖和箱線圖進行判定。根據(jù)散點圖和箱線圖,需要去除離散值的范圍如下:
1)單價:基本都在100000以內(nèi),沒有特別的異常值。
2)總價:基本都集中在3000以內(nèi),這里我們需要去除3000外的異常值。
3)建筑面積:基本都集中在500以內(nèi),這里我們需要去除500外的異常值。
數(shù)據(jù)的標準化
因為總價的單位為萬元,單價的單位為元/平米,建筑面積的單位為平米,所以數(shù)據(jù)點計算出歐幾里德距離的單位是沒有意義的。同時,總價都是3000以內(nèi)的數(shù),建筑面積都是500以內(nèi)的數(shù),但單價基本都是20000以上的數(shù),在計算距離時單價起到的作用就比總價大,總價和單價的作用都遠大于建筑面積,這樣聚類出來的結(jié)果是有問題的。這樣的情況下,我們需要將數(shù)據(jù)標準化,即將數(shù)據(jù)按比例縮放,使之都落入一個特定區(qū)間內(nèi)。去除數(shù)據(jù)的單位限制,將其轉(zhuǎn)化為無量綱的純數(shù)值,便于不同單位或量級的指標能夠進行計算和比較。
我們將單價、總價和面積都映射到500,因為面積本身就都在500以內(nèi),不要特別處理。單價在計算距離時,需要先乘以映射比例0.005,總價需要乘以映射比例0.16。進行數(shù)據(jù)標準化前和進行數(shù)據(jù)標準化后的聚類效果對比如下:圖32、圖33是沒有數(shù)據(jù)標準化前的聚類效果散點圖;圖34、圖35是數(shù)據(jù)標準化后的聚類效果散點圖。
數(shù)據(jù)標準化前的單價與建筑面積聚類效果散點圖:

聚類結(jié)果分析
聚類結(jié)果如下
1)聚類結(jié)果統(tǒng)計信息如下:

2)聚類后的單價與建筑面積散點圖和總價與建筑面積散點圖。
3)聚類結(jié)果分組0、1、2、3、4的區(qū)域分布圖如下實例。
聚類結(jié)果分組0的區(qū)域分布圖如下:

6 部分核心代碼
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 23 10:09:15 2016
K-means cluster
@author: liudiwei
"""
import numpy as np
class KMeansClassifier():
? ?"this is a k-means classifier"
? ?
? ?
? ?def __init__(self, k=3, initCent='random', max_iter=5000):
? ? ? ?"""構(gòu)造函數(shù),初始化相關屬性"""
? ? ? ?self._k = k
? ? ? ?self._initCent = initCent#初始中心
? ? ? ?self._max_iter = max_iter#最大迭代
? ? ? ?#一個m*2的二維矩陣,矩陣第一列存儲樣本點所屬的族的索引值,
? ? ? ?#第二列存儲該點與所屬族的質(zhì)心的平方誤差
? ? ? ?self._clusterAssment = None#樣本點聚類結(jié)結(jié)構(gòu)矩陣
? ? ? ?self._labels = None
? ? ? ?self._sse = None#SSE(Sum of squared errors)平方誤差和
? ?
? ? ? ?
? ?def _calEDist(self, arrA, arrB):
? ? ? ?"""
? ? ? ?功能:歐拉距離距離計算
? ? ? ?輸入:兩個一維數(shù)組
? ? ? ?"""
? ? ? ?arrA_temp = arrA.copy()
? ? ? ?arrB_temp = arrB.copy()
? ? ? ?arrA_temp[0] = arrA_temp[0]*0.16
? ? ? ?arrA_temp[1] = arrA_temp[1]*0.005
? ? ? ?arrB_temp[0] = arrB_temp[0]*0.16
? ? ? ?arrB_temp[1] = arrB_temp[1]*0.005
? ? ? ?return np.math.sqrt(sum(np.power(arrA_temp - arrB_temp, 2)))
? ?
? ?
? ?def _calMDist(self, arrA, arrB):
? ? ? ?"""
? ? ? ?功能:曼哈頓距離距離計算
? ? ? ?輸入:兩個一維數(shù)組
? ? ? ?"""
? ? ? ?return sum(np.abs(arrA-arrB))
? ?def _randCent(self, data_X, k):
? ? ? ?"""
? ? ? ?功能:隨機選取k個質(zhì)心
? ? ? ?輸出:centroids #返回一個m*n的質(zhì)心矩陣
? ? ? ?"""
? ? ? ?n = data_X.shape[1] - 3 #獲取特征值的維數(shù)(要刪除一個用于標記的id列和經(jīng)緯度值)
? ? ? ?centroids = np.empty((k,n)) ?#使用numpy生成一個k*n的矩陣,用于存儲質(zhì)心
? ? ? ?for j in range(n):
? ? ? ? ? ?minJ = min(data_X[:,j+1])
? ? ? ? ? ?rangeJ = max(data_X[:,j+1] - minJ)
? ? ? ? ? ?#使用flatten拉平嵌套列表(nested list)
? ? ? ? ? ?centroids[:, j] = (minJ + rangeJ * np.random.rand(k, 1)).flatten()
? ? ? ?return centroids
? ?
? ?
? ?def fit(self, data_X):
? ? ? ?"""
? ? ? ?輸入:一個m*n維的矩陣
? ? ? ?"""
? ? ? ?if not isinstance(data_X, np.ndarray) or \
? ? ? ? ? ? ? isinstance(data_X, np.matrixlib.defmatrix.matrix):
? ? ? ? ? ?try:
? ? ? ? ? ? ? ?data_X = np.asarray(data_X)
? ? ? ? ? ?except:
? ? ? ? ? ? ? ?raise TypeError("numpy.ndarray resuired for data_X")
? ? ? ? ? ? ? ?
? ? ? ?m = data_X.shape[0] ?#獲取樣本的個數(shù)
? ? ? ?#一個m*2的二維矩陣,矩陣第一列存儲樣本點所屬的族的編號,
? ? ? ?#第二列存儲該點與所屬族的質(zhì)心的平方誤差
? ? ? ?self._clusterAssment = np.zeros((m,2))
? ? ? ?
? ? ? ?#創(chuàng)建k個點,作為起始質(zhì)心
? ? ? ?if self._initCent == 'random':
? ? ? ? ? ?self._centroids = self._randCent(data_X, self._k)
? ? ? ? ? ?
? ? ? ?clusterChanged = True
? ? ? ?#循環(huán)最大迭代次數(shù)
? ? ? ?for _ in range(self._max_iter): #使用"_"主要是因為后面沒有用到這個值
? ? ? ? ? ?clusterChanged = False
? ? ? ? ? ?for i in range(m): ? #將每個樣本點分配到離它最近的質(zhì)心所屬的族
? ? ? ? ? ? ? ?minDist = np.inf #首先將minDist置為一個無窮大的數(shù)
? ? ? ? ? ? ? ?minIndex = -1 ? ?#將最近質(zhì)心的下標置為-1
? ? ? ? ? ? ? ?for j in range(self._k): #次迭代用于尋找元素最近的質(zhì)心
? ? ? ? ? ? ? ? ? ?arrA = self._centroids[j,:]
? ? ? ? ? ? ? ? ? ?arrB = data_X[i,1:4]
? ? ? ? ? ? ? ? ? ?distJI = self._calEDist(arrA, arrB) #計算距離
? ? ? ? ? ? ? ? ? ?if distJI < minDist:
? ? ? ? ? ? ? ? ? ? ? ?minDist = distJI
? ? ? ? ? ? ? ? ? ? ? ?minIndex = j
? ? ? ? ? ? ? ?if self._clusterAssment[i, 0] != minIndex or self._clusterAssment[i, 1] > minDist**2:
? ? ? ? ? ? ? ? ? ?clusterChanged = True
? ? ? ? ? ? ? ? ? ?self._clusterAssment[i,:] = minIndex, minDist**2
? ? ? ? ? ?if not clusterChanged:#若所有樣本點所屬的族都不改變,則已收斂,結(jié)束迭代
? ? ? ? ? ? ? ?break
? ? ? ? ? ?for i in range(self._k):#更新質(zhì)心,將每個族中的點的均值作為質(zhì)心
? ? ? ? ? ? ? ?index_all = self._clusterAssment[:,0] #取出樣本所屬簇的編號
? ? ? ? ? ? ? ?value = np.nonzero(index_all==i) #取出所有屬于第i個簇的索引值
? ? ? ? ? ? ? ?ptsInClust = data_X[value[0]] ? ?#取出屬于第i個簇的所有樣本點
? ? ? ? ? ? ? ?self._centroids[i,:] = np.mean(ptsInClust[:,1:4], axis=0) #計算均值,賦予新的質(zhì)心
? ? ? ?
? ? ? ?self._labels = self._clusterAssment[:,0]
? ? ? ?self._sse = sum(self._clusterAssment[:,1])
? ?
? ?
? ?def predict(self, X):#根據(jù)聚類結(jié)果,預測新輸入數(shù)據(jù)所屬的族
? ? ? ?#類型檢查
? ? ? ?if not isinstance(X,np.ndarray):
? ? ? ? ? ?try:
? ? ? ? ? ? ? ?X = np.asarray(X)
? ? ? ? ? ?except:
? ? ? ? ? ? ? ?raise TypeError("numpy.ndarray required for X")
? ? ? ?
? ? ? ?m = X.shape[0]#m代表樣本數(shù)量
? ? ? ?preds = np.empty((m,))
? ? ? ?for i in range(m):#將每個樣本點分配到離它最近的質(zhì)心所屬的族
? ? ? ? ? ?minDist = np.inf
? ? ? ? ? ?for j in range(self._k):
? ? ? ? ? ? ? ?distJI = self._calEDist(self._centroids[j,:], X[i,:])
? ? ? ? ? ? ? ?if distJI < minDist:
? ? ? ? ? ? ? ? ? ?minDist = distJI
? ? ? ? ? ? ? ? ? ?preds[i] = j
? ? ? ?return preds
? ? ? ?
class biKMeansClassifier():
? ?"this is a binary k-means classifier"
? ?
? ?def __init__(self, k=3):
? ? ? ?
? ? ? ?self._k = k
? ? ? ?self._centroids = None
? ? ? ?self._clusterAssment = None
? ? ? ?self._labels = None
? ? ? ?self._sse = None
? ? ? ?
? ?
? ?def _calEDist(self, arrA, arrB):
? ? ? ?"""
? ? ? ?功能:歐拉距離距離計算
? ? ? ?輸入:兩個一維數(shù)組
? ? ? ?"""
? ? ? ?return np.math.sqrt(sum(np.power(arrA-arrB, 2)))
? ? ? ?
? ?def fit(self, X):
? ? ? ?m = X.shape[0]
? ? ? ?self._clusterAssment = np.zeros((m,2))
? ? ? ?centroid0 = np.mean(X, axis=0).tolist()
? ? ? ?centList =[centroid0]
? ? ? ?for j in range(m):#計算每個樣本點與質(zhì)心之間初始的平方誤差
? ? ? ? ? ?self._clusterAssment[j,1] = self._calEDist(np.asarray(centroid0), \
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?X[j,:])**2
? ? ? ?
? ? ? ?while (len(centList) < self._k):
? ? ? ? ? ?lowestSSE = np.inf
? ? ? ? ? ?#嘗試劃分每一族,選取使得誤差最小的那個族進行劃分
? ? ? ? ? ?for i in range(len(centList)):
? ? ? ? ? ? ? ?index_all = self._clusterAssment[:,0] #取出樣本所屬簇的索引值
? ? ? ? ? ? ? ?value = np.nonzero(index_all==i) #取出所有屬于第i個簇的索引值
? ? ? ? ? ? ? ?ptsInCurrCluster = X[value[0],:] #取出屬于第i個簇的所有樣本點
? ? ? ? ? ? ? ?clf = KMeansClassifier(k=2)
? ? ? ? ? ? ? ?clf.fit(ptsInCurrCluster)
? ? ? ? ? ? ? ?#劃分該族后,所得到的質(zhì)心、分配結(jié)果及誤差矩陣
? ? ? ? ? ? ? ?centroidMat, splitClustAss = clf._centroids, clf._clusterAssment
? ? ? ? ? ? ? ?sseSplit = sum(splitClustAss[:,1])
? ? ? ? ? ? ? ?index_all = self._clusterAssment[:,0]
? ? ? ? ? ? ? ?value = np.nonzero(index_all==i)
? ? ? ? ? ? ? ?sseNotSplit = sum(self._clusterAssment[value[0],1])
? ? ? ? ? ? ? ?if (sseSplit + sseNotSplit) < lowestSSE:
? ? ? ? ? ? ? ? ? ?bestCentToSplit = i
? ? ? ? ? ? ? ? ? ?bestNewCents = centroidMat
? ? ? ? ? ? ? ? ? ?bestClustAss = splitClustAss.copy()
? ? ? ? ? ? ? ? ? ?lowestSSE = sseSplit + sseNotSplit
? ? ? ? ? ?#該族被劃分成兩個子族后,其中一個子族的索引變?yōu)樵宓乃饕?br> ? ? ? ? ? ?#另一個子族的索引變?yōu)閘en(centList),然后存入centList
? ? ? ? ? ?bestClustAss[np.nonzero(bestClustAss[:,0]==1)[0],0]=len(centList)
? ? ? ? ? ?bestClustAss[np.nonzero(bestClustAss[:,0]==0)[0],0]=bestCentToSplit
? ? ? ? ? ?centList[bestCentToSplit] = bestNewCents[0,:].tolist()
? ? ? ? ? ?centList.append(bestNewCents[1,:].tolist())
? ? ? ? ? ?self._clusterAssment[np.nonzero(self._clusterAssment[:,0] == \
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?bestCentToSplit)[0],:]= bestClustAss
? ? ? ? ? ? ? ? ?
? ? ? ?self._labels = self._clusterAssment[:,0]
? ? ? ?self._sse = sum(self._clusterAssment[:,1])
? ? ? ?self._centroids = np.asarray(centList)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ?def predict(self, X):#根據(jù)聚類結(jié)果,預測新輸入數(shù)據(jù)所屬的族
? ? ? ?#類型檢查
? ? ? ?if not isinstance(X,np.ndarray):
? ? ? ? ? ?try:
? ? ? ? ? ? ? ?X = np.asarray(X)
? ? ? ? ? ?except:
? ? ? ? ? ? ? ?raise TypeError("numpy.ndarray required for X")
? ? ? ?
? ? ? ?m = X.shape[0]#m代表樣本數(shù)量
? ? ? ?preds = np.empty((m,))
? ? ? ?for i in range(m):#將每個樣本點分配到離它最近的質(zhì)心所屬的族
? ? ? ? ? ?minDist = np.inf
? ? ? ? ? ?for j in range(self._k):
? ? ? ? ? ? ? ?distJI = self._calEDist(self._centroids[j,:],X[i,:])
? ? ? ? ? ? ? ?if distJI < minDist:
? ? ? ? ? ? ? ? ? ?minDist = distJI
? ? ? ? ? ? ? ? ? ?preds[i] = j
? ? ? ?return preds
畢設幫助,選題指導,技術(shù)解答,歡迎打擾,見B站個人主頁
https://space.bilibili.com/33886978