본 문서를 위해 이민호 님의 github 페이지를 참고하였습니다.
Hadley Wickham 교수에 의해 2005년부터 개발되고 있으며 ‘Grammar of Graphics’의 개념을 적용하여 기본 R 그래픽스에서 제공하는 대부분의 작업을 효과적으로 수행한다.
자료를 그래프로 그리는 과정은 다시 말하면 자료의 속성을 그래프의 시각적인 속성에 대입(대응)시키는 과정으로 예를 들어, 시계열 자료인 종합주가지수를 그래프로 그린다고 하면, 즉, 시간에 따른 시계열 변화를 선그래프로 표현할 때 시간이라고 하는 변수는 그래프에서 X축에 대입된다면, 수치(연속형)형 자료인 종합주가지수는 Y축에 대응됩니다. 한국과 일본의 주가지수를 비교해 볼 수도 있을 텐데요. 이 경우에 국가라는 변수는 서로 다른 직선으로 표현되어야 하는데, 색상을 다르게 주거나 선의 형태를 실선/점선으로 구분하여 국가를 구분해서 보여줄 수 있습니다.
R이 내장하고 있는 기본 그래픽 함수를 통해 쉽게 그래프를 그릴 수 있으나 무엇이 문제인지 알아보기 위해 먼저 R의 내장 자료인 BOD
(시간에 따른 용존 산소량이 저장된 데이터) 를 이용하여 시간에 따른 용존 산소량을 나타내 봅시다.
BOD
## Time demand
## 1 1 8.3
## 2 2 10.3
## 3 3 19.0
## 4 4 16.0
## 5 5 15.6
## 6 7 19.8
plot(BOD)
기본 내장 함수인 plot()
은 기본적으로 점을 찍는 함수입니다. 만일 이 점들을 연결하고자 하면 옵션으로 type='l'
을 주어 다음과 같이 입력합니다.
plot(BOD, type='l')
선으로 연결한 선그래프를 위해 점을 찍는 plot()
함수에 별도의 옵션으로 선그래프를 완성하였습니다. 이제 시간에 따른 용존 산소량의 변화를 막대그래프로 그려보겠습니다.
barplot(BOD)
Error in barplot.default(BOD) :
'height'는 반드시 벡터 또는 행렬이어야 합니다
BOD자료에 대해 barplot을 바로 적용하면 height는 반드시 벡터 또는 행렬이라고 출력됩니다. 즉, 데이터 프레임을 바로 넣어서는 안되고 실행을 위해 데이터 프레임을 as.matrix()
를 이용하여 행렬로 변환한 다음 출력하겠습니다.
barplot(as.matrix(BOD))
그래프를 보시면 아시겠지만 각 열을 하나의 기둥으로 그리고 각 값을 그려야 할 높이로 하여 막대들을 그리고 있습니다. 각 기둥이 시간이 되길 원했는데 그렇게 나오지 않았습니다. 이를 위해 행과 열을 바꾸는 즉, 전치하여 그리도록 하겠습니다. 이를 위해 t()
함수를 사용하겠습니다.
barplot(t(as.matrix(BOD)))
그림을 보면 각 시점과 demand 값이 한 기둥 안에 들어가 있습니다. 여기서 시점을 제외한 demand 값을 높이로 해서 막대를 그리기 위해 BOD에서 demand 만 가져와 그려보겠습니다.
barplot(BOD$demand)
이상으로 부터 우리가 원하는 그림을 그리기 위해 plot과 barplot을 사용해 봤습니다. 그런데 무언가 조금 복잡합니다. 각 함수들이 완전히 다른 함수에, 받아들이는 데이터의 형태도 다르고 내부의 옵션들 또한 내용이 많이 다릅니다. 각각의 함수는 쉽게 사용할 수 있다고 하지만, 새로운 그래프를 그리려면 처음부터 일일이 알아야 합니다.
ggplot2는 앞서 말씀드린 ‘Grammar of Graphics’ 을 적용하여 다음과 같은 다섯가지 사항으로 그림을 그립니다.
그래프를 그리려는 데이터로 구조는 데이터 프레임이고 데이터의 기록 방식은 long-farmat에 기반한 tidy data여야 합니다.
데이터의 요소와 그래프의 요소를 대응시키는 과정으로 그리려고 하는 그래프가 필수적으로 요구하는 대응요소만 만족시키면 됩니다. 하나의 변수가 여러 가지 시각적 요소에 대응될 수도 있습니다.
어떤 형태의 그래프를 그릴지 지정해야 합니다. ggplot2에서는 이것을 Geometric Object, 줄여서 geom이라고 합니다.
그래프의 형태를 지정했으면 그래프에서 각 도형이 어떤 식으로 배치될 지를 결정할 수 있습니다. 이 옵션이 바로 position입니다. 막대그래프나 선 그래프라면 누적 그래프를 그리거나 할 때 position 옵션을 조정해서 형태를 바꿀 수 있습니다.
마지막으로 값이 어떻게 그래프에 반영되는지 결정하는 옵션이 있습니다. Statistical Transformation, 줄여서 stat 옵션입니다. 히스토그램과 같이 구간내에 존재하는 값의 개수를 세거나 밀도를 계산하는 등, 주어진 값을 변형시켜서 그래프에 반영시킬 때 사용합니다.
앞서 그린 BOD를 위의 다섯가지 요소에 맞춰 ggplot2를 통해 그려보겠습니다.
library(ggplot2)
ggplot(data = BOD, aes(x=Time, y=demand)) +
geom_bar(stat='identity')
BOD
형태를 살펴보시면 데이터와 매핑이 결합된 ggplot(data = BOD, aes(x=Time, y=demand))
으로 ggplot객체를 만들고 이 위에 Geometric Object를 추가하는 형태(+ geom_bar(stat='identity')
)로 되어 있으며 이런 방식으로 다른 요소들을 필요에 따라 추가할 수 있습니다. 위의 코드는 다음과 같은 방식으로 실행할 수 있습니다.
library(ggplot2)
gg2o <- ggplot(data = BOD, aes(x=Time, y=demand))
gg2o + geom_bar(stat='identity')
그럼 이제 ggplot2의 작동방식을 알아보겠습니다. (Hadley Wickham의 One hour ggplot2 workshop given at Vanderbilt, 2007.) 다음과 같은 데이터가 있다고 해보겠습니다.
length | width | depth | trt |
---|---|---|---|
2 | 3 | 4 | a |
1 | 2 | 1 | a |
4 | 5 | 15 | b |
9 | 10 | 80 | b |
위의 자료에서 길이와 폭을 trt별로 산점도(Scatter Plot)을 그립니다.
먼저 데이터를 준비합니다.
aes()
이용ggo1 <- ggplot(data=td, aes(x=length, y=width))
이 단계는 다음과 같이 자료가 Mapping된 것으로 보시면 좋습니다. (추후 색상을 trt 구별값으로 사용할 것이므로 여기서 같이 살펴보겠습니다.)
x | y | colour |
---|---|---|
2 | 3 | a |
1 | 2 | a |
4 | 5 | b |
9 | 10 | b |
매핑을 한 후에 진행되는 작업은 매핑된 데이터를 갖고 컴퓨터가 실제 사용할 데이터로 변환시키는 과정입니다. ggplot2의 스케일링(scaling) 작업입니다. x, y축 데이터는 이미지를 출력하는 대상에 맞게 변수 변환이 이뤄지고 ggplot2에서 사용하는 시스템은 grid이기 때문에 [0,1] 사이의 값으로 스케일링 됩니다. 그리고 colour 값은 자동으로 사람의 눈으로 구분하기 쉬운 색상으로 매핑됩니다. 사람이 구분하기 쉬운 색상을 사용하기 위한 작업도 이뤄지는데 컬러휠(color wheel)을 구분하고 싶은 레벨 개수로 색상과 명암 기준으로 일정하게 분할해 색상을 매칭시키며 이는 ggplot2가 자동으로 지정 해주기 때문에 다변량에 대한 그래프를 그릴 때 매우 편합니다.(물론 사용자가 이 값들을 직접 정해줄 수 있습니다.) 이 결과는 내부적으로 다음과 같은 값을 갖게 합니다. (x와 y를 0부터 1사이의 값을 갖게 하고 사용자의 디스플레이 환경에 맞춰 가로축의 값과 세로축의 값을 곱하여 실제 화면에 찍힐 좌표를 사용합니다. 즉 가로축이 예를 들어 800px이라면 0.125는 800*0.125=100이 되는 위치를 사용합니다.)
x | y | colour |
---|---|---|
0.125 | 0.125 | #FF6C91 |
0.000 | 0.000 | #FF6C91 |
0.375 | 0.375 | #00C1A9 |
1.000 | 1.000 | #00C1A9 |
이상으로부터 다음과 같은 ggplot 명령을 수행합니다.
ggo1 <- ggplot(data=td, aes(x=length, y=width))
ggo1 + geom_point(aes(colour=trt))
iris 데이터를 이용하여 다시 한번 정리해 보겠습니다.
Data : 그래프를 그리려는 데이터는 iris
Mapping : 데이터의 요소와 그래프의 요소를 대응시키는 과정
Sepal.Length - X축 Sepal.Width - Y축 Species - 점의 색상 이 대응됩니다. 여기서 대응은 무조건 1:1로 대응될 필요는 없고, 그리려고 하는 그래프가 필수적으로 요구하는 대응요소만 만족시키면 됩니다. 하나의 변수가 여러 가지 시각적 요소(ggplot2에서는 이것을 에스테틱 Aesthetic 이라고 합니다)에 대응될 수도 있습니다.
Geometric Object : ggplot에서 산점도는 geom_point 함수를 사용합니다.
Position : 여기서는 특별한 배치가 필요없기 때문에 그대로(‘identity’) 배치합니다(지정 없음).
Statistical Transformation : 특별한 변환 없이 값을 그대로(‘identity’) 사용합니다(지정 없음).
ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width)) +
geom_point(aes(colour=Species))
ggplot2 그래프는 > Layer + Scale + Coordinate System + (Facet) + (Guide) 으로 구성되며 이를 구성하는 다섯가지에 대해 알아보겠습니다.
위에서 살펴본 다섯 가지 구성 요소들은 하나의 레이어를 구성합니다. 그리고 하나 이상의 레이어만 구성되면 ggplot2는 우리에게 그래프를 출력합니다. 앞서 그린 그래프를 layer() 함수를 이용해 그리면 다음과 같습니다. (좀 더 명시적으로 다섯개의 요소를 확인할 수 있습니다. geom_point는 geom = 'point', stat = 'identity', position = 'identity'
인 단축된 함수(shortcut function입니다.)
ggplot() +
layer(data = iris,
mapping = aes(x = Sepal.Length, y = Sepal.Width, colour = Species),
geom = 'point',
stat = 'identity',
position = 'identity')
또한 레이어를 ggplot객체에 shortcut function들을 추가하여 다중으로 배치할 수 있습니다.
iris_plot = ggplot(data = iris, aes(x=Sepal.Length, y=Sepal.Width))
iris_plot +
geom_point() +
geom_smooth(method='lm')
바로 위의 코드에서 geom\_smooth(method='lm')
으로 기본값으로 그리면 어떤 에러가 발생하여 선형 모형으로 smooth하기 위한 parameter를 적용해 봤습니다(method='lm'
). 만일 parameter를 추가하면 geom의 형태를 변경하거나 stat이 적용되는 방법을 바꿀 수 있습니다. 에스테틱을 직접 적용하거나 각 함수마다 주어지는 옵션을 변경하게 됩니다. 에스테틱은 aes함수 안에서 쓰이면 데이터의 값에 매핑되지만, 밖에서 쓰일 경우에는 해당 요소를 전부 변경합니다. 예를 들어 geom_point(aes(size = var))은 var 데이터의 값에 따라 점의 크기가 변하게 됩니다. 하지만 geom_point(size = 3) 의 경우에는 모든 점의 크기가 3으로 고정됩니다.
iris_plot +
geom_point(size = 3, colour = 'red') +
geom_smooth(method='lm')
에스테틱 중에서 좌표를 나타내는 x와 y를 제외하면 * colour(또는 color)는 점이나 선 색상이나 면의 테두리 색을 결정합니다 * fill은 면의 색상을 결정합니다 * alpha는 색의 투명도를 나타냅니다
에스테틱은 아니지만 각 함수의 세부적인 내용을 조정하는 parameter들도 존재합니다. 이런 용도로 앞서 geom_smooth에 method = ’lm’으로 지정하면 추세선을 직선으로 그려보았고 다음과 같은 parameter들이 존재합니다. * linetype 옵션은 선의 모양을 변경할 수 있게 합니다. * se 옵션을 끄면 standard error의 범위를 표현하지 않습니다.
iris_plot +
geom_point(size = 3, colour = 'red') +
geom_smooth(method='lm', linetype=2, se = FALSE)
각각의 함수들에는 적용할 수 있는 다양한 parameter들이 있습니다. 각 parameter들에 대한 자세한 설명은 링크를 참조하세요.
position은 geom이 보여지는 형태를 바꿉니다. 막대그래프라면 막대가 누적으로 쌓일지, 옆에 나란히 놓일지 결정할 수 있습니다. 산점도라면 여러 점들이 겹쳐있을때 각 좌표에 일정 난수를 더해서 흩어져보이게 만들 수도 있습니다.
position = ‘jitter’를 적용하면 각 x,y값에 난수를 더해서 점이 겹치지 않도록 합니다. geom_point(position=’jitter’)는 geom_jitter() 로 줄여서 쓸 수 있습니다
ggplot(data = mpg, aes(displ, hwy)) +
geom_point(position = "jitter") +
geom_smooth()
## geom_smooth: method="auto" and size of largest group is <1000, so using loess. Use 'method = x' to change the smoothing method.
막대그래프에서 position = ’stack’은 각 막대를 쌓아서 누적된 형태로 보여줍니다.
ggplot(mtcars,
aes(factor(cyl), fill = factor(vs))) +
geom_bar(position="stack")
position = ’fill’은 비율을 나타내는 누적 막대를 보여줍니다.
ggplot(mtcars,
aes(factor(cyl), fill = factor(vs))) +
geom_bar(position="fill")
position = ’dodge’는 각 막대를 옆으로 나열합니다
ggplot(mtcars,
aes(factor(cyl), fill = factor(vs))) +
geom_bar(position="dodge")
postion을 적용하지 않을 때에는 position = ‘identity’ 옵션이 사용됩니다. 기본값이 postion = ’identity’인 함수를 사용하고 있다면 position옵션을 생략할 수 있습니다.
그리고, position을 더 상세하게 지정하고 싶다면 position_옵션이름 으로 지정된 함수에 parameter를 적용하면 됩니다. position_dodge의 경우에는 어느 정도 간격으로 떨어뜨려 놓을지 정할 수 있고, position_jitter에서는 난수를 어느 정도로 적용할 지 결정할 수 있습니다.
dodge = position_dodge(0.3)
ggplot(sleep, aes(x=ID, y=extra, group=group)) +
geom_line(position = dodge) +
geom_point(position = dodge, size=4, fill='white', shape=21)
## ymax not defined: adjusting position using y instead
## ymax not defined: adjusting position using y instead
Scale은 데이터의 값이 그래프의 에스테틱에 대입되는 과정을 조절하고 guide(축과 범례 등)가 어떻게 표시되는지 결정합니다. 보통은 ggplot2가 알아서 scale을 추가하지만 원하는 사항에 대해서는 명시해 주면 그래프에 반영시킬 수 있습니다. scale을 조절하면 log나 sqrt 등의 변수 변환을 ggplot2 상에서 할 수 있고, fill이나 colour 등의 에스테틱에서 색상을 사용자가 지정할 수 있게 됩니다.
Scale은 데이터의 값이 그래프의 에스테틱에 대입되는 과정을 조절하고 guide(축과 범례 등)가 어떻게 표시되는지 결정한다고 저번 글에서 말씀드렸습니다. 스케일과 관련된 함수는 기본적으로는 scale\_에스테틱\_적용방법
으로 이름이 정해집니다. 스케일을 조절하는 함수는 에스테틱의 종류만큼이나 다양하게 존재하지만, 어느 정도 규칙적인 함수명 덕분에 비교적 쉽게 검색해서 적용이 가능합니다.
우선 본격적으로 관련 함수들에 대해서 알아보기 전에 xlim, ylim 함수를 살펴보겠습니다.
ggplot(data = mpg, aes(displ, hwy)) +
geom_point() +
geom_smooth() +
xlim(4,6)
## geom_smooth: method="auto" and size of largest group is <1000, so using loess. Use 'method = x' to change the smoothing method.
## Warning: Removed 153 rows containing missing values (stat_smooth).
## Warning: Removed 153 rows containing missing values (geom_point).
다음으로는 색상과 관련된 scale에 대해서 살펴보려고 합니다. 아래 예제에서는 fill을 기준으로 설명하겠지만, fill을 colour로 바꾸기만 하면 많은 경우에 동일한 방식으로 적용할 수 있습니다.
dia_bar <- ggplot(diamonds, aes(x=cut, fill = cut)) + geom_bar()
dia_bar +
scale_fill_brewer()
xxx_brewer로 끝나는 색상관련 scale 함수들은 RColorBrewer패키지를 이용해서 색상을 지정합니다.
RColorBrewer::display.brewer.all()
display.brewer.all() 함수를 통해 사용가능한 색 조합의 이름과 색상들을 살펴볼 수 있습니다. 값의 높고 낮음을 보여줄 수 있는 Sequential, 서로 다른 범주나 그룹들을 구분지어 보여줄 수 있는 Qualitative, 어느 중심 값을 기준으로 얼마나 떨어져 있는지를 보여주는 Diverging 의 순서로 정리가 되어 있습니다. 현재 표현하고자 하는 데이터의 형태를 잘 살펴보고, 적절한 색조합을 적용시키면 됩니다.
원하는 색조합을 찾았다면, palette 옵션에 조합의 이름을 넣어서 색상을 적용할 수 있습니다.
dia_bar +
scale_fill_brewer(palette = 'Paired')
dia_bar +
scale_fill_brewer(palette = 'Accent')
다음은 축에 대한 scale함수를 사용해보겠습니다. 연속적인 수치 자료가 y축에 있을 때 스케일은 scale_y_continuous함수를 적용할 수 있습니다. x축이라면 y를 x로 바꾸면 되고, 이산형자료라면 continuous가 discrete로 바뀝니다.
위에서 xlim 함수를 살펴보았는데요. scale_x_continuous 안에 limits 옵션을 지정하면 동일한 효과를 얻을 수 있습니다. xlim이 적용되었던 예제를 scale_x_continuous로 표현하면 다음과 같습니다.
ggplot(data = mpg, aes(displ, hwy)) +
geom_point() +
geom_smooth() +
scale_x_continuous(limits=c(4,6))
## geom_smooth: method="auto" and size of largest group is <1000, so using loess. Use 'method = x' to change the smoothing method.
## Warning: Removed 153 rows containing missing values (stat_smooth).
## Warning: Removed 153 rows containing missing values (geom_point).
산점도 같은 경우에는 그래프로 표현하는 범위를 강제하더라도 문제가 없겠지만, 막대 그래프 같은 경우에는 위와 같은 방식을 사용하면 막대 자체가 표현이 되지 않을 수 있으니 유의하시기 바랍니다.
trans옵션은 변수변환을 스케일 차원에서 조절할 수 있게 해줍니다. trans = log10옵션을 적용하면 y축에 log10 함수를 적용합니다. scale_y_log10 처럼 자주 쓰이는 함수들은 미리 함수들이 지정되어 있기도 하지만 옵션에서 사용하면 사용가능한 함수의 이름을 입력하는 방식으로 사용할 수 있습니다.
dia_bar +
scale_fill_brewer(palette = "Accent") +
scale_y_continuous(trans = "log10")
breaks옵션은 눈금에 표시되는 항목을 지정할 수 있게 해줍니다.
dia_bar +
scale_y_continuous(breaks = c(5000, 15000))
여기서는 좌표를 결정하는 에스테틱들이 어떻게 연결되는지를 결정합니다. 기본값은 직교좌표계(Cartesian)로 지정되어 있습니다. stat과 geom이 결정되면 좌표계를 변경할 수 있습니다. 또, Cartesian에서 x축과 y축을 바꾸는 것도 이 부분에서 변경할 수 있습니다.
좌표계의 경우 아무런 값이 지정되지 않으면 기본값으로 Cartesian을 적용합니다(coord_cartesian()). coord_flip()을 이용해 x축과 y축을 뒤바꿀 수 있고, coord_polar()를 사용하면 극좌표계로 변환시킬 수 있습니다.
dia_bar +
coord_flip()
dia_bar +
coord_polar()
그래프에서 보이는 축의 범위를 조절하려고 할 때 위에서 했던 대로 scale을 변경할 수도 있지만, 좌표계를 직접 변형시키는 방법이 있습니다. 하지만 두 개의 방식에는 큰 차이가 있는데, scale을 변경하면 해당 스케일을 벗어나는 데이터는 제거하고 그래프를 그립니다. 따라서 boxplot의 경우에는 남아있는 데이터로 box가 새로 그려질 것이고 막대그래프의 경우에는 데이터의 범위를 벗어나는 막대는 막대 자체가 그려지지 않습니다.
dia_bar + scale_y_continuous(limits=c(0,15000))
* Ideal 의 막대가 그려지지 않는다
ggplot(InsectSprays, aes(x=spray, y=count)) +
geom_boxplot() +
scale_y_continuous(limits=c(0,15))
## Warning: Removed 16 rows containing non-finite values (stat_boxplot).
반면에 좌표를 직접 수정하는 경우에는 그래프를 일단 그린 다음에 입력한 범위만 확대하거나 축소하는 등 변형을 시킵니다. 그래서 그래프의 모양이 변하거나 하지 않습니다.
dia_bar +
coord_cartesian(ylim=c(0,15000))
데이터를 특정 변수를 기준으로 하는 부분집합들로 쪼개서 small multiple의 형태로 그릴 수 있게 합니다. iris 데이터를 기준으로 말하자면, 위에서 했던 것처럼 색상으로 구분하지 않고 Species별로 각각의 그래프를 따로 그리게 됩니다.
facet을 적용하면 데이터의 부분집합들을 하위 그래프로 분리해서 그릴 수 있습니다.
facet_wrap()을 사용하면 하위 그래프들이 수평으로 나열되고, facet_grid()은 수직과 수평으로 각각 다른 변수를 지정할 수 있습니다.
mtc_point <- ggplot(mtcars, aes(mpg, wt)) +
geom_point()
mtc_point +
facet_wrap(~cyl)
두 개 이상의 변수를 동시에 지정하면 각 변수들의 경우의 수에 맞게 그래프를 그려줍니다.
이렇게 많은 숫자의 그래프가 그려질 때 ncol 옵션을 이용하면 한 화면에 보여질 그래프 열의 개수를 조정할 수 있습니다.
mtc_point +
facet_wrap(~cyl+gear, ncol = 2)
mtc_point +
facet_grid(gear~cyl)
facet_wrap의 경우에는 scales 옵션을 이용하면 각 그래프의 축을 고정시킬지 그래프별로 다르게 할지 결정할 수 있습니다.
scales = ’fixed’는 각 그래프 모두 축이 일정하게 그려집니다.
scales = ’free’는 각 그래프의 데이터 범위에 맞게 조정해서 그립니다.
scales = ‘free_x’는 x축은 데이터 범위에 맞게 조절하고, y축은 고정시킵니다. ’free_y’ 옵션도 마찬가지로 사용할 수 있습니다.
mtc_point +
facet_wrap(~cyl, scales = "fixed")
mtc_point +
facet_wrap(~cyl, scales = "free")
mtc_point +
facet_wrap(~cyl, scales = "free_x")
facet_grid에서도 마찬가지로 scales 옵션에 ‘free’ 나 ‘free_x’, ‘free_y’ 등을 적용할 수 있습니다. facet_grid에서는 추가적으로 space 옵션을 추가할 수 있는데, 기본적으는 각 그래프의 크기가 동일하게 그려지지만, space = ‘free’ 옵션이 지정되면 데이터의 범위에 따라 그래프의 크기도 변경됩니다.
mtc_point +
facet_grid(gear~cyl, scales = "free_x")
mtc_point +
facet_grid(gear~cyl, scales = "free")
mtc_point +
facet_grid(gear~cyl, scales = "free", space = "free")
가이드는 그래프를 보는 사람이 그래프의 시각적인 속성들을 어떻게 데이터와 연관지어서 볼 수 있는지 안내하는 역할을 합니다. 눈금을 표시하고 축에 이름표를 달거나 범례를 작성하는 등 그래프를 보고 데이터를 떠올리는 데 도움을 주는 항목들입니다.