# 1 前言
?? 這兩年開始畢業(yè)設(shè)計(jì)和畢業(yè)答辯的要求和難度不斷提升,傳統(tǒng)的畢設(shè)題目缺少創(chuàng)新和亮點(diǎn),往往達(dá)不到畢業(yè)答辯的要求,這兩年不斷有學(xué)弟學(xué)妹告訴學(xué)長(zhǎng)自己做的項(xiàng)目系統(tǒng)達(dá)不到老師的要求。
為了大家能夠順利以及最少的精力通過(guò)畢設(shè),學(xué)長(zhǎng)分享優(yōu)質(zhì)畢業(yè)設(shè)計(jì)項(xiàng)目,今天要分享的是
?? 基于大數(shù)據(jù)招聘崗位數(shù)據(jù)分析與可視化系統(tǒng)
??學(xué)長(zhǎng)這里給一個(gè)題目綜合評(píng)分(每項(xiàng)滿分5分)
- 難度系數(shù):3分
- 工作量:3分
- 創(chuàng)新點(diǎn):5分
1 課題背景
首先通過(guò)爬蟲采集鏈家網(wǎng)上所有二手房的房源數(shù)據(jù),并對(duì)采集到的數(shù)據(jù)進(jìn)行清洗;然后,對(duì)清洗后的數(shù)據(jù)進(jìn)行可視化分析,探索隱藏在大量數(shù)據(jù)背后的規(guī)律;最后,采用一個(gè)聚類算法對(duì)所有二手房數(shù)據(jù)進(jìn)行聚類分析,并根據(jù)聚類分析的結(jié)果,將這些房源大致分類,以對(duì)所有數(shù)據(jù)的概括總結(jié)。通過(guò)上述分析,我們可以了解到目前市面上二手房各項(xiàng)基本特征及房源分布情況,幫助我們進(jìn)行購(gòu)房決策。
2 實(shí)現(xiàn)效果
整體數(shù)據(jù)文件詞云
各區(qū)域二手房房源數(shù)量折線圖
二手房房屋用途水平柱狀圖
二手房基本信息可視化分析
各區(qū)域二手房平均單價(jià)柱狀圖
各區(qū)域二手房單價(jià)和總價(jià)箱線圖
二手房單價(jià)最高Top20
二手房單價(jià)和總價(jià)熱力圖
二手房單價(jià)熱力圖
二手房總價(jià)小于200萬(wàn)的分布圖
二手房建筑面積分析
二手房建筑面積分布區(qū)間柱狀圖
二手房房屋屬性可視化分析
二手房房屋戶型占比情況
從二手房房屋戶型餅狀圖中可以看出,2室1廳與2室2廳作為標(biāo)準(zhǔn)配置,一共占比接近一半。其中3室2廳和3室1廳的房源也占比不少,其他房屋戶型的房源占比就比較少了。
二手房房屋裝修情況
二手房房屋朝向分布情況
二手房建筑類型占比情況
3 數(shù)據(jù)采集
該部分通過(guò)網(wǎng)絡(luò)爬蟲程序抓取鏈家網(wǎng)上所有二手房的數(shù)據(jù),收集原始數(shù)據(jù),作為整個(gè)數(shù)據(jù)分析的基石。
鏈家網(wǎng)網(wǎng)站結(jié)構(gòu)分析
鏈家網(wǎng)二手房主頁(yè)界面如下圖,主頁(yè)上面紅色方框位置顯示目前二手房在售房源的各區(qū)域位置名稱,中間紅色方框位置顯示了房源的總數(shù)量,下面紅色方框顯示了二手房房源信息縮略圖,該紅色方框區(qū)域包含了二手房房源頁(yè)面的URL地址標(biāo)簽。圖2下面紅色方框顯示了二手房主頁(yè)上房源的頁(yè)數(shù)。
鏈家網(wǎng)二手房主頁(yè)截圖上半部分:
二手房房源信息頁(yè)面如下圖。我們需要采集的目標(biāo)數(shù)據(jù)就在該頁(yè)面,包括基本信息、房屋屬性和交易屬性三大類。各類信息包括的數(shù)據(jù)項(xiàng)如下:
1)基本信息:小區(qū)名稱、所在區(qū)域、總價(jià)、單價(jià)。
2)房屋屬性:房屋戶型、所在樓層、建筑面積、戶型結(jié)構(gòu)、套內(nèi)面積、建筑類型、房屋朝向、建筑結(jié)構(gòu)、裝修情況、梯戶比例、配備電梯、產(chǎn)權(quán)年限。
3)交易屬性:掛牌時(shí)間、交易權(quán)屬、上次交易、房屋用途、房屋年限、產(chǎn)權(quán)所屬、抵押信息、房本備件。
網(wǎng)絡(luò)爬蟲程序關(guān)鍵問(wèn)題說(shuō)明
1)問(wèn)題1:鏈家網(wǎng)二手房主頁(yè)最多只顯示100頁(yè)的房源數(shù)據(jù),所以在收集二手房房源信息頁(yè)面URL地址時(shí)會(huì)收集不全,導(dǎo)致最后只能采集到部分?jǐn)?shù)據(jù)。
解決措施:將所有二手房數(shù)據(jù)分區(qū)域地進(jìn)行爬取,100頁(yè)最多能夠顯示3000套房,該區(qū)域房源少于3000套時(shí)可以直接爬取,如果該區(qū)域房源超過(guò)3000套可以再分成更小的區(qū)域。
2)問(wèn)題2:爬蟲程序如果運(yùn)行過(guò)快,會(huì)在采集到兩、三千條數(shù)據(jù)時(shí)觸發(fā)鏈家網(wǎng)的反爬蟲機(jī)制,所有的請(qǐng)求會(huì)被重定向到鏈家的人機(jī)鑒定頁(yè)面,從而會(huì)導(dǎo)致后面的爬取失敗。
解決措施:①為程序中每次http請(qǐng)求構(gòu)造header并且每次變換http請(qǐng)求header信息頭中USER_AGENTS數(shù)據(jù)項(xiàng)的值,讓請(qǐng)求信息看起來(lái)像是從不同瀏覽器發(fā)出的訪問(wèn)請(qǐng)求。②爬蟲程序每處理完一次http請(qǐng)求和響應(yīng)后,隨機(jī)睡眠1-3秒,每請(qǐng)求2500次后,程序睡眠20分鐘,控制程序的請(qǐng)求速度。
4 數(shù)據(jù)清洗
對(duì)于爬蟲程序采集得到的數(shù)據(jù)并不能直接分析,需要先去掉一些“臟”數(shù)據(jù),修正一些錯(cuò)誤數(shù)據(jù),統(tǒng)一所有數(shù)據(jù)字段的格式,將這些零散的數(shù)據(jù)規(guī)整成統(tǒng)一的結(jié)構(gòu)化數(shù)據(jù)。
原始數(shù)據(jù)主要需要清洗的部分
主要需要清洗的數(shù)據(jù)部分如下:
1)將雜亂的記錄的數(shù)據(jù)項(xiàng)對(duì)齊
2)清洗一些數(shù)據(jù)項(xiàng)格式
3)缺失值處理
3.2.3 數(shù)據(jù)清洗結(jié)果
數(shù)據(jù)清洗前原始數(shù)據(jù)如下圖,
清洗后的數(shù)據(jù)如下圖,可以看出清洗后數(shù)據(jù)已經(jīng)規(guī)整了許多。
5 數(shù)據(jù)聚類分析
該階段采用聚類算法中的k-means算法對(duì)所有二手房數(shù)據(jù)進(jìn)行聚類分析,根據(jù)聚類的結(jié)果和經(jīng)驗(yàn),將這些房源大致分類,已達(dá)到對(duì)數(shù)據(jù)概括總結(jié)的目的。在聚類過(guò)程中,我們選擇了面積、總價(jià)和單價(jià)這三個(gè)數(shù)值型變量作為樣本點(diǎn)的聚類屬性。
k-means算法原理
基本原理
k-Means算法是一種使用最普遍的聚類算法,它是一種無(wú)監(jiān)督學(xué)習(xí)算法,目的是將相似的對(duì)象歸到同一個(gè)簇中。簇內(nèi)的對(duì)象越相似,聚類的效果就越好。該算法不適合處理離散型屬性,但對(duì)于連續(xù)型屬性具有較好的聚類效果。
聚類效果判定標(biāo)準(zhǔn)
使各個(gè)樣本點(diǎn)與所在簇的質(zhì)心的誤差平方和達(dá)到最小,這是評(píng)價(jià)k-means算法最后聚類效果的評(píng)價(jià)標(biāo)準(zhǔn)。
算法實(shí)現(xiàn)步驟
1)選定k值
2)創(chuàng)建k個(gè)點(diǎn)作為k個(gè)簇的起始質(zhì)心。
3)分別計(jì)算剩下的元素到k個(gè)簇的質(zhì)心的距離,將這些元素分別劃歸到距離最小的簇。
4)根據(jù)聚類結(jié)果,重新計(jì)算k個(gè)簇各自的新的質(zhì)心,即取簇中全部元素各自維度下的算術(shù)平均值。
5)將全部元素按照新的質(zhì)心重新聚類。
6)重復(fù)第5步,直到聚類結(jié)果不再變化。
7)最后,輸出聚類結(jié)果。
算法缺點(diǎn)
雖然K-Means算法原理簡(jiǎn)單,但是有自身的缺陷:
1)聚類的簇?cái)?shù)k值需在聚類前給出,但在很多時(shí)候中k值的選定是十分難以估計(jì)的,很多情況我們聚類前并不清楚給出的數(shù)據(jù)集應(yīng)當(dāng)分成多少類才最恰當(dāng)。
2)k-means需要人為地確定初始質(zhì)心,不一樣的初始質(zhì)心可能會(huì)得出差別很大的聚類結(jié)果,無(wú)法保證k-means算法收斂于全局最優(yōu)解。
3)對(duì)離群點(diǎn)敏感。
4)結(jié)果不穩(wěn)定(受輸入順序影響)。
5)時(shí)間復(fù)雜度高O(nkt),其中n是對(duì)象總數(shù),k是簇?cái)?shù),t是迭代次數(shù)。
算法實(shí)現(xiàn)關(guān)鍵問(wèn)題說(shuō)明
K值的選定說(shuō)明
根據(jù)聚類原則:組內(nèi)差距要小,組間差距要大。我們先算出不同k值下各個(gè)SSE(Sum of
squared
errors)值,然后繪制出折線圖來(lái)比較,從中選定最優(yōu)解。從圖中,我們可以看出k值到達(dá)5以后,SSE變化趨于平緩,所以我們選定5作為k值。
初始的K個(gè)質(zhì)心選定說(shuō)明
初始的k個(gè)質(zhì)心選定是采用的隨機(jī)法。從各列數(shù)值最大值和最小值中間按正太分布隨機(jī)選取k個(gè)質(zhì)心。
關(guān)于離群點(diǎn)
離群點(diǎn)就是遠(yuǎn)離整體的,非常異常、非常特殊的數(shù)據(jù)點(diǎn)。因?yàn)閗-means算法對(duì)離群點(diǎn)十分敏感,所以在聚類之前應(yīng)該將這些“極大”、“極小”之類的離群數(shù)據(jù)都去掉,否則會(huì)對(duì)于聚類的結(jié)果有影響。離群點(diǎn)的判定標(biāo)準(zhǔn)是根據(jù)前面數(shù)據(jù)可視化分析過(guò)程的散點(diǎn)圖和箱線圖進(jìn)行判定。根據(jù)散點(diǎn)圖和箱線圖,需要去除離散值的范圍如下:
1)單價(jià):基本都在100000以內(nèi),沒有特別的異常值。
2)總價(jià):基本都集中在3000以內(nèi),這里我們需要去除3000外的異常值。
3)建筑面積:基本都集中在500以內(nèi),這里我們需要去除500外的異常值。
數(shù)據(jù)的標(biāo)準(zhǔn)化
因?yàn)榭們r(jià)的單位為萬(wàn)元,單價(jià)的單位為元/平米,建筑面積的單位為平米,所以數(shù)據(jù)點(diǎn)計(jì)算出歐幾里德距離的單位是沒有意義的。同時(shí),總價(jià)都是3000以內(nèi)的數(shù),建筑面積都是500以內(nèi)的數(shù),但單價(jià)基本都是20000以上的數(shù),在計(jì)算距離時(shí)單價(jià)起到的作用就比總價(jià)大,總價(jià)和單價(jià)的作用都遠(yuǎn)大于建筑面積,這樣聚類出來(lái)的結(jié)果是有問(wèn)題的。這樣的情況下,我們需要將數(shù)據(jù)標(biāo)準(zhǔn)化,即將數(shù)據(jù)按比例縮放,使之都落入一個(gè)特定區(qū)間內(nèi)。去除數(shù)據(jù)的單位限制,將其轉(zhuǎn)化為無(wú)量綱的純數(shù)值,便于不同單位或量級(jí)的指標(biāo)能夠進(jìn)行計(jì)算和比較。
我們將單價(jià)、總價(jià)和面積都映射到500,因?yàn)槊娣e本身就都在500以內(nèi),不要特別處理。單價(jià)在計(jì)算距離時(shí),需要先乘以映射比例0.005,總價(jià)需要乘以映射比例0.16。進(jìn)行數(shù)據(jù)標(biāo)準(zhǔn)化前和進(jìn)行數(shù)據(jù)標(biāo)準(zhǔn)化后的聚類效果對(duì)比如下:圖32、圖33是沒有數(shù)據(jù)標(biāo)準(zhǔn)化前的聚類效果散點(diǎn)圖;圖34、圖35是數(shù)據(jù)標(biāo)準(zhǔn)化后的聚類效果散點(diǎn)圖。
數(shù)據(jù)標(biāo)準(zhǔn)化前的單價(jià)與建筑面積聚類效果散點(diǎn)圖:
聚類結(jié)果分析
聚類結(jié)果如下
1)聚類結(jié)果統(tǒng)計(jì)信息如下:
2)聚類后的單價(jià)與建筑面積散點(diǎn)圖和總價(jià)與建筑面積散點(diǎn)圖。
3)聚類結(jié)果分組0、1、2、3、4的區(qū)域分布圖如下實(shí)例。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-808231.html
聚類結(jié)果分組0的區(qū)域分布圖如下:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-808231.html
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ù),初始化相關(guān)屬性"""
self._k = k
self._initCent = initCent#初始中心
self._max_iter = max_iter#最大迭代
#一個(gè)m*2的二維矩陣,矩陣第一列存儲(chǔ)樣本點(diǎn)所屬的族的索引值,
#第二列存儲(chǔ)該點(diǎn)與所屬族的質(zhì)心的平方誤差
self._clusterAssment = None#樣本點(diǎn)聚類結(jié)結(jié)構(gòu)矩陣
self._labels = None
self._sse = None#SSE(Sum of squared errors)平方誤差和
def _calEDist(self, arrA, arrB):
"""
功能:歐拉距離距離計(jì)算
輸入:兩個(gè)一維數(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):
"""
功能:曼哈頓距離距離計(jì)算
輸入:兩個(gè)一維數(shù)組
"""
return sum(np.abs(arrA-arrB))
def _randCent(self, data_X, k):
"""
功能:隨機(jī)選取k個(gè)質(zhì)心
輸出:centroids #返回一個(gè)m*n的質(zhì)心矩陣
"""
n = data_X.shape[1] - 3 #獲取特征值的維數(shù)(要?jiǎng)h除一個(gè)用于標(biāo)記的id列和經(jīng)緯度值)
centroids = np.empty((k,n)) #使用numpy生成一個(gè)k*n的矩陣,用于存儲(chǔ)質(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):
"""
輸入:一個(gè)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] #獲取樣本的個(gè)數(shù)
#一個(gè)m*2的二維矩陣,矩陣第一列存儲(chǔ)樣本點(diǎn)所屬的族的編號(hào),
#第二列存儲(chǔ)該點(diǎn)與所屬族的質(zhì)心的平方誤差
self._clusterAssment = np.zeros((m,2))
#創(chuàng)建k個(gè)點(diǎn),作為起始質(zhì)心
if self._initCent == 'random':
self._centroids = self._randCent(data_X, self._k)
clusterChanged = True
#循環(huán)最大迭代次數(shù)
for _ in range(self._max_iter): #使用"_"主要是因?yàn)楹竺鏇]有用到這個(gè)值
clusterChanged = False
for i in range(m): #將每個(gè)樣本點(diǎn)分配到離它最近的質(zhì)心所屬的族
minDist = np.inf #首先將minDist置為一個(gè)無(wú)窮大的數(shù)
minIndex = -1 #將最近質(zhì)心的下標(biāo)置為-1
for j in range(self._k): #次迭代用于尋找元素最近的質(zhì)心
arrA = self._centroids[j,:]
arrB = data_X[i,1:4]
distJI = self._calEDist(arrA, arrB) #計(jì)算距離
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:#若所有樣本點(diǎn)所屬的族都不改變,則已收斂,結(jié)束迭代
break
for i in range(self._k):#更新質(zhì)心,將每個(gè)族中的點(diǎn)的均值作為質(zhì)心
index_all = self._clusterAssment[:,0] #取出樣本所屬簇的編號(hào)
value = np.nonzero(index_all==i) #取出所有屬于第i個(gè)簇的索引值
ptsInClust = data_X[value[0]] #取出屬于第i個(gè)簇的所有樣本點(diǎn)
self._centroids[i,:] = np.mean(ptsInClust[:,1:4], axis=0) #計(jì)算均值,賦予新的質(zhì)心
self._labels = self._clusterAssment[:,0]
self._sse = sum(self._clusterAssment[:,1])
def predict(self, X):#根據(jù)聚類結(jié)果,預(yù)測(cè)新輸入數(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):#將每個(gè)樣本點(diǎn)分配到離它最近的質(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):
"""
功能:歐拉距離距離計(jì)算
輸入:兩個(gè)一維數(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):#計(jì)算每個(gè)樣本點(diǎn)與質(zhì)心之間初始的平方誤差
self._clusterAssment[j,1] = self._calEDist(np.asarray(centroid0), \
X[j,:])**2
while (len(centList) < self._k):
lowestSSE = np.inf
#嘗試劃分每一族,選取使得誤差最小的那個(gè)族進(jìn)行劃分
for i in range(len(centList)):
index_all = self._clusterAssment[:,0] #取出樣本所屬簇的索引值
value = np.nonzero(index_all==i) #取出所有屬于第i個(gè)簇的索引值
ptsInCurrCluster = X[value[0],:] #取出屬于第i個(gè)簇的所有樣本點(diǎn)
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
#該族被劃分成兩個(gè)子族后,其中一個(gè)子族的索引變?yōu)樵宓乃饕?/span>
#另一個(gè)子族的索引變?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é)果,預(yù)測(cè)新輸入數(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):#將每個(gè)樣本點(diǎn)分配到離它最近的質(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
7 最后
到了這里,關(guān)于python畢設(shè)選題 - 大數(shù)據(jù)二手房數(shù)據(jù)爬取與分析可視化 -python 數(shù)據(jù)分析 可視化的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!