그룹(집단, 요인) 간의 데이터 분포 형태, 변화 추이 등을 비교 분석하기에 유용한 방법으로 비교하려는 축을 기준으로 면을 분할하여 그래프를 그룹 간 비교하는 방법이 있습니다.
Lattice 패키지에서는 Trellis 를 사용하는데요, ggplot2 패키지에서는 facet_grid() 함수와 facet_wrap() 함수를 사용하여 면 분할을 구현할 수 있습니다.
Base Graphics 패키지에서는 par() 함수를 사용해서 면 분할을 지정해줄 수 있습니다만, x축과 y축의 scale이 들쭉날쭉해서 직접적으로 서로 비교하기가 곤란하거나, y축의 min, max 값이 그룹 간 숫자를 모두 감안해서 자동 설정되는 것이 아니다보니 분석가가 미리 y축 값의 범위를 계산해보고, 혹은 그려보고 나서 y축 값을 세팅해줘야 하므로 lattice나 ggplot2 대비 불편합니다. 따라서 집단간 비교를 위한 면 분할이 필요한 경우 ggplot2나 lattice 패키지를 권합니다.
MASS 패키지 내 무게(Weight), 고속도로연비(MPG.highway), 차종(Type, 범주형), 생산국가(Origin, 범주형) 의 4개 변수를 사용해서, x축에 무게(Weight), y축에 고속도로연비(MPG.highway), 그리고 면 분할의 기준으로 범주형 변수인 차종(Type)과 생산국가(Origin) 변수를 사용하겠습니다.
facet_grid()를 먼저 예제를 보이고, 그 후에 facet_wrap()의 예제를 들겠습니다. 두 함수가 비슷하면서도 조금 다릅니다. 분석가가 필요로 하는 아웃풋 이미지에 맞게 골라서 사용하면 되겠습니다.
이전 포스팅에서 x축과 y축의 값에 따라 산점도 그리는 방법을 알아보았다면, 이번 포스팅에서는 여기에 더해서 z라는 제 3의 변수에 비례해서 점의 크기를 변화시켜서 그린 그래프가 버블 그래프 (Bubble Chart) 입니다. 산점도가 2차원의 그래프(단, 색깔이나 모양 조건을 추가하면 3차원 정보 제공 가능)라면, 버블 그래프 (Bubble Chart)는 3차원의 그래프가 되어 지면에 보다 많은 정보량을 제공할 수 있는 장점이 있습니다.
ggplot2에서는 산점도, 점 그래프를 그리는 geom_point() 함수와 함께 scale_size_area() 함수를 같이 사용하면 버블 그래프 (Bubble Chart)를 그릴 수가 있습니다.
MASS 패키지의 Cars93 데이터 프레임 내에 차 모델명(Model), 차종(Type), 무게(Weight), 고속도로연비(MPG.highway), 가격(Price)의 5개 변수를 사용하여 버블 그래프를 그려보겠습니다. 데이터가 너무 많으면 버블 그래프를 그릴 때 겹쳐 보여서 보기 싫으므로 차종(Type)에서 "compact"와 "large"의 두 종만 선별해서 예를 들어보겠습니다.
ggplot2의 geom_point()와 scale_size_area() 함수를 사용하여 버블 그래프 (bubble chart)를 그려보겠습니다. ggplot2는 별도의 설치와 호출이 필요한 패키지이므로 아래와 같이 install.packages()와 library()로 설치 및 호출을 먼저 해야 합니다.
> install.packages("ggplot2")
Installing package into ‘C:/Users/user/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
trying URL 'http://cran.rstudio.com/bin/windows/contrib/3.2/ggplot2_1.0.1.zip'
Content type 'application/zip' length 2676272 bytes (2.6 MB)
downloaded 2.6 MB
package ‘ggplot2’ successfully unpacked and MD5 sums checked
The downloaded binary packages are in
C:\Users\user\AppData\Local\Temp\RtmpKeoxEa\downloaded_packages
> library(ggplot2)
x축에는 무게(Weight)를, y축에는 고속도로연비(MPG.highway)를, 원의 크기는 가격(Price)를 설정하였습니다. 그리고 겹치는 부분이 있어서 alpha=0.5 로 해서 반투명하게 하였습니다. scale_size_area 에서 원의 크기의 최대값(max)을 15개 한정을 지었으며, geom_text() 함수를 활용해 vjust=1로 해서 x축 값에 align되고 y값은 MPG.highway값에 살짝 조정을 가해서 label로는 모델명(Model) 변수값을 가져다가 라벨링을 하였습니다.
ggplot2로 두 변수만 가지고 산점도는 유연하게 그릴 수 있는데요, 3개 이상의 변수를 가지고 산점도 행렬을 그리기는 매우 힘이 듭니다. (프로그래밍을 해야 합니다) 따라서 산점도 행렬은 plot() 함수를 써서 한방에 그리는 것이 제일 편하구요,
이번 포스팅에서는 Base Graphics 패키지 내에 pairs() 함수를 이용해서 산점도 행렬에 몇가지 사용자 정의 함수를 추가하여 히스토그램도 집어 넣고 상관계수 숫자도 포함시키는 방법을 소개하겠습니다. 산점도 행렬에 많은 추가 정보를 담을 수 있어서 매우 보기에 좋고 유용합니다. 사용자 정의 함수는 pairs() 도움말(help)을 참조하였습니다.
예제로 사용한 데이터는 뉴욕의 1973년도 공기의 질을 측정한 airquality 데이터셋의 Ozone, Solar.R, Wind, Temp 4개의 변수가 되겠습니다.
> str(airquality)
'data.frame': 153 obs. of 6 variables:
$ Ozone : int 41 36 12 18 NA 28 23 19 8 NA ...
$ Solar.R: int 190 118 149 313 NA NA 299 99 19 194 ...
$ Wind : num 7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
$ Temp : int 67 72 74 62 56 66 65 59 61 69 ...
$ Month : int 5 5 5 5 5 5 5 5 5 5 ...
$ Day : int 1 2 3 4 5 6 7 8 9 10 ...
>
> # 1~4번째 변수만 선택
> airquality_1 <- airquality[,c(1:4)]
> str(airquality_1)
'data.frame': 153 obs. of 4 variables:
$ Ozone : int 41 36 12 18 NA 28 23 19 8 NA ...
$ Solar.R: int 190 118 149 313 NA NA 299 99 19 194 ...
$ Wind : num 7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
$ Temp : int 67 72 74 62 56 66 65 59 61 69 ...
결측값이 있으면 상관계수를 구할 때 NA 값이 나오므로, 결측값 여부 확인하고 결측값이 있는 행은 삭제한 후에 산점도 행렬을 그려보겠습니다.
> # 결측값 개수 확인
> sum(is.na(airquality_1$Ozone)) # 37
[1] 37
> sum(is.na(airquality_1$Solar.R)) # 7
[1] 7
> sum(is.na(airquality_1$Wind)) # 0
[1] 0
> sum(is.na(airquality_1$Temp)) # 0
[1] 0
> > # 결측값 있는 상태에서 상관계수 계산했을 때
> cor(airquality_1)
Ozone Solar.R Wind Temp
Ozone 1 NA NA NA
Solar.R NA 1 NA NA
Wind NA NA 1.0000000 -0.4579879
Temp NA NA -0.4579879 1.0000000
> > # 결측값 있는 행 전체 삭제
> airquality_2 <- na.omit(airquality_1)
> str(airquality_2)
'data.frame': 111 obs. of 4 variables:
$ Ozone : int 41 36 12 18 23 19 8 16 11 14 ...
$ Solar.R: int 190 118 149 313 299 99 19 256 290 274 ...
$ Wind : num 7.4 8 12.6 11.5 8.6 13.8 20.1 9.7 9.2 10.9 ...
$ Temp : int 67 72 74 62 65 59 61 69 66 68 ...
- attr(*, "na.action")=Class 'omit' Named int [1:42] 5 6 10 11 25 26 27 32 33 34 ...
.. ..- attr(*, "names")= chr [1:42] "5" "6" "10" "11" ...
> sum(is.na(airquality_2$Ozone)) # 0
[1] 0
> sum(is.na(airquality_2$Solar.R)) # 0
[1] 0
산점도 행렬의 대각선에 히스토그램을 추가하는 사용자 정의 함수입니다. pairs() 도움말(help)에 나와있는 사용자 정의함수 그대로 가져왔습니다. 아래 사용자 정의 함수를 카피해서 사용하시기 바랍니다.
## put histograms on the diagonal panel.hist <- function(x, ...) { usr <- par("usr"); on.exit(par(usr)) par(usr = c(usr[1:2], 0, 1.5) ) h <- hist(x, plot = FALSE) breaks <- h$breaks; nB <- length(breaks) y <- h$counts; y <- y/max(y) rect(breaks[-nB], 0, breaks[-1], y, col = "cyan", ...) }
# source: help(pairs)
다음으로 산점도 행렬의 위쪽에 상관계수 숫자를 집어넣는 사용자 정의 함수입니다. 이 또한 pairs() 도움말(help)에 나와있는 사용자 정의함수 그대로 가져왔습니다. 아래 사용자 정의 함수를 카피해서 사용하시기 바랍니다.
## put (absolute) correlations on the upper panels, ## with size proportional to the correlations. panel.cor <- function(x, y, digits = 2, prefix = "", cex.cor, ...) { usr <- par("usr"); on.exit(par(usr)) par(usr = c(0, 1, 0, 1)) r <- abs(cor(x, y)) txt <- format(c(r, 0.123456789), digits = digits)[1] txt <- paste0(prefix, txt) if(missing(cex.cor)) cex.cor <- 0.8/strwidth(txt) text(0.5, 0.5, txt, cex = cex.cor * r) }
# source : help(pairs)
다음으로 산점도에 선형 회귀선을 추가하는 사용자 정의 함수입니다. 이는 R Graphics Cookbook (원스턴 챙 지음, 이제원 옮김)을 참조하였습니다. 아래 사용자 정의 함수를 카피해서 사용하시기 바랍니다.
## put linear regression line on the scatter plot panel.lm <- function(x, y, col=par("col"), bg=NA, pch=par("pch"), cex=1, col.smooth="black", ...) { points(x, y, pch=pch, col=col, bg=bg, cex=cex) abline(stats::lm(y~x), col=col.smooth, ...) }
이제 준비가 다 되었습니다. airquality의 4개 변수 간의 산점도 행렬, 상관계수 숫자, 히스토그램을 하나의 도표로 나타내보겠습니다.
> ## 산점도 행렬(scatter-plot matrix), 상관계수(correlation), 히스토그램(histogram)
> pairs(airquality_2,
+ lower.panel = panel.lm, # 아래쪽 산점도에 선형 직선 추가
+ upper.panel = panel.cor, # 위쪽에는 상관계수 숫자 (상관계수에 크기 비례)
+ diag.panel = panel.hist, # 대각선에는 히스토그램
+ pch="*", # 점 모양은 * 로
+ main = "scatter-plot matrix, correlation coef., histogram"
+ )
보너스로, pairs() 함수를 사용해서 범주(그룹)별로 점의 색깔을 달리하는 방법도 소개하겠습니다. 이 역시 pairs() 함수 도움말(help)에 있는 R script 를 가져왔습니다. 도움말(help)이 정말 도움이 많이 됩니다. ^^ 사용한 데이터는 그 유명한 Iris 데이터가 되겠습니다.
> # 범주(그룹)을 색깔로 구분하여 산점도 행렬 그리기
> pairs(iris[1:4], main = "Anderson's Iris Data -- 3 species",
+ pch = 21, bg = c("red", "green3", "blue")[unclass(iris$Species)])
다수의 변수간 상관관계를 파악하려고 할 때, 회귀분석에서 종속변수와 독립변수간 선형관계를 파악하거나 독립변수간 다중공선성을 파악하려고 할 때 사용하는 분석 기법이 상관계수 행렬이며, 시각화 방법이 산점도 행렬과 상관계수 행렬 Plot (correlation matrix plot) 입니다.
이번 포스팅에서는 상관계수 행렬 Plot을 중심으로 해서 corrplot 패키지 사용법을 알아보겠습니다.
예제로 사용한 데이터는 뉴욕의 1973년도 공기의 질을 측정한 airquality 데이터셋의 Ozone, Solar.R, Wind, Temp 4개의 변수가 되겠습니다.
> str(airquality)
'data.frame': 153 obs. of 6 variables:
$ Ozone : int 41 36 12 18 NA 28 23 19 8 NA ...
$ Solar.R: int 190 118 149 313 NA NA 299 99 19 194 ...
$ Wind : num 7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
$ Temp : int 67 72 74 62 56 66 65 59 61 69 ...
$ Month : int 5 5 5 5 5 5 5 5 5 5 ...
$ Day : int 1 2 3 4 5 6 7 8 9 10 ...
>
> # Month, Day는 빼기
> airquality_1 <- airquality[,c(1:4)]
>
> str(airquality_1)
'data.frame': 153 obs. of 4 variables:
$ Ozone : int 41 36 12 18 NA 28 23 19 8 NA ...
$ Solar.R: int 190 118 149 313 NA NA 299 99 19 194 ...
$ Wind : num 7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
$ Temp : int 67 72 74 62 56 66 65 59 61 69 ...
상관계수 분석을 할 때 결측값이 있으면 NA 값이 나오게 되므로 사전에 결측값 처리하는 것이 필요합니다. Ozone과 Solar.R이 결측값이 각각 37개, 7개 있다보니 아래처럼 상관계수가 NA가 나왔습니다.
> # 결측값 확인
> sum(is.na(airquality_1$Ozone)) # 37
[1] 37
> sum(is.na(airquality_1$Solar.R)) # 7
[1] 7
> sum(is.na(airquality_1$Wind)) # 0
[1] 0
> sum(is.na(airquality_1$Temp)) # 0
[1] 0
> # 결측값 있는 상태에서 상관계수 계산했을 때
> cor(airquality_1)
Ozone Solar.R Wind Temp
Ozone 1 NA NA NA
Solar.R NA 1 NA NA
Wind NA NA 1.0000000 -0.4579879
Temp NA NA -0.4579879 1.0000000
na.omit() 함수를 사용하여 결측값이 있는 행 전체를 삭제한 후에 상관계수를 구해보면 아래와 같습니다. corrplot 패키지의 corrplot() 함수는 상관계수 행렬 데이터셋을 가지고 그래프를 그리므로 아래처럼 결측값을 제거한 후의 데이터셋을 가지고 미리 상관계수 행렬을 계산해두어야 합니다.
corrplot 패키지는 별도의 설치 및 호출이 필요한 패키지이므로 아래의 절차를 거칩니다.
> install.packages("corrplot")
Installing package into ‘C:/Users/user/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
trying URL 'http://cran.rstudio.com/bin/windows/contrib/3.2/corrplot_0.73.zip'
Content type 'application/zip' length 2680505 bytes (2.6 MB)
downloaded 2.6 MB
package ‘corrplot’ successfully unpacked and MD5 sums checked
The downloaded binary packages are in
C:\Users\user\AppData\Local\Temp\Rtmpk1gkRL\downloaded_packages
> library(corrplot)
산점도 행렬 그림 (scatter matrix plot)을 복습해보자면 아래와 같습니다.
> # scatter plot matrix
> plot(airquality_2)
correlation plot의 method 에는 method = c("circle", "square", "ellipse", "number", "shade", "color", "pie") 등이 있으며, method별로 하나씩 예를 들어보겠습니다.
> corrplot(airquality_cor, method="circle")
> corrplot(airquality_cor, method="square")
> corrplot(airquality_cor, method="ellipse")
> corrplot(airquality_cor, method="number")
> corrplot(airquality_cor, method="shade")
> corrplot(airquality_cor, method="color")
> corrplot(airquality_cor, method="pie")
마지막으로 mehtod="shade", 상관관계 방향성 제시, 대각선 값 미제시, 상관계수 숫지 검정색으로 해서 추가해서 corrplot을 그려보겠습니다. order 는 FPC(First Principle Component), hclust(hierarchical clustering), AOE(Angular Order of Engenvectors) 등이 있으며, 정렬 기준을 지정해주면 같은 색깔 끼리 뭉쳐서 보일 수 있도록 정렬을 시켜줘서 보기에, 해석하기에 더 좋게 보여줍니다.
> # corrplot
> corrplot(airquality_cor,
+ method="shade", # 색 입힌 사각형
+ addshade="all", # 상관관계 방향선 제시
+ # shade.col=NA, # 상관관계 방향선 미제시
+ tl.col="red", # 라벨 색 지정
+ tl.srt=30, # 위쪽 라벨 회전 각도
+ diag=FALSE, # 대각선 값 미제시
+ addCoef.col="black", # 상관계수 숫자 색
+ order="FPC" # "FPC": First Principle Component
+ # "hclust" : hierarchical clustering
+ # "AOE" : Angular Order of Eigenvectors
+ )
ggplot2로 막대그래프를 그렸는데 데이터가 양수와 음수로 구분이 되는 경우 그래프의 가독성을 높이기 위해서 양수냐, 음수냐에 따라 색상을 다르게 하고 싶을 때가 있습니다.
이번 포스팅에서는 R에 내장되어 있는 airquaility 데이터셋 (뉴욕의 1973년 5월~9월까지의 daily air quality measurements) 에서 5월달 온도(Temp) 만을 가져온 후에, 5월달 daily 온도의 1차 차분 데이터를 만들어서 막대그래프를 그려보도록 하겠습니다.
> str(airquality)
'data.frame': 153 obs. of 6 variables:
$ Ozone : int 41 36 12 18 NA 28 23 19 8 NA ...
$ Solar.R: int 190 118 149 313 NA NA 299 99 19 194 ...
$ Wind : num 7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
$ Temp : int 67 72 74 62 56 66 65 59 61 69 ...
$ Month : int 5 5 5 5 5 5 5 5 5 5 ...
$ Day : int 1 2 3 4 5 6 7 8 9 10 ...
> sum(is.na(airquality$Temp))
[1] 0
>
> # 5월 온도만 선택
> May <- subset(airquality, select = c(Month, Day, Temp), subset = (Month == "5"))
>
온도의 1차 차분은 diff(변수, lag=차수) 함수를 사용합니다. 아래는 1차 차분을 하였으므로 5월1일은 빼고, 5월2일부터 5월31일까지의 날짜만 가져온 후에, 날짜와 온도 1차 차분한 값을 data frame으로 묶었습니다. 그 후에 ifelse() 함수를 사용해서 온도 1차 차분 값이 0 이상이면 "PLUS", 0 미만이면 "MINUS"라는 구분자 변수를 새로 생성하였습니다.
> attach(May_Temp_Diff.df)> May_Temp_Diff.df$plus_minus <- ifelse(May_Temp_Diff >= 0, "PLUS", "MINUS")
> May_Temp_Diff.df
May_Day May_Temp_Diff plus_minus
1 2 5 PLUS
2 3 2 PLUS
3 4 -12 MINUS
4 5 -6 MINUS
5 6 10 PLUS
6 7 -1 MINUS
7 8 -6 MINUS
8 9 2 PLUS
9 10 8 PLUS
10 11 5 PLUS
11 12 -5 MINUS
12 13 -3 MINUS
13 14 2 PLUS
14 15 -10 MINUS
15 16 6 PLUS
16 17 2 PLUS
17 18 -9 MINUS
18 19 11 PLUS
19 20 -6 MINUS
20 21 -3 MINUS
21 22 14 PLUS
22 23 -12 MINUS
23 24 0 PLUS
24 25 -4 MINUS
25 26 1 PLUS
26 27 -1 MINUS
27 28 10 PLUS
28 29 14 PLUS
29 30 -2 MINUS
30 31 -3 MINUS
> detach(May_Temp_Diff.df)
ggplot2 패키지는 사용자가 추가로 설치해야 합니다. intall.packages()함수로 설치하고 library() 함수로 호출해보겠습니다.
> install.packages("ggplot2")
> library(ggplot2)
이제 준비가 다 되었습니다. 1차 차분한 5월달의 온도에 대해서 양수(전날 보다 온도 상승)는 빨간색, 음수(전날보다 온도 하락)는 파란색으로 막대 그래프를 그려보겠습니다. aes(fill=구분자 변수) 함수를 사용하고, 색깔지정은 scale_fill_manual(values=c(색깔1, 색깔2)) 로 지정해주면 됩니다.
> # 양수는 빨간색, 음수는 파란색으로 막대 색 구분
> ggplot(data=May_Temp_Diff.df, aes(x=May_Day, y=May_Temp_Diff, fill=plus_minus)) +
+ geom_bar(stat="identity", position="identity", colour="white", width=0.2) + # width 막대 폭 좁게
+ scale_fill_manual(values=c("blue", "red"), guide=FALSE) + # guide=F 범례 생략
+ ggtitle("1st order differenced Temp of May")
막대 폭이 너무 가늘어서 보기 싫다면, 막대 폭을 좀더 넓히고 싶다면 geom_bar(width=숫자) 함수를 사용하면 됩니다.
> # width 막대 폭 넓게
> ggplot(data=May_Temp_Diff.df, aes(x=May_Day, y=May_Temp_Diff, fill=plus_minus)) +
+ geom_bar(stat="identity", position="identity", colour="white", width=1) + # width 막대 폭 넓게
+ scale_fill_manual(values=c("blue", "red"), guide=FALSE) + # guide=F 범례 생략
+ ggtitle("1st order differenced Temp of May")
다음으로, 위의 그래프에서 보면 ggplot2 가 알아서 x축을 10, 20, 30으로 해서 10일 간격으로 설정해서 그래프를 그렸는데요, 이를 좀더 세분화하고 싶다면 scale_x_continuous(breaks=c(숫자, 숫자...)) 로 지정해주면 됩니다.
DB에 들어있는 데이터를 내렸을 때, 설문조사 데이터를 받았을 때, 원천 거래 데이터를 받았을 때, 혹은 분석 과정 중의 데이터 셋이 분석가가 하고자 하는 통계분석, 데이터마이닝 분석이나 ggplot2 등의 그래프/시각화를 위해 필요한 데이터 구조로 딱 맞아떨어지지 않는 경우가 굉장히 많습니다.
이때 필요한 것이 데이터를 분석 목적, 기법에 맞게 자유자재로 변형하여 재구조화하는 일입니다.
엑셀에서 Pilvot Table 생성하는 것이 제일 이해하기 쉬운 예가 될거 같습니다. 세로로 길게 늘어서 있는 원 데이터를 가로와 세로로 틀을 잡아 주고, 가운데에는 value 값에 대해서 개수를 센다든지, 합계를 낸다든지, 평균을 구한다든지 하는 함수를 적용하는 것을 해보셨을 겁니다.
비유를 해보자면, 레고블록으로 높이 세워진 탑을 모양과 색깔별로 레고 블록을 잘 분해한 다음에, 이를 재료로 해서 가로와 세로로 넓게 펴고 높이는 낮추어서 새로운 탑을 만드는 것이라고 생각해보는 것도 이해하는데 도움이 될거 같습니다.
이때 데이터 재구조화하는 기준 변수를 무엇으로 하느냐, 가로로 길게 늘어뜨릴 변수는 또 무엇으로 하느냐, 가운데에 값을 볼 때 무슨 함수를 사용해서 값을 계산해서 볼 것이냐에 따라서 다양한 경우의 수가 발생하게 됨에 따라 말로 모든 것을 설명하는 것이 무리가 있습니다. 따라서 아래에 reshape 패키지의 melt(), cast() 함수를 몇 가지 경우의 수에 적용해 보면서 원래 값이 이후에 어떻게 바뀌는지를 유심히 보시고, 필요한 상황에 맞는 R script를 참고하시면 되겠습니다.
[ reshape 패키지 내 melt()함수, cast() 함수 사용 데이터 재구조화 예시 ]
예제로 사용할 데이터는 MASS 패키지에 내장되어 있는 Cars93 데이터 프레임의 차종(Type), 제조국(Origin), 도시 연비(MPG.city), 고속도로 연비(MPG.highway) 4개의 변수를 사용해서 몇가지의 경우의 수를 조합해 가면서 데이터를 녹였다가 (melt) 재구조화 (cast) 를 해보겠습니다.
이제 melt(data, id.vars, measure.vars) 함수를 사용해서 기존 데이터셋을 녹여보도록 하겠습니다.
> # melt()
> Cars93_sample_melt <- melt(data = Cars93_sample,
+ id.vars = c("Type", "Origin"),
+ measure.vars = c("MPG.city", "MPG.highway"))
> > Cars93_sample_melt
Type Origin variable value
1 Compact non-USA MPG.city 20
2 Compact USA MPG.city 25
3 Compact USA MPG.city 25
4 Van USA MPG.city 18
5 Van USA MPG.city 15
6 Compact USA MPG.city 23
7 Compact USA MPG.city 22
8 Van USA MPG.city 17
9 Compact USA MPG.city 22
10 Van USA MPG.city 15
11 Compact non-USA MPG.city 24
12 Compact non-USA MPG.city 26
13 Van non-USA MPG.city 18
14 Compact non-USA MPG.city 20
15 Compact non-USA MPG.city 24
16 Van non-USA MPG.city 17
17 Compact USA MPG.city 24
18 Van USA MPG.city 18
19 Compact USA MPG.city 23
20 Compact non-USA MPG.city 20
21 Compact non-USA MPG.city 23
22 Van non-USA MPG.city 18
23 Van non-USA MPG.city 17
24 Compact non-USA MPG.city 21
25 Compact non-USA MPG.city 21
26 Compact non-USA MPG.highway 26
27 Compact USA MPG.highway 36
28 Compact USA MPG.highway 34
29 Van USA MPG.highway 23
30 Van USA MPG.highway 20
31 Compact USA MPG.highway 28
32 Compact USA MPG.highway 27
33 Van USA MPG.highway 21
34 Compact USA MPG.highway 27
35 Van USA MPG.highway 20
36 Compact non-USA MPG.highway 31
37 Compact non-USA MPG.highway 34
38 Van non-USA MPG.highway 24
39 Compact non-USA MPG.highway 29
40 Compact non-USA MPG.highway 30
41 Van non-USA MPG.highway 23
42 Compact USA MPG.highway 31
43 Van USA MPG.highway 23
44 Compact USA MPG.highway 31
45 Compact non-USA MPG.highway 26
46 Compact non-USA MPG.highway 30
47 Van non-USA MPG.highway 22
48 Van non-USA MPG.highway 21
49 Compact non-USA MPG.highway 30
50 Compact non-USA MPG.highway 28
> dim(Cars93_sample_melt)
[1] 50 4
이렇게 melt()함수를 사용해 녹인 데이터를 cast()함수를 사용해서 재구조화 해보겠습니다. 세로와 가로에 무슨 변수를 넣을지가 결정되었다면 아래의 예제를 참고해서 원하는 구조에 맞게 R script를 작성하시면 되겠습니다. (말로 설명하기가 쉽지가 않습니다 ^^;) function 란에는 R에서 사용할 수 있는 통계량 함수를 사용하면 되며, 이번 예제에서는 평균(mean) 함수를 사용하였습니다.
> # cast()
> options(digits=3) # 소숫점 너무 밑에 까지 나오지 않도록 설정
> > # 한개의 id.var 기준(세로) & variable(가로) 조합의 value 값에 mean 함수 적용
> cast(data = Cars93_sample_melt, Type ~ variable, fun = mean)
Type MPG.city MPG.highway
1 Compact 22.7 29.9
2 Van 17.0 21.9
R의 aggregate() 함수로 차종(Type)별 도시 연비(MPG.city)와 고속도로 연비(MPG.highway)의 평균을 구해보겠습니다.
> # aggregate
> R_aggregate_mean <- aggregate(Cars93[,c(7,8)],
+ by = list(Car_Type = Cars93$Type), # list
+ FUN = mean, # function
+ na.rm = TRUE)
> > R_aggregate_mean
Car_Type MPG.city MPG.highway
1 Compact 22.68750 29.87500
2 Large 18.36364 26.72727
3 Midsize 19.54545 26.72727
4 Small 29.85714 35.47619
5 Sporty 21.78571 28.78571
6 Van 17.00000 21.88889
이번에는 install.packages()함수와 library()함수를 사용하여 sqldf Package 를 설치하고 호출한 후에, sqldf 패키지를 사용하여 위와 같이 차종(Type)별 도시 연비(MPG.city)와 고속도로 연비(MPG.highway)의 평균을 구해보겠습니다.
> install.packages("sqldf")
Installing package into ‘C:/Users/user/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
trying URL 'http://cran.rstudio.com/bin/windows/contrib/3.2/sqldf_0.4-10.zip'
Content type 'application/zip' length 71825 bytes (70 KB)
downloaded 70 KB
package ‘sqldf’ successfully unpacked and MD5 sums checked
The downloaded binary packages are in
C:\Users\user\AppData\Local\Temp\Rtmp4i7Dhq\downloaded_packages
> library(sqldf)
필요한 패키지를 로딩중입니다: gsubfn
필요한 패키지를 로딩중입니다: proto
필요한 패키지를 로딩중입니다: RSQLite
필요한 패키지를 로딩중입니다: DBI
Warning messages:
1: 패키지 ‘sqldf’는 R 버전 3.2.2에서 작성되었습니다
2: 패키지 ‘gsubfn’는 R 버전 3.2.2에서 작성되었습니다
3: 패키지 ‘RSQLite’는 R 버전 3.2.2에서 작성되었습니다
4: 패키지 ‘DBI’는 R 버전 3.2.2에서 작성되었습니다
> R_sqldf_1 <- sqldf('
+ select "Type" as "Car_Type",
+ avg("MPG.city") as "mean_MPG.city",
+ avg("MPG.highway") as "mean_MPG.highway"
+ from Cars93
+ group by Type
+ order by Type
+ ')
> R_sqldf_1
Car_Type mean_MPG.city mean_MPG.highway
1 Compact 22.68750 29.87500
2 Large 18.36364 26.72727
3 Midsize 19.54545 26.72727
4 Small 29.85714 35.47619
5 Sporty 21.78571 28.78571
6 Van 17.00000 21.88889
R의 aggregate()함수로 만든 평균과 sqldf로 만든 평균 데이터 셋을 차종(Type) 을 key로 항 merge 한 후에 두 값들이 서로 같은지 한번 점검해보겠습니다.
얼핏 보면 R의 aggregate() 함수와 sqldf 가 서로 큰 차이가 없거나 혹은 aggregate()함수가 더 편하다고 느낄 수도 있겠습니다. 그런데, 아래의 경우처럼 다수의 함수들(count, sum, avg, variance, stdev, min, max 등)을 그룹 변수에 대해서 구분해서 집계를 할 경우에는, 그리고 SQL에 익숙한 사용자라면 sqldf 패키지를 사용하는게 편할 수 있을 것입니다
> # SQL의 aggregation 함수 사용하기
> R_sqldf_2 <- sqldf('
+ select "Type" as "Car_Type",
+ count("MPG.city") as "count_MPG.city",
+ sum("MPG.city") as "sum_MPG.city",
+ + avg("MPG.city") as "mean_MPG.city",
+ variance("MPG.city") as "variance_MPG.city",
+ stdev("MPG.city") as "stdev_MPG.city",
+ + min("MPG.city") as "min_MPG.city",
+ + max("MPG.city") as "max_MPG.city"
+ + from Cars93
+ group by Type
+ order by Type desc
+ ')
> > # count : 행의 개수
> # sum : 합계
> # avg : 평균
> # var : 분산
> # stddev : 표준편차
> # min : 최소값
> # max : 최대값
> # order by xx desc : 내림차순 정렬
> trade_stat <- read.csv("C:/Users/user/Documents/R/trade_stat_07_14.csv", # 경로 설정
+ header = TRUE)
> > > trade_stat <- transform(trade_stat, Year = substr(Time, 1, 4))
> > sapply(trade_stat, class)
Time export_amt import_amt Year
"numeric" "integer" "integer" "factor"
> > library(sqldf)
> # 한국 수/출입 무역금액, 단위: 1B$
> trade_stat_Year <- sqldf('select Year,
+ sum(export_amt)/100000 as exp_amt_Year,
+ sum(import_amt)/100000 as imp_amt_Year
+ from trade_stat
+ group by Year
+ order by Year
+ ')
> trade_stat_Year
Year exp_amt_Year imp_amt_Year
1 2007 3714 3568
2 2008 4220 4352
3 2009 3635 3230
4 2010 4663 4252
5 2011 5552 5244
6 2012 5478 5195
7 2013 5596 5155
8 2014 5726 5255
여기까지 했는데도 누적 영역 그래프를 그리기에 딱 맞는 데이터 형태가 아니라서 reshape 패키지의 melt() 함수를 사용하여 데이터를 현재의 가로로 늘어져있는 exp_amt_Year, imp_amt_Year 변수를 -> 세로로 세워서 데이터 구조를 변경해보겠습니다.
그 다음에 variable -> trade_cd (수입, 수출 구분 코드), value -> amount_B (무역금액, 단위 : 1B$) 로 변수명을 변경하였습니다.
이제 드디어 누적 영역 그래프를 그릴 데이터 셋 준비가 다 되었군요. ggplot2의 geom_area() 함수를 사용하여 우선 값 기준으로 그리고, 다음으로 비율 기준으로도 그려보겠습니다.
geom_area(colour=NA)로 하고 geom_line(position="stack")으로 해서 양 옆에 선은 트여주고, 영역 간 경계선은 그려주었습니다.
> # 누적 영역 그래프 그리기
> ggplot(trade_stat_Year_melt, aes(x=Year, y=amount_B, fill=trade_cd, group=trade_cd)) +
+ geom_area(colour=NA, alpha=0.5) + # alpha 투명도
+ scale_fill_brewer(palette="Blues") +
+ geom_line(position="stack", size=0.3) +
+ ggtitle("Stacked Area Plot of Trade (Import, Export) from 2007 to 2014")
ymax not defined: adjusting position using y instead
aes(arder=desc()) 를 사용하여 위의 영역 구분 그룹의 순서를 바꿀 수도 있습니다. 위의 예제에서는 exp_amt_Year (수출액)이 아래에 위치했습니다만, 아래 예제에서는 exp_amt_Year(수출액)이 위로 위치가 바뀌었음을 알 수 있습니다.
> # 누적 영역 순서 바꾸기
> library(plyr) # desc() 함수 사용 위해 필요
> ggplot(trade_stat_Year_melt, aes(x=Year, y=amount_B, fill=trade_cd, group=trade_cd,
+ order=desc(trade_cd))) + # 누적 영역 순서 내림차순 정렬
+ geom_area(colour=NA, alpha=0.5) + # alpha 투명도
+ scale_fill_brewer(palette="Blues") +
+ geom_line(position="stack", size=0.3) +
+ ggtitle("Stacked Area Plot of Trade (Import, Export) from 2007 to 2014")
ymax not defined: adjusting position using y instead
이번에는 비율 기준으로 해서 누적 영역 그래프를 그려보겠습니다. 이를 위해서는 데이터셋에서 Year 별로 비율을 계산해주어야 합니다. 데이터 프레임에서 사칙연산을 써가면서 transform() 함수로 step-by-step 해나갈 수도 있는데요, plyr패키지의 ddply() 함수를 사용하면 놀랍도록 간편하게 원하는 비율 값을 구할 수 있습니다.
위의 trade_prop 변수를 활용해서 비율 누적 영역 그래프(Propostion stacked area plot)을 그려보도록 하겠습니다. 값을 기준으로 했을 때와 script는 동일하며, y값 자리에 trade_prop (수출입 무역 비율) 변수로 바꾸어주기만 하면 됩니다.
그래프 뒤에 단위 격자가 보이도록 geom_area(alpha=0.5) 로 해서 약간 투명하게 처리했습니다.
> # 비율 누적 영역 그래프 그리기
> library(plyr) # desc() 함수 사용 위해 필요
> ggplot(trade_stat_Year_melt_prop, aes(x=Year, y=trade_prop, fill=trade_cd, group=trade_cd,
+ order=desc(trade_cd))) + # 누적 영역 순서 내림차순 정렬
+ geom_area(colour=NA, alpha=0.5) + # alpha 투명도
+ scale_fill_brewer(palette="Blues") +
+ geom_line(position="stack", size=0.3) +
+ ggtitle("Stacked Area Plot of Trade Proportion (Import, Export) from 2007 to 2014")
ymax not defined: adjusting position using y instead
예전 포스팅 중에서 일변량 연속형 변수에 대해 ggplot2로 막대 그래프 그리는 법을 소개했었는데요, 막대 그래프의 훌륭한 대안으로서 점 그래프(Dot Plot)이 있습니다.
Cleveland and McGill (1984) 이 “Graphical Methods for Data Presentation: Full Scale Breaks, Dot Charts, and Multibased Logging.” 이라는 논문에서 막대 그래프 대비 점 그래프가 데이터 해석, 가독성에서 가지는 우수성을 소개하면서 Cleveland Dot Plot 이라고도 많이 불리는 그래프입니다.
분석에 활용할 데이터는 MASS 패키지 내 Cars93 데이터 프레임에서, 차종(Type), 모델(Model), Max.Price, Min.Price의 4개 변수를 사용하겠으며, 관측치 개수가 많아서 화면 하나에 전부 뿌리기에는 너무 많으므로 차종(Type)의 Level 중에서 "Large", "Midsize", "Small" 만 선별하고 "Compact", "Sproty", "Van"은 제외하도록 하겠습니다.
geom_point() 함수를 사용하여 클리브랜드 점 그래프(Cleveland dot plot)을 그려보겠습니다.
aes(y = reorder(Model, Max.Price)) 를 사용해서 y축에 사용할 Model 을 Max.Price 를 기준으로 정렬을 하였기 때문에 아래처럼 Max.Price가 높은 것부터 낮은 것으로 정렬이 된 채로 점 그래프가 제시되었습니다.
aes(shape = Type) 을 적용하여서 Type(Large, Midsize, Small) 별로 모양(shape)을 달리해서 제시하였습니다.
> # Cleveland dot plot of Max Price of Models with different shape by Type > library(ggplot2) > > ggplot(Cars93_P, aes(x = Max.Price, y = reorder(Model, Max.Price), shape = Type)) + + geom_point(size = 3, colour = "blue") + + theme_bw() + # background 색 없애기 + theme(panel.grid.major.x = element_blank(), # x축 선 없애기 + panel.grid.minor.x = element_blank(), + panel.grid.major.y = element_line(colour="grey90", linetype="dashed")) + + ggtitle("Cleveland dot plot of Max.Price of Models with different shape by Type")
다음으로, Type(Large, Midsize, Small) 별로 facet_grid(Type ~ ., scales="free_y", space="free_y") 을 적용하여 면을 분할을 한 클리브랜드 점 그래프(Cleveland dot plot)을 그려보겠습니다.
면 분할해서 그리려면 위의 예처럼 ggplot2 내 aes(reorder)로는 안되구요, 먼저 Type과 Max.Price 순서대로 데이터셋을 따로 정렬해서 요인(factor)으로 levels 를 지정해서 변환해주어야 합니다. 그래프는 상대적으로 쉬운데, 데이터셋 정렬/요인변환이 어려울 수 있겠습니다.
> # Type, Max.Price 순서대로 정렬 > Model_Order <- Cars93_P$Model[order(Cars93_P$Type, # Large, Midsize, Small 순서 + -Cars93_P$Max.Price, # 높은것에서 낮은 순서 + decreasing=TRUE)] > > # Model_Order를 요인(factor)으로 변환 > Cars93_P$Model <- factor(Cars93_P$Model, levels=Model_Order) > > # Type별로 면 분할, Max.Price 순서대로 정렬된 Cleveland dot plot > ggplot(Cars93_P, aes(x = Max.Price, y = Model)) + + geom_point(size = 3, aes(colour = Type)) + + theme_bw() + + theme(panel.grid.major.y = element_blank(), + panel.grid.minor.y = element_blank()) + + facet_grid(Type ~ ., scales="free_y", space="free_y") + + ggtitle("Cleveland dot plot of Max.Price of Models with Facets of Type")
다음으로, 차종(Type)별로 면 분할은 유지하면서 위의 Max.Price 에 더해서 Min.Price 를 추가하고 모양(shape)을 다르게 제시해보겠습니다.
이것도 데이터셋을 따로 미리 손을 봐줘야 합니다. reshape 패키지의 melt() 함수를 사용해서 Max.Price, Min.Price 두 값을 Price_cd (Max.Price, Min.Price)와 Price (value) 의 두개 변수로 녹여서 데이터 구조를 ggplot2의 geom_point()에 사용할 수 있도록 변경하여야 합니다. (reshape 패키지의 melt(), cast() 함수는 여기서 자세히 설명하기가 힘든데요, 따로 알아보시면 좋겠습니다)
> #-------- > # Min.Price 추가 > # melt > library(reshape) > Cars93_P_melt <- melt(Cars93_P, idvars = c("Type", "Model")) Using Model, Type as id variables > head(Cars93_P_melt) Model Type variable value 1 Integra Small Min.Price 12.9 2 Legend Midsize Min.Price 29.2 3 100 Midsize Min.Price 30.8 4 535i Midsize Min.Price 23.7 5 Century Midsize Min.Price 14.2 6 LeSabre Large Min.Price 19.9 > > # 변수명 변경 > Cars93_P_melt <- rename(Cars93_P_melt, c(variable = "Price_cd", value = "Price")) > head(Cars93_P_melt) Model Type Price_cd Price 1 Integra Small Min.Price 12.9 2 Legend Midsize Min.Price 29.2 3 100 Midsize Min.Price 30.8 4 535i Midsize Min.Price 23.7 5 Century Midsize Min.Price 14.2 6 LeSabre Large Min.Price 19.9 > > # Type별로 면 분할, Max.Price 순서대로 정렬, Min.Price추가된 Cleveland dot plot > ggplot(Cars93_P_melt, aes(x = Price, y = Model)) + + geom_segment(aes(yend=Model, xend=0)) + # 점까지만 선 그리기 + geom_point(size=3, aes(shape = Price_cd)) + # Price_cd로 모양 구분 + theme_bw() + # backgroud 색 없애기 + theme(panel.grid.major.y = element_blank(), # y축 없애기 + panel.grid.minor.y = element_blank()) + # y축 없애기 + facet_grid(Type ~ ., scales="free_y", space="free_y") + # Type별로 면 분할 + ggtitle("Cleveland dot plot of Max, Min Price of Models with Facets of Type")
위의 세번째 그래프처럼 Max.Price와 Min.Price를 같은 그래프에 그리는데, 만약 이것을 막대 그래프로 그린다고 상상해 보세요. 막대그래프로 그린다면 지저분하고 해석, 가독성이 클리브랜드 점 그래프 대비 떨어질겁니다.
Python 의 Plotly 모듈을 사용해서 클리브랜드 점 그래프 (Cleveland Dot Plot in Python using Plotly) 그리는 방법은 https://rfriend.tistory.com/802 를 참고하세요.
[Reference]
Cleveland, William S. 1984. “Graphical Methods for Data Presentation: Full Scale Breaks, Dot Charts, and Multibased Logging.” The American Statistician, 38:270-280.
Dot Plots: A Useful Alternative to Bar Charts, Naomi B. Robbins, Ph.D. March 7, 2006
이제 이차원 밀도 그래프 (2D Density Plot)을 그려보겠습니다. 그리고 5월과 7월달의 Month를 색깔로 구분하여 보겠습니다.
이때 조심해야 할 것이 있습니다. aes() 에 shape이나 colour 에는 범주형변수(factor)가 들어가야 합니다. 만약 연속형 변수가 들어가면 "Error: A continuous variable can not be mapped to shape" 라는 에러 메시지가 뜹니다.
> # 2차원 밀도 그래프 : 모양과 색깔로 구분
> # 연속형 변수라서 error
> ggplot(data=airquality_May_July, aes(x=Wind, y=Temp, shape=Month)) +
+ geom_point() +
+ stat_density2d() +
+ ggtitle("2D desity plot of Wind and Tmep, at May and July")
Error: A continuous variable can not be mapped to shape
Month를 Month.ch라는 새로운 문자형 변수로 변환해, 이를 사용해서 이차원 밀도 그래프를 Month별로 모양과 색깔을 구분해서 그려보겠습니다.
stat_density2d() 함수로 커널 밀도 추정치를 계산해서 2차원 밀도 그래프를 그리면,
> # 2차원 밀도 그래프 : Month를 모양으로 구분
> ggplot(data=airquality_May_July, aes(x=Wind, y=Temp, shape=Month.ch)) +
+ geom_point(size=4) +
+ stat_density2d() +
+ ggtitle("2D desity plot of Wind and Tmep, May/July by Shape")
>
> # 2차원 밀도 그래프 : Month를 색깔로 구분
> ggplot(data=airquality_May_July, aes(x=Wind, y=Temp, colour=Month.ch)) +
+ geom_point(size=4) +
+ stat_density2d() +
+ ggtitle("2D desity plot of Wind and Tmep, at May/July by Colour")
이번에는 (범례가 있기는 합니다만) 사용자의 가독성을 조금 더 높여주기 위해 2차원 밀도 그래프의 5월, 7월 두 집단의 중앙 부위에 년/월을 annotate()의 "text"로 라벨을 추가해 보겠습니다.
> # 2차원 밀도 그래프 : Month를 색깔로 구분, 년/월 라벨 추가
> ggplot(data=airquality_May_July, aes(x=Wind, y=Temp, colour=Month.ch)) +
+ geom_point(size=4) +
+ stat_density2d() +
+ ggtitle("2D desity plot of Wind and Tmep, at1973. May/July by Colour") +
+ annotate("text", x=11, y=65, label="1973.May", alpha=0.5) +
+ annotate("text", x=9, y= 83, label="1973.July", alpha=0.5)