일반적으로 R ggplot2로 히스토그램을 그릴 때 동일한 폭의 bin 넓이(bin width)를 설정해줍니다. 


이번 포스팅에서는 R ggplot2로 bin의 넓이를 다르게 설정한 히스토그램 그리는 방법을 소개하겠습니다. (histogram with different bin width using R ggplot2)


간단한 예제 데이터프레임을 만들어서 예를 들어보았습니다. 요점은 사용자가 정의한 binwidth에 해당하는 group 변수를 하나 만들구요, geom_histogram() 함수로 히스토그램을 그릴 때 group 별로 subset을 하고 breaks argument로 사용자가 정의한 binwidth 구간을 설정해주는 것입니다. 



#----------------

# histogram with variable size of bin width and different colors per bins using ggplot2

#----------------


# sample data frame

mydf <- data.frame(var = c(1100, 10000, 100000, 190000, 110000, 220000, 550000, 701000, 790000))


# numeric notation for large numbers

options(scipen = 30)


library("ggplot2")


# fill color with different colors per bins

mydf$group <- ifelse(mydf$var < 10000, 1, 

                          ifelse(mydf$var < 100000, 2, 

                                 ifelse(mydf$var < 200000, 3, 

                                        ifelse(mydf$var < 500000, 4, 5))))


# breaks of bin

bins <- c(1000, 10000, 100000, 200000, 500000, 800000)


# draw histogram with variable size of bin width and different colors per bins

ggplot(mydf, aes(x= var)) +

  geom_histogram(data=subset(mydf, group==1), breaks = c(1000, 10000), fill="black") +

  geom_histogram(data=subset(mydf, group==2), breaks = c(10000, 100000), fill="yellow") +

  geom_histogram(data=subset(mydf, group==3), breaks = c(100000, 200000), fill="green") +

  geom_histogram(data=subset(mydf, group==4), breaks = c(200000, 500000), fill="blue") +

  geom_histogram(data=subset(mydf, group==5), breaks = c(500000, 800000), fill="red") +

  scale_x_continuous(breaks = bins, limits = c(1000, 800000)) +

  xlab("variable 1") + 

  ylab("count") +

  ggtitle("Histogram with different size of bin width and colors") + 

  theme(plot.title = element_text(hjust = 0.5, size = 14))


 



많은 도움이 되었기를 바랍니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



Posted by R Friend R_Friend

이번 포스팅에서는 R ggplot2 로 y축이 2개 있는 이중 축 그래프 그리는 방법을 소개하겠습니다.

 

엑셀로는 이중 축 그래프 그리기가 그리 어렵지 않은데요, R의 ggplot 도 sec.axis 을 사용하면 두번째 y축을 추가할 수 가 있습니다.

 

먼저 예제로 사용할 데이터셋을 만들어보겠습니다.

 

 

#=========================================
# Dual y-axes plot using ggplot2 sec.axis
#=========================================

rm(list=ls()) # clear all

# make a DataFrame
data_val <- matrix(
  c(-4.2922960, 0.00000000000389755, 0.00000000268579,
  -3.9102123, 0.00000000000939471, 0.00000003082191,
  -3.5281286, 0.00000000002264455, 0.00000027610486,
  -3.1460449, 0.00000000005458189, 0.00000193070482,
  -2.7639612, 0.00000000013156254, 0.00001053865139,
  -2.3818776, 0.00000000031711433, 0.00004490363779,
  -1.9997939, 0.00000000076436268, 0.00014935003987,
  -1.6177102, 0.00000000184239690, 0.00038775418083,
  -1.2356265, 0.00000000444085801, 0.00078584150863,
  -0.8535428, 0.00000001070411038, 0.00124319933681,
  -0.4714591, 0.00000002580086522, 0.00153523161114,
  -0.0893754, 0.00000006218962756, 0.00147990680737,
  0.2927083, 0.00000014989999897, 0.00111358189390,
  0.6747920, 0.00000036131440584, 0.00065408965954,
  1.0568757, 0.00000087090114242, 0.00029990225777,
  1.4389594, 0.00000209919260108, 0.00010733701573,
  1.8210431, 0.00000505982314747, 0.00002998793957,
  2.2031268, 0.00001219600184343, 0.00000653989939,
  2.5852105, 0.00002939662278856, 0.00000111332724,
  3.5852105, 0.00029392734366929, 0.00000000000000,
  4.5852105, 0.00293538878457633, 0.00000000000000,
  5.5852105, 0.02896916461589227, 0.00000000000000,
  6.5852105, 0.25470155892952129, 0.00000000000000,
  7.5852105, 0.94711869936516890, 0.00000000000000,
  8.5852105, 0.99999999999982903, 0.00000000000000,
  9.5852105, 1.00000000000000000, 0.0000000000000),
  nrow=26,
  byrow=TRUE)

# convert a matrix into a dataframe
df <- data.frame(data_val)

# column name
colnames(df) <- c("x", "y1", "y2")

 

> head(df)
          x           y1           y2
1 -4.292296 3.897550e-12 2.685790e-09
2 -3.910212 9.394710e-12 3.082191e-08
3 -3.528129 2.264455e-11 2.761049e-07
4 -3.146045 5.458189e-11 1.930705e-06
5 -2.763961 1.315625e-10 1.053865e-05
6 -2.381878 3.171143e-10 4.490364e-05

 

 

 

두 개 y축의 요약 정보를 보니 최대값의 차이가 매우 크다는 것을 알 수 있습니다. y1 축과 y2 축의 비율을 보니 약 651배 차이가 나네요. 이런 상태에서 그래프를 그리면 y2 축의 그래프가 바닥에 쫙 붙어서 그려지므로 두 축 간의 패턴, 특징, 구조를 비교하기가 어렵게 됩니다. 따라서 y2 축의 데이터를 y1축과 비교하기 쉽도록 y2에 'y1과 y2의 최대값의 비율(max_ratio)'를 곱해서 그래프를 그려보겠습니다.

 

 

> # max ratio b/w 2 axes
> summary(df)
       x                 y1                  y2          
Min.   :-4.2923   Min.   :0.0000000   Min.   :0.000e+00 
1st Qu.:-1.9043   1st Qu.:0.0000000   1st Qu.:7.000e-10 
Median : 0.4838   Median :0.0000003   Median :8.539e-06 
Mean   : 1.1492   Mean   :0.1243873   Mean   :3.020e-04 
3rd Qu.: 3.3352   3rd Qu.:0.0002278   3rd Qu.:3.658e-04 
Max.   : 9.5852   Max.   :1.0000000   Max.   :1.535e-03 
> max_ratio <- max(df$y1)/max(df$y2); max_ratio
[1] 651.367

 

 

 

y1 데이터로는 선 그래프를 그리고, y2 데이터로는 막대그래프를 그려보겠습니다.

이때 y2 에 해당하는 두번째 축의 막대그래프를 그릴 때는

 - (1) max(y1)/max(y2) 로 계산한 두 축의 최대값의 비율인 651을 y2에 곱했으며

       (geom_bar(aes(y = y2*max_ratio) 

 - (2) scale_y_continuous(sec.axis = sec_axis(~.*maxratio, name="y2") 를 사용해서 오른쪽의 두번째 축을 추가하였습니다.

 

 

> # Dual y-axes plot using ggplot2 sec.axis
> library(ggplot2)
> 
> g <- ggplot(df, aes(x = x))
>   g <- g + geom_line(aes(y = y1), colour = "red", size = 2)
>   
>   # adding the relative result5.data.A, 
> # transformed to match roughly the range of the B
> g <- g + geom_bar(aes(y = y2*max_ratio),
> fill = "blue",
> stat = "identity")
> > # adding secondary axis > g <- g + scale_y_continuous(sec.axis = sec_axis(~.*max_ratio, name="y2*651")) > > g

 

 

 

해석하기에 편리하도록 데이터의 소스가 무엇인지를 나타내는 텍스트와 화살표를 추가해보겠습니다.

 

 

> # adding text > g <- g + annotate("text", x = -2, y = 0.75, colour="black", label="y2*651") > g <- g + annotate("text", x = 5, y = 0.5, colour="black", label="y1") > > # adding arrow > library(grid) > g <- g + annotate("segment", x = -1.8, y = 0.75, + xend = -1, yend = 0.6, colour="blue", arrow=arrow()) > g <- g + annotate("segment", x = 5.2, y = 0.5, + xend = 6.7, yend = 0.4, colour="red", arrow=arrow()) > g

 

 

많은 도움이 되었기를 바랍니다.

 

Posted by R Friend R_Friend

페이스북의 KRUG (Korean R User Group) 에서 2017.11.18일 주말 퀴즈로 아래의 시각화를 푸는 문제가 있어서 재미로 풀어보았습니다. 


처음엔 금방 코드 짤 것으로 예상했는데요, 출제자분께서 중간 중간 장애물을 심어놓으셔서 제 예상보다 좀 더 걸렸네요. 


문제 풀면 KRUG 운영자분께서 스타벅스 기프티콘 주신다고 하는데요, 기대되네요. ^___^



# KRUG's plot quiz, as of 18th NOV. 2017


library(ggplot2)

#install.packages("dplyr")

library(dplyr)

str(mtcars)


# making 'model' variable

mtcars$model <- rownames(mtcars)


# ordering by cyl, hp

mtcars_ord <- arrange(mtcars[,c('model', 'cyl', 'hp')], cyl, desc(hp))


# marking an inflection point within cluster : threshold >= 40

mtcars_cyl <- c(4, 6, 8)

mtcars_ord_2 <- data.frame()


for (i in 1:length(mtcars_cyl)) {

  

  mtcars_tmp <- subset(mtcars_ord, subset = (cyl == mtcars_cyl[i]))

  

  for (j in 1:nrow(mtcars_tmp)) {

    if (j != nrow(mtcars_tmp) & mtcars_tmp$hp[j] - mtcars_tmp$hp[j+1] >= 40) {

      mtcars_tmp$hp_outlier[j] = '1_outlier'

    } else {

      mtcars_tmp$hp_outlier[j] = '2_normal'

    }

  }

  

  mtcars_ord_2 <- rbind(mtcars_ord_2, mtcars_tmp)

  rm(mtcars_tmp)

}


# converting cyl variable type from numeric to factor

mtcars_ord_2$cyl_cd <- paste0("cyl :", mtcars_ord_2$cyl)


model_order <- mtcars_ord_2$model[order(mtcars_ord_2$cyl_cd, 

                                        mtcars_ord_2$hp, 

                                        decreasing = FALSE)]


mtcars_ord_2$model <- factor(mtcars_ord_2$model, levels = model_order)



# drawing cleveland dot plot

ggplot(mtcars_ord_2, aes(x = hp, y = model)) +

  geom_point(size = 2, aes(colour = hp_outlier)) +

  scale_colour_manual(values = c("red", "black")) + 

  theme_bw() +

  facet_grid(. ~ cyl_cd, scales = "free_y", space = "free_y") +

  xlim(0, max(mtcars_ord_2$hp)) +

  geom_hline(yintercept = nrow(mtcars_ord_2[mtcars_ord_2$cyl == 4,]) + 0.5, 

             colour = "black", linetype = "dashed", size = 0.5) +

  geom_hline(yintercept = nrow(mtcars_ord_2) - 

               nrow(mtcars_ord_2[mtcars_ord_2$cyl == 8,]) + 0.5, 

             colour = "black", linetype = "dashed", size = 0.5) +

  theme(legend.position = 'none')

 




R Korea - KRUG(Korean R User Group) 에 문제 출제해주신 정우준님께서 나중에 정답지 올려주신 코드도 아래에 같이 공유합니다. 제가 짠 코드보다 한결 간결하네요. 



library(data.table)
library(dplyr)
library(ggplot2)

mtcars %>%
mutate(car.name=rownames(.)) %>%
arrange(cyl, hp) %>%
mutate(order.key=1:n()) -> data

data %>%
ggplot(aes(x=hp, y=reorder(car.name, order.key))) +
geom_point(
colour=case_when(
data$car.name %in% c('Ferrari Dino','Maserati Bora') ~ 'red', 
TRUE ~ 'black')) +
geom_hline(yintercept = 11.5, linetype='dashed') +
geom_hline(yintercept = 18.5, linetype='dashed') +
facet_wrap(~ cyl, labeller = label_both) +
scale_x_continuous(limits=c(0,max(data$hp))) +
theme_bw() +
theme(axis.title.y=element_blank())

 






Posted by R Friend R_Friend

이번 포스팅에서는 R ggplot2 패키지의 coord_fixed(ratio = number) 옵션을 사용해서 그래프의 크기 (가로, 세로 비율)를 조정하는 방법을 소개하겠습니다. 


1 ~ 4의 정수 좌표를 가지는 X 축과 

1 ~ 4의 정수 좌표를 가지는 Y 축을 가지는 

가상의 데이터를 사용해서 예를 들어보겠습니다. 


X축이 Y축이 1:1 비율인 그래프가 되겠습니다. 



[ 가상 데이터셋 생성 ]



#==========================

# Rssizing the plot

#==========================


install.packages("ggplot2")

library(ggplot2)


# X, Y coordinate and segments(A, B category) data set

my.df <- data.frame(XCoord = c(1, 1, 1, 2, 2, 2, 3, 3, 4, 4), 

                    YCoord = c(1, 2, 4, 1, 3, 4, 2, 4, 1, 4), 

                    Seg = c("A", "A", "A", "A", "B", "A", "B", "B", "B", "B"))





X와 Y좌표별로 Seg. 변수의 "A"는 검정색, "B"는 흰색으로 색깔을 지정해서 Heatmap을 그려보겠습니다. 

X좌표와 Y좌표가 1~4 범위를 동일하게 가지고 있으므로 크기에 대한 설정없이 디폴트 세팅으로 그래프를 그리면 아래 처럼 정사각형 1:1 비율로 그래프가 그려집니다. 



[ 그림 1 ] 원본 그래프 (Original Plot) : 가로축과 세로축이 1:1



# Original plot

ggplot(my.df, aes(x=XCoord, y=YCoord, fill=Seg)) +

  geom_tile(colour="gray80") +

  scale_fill_manual(values = c("black", "white")) +

  ggtitle("Original Heatmap by X4*Y4 size")






[ 그림 2 ] 가로축과 세로축의 비율을 1:2 로 설정하기 : coord_fixed(ratio = 2)


[그림 1]에서 원본 이미지가 가로축과 세로축이 1:1 비율의 정사각형 그래프였는데요, 이를 가로:세로 비율을 1:2로 세로가 가로의 2배 비율인 그래프(가로:세로 = 1:2)로 바꾸어 주려면 coord_fixed(ratio = 2) 를 설정해주면 됩니다. 



# Resized plot using coord_fixed(ratio = number)

ggplot(my.df, aes(x=XCoord, y=YCoord, fill=Seg)) +

  geom_tile(colour="gray80") +

  scale_fill_manual(values = c("black", "white")) +

  coord_fixed(ratio = 2) +

  ggtitle("Heatmap : Resized X:Y axis size with 1:2 ratio")







[ 그림 3 ] 가로축과 세로축의 비율을 2:1 로 설정하기 : coord_fixed(ratio = 0.5)


가로와 세로축 비율이 1:1인 원본 이미지 [그림 1] 을 가로가 세로축의 2배인 그래프 (가로:세로 = 2:1)로 바꾸고 싶다면 coord_fixed(ratio = 0.5) 로 설정해주면 됩니다. 



# Resized plot using coord_fixed(ratio = number)

ggplot(my.df, aes(x=XCoord, y=YCoord, fill=Seg)) +

  geom_tile(colour="gray80") +

  scale_fill_manual(values = c("black", "white")) +

  coord_fixed(ratio = 0.5) +

  ggtitle("Heatmap : Resized X:Y axis size with 2:1 ratio")









아래는 EBImage 패키지의 resize() 함수를 사용해서 png 이미지 파일로 출력할 때 이미지 크기를 (a) 특정 가로, 세로 크기로 설정해주는 방법과 (b) 비율로 설정해주는 방법입니다. 

R code는 stackoverflow 의 답변 중에서 aoles 님께서 달아놓은 것인데요, 코드 그대로 인용해서 소개합니다. ( * R code 출처 : https://stackoverflow.com/questions/35786744/resizing-image-in-r )


#==============
# Image resize using EBImage package

# installing EBImage package
source("http://bioconductor.org/biocLite.R")
biocLite("EBImage")


# resizing image using EBImage package's resize() function

library("EBImage")

x <- readImage(system.file("images", "sample-color.png", package="EBImage"))


# width and height of the original image
dim(x)[1:2]


# scale to a specific width and height
y <- resize(x, w = 200, h = 100)


# scale by 50%; the height is determined automatically so that
# the aspect ratio is preserved
y <- resize(x, dim(x)[1]/2)


# show the scaled image
display(y)


# extract the pixel array
z <- imageData(y)


# or
z <- as.array(y)

 



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾸욱 눌러주세요. 



Posted by R Friend R_Friend

(X축) 시간의 흐름에 따른 (Y축) 값의 추세, 변화 분석 및 탐색을 하는데 시계열 선 그래프(time series plot, line graph)를 많이 이용합니다. 


 이번 포스팅에서는 R ggplot2 패키지로 시계열 선그래프를 그리고, 거기에 세로선을 추가하는 작업을 해보겠습니다. 


ggplot2 에서 세로선을 추가할 때 geom_vline() 함수를 사용하는데요, 이게 시계열 데이터의 경우는 as.numeric() 함수를 사용해서 시계열 데이터를 숫자형 데이터로 변환을 해주어야 에러가 안나고 제대로 세로선이 그려집니다. 


이거 몰라서 한참을 구글링하면서 애 좀 먹었습니다. ^^;


간단한 시계열 데이터를 만들어서 예를 들어보겠습니다. 




> ##======================================================

> ## adding multiple vertical lines at time-series plot using R ggplot2

> ##======================================================

> # making time series data

> dt <- c("20170609100000", "20170609100100", "20170609100200", 

+         "20170609100300", "20170609100400", "20170609100500")

> val <- c(5.2, 3.4, 3.9, 6.3, 4.7, 5.6)

> dt_val <- data.frame(dt, val)

> dt_val <- transform(dt_val, 

+                     dt = as.POSIXct(dt, 

+                                     format = '%Y%m%d%H%M%S', 

+                                     origin = "1970-01-01", 

+                                     tz = "UTC"))

> dt_val

                   dt val

1 2017-06-09 10:00:00 5.2

2 2017-06-09 10:01:00 3.4

3 2017-06-09 10:02:00 3.9

4 2017-06-09 10:03:00 6.3

5 2017-06-09 10:04:00 4.7

6 2017-06-09 10:05:00 5.6

 




R ggplot2 패키지의 geom_line() 함수를 사용해서 시계열 선그래프를 그려보겠습니다. 



> # making time series plot

> library(ggplot2)

> ggplot(dt_val, aes(x = dt, y = val)) +

+   geom_line(size=1, color = "blue") + 

+   ggtitle("Time-series plot")

 




R ggplot2로 세로선을 추가할 때는 geom_vline(xintercept = x) 함수를 추가해주면 됩니다. 하지만 xintercept 에 들어가는 값이 날짜, 시간 포맷의 데이터일 경우 아래 처럼 에러가 납니다. 



> # To add vertical line at time series plot

> # Error in Ops.POSIXt((x - from[1]), diff(from)) : '/' not defined for "POSIXt" objects

> ggplot(dt_val, aes(x = dt, y = val)) +

+   geom_line(size = 1, color = "blue") +

+   geom_vline(xintercept = dt_val$dt[3], color =  "red", linetype = 2) +

+   ggtitle("Adding vertical line at time-series plot using geom_vline()")

Error in Ops.POSIXt((x - from[1]), diff(from)) : 

  '/' not defined for "POSIXt" objects

 




R ggplot2 시계열 선그래프에 X축이 날짜, 시간 포맷의 시계열 데이터인 경우 특정 날짜/시간에 세로선을 추가하기 위해서는 as.numeric(x) 함수를 사용해서 숫자형 데이터로 포맷을 바꾸어 주어야 합니다



> # Use as.numeric() function at xintercept

> ggplot(dt_val, aes(x = dt, y = val)) +

+   geom_line(size = 1, color = "blue") +

+   geom_vline(xintercept = as.numeric(dt_val$dt[3]), color = "red", linetype = 2) + # as.numeric() transformation

+   ggtitle("Adding vertical line at time-series plot using geom_vline() and as.numeric() transformation")

 







만약 복수의 세로선을 추가하고 싶다면 아래의 예제를 참고하세요. 만약 3번째와 5번째 x변수의 날짜/시간에 세로선을 추가하고 싶다면 dataset$variable[c(3, 5)] 처럼 indexing을 해서 xintercept 에 넣어주면 됩니다. 

(세로선 2개가 그려지기는 했는데요, 하단에 빨간색으로 "HOW_BACKTRACK environmental varialbe"이라는 경고메시지가 떴습니다. -_-; )



> # adding "Multiple" vertical lines

> ggplot(dt_val, aes(x = dt, y = val)) +

+   geom_line(size = 1, color = "blue") +

+   geom_vline(xintercept = as.numeric(dt_val$dt[c(3,5)]), color = "red", linetype = 2) +

+   ggtitle("Adding multiple vertical lines at time-series plot using geom_vline()")

HOW_BACKTRACE environmental variable.


 




이번에는 세로선을 그릴 기준 날짜/시간 데이터를 다른 데이터프레임에서 가져와야 하는 경우를 예로 들어보겠습니다. 먼저 세로선의 기준이 되는 xintercept 에 들어갈 날짜/시간 정보가 들어있는 data frame 을 만들어보죠. 



> # adding multiple vertical lines with another data frame

> dt_2 <- c("20170609100150", "20170609100430")

> val_2 <- c("yes", "yes")

> dt_val_2 <- data.frame(dt_2, val_2)

> dt_val_2 <- transform(dt_val_2, 

+                       dt_2 = as.POSIXct(dt_2, 

+                                         format = '%Y%m%d%H%M%S', 

+                                         origin = "1970-01-01", 

+                                         tz = "UTC"))

> dt_val_2

                 dt_2 val_2

1 2017-06-09 10:01:50   yes

2 2017-06-09 10:04:30   yes

 




R ggplot2 시계열 선그래프를 그린 원본 데이터프레임(아래 예제에서는 dt_val)과는 다른 데이터프레임(아래 예제에서는 dt_val_2)의 날짜/시간 데이터를 사용해서 복수의 세로선을 그려보겠습니다.  두 개의 세로선이 그려지기는 했는데요, "HOW_BACKTRACE environmental variable"이라는 빨간색 경고 메시지가 떴습니다. 그런데, 예전에는 에러 메시지 뜨면서 안그려졌었는데, 블로그 쓰려고 다시 해보니 그려지기는 하는군요. ^^; 



> # time series plot with multiple vertical lines from another data frame

> ggplot(dt_val, aes(x = dt, y = val)) +

+   geom_line(size = 1, color = "blue") +

+   geom_vline(xintercept = as.numeric(dt_val_2$dt_2), color = "red", linetype = 2) +

+   ggtitle("Time-seires plot with multiple vertical line from another dataframe")

HOW_BACKTRACE environmental variable.




위의 예제처럼 했는데 혹시 Error: Aesthetics must be either length 1 or the same as the data (6): xintercept 와 같은 에러 메시지가 뜨고 그래프가 안그려진다면 아래처럼 geom_vline(data = dataframe, xintercept = ... ) 처럼 데이터를 가져오는 데이터프레임을 명시해주면 문제가 해결됩니다.  이걸 몰라서 또 한참을 고민하고, 구글링하고, 참 애먹었던 적이 있습니다. -_-;



> # time series plot with multiple vertical lines from another data frame(2)

> ggplot(dt_val, aes(x = dt, y = val)) +

+   geom_line(size = 1, color = "blue") +

+   geom_vline(data = dt_val_2, xintercept = as.numeric(dt_val_2$dt_2), color = "red", linetype = 2) +

+   ggtitle("Time-series plot with multiple vertical lines from another dataframe 2")



 



많은 도움이 되었기를 바랍니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾸욱 눌러주세요. ^^



Posted by R Friend R_Friend

프로젝트 과제, 세부 업무 별로 시작 시점과 끝 시점을 선으로 그어서 한 눈에 일정 관리를 할 수 있도록 도와주는 시각화 기법으로 간트 차트(Gantt Chart)를 많이 사용합니다. 


간트 차트가 일정 관리 측면에서는 매우 강력한 시각화 도구이다 보니 간트 차트를 쉽고 빠르게 그릴 수 있도록 도와주는 '뉴간트메이커 간트 차트', '차트 스쿨 2.0', 'GanttProject', 'Online Gantt Chart' 등의 전문 소프트웨어가 있습니다. 


R을 가지고도 간트 차트를 그릴 수 있는데요, 이번 포스팅에서는 R의 timevis 패키지를 사용해서 간트 차트를 그리는 방법을 알아보겠습니다. 


R로 간트 차트를 그리려고 반나절 이상을 골머리를 앓다가 timevis 패키지를 발견하고서 얼마나 반가웠는지 모릅니다. 그리고 timevis 패키지를 사용해보니 제공하는 기능이 매우 놀라웠습니다. 들인 노력 대비 산출물이 매우 세련되 보이거든요. interactive 기능도 제공합니다. ^^b  timevis 패키지는 vis.js 의 timeline module 과 htmlwidgets R package 를 근간으로 해서 만들어졌다고 하는걸 보니 java script 기반의 패키지를 가지고 R 바인딩해놓은 같습니다. 


timevis 패키지는 Dean Attali 라는 분이 오픈 소스로 개발했는데요, R-Shiny founder라고 자기소개에 나와있네요. 요즘 탐색적 데이터 분석(Exploratory Data Analysis)하는데 R-Shiny 사용해보면서 감탄하고 있는데요, R-Shiny app에 timevis 로 그린 간트차트도 추가해서 이쁘게 app하나 만들었더니 간지 나더라구요. Dean Attali 개발자님께 이래저래 고마운 마음 전합니다. ^^b


아래 포스팅은 Deam Attali의 Git-Hub(https://github.com/daattali/timevis) 페이지와 R ?timevis 도움말의 예제 코드를 거의 수정없이 인용해서 작성하였구요, 그래프의 화면 캡쳐와 동영상을 추가해서 처음 사용하시는 분의 이해를 조금 더 돕는다는 취지로 작성을 해보았습니다. 



1. timevis 패키지 설치 및 로딩



##---------------------------------

## gantt chart by timevis package

##---------------------------------


# reference : https://github.com/daattali/timevis


# install timevis package

install.packages("timevis")

 




2. 날짜 데이터 입력 없는 상태에서 timeline 형태 살펴보기 


아래 timeline의 가운데 빨간 선은 시스템 날짜(Sys.Date) 입니다. 



# minimum view of timeline without any data at system time

library(timevis)

timevis()

 






3. 특정 날짜(Item), 혹은 기간(Range) 데이터를 입력한 간트 차트 (Gantt Chart) 그리기


timevis 패키지에서 사용하는 데이터셋이 데이터프레임(DataFrame)이라는 점이 원천 데이터를 거의 손볼 필요없이 그대로 사용할 수 있어서 저는 매우 좋더군요.  아래의 R script 처럼 'id', 'content', 'start', 'end'라는 칼럼이 데이터프레임에 포함되어 있으면 됩니다. 


  • id : 인덱싱(indexing) 할 때 사용
  • content : 간트 차트에 포함될 내용
  • start : 시작 시간 (년-월-일, 혹은 년-월-일 시간:분:초 포맷)
  • end : 끝 시간 (년-월-일, 혹은 년-월-일 시간:분:초 포맷)


start은 반드시 날짜, 혹은 날짜&시간 데이터가 들어가야만 에러가 안나구요, end 칼럼에는 NA 결측값으로 비워두어도 상관없습니다. end 칼럼에도 일시 데이터가 들어가면 start ~ end 의 기간(range)의 timeline 이 선으로 길게 그려집니다. 



# adding data to timevis() by DataFrame

data <- data.frame(

  id      = c(1:4),

  content = c("Item_First"  , "Item_Second"  ,"Ranged_First", "Ranged_Second"),

  start   = c("2017-05-26", "2017-05-27 01:30:00", "2017-05-27 05:00:00", "2017-05-30"),

  end     = c(NA          , NA                   , "2017-05-28 15:00:00", "2017-05-31 03:10:00")

)


# view of Gantt chart

timevis(data)

 






4. 데이터프레임의 각 칼럼 데이터 형태


input으로 사용되는 DataFrame의 각 칼럼의 데이터 행태를 살펴보면 id는 정수형(integer), content, start, end는 요인형(factor) 입니다.  혹시 데이터프레임 만들어서 timevis() 함수 적용했는데 그래프가 안그려지고 에러가 나면 content, start, end 칼럼의 데이터 형태가 요인형(factor)인지 확인해보시기 바라며, 혹시 문자형(character)으로 되어 있으면 as.factor() 를 사용해서 요인형으로 변환 후에 timevis() 를 다시 적용해보시기 바랍니다. 



> # checking data type

> sapply(data, class)

       id      content      start        end 

"integer"  "factor"  "factor"  "factor"

 




5. Zoom-in, Zoom-out, 좌-우 이동하는 동적 시각화


우측 상단에 있는 '+' 단추를 누르면 'Zoom-in'이 되어 더 짧은 기간으로 현미경의 눈으로 심화해서 간트 차트를 그려줍니다 (아래 그림 예시).  반대로 '-' 단추를 누르면 'Zoom-out'이 되어 높은 하늘 위의 새의 눈으로 더 넓은 기간의 간트 차트를 보여줍니다. 


 





커서로 timeline 을 클릭한 후에 좌, 우로 끌고 가면 간트 차트가 동적으로 움직이며, 마우스 휠을 사용해서도 Zoom-in, Zoom-out 을 할 수가 있습니다.  동적으로 움직이는 신기한(?) 그래프를 단 한 줄의 R script (즉, timevis(data) 로 끝) 로 만든 것입니다.  말로 설명하려니 감흥이 덜할 것 같은데요, 아래 동영상 참고하세요. 





6. Zoom 단추 숨기기, 편집 기능 설정, timeline 높이 설정


  • showZoom = FALSE   : 우측 상단의 Zoom 단추 숨기기  ( <= 이거 없어도 마우스 휠 사용하면 됨)
  • options = list(editable = TRUE)  :  TRUE 이면 timeline bar를 커서로 선택했을 때 색이 바뀌며, 'X' 표시를 누르면 간트 차트에서 사라짐 
  • options = list(height = "300px")  : timeline 높이를 하드 코딩으로 설정 ( <= 이거 굳이 하드코딩 안해줘도 알아서 유동적으로 높이 잘 조정해 줌) 


# hide the zoom buttons, options for editable, height

timevis(data, 

            showZoom = FALSE, 

            options = list(editable = TRUE, 

                                  height = "300px"))


 




7. %>% chain operator, 일정 추가(add Item), 디폴트 일정 선택(set Selected Item)


  • timevis() %>% : chain operator  (dplyr 패키지 사용해본 분이라면 익숙하실 듯)
  • setItems(data.frame(id, content, start, end)) : id, content, start, end(optional) 로 이루어진 원본 데이터셋
  • addItem(list(id, content, start, end)) : 개별 일정을 추가하고 싶을 때 사용
  • setSelection("1") : 간트 차트가 화면에 떴을 때 처음 선택이 되게 하고 싶은 id 를 지정 (아래 예에서는 "1"번의 "one" item 이 색깔이 바뀌어 있고, editable = TRUE 로 설정했으므로 'X' 표시가 같이 나타남)


# %>% operator, set editable Options, add Item, set Selected Item

timevis() %>%

  setItems(data.frame(

    id = 1:2,

    content = c("one", "two"),

    start = c("2017-05-28", "2017-05-30")

  )) %>%

  setOptions(list(editable = TRUE)) %>%

  addItem(list(id = 3, content = "three", start = "2017-05-29")) %>%

  setSelection("1") 





8. 그룹(group)으로 묶어서 간트 차트 구성하기


업무의 특성(예: 컨설팅, 개발), 프로젝트 단계(예 : Phase 1, Phase 2), 조직(예: 설계팀, 개발팀, 지원팀) 등 특정 기준에 따라서 Item 들을 그룹으로 묶어서 간트 차트를 그리고 싶을 때가 있습니다.  이 기능을 잘 사용하면 간트 차트를 좀더 구조화해서 보여줄 수 있으므로 일목요연하게 시각화하는데 매우 유용합니다. 



# using the groups feature to group together multiple items into different buckets

timevis(

  data = data.frame(

    start = c(Sys.Date(), Sys.Date(), Sys.Date() + 1, Sys.Date() + 2),

    content = c("one", "two", "three", "four"), 

    group = c(1, 2, 1, 2)),

  groups = data.frame(id = 1:2, content = c("Group_1", "Group_2"))

)

 




9. timevis() 간트 차트를 RShiny 에 연동


RShiny 에도 timevis() 패키지를 사용해서 간트 차트, timeline을 삽입할 수 있습니다.  RShiny 문법에 대해서는 이 포스팅에서 설명하자면 너무 길어지게 되므로 생략하겠구요, 동적 문서(Dynamic Document) 카테고리에서 별도로 나중에 포스팅하겠습니다. 



# RShiny


library(shiny)


data <- data.frame(

  id = 1:3,

  start = c("2015-04-04", "2015-04-05 11:00:00", "2015-04-06 15:00:00"),

  end = c("2015-04-08", NA, NA),

  content = c("<h2>Vacation!!!</h2>", "Acupuncture", "Massage"),

  style = c("color: red;", NA, NA)

)


ui <- fluidPage(

  timevisOutput("appts"),

  div("Selected items:", textOutput("selected", inline = TRUE)),

  div("Visible window:", textOutput("window", inline = TRUE)),

  tableOutput("table")

)


server <- function(input, output) {

  output$appts <- renderTimevis(

    timevis(

      data,

      options = list(editable = TRUE, multiselect = TRUE, align = "center")

    )

  )

  

  output$selected <- renderText(

    paste(input$appts_selected, collapse = " ")

  )

  

  output$window <- renderText(

    paste(input$appts_window[1], "to", input$appts_window[2])

  )

  

  output$table <- renderTable(

    input$appts_data

  )

}


shinyApp(ui, server)


 



[Reference] https://github.com/daattali/timevis


많은 도움 되었기를 바랍니다. 


이번 포스팅이 도움이 되셨다면 아래의 '공감~'를 꾸욱 눌러주세요. ^^



Posted by R Friend R_Friend

이번 포스팅에서는 ggplot2로 그린 그래프에서 


 - (1) 범례 위치 바꾸기

        (changing the position of legend)


 - (2) 범례 글자 크기 및 색깔 바꾸기

        (changing the size and color of the legend)


 - (3) 범례 항목 순서 바꾸기

        (changing the order of legend label)


 - (4) 범례 없애기

        (removing the legend)


등 범례(legend)를 다루는 방법에 대해서 알아보겠습니다. 



MASS package의 Cars93 데이터프레임에 있는 '차종(Type)별 고속도로 연비(MPG.highway)' 박스 그래프를 가지고 예를 들어보겠습니다. 


박스 그래프 디폴트 옵션으로 그리면 아래와 같은 결과가 나옵니다. 



##---------------------------

# ggplot2 : legend 

##---------------------------

library(MASS)

library(ggplot2)


# Boxplot of MPG.highway per Car Type

mpg <- ggplot(Cars93, aes(x = Type, y = MPG.highway, fill = Type)) +

  geom_boxplot() +

  theme_bw() +

  ggtitle("MPG.highway per Car Type")


mpg

 






 (1) 범례 위치 바꾸기 (changing the position of legend) : theme(legend.position = "bottom")


디폴트 세팅에서는 오른쪽("right")에 범례가 있습니다.  

theme(legend.position) argument를 사용해서 범례 위치를 그래프 바깥쪽으로 해서 아래("bottom"), 왼쪽("left"), 위쪽("top")으로 차례대로 바꾸어 보겠습니다. (범례를 위쪽에 배치하는 것은 이상하게 보이네요. ^^;)



# (1) Changing the legend position

# (1-1) outside the plot by using theme(legend.position = "right", "bottom", "left", "top")


# bottom

mpg + theme(legend.position = "bottom")




# left

mpg + theme(legend.position = "left")



# top

mpg + theme(legend.position = "top") 





범례를 놓고 싶은 위치의 x, y 좌표를 숫자 벡터로 입력을 해주면 그래프의 안쪽에도 범례를 집어넣을 수 있습니다. 이때 x, y 좌표는 0~1 사이의 실수 값을 넣어주면 됩니다. x 좌표에서는 0이 그래프 안쪽의 가장 왼쪽이고 1은 가장 오른쪽이 되며, y 좌표에서 0은 그래프 안쪽의 가장 아래쪽이고 1은 가장 위쪽 방향이 되겠습니다. 아래 예에서는 그래프의 오른쪽 상단에 여유 공간이 많이 있으므로 c(0.9, 0.8)의 숫자 벡터를 입력해서 범례를 우측 상단에 놓아 보겠습니다. 



# (1-2) inside the plot by using the location vector c(x coordinate, y coordinate) 

mpg + theme(legend.position = c(0.9, 0.8)) # coordinate value : between 0 and 1





 (2) 범례 글자 크기 및 색깔 바꾸기 (changing the size and color of the legend)

      : theme(legend.title = element_text(color, size, face))

      : theme(legend.text = element_text(color, size, face))


범례의 글자 크기와 색깔, 폰트도 바꿀 수가 있습니다. 자유도가 높아 그래프를 예쁘게 꾸미고 싶은 분에게는 유용할 것입니다. 범례의 제목(title)과 레이블(label text)의 색을 파란색(color = "blue)으로 바꾸고, 범례 제목은 12 크기(size = 12)로 더 키우고, 범례 레이블은 8 크기로 좀더 작게 조정하겠습니다. 그리고 범례 제목은 굵게(face = "bold"), 범례 레이블은 이탤릭체(face = "italic")로 바꾸어 보겠습니다. 



# (2) Changing the legend title and label size and color

mpg + 

  theme(legend.title = element_text(color = "blue", size = 12, face = "bold")) + # legend title

  theme(legend.text = element_text(color = "blue", size = 8, face = "italic")) # legend label





 (3) 범례 항목 순서 바꾸기 (changing the order of legend label)

      : factor(Type, levels = ("Compact", "Small", "Midsize", "Large", "Sporty", "Van")


범례의 레이블 순서를 차종(Type)의 크기에 맞게 바꾸고 싶을 때는 ggplot2 에서 무얼 하는 것은 아니구요, 데이터너 전처리 단계에서 transform() 함수를 사용해서 factor(Type, levels = c("Compact", "Small", "Midsize", "Large", "Sporty", "Van") 처럼 요인(factor)의 levels 의 순서를 바꾸어 주면 됩니다. (ie, ordered factor)


이렇게 해주면 범례의 레이블 순서도 바뀌고, x 축의 항목들의 순서도 역시 바뀌게 됩니다. 



> # (3) Changing the order of legend labels

> # checking the levels and class of 'Type' variable

> attributes(Cars93$Type)

$levels

[1] "Compact" "Large"   "Midsize" "Small"   "Sporty"  "Van"    


$class

[1] "factor"


> # changing the level's order of factor 'Type'

> Cars93 <- transform(Cars93, 

+                  Type = factor(Type, levels = c("Compact", "Small", "Midsize", 

+                                                                    "Large", "Sporty", "Van")))

> attributes(Cars93$Type)

$levels

[1] "Compact" "Small"   "Midsize" "Large"   "Sporty"  "Van"    


$class

[1] "factor"


> mpg_legend_order_change <- ggplot(Cars93, aes(x = Type, y = MPG.highway, fill = Type)) +

+   geom_boxplot() +

+   theme_bw() +

+   ggtitle("Changing the order of legend labels by Car Size")

> mpg_legend_order_change

 




 (4) 범례 없애기 (removing the legend) 

        : theme(legend.title = element_blank())

        : theme(legend.position = 'none')


범례를 아예 없애고 싶을 때는 범례의 제목을 없애는 theme(legend.title = element_blank())와 범례의 레이블을 없애는 theme(legend.position = 'none') 의 두 개의 arguments 를 추가해주면 됩니다. 



# (4) Removing the legend title and labels

mpg + 

  theme(legend.title = element_blank()) +   # remove legend title

  theme(legend.position = 'none')                # remove legend labels


 



이상으로 ggplot2로 그린 그래프의 범례(legend)를 설정하는 여러가지 방법을 알아보았습니다. 

많은 도움 되었기를 바랍니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾸욱 눌러주세요. ^^



Posted by R Friend R_Friend

R ggplot2 패키지로 box-plot 을 그렸을 때 조금 더 손을 보고 싶을 때가 있습니다.  


가령, 배경색을 흰색으로 바꾸어서 좀더 깔끔하게 보이게 하고 싶을 수 있습니다. 혹은 라벨 길이가 너무 길거가 요인 개수가 너무 많아서 가로의 X축이 모자라서 라벨명이 짤리는 상황이 발생했을 때 라벨을 45도나 90도 돌려 줌으로써 긴 라벨을 온전히 그래프에 다 제시를 해줄 수도 있겠지요. 아니면 아예 라벨을 가로축이 아니라 세로축으로 제시를 해주는 것도 방법이겠구요. 


이에 이번 포스팅에서는 아래의 3가지 소소한 팁을 공유하고자 합니다. 



(1) ggplot 배경을 흰색으로 바꾸기 


(2) ggplot x축 라벨 각도를 90도 돌리기


(3) ggplot x축과 y축 바꾸기 (x축 라벨을 세로 축으로 옮기기)

 

(4) ggplot x축 라벨의 폭(width)을 일정한 값으로 고정하고, 라벨이 일정 폭을 넘으면 다음 줄로 넘겨서 라벨을 명기하기



예제로 사용한 데이터는 MASS 패키지의 Cars93 데이터프레임에 들어있는 '차종(Type)'과 '고속도로연비(MPG.highway)' 입니다. 



> library(ggplot2)

> library(MASS)

> str(Cars93)

'data.frame': 93 obs. of  27 variables:

 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...

 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...

 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...

 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...

 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...

 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...

 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...

 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...

 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...

 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...

 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...

 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...

 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...

 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...

 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...

 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...

 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...

 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...

 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...

 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...

 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...

 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...

 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...

 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...

 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...

 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...

 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...

 




  (1) ggplot 그래프 배경을 흰색으로 바꾸기 : theme_bw() 


기본 설정값을 사용해서 박스 그래프를 그려보면 아래와 같은 배경색으로 그래프가 나타납니다. 



# box-plot by default

ggplot(Cars93, aes(x=Type, y=MPG.highway)) +

  geom_boxplot()

 






ggplot배경을 흰색으로 바꾸려면 theme_bw() 를 추가해주면 됩니다. 


 

# box-plot with white background

ggplot(Cars93, aes(x=Type, y=MPG.highway)) +

  geom_boxplot() +

  theme_bw() # white background 






 (2) ggplot x축 라벨 각도를 90도 돌리기 : theme(axis.text.x=element_text(angle=90, hjust=1))


이번에는 x축에 있는 '차종(Type)' 라벨을 90도 회전시켜서 세로로 세워보겠습니다.  

라벨이 길다거나 라벨 개수가 많아서 x축에 다 안들어가는 바람에 라벨이 짤리는 경우에 사용하면 유용합니다. 



# box-plot with axis label's angle of 90 degrees

ggplot(Cars93, aes(x=Type, y=MPG.highway)) +

  geom_boxplot() +

  theme_bw() +

  theme(axis.text.x=element_text(angle=90, hjust=1))




 




물론 라벨을 눕히는 각도를 'angle' 옵션을 사용해서 원하는 만큼 조절할 수도 있습니다.  

아래는 x축 라벨의 각도를 45도 회전시켜본 예제입니다. 



# box-plot with x axis label's angle of 45 degrees

ggplot(Cars93, aes(x=Type, y=MPG.highway)) +

  geom_boxplot() +

  theme_bw() +

  theme(axis.text.x=element_text(angle=45, hjust=1))


 





x축 라벨을 회전시킬 수 있으면 y축 라벨도 회전시킬 수 있겠지요? 

아래 예제는 왼쪽 세로의 y축 라벨을 45도 회전 시켜본 것입니다. 



# box-plot with y axis label's angle of 45 degrees

ggplot(Cars93, aes(x=Type, y=MPG.highway)) +

  geom_boxplot() +

  theme_bw() +

  theme(axis.text.y=element_text(angle=45, hjust=1))


 






 (3) ggplot x축과 y축의 위치를 바꾸기 : coord_flip()


아래는 coord_flip() 을 사용해서 x축의 '차종(Type)'을 세로축으로 옮기고, y축의 '고속도로연비(MPG.highway)'를 가로축으로 옮겨본 것입니다. 


x축의 라벨이 너무 많거나 길 경우에 이처럼 x축과 y축을 바꿔주면 라벨이 짤리는 경우 없이 세로로 길게 볼 수 있어서 유용합니다. 



# box-plot with the label at vertical axis

ggplot(Cars93, aes(x=Type, y=MPG.highway)) +

  geom_boxplot() +

  theme_bw() +

  coord_flip()





 

 

  (4) ggplot x축 라벨의 폭(width)을 일정한 값으로 고정하고, 
     라벨이 일정 폭을 넘으면 다음 줄로 넘겨서 라벨을 명기하기

   : aes(stringr::str_wrap(V1, 15), V2)

 

x 축 라벨이 너무 많거나 길 경우에 위의 (3번) x축, y축 바꾸기 외에도, 이번에 소개할 x축의 폭(width)을 stringr::str_wrap() 함수일정한 값으로 고정하고, 그 갑을 넘으면 다음 줄로 넘겨서 라벨을 표시하도록 하는 방법이 있습니다.  아래에 간단한 예를 들어서 전, 후 비교 설명을 해보겠습니다.

 

  • x축의 라벨이 너무 많고 길어서 서로 중첩되고 있는 예제

 

> library(ggplot2)
> library(MASS)
> library(stringr)
> 
> label <- c("1_Short", 
+            "2_Long label", 
+            "3_A very, very long label", 
+            "4_Really long, long, long and long label",
+            "5_An extremely long, long, long, and long label")
> value <- c(10, 20, 30, 40, 50)
> df <- data.frame(label, value)
> 
> ggplot(df, aes(x=label, y=value)) +
+   geom_bar(stat="identity") + 
+   xlab(NULL)

 

 

 

 

  • x축의 라벨 폭(width)을 stringr 의 str_wrap() 함수로 폭을 조정한 예제

 

> ggplot(df, aes(x=label, y=value)) + + geom_bar(stat="identity") + + aes(stringr::str_wrap(label, 15), value) + + xlab(NULL)

 

 

 


 

많은 도움이 되었기를 바랍니다. 


이번 포스팅이 도움이 되셨다면 아래의 '공감~'를 꾹 눌러주세요. ^^





Posted by R Friend R_Friend

이번 포스팅에서는 방향성 있는 가중 네트워크 시각화 (directed and weighted network visualization)에 대해서 소개하겠습니다.

 

 - R igraph package 를 사용해서

 - 심리학 저널의 상호인용 빈도 데이터

  (* source : Lattin, Carroll and Green, 2003)

 

를 대상으로 논문 상호인용 빈도 네트워크를 그려보겠습니다.

 

먼저, 방향성 있는 가중 네트워크에 대해서 한번 더 소개하자면, 아래와 같이 화살표로 방향이 있고, 연결선에 가중치(weight)가 있는 네트워크를 말합니다.

 

 

[ 방향성 있는 가중 네트워크 (Directed and Weighted Network) ]

 

 

 

심리학 저널의 상호인용 빈도를 행렬 형태로 나타내보면 아래와 같습니다.

행렬의 요소 (i, j) 빈도는 저널 i의 논문이 저널 j에 인용된 회수입니다.

 - 예) (1, 2) = 32 : AJP 논문 32편이 JASP 논문에 인용됨

        (2, 1) = 8 : JASP 논문 8편이 AJP 논문에 인용됨

 

[ 심리학 저널 간 상호 인용 빈도 ]

 

[1] AJP

 [2] JASP

[3] JAP 

[4] JCPP 

[5] JCP 

[6] JEDP 

[7] JEXP 

[8] PKA 

[1] AJP

119

32 

35 

125 

[2] JASP

510 

116 

19 

[3] JAP

16 

84 

11 

[4] JCPP

21 

11 

533 

70 

[5] JCP

73 

225 

13 

[6] JEDP

52 

[7] JEXP

85 

119 

16 

126 

12 

27 

586 

13 

[8] PKA

10 

15 

58 

 

 

library()함수로 R의 igraph package를 로딩해서 시각화를 해보겠습니다.

igraph package는 데이터 포맷이 ID1, ID2, Weight or Frequency 의 행렬(matrix) 입니다.

아래에 위의 표를 행렬로 입력해서 불러들였는데요, 빈도(3번째 열)에 +1 을 해주었습니다.

(0이면 에러가 나는지 안되네요)

 

R script는 'R을 활용한 사회네트워크분석 입문'의 예시를 거의 대부분 사용하였으며,

그래프의 설정 parameter를 이리 저리 숫자를 조절해보면서 살짝 바꿔보았습니다.

 

> ############################################# > ## Network Analysis - igraph package > ############################################# > library(igraph) > > psych_edgelist <- matrix( + c(0, 1, 32, 0, 2, 2, 0, 3, 35, 0, 4, 6, 0, 5, 4, + 0, 6, 125, 0, 7, 2, 1, 0, 8, 1, 2, 8, 1, 3, 8, + 1, 4, 116, 1, 5, 9, 1, 6, 19, 1, 7, 5, 2, 0, 4, + 2, 1, 16, 2, 3, 0, 2, 4, 11, 2, 5, 7, 2, 6, 6, + 2, 7, 5, 3, 0, 21, 3, 1, 11, 3, 2, 1, 3, 4, 1, + 3, 5, 0, 3, 6, 70, 3, 7, 0, 4, 0, 0, 4, 1, 73, + 4, 2, 7, 4, 3, 0, 4, 5, 3, 4, 6, 0, 4, 7, 13, + 5, 0, 1, 5, 1, 9, 5, 2, 8, 5, 3, 1, 5, 4, 7, + 5, 6, 0, 5, 7, 2, 6, 0, 85, 6, 1, 119, 6, 2, 16, + 6, 3, 126, 6, 4, 12, 6, 5, 27, 6, 7, 13, 7, 0, 2, + 7, 1, 4, 7, 2, 10, 7, 3, 1, 7, 4, 7, 7, 5, 5, 7,6, 15), + byrow = T, ncol = 3) + 1 > > psych_edgelist [,1] [,2] [,3] [1,] 1 2 33 [2,] 1 3 3 [3,] 1 4 36 [4,] 1 5 7 [5,] 1 6 5 [6,] 1 7 126 [7,] 1 8 3 [8,] 2 1 9 [9,] 2 3 9 [10,] 2 4 9 [11,] 2 5 117 [12,] 2 6 10 [13,] 2 7 20 [14,] 2 8 6 [15,] 3 1 5 [16,] 3 2 17 [17,] 3 4 1 [18,] 3 5 12 [19,] 3 6 8 [20,] 3 7 7 [21,] 3 8 6 [22,] 4 1 22 [23,] 4 2 12 [24,] 4 3 2 [25,] 4 5 2 [26,] 4 6 1 [27,] 4 7 71 [28,] 4 8 1 [29,] 5 1 1 [30,] 5 2 74 [31,] 5 3 8 [32,] 5 4 1 [33,] 5 6 4 [34,] 5 7 1 [35,] 5 8 14 [36,] 6 1 2 [37,] 6 2 10 [38,] 6 3 9 [39,] 6 4 2 [40,] 6 5 8 [41,] 6 7 1 [42,] 6 8 3 [43,] 7 1 86 [44,] 7 2 120 [45,] 7 3 17 [46,] 7 4 127 [47,] 7 5 13 [48,] 7 6 28 [49,] 7 8 14 [50,] 8 1 3 [51,] 8 2 5 [52,] 8 3 11 [53,] 8 4 2 [54,] 8 5 8 [55,] 8 6 6 [56,] 8 7 16 > > psych.w <- graph.edgelist(psych_edgelist[, 1:2]) > psych.w IGRAPH D--- 8 56 -- + edges: [1] 1->2 1->3 1->4 1->5 1->6 1->7 1->8 2->1 2->3 2->4 2->5 2->6 [13] 2->7 2->8 3->1 3->2 3->4 3->5 3->6 3->7 3->8 4->1 4->2 4->3 [25] 4->5 4->6 4->7 4->8 5->1 5->2 5->3 5->4 5->6 5->7 5->8 6->1 [37] 6->2 6->3 6->4 6->5 6->7 6->8 7->1 7->2 7->3 7->4 7->5 7->6 [49] 7->8 8->1 8->2 8->3 8->4 8->5 8->6 8->7 > > E(psych.w)$weight <- psych_edgelist[,3] > psych.w IGRAPH D-W- 8 56 -- + attr: weight (e/n) + edges: [1] 1->2 1->3 1->4 1->5 1->6 1->7 1->8 2->1 2->3 2->4 2->5 2->6 [13] 2->7 2->8 3->1 3->2 3->4 3->5 3->6 3->7 3->8 4->1 4->2 4->3 [25] 4->5 4->6 4->7 4->8 5->1 5->2 5->3 5->4 5->6 5->7 5->8 6->1 [37] 6->2 6->3 6->4 6->5 6->7 6->8 7->1 7->2 7->3 7->4 7->5 7->6 [49] 7->8 8->1 8->2 8->3 8->4 8->5 8->6 8->7 > > psych.diag <- c(119, 510, 84, 533, 225, 52, 586, 58) > > psych.name <- c("AJP", "JASP", "JAP", "JCPP", "JCP", "JEDP", "JEXP", "PKA") > > plot(psych.w, + layout = layout.circle, + vertex.size = 2, + vertex.shape = "none", + vertex.size = psych.diag, + vertex.label = psych.name, + vertex.label.font = 2, + vertex.label.cex = sqrt(psych.diag)/10, + edge.width=2 + E(psych.w)$weight/10, + edge.arrow.width = E(psych.w)$weight/100 + )

 

 

 

 

 

igraph plot 의 parameter 기능은 아래와 같습니다. 

parameter를 바꿔가면서 최적의 이쁜 그래프를 찾아가 보시기 바랍니다.

 

 

1) Layout : 점의 좌표를 정하는 알고리즘

 

 - layout.circle : 원 배치 (위의 예시)

 

 - layout.random : 무작위 배치

 

 

- layout.fruchterman.reingold : Fruchterman Reingold 배치 알고리즘

 

 

 - layout.kamada.kawai : kamada Kawai 배치 알고리즘

 

 

 

 - layout.lgl : 대규모 네트워크를 위한 배치 알고리즘

 

 

 

2) Edge : 선 관련 파라미터

 

- edge.color : 선 색 지정 (default = "darkgrey")

- edge.width : 선 폭

 

- edge.arrow.size : 화살 크기

- edge.arrow.width : 화살 폭

- edge.arrow.mode : 화살 머리 유형 (0 : 없음,  1 : 역방향,  2 : 순방향,   3 : 양방향)

                            (무방향 네트워크의 경우 default = 0)

 

- edge.lty : 선 유형 ("solid", "dashed", "dotted", "dotdash", "longdash", "twodash")

- edge.label : 선 레이블

- edge.label.family : 선 레이블 종류 ("serif", "sans", "mono" 등)

- edge.label.font : 선 레이블 글자형 (1 : plain text, 2 : bold, 3 : italic, 4 : bold italic)

- edge.label.cex : 선 레이블 크기 (default = 1)

- edge.label.color : 선 레이블 색 (default = "navy")

 

 

3) Vertex : 점 관련

 

- vertex.size : 점 크기, vector도 가능 (default = 15)

- vertex.color : 점 색 (default = "SkyBlue2")

- vertex.frame.color : 점 윤곡의 색 (default = "black")

- vertex.shape : 점 형태 ("circle", "square", "rectangle", "none", default = "circle")

 

- vertex.label : 점 레이블 (vector)

- vertex.label.family : 점 레이블 종류 ("serif", "sans", "mono" 등)

- vertex.label.font : 점 레이블 글자형 (1 : plain text, 2 : bold, 3 : italic, 4 : bold italic)

- vertex.label.cex : 점 레이블 크기 (default = 1)

- vertex.label.dist : 점 중심과 레이블 간 거리 (default = 0)

- vertex.label.degree : 점 레이블 방향(radian) (좌 : 0, 우 : pi, 상 : -pi/2, 하 : pi/2)

- vertex.label.color : 점 레이블 색 (default = "navy")

 

 

네트워크 그래프 하나 그리는데 무슨 놈의 파라미터가 종류가 이렇게 많은 건지 놀랍기도하고, 이걸 언제 다 설정하나 부담되기도 하지요?

 

default 설정 값을 이용해서 한번 얼른 그려보시고요, 원하는 모양이 아니다 싶으면 parameter 종류 중에서 살짝 살짝 손을 좀 봐가면서 몇 번 더 그래프를 그려보시기 바랍니다. 

 

R이 제공하는 이런 다양한 그래프 옵션이면 못할 것이 없겠지요?!

 

[Reference]

- R을 활용한 사회네트워크분석 입문, 허명회 저, 자유아카데미, 2012

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡'를 꾸욱 눌러주세요.

 

 

Posted by R Friend R_Friend

이번 포스팅에서는 방향을 가진 네트워크 상에서 유량, 에너지, 물자 등의 '네트워크(network) 기반'의 흐름(flow)을 시각화하는데 유용한 Sankey Diagram 에 대해서 알아보겠습니다. 

 

Sankey Diagram 은 얼핏보면 평행좌표그림(parallel coordinate plot)과 비슷한 면이 있습니다.  하지만 Sankey Diagram은 Node -> Edge 로의 관계, 경로가 있다는 점, 경로의 두께를 weight 에 따라서 다르게 한다든지, 색깔을 부여할 때도 반투명(alpha)하게 한다든지 해서 평행좌표그림 대비 다릅니다. (평행좌표그림은 그냥 다변량 변수들을 y축 높이를 같게 해서 옆으로 x를 죽 늘어놓은 형태. 경로 개념 없음. 선 두께 동일) 

 

아래 이미지는 구글에서 Sankey Diagram 이라고 키워드 검색했을 때 나오는 이미지들을 화면캡쳐한 것입니다.  아래 이미지를 보면 '아, 이거~ ' 싶으시죠?

 

 

* 이미지 출처 : Google 검색

 

 

자, 그럼

 

 - R의 riverplot package(* author : January Weiner) 를 가지고

 - minard 데이터셋(* source : Charles Joseph Minard)을 사용해서
   (R riverplot package에 내장되어 있음)

 

나폴레옹 군대가 러시아로 진군했다가 퇴각했던 경로를 시각화해보겠습니다.

 

 

분석의 재미를 더하기 위해 "minard" 데이터셋에 대한 역사적인 배경을 간략히 알아보고 넘어가겠습니다.

 

때는 바야흐로 1812년, 유럽이 되겠습니다.  프랑스의 나폴레옹은 유럽의 상당 국가를 점령했으며, 영국을 침탈하기 위해 백방으로 쌈질을 걸었지만 번번히 실패하고 있던 상황이었습니다.  이에 화가 난 나폴레옹은 '바다를 건너가서 싸우는 게 승산이 낮으니 차라리 유럽 본토와 영국과의 무역을 봉쇄해서 영국의 피를 말리자. 그래, 바로 이거야. 내 사전에 불가능은 없어!' 라는 계획을 세우게 됩니다.

 

그러나 but,

 

나폴레옹의 프랑스가 이미 과도하게 힘이 세졌기 때문에 견제가 필요하다가 생각한 러시아의 알렉산더 짜르는 '나폴레옹, 내가 니 봉이냐?  영국 다음에 러시아 공격할거지?  내가 누구 좋으라고 니 말을 따라?' 라면서 나폴레옹의 영국과의 무역 폐쇄령을 쌩까고 게기게 됩니다. 

 

이에 발끈한 나폴레옹은 프랑스 대군을 모집해서 러시아 짜르의 못되고 괴씸한(?) 버릇을 고쳐주고자, 본떼를 보여주고자 1812년 10월, 겨울이 코앞인 시점에 러시아 모스코바로 진격을 하게 됩니다. 이때만 해도 정말 나폴레옹 군대는 "진격의 거인" 이었습니다.

 

그러나 but,

 

러시아 알렉산더 짜르가 대책없이 나폴레옹에게 대든게 아니었습니다.  러시아 짜르는 러시아 군과 국민에게 아주 간단한(?) 전략의 명령을 내립니다.  "프랑스군이 진격하는 곳의 모든 것을 태워서 프랑스군이 아무것도 탈취하지 못하도록 하고, 싸우지는 말고 퇴각하라.  전투 전략 끝!"

 

아마, 나폴레옹의 프랑스군은 처음 며칠은 러시아로 무혈입성하는 것에 신이 났을지도 모릅니다.

 

그러나 but,

 

10월이 11월이 되고, 그 담에 12월이 되면서 추위와 배고픔과 질병에 프랑스 군인들의 대부분이 죽어나갔습니다.  진격의 거인 나폴레옹은 러시아 짜르의 "불태우고 후퇴" 전략에 속수무책으로 당하면서 거의 전멸을 당하게 됩니다.

 

 

 

아래의 그림이 나폴레옹 프랑스 군대가 러시아로 진격했다가 퇴각한 진로를 지도 상에 표기한 것입니다.

 

* 출처 : https://robots.thoughtbot.com/analyzing-minards-visualization-of-napoleons-1812-march

 

 

 

여기까지의 이야기를 토대로 숫자와 그래프를 가지고 한눈에 실감할 수 있는 시각화를 Charles Joseph Minard 이라는 분이 아래와 같이 했습니다.

 

[ Napoleon army march : minard ]

* 출처 : https://robots.thoughtbot.com/analyzing-minards-visualization-of-napoleons-1812-march

 

 

 

위의 2차원의 minard visualization에는 다양한 차원의 정보가 알차게(!) 들어있는데요,


 - (1) 프랑스군의 진격과 퇴각 경로 (advance and retreat path and direction)

        : 연한 색깔이 진격(advance), 검정 색깔이 퇴각(retreat)

        : 도시 이름은 좌표에 따라 text로 표기

        : 프랑스 군이 세갈래(하나의 큰 줄기, 두 개의 얇은 줄기)로 나누어 진격한 것도 선이 갈라지게 표현

 - (2) 프랑스군의 인명 손실 규모 (loss of life at a time and location)
        : 선의 두께(line width), 처음에는 몽둥이처럼 두꺼웠던 선이 퇴각 마무리 시점에는 실처럼 가늘게 됨

 

 - (3) 온도 (temperature)

        : 그림 하단에 퇴각 시점의 온도를 그리 넣음.  최저 -30도씨까지 떨어졌음.  얼어죽기 딱 좋은 날씨. -_-;

 

 - (4) 강 (river)

        : 하단에 얇은 수직 선으로 나폴레옹군이 퇴각 시점에 맞닥트려 시련을 더해 준 강(river)을 그려 넣음. 

 

 

이렇게 많은 알찬 정보를 저 위의 시각화 하나에 오롯히 담아 냈습니다!!!  이해하기 쉽죠?!

 

 

이걸 데이터로 나타내 보면 아래와 같습니다.  @@;  

이게 무슨 소린가, 데이터가 뭘 말해주려고 하나.... 눈 돌아가지요? 

위의 그래프로 보면 단박에 이해되는 것을 아래의 숫자로 보면 한숨 나오고 갑갑하지요? ㅋㅋ

 

> install.packages("riverplot")
> library(riverplot)

 

> data( minard )
> minard
$edges
    ID1  ID2  Value direction
1    A1   A2 422000         A
2    A2   A3 400000         A
3    A3   A4 320000         A
4    A4   A5 320000         A
5    A5   A6 300000         A
6    A6   A7 280000         A
7    A7   A8 240000         A
8    A8   A9 210000         A
9    A9  A10 180000         A
10  A10  A11 175000         A
11  A11  A12 145000         A
12  A12  A13 140000         A
13  A13  A14 127100         A
14  A14  A15 100000         A
15  A15  A16 100000         A
16  A16   R1 100000         A
17   R1   R2 100000         R
18   R2   R3  98000         R
19   R3   R4  97000         R
20   R4   R5  96000         R
21   R5   R6  87000         R
22   R6   R7  55000         R
23   R7   R8  37000         R
24   R8   R9  24000         R
25   R9  R10  20000         R
26  R10  R11  50000         R
27  R11  R12  50000         R
28  R12  R13  48000         R
29  R13  R14  20000         R
30  R14  R15  12000         R
31  R15  R16  14000         R
32  R16  R17   8000         R
33  R17  R18   4000         R
34  R18  R19  10000         R
35  R19  R19  10000         R
36   A3 A4.2  60000         A
37 A4.2 A5.2  40000         A
38 A5.2 A6.2  33000         A
39 A6.2  R10  30000         R
40   A2 A3.1  22000         A
41 A3.1  R18   6000         A

$nodes
       ID Longitude Latitude
A1     A1      24.0     54.9
A2     A2      24.5     55.0
A3     A3      25.5     54.5
A4     A4      26.0     54.7
A5     A5      27.0     54.8
A6     A6      28.0     54.9
A7     A7      28.5     55.0
A8     A8      29.0     55.1
A9     A9      30.0     55.2
A10   A10      30.3     55.3
A11   A11      32.0     54.8
A12   A12      33.2     54.9
A13   A13      34.4     55.5
A14   A14      35.5     55.4
A15   A15      36.0     55.5
A16   A16      37.6     55.8
R1     R1      37.7     55.7
R2     R2      37.5     55.7
R3     R3      37.0     55.0
R4     R4      36.8     55.0
R5     R5      35.4     55.3
R6     R6      34.3     55.2
R7     R7      33.3     54.8
R8     R8      32.0     54.6
R9     R9      30.4     54.4
R10   R10      29.2     54.3
R11   R11      28.5     54.2
R12   R12      28.3     54.3
R13   R13      27.5     54.5
R14   R14      26.8     54.3
R15   R15      26.4     54.4
R16   R16      25.0     54.4
R17   R17      24.4     54.4
R18   R18      24.2     54.4
R19   R19      24.1     54.4
A4.2 A4.2      26.6     55.7
A5.2 A5.2      27.4     55.6
A6.2 A6.2      28.7     55.5
A3.1 A3.1      24.6     55.8

$cities
   Longitude Latitude           Name
1       24.0     55.0          Kowno
2       25.3     54.7          Wilna
3       26.4     54.4       Smorgoni
4       26.8     54.3      Moiodexno
5       27.7     55.2      Gloubokoe
7       28.5     54.3     Studienska
8       28.7     55.5        Polotzk
9       29.2     54.4           Bobr
10      30.2     55.3        Witebsk
11      30.4     54.5         Orscha
13      32.0     54.8       Smolensk
14      33.2     54.9    Dorogobouge
15      34.3     55.2          Wixma
16      34.4     55.5          Chjat
17      36.0     55.5        Mojaisk
18      37.6     55.8         Moscou
19      36.6     55.3      Tarantino
20      36.5     55.0 Malo-Jarosewii

 

 

* source : R riverplot package, author : January Weiner, data source : Charles Joseph Minard

 

 

 Sankey Diagram에서 사용하는 node, edge라는 용어를 이해하기 위해서, 두 개체 간 쌍을 이룬 관계 (mathematical structures used to model pairwise relations between objects)를 다루는 Graph Theory에 대해서 간략히 짚고 넘어가겠습니다.

 

아래 그래프처럼 점(Node or Point or Vertice)과 선(Edge or Link or Line or Arc)으로 개체 간의 관계를 나타내는 그래프로 나타내어 연구하는 수학, 컴퓨터 과학 분야가 Graph theory입니다.  최적화(optimization) 할 때도 네트워크 그래프 많이 쓰곤 합니다.

 

 

 

 

Sankey Diagram 그리려면

 

  - Nodes : 개체들의 ID, Longitude, Latitude, Labels

  - Edges : 개체 간 관계를 나타내는 ID 1, ID 2, Value (or weight) 

 

정보가 필요합니다.

 

> str(minard)
List of 3
 $ edges :'data.frame':	41 obs. of  4 variables:
  ..$ ID1      : chr [1:41] "A1" "A2" "A3" "A4" ...
  ..$ ID2      : chr [1:41] "A2" "A3" "A4" "A5" ...
  ..$ Value    : num [1:41] 422000 400000 320000 320000 300000 280000 240000 210000 180000 175000 ...
  ..$ direction: Factor w/ 2 levels "A","R": 1 1 1 1 1 1 1 1 1 1 ...
 $ nodes :'data.frame':	39 obs. of  3 variables:
  ..$ ID       : chr [1:39] "A1" "A2" "A3" "A4" ...
  ..$ Longitude: num [1:39] 24 24.5 25.5 26 27 28 28.5 29 30 30.3 ...
  ..$ Latitude : num [1:39] 54.9 55 54.5 54.7 54.8 54.9 55 55.1 55.2 55.3 ...
 $ cities:'data.frame':	18 obs. of  3 variables:
  ..$ Longitude: num [1:18] 24 25.3 26.4 26.8 27.7 28.5 28.7 29.2 30.2 30.4 ...
  ..$ Latitude : num [1:18] 55 54.7 54.4 54.3 55.2 54.3 55.5 54.4 55.3 54.5 ...
  ..$ Name     : Factor w/ 20 levels "Bobr","Chjat",..: 5 18 15 9 4 16 13 1 19 12 

 

 

 

 

R riverplot package를 사용해서 드디어 Sankey diagram을 그려보겠습니다.  R script는 riverplot package(* author : January Weiner)에 있는 예제 script를 그대로 인용하였습니다. (날로 먹는 듯한 이 기분..^^;) 

 

 

> ############################
> # Sankey diagram 
> # using R riverplot package 
> # minard data (list format)
> ############################
> 
> # install.packages("riverplot")
> library(riverplot)
> data( minard )
> 
> nodes <- minard$nodes
> edges <- minard$edges
> colnames( nodes ) <- c( "ID", "x", "y" )
> colnames( edges ) <- c( "N1", "N2", "Value", "direction" )
> 
> 
> # color the edges by troop movement direction
> edges$col <- c( "#e5cbaa", "black" )[ factor( edges$direction ) ]
> 
> # color edges by their color rather than by gradient between the nodes
> edges$edgecol <- "col"
> 
> # generate the riverplot object and a style
> river <- makeRiver( nodes, edges )
> style <- list( edgestyle= "straight", nodestyle= "invisible" )
> 
> # plot the generated object
> plot( river, lty= 1, default_style= style )
> 
> # Add cities
> with( minard$cities, points( Longitude, Latitude, pch= 19 ) )
> with( minard$cities, text( Longitude, Latitude, Name, adj= c( 0, 0 ) ) )
> # Add title
> title("Sankey Diagram - Napoleon army march, minard")

 

* R script author : January Weiner

 

 

다음번 포스팅에서는 igraph package를 사용해서 방향성 있는 가중 네트워크를 시각화해보겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡'를 꾸욱 눌러주세요. ^^

 

[Reference]

 - R riverplot package manual : https://cran.r-project.org/web/packages/riverplot/riverplot.pdf

 - On minards visualization : https://robots.thoughtbot.com/analyzing-minards-visualization-of-napoleons-1812-march

 

 

Posted by R Friend R_Friend