R에서 묶은 막대 그래프에 레이블 쓰기(feat. position_dodge)
이건 사실 말보다는 그림으로 설명 드리는 게 백배 낫습니다.
그러니까 그래프를 묶어서 그릴 때 (적어도) 아래 그림처럼 레이블이 나타나게 하고 싶은데
그냥 손가락이 가는 대로 코드를 쓰면 레이블이 아래 그림처럼 레이블이 나오는 현상 말입니다.
이럴 때는 어떻게 해야 각 레이블이 자기 자리를 찾아가도록 만들 수 있을까요?
일단 정답은 제목에 쓴 것처럼 position 속성에 position_dodge 값을 주는 겁니다.
그러니까 geom_text(position=position_dodge(width=1))처럼 쓰시면 됩니다.
정답은 이미 알려드렸으니 이제부터 차근차근 레이블을 벌리는(?) 연습을 해보도록 하겠습니다.
첫 단계는 물론 tidyverse 패키지 불러오기입니다.
#install.packages('tidyverse')
library('tidyverse')
## -- Attaching packages --------------------------------------------------------------------------------- tidyverse 1.3.0 --
## √ ggplot2 3.2.1 √ purrr 0.3.2 ## √ tibble 2.1.3 √ dplyr 0.8.3 ## √ tidyr 0.0.3 √ stringr 1.4.0 ## √ readr 1.3.1 √ forcats 0.3.0
## -- Conflicts ------------------------------------------------------------------------------------ tidyverse_conflicts() -- ## x dplyr::filter() masks stats::filter() ## x dplyr::lag() masks stats::lag()
이어서 데이터를 마련해야 합니다.
이 블로그 전통에 따라 지난해 프로야구 10개 구단 타율, 출루율, 장타력 자료를 가지고 작업을 진행하겠습니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
)
## # A tibble: 10 x 4 ## 팀 타율 출루율 장타력 ## <chr> <dbl> <dbl> <dbl> ## 1 키움 0.282 0.354 0.414 ## 2 NC 0.278 0.345 0.416 ## 3 두산 0.278 0.355 0.389 ## 4 KT 0.277 0.344 0.385 ## 5 삼성 0.256 0.329 0.389 ## 6 SK 0.262 0.334 0.384 ## 7 LG 0.267 0.333 0.384 ## 8 KIA 0.264 0.336 0.369 ## 9 한화 0.256 0.324 0.362 ## 10 롯데 0.25 0.316 0.358
그런데 막상 10개 구단 기록을 가지고 그래프를 그려 보니 문제가 하나 있습니다.
10개 구단을 모두 쓰면 그래프가 너무 좁아서 글자가 잘 보이지 않는다는 것.
그런 이유로 sample() 함수를 써서 5개 구단만 임의로 골라내도록 하겠습니다.
숫자 1~10 가운데 다섯 개를 골라내라는 걸 코드로 쓰면 'sample(1:10, 5)가 됩니다.
그다음 행 번호(row_number())가 이 숫자 안에 들어 있을 때(%in%)만 필터링(filter())하겠습니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5))
## # A tibble: 5 x 4 ## 팀 타율 출루율 장타력 ## <chr> <dbl> <dbl> <dbl> ## 1 키움 0.282 0.354 0.414 ## 2 두산 0.278 0.355 0.389 ## 3 SK 0.262 0.334 0.384 ## 4 한화 0.256 0.324 0.362 ## 5 롯데 0.25 0.316 0.358
아, 참고로 코드를 실행할 때마다 추출한 숫자가 달라지기 때문에 팀 선택도 달라집니다.
지금은 팀이 바뀌는 게 중요한 문제가 아니니 그냥 이대로 진행하도록 하겠습니다.
현재 우리가 입력한 데이터는 와이드 폼입니다.
pivot_longer() 함수를 써서 롱 폼으로 바꾸겠습니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값')
## # A tibble: 15 x 3 ## 팀 기록 값 ## <chr> <chr> <dbl> ## 1 두산 타율 0.278 ## 2 두산 출루율 0.355 ## 3 두산 장타력 0.389 ## 4 SK 타율 0.262 ## 5 SK 출루율 0.334 ## 6 SK 장타력 0.384 ## 7 LG 타율 0.267 ## 8 LG 출루율 0.333 ## 9 LG 장타력 0.384 ## 10 KIA 타율 0.264 ## 11 KIA 출루율 0.336 ## 12 KIA 장타력 0.369 ## 13 롯데 타율 0.25 ## 14 롯데 출루율 0.316 ## 15 롯데 장타력 0.358
혹시 이렇게 데이터 구조를 바꾸는 작업이 잘 이해가 가지 않으시다면 '최대한 친절하게 쓴 R로 데이터 깔끔하게 만들기(feat. tidyr)' 포스트가 도움이 될 수 있습니다.
이 포스트를 쓸 때는 gather() 함수가 '와이드 폼 → 롱 폼' 변환을 맡았는데 그 뒤로 pivot_longer() 동사가 나왔습니다.
데이터 구조를 바꾸는 작업까지 끝났으니 일단 그래프를 하나 그리겠습니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값)) +
geom_bar(stat='identity')
우리는 타율 출루율 장타력을 따로 따로 나타내고 싶으니까, 그러려고 와이드 폼을 롱 폼으로 바꾼 거니까 색깔로 구분을 짓겠습니다.
특정 변수에 들어 있는 값에 따라 색깔을 바꾸고 싶을 때는 aes(fill='변수') 형태로 쓰시면 됩니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=기록)) +
geom_bar(stat='identity')
우리는 이렇게 누적으로 그래프를 그리고 싶은 게 아니라 묶음으로 그리고 싶습니다.
이럴 때는 geom_bar() 안에 position='dodge' 속성을 지정하면 됩니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=기록)) +
geom_bar(stat='identity', position='dodge')
잘 나왔습니다. 이제 각 그래프에 레이블을 붙여 보겠습니다. geom_text()를 쓰면 되겠죠?
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=기록)) +
geom_bar(stat='identity', position='dodge') +
geom_text(aes(label=값))
이 글을 쓰게 만든 그래프가 드디어 등장했습니다.
글 초반에 말씀드린 것처럼 이때는 geom_text() 안에 position=position_dodge(width=1)를 추가하면 됩니다. 일단 넣어보겠습니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=기록)) +
geom_bar(stat='identity', position='dodge') +
geom_text(aes(label=값), position=position_dodge(width=1))
일단 레이블을 옆으로 벌리는 데는 성공했습니다.
참고로 위에서는 geom_bar(position='dodge')라고 썼는데 이 부분 역시 position=position_dodge()라고 쓰셔도 같은 결과가 나옵니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=기록)) +
geom_bar(stat='identity', position=position_dodge()) +
geom_text(aes(label=값), position=position_dodge(width=1))
레이블을 막대 끝에 그대로 두는 것보다는 위로 살짝 띄우면 더 좋겠죠?
이럴 때는 geom_text()에서 y값을 수정하면 됩니다.
현재는 ggplot() 코드 맨 처음에 있는 aes(x=팀, y=값, fill=기록)에 따라 geom_text() 역시 '값'을 y값으로 쓰고 있는 상태입니다. 이 '값'에 .0125를 더해보겠습니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=기록)) +
geom_bar(stat='identity', position='dodge') +
geom_text(aes(y=값+.0125, label=값), position=position_dodge(width=1))
이걸로 일단 기본적인 틀은 모두 잡았습니다.
그래도 이대로 끝나면 아쉬우니까 몇 가지 장난(?)을 더 쳐보도록 하겠습니다.
일단 오른쪽에 있는 범례(legend)를 아래로 내려볼까요?
이 때는 theme() 함수를 쓰고 그 안에 legend.position 속성을 활용해 위치를 조정하시면 됩니다.
우리는 범례를 아래로 보내고 싶으니까 legend.poisition='bottom'이라고 쓰면 됩니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=기록)) +
geom_bar(stat='identity', position='dodge') +
geom_text(aes(y=값+.0125, label=값), position=position_dodge(width=1)) +
theme(legend.position='bottom')
현재 그래프는 한 팀을 놓고 볼때 장타력 출루율 타율 그래프가 전부 붙어 있는 형태입니다.
이 그래프 사이에 간격을 주고 싶을 때는 어떻게 할까요?
가장 간단한 방법은 position='dodge' 뒤에 2를 붙이는 겁니다.
그냥 geom_bar(position='dodge2')처럼 쓰면 그만입니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=기록)) +
geom_bar(stat='identity', position='dodge2') +
geom_text(aes(y=값+.0125, label=값), position=position_dodge(width=1)) +
theme(legend.position='bottom')
야구를 좋아하시는 분이라면 타격 기록을 쓸 때는 타율/출루율/장타력 순서가 기본이라는 걸 알고 계실 겁니다.
지금은 순서가 반대로 나와 있습니다. 이를 바로 잡으려면 어떻게 할까요?
먼저 factor() 함수로 값을 요인(factor) 형태로 만든 뒤 forcats 패키지에 들어 있는 fct_rev() 함수를 써서 순서를 바꿔주면 됩니다.
위에서 tidyverse 패지키를 불러오셨다면 forcats 패키지는 따로 부르지 않으셔도 됩니다.
forcats 패지키는 요인을 깔끔하게 처리할 수 있도록 도와주는 패키지입니다.
이 그래프에서는 'fill=factor(기록) %>% fct_rev()'로 쓰시면 됩니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=factor(기록) %>% fct_rev())) +
geom_bar(stat='identity', position='dodge2') +
geom_text(aes(y=값+.0125, label=값), position=position_dodge(width=1)) +
theme(legend.position='bottom')
이렇게 쓰다 보니 범례 이름이 좀 이상하게 나왔습니다.
이때는 scale_fill_discrete() 함수 안에 name 속성을 써서 이름을 바꿀 수 있습니다.
원래대로 '기록'이라고 나타내려면 scale_fill_discrete(name='기록')이라고 쓰면 됩니다.
tribble(
~팀, ~타율, ~출루율, ~장타력,
'키움', .282, .354, .414,
'NC', .278, .345, .416,
'두산', .278, .355, .389,
'KT', .277, .344, .385,
'삼성', .256, .329, .389,
'SK', .262, .334, .384,
'LG', .267, .333, .384,
'KIA', .264, .336, .369,
'한화', .256, .324, .362,
'롯데', .250, .316, .358
) %>%
filter(row_number() %in% sample(1:10, 5)) %>%
pivot_longer(-팀, names_to='기록', values_to='값') %>%
ggplot(aes(x=팀, y=값, fill=factor(기록) %>% fct_rev())) +
geom_bar(stat='identity', position='dodge2') +
geom_text(aes(y=값+.0125, label=값), position=position_dodge(width=1)) +
scale_fill_discrete(name='기록') +
theme(legend.position='bottom')
이런 식으로 자료를 정리해 그래프를 그리고 싶은데 레이블이 엉뚱한 데 붙어서 어려움을 겪는 분들이 계시다면 이 포스트가 도움이 되었기를 바랍니다.
그럼 Happy Charting!