聰明不如鈍筆
총명불여둔필
assignment KindeR

ggplot 산점도 위에 회귀선(식) 표시하기(feat. geom_text)


ggplot 산점도 위에 회귀직선식을 그리는 방법이 있나요?


이번 포스트는 'ggplot 레이블 겹치지 않게 쓰기(feat. ggrepel)'에 '감사합니다'라는 이름으로 남겨주신 저 댓글에서 시작합니다.


사실 마이크로소프트(MS) 엑셀만 있으면 누구나 쉽게(?) 클릭 몇 번으로 산점도를 그리고 그 위에 회귀선(추세선)과 수식(회귀식) 그리고 R-제곱값(R²)을 표시할 수 있습니다.


아래는 이번 포스트에서 사용할 데이터를 가지고 엑셀에서 같은 작업을 진행한 결과물입니다. 



R에서 ggplot2 패키지를 활용해 같은 차트를 그리려면 어떻게 해야 하는지 지금부터 차근차근 알아보도록 하겠습니다.


잘 아시는 것처럼 제일 먼저 할 일은 ggplot2 패키지 불러오기.

library(ggplot2)


그다음으로 산점도를 그릴 데이터를 무작위로 만들어 보겠습니다. 무작위로 숫자를 뽑아내는 과정을 흔히 '난수(亂數) 생성'이라고 부릅니다. R는 난수를 생성할 수 있는 함수를 여러가지 마련해두고 있는데 저는 그 중에서 rnorm()을 썼습니다. nrorm()은 정규 분포에서 난수를 생성하는 구실을 합니다.


아, 난수는 문자 그대로 무작위로 숫자를 뽑아내기 때문에 실행할 때마다 결과가 바뀝니다. 이럴 때는 시드(seed)를 지정하면 계속 같은 결과를 얻을 수 있습니다. R에서 시드를 지정하는 함수는 set.seed()입니다.


먼저 시드를 설정하고, 데이터 프레임을 만들어 x열에는 1부터 100까지, y열에는 x에 6을 곱한 뒤 표준편차(sd) 64가 나오도록 무작위로 뽑은 숫자를 더한 값을 넣도록 하겠습니다. (y열 설명이 어렵다고 고민하실 필요는 없습니다. 그냥 아무 숫자나 넣었다는 뜻입니다.)

set.seed(20190606)
df <- data.frame(x = c(1:100))
df$y <- 6 * df$x + rnorm(100, sd = 64)


이 데이터를 가지고 ggplot으로 산점도를 그리면 아래 그림처럼 나옵니다.

ggplot(df, aes(x=x, y=y)) + geom_point()


이제 추세선을 그리도록 하겠습니다. ggplot에서는 stat_smooth() (또는 geom_smooth()) 함수가 이 구실을 합니다. 위에서 쓴 코드에 이것만 더하면 되겠죠?

ggplot(df, aes(x=x, y=y)) + geom_point() + stat_smooth()
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'


고객님 많이 당황하셨죠? 대부분 직선이 나올 것이라고 예상하셨을 텐데 그렇지 않습니다. 이건 stat_smooth()가 기본적으로 국소 회귀분석(loess) 방식을 채택하고 있기 때문입니다. 선형 회귀분석 결과가 필요할 때는 stat_smooth()에서 'method' 속성을 'lm'으로 바꿔줘야 합니다. 이렇게 말입니다.

ggplot(df, aes(x=x, y=y)) + geom_point() + stat_smooth(method = 'lm')


이 산점도에는 추세선 양옆에 표준오차(standard error)를 같이 표시합니다. 필요에 따라서는 이를 지우고 싶은 분도 계실 터. 그럴 때는 stat_smooth() 안에 'se=FALSE' 속성을 넣어주시면 됩니다. R에서 FALSE는 F만 써도 됩니다.


기왕 하는 김에 각 점 색깔은 좀 연하게 바꾸고, 추세선 색깔은 검은색으로 바꾸는 것도 해볼까요? 그러면 코드를 이렇게 쓸 수 있습니다.

ggplot(df, aes(x=x, y=y)) + 
  geom_point(color='gray75') + 
  stat_smooth(method = 'lm', se=F, color='black')


여기까지 오셨으면 일단 추세선을 그리는 데까지는 끝났습니다. 일부러 천천히 와서 그렇지 별로 어려울 게 없는 작업입니다.


이제 수식과 R제곱값을 표시하는 방법을 알아볼 차례. 일단 어떤 식을 써야 하고, R제곱값을 얼마인지 알아봐야겠죠?


R에서 (단순) 선형 회귀분석을 진행하는 함수는 lm(). 여기에 summary() 함수까지 같이 쓰면 분석 결과를 한 눈에 볼 수 있습니다.

summary(lm(y~x, df))
## 
## Call:
## lm(formula = y ~ x, data = df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -124.768  -54.331   -2.862   44.922  171.798 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -18.1421    13.2086  -1.374    0.173    
## x             6.4516     0.2271  28.412   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 65.55 on 98 degrees of freedom
## Multiple R-squared:  0.8917, Adjusted R-squared:  0.8906 
## F-statistic: 807.2 on 1 and 98 DF,  p-value: < 2.2e-16


실제로 R가 저렇게 음영을 넣어서 출력하는 건 아닙니다. 중요한 값을 제가 미리 골라서 표시한 것.


이 결과를 통해 우리는 (반올림을 좀 해서) 회귀식을 'y=6.452x-18.142'처럼 쓸 수 있으며 'R²=0.8917'도 필요하다는 사실을 알 수 있습니다. 이제 그래프 위에 그냥 쓰기만 하면 됩니다. ggplot에서 글씨를 쓸 때 쓰는 함수는 geom_text()입니다.

ggplot(df, aes(x=x, y=y)) + 
  geom_point(color='gray75') + 
  stat_smooth(method = 'lm', se=F, color='black') + 
  geom_text(x=85, y=450, label="y=6.452x-18.142") + 
  geom_text(x=85, y=405, label="R²=0.8917")


예, 이렇게 끝이 났습니다. 다소 원시적인 방법이지만 가장 기초적인 게 가장 잘 통하는 법 아니겠습니까? 혹시 숫자를 옮기는 과정에서 오타가 날지 몰라 걱정이 되신다면 함수를 만드는 방법도 있기는 합니다. 제가 보기에는 함수를 만드는 게 오히려 손이 더 가는 일처럼 보이지만 말입니다.


혹시 이해가 잘 가지 않으시거나 잘못된 점이 있다면 언제든 알려주세요. 함께 공부하면서 바꾸도록 하겠습니다. 그럼 모두들 Happy Charting!


댓글,

KindeR | 카테고리 다른 글 더 보기