一. 問題描述
每個人都希望自己能獲得更高的收入,而影響收入高低的因素有很多,能否通過大數據分析來找出對收入影響相對較大的因素?
二. 研究意義
如果我們知道對收入高低起決定性的作用,或者哪些因素組合在一起也能增大收入的可能性,那可以幫助很多人少走彎路,朝着正確的方向努力,早日達到目標。
三. 數據預處理
1. 選取數據集
本報告選取“adult”數據集,由美國人口普查數據集庫抽取而來。
該數據集類變量為年收入是否超過50k,屬性變量包含年齡,工種,學歷,職業,人種等14個屬性變量,其中有7個類別型變量。共有30000多條數據。
2. 預處理
由於capital-gain、capital-loss屬性缺失70%以上的數據,所以選擇刪去這兩個屬性。
在其他類變量中,有缺少或異常屬性400多條,占總數據比重較小,也選擇刪去。
四. 數據可視化
1.workclass
2.education
3.race
4.sex
5.marital-status
五. 算法選取與實現
本次報告中選用決策樹算法。決策樹是一種依托決策而建立起來的一種樹。在機器學習中,決策樹是一種預測模型,代表的是一種對象屬性與對象值之間的一種映射關系,每一個節點代表某個對象,樹中的每一個分叉路徑代表某個可能的屬性值,而每一個葉子節點則對應從根節點到該葉子節點所經歷的路徑所表示的對象的值。決策樹僅有單一輸出,如果有多個輸出,可以分別建立獨立的決策樹以處理不同的輸出。
由於數據量過大,普通決策樹不能達到預期效果,所以再用預剪枝進行處理。預剪枝是在決策樹生成過程中,在划分節點時,若該節點的划分沒有提高其在訓練集上的准確率,則不進行划分。
下面是預剪枝決策樹程序
1. 計算數據集的基尼系數
def calcGini(dataSet): numEntries=len(dataSet) labelCounts={} #給所有可能分類創建字典 for featVec in dataSet: currentLabel=featVec[-1] if currentLabel not in labelCounts.keys(): labelCounts[currentLabel]=0 labelCounts[currentLabel]+=1 Gini=1.0 #以2為底數計算香農熵 for key in labelCounts: prob = float(labelCounts[key])/numEntries Gini-=prob*prob return Gini
2. 對離散變量划分數據集,取出該特征取值為value的所有樣本
def splitDataSet(dataSet,axis,value): retDataSet=[] for featVec in dataSet: if featVec[axis]==value: reducedFeatVec=featVec[:axis] reducedFeatVec.extend(featVec[axis+1:]) retDataSet.append(reducedFeatVec) return retDataSet
3. 對連續變量划分數據集,direction規定划分的方向,決定是划分出小於value的數據樣本還是大於value的數據樣本集
def splitContinuousDataSet(dataSet,axis,value,direction): retDataSet=[] for featVec in dataSet: if direction==0: if featVec[axis]>value: reducedFeatVec=featVec[:axis] reducedFeatVec.extend(featVec[axis+1:]) retDataSet.append(reducedFeatVec) else: if featVec[axis]<=value: reducedFeatVec=featVec[:axis] reducedFeatVec.extend(featVec[axis+1:]) retDataSet.append(reducedFeatVec) return retDataSet
4. 選擇最好的數據集划分方式
def chooseBestFeatureToSplit(dataSet,labels): numFeatures=len(dataSet[0])-1 bestGiniIndex=100000.0 bestFeature=-1 bestSplitDict={} for i in range(numFeatures): featList=[example[i] for example in dataSet] #對連續型特征進行處理 if type(featList[0]).__name__=='float' or type(featList[0]).__name__=='int': #產生n-1個候選划分點 sortfeatList=sorted(featList) splitList=[] for j in range(len(sortfeatList)-1): splitList.append((sortfeatList[j]+sortfeatList[j+1])/2.0) bestSplitGini=10000 slen=len(splitList) #求用第j個候選划分點划分時,得到的信息熵,並記錄最佳划分點 for j in range(slen): value=splitList[j] newGiniIndex=0.0 subDataSet0=splitContinuousDataSet(dataSet,i,value,0) subDataSet1=splitContinuousDataSet(dataSet,i,value,1) prob0=len(subDataSet0)/float(len(dataSet)) newGiniIndex+=prob0*calcGini(subDataSet0) prob1=len(subDataSet1)/float(len(dataSet)) newGiniIndex+=prob1*calcGini(subDataSet1) if newGiniIndex<bestSplitGini: bestSplitGini=newGiniIndex bestSplit=j #用字典記錄當前特征的最佳划分點 bestSplitDict[labels[i]]=splitList[bestSplit] GiniIndex=bestSplitGini #對離散型特征進行處理 else: uniqueVals=set(featList) newGiniIndex=0.0 #計算該特征下每種划分的信息熵 for value in uniqueVals: subDataSet=splitDataSet(dataSet,i,value) prob=len(subDataSet)/float(len(dataSet)) newGiniIndex+=prob*calcGini(subDataSet) GiniIndex=newGiniIndex if GiniIndex<bestGiniIndex: bestGiniIndex=GiniIndex bestFeature=i #若當前節點的最佳划分特征為連續特征,則將其以之前記錄的划分點為界進行二值化處理 #即是否小於等於bestSplitValue #並將特征名改為 name<=value的格式 if type(dataSet[0][bestFeature]).__name__=='float' or type(dataSet[0][bestFeature]).__name__=='int': bestSplitValue=bestSplitDict[labels[bestFeature]] labels[bestFeature]=labels[bestFeature]+'<='+str(bestSplitValue) for i in range(shape(dataSet)[0]): if dataSet[i][bestFeature]<=bestSplitValue: dataSet[i][bestFeature]=1 else: dataSet[i][bestFeature]=0 return bestFeature
5. 特征若已經划分完,節點下的樣本還沒有統一取值,則需要進行投票
def majorityCnt(classList): classCount={} for vote in classList: if vote not in classCount.keys(): classCount[vote]=0 classCount[vote]+=1 return max(classCount)
6.由於在Tree中,連續值特征的名稱以及改為了 feature<=value的形式,因此對於這類特征,需要利用正則表達式進行分割,獲得特征名以及分割閾值
def classify(inputTree,featLabels,testVec): firstStr=list(inputTree.keys())[0] if '<=' in firstStr: featvalue=float(re.compile("(<=.+)").search(firstStr).group()[2:]) featkey=re.compile("(.+<=)").search(firstStr).group()[:-2] secondDict=inputTree[firstStr] featIndex=featLabels.index(featkey) if testVec[featIndex]<=featvalue: judge=1 else: judge=0 for key in secondDict.keys(): if judge==int(key): if type(secondDict[key]).__name__=='dict': classLabel=classify(secondDict[key],featLabels,testVec) else: classLabel=secondDict[key] else: secondDict=inputTree[firstStr] featIndex=featLabels.index(firstStr) for key in secondDict.keys(): if testVec[featIndex]==key: if type(secondDict[key]).__name__=='dict': classLabel=classify(secondDict[key],featLabels,testVec) else: classLabel=secondDict[key] return classLabel def testing(myTree,data_test,labels): error=0.0 for i in range(len(data_test)): if classify(myTree,labels,data_test[i])!=data_test[i][-1]: error+=1 print ('myTree %d' %error) return float(error) def testingMajor(major,data_test): error=0.0 for i in range(len(data_test)): if major!=data_test[i][-1]: error+=1 print ('major %d' %error) return float(error)
7.主程序,遞歸產生決策樹
def createTree(dataSet,labels,data_full,labels_full,data_test): classList=[example[-1] for example in dataSet] if classList.count(classList[0])==len(classList): return classList[0] if len(dataSet[0])==1: return majorityCnt(classList) temp_labels=copy.deepcopy(labels) bestFeat=chooseBestFeatureToSplit(dataSet,labels) bestFeatLabel=labels[bestFeat] myTree={bestFeatLabel:{}} if type(dataSet[0][bestFeat]).__name__=='str': currentlabel=labels_full.index(labels[bestFeat]) featValuesFull=[example[currentlabel] for example in data_full] uniqueValsFull=set(featValuesFull) featValues=[example[bestFeat] for example in dataSet] uniqueVals=set(featValues) del(labels[bestFeat]) #針對bestFeat的每個取值,划分出一個子樹。 for value in uniqueVals: subLabels=labels[:] if type(dataSet[0][bestFeat]).__name__=='str': uniqueValsFull.remove(value) myTree[bestFeatLabel][value]=createTree(splitDataSet\ (dataSet,bestFeat,value),subLabels,data_full,labels_full,\ splitDataSet(data_test,bestFeat,value)) if type(dataSet[0][bestFeat]).__name__=='str': for value in uniqueValsFull: myTree[bestFeatLabel][value]=majorityCnt(classList) #進行測試,若划分沒有提高准確率,則不進行划分,返回該節點的投票值作為節點類別 if testing(myTree,data_test,temp_labels)<testingMajor(majorityCnt(classList),data_test): return myTree return majorityCnt(classList)
六. 結果分析
1. 未剪枝決策樹
2. 預剪枝決策樹