logistic回归—R语言实操

回归模型是统计中最常用的模型之一,它主要用于解释和预测。本文要讲的Logistic回归又是一种应用非常广泛的回归模型。

线性回归是人们最熟悉的回归模型,简要表达为:Y = aX + b,其中X和Y都是连续数据,比如想知道人的智力(X)能不能预测其未来的数学成绩(Y)。然而有时候,Y不是连续数据,比如一个人会不会因为你的工资数额(X)而喜欢你(Y),这里的Y只有喜欢和不喜欢两类;再比如今天股市的收益率(X)能不能预测明天股市的涨跌(Y),这里的Y只有涨和跌两类。进行线性回归需要满足很多前提条件,比如balabala…,Logistic回归则没那么多条条框框。

与线性回归一样,Logistic回归的简要表达也是:Y = aX + b。不同点在于Y。线性回归中一般Y就是Y,而在Logistic回归中,由于Y只有两种选择(事实上也可以有3、4、5…等等多类,但不在本文讨论范围),喜欢或不喜欢、涨或跌、成功或失败、发生或未发生、正面或反面、男或女…,也就是说,Y要么是1,要么是0。假设Y是1的概率是P1,是0的概率是P0,P1+P0 = 1Logistic回归的Y是P1/P0的自然对数,即:

Y = LN(P1/P0) = LN(P1/(1-P1)) = aX + b

例子1:假如TA喜欢你的概率是P1 = 50%,不喜欢你的概率是P0 = 50%,则:Y = LN(50%/50%) = LN(0.5/(1-0.5)) = LN(0.5/0.5) = 0 = aX + b。

那么,再假设斜率a = 2,截距b = 2,得:0 = 2X + 2;求得X = 1,之所以不是-1,是因为工资数额不可能是负数。

例子1说明,假如你的工资是1,那么TA有50%的可能性喜欢你,有50%的可能性不喜欢你。

那如果你的工资是2呢?即需要求Y,有:

LN(P1/(1-P1)) = 2X + 2 = 2*2 + 2 = 6 ,

P1/(1-P1) = EXP(6) = 403

P1 = 403 – 403*P1

求得P1 ≈ 0.998

没错,如果你的工资翻了一倍,TA有99.8%的可能性喜欢你

这就是Logistic回归的另一个重要作用:基于概率来判定类别!


接下来结合R语言进一步讲解Logistic回归。

如果你见到的数据是下面这样的:一个变量(Y)不是0就是1,另一个变量(X)是连续的,你可能就要考虑使用逻辑回归来进行分析了。

通过以下R代码可调取上述数据:

library(DAAG)

head(anesthetic)

anesthetic”数据中,conc表示药物使用剂量,move/nomove表示用药后患者是否可以正常移动。

用以下R代码对数据“anesthetic”进行加工:

anestot=aggregate(anesthetic[,c('move','nomove')],by=list(conc=anesthetic$conc),FUN=sum)

得到anestot数据:

如前所述,当Y是两类时,在本例中,move的反面是nomove,也就是move=0时,nomove=1,反之亦然。而与Y对应的X实际上也可以分类整理,比如在本例中,注射剂量conc=0.8的有7人,其中6人可以移动,1人无法移动。

通过以下R代码对数据“anestot”进行加工:

anestot$total = apply(anestot[,c('move','nomove')],1,sum)

anestot$prop = round(anestot$nomove/anestot$total,3)

anestot$logit = round(log(anestot$prop/(1-anestot$prop)),3)

anestot”变为:

上表中的prop为某剂量下,不能移动的人数占总人数的比例,也就是Y是1的概率:P1。logit = LN(P1/(1-P1))。至此,logistic回归变成了:logit = a * conc + b

以上是logistic回归的前期数据整理过程。

针对不同类型的数据形式,R语言有相应的代码可以进行分析,其结果相同:

anes1=glm(nomove~conc,family=binomial(link='logit'),data=anesthetic)

anes2=glm(cbind(nomove,move)~conc,family=binomial(link='logit'),data=anestot)

anes3=glm(prop~conc,family=binomial(link='logit'),weights=total,data=anestot)

若是未经整理的原始数据,如anesthetic数据表,使用anes1语句分析;若是数据已经过整理,如anestot数据表,使用anes2、anes3语句。

分析可求得:a = 5.57,b = -6.47。

在logistic回归中,如果假定50%是临界值(即下文会详细说到的阈值),P1>50%意味着Y=1,P1<50%则意味着Y=0。因此,无论是超过临界值还是低于临界值,都意味着选择的改变。若假定50%是临界值,如果a、b已知,则可求取临界的X值临界P值=50%时,Y = 0 ,至于Y为什么等于0,以及如何求临界X值,详见上述例子1)。

求临界X值的R代码:

X = abs(anes1$coefficients[1]/anes1$coefficients[2])

求得:X = 1.16

结果意味着,当用药剂量超过1.16时,病人很可能就无法移动了!如果这是一项正式的研究,结果的准确性是很重要的!

cdplot(factor(nomove)~conc,data=anesthetic,main='条件密度图',ylab='病人移动',xlab='麻醉剂量') ##通过绘图也大致可以看出临界X值在1.2附近。

值得一提的是,一般来说,进行logistic回归分析的样本量不应该低于100,并且随着变量的增加,样本量也需要相应增加。

至此,本文用简单的例子讲述了什么是logistic回归。但这批数据过少,变量也只有1个,接下来,本文将用一个更为复杂的实际例子,详细讲述如何使用logistic回归进行建模分析。


本例将使用logistic回归预测股市的涨跌,部分内容来自书籍《An Introduction to Statistical Learning with Applications in R》

R代码调取数据:

library (ISLR)

head(Smarket)

此数据集包括从2001~2005年标准普尔500指数的投资回报率。Lag1~Lag5为过去5天每个交易日的投资回报率,Today为当日的投资回报率,Direction表示当日投资回报率是正数Up (涨),还是负数Down (跌)。

本文将使用2001~2004年的数据进行建模,然后预测2005年的涨跌

 
train =(Smarket$Year <2005) ##设定数据分割标准
Smarket.2005 = Smarket[! train ,]##提取2005年的数据
 
glm.fits1 = glm(Direction ~ Lag1+Lag2+Lag3+Lag4+Lag5+Volume, data=Smarket,
                family =binomial ,subset =train)##使用2001-2004的数据建模
 
glm.probs1 = predict(glm.fits1, Smarket.2005, type="response")##通过2001-2004的数据模型,预测2005年的股市涨跌
 
Direction.2005 = Smarket$Direction [! train]##2005年每天的实际涨跌情况
 
glm.pred1=rep("Down" ,252)
glm.pred1[glm.probs1>0.5]="Up"##涨跌预测结果
glm.pred1 = as.factor(glm.pred1)
 
table(glm.pred1, Direction.2005)###实际情况与预测情况对比
 
mean(glm.pred1 == Direction.2005)###预测正确的概率
mean(glm.pred1 != Direction.2005)###预测错误的概率
 

通过计算结果可知,预测正确率为48%!这是一个糟糕的结果,因为只有up&down两类,即使是瞎猜,正确率也应该有50%!

summary(glm.fits1)$coef ##回到最开始的模型数据,观察Pr(>|z|)值,P值越小,说明因素的影响力越大,越关键。
cor(Smarket[,-9]) ##同样,可以在建模之初通过相关分析,迅速锁定哪些因素与要预测的变量相关高。

通过不断尝试,最终确定,用过去两天的投资回报率进行建模来预测今天的回报率最为准确(这说明在建模过程中,并非变量越多越好)。准确率为56%,比瞎猜好一些了!具体代码如下:

glm.fits2 = glm(Direction ~ Lag1+Lag2, data=Smarket,
                family =binomial ,subset =train)##使用2001-2004的数据建模
 
glm.probs2 = predict(glm.fits2, Smarket.2005, type="response")##通过2001-2004的数据模型,预测2005年的股市涨跌
 
Direction.2005 = Smarket$Direction [! train]##2005年每天的实际涨跌情况
 
glm.pred2 = rep("Down" ,252)
glm.pred2[glm.probs2>0.5] = "Up"##涨跌预测结果
glm.pred2 = as.factor(glm.pred2)
glm.pred2 = factor(glm.pred2, levels=rev(levels(glm.pred2)))
 
mean(glm.pred2 == Direction.2005)## 预测正确的概率
mean(glm.pred2 != Direction.2005)## 预测错误的概率
 
 
table(glm.pred2, Direction.2005)###这句代码的输出被称为分类矩阵,如下。
 
 
AP = 106+35 ##实际为up的天数
AN = 76+35 ##实际为down的天数
 
TP = 106 ##正确地预测up为up的天数
TN = 35 ##正确地预测down为down的天数
FP = 76 ##错误地预测down为up的天数
FN = 35 ##错误地预测up为down的天数
 
TPTN = 35+106 #预测正确的天数,同是down和同是up
FPFN = 35+76 #预测错误的天数,一个为up另一个为down
 
total = 35+106+35+76
rightR = (TP+TN)/total ##预测正确的概率
wrongR = (FP+FN)/total ##预测错误的概率
 
TPR = TP/AP  ###TPR全称为True Positive Rate,表明了将实际的up正确地预测为up的概率
FPR = FP/AN ###FPR全称为False Positive Rate,表明了将实际的down错误地预测为up的概率
 
 

下表为英文对照~~~

 
 

在建模过程中,目标是希望TPR尽量的大,而FPR尽量的小,最佳状况是TPR=1,FPR=0。但是,TPR和FPR会同时增加或同时减小。因此,需要找到一个临界点,在这个临界点上,TPR尽可能的大,FPR尽可能的小!

为了找到这个临界点,需要画一条曲线,这条曲线被称为ROC(receiver operating characteristic)曲线,中文译为:接受者操作特性曲线

来画一下:

library(pROC)
roc1=roc(Direction.2005, glm.probs1)
roc2=roc(Direction.2005, glm.probs2)
plot(roc2,print.auc=TRUE,auc.polygon=TRUE,
grid=c(0.1,0.2),grid.col=c("green","red"),max.auc.polygon=TRUE,
auc.polygon.col="lightblue",print.thres="best")
plot(roc1, add=TRUE, col="red")
plot(smooth(roc2), add=TRUE, col="blue")

上图中有很多起伏的黑线即是最佳模型的ROC曲线(预测准确率56%),蓝色的线为它的平滑曲线。ROC曲线离对角线(那条不太看得清的灰线)越远,表明模型的预测效果越好。56%的准确率实在不怎么好,所以它们离得很近。红色的是预测准确率只有48%模型的ROC曲线,通常会将多个预测模型的ROC曲线绘制在一起,直观地鉴别模型优劣。

AUC(Area Under Curve)是另一个模型评估指标,即ROC曲线下的面积(蓝色区域的面积)。图中显示AUC=0.558,意思是在这块正方形的区域中,蓝色的面积占55.8%。AUC最大为1,当AUC≤0.5时,表明预测模型毫无价值!一般情况,当AUC大于0.8时,才说明该预测模型有较好的价值。AUC表示了模型的解释能力或预测能力!

在ROC2曲线上,有1个小黑点,被称为阈值点ROC曲线越靠近左上角,说明模型预测能力越好阈值点是ROC曲线上离左上角最近的一点,其值为0.502(0.387,0.738)。

上图是怎么画出来的?横坐标和纵坐标分别是什么?阈值点是怎么算出来的?它的意义是什么?用代码来解释会很清楚。

ROC曲线绘制过程代码如下:

data=data.frame(prob=glm.probs2,obs=Direction.2005)##将预测概率和实际结果放在一个数据框中
data=data[order(data$prob),]##将预测概率按照从低到高排序
n=nrow(data)
tpr=fpr=rep(0,n)
head(data)
tpr = c()
fpr = c()
tnr = c()
for (i in 1:n){
  threshold=data$prob[i]
  tp=sum(data$prob>threshold&data$obs=="Up")##Up预测判定为Up
  fp=sum(data$prob>threshold&data$obs=="Down")##Down预测判定为Up
  tn=sum(data$prob<threshold&data$obs=="Down")##Down预测判定为Down
  fn=sum(data$prob<threshold&data$obs=="Up")##Up预测判定为Down
  tpr[i]=tp/(tp+fn)  ##即上图和本图的纵坐标
  fpr[i]=fp/(tn+fp)  ##即本图的横坐标
  tnr[i]=1-fpr[i]  ##即上图的横坐标。注意!上图横坐标是从1到0,故横坐标是1- fpr
}
plot(fpr,tpr,type='l')
abline(a=0,b=1) ###至此完成简易版ROC曲线的绘制
point = tpr - fpr##求tpr与fpr的差
options(digits = 3)
tdata = cbind(data,tpr,fpr,tnr,point) ##将预测概率、实际结果、tpr、fpr、tnr、point放在一个数据框
tdata = tdata[order(tdata$point,decreasing = TRUE),]##将tpr与fpr的差按照从高到低排序
head(tdata)##结果如下
 
 
 

上表第一行即是阈值点0.502(0.387,0.738)中各值的来处及意义!

最佳模型应满足TPR尽量的大,FPR尽量的小,故可通过求两者之差,找到最大的差值,即为阈值,此时模型表现最佳。

阈值0.502是一个预测概率(即tdata$prob中的一个值),它的意思是当概率超过50.2%时,预测结果为Up(对应代码为1),反之为Down(对应代码为0)。对应的TPR=0.736,FPR=0.613,TNR=0.387(至于它为什么要用第3行的TPR=0.738,我也不清楚:-)。由此可知,上图中的横坐标是TNR,TNR = 1-FPR,又称特异性;纵坐标即TPR,又称敏感性

当阈值为0.502时,该模型有最佳表现,满足TPR和TNR尽量的大,FPR和FNR尽量的小

 

至此,本文通过两个简单的例子,运用R语言代码,Step by step的讲述了Logistic回归是什么,以及应该怎么用。要说明的是,Logistic回归在真实场景被广泛的应用,实际中需要考虑更多的因素,应用更加复杂。

注*本文部分内容来自网络或书籍。

###代码汇总###
 
library(DAAG)
head(anesthetic)
anestot=aggregate(anesthetic[,c('move','nomove')],by=list(conc=anesthetic$conc),FUN=sum)
 
anestot$total = apply(anestot[,c('move','nomove')],1,sum)
anestot$prop = round(anestot$nomove/anestot$total,3)
anestot$logit = round(log(anestot$prop/(1-anestot$prop)),3)
 
anes1=glm(nomove~conc,family=binomial(link='logit'),data=anesthetic)
 
anes2=glm(cbind(nomove,move)~conc,family=binomial(link='logit'),data=anestot)
 
anes3=glm(prop~conc,family=binomial(link='logit'),weights=total,data=anestot)
 
X = abs(anes1$coefficients[1]/anes1$coefficients[2])
 
cdplot(factor(nomove)~conc,data=anesthetic,
       main='条件密度图',ylab='病人移动',xlab='麻醉剂量') ##通过绘图也大致可以看出临界X值在1.2附近。
 
library (ISLR)
head(Smarket)
 
train = (Smarket$Year <2005) ##设定数据分割标准
Smarket.2005 = Smarket[! train ,]##提取2005年的数据
 
glm.fits1 = glm(Direction ~ Lag1+Lag2+Lag3+Lag4+Lag5+Volume, data=Smarket,
                family =binomial ,subset =train)##使用2001-2004的数据建模
 
glm.probs1 = predict(glm.fits1, Smarket.2005, type="response")##通过2001-2004的数据模型,预测2005年的股市涨跌
 
Direction.2005 = Smarket$Direction [! train]##2005年每天的实际涨跌情况
 
glm.pred1=rep("Down" ,252)
glm.pred1[glm.probs1>0.5]="Up"##涨跌预测结果
glm.pred1 = as.factor(glm.pred1)
#glm.pred1 = factor(glm.pred1, levels=rev(levels(glm.pred1)))
 
table(glm.pred1, Direction.2005)###实际情况与预测情况对比
 
mean(glm.pred1 == Direction.2005)###预测正确的概率
mean(glm.pred1 != Direction.2005)###预测错误的概率
 
summary(glm.fits1)$coef ##回到最开始的模型数据,观察Pr(>|z|)值,P值越小,说明因素的影响力越大,越关键。
cor(Smarket[,-9]) ##同样,可以在建模之初通过相关分析,迅速锁定哪些因素与要预测的变量相关高。
 
 
 
glm.fits2 = glm(Direction ~ Lag1+Lag2, data=Smarket,
                family =binomial ,subset =train)##使用2001-2004的数据建模
 
glm.probs2 = predict(glm.fits2, Smarket.2005, type="response")##通过2001-2004的数据模型,预测2005年的股市涨跌
 
Direction.2005 = Smarket$Direction [! train]##2005年每天的实际涨跌情况
 
glm.pred2 = rep("Down" ,252)
glm.pred2[glm.probs2>0.5] = "Up"##涨跌预测结果
glm.pred2 = as.factor(glm.pred2)
glm.pred2 = factor(glm.pred2, levels=rev(levels(glm.pred2)))
 
mean(glm.pred2 == Direction.2005)## 预测正确的概率
mean(glm.pred2 != Direction.2005)## 预测错误的概率
 
 
table(glm.pred2, Direction.2005)###这句代码的输出被称为分类矩阵。
 
 
AP = 106+35 ##实际为up的天数
AN = 76+35 ##实际为down的天数
 
TP = 106 ##正确地预测up为up的天数
TN = 35 ##正确地预测down为down的天数
FP = 76 ##错误地预测down为up的天数
FN = 35 ##错误地预测up为down的天数
 
TPTN = 35+106 #预测正确的天数,同是down和同是up
FPFN = 35+76 #预测错误的天数,一个为up另一个为down
 
total = 35+106+35+76
rightR = (TP+TN)/total ##预测正确的概率
wrongR = (FP+FN)/total ##预测错误的概率
 
TPR = TP/AP  ###TPR全称为True Positive Rate,表明了将实际的up正确地预测为up的概率
FPR = FP/AN ###FPR全称为False Positive Rate,表明了将实际的down错误地预测为up的概率
 
library(pROC)
roc1=roc(Direction.2005, glm.probs1)
roc2=roc(Direction.2005, glm.probs2)
plot(roc2,print.auc=TRUE,auc.polygon=TRUE,
     grid=c(0.1,0.2),grid.col=c("green","red"),max.auc.polygon=TRUE,
     auc.polygon.col="lightblue",print.thres="best")
plot(roc1, add=TRUE, col="red")
plot(smooth(roc2), add=TRUE, col="blue")
 
 
data=data.frame(prob=glm.probs2,obs=Direction.2005)##将预测概率和实际结果放在一个数据框中
data=data[order(data$prob),]##将预测概率按照从低到高排序
n=nrow(data)
tpr=fpr=rep(0,n)
head(data)
tpr = c()
fpr = c()
tnr = c()
for (i in 1:n){
  threshold=data$prob[i]
  tp=sum(data$prob>threshold&data$obs=="Up")##Up预测判定为Up
  fp=sum(data$prob>threshold&data$obs=="Down")##Down预测判定为Up
  tn=sum(data$prob<threshold&data$obs=="Down")##Down预测判定为Down
  fn=sum(data$prob<threshold&data$obs=="Up")##Up预测判定为Down
  tpr[i]=tp/(tp+fn)  ##即上图和本图的纵坐标
  fpr[i]=fp/(tn+fp)  ##即本图的横坐标
  tnr[i]=1-fpr[i]  ##即上图的横坐标。注意!上图横坐标是从1到0,故横坐标是1- fpr
}
plot(fpr,tpr,type='l')
abline(a=0,b=1) ###至此完成简易版ROC曲线的绘制
 
point = tpr - fpr##求tpr与fpr的差
options(digits = 3)
tdata = cbind(data,tpr,fpr,tnr,point) ##将预测概率、实际结果、tpr、fpr、tnr、point放在一个数据框
tdata = tdata[order(tdata$point,decreasing = TRUE),]##将tpr与fpr的差按照从高到低排序
head(tdata)##结果如下

发表评论

您的电子邮箱地址不会被公开。