前言:之前研究方向是机器视觉,从openCV到Torch,因为更换行业的原因,最近突发奇想学习一下数据分析统计以及预测,看了一些大佬的描述,决定从决策树入手,因此有了以下叙述,旨在记录学习以及实战过程,便于后续自己需要时及时使用,刚开始学习,有误之处请私信告知,万分感谢。本文从决策树的构建开始,详细描述决策树算法的构建过程,掌握其原理,后利用Python sklearn快速构建随机森林,分别展示分类预测以及回归预测两种模型的建立、预测以及对应评价指标。
实战案例:1.决策树预测“银行是否放贷”。(数据集为虚构数据集)
实战案例:2.随机森林分类预测“是否购买该车辆”。(数据集为虚构数据集)
实战案例:3.随机森林回归预测“云南省玉溪市乡村振兴指数”,分析影响乡村振兴指数相关特征变量(数据集来源《地级市乡村振兴指数测算数据2000-2022年》)
PS:介于初学者难以分清回归预测和分类预测的使用场景,再此先提一句,节约时间。简单来讲,分类预测预测是的类,是一个种类,一个范围,而回归预测是一个连续的数值,可以是Float、int等等。
####################################################################################
一、决策树实战
链接: 决策树完整代码
- 决策树算法简介
介绍决策树算法的很多,就不照搬了,简单说下个人理解。核心要义就是通过熵值计算选择不同的特征作为主要节点以及次要节点(即分类条件),说到底该算法也就一个简单公式如下图:
- 决策树算法代码构建
(1)虚构一个银行是否发放贷款的数据集
def createDataSet():
dataSet = [[0, 0, 0, 0, 'no'],
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
labels = ['F1-AGE', 'F2-WORK', 'F3-HOME', 'F4-LOAN'] #yes or no 银行贷款否
return dataSet, labels
(2)构建决策树
def createTree(dataset,labels,featLabels):
classList = [example[-1] for example in dataset]
#将dataSet中的数据先按行依次放入example中
#然后取得example中的example[-1]元素(yes or no),放入列表classList中
#构建树的停止条件,树模型嵌套到最后分类完毕,符合后则跳出树模型嵌套
#########################################################################################
if classList.count(classList[0]) == len(classList):
#假设所有样本都为yes or no
#(len看classList中元素个数,如10个, .count看classList[0]这个子字符串在classList中出现次数)
return classList[0]
#一样的话返回哪个都一样
if len(dataset[0]) == 1:
#每一列作为一个根节点,当遍历完一个根节点后就要删除,当所有遍历完了之后
#dataset就只剩yes or no一个标签列,即F4-LOAN
return majorityCnt(classList)
#########################################################################################
bestFeat = chooseBestFeatureToSplit(dataset)
#选择最好特征进行分割,通过chooseBestFeatureToSplit函数后已获得当前情况下最好根节点特征
bestFeatLabel = labels[bestFeat]
featLabels.append(bestFeatLabel)
myTree = {bestFeatLabel:{}}
#构建嵌套树模型
del labels[bestFeat]
#删除上一根节点
featValue = [example[bestFeat] for example in dataset]
#取当前一列包含所有值
uniqueVals = set(featValue)
#看唯一值有多少个,就是看不同的值有几类
#有几个不同的值就分几个叉,for为了分完后继续分
for value in uniqueVals:
sublabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataset,bestFeat,value),sublabels,featLabels)
return myTree
(3)对createTree类函数中使用到的相关函数进行定义
def majorityCnt(classList):
classCount={}
for vote in classList: #遍历classList
if vote not in classCount.keys():classCount[vote] = 0
classCount[vote] += 1
sortedclassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
return sortedclassCount[0][0]
#chooseBestFeatureToSplit函数分别计算煽值,信息增益
#(注意createTree是递归调用,每次调用dataset少一列特征)
def chooseBestFeatureToSplit(dataset):
#计算当前特征数量 列数 labels标签数就是特征数量
numFeatures = len(dataset[0]) - 1
#当前煽值(没有设置根节点情况下的煽值) calcShannonEnt计算函数
baseEntropy = calcShannonEnt(dataset)
bestInfoGain = 0 #最好的信息增益
bestFeature = -1 #最好特征
#有多少特征遍历多少次
for i in range(numFeatures):
#提取出整个二维矩阵中的一列特征,用for遍历所有特征,分别计算
featList = [example[i] for example in dataset]
#set去除featList重复特征
uniqueVals = set(featList)
newEntropy = 0 #定义新的煽值,就是选中的特征节点下的每一个叉的煽值
#选定其中一个特征节点,然后根据该特征节点下对应的不同属性值所对应的最终结果
#计算该特征节点下各属性值对应的煽值
for val in uniqueVals:
##splitDataSet切分数据集,i表示当前遍历过程中选择的第几个特征,val表示哪个叉
#(例如sunny\rainy\overcast)
#subDataSet就是去分别算sunny\rainy\overcast的煽值,所以splitDataSet每次删除一叉
subDataSet = splitDataSet(dataset,i,val)
#当前一叉所有样本占整个跟节点中样本的比例
prob = len(subDataSet)/float(len(dataset))
#计算sunny等三个叉的煽值算出并加权prob,从而算出该特征节点后的煽值
newEntropy += prob * calcShannonEnt(subDataSet)
#计算信息增益,即使用该特征点作为根节点后的信息增益
infoGain = baseEntropy - newEntropy
#判断当前根节点是否提升分类效果
if (infoGain > bestInfoGain):
bestInfoGain = infoGain #当前最号的信息增益为infoGain
bestFeature = i #当前最好的特征索引为i
return bestFeature
def splitDataSet(dataset,axis,val):
retDataSet = [] #为了一会返回一个子集
for featVec in dataset: #遍历每一个样本,就是获取sunny下的3个no和2个yes,其余两叉同理获得
if featVec[axis] == val: #判断该样本是否属于当前val这个叉
reducedFeatVec = featVec[:axis] #删除当前列,但是为什么存疑
reducedFeatVec.extend(featVec[axis+1:]) #从删除列的下一列开始
retDataSet.append(reducedFeatVec)
return retDataSet
#计算煽值 = 求和[-(标签不同值个数/样本个数)*log(标签不同值个数/样本个数)]
def calcShannonEnt(dataset):
numexamples = len(dataset) #样本个数统计
labelCounts = {} # 创建一个空字典,用来统计 yes/no的个数
for featVec in dataset:#在dataset中遍历每一组数(行),赋值给featVec
currentlabel = featVec[-1] #最后一列是labels值,-1取矩阵最后一个值
if currentlabel not in labelCounts.keys(): #如果当前标签不存在,就加入字典就是没yes加入yes,有yes了那么出现次数+1
labelCounts[currentlabel] = 0
labelCounts[currentlabel] += 1 #统计不同标签出现次数
shannonEnt = 0 #计算煽值,先定义为0
for key in labelCounts:
prop = float(labelCounts[key])/numexamples
shannonEnt -= prop*log(prop,2)
return shannonEnt
(4)绘图函数
def getNumLeafs(myTree):
numLeafs = 0
firstStr = next(iter(myTree))
secondDict = myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':
numLeafs += getNumLeafs(secondDict[key])
else: numLeafs +=1
return numLeafs
def getTreeDepth(myTree):
maxDepth = 0
firstStr = next(iter(myTree))
secondDict = myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':
thisDepth = 1 + getTreeDepth(secondDict[key])
else: thisDepth = 1
if thisDepth > maxDepth: maxDepth = thisDepth
return maxDepth
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
arrow_args = dict(arrowstyle="<-")
font = FontProperties(fname=r"c:\windows\fonts\simsunb.ttf", size=14)
createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',
xytext=centerPt, textcoords='axes fraction',
va="center", ha="center", bbox=nodeType, arrowprops=arrow_args, FontProperties=font)
def plotMidText(cntrPt, parentPt, txtString):
xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
def plotTree(myTree, parentPt, nodeTxt):
decisionNode = dict(boxstyle="sawtooth", fc="0.8")
leafNode = dict(boxstyle="round4", fc="0.8")
numLeafs = getNumLeafs(myTree)
depth = getTreeDepth(myTree)
firstStr = next(iter(myTree))
cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
plotMidText(cntrPt, parentPt, nodeTxt)
plotNode(firstStr, cntrPt, parentPt, decisionNode)
secondDict = myTree[firstStr]
plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':
plotTree(secondDict[key],cntrPt,str(key))
else:
plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
def createPlot(inTree):
fig = plt.figure(1, facecolor='white') #创建fig
fig.clf() #清空fig
axprops = dict(xticks=[], yticks=[])
createPlot.ax1 = plt.subplot(111, frameon=False, **axprops) #去掉x、y轴
plotTree.totalW = float(getNumLeafs(inTree)) #获取决策树叶结点数目
plotTree.totalD = float(getTreeDepth(inTree)) #获取决策树层数
plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0; #x偏移
plotTree(inTree, (0.5,1.0), '') #绘制决策树
plt.show()
################################################################################################
(5)运行结果
(6)关于想通过决策树实现预测的可以参考该文章
链接: https://blog.csdn.net/m0_74216325/article/details/137967669
二、随机森林分类预测
链接: 随机森林分类预测完整代码
- 前言
通过Python sklearn构建随机森林实现预测,代码量相较于自行构建决策树少的多得多,作为项目实战,还是import一下sklearn,省时省力的。基于最近这段时间的学习,随机森林预测的难点其实不在于模型训练以及预测,而是在于前期的数据处理,所以本文后续着重讲数据如何处理。
简单贴一下,通过sklearn构建随机森林预测的代码吧,看看多么简洁。
x, x_test, y, y_test = train_test_split(x, y, test_size=0.3)
# 训练数据与测试数据分离,测试数据30%,训练数据70%
clf = RandomForestClassifier(n_estimators=60, max_depth=8)
# 随机森林分类器
clf = RandomForestRegressor()
# 随机森林回归器
clf.fit(x, y)
#进行数据拟合
y_hat = clf.predict(x)
#数据预测
- 数据处理
先谈谈核心思想吧,对于随机森林分类器而言,其输入的数据多为字符型数据,即每一个特征列中存在多种不同的属性值。
第一步:将每一列赋予一个特征名,通过字典操作实现。
data = pd.read_csv('car.data', header=None)
# 读取已有的数据
n_columns = len(data.columns)
# 获取数据元素的个数(data.columns获取data中数据的列名字,len之后获得列数)
columns = ['buy', 'maintain', 'doors', 'persons', 'boot', 'safety', 'accept']
# 设置一个列表,并存储不同的元素(用来赋值特征名)
new_columns = dict(list(zip(np.arange(n_columns), columns)))
# zip做的相当于把前后两个列表的元素按照顺序一一组合起来
# 然后变成列表,然后使用dict创建两个元素之间的对应关系
# 形成一个字典new_columns,将原数据的列名(0 1 2 3...)变为columns,键与值对应
data.rename(columns=new_columns, inplace=True)
# 对上面读取的数据进行一个重命名,将刚才制作的字典用于数据元素的命名
第二步:比较难理解的一步,每一列中存在不同的字符类型,将列的特征名与这些不同的字符类型组合,形成一个新的列,将该列里对应属性值换成布尔值。
x = pd.DataFrame()
# 创建一个dataframe类型的数据,然后保存到x当中
# print(columns[:-1]) 这里想表达的是去掉最后一个元素,保留其他的元素
for col in columns[:-1]:
t = pd.get_dummies(data[col],prefix=col)
#这里呢只是区分了不同的类型并进行了转化,但是前缀的名称
# 都是high low med 有可能会重复
#t = t.rename(columns=lambda x: col+'_'+str(x))
# 上面这一行的功能可以利用 ,prefix=col 写入上上一行命令括号内实现
x = pd.concat((x, t), axis=1)
# 最后这一步其实比较简单,每次处理好t之后把t依次添加到x后面
y = np.array(pd.Categorical(data['accept']).codes)
# pd.Categorical将data中'accept'列的重复值删除,保留唯一
# 通过上个函数我们可以获得data对应的行中数据的类型存在哪几种,并进行数字编号,然后codes直接获得对应的编号数字,重复的'accept'值对应编号数字相同
# 外面的np.array命令 将数据格式进行转化
第三步:将x和y放入训练即可。
结果看起来还不错。
三、随机森林回归预测“云南省玉溪市乡村振兴指数”,分析影响乡村振兴指数相关特征变量(数据集来源《地级市乡村振兴指数测算数据2000-2022年》)
链接: 随机森林回归预测“云南省玉溪市乡村振兴指数”,分析影响乡村振兴指数相关特征变量完整代码
1.数据集展示
2. 数据预处理
他们金融界好像叫做数据清洗?都一样,就清洗一下嘛。首先人为清洗,没用的数据肯定是前三列,地名年份啥的,直接删除;然后我们利用皮尔逊算法计算各个变量之间的正负相关性。代码如下:
#皮尔逊相关系数,统计各个变量之间的关系
correlations = data.corr(method='pearson')
plt.figure(figsize=(25, 15))
sns.heatmap(correlations, cmap="coolwarm", annot=True)
生成一个和混淆矩阵一模一样的皮尔逊系数图,看着真亲切啊对于做机器视觉的同志们。简单来说,红的正相关,蓝的负相关,这里我们选择预测的指标是乡村振兴指数(Rural Revitalization Index),所以我们就看那些变量对乡村振兴指数呈正相关就完事了(金融界叫做该变量对乡村振兴指数这个结果显著?现学现卖哈哈哈),至于蓝的就“清洗”了就行,就是不放入训练集。
3.模型训练
直接贴代码了,和随机森林分类预测就差一个函数而已,大家找不同。
xtrain, xtest, ytrain, ytest = train_test_split(x, y,
test_size=0.2,
random_state=42)
model = RandomForestRegressor()
model.fit(xtrain, ytrain)
#features = np.array([[3762.179232, 2655.104928, 714.9236304, 720.729408, 78.39743856, 4.573309104, 4.015618656, 3.653309856, 4.851138096, 1.783001136, 3.972406704,
# 4.213739712, 0.60168888, 4.396926576, 4.41530712, 5, 4.030552848, 2.826097008, 3.715697664, 8013.84649, 4, 3.068048592, 3.737789664, 4.352477472, 72.70653936, 7]])
print(xtest)
y_hat = model.predict(xtest)
print(y_hat)
4.模型预测
训练时只取到2021年的数据,然后对2022年的乡村振兴指数进行预测,预测值为 [0.08734576],实际值为[0.088368],可以看到效果还是非常不错滴。
5.模型评价
最终回归性能指标:
平均绝对误差:0.002115132000000075
均方误差:6.753657174640335e-06
均方根误差:0.0025987799396332764
R平方:0.9902446672031469
#输出回归方程
#data_labels = data.columns[1:]
#print("目标方程:")
#for i, feature in enumerate(data_labels):
# print("{} * {} +".format(model.feature_importances_[i], feature), end=' ')
# 评估模型
print('回归性能指标:')
print('Mean Absolute Error:', metrics.mean_absolute_error(ytest, y_hat))
print('Mean Squared Error:', metrics.mean_squared_error(ytest, y_hat))
print('Root Mean Squared Error:',
np.sqrt(metrics.mean_squared_error(ytest, y_hat)))
from sklearn.metrics import mean_squared_error, r2_score
r2 = r2_score(ytest, y_hat)
print('R-squared:', r2)
# 查看模型的特征重要性
# 分离特征和目标变量
X = data.drop('Rural Revitalization Index', axis=1)
y = data['Rural Revitalization Index']
feature_importances = model.feature_importances_
for feature, importance in zip(X.columns, feature_importances):
print(f'{feature}: {importance}')