patchwork를 활용한 고급 시각화

ggplot2을 활용한 R 시각화의 결과물을 MS 파워포인트로 만들어내는 과정에서 쓰이는 R 패키지와 방법을 소개합니다.

R
Author
Published

May 17, 2024

data visualization

데이터 시각화는 데이터 분석에서 중요한 역할을 한다. 다행히 R은 이 방면에서는 ggplot2를 필두로 다른 프로그래밍 언어들 이상의 뛰어난 여러 기능들을 사용할 수 있다는 장점이 있다.

한편 데이터 시각화는 제작 이후 색상이나 레이아웃 등의 추가적인 커스텀 수정을 필요로 하기도 한다. 이를 위해 R 내에서 할 수 있다면 더할 나위 없이 좋지만 떄로는 단순한 작업을 위해 여러줄 코드를 사용하는 것보다 간단히 ppt 같은 외부 프로그램을 활용하는 것이 더 간편한 경우도 많다.

이전의 다른 아티클에서 officer 패키지를 활용해 MS powerpoint로 벡터 이미지를 만들고 편집하는 방법을 소개하였는데, 이번 글에서는 여러 장의 이미지를 대상으로 R에서 할 수 있는 고급 방법들과 이에 쓰이는 R 패키지를 소개한다.

result

이번 글에서 소개하는 방법들을 적용한 ppt 결과물을 먼저 소개한다.

우선 위 이미지는

  1. R에서 ggplot2를 사용하여 만든 시각화를
  2. cowplot을 이용하여 박스로 감싸고
  3. patchwork와 를 사용해 시각화와 설명을 위한 텍스트를 레이아웃에 따라 배치한 뒤
  4. officer를 활용하여 와이드스크린(혹은 16:9) 해상도 크기의 MS powerpoint로 만들어 낸 결과물이다.

이 결과물들은 벡터 그래픽스를 활용한 만큼, 다음 이미지처럼 ppt에서 편리하게 커스텀 수정이 가능하다.

patchwork

예시에서 사용할 이미지는 ggplot2의 mtcars 데이터셋을 사용하는 patchwork의 예시 코드를 사용한다. ggplot2과 각 차트에 대해서는 별도로 설명하지 않는다.

mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1
Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4
Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2
Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2
Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4
Merc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4
Merc 450SE 16.4 8 275.8 180 3.07 4.070 17.40 0 0 3 3
Merc 450SL 17.3 8 275.8 180 3.07 3.730 17.60 0 0 3 3
Merc 450SLC 15.2 8 275.8 180 3.07 3.780 18.00 0 0 3 3
Cadillac Fleetwood 10.4 8 472.0 205 2.93 5.250 17.98 0 0 3 4
Lincoln Continental 10.4 8 460.0 215 3.00 5.424 17.82 0 0 3 4
Chrysler Imperial 14.7 8 440.0 230 3.23 5.345 17.42 0 0 3 4
Fiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1
Honda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2
Toyota Corolla 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1
Toyota Corona 21.5 4 120.1 97 3.70 2.465 20.01 1 0 3 1
Dodge Challenger 15.5 8 318.0 150 2.76 3.520 16.87 0 0 3 2
AMC Javelin 15.2 8 304.0 150 3.15 3.435 17.30 0 0 3 2
Camaro Z28 13.3 8 350.0 245 3.73 3.840 15.41 0 0 3 4
Pontiac Firebird 19.2 8 400.0 175 3.08 3.845 17.05 0 0 3 2
Fiat X1-9 27.3 4 79.0 66 4.08 1.935 18.90 1 1 4 1
Porsche 914-2 26.0 4 120.3 91 4.43 2.140 16.70 0 1 5 2
Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.90 1 1 5 2
Ford Pantera L 15.8 8 351.0 264 4.22 3.170 14.50 0 1 5 4
Ferrari Dino 19.7 6 145.0 175 3.62 2.770 15.50 0 1 5 6
Maserati Bora 15.0 8 301.0 335 3.54 3.570 14.60 0 1 5 8
Volvo 142E 21.4 4 121.0 109 4.11 2.780 18.60 1 1 4 2

mtcars

library(ggplot2)

p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear))
p3 <- ggplot(mtcars) + geom_bar(aes(gear)) + facet_wrap(~cyl)
p4 <- ggplot(mtcars) + geom_bar(aes(carb))
p5 <- ggplot(mtcars) + geom_violin(aes(cyl, mpg, group = cyl))
p6 <- ggplot(mtcars) + geom_point(aes(mpg, disp)) + facet_wrap( ~ cyl)

patchwork는 여러 개의 ggplot 결과물들을 하나(한장)의 그래픽에 간단하게 배치할 수 있게 하는 R 패키지이다. 유사한 목적으로 patchwork외에 gridExtra나 cowplot등의 다른 패키지도 사용할 수 있다.

patchwork의 사용법은 크게 +, |, ( ), /로 구성된다.

| (vertical bar)

먼저 | 는 여러 이미지를 하나의 행에 배치하는 역할을 한다.

p1 | p2 | p3 | p4
patchwork - vertical bar

+

두번째로 +는 여러 이미지를 배치하는데 이때 행과 열은 grid 형태로, 행 순서로 채우는 방식을 사용한다.

p1 + p2 + p3 + p4
patchwork - plus

이때 이미지 배치를 특별히 지정을 하기 위해서는 plot_layout이라는 함수를 사용한다.

p1 + p2 + p3 + p4 + p5 +
  plot_layout(ncol = 3, byrow = FALSE)
patchwork - plot_layout

/

이어서 /를 사용하면 이미지를 열로 이어서 배치할 수 있다.

p1 / p2 
patchwork - slash

( )

마지막으로 ( )를 사용하면 이미지를 하나의 그룹으로 묶어서 배치할 수 있다.

p1 | (p2 / p3)
patchwork - parenthesis

물론 이 외에도 patchwork는 다양한 기능을 제공하는데, 자세한 내용은 공식 문서를 참고하자.

이를 활용해서 이제 앞에서 만들었던 예시 이미지 6개를 한장의 ppt에 배치해보자.

combined_plot <- (p1 | p2 | p3) /
  (p4 | p5 | p6) +
  theme(plot.margin = margin(1, 10, 1, 10)) 

combined_plot
patchwork - combined

cowplot

이어서 cowplot을 사용해 이미지 사이에 캡션과 박스를 추가하는 방법을 다루겠다.

우선 첫 이미지 3장을 표현하는 가상의 캡션을 list 형태로 생성한다. 참고로 <br>은 줄넘김을 의미한다.

내용은 lorem ipsum을 활용했다.

text <- list(
  p1 = "Lorem ipsum dolor sit amet <br> consectetur adipiscing elit.",
  p2 = "Integer lectus risus, <br> tincidunt eget felis non.",
  p3 = "Cras varius sapien et est consectetur porttitor."
)

이를 이전 combined_plot에 추가한다.

combined_plot <- (p1 | p2 | p3) /
  ( text$p1 | text$p2 | text$p3 ) /
  (p4 | p5 | p6) +
  theme(plot.margin = margin(1, 10, 1, 10)) 

# combined_plot 
# ERROR !!

그러나 이 상태로는 text 오브젝트가 ggplot 결과가 아닌 단순 텍스트만을 포함하기 때문에 에러가 발생한다. 이를 해결하기 위해 cowplot의 ggdraw 함수를 사용한다.

ggdraw

우선 cowplot은 ggplot2의 결과물에 annotation, theme를 추가하는 기능등을 제공하는 R 패키지로, ggdraw는 ggplot2의 결과물에 추가적인 그래픽을 그릴 수 있게 제일 상위 레벨에 레이어를 추가한다고 생각하면 편하다.

scatter <- ggplot(mpg, aes(displ, cty)) +
  geom_point() +
  theme_minimal_grid()

draft <- ggdraw(scatter) + 
  draw_label("Draft", colour = "#80404080", size = 120, angle = 45)

scatter | draft
cowplot - ggdraw

이 ggdraw를 사용해 이전의 text 내용 중 첫번째 라벨(p1)을 label로 갖는 ggplot 오브젝트를 생성하고 이를 combined_plot에 추가한다.

combined_plot <- (p1 | p2 | p3) /
  ( 
    ggdraw() + 
      labs(subtitle = text$p1) + 
      theme_void() +
      theme(
        text = element_text(size = 8),
        plot.subtitle = ggtext::element_textbox_simple(
          hjust = 0,
          halign = 0,
          margin = margin(3, 0, 0, 0)
        ),
        plot.margin = margin(0, 0, 0, 0)
      ) 
  ) /
  (p4 | p5 | p6) +
  theme(plot.margin = margin(1, 10, 1, 10)) 

combined_plot
cowplot - combined with caption

이어서 남은 라벨을 추가하기 전, 라벨 커스텀에 반복적으로 쓰이는 기능들을 별도의 함수로 만들어 사용하자. 추가로 caption과 그래프를 동일한 1:1:1의 높이로 할당하지 않고 caption 부분을 줄이기 위해 plot_layout의 height로 높이를 조절한다.

cowplot - caption function

다음은 각 시각화를 박스(테두리)로 감싸는 방법을 다룬다. 이를 위해 각 시각화에 ggdraw를 사용하여 레이어를 만들고, 그 레이어에 draw_line 함수를 사용해 (0,0) 부터 (1,1)을 지나는 직선을 추가하는 방법을 사용한다.

추가로 각 시각화에 text 속성을 조절하기 위해 theme 함수를 사용한다.

text_theme <- theme(
  text = element_text(size = 6), 
  axis.text = element_text(size = 6), 
  axis.title = element_text(size = 6),
  axis.title.x = element_text(size = 6), 
  axis.title.y = element_text(size = 6), 
  plot.title = element_text(size = 6),
  legend.text = element_text(size = 6),
  legend.title = element_text(size = 6)
)

(
  p1 | 
  ggdraw(p1 + text_theme) +
    draw_line(
      x = c(0, 1, 1, 0, 0), 
        y = c(0, 0, 1, 1, 0), 
        color = "black", 
        size = 0.5
    )
)
cowplot - box

이전과 마찬가지로 (반복되는) 박스를 만드는 기능들을 함수로 만들어 사용하자.

cowplot - box function

officer

이제 officer 패키지를 사용해 위에서 만든 그래프를 ppt에 추가해보자.

기본적인 officer에 대한 소개는 이전 아티클을 참고하면 좋다.

officer에서는 read_pptx 함수로 ppt 오브젝트를 생성하는데 이때 읽을 파일을 입력하지 않으면 너비와 높이가 4:3 비율인 새로운 오브젝트를 생성하여 사용한다.

만약 이를 그대로 사용한다면 다음 그림과 같이 애써만든 레이아웃이 깨지는 상황이 발생할 수 있기 때문에, ppt에서 임의의 사이즈를 갖는 템플릿을 만들고 이를 파일로 읽어 사용한다.

ppt를 생성한 다음, 페이지 설정에서 16:9 혹은 와이드 스크린으로 변경하는 방법도 있지만, 이 방법 또한 마찬가지로 그래프 요소들을 다시 배치해야 한다는 점은 동일하다.

read_pptx("~/Documents/template.pptx") |>
  remove_slide(1) |>
  add_slide() |>
  ph_with(
    value = "Example Title (baseline ~ X)", 
    location = ph_location_type(type = "title")
  ) |> 
  ph_with(
    rvg::dml(ggobj = combined_plot), 
    location = ph_location(left = 0, top = 1.5, height = 6, width = 13.333)
  ) |>
  print(target = "output2.pptx")

위 코드에서 2번째 줄 remove_slide 함수를 사용하지 않으면, 기존 템플릿의 슬라이드 이후 에 ggplot 결과를 담는 슬라이드를 만들기 때문에 아래와 같이 불필요한 첫페이지를 가지고 시작하게 된다.

한편 remove_slide와 add_slide를 둘 다 제거하고 ph_with으로 이미지만 더하게 되면 아래와 같이 템플릿의 제목과 새로 추가한 제목이 겹쳐서 보여지게 된다.

read_pptx("~/Documents/template.pptx") |>
  ph_with(
    value = "Example Title (baseline ~ X)", 
    location = ph_location_type(type = "title")
  ) |> 
  ph_with(
    rvg::dml(ggobj = combined_plot), 
    location = ph_location(left = 0, top = 1.5, height = 6, width = 13.333)
  ) |>
  print(target = "output2.pptx")

그러므로 템플릿을 사용하는 경우에는 remove_slide와 add_slide를 활용하는 것을 권장한다.

summary

이번 아티클에서는 patchwork와 cowplot을 사용해 여러 그래프를 하나로 합치고 약간의 커스텀을 거쳐, officer를 사용해 ppt에 추가하는 방법을 알아보았다. 이처럼 R의 기능과 ppt를 연결하는 방법은 다양하게 활용할 수 있으며, 이를 통해 보다 효율적인 작업을 할 수 있을 것이다.

최종 코드는 다음과 같다.

Code
library(ggplot2)
library(patchwork)
library(cowplot)
library(officer)

p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear))
p3 <- ggplot(mtcars) + geom_bar(aes(gear)) + facet_wrap(~cyl)
p4 <- ggplot(mtcars) + geom_bar(aes(carb))
p5 <- ggplot(mtcars) + geom_violin(aes(cyl, mpg, group = cyl))
p6 <- ggplot(mtcars) + geom_point(aes(mpg, disp)) + facet_wrap( ~ cyl)

text <- list(
  p1 = "Lorem ipsum dolor sit amet <br> consectetur adipiscing elit.",
  p2 = "Integer lectus risus, <br> tincidunt eget felis non.",
  p3 = "Cras varius sapien et est consectetur porttitor."
)

cap <- function(text){
  ggdraw() + 
    labs(subtitle = text) +
    theme_void() +
    theme(
      text = element_text(size = 8),
      plot.margin = margin(0, 0, 0, 0)
    )
}

text_theme <- theme(
  text = element_text(size = 6), 
  axis.text = element_text(size = 6), 
  axis.title = element_text(size = 6),
  axis.title.x = element_text(size = 6), 
  axis.title.y = element_text(size = 6), 
  plot.title = element_text(size = 6),
  legend.text = element_text(size = 6),
  legend.title = element_text(size = 6)
)

with.box <- function(p){
  ggdraw(p + text_theme) +
    cowplot::draw_line(
      x = c(0, 1, 1, 0, 0), 
      y = c(0, 0, 1, 1, 0), 
      color = "black", 
      size = 0.5
    ) 
}

combined_plot <- (with.box(p1) | with.box(p2) | with.box(p3)) /
  ( cap(text$p1 + text_theme) | cap(text$p2 + text_theme) | cap(text$p3 + text_theme) ) /
  (with.box(p4) | with.box(p5) | with.box(p6)) +
  plot_layout(heights = c(5, 0.1, 5)) +
  theme(plot.margin = margin(1, 10, 1, 10)) 

combined_plot

read_pptx("~/Documents/template.pptx") |>
  ph_with(
    value = "Example Title (baseline ~ X)", 
    location = ph_location_type(type = "title")
  ) |> 
  ph_with(
    rvg::dml(ggobj = combined_plot), 
    location = ph_location(left = 0, top = 1.5, height = 6, width = 13.333)
  ) |>
  print(target = "output2.pptx")

Reuse

Citation

BibTeX citation:
@online{kim2024,
  author = {Kim, Jinhwan},
  title = {Patchwork를 {활용한} {고급} {시각화}},
  date = {2024-05-17},
  url = {https://blog.zarathu.com/posts/2024-05-17-patchwork/},
  langid = {en}
}
For attribution, please cite this work as:
Kim, Jinhwan. 2024. “Patchwork를 활용한 고급 시각화.” May 17, 2024. https://blog.zarathu.com/posts/2024-05-17-patchwork/.