1 plotly

plotly.jsplot.lyDash를 배경에 두고 개발된 오픈소스 자바스크립트 라이브러리로 R로 가져온 인터랙티브 시각화 팩키지는 plotly는 웹페이지 Plotly R Open Source Graphing Library에서 다양한 예제를 만나볼 수 있고, Plotly ggplot2 Library를 통해서 영감을 얻을 수 있다. An interactive graphing library for R를 공식적인 GitHub 개발 사이트로 알려져 있다.

2 헬로 월드

rbokeh 인터랙티브 데이터 시각화 팩키지와 비교하여 plotly의 장점은 ggplot2와 연계가 된다는 점이 강점이다. 즉, plotly를 통해서 정적 ggplot2 시각화 산출물을 명령어 하나로 인터랙티브 시각화 산출물로 수월하게 탈바꿈시킬 수 있다.

즉, 정적 ggplot 그래프 객체를 생성시킨 후에 ggplotly() 함수에 던지게 되면 나름 유용한 인터랙티브 시각화 그래프 산출물이 생성된다.

library(tidyverse)
library(plotly)
library(gapminder)

## `ggplot` 정적 그래프
hello_world_g <- gapminder %>% 
  filter(year == max(year)) %>% 
  ggplot(aes(x=gdpPercap, y=lifeExp, color=continent)) +
    geom_point() 

hello_world_g

## `plotyly` 인터랙티브 그래프
ggplotly(hello_world_g)

3 단변량 인터랙티브 시각화

3.1 막대그래프

각 대륙별로 속한 국가수를 가장 최근 연도를 기준으로 살펴보자. 이를 위해서 먼저 가장 최근 조사연도를 추출하고 각 대륙별로 국가수를 계수한다. 그리고 나서 plot_ly() 함수에 던지는데 변수를 식별하는데 ~를 사용하는 것이 plotly 팩키지를 사용할 때 다른 rbokeh 혹은 ggplot2와 차이가 난다. ggplot2 문법과 마찬가지로 geom_col()와 유사하게 add_bars()를 사용해서 막대그래프를 완성하게 된다.

gapminder %>% 
  filter(year == max(year)) %>% 
  count(continent) %>% 
  mutate(continent = fct_reorder(continent, n, .desc=TRUE)) %>% 
  plot_ly(x = ~continent, y = ~n) %>% 
    add_bars()

3.2 히스토그램

기대수명(lifeExp)은 돈(gdpPercap)보다 소중하기 때문에 가장 최근연도 기대수명 산포를 add_histogram() 함수를 통해서 시각화한다. 최대 bin 갯수를 nbinsx=로 설정하여 히스토그램을 제작할 수 있다.

gapminder %>% 
  filter(year == max(year)) %>% 
  plot_ly(x = ~lifeExp) %>% 
    add_histogram(nbinsx = 30)

혹은 xbins=를 통해 구간을 설정하여 35세를 기준으로 5세 간격으로 히스토그램 생성도 가능하다.

gapminder %>% 
  filter(year == max(year)) %>% 
  plot_ly(x = ~lifeExp) %>% 
    add_histogram(xbins = list(start = 35, end = 85, size = 5))

3.3 밀도(density) 그래프

밀도는 선으로 표현되기 때문에 add_lines()를 사용한다. 각 대륙별로 데이터프레임을 생성시키고, 밀도를 density()로 추정한다. 그리고 나서 plot_ly()에 기대수명 밀도에 색상을 부여한다.

gapminder_df <- gapminder %>% 
  filter(year == max(year))

## 대륙별 데이터프레임 -----
africa_df <- gapminder_df %>% filter(continent == "Africa")
asia_df   <- gapminder_df %>% filter(continent == "Asia")
europe_df <- gapminder_df %>% filter(continent == "Europe")

## 밀도 추정 -----
africa_den <- density(africa_df$lifeExp)
asia_den   <- density(asia_df$lifeExp)
europe_den <- density(europe_df$lifeExp)

## 시각화 -----
plot_ly(opacity = 1) %>% 
    add_lines(x = ~africa_den$x, y = ~africa_den$y, name = "아프리카") %>% 
    add_lines(x = ~asia_den$x, y = ~asia_den$y, name = "아시아") %>% 
    add_lines(x = ~europe_den$x, y = ~europe_den$y, name = "유럽") %>% 
    layout(xaxis = list(title = '기대수명'),
           yaxis = list(title = '밀도'))

4 이변량 인터랙티브 시각화

4.1 산점도

x 변수도 연속형이고, y 변수도 연속형인 경우 인터랙티브 산점도를 add_markers() 함수로 구현한다.

gapminder %>% 
  filter(year == max(year)) %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp) %>% 
    add_markers()

4.2 그룹 막대그래프

add_bars() 막대그래프는 기본디폴트 설정으로 barmode = "dodge"로 되어 있어 이를 쌓고자 하는 경우 layout(barmode = "stack")으로 추가 설정을 한다.

gapminder %>% 
  count(year, continent) %>% 
  plot_ly(x = ~year, y = ~n, color = ~continent) %>% 
    add_bars() %>% 
    layout(barmode = "stack")

비율로 변환시킬 경우 비율을 구한 후에 계산된 비율을 plot_ly()에 전달한다.

gapminder %>% 
  count(year, continent) %>% 
  group_by(year) %>% 
  mutate(`비율` = n / sum(n)) %>% 
  plot_ly(x = ~year, y = ~`비율`, color = ~continent) %>% 
    add_bars() %>% 
    layout(barmode = "stack")

4.3 상자그림(boxplot)

add_boxplot() 함수를 사용하게 되면 대륙별로 상자그림을 통해 기대수명 분포를 한눈에 파악할 수 있다.

gapminder %>% 
  filter(year == max(year)) %>% 
  plot_ly(x = ~continent, y = ~lifeExp, color = ~continent) %>% 
    add_boxplot()

5 시각화 산출물 꾸미기

rbokeh와 마찬가지로 plotly도 색상(color), 투명도(opacity), 모양(symbol), 크기(size), 폭(width) 등을 지정할 수 있다.

투명도(opacity =), 모양(symbol =), 크기(size =)를 지정할 수 있다.

gapminder %>% 
  filter(year == max(year)) %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp, color = ~continent) %>% 
    add_markers(marker = list(opacity = 0.5, symbol = "square", size=20))

RColorBrewer 팩키지가 내장되되어 있어 add_markers() 함수의 colors =인자에 팔레트를 사용하는 것도 가능하고 색상을 각 범주별로 지정하는 것도 가능하다.

gapminder %>% 
  filter(year == max(year)) %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp, color = ~continent) %>% 
    add_markers(colors = "Dark2")

layout() 함수에 title=을 통해 그래프 제목을 달 수도 있고 xaxis =, yaxis =에 축라벨을 붙이는 것도 가능하다. showgrid=FALSE를 통해 참조선도 보여주거나 감출 수 있다.

gapminder %>% 
  filter(year == max(year)) %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp, color = ~continent) %>% 
  add_markers(colors = c("orange", "darkgray", "skyblue", "black", "red")) %>% 
  layout(title = '1인당 GDP와 기대수명 관계',
       xaxis = list(title = '1인당 GDP',
                    zeroline = TRUE,
                    range = c(0, 50000)),
       yaxis = list(title = '기대수명',
                    range = c(0,85),
                    showgrid = FALSE))

6 인터랙티브 시각화

6.1 tooltip

hoverinfo = "text"를 사용해서 text에 마우스로 점 주변을 선회(hover)시킬 때 표식에 제공할 내용을 정리한다.

gapminder %>% 
  filter(year == max(year)) %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp, color = ~continent, hoverinfo = "text",
          text = ~paste0("<b>", "Gapminder", "</b> <br>",
                         "인당 GDP: ", gdpPercap, "<br>",
                         "기대수명: ", lifeExp)) %>% 
  add_markers(colors = c("orange", "darkgray", "skyblue", "black", "red")) %>% 
  layout(title = '1인당 GDP와 기대수명 관계',
       xaxis = list(title = '1인당 GDP',
                    zeroline = TRUE,
                    range = c(0, 50000)),
       yaxis = list(title = '기대수명',
                    range = c(0,85)))

6.2 회귀선

회귀선을 비롯한 모형 적합선을 add_lines() 명령어를 통해서 추가할 수 있다. showlegend = FALSE를 통해 모형적합선에 대한 범례를 추가시킬 수 있다.

gap_loess <- loess(lifeExp ~ gdpPercap, data = gapminder_df)
gap_lm    <- lm(lifeExp ~ gdpPercap, data = gapminder_df)

gapminder_df %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp, hoverinfo = "text",
          text = ~paste0("<b>", "Gapminder", "</b> <br>",
                         "인당 GDP: ", gdpPercap, "<br>",
                         "기대수명: ", lifeExp)) %>% 
  add_markers(colors = c("orange", "darkgray", "skyblue", "black", "red"), showlegend = FALSE) %>% 
  add_lines(y = ~fitted(gap_lm), name = "회귀선") %>% 
  add_lines(y = ~fitted(gap_loess), name = "LOESS")
  # layout(showlegend = FALSE)

7 다수 그래프

7.1 subplot으로 병합

subplot() 함수로 서로 다른 형태 plotly 객체를 하나로 모아 작성할 수 있다.

asia_plotly <- gapminder_df %>% 
  filter(continent == "Asia") %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp, hoverinfo = "text",
          text = ~paste0("<b>", "Gapminder", "</b> <br>",
                         "인당 GDP: ", gdpPercap, "<br>",
                         "기대수명: ", lifeExp)) %>% 
    add_markers(name = ~continent) %>% 
    layout(title = '아시아 인당 GDP와 기대수명',
       xaxis = list(title = '1인당 GDP'),
       yaxis = list(title = '기대수명'))

africa_plotly <- gapminder_df %>% 
  filter(continent == "Africa") %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp, hoverinfo = "text",
          text = ~paste0("<b>", "Gapminder", "</b> <br>",
                         "인당 GDP: ", gdpPercap, "<br>",
                         "기대수명: ", lifeExp)) %>% 
    add_markers(name = ~continent) %>% 
    layout(title = '아프리카 인당 GDP와 기대수명',
       xaxis = list(title = '1인당 GDP'),
       yaxis = list(title = '기대수명'))

subplot(asia_plotly, africa_plotly, nrows = 1, shareY = TRUE, shareX = TRUE)

7.2 group_by() + do() 조합

group_by()split() 함수와 유사한 기능을 제공하여 각 대륙별로 데이터프레임을 쪼개고 do() 명령어가 lapply() 함수와 동일하게 시각화를 각 대륙별 작업을 담당하게 되고 이를 subplot() 함수로 최종 조합하여 시각화 결과물을 산출시키게 된다.

gapminder_df %>%
  group_by(continent) %>%
  do(plot = plot_ly(data =., x = ~gdpPercap, y = ~lifeExp, hoverinfo = "text",
                    text = ~paste0("<b>", "Gapminder", "</b> <br>",
                                   "인당 GDP: ", gdpPercap, "<br>",
                                   "기대수명: ", lifeExp)) %>% 
    add_markers(name = ~continent) %>% 
    layout(title = '인당 GDP와 기대수명',
       xaxis = list(title = '1인당 GDP'),
       yaxis = list(title = '기대수명'))
  ) %>%
  subplot(nrows = 2)

7.3 산점도 행렬 - SPLOM

산점도 행렬은 1975년 John Hartigan이 제안한 방식으로 산점도를 행렬형태 다수 작은 창으로 시각화하고 인터랙티브 방식으로 작은 창에 연결된 관계를 시각적으로 확인할 수 있도록 한다. plot_ly() 함수 다음에 add_trace()를 추가하고 dimensions =에 관찰하고자 하는 변수를 추가하면 수월히 구현할 수 있다. 마지막에 style()을 통해 중복되는 행렬창을 제거할 수 있다.

gapminder_df %>%
  mutate(log_pop = log10(pop)) %>% 
  plot_ly(color = ~continent) %>%
  add_trace(
    type = 'splom',
    dimensions = list(
      list(label='기대수명', values = ~lifeExp),
      list(label='인구수',   values = ~log_pop),
      list(label='인당GDP',  values = ~gdpPercap)
    )
  ) %>% 
  style(showlowerhalf = FALSE)

7.4 2D 히스토그램

적외선 열지도(heatmap)와 유사하게 두연속형 변수로 산점도를 그릴 경우 너무 많은 점이 특정 지역에 집중되는 경우 “2D 히스토그램” 혹은 “적외선 열지도(heatmap)”를 통해 시각화하는 것이 많이 사용된다. 이를 위해서 add_histogram2d() 함수를 활용한다.

gapminder_df %>%
  plot_ly(x = ~gdpPercap, y = ~lifeExp) %>%
  add_histogram2d(nbinsx= 30, nbinsy=30)

8 인터랙티브 지도

airports <- read_csv("https://raw.githubusercontent.com/datasets/airport-codes/master/data/airport-codes.csv")

airport_df <- airports %>% 
  select(ident, name, iso_country, municipality, coordinates) %>% 
  separate(coordinates, into=c("lat", "long"), sep = ",") %>% 
  mutate(long = as.numeric(long),
         lat = as.numeric(lat)) %>% 
  filter(iso_country == "KR") %>% 
  mutate(value = rpois(1374, lambda=100))

map_data(database = 'world', regions = 'south korea') %>% 
  group_by(group) %>% 
  plot_geo(x = ~long, y = ~lat) %>% 
   add_markers(size = I(1))