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

ggplot 레이블 겹치지 않게 쓰기(feat. ggrepel)


R에서 ggplot2 패키지로 그래프를 그리다 보면 레이블(label)을 붙어줘야 할 때가 있습니다.


이럴 때는 기본적으로 geom_text() 또는 geom_label() 함수를 쓰시면 됩니다.


한 번 그래프를 직접 그리면서 두 함수 사용법을 알아보겠습니다.


이번에 우리가 그래프에 쓸 데이터는 2015~2018년 프로야구 10개 구단 팀 타격 결과입니다. 2015년을 기준으로 삼은 건 이해부터 프로야구가 10개 구단 체제로 개편했기 때문.


아래 있는 CSV 파일을 내려받으시고 read.csv() 함수 등으로 불러와 주세요.


kbo.csv


kbo <- read.csv('kbo.csv')
head(kbo)
##   연도   팀  타율 출루율 장타력
## 1 2015 삼성 0.302  0.378  0.469
## 2 2015 넥센 0.298  0.372  0.486
## 3 2015 두산 0.290  0.370  0.435
## 4 2015   NC 0.289  0.367  0.455
## 5 2015 롯데 0.280  0.356  0.446
## 6 2015   SK 0.272  0.349  0.410


이어서 할 일은 ggplot2 패키지 불러오기. 

library('ggplot2')


그럼 이제 '출루율'을 x축 기준으로, '장타력'을 y축 기준으로 삼는 산점도를 그려보겠습니다. ggplot으로 그래프 그리기에 익숙한 분이시라면 전혀 어려울 게 없습니다.

ggplot(kbo, aes(x=출루율, y=장타력)) + 
  geom_point()


이제 geom_text()를 써서 각 점에 레이블을 붙이도록 하겠습니다.


레이블로 쓰려고 하는 건 '연도 +팀' 형태. R에서 이렇게 두 문자값을 결합할 때는 paste() 함수가 기본입니다.

paste(kbo$연도, kbo$팀)
 [1] "2015 삼성" "2015 넥센" "2015 두산" "2015 NC"   "2015 롯데" "2015 SK"  
 [7] "2015 KT"   "2015 한화" "2015 LG"   "2015 KIA"  "2016 두산" "2016 NC"  
[13] "2016 넥센" "2016 삼성" "2016 롯데" "2016 KIA"  "2016 LG"   "2016 SK"  
[19] "2016 한화" "2016 KT"   "2017 두산" "2017 KIA"  "2017 NC"   "2017 넥센"
[25] "2017 SK"   "2017 롯데" "2017 한화" "2017 삼성" "2017 LG"   "2017 KT"  
[31] "2018 두산" "2018 KIA"  "2018 롯데" "2018 SK"   "2018 넥센" "2018 LG"  
[37] "2018 삼성" "2018 KT"   "2018 한화" "2018 NC


참고로 paste()는 문자열 사이에 자동으로 공백을 넣는데 이게 싫으시면 paste0()을 쓰시면 됩니다. 나머지 사용법은 똑같습니다.


두 변수를 어떻게 붙이는 줄 알았으니 그래프에 레이블을 붙이는 코드를 써보겠습니다.

ggplot(kbo, aes(x=출루율, y=장타력, label=paste(연도, 팀))) + 
  geom_point() + 
  geom_text()


레이블 역시 x, y 좌표 기준이 모두 출루율, 장타력이라서 점 가운데 글씨가 나옵니다. 이럴 때는 hjust(가로), vjust(세로) 속성을 통해 글씨 위치를 조정할 수 있습니다.


한 번 다음처럼 코드를 써보겠습니다.

ggplot(kbo, aes(x=출루율, y=장타력, label=paste(연도, 팀))) + 
  geom_point() + 
  geom_text(hjust=-.1) 


한결 낫죠? 문제는 hjust, vjust 뒤에 들어가야 할 숫자가 얼마인지 정하기가 쉽지 않다는 것.


기본적으로 이 숫자, 저 숫자를 넣어서 제일 그럴 듯해 보이는 값을 찾을 수밖에 없습니다. 게다가 hjust, vjust는 레이블 위치를 일률적으로 조정하기 때문에 레이블이 겹치는 증상을 해소하지는 못합니다.


이때는 'check_overlap' 속성을 'TRUE'로 바꾸면 도움을 얻을 수 있습니다. 지금 코드 뒤에 'check_overlap=TRUE'를 더해보겠니다.

ggplot(kbo, aes(x=출루율, y=장타력, label=paste(연도, 팀))) + 
  geom_point() + 
  geom_text(hjust=-.1, check_overlap=TRUE) 


훨씬 깔끔합니다. 단, 이번에도 모든 문제를 해결한 건 아닙니다.


오버랩된 레이블을 정리하다 보니 어떤 점에서는 아예 레이블이 사라지고 말았습니다. 또 레이블이 잘린 지점도 있습니다.


이런 문제를 해결해주는 패키지가 바로 이 포스트 주인공 ggrepel입니다.


새로운 패키지를 쓰시려면 설치하고 불러오는 과정부터 진행해야 합니다. 이렇게 말입니다.

install.packages('ggrepel')
library('ggrepel')


ggrepel을 활용하는 첫 단계는 geom_text() 대신 geom_text_repel() 함수를 쓰는 겁니다. 그냥 geom_text() 자리에 geom_text_repel()을 넣으면 그만입니다.

ggplot(kbo, aes(x=출루율, y=장타력, label=paste(연도, 팀))) + 
  geom_point() + 
  geom_text_repel() 


깔끔하지 않습니까? 모든 점에 레이블이 붙은 건 물론이고 레이블이 떨어져 있을 때는 선으로 연결하는 친절함까지 느낄 수 있습니다.


그래도 (저처럼) 조금 빡빡하다고 느끼시는 분이 계시다면 여백을 조금 더 주는 것으로 숨통을 틀 수도 있습니다. xlim은 x축 끝을 어디서 어디까지 정하라는 뜻입니다.

ggplot(kbo, aes(x=출루율, y=장타력, label=paste(연도, 팀))) + 
  geom_point() + 
  geom_text_repel() + 
  xlim(0.32, 0.39)


이번에 우리가 그린 산점도는 처음부터 그래도 여유가 있는 편이어서 geom_text()와 geom_text_repel() 사이에 차이가 크지 않다고 생각하시는 분도 계셨을지 모르겠습니다. 그러면 이런 그래프는 어떨까요?



이 정도면 geom_text_repel()을 쓰는 게 확실히 낫다고 생각하지 않으십니까?


아, 혹시 이 그래프를 직접 그려보고 싶은 분이 계시다면 아래 코드를 입력하시면 됩니다.

ggplot(kbo, aes(x=타율, y=1, label=paste(연도, 팀))) + 
  geom_point(color='#14789D') +   
  geom_text_repel(nudge_y = 0.05,  direction = 'x', angle = 90, vjust = 0, segment.size = 0.2) + 
  ylim(1, 0.8) + 
  theme_classic() +  
  theme(axis.line.y  = element_blank(), 
        axis.ticks.y = element_blank(), 
        axis.text.y  = element_blank(), 
        axis.title.y = element_blank())

이 예시는 ggrepel 패키지 공식 설명 문서(vignette)에서 가져온 것.


저는 기본적인 사용법만 말씀드렸을 뿐이니 관심 있으신 분은 한 번 들러보시면 조금 더 다양한 예제와 사용 방법을 확인하실 수 있을 겁니다.


그러면 Happy Charting!


댓글,

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