다중회귀모형에서 여러 개의 설명변수가 있는 경우 각 변수들의 상대적인 중요성을 평가하기 위한 방법은 모든 가능한 서브모형을 만들고 설명변수 하나를 추가하였을 때 \(R^2\) 값이 평균적으로 어느 정도 증가하는지 구해 봄으로써 알 수 있다. 다음 함수는 Robert I. Kabacoff의 R in action(2nd edition)책에 소개되어 있는 함수로 회귀모형에서 상대적인 중요성을 보여준다.
relweights <- function(fit,...){
R <- cor(fit$model)
nvar <- ncol(R)
rxx <- R[2:nvar, 2:nvar]
rxy <- R[2:nvar, 1]
svd <- eigen(rxx)
evec <- svd$vectors
ev <- svd$values
delta <- diag(sqrt(ev))
lambda <- evec %*% delta %*% t(evec)
lambdasq <- lambda ^ 2
beta <- solve(lambda) %*% rxy
rsquare <- colSums(beta ^ 2)
rawwgt <- lambdasq %*% beta ^ 2
import <- (rawwgt / rsquare) * 100
import <- as.data.frame(import)
row.names(import) <- names(fit$model[2:nvar])
names(import) <- "Weights"
import <- import[order(import),1, drop=FALSE]
dotchart(import$Weights, labels=row.names(import),
xlab="% of R-Square", pch=19,
main="Relative Importance of Predictor Variables",
sub=paste("Total R-Square=", round(rsquare, digits=3)),
...)
return(import)
}
사용 예는 다음과 같다. 자동차의 연비에 관한 데이터인 mtcars 데이터로 자동차의 연비(mile per gallon, mpg)를 결정하는 요인들을 회귀분석 해보면 다음과 같다.
fit=lm(mpg~wt+hp+cyl+am,data=mtcars)
summary(fit)
Call:
lm(formula = mpg ~ wt + hp + cyl + am, data = mtcars)
Residuals:
Min 1Q Median 3Q Max
-3.4765 -1.8471 -0.5544 1.2758 5.6608
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 36.14654 3.10478 11.642 4.94e-12 ***
wt -2.60648 0.91984 -2.834 0.0086 **
hp -0.02495 0.01365 -1.828 0.0786 .
cyl -0.74516 0.58279 -1.279 0.2119
am 1.47805 1.44115 1.026 0.3142
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 2.509 on 27 degrees of freedom
Multiple R-squared: 0.849, Adjusted R-squared: 0.8267
F-statistic: 37.96 on 4 and 27 DF, p-value: 1.025e-10
다중회귀분석 결과 차의 중량(wt)이 가장 유의하고 마력(hp), 엔진수(cyl), 자동변속여부(am) 순으로 유의한 것으로 나타난다. 이 모형의 \(R^2\) 값은 0.849, adjusted \(R^2\) 값은 0.8267이다. 이 모형에서 설명변수들의 상대적 중요성은 다음과 같이 구할 수 있다.
relweights(fit,col="blue")
Weights
am 15.53312
hp 26.75247
cyl 27.00191
wt 30.71250
이 모형의 설명변수 중 차중량(wt)의 상대적 중요성이 30.7%로 나타났다.
다중회귀모형에서 설명변수가 범주형/문자형 변수가 있어도 회귀분석에는 아무런 문제가 없다. 저자가 만든 moonBook패키지에 포함되어 있는 radial 데이타에는 동맥경화 정도를 나타내는 NTAV(표준화된 동맥경화반 부피)와 함께 여러 임상적인 특징들이 포함되어 있다. NTAV를 반응변수로 하고 나이와 성별, 당뇨병 유무,고혈압유무를 설명변수로 다중회귀분석을 해보면 다음과 같이 할 수 있다.
require(moonBook)
Loading required package: moonBook
fit1=lm(NTAV~age+sex+DM+HBP, data=radial)
summary(fit1)
Call:
lm(formula = NTAV ~ age + sex + DM + HBP, data = radial)
Residuals:
Min 1Q Median 3Q Max
-50.054 -10.957 -2.535 7.263 85.306
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 17.2295 14.1442 1.218 0.2258
age 0.5371 0.2144 2.505 0.0137 *
sexM 21.9737 4.1654 5.275 6.7e-07 ***
DM 0.2187 4.2774 0.051 0.9593
HBP 10.2796 4.3041 2.388 0.0186 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 21.45 on 110 degrees of freedom
Multiple R-squared: 0.2376, Adjusted R-squared: 0.2099
F-statistic: 8.571 on 4 and 110 DF, p-value: 4.653e-06
다중회귀분석 결과 나이와 성별, 고혈압이 유의한 설명변수로 나타났고 당뇨병 유무는 통계적으로 유의하지 않았다. 이 모형에서 상대적인 중요성을 보기 위해 relweight()함수를 써보면 다음과 같은 에러가 난다.
relweights(fit1)
Error in cor(fit$model): 'x' must be numeric
숫자가 아닌 설명변수는 relweights() 함수로 처리가 안된다. 문자형인 sex를 factor()gkatnfh 범주형변수로 바꾸어 주면 어떻게 될까?
fit2=lm(NTAV~age+factor(sex)+DM+HBP, data=radial)
summary(fit2)
Call:
lm(formula = NTAV ~ age + factor(sex) + DM + HBP, data = radial)
Residuals:
Min 1Q Median 3Q Max
-50.054 -10.957 -2.535 7.263 85.306
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 17.2295 14.1442 1.218 0.2258
age 0.5371 0.2144 2.505 0.0137 *
factor(sex)M 21.9737 4.1654 5.275 6.7e-07 ***
DM 0.2187 4.2774 0.051 0.9593
HBP 10.2796 4.3041 2.388 0.0186 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 21.45 on 110 degrees of freedom
Multiple R-squared: 0.2376, Adjusted R-squared: 0.2099
F-statistic: 8.571 on 4 and 110 DF, p-value: 4.653e-06
relweights(fit2)
Error in cor(fit$model): 'x' must be numeric
여전히 에러가 난다. 범주형 변수인 factor(sex)를 다시 숫자로 바꾸어주면 다음과 같다.
fit3=lm(NTAV~age+as.numeric(factor(sex))+DM+HBP, data=radial)
summary(fit3)
Call:
lm(formula = NTAV ~ age + as.numeric(factor(sex)) + DM + HBP,
data = radial)
Residuals:
Min 1Q Median 3Q Max
-50.054 -10.957 -2.535 7.263 85.306
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -4.7442 16.1717 -0.293 0.7698
age 0.5371 0.2144 2.505 0.0137 *
as.numeric(factor(sex)) 21.9737 4.1654 5.275 6.7e-07 ***
DM 0.2187 4.2774 0.051 0.9593
HBP 10.2796 4.3041 2.388 0.0186 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 21.45 on 110 degrees of freedom
Multiple R-squared: 0.2376, Adjusted R-squared: 0.2099
F-statistic: 8.571 on 4 and 110 DF, p-value: 4.653e-06
relweights(fit3)
Weights
DM 0.4525551
age 15.2713467
HBP 15.4709620
as.numeric(factor(sex)) 68.8051361
사실 radial 데이터에는 문자형변수인 sex이외에 숫자형변수인 male 이라는 변수가 있다. 이 변수는 남자는 1,여자는 0으로 입력되어 있다. sex 대신 male을 써서 다중회귀를 해보면 다음과 같다.
fit4=lm(NTAV~age+male+DM+HBP, data=radial)
summary(fit4)
Call:
lm(formula = NTAV ~ age + male + DM + HBP, data = radial)
Residuals:
Min 1Q Median 3Q Max
-50.054 -10.957 -2.535 7.263 85.306
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 17.2295 14.1442 1.218 0.2258
age 0.5371 0.2144 2.505 0.0137 *
male 21.9737 4.1654 5.275 6.7e-07 ***
DM 0.2187 4.2774 0.051 0.9593
HBP 10.2796 4.3041 2.388 0.0186 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 21.45 on 110 degrees of freedom
Multiple R-squared: 0.2376, Adjusted R-squared: 0.2099
F-statistic: 8.571 on 4 and 110 DF, p-value: 4.653e-06
relweights(fit4)
Weights
DM 0.4525551
age 15.2713467
HBP 15.4709620
male 68.8051361
데이터프레임에 있는 문자형변수를 모두 숫자형 변수로 바꾸려면 어떻게 해야 할까? 저자가 쓴 “의학논문 작성을 위한 R 통계와 그래프”(한나래,2015) 책에 소개한 방법은 다음과 같다. acs데이터에는 문자형변수가 많이 있다. acs데이터의 구조는 다음과 같다.
str(acs)
'data.frame': 857 obs. of 17 variables:
$ age : int 62 78 76 89 56 73 58 62 59 71 ...
$ sex : chr "Male" "Female" "Female" "Female" ...
$ cardiogenicShock: chr "No" "No" "Yes" "No" ...
$ entry : chr "Femoral" "Femoral" "Femoral" "Femoral" ...
$ Dx : chr "STEMI" "STEMI" "STEMI" "STEMI" ...
$ EF : num 18 18.4 20 21.8 21.8 22 24.7 26.6 28.5 31.1 ...
$ height : num 168 148 NA 165 162 153 167 160 152 168 ...
$ weight : num 72 48 NA 50 64 59 78 50 67 60 ...
$ BMI : num 25.5 21.9 NA 18.4 24.4 ...
$ obesity : chr "Yes" "No" "No" "No" ...
$ TC : num 215 NA NA 121 195 184 161 136 239 169 ...
$ LDLC : int 154 NA NA 73 151 112 91 88 161 88 ...
$ HDLC : int 35 NA NA 20 36 38 34 33 34 54 ...
$ TG : int 155 166 NA 89 63 137 196 30 118 141 ...
$ DM : chr "Yes" "No" "No" "No" ...
$ HBP : chr "No" "Yes" "Yes" "No" ...
$ smoking : chr "Smoker" "Never" "Never" "Never" ...
먼저 데이타프레임 중 문자형 변수를 찾아 select에 저장한다.
df<-acs
select<-sapply(df,function(x) {is.character(x)})
select
age sex cardiogenicShock entry
FALSE TRUE TRUE TRUE
Dx EF height weight
TRUE FALSE FALSE FALSE
BMI obesity TC LDLC
FALSE TRUE FALSE FALSE
HDLC TG DM HBP
FALSE FALSE TRUE TRUE
smoking
TRUE
select에 저장된 문자형변수를 범주형변수로 바꾼다.
df[,select]<-lapply(df[,select],factor)
str(df)
'data.frame': 857 obs. of 17 variables:
$ age : int 62 78 76 89 56 73 58 62 59 71 ...
$ sex : Factor w/ 2 levels "Female","Male": 2 1 1 1 2 1 2 2 1 2 ...
$ cardiogenicShock: Factor w/ 2 levels "No","Yes": 1 1 2 1 1 1 1 1 1 1 ...
$ entry : Factor w/ 2 levels "Femoral","Radial": 1 1 1 1 2 2 2 1 2 1 ...
$ Dx : Factor w/ 3 levels "NSTEMI","STEMI",..: 2 2 2 2 1 3 3 2 3 2 ...
$ EF : num 18 18.4 20 21.8 21.8 22 24.7 26.6 28.5 31.1 ...
$ height : num 168 148 NA 165 162 153 167 160 152 168 ...
$ weight : num 72 48 NA 50 64 59 78 50 67 60 ...
$ BMI : num 25.5 21.9 NA 18.4 24.4 ...
$ obesity : Factor w/ 2 levels "No","Yes": 2 1 1 1 1 2 2 1 2 1 ...
$ TC : num 215 NA NA 121 195 184 161 136 239 169 ...
$ LDLC : int 154 NA NA 73 151 112 91 88 161 88 ...
$ HDLC : int 35 NA NA 20 36 38 34 33 34 54 ...
$ TG : int 155 166 NA 89 63 137 196 30 118 141 ...
$ DM : Factor w/ 2 levels "No","Yes": 2 1 1 1 2 2 2 2 2 2 ...
$ HBP : Factor w/ 2 levels "No","Yes": 1 2 2 1 2 2 2 2 2 1 ...
$ smoking : Factor w/ 3 levels "Ex-smoker","Never",..: 3 2 2 2 3 2 1 1 2 3 ...
바뀐 데이터프레임을 보면 모두 범주형변수로 바뀐 것을 알 수 있다. 이상의 과정을 함수로 만들면 다음과 같이 만들 수 있다.
chr2factor=function(df){
select=sapply(df,function(x) {is.character(x)})
df[,select]<-lapply(df[,select],factor)
df
}
함수가 잘 동작하는지 시험해보자.
df2<-chr2factor(acs)
str(df2)
'data.frame': 857 obs. of 17 variables:
$ age : int 62 78 76 89 56 73 58 62 59 71 ...
$ sex : Factor w/ 2 levels "Female","Male": 2 1 1 1 2 1 2 2 1 2 ...
$ cardiogenicShock: Factor w/ 2 levels "No","Yes": 1 1 2 1 1 1 1 1 1 1 ...
$ entry : Factor w/ 2 levels "Femoral","Radial": 1 1 1 1 2 2 2 1 2 1 ...
$ Dx : Factor w/ 3 levels "NSTEMI","STEMI",..: 2 2 2 2 1 3 3 2 3 2 ...
$ EF : num 18 18.4 20 21.8 21.8 22 24.7 26.6 28.5 31.1 ...
$ height : num 168 148 NA 165 162 153 167 160 152 168 ...
$ weight : num 72 48 NA 50 64 59 78 50 67 60 ...
$ BMI : num 25.5 21.9 NA 18.4 24.4 ...
$ obesity : Factor w/ 2 levels "No","Yes": 2 1 1 1 1 2 2 1 2 1 ...
$ TC : num 215 NA NA 121 195 184 161 136 239 169 ...
$ LDLC : int 154 NA NA 73 151 112 91 88 161 88 ...
$ HDLC : int 35 NA NA 20 36 38 34 33 34 54 ...
$ TG : int 155 166 NA 89 63 137 196 30 118 141 ...
$ DM : Factor w/ 2 levels "No","Yes": 2 1 1 1 2 2 2 2 2 2 ...
$ HBP : Factor w/ 2 levels "No","Yes": 1 2 2 1 2 2 2 2 2 1 ...
$ smoking : Factor w/ 3 levels "Ex-smoker","Never",..: 3 2 2 2 3 2 1 1 2 3 ...
하지만 이 방법은 치명적인 단점이 하나 있다. 이 함수는 문자형 변수가 하나만 있을 때는 제대로 동작하지 않는다. 예를 들어 acs데이터 중 첫번째 열인 age와 두번째 열인 sex 만으로 이루어진 acs2를 만들고 테스트해보자.
acs2=acs[1:2]
str(acs2)
'data.frame': 857 obs. of 2 variables:
$ age: int 62 78 76 89 56 73 58 62 59 71 ...
$ sex: chr "Male" "Female" "Female" "Female" ...
df3<-chr2factor(acs2)
Warning in `[<-.data.frame`(`*tmp*`, , select, value =
list(structure(1L, .Label = "Male", class = "factor"), : provided 857
variables to replace 1 variables
str(df3)
'data.frame': 857 obs. of 2 variables:
$ age: int 62 78 76 89 56 73 58 62 59 71 ...
$ sex: Factor w/ 1 level "Male": 1 1 1 1 1 1 1 1 1 1 ...
뭔가 경고가 생기고 제대로 변환이 이루어지지 않았다.이 점을 보완한 함수는 다음과 같다.
chr2factor=function(df){
select=sapply(df,function(x) {is.character(x)})
if(sum(select)==1) df[[which(select)]]<-factor(df[[which(select)]])
else if(sum(select)>1) df[,select]<-lapply(df[,select],factor)
df
}
이번에는 제대로 동작하는지 테스트해보자.
str(acs2)
'data.frame': 857 obs. of 2 variables:
$ age: int 62 78 76 89 56 73 58 62 59 71 ...
$ sex: chr "Male" "Female" "Female" "Female" ...
df4<-chr2factor(acs2)
str(df4)
'data.frame': 857 obs. of 2 variables:
$ age: int 62 78 76 89 56 73 58 62 59 71 ...
$ sex: Factor w/ 2 levels "Female","Male": 2 1 1 1 2 1 2 2 1 2 ...
함수가 제대로 동작하는 것을 확인했으니 다시 범주형변수를 숫자형변수로 만드는 함수를 만들어보면 다음과 같다.
factor2number=function(df){
select=sapply(df,function(x) {is.factor(x)})
if(sum(select)==1) df[[which(select)]]<-as.numeric(df[[which(select)]])
else if(sum(select)>1) df[,select]<-lapply(df[,select],as.numeric)
df
}
처음 만들었던 relweights()함수를 개선하여 회귀모형에 문자형 변수, 범주형 변수가 포함되어 있으면 숫자형변수로 바꾸어 주고 상대적 중요성을 구해주는 함수를 만들어 보면 다음과 같다.
relweights <- function(fit,plot=FALSE,...){
df<-fit$model
df1<-chr2factor(df)
df2<-factor2number(df1)
R <- cor(df2)
nvar <- ncol(R)
rxx <- R[2:nvar, 2:nvar]
rxy <- R[2:nvar, 1]
svd <- eigen(rxx)
evec <- svd$vectors
ev <- svd$values
delta <- diag(sqrt(ev))
lambda <- evec %*% delta %*% t(evec)
lambdasq <- lambda ^ 2
beta <- solve(lambda) %*% rxy
rsquare <- colSums(beta ^ 2)
rawwgt <- lambdasq %*% beta ^ 2
import <- (rawwgt / rsquare) * 100
import <- as.data.frame(import)
row.names(import) <- names(fit$model[2:nvar])
names(import) <- "Weights"
import <- import[order(import),1, drop=FALSE]
if(plot) {
dotchart(import$Weights, labels=row.names(import),
xlab="% of R-Square", pch=19,
main="Relative Importance of Predictor Variables",
sub=paste("Total R-Square=", round(rsquare, digits=3)),
...)
}
attr(import,"R-square")<-round(rsquare, digits=3)
return(import)
}
제대로 동작하는지 확인해보면 다음과 같다.
fit=lm(NTAV~age+sex+DM+HBP, data=radial)
summary(fit)
Call:
lm(formula = NTAV ~ age + sex + DM + HBP, data = radial)
Residuals:
Min 1Q Median 3Q Max
-50.054 -10.957 -2.535 7.263 85.306
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 17.2295 14.1442 1.218 0.2258
age 0.5371 0.2144 2.505 0.0137 *
sexM 21.9737 4.1654 5.275 6.7e-07 ***
DM 0.2187 4.2774 0.051 0.9593
HBP 10.2796 4.3041 2.388 0.0186 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 21.45 on 110 degrees of freedom
Multiple R-squared: 0.2376, Adjusted R-squared: 0.2099
F-statistic: 8.571 on 4 and 110 DF, p-value: 4.653e-06
result=relweights(fit)
result
Weights
DM 0.4525551
age 15.2713467
HBP 15.4709620
sex 68.8051361
위에서 구한 ggplot2를 이용하여 가로막대그래프를 그려주는 함수는 다음과 같이 만들 수 있다.
require(ggplot2)
Loading required package: ggplot2
plotRelWeights=function(fit){
data<-relweights(fit)
data$Predictors<-rownames(data)
p<-ggplot(data=data,aes(x=reorder(Predictors,Weights),y=Weights,fill=Predictors))+
geom_bar(stat="identity",width=0.5)+
ggtitle("Relative Importance of Predictor Variables")+
ylab(paste0("% of R-square \n(Total R-Square=",attr(data,"R-square"),")"))+
geom_text(aes(y=Weights-0.1,label=paste(round(Weights,1),"%")),hjust=1)+
guides(fill=FALSE)+
coord_flip()
p
}
위의 함수를 이용하여 plot을 그려보면 다음과 같다.
plotRelWeights(fit1)