patchwork를 활용한 고급 시각화

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

R
cowpot
ggplot2
officer
patchwork
powerpoint
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

예시에서 사용할 이미지는 ggplot2mtcars 데이터셋을 사용하는 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외에 gridExtracowplot등의 다른 패키지도 사용할 수 있다.

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 결과가 아닌 단순 텍스트만을 포함하기 때문에 에러가 발생한다. 이를 해결하기 위해 cowplotggdraw 함수를 사용한다.

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_layoutheight로 높이를 조절한다.

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_slideadd_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_slideadd_slide를 활용하는 것을 권장한다.

summary

이번 아티클에서는 patchworkcowplot을 사용해 여러 그래프를 하나로 합치고 약간의 커스텀을 거쳐, 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/.