ggplot과 동적 데이터

Visualization

“R 데이터 분석에서의 시각화는 ggplot2 패키지다.” 이 명제는 부정할 수 없는 현실이다. lattice 패키지가 S-PLUS의 Trellis 그래프를 R에 구현하였기에 늘 감사한 마음으로 애용했었다. 그런데 언젠가부터 ggplot2 패키지가 lattice 패키지를 대체하게 되었고, 이제는 ggplot2 패키지 이전과 이후로 나눌 정도로 표준이 되어 버렸다.

유충현
2021-09-19

다룰 이야기

“R 데이터 분석에서의 시각화는 ggplot2 패키지다.” 이 명제는 부정할 수 없는 현실이다. lattice 패키지가 S-PLUS의 Trellis 그래프를 R에 구현하였기에 늘 감사한 마음으로 애용했었다. 그런데 언젠가부터 ggplot2 패키지가 lattice 패키지를 대체하게 되었고, 이제는 ggplot2 패키지 이전과 이후로 나눌 정도로 표준이 되어 버렸다.

이미 정해져 있는 데이터로 시각화하는 과정을 반복하다 보면, 어느 새 함수와 같은 자신만의 서브 루틴을 만들게 된다. 이것은 정해져 있는 데이터가 아닌, 데이터의 구조와 변수의 이름이 다른, 함수 입장에서는 동적 데이터다.

이미 알고 있는 데이터는 ggplot2 문법의 R 스크립트에 변수를 미학이라고 어색하게 번역되는 Aesthetic의 줄임말인 aes() 함수를 통해서 좌표계의 기하학적인 요소에 매핑을 하게 된다. 그러나 함수의 인수로 넘어온 변수를 매핑하는데는 몇 가지 이슈가 발생한다. 이번 글에서는 이 이슈를 트러블 슈팅하는 방법을 살펴본다.

산점도를 그려 보자

ggplot2 패키지는 geom_point() 함수로 산점도를 그린다.

library(ggplot2)

p <- ggplot(mtcars, aes(wt, mpg))
p + geom_point()

우리는 이미 mtcars라는 데이터 프레임과 변수 wt와 mpg를 알고 있다. 그렇기 때문에 aes() 함수에 차량의 무게인 wt(Weight) 변수를 x-축에 지정하고, 연비인 mpg(Miles/gallon) 변수를 y-축에 지정한 산점도를 그릴 수 있었다. 명시적으로 변수 이름으로 좌표에 매핑하여 산점도를 그린 것이다.

산점도 함수를 만들어 보자

산점도를 그리는 일이 빈번해 졌다. Copy & Paste 신공을 펼쳐서 산점도를 그리는 R 스크립트를 여러 번 복사하고 변수 이름만 바꾸어 산점도를 그릴 수도 있지만 산점도를 그리는 함수를 만들면 좀 더 쉽게 산점도를 그릴 수 있을 것 같다.

첫 번째 함수의 정의

함수를 정의하는 방법을 사용해서 plot_scatter()을 정의한 후

plot_scatter <- function(data, x, y) {
  library(ggplot2)

  p <- ggplot(data = data, aes(x = x, y = y))
  p + geom_point()
}

앞서 시각화했던 차량의 무게와 연비와의 관계를 살펴보기 위한 산점도 함수를 그리려 시도하였다.

plot_scatter(data = mtcars, x = wt, y = mpg)

그러나 호출한 함수에서는 다음과 같은 오류가 발생하였다.

Error in FUN(X[[i]], ...) : object 'wt' not found

두 번째 함수의 정의

aes_string() 함수는 인수값인, 좌표계에 매핑할 변수의 이름을 심볼이 아닌 문자열로 지정하는 함수다. 함수 이름에 string이 포함된 것으로 쉽게 유추 가능하다.
이 함수를 사용해서 plot_scatter2()를 정의한 후

plot_scatter2 <- function(data, x, y) {
  library(ggplot2)

  p <- ggplot(data = data, aes_string(x = x, y = y))
  p + geom_point()
}

앞서 시각화했던 차량의 무게와 연비와의 관계를 살펴보기 위한 산점도 함수를 그리려 시도하였다. 이번에는 에러 없이 정상적인 산점도를 얻었다. 인수값은 심볼이 아닌 문자열이기 때문에 “wt”, “mpg”를 사용하게 된다.

plot_scatter2(data = mtcars, x = "wt", y = "mpg")

변수의 이름에 공백이 있는 경우

변수 이름에 공백을 포함하는 것은 단언컨데 지양해야 할 방법이다. 굳이 어절을 공백으로 나눌 필요가 있다면, 공백 대신 언더라인(_)을 사용하는 것이 좋다. 그러나 권장되지 않더라도 이미 공백이 포함된 데이터를 R에서 사용한다면, 당신은 가끔 부작용(side effect)에 당황할 수도 있다.

세 번째 함수의 정의

공공데이터인 국민건강보험공단의 검강검진 정보로 변수의 이름에 공백이 포함된 사례를 살펴본다.

library(readr)

health <- read_csv("data/국민건강보험공단_건강검진정보_20191231.csv", 
                   locale = locale(encoding = "cp949"),
                   show_col_types = FALSE)

spec(health)
cols(
  기준년도 = col_double(),
  `가입자 일련번호` = col_double(),
  시도코드 = col_double(),
  성별코드 = col_double(),
  `연령대 코드(5세단위)` = col_double(),
  `신장(5Cm단위)` = col_double(),
  `체중(5Kg 단위)` = col_double(),
  허리둘레 = col_double(),
  `시력(좌)` = col_double(),
  `시력(우)` = col_double(),
  `청력(좌)` = col_double(),
  `청력(우)` = col_double(),
  `수축기 혈압` = col_double(),
  `이완기 혈압` = col_double(),
  `식전혈당(공복혈당)` = col_double(),
  `총 콜레스테롤` = col_double(),
  트리글리세라이드 = col_double(),
  `HDL 콜레스테롤` = col_double(),
  `LDL 콜레스테롤` = col_double(),
  혈색소 = col_double(),
  요단백 = col_double(),
  혈청크레아티닌 = col_double(),
  `(혈청지오티)AST` = col_double(),
  `(혈청지오티)ALT` = col_double(),
  `감마 지티피` = col_double(),
  흡연상태 = col_double(),
  음주여부 = col_double(),
  `구강검진 수검여부` = col_double(),
  치아우식증유무 = col_double(),
  `결손치 유무` = col_character(),
  치아마모증유무 = col_character(),
  `제3대구치(사랑니) 이상` = col_character(),
  치석 = col_double(),
  `데이터 공개일자` = col_double()
)

변수 이름을 보면 공백을 포함한 것도 문제지만 괄호를 포함한 변수도 많다. 변수 이름에 공백을 포함하는 것도 문제지만 괄호처럼 특수문자를 포함하는 것도 문제다.

괄호가 포함된 변수 이름으로 산점포를 그리는 예제다. 그러나 에러가 발생한다.

plot_scatter2(data = health, x = "시력(좌)", y = "시력(우)")

Error in 시력(좌) : could not find function "시력"

변수에 포함된 괄호가 함수를 호출할 때의 괄호와 충돌이 발생해서, R 엔진이 시력이라는 함수를 찾다가 오류가 발생한 경우다. 그러므로 변수의 이름에는 괄호와 같은 특수문자를 사용하지 말자.

다음은 공백이 포함된 변수 이름으로 산점포를 그리는 예제다. 역시 에러가 발생한다.

plot_scatter2(data = health, x = "총 콜레스테롤", y = "감마 지티피")
Error in parse(text = elt) : <text>:1:3: unexpected symbol
1: 총 콜레스테롤
      ^ 

세번째 사용자 정의 함수는 aes() 함수를 사용한다. aes() 함수는 인수값에 심볼을 지정해야 하기 때문에 sym() 함수로 문자열을 심볼로 바꾼 다음에 rlang 패키지의 인젝션 연산자 !!로 싱글 객체를 넘겨준다.

plot_scatter3 <- function(data, x, y) {
  library(tidyverse)

  p <- ggplot(data = data, aes(x = !!sym(x), y = !!sym(y)))
  p + geom_point()
}

괄호가 포함된 변수에 대해서도 정상적으로 수행되며,

plot_scatter3(data = health, x = "시력(좌)", y = "시력(우)") +
  ylim(0, 2.0) +
  theme_grey(base_family = "NanumSquare")

공백이 포함된 변수에 대해서도 정상적으로 수행되며,

plot_scatter3(data = health, x = "총 콜레스테롤", y = "감마 지티피") +
  ylim(0, 500) +
  theme_grey(base_family = "NanumSquare")

덤 하나 더

선형모형을 수행하는 함수를 하나 만들었다.

linear <- function(data, x, y) {
  formula_str <- sprintf("%s ~ %s", y, x)  

  lm(formula_str, data = data) %>% 
    broom::tidy()
}

잘 알려져 있는 붓꽃 데이터로 단순선형모형을 적합해 보자.

linear(data = iris, x = "Sepal.Length", y = "Sepal.Width")
# A tibble: 2 x 5
  term         estimate std.error statistic  p.value
  <chr>           <dbl>     <dbl>     <dbl>    <dbl>
1 (Intercept)    3.42      0.254      13.5  1.55e-27
2 Sepal.Length  -0.0619    0.0430     -1.44 1.52e- 1

이번에는 앞서 문제가 되었던 두 가지 사례의 변수로 단순선형모형을 적합해 보자.

linear(data = health, x = "시력(좌)", y = "시력(우)") 

Error in 시력(우) : could not find function "시력"

linear(data = health, x = "총 콜레스테롤", y = "감마 지티피")
Error in str2lang(x) : <text>:1:4: unexpected symbol
1: 감마 지티피
       ^ 

두 사례 모두 산점도와 유사한 에러가 발생하였다.

이런 문제를 해결하기 위해서 다음과 같이 함수를 수정하였다. R에서 억음부호(Backticks) `는 공백 문자 및 특수 문자가 포함된, 표준 변수 이름이 아닌 변수 이름(non-standard)을 지정할 때 사용한다. 그러므로 sprintf() 함수의 포뮬러를 만드는 문자열에 억음부호를 넣으면 문제가 쉽게 해결된다.

linear2 <- function(data, x, y) {
  formula_str <- sprintf("`%s` ~ `%s`", y, x)  

  lm(formula_str, data = data) %>% 
    broom::tidy()
}
linear2(data = health, x = "시력(좌)", y = "시력(우)") 
# A tibble: 2 x 5
  term        estimate std.error statistic  p.value
  <chr>          <dbl>     <dbl>     <dbl>    <dbl>
1 (Intercept)    0.411    0.0498      8.27 4.28e-16
2 `시력(좌)`     0.587    0.0497     11.8  3.34e-30
linear2(data = health, x = "총 콜레스테롤", y = "감마 지티피")
# A tibble: 2 x 5
  term            estimate std.error statistic p.value
  <chr>              <dbl>     <dbl>     <dbl>   <dbl>
1 (Intercept)       11.5     11.7        0.984  0.326 
2 `총 콜레스테롤`    0.135    0.0598     2.26   0.0242

시사점

R에서 변수명을 정의할 때에는 공백이나 특수문자를 넣지 말자. 원하지 않는 부작용이 발생할 수 있다. 부작용을 회피하는 스크립트를 작성하는 것 보다, 근본적으로 문제가 되는 변수명을 지양하고 표준 변수명을 사용한다. 그리고 가급적 한글 변수명을 사용하지 않는 것도 권장한다.

Citation

For attribution, please cite this work as

유충현 (2021, Sept. 19). Dataholic: ggplot과 동적 데이터. Retrieved from https://choonghyunryu.github.io/posts/2021-09-19-ggplot_aes/

BibTeX citation

@misc{유충현2021ggplot과,
  author = {유충현, },
  title = {Dataholic: ggplot과 동적 데이터},
  url = {https://choonghyunryu.github.io/posts/2021-09-19-ggplot_aes/},
  year = {2021}
}