决策树-隐形眼镜分类(计算信息增益和信息熵以及准确率)
Title : 使用决策树预测隐形眼镜类型
# Description :隐形眼镜数据是非常著名的数据集 ,它包含很多患者眼部状况的观察条件以及医生推荐的隐形眼镜类型 。
# 隐形眼镜类型包括硬材质 、软材质以及不适合佩戴隐形眼镜 。数据来源于UCI数据库
# 为了更容易显示数据,本书对数据做了简单的更改 ,数据存储在源代码下载路径的文本文件中 。
运行结果展现
运行代码:
import numpy as np
import pandas as pd
import random
# 导入数据集
lenses = pd.read_table('lenses.txt', header=None)
lenses.columns = ['age', 'prescript', 'astigmatic', 'tearRate', 'class']
def calEnt(dataSet):
# 定义了一个函数calEnt,接受一个数据集作为输入参数
n = dataSet.shape[0] #数据集总行数
iset = dataSet.iloc[:,-1].value_counts() #统计标签的所有类别
p = iset/n #每一类标签所占比
ent = (-p*np.log2(p)).sum() #计算信息熵,即对每个类别的比例进行取对数并求和的负值
return ent
#选择最优的列进行切分
def bestSplit(dataSet):
baseEnt = calEnt(dataSet) #计算原始熵
bestGain = 0 #初始化信息增益
axis = -1 #初始化最佳切分列,标签列
for i in range(dataSet.shape[1]-1): #对特征的每一列进行循环
levels= dataSet.iloc[:,i].value_counts().index #提取出当前列的所有取值
ents = 0 #初始化子节点的信息熵
for j in levels: #对当前列的每一个取值进行循环
childSet = dataSet[dataSet.iloc[:,i]==j] #某一个子节点的dataframe
ent = calEnt(childSet) #计算某一个子节点的信息熵
ents += (childSet.shape[0]/dataSet.shape[0])*ent #计算当前列的信息熵
# print(f'第{i}列的信息熵为{ents}')
print(f'第{i}列{j}类的信息熵为{ents}')
infoGain = baseEnt-ents #计算当前列的信息增益
# print(f'第{i}列的信息增益为{infoGain}')
print(f'第{i}列的信息增益为{infoGain}')
if (infoGain > bestGain):
bestGain = infoGain #选择最大信息增益
axis = i #最大信息增益所在列的索引
return axis
def mySplit(dataSet,axis,value):
col = dataSet.columns[axis]
redataSet = dataSet.loc[dataSet[col]==value,:].drop(col,axis=1)
return redataSet
def createTree(dataSet):
featlist=list(dataSet.columns) #提取出数据集所有的列
classlist=dataSet.iloc[:,-1].value_counts() #获取最后一列类标签
#判断最多标签数目是否等于数据集行数,或者数据集是否只有一列
if classlist[0]==dataSet.shape[0] or dataSet.shape[1] ==1:
return classlist.index[0] #如果是,返回类标签
axis=bestSplit(dataSet) #确定出当前最佳切分列的索引
bestfeat=featlist[axis] #获取该索引对应的特征
myTree={bestfeat:{}} #采用字典嵌套的方式存储树信息
del featlist[axis] #删除当前特征
valuelist=set(dataSet.iloc[:,axis]) #提取最佳切分列所有属性值
for value in valuelist: #对每一个属性值递归建树
myTree[bestfeat][value]=createTree(mySplit(dataSet,axis,value))
return myTree
def classify(inputTree,labels, testVec):
firstStr = next(iter(inputTree)) #获取决策树第一个节点
secondDict = inputTree[firstStr] #下一个字典
featIndex = labels.index(firstStr) #第一个节点所在列的索引
for key in secondDict.keys():
if testVec[featIndex] == key:
if type(secondDict[key]) == dict :
classLabel = classify(secondDict[key], labels, testVec)
else:
classLabel = secondDict[key]
return classLabel
def acc_classify(train,test):
inputTree = createTree(train) #根据测试集生成一棵树
labels = list(train.columns) #数据集所有的列名称
result = []
for i in range(test.shape[0]): #对测试集中每一条数据进行循环
testVec = test.iloc[i,:-1] #测试集中的一个实例
classLabel = classify(inputTree,labels,testVec) #预测该实例的分类
result.append(classLabel) #将分类结果追加到result列表中
test['predict']=result #将预测结果追加到测试集最后一列
acc = (test.iloc[:,-1]==test.iloc[:,-2]).mean() #计算准确率
print(f'模型预测准确率为{acc}')
return test
#划分训练集和测试集
'''
参数说明:
dataSet:输入的数据集
rate:训练集所占比例
train,test:切分好的训练集和测试集
'''
def randSplit(dataSet,rate):
l=list(dataSet.index) #提取出索引
random.shuffle(l) #随机打乱索引 索引后面的值并没有改变
dataSet.index=l #将打乱后的索引重新赋值给原数据集
n=dataSet.shape[0] #总行数
m=int(n*rate) #训练集的数量
train=dataSet.loc[range(m),:] #提取前m个记录作为训练集
test=dataSet.loc[range(m,n),:] #剩下的作为测试集
dataSet.index=range(dataSet.shape[0]) #更新数据集的索引 把dataset前面的索引更新为从0开始 前面已经打乱了索引值
test.index=range(test.shape[0]) #更新测试集的索引 把test前面的索引更新为从0开始
return train,test
dataSet=lenses
train1,test1=randSplit(lenses,0.8)
lensesTree=createTree(train1)
print(lensesTree)
print(acc_classify(train1,test1))