-
ggplot을 이용한 데이터 가시화 I데이터 2022. 6. 26. 18:18
ggplot을 이용한 데이터 가시화
R 기본 패키지에도 다양한 가시화를 위한 함수들이 존재하며 그것들 만으로도 다양한 그래프를작성하는 것이 가능하다. 하지만 많은 데이터 과학자들이 ggplot을 추천하는 이유는 그것이 데이터를 미학적으로 유려하게 표현 할 수 있는 능력외에도 문법이 구조적이고 배우기 수월하다는 점 때문이다. R은 ggplot이전과 이후로 나눌 수 있다고 말하는 사람들도 있다. tidyverse 생태계가 진화하면서 다양한 패키지들과 연계되어 데이터를 더욱 쉽게 다룰 수 있게 되었다. 이런 경이로운 패키지를 개발해서 오픈 소스로 R 생태계를 풍부하게 만들어 준 Hadley Wichkam 등 패키지 개발자들에게 감사를 표하며 글을 시작한다. ggplot세계로 들어가보자.
1. ggplot의 특징
Grammer of Graphic(그래픽 문법)은 Layered Grammer(계층화된 문법) 체계를 가지며, 다음과 같은 요소들로 구성된다.
- data and aesthetic mappings(심미적 매핑)
- geometric objects(기하객체, 예 : 점, 선 등)
- scales(척도)
- facet specification(면 분할)
- statistical transformations(통계적 변환)
- coordinate system(좌표계)
2. ggplot 구조와 개념 이해하기
ggplot을 처음 접했을 때, 마치 마술을 보는 느낌이 들었다. Grammer of Graphic 개념에 입각하여 만들어진 ggplot은 전체 구조를 한꺼번에 파악하기 보다 익숙한 그래프를 먼저 그려보고, 이를 점차 확장해 나가면서 배우는것이 쉬운 접근법이다. 이 글에서도 산점도를 기본 플롯을 배우고 그 플롯의 내용과 심미성을 확장해 가면서 각 코드와 그 결과 플롯을 비교 하면서 진행하겠다. ggplot에는 많은 종류의 플롯과 요소들로 구성되어 모든 것을 암기하기 쉽지 않으니 ggplot 치트시트를 활용하기 바란다.
ggplot 치트시트 https://www.rstudio.com/resources/cheatsheets/ 다운 받으면 된다. 본 강의에 사용하는 팔머 펭귄데이터는 남극의 팔머열도 일대의 펭귄에 대한 조사 데이터로 펭귄의 종류, 부리길이, 부리두께, 날개길이와 체중, 성별이 포함된 데이터이다. iris 데이터 보다 형식이 다양해서 데이터 분석이나 머신러닝의 예제에 많이 활용되는 데이터이다.
편의상 원 데이터의 컬럼 이름을 수정하고 결측치는 제거해서 사용한다.
p <- penguins names(p) <- c("species", "island", "bill.l", "bill.d","flipper.l", "body.m","sex","year") p <- na.omit(p) head(p)
# A tibble: 6 × 8 species island bill.l bill.d flipper.l body.m sex year <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int> 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007 2 Adelie Torgersen 39.5 17.4 186 3800 female 2007 3 Adelie Torgersen 40.3 18 195 3250 female 2007 4 Adelie Torgersen 36.7 19.3 193 3450 female 2007 5 Adelie Torgersen 39.3 20.6 190 3650 male 2007 6 Adelie Torgersen 38.9 17.8 181 3625 female 2007
3. 산점도(Scatter Plot)
먼저 두 연속형 변수를 쌍으로 해서 직교좌표에 표정한 산점도를 그려보자.
ggplot에 데이터를 p로, aes에 x = flipper.l , y = body.m 를 지정하여 매팽한 다음 geom_함수를 이용하여 표시할 플롯의 종류(geom_point())를 지정하면 된다.ggplot(data = p, mapping = aes(x = flipper.l , y = body.m)) + geom_point()
산점도는 두 연속형 변수의 관계를 보여주는 플롯으로 x 축 flipper.l(날개의 길이)와 y축 body.m(체중)을 좌표로 표정한 플롯으로 x변수와 y변수간 상관관계를 알 수 있는 그래프이다. 펭귄의 날개길이와 체중간에는 양의 상관관계 즉 “날개의 길이가 길수록 체중이 증가한다” 라는 관계가 있음을 알 수 있다.
이제 심미성(aesthetic) 매핑을 추가하여 펭귄의 종류별로 구분해서 그래프를 그려보자.ggplot(data = p, mapping = aes(x = flipper.l , y = body.m, color = species)) + geom_point()
color 인자에 species를 매핑하여, 펭귄의 종류별로 다른 색갈로 표시하여 플롯하였다. 플롯 우측에 색상별로 펭귄의 종류를 나타내는 범례가 표시된것을 볼 수 있다. 펭귄의 성별을 심미성 인자의 color 에 매핑할 수 있고, shape인자로 매핑할 수 도 있다. ggplot에서는 aes의 color, fill, shape 등의 인자를 팩터형 변수에 매핑하여 쉽게 집단을 구분해서 플롯팅 할 수 있다. 연속형 변수에도 aes요소를 매핑해서 연속형 변수의 값의 크기를 다르게 표현 할 수도 있다. 예를 들어 같은 날개 길이와 체중의 산점도 지만 펭귄의 체중이 커질 수록 점의 크기가 커지 함으로써 체중의 변화를 나타내게 할 수도 있다. 이런 종류의 그래프를 버블 차트라고한다.
point3 <- ggplot(data = p, mapping = aes(x = flipper.l , y = body.m, color = sex)) + geom_point() point4 <- ggplot(p, aes(x = flipper.l, y = body.m, size = body.m)) + geom_point(shape = 21, color = "navyblue", fill = "skyblue", alpha = 0.7) grid.arrange(point3, point4, nrow = 1)
위와 같이 플롯 여러개를 하나의 행에 포함 할 경우 gridExtra 패키지의 grid.arrange() 함수를 이용하여 다른 종류의 그래프를 한 공간에 표시 할 수 있다.
지금까지 산점도를 그리기 위해 사용한 geom_point를 geom 객체라고 한다. ggplot에서는 플롯을 그리기 위한 다양한 geom 객체를 제공한다. 박스플롯을 그리기 위해서는 geom_boxblot, 선 그래는 geom_line, 막대 그래프는 geom_bar와 geom_col, 데이터의 평활선은 geom_smooth 등 40여개가 넘는 다양한 geom 객체가 존재하며 ggplot 치트시트에 많은 geom들의 사용예들이 나와있다.
ggplot 특징중 하나가 레이어를 계층화(중첩)해서 다양한 플롯을 표현할 수 있다는 점인데 이때 중첩해서 사용되는 geom 객체의 인수 값이 영향을 미치는 범위에 대해 알 아보자.
point5 <- ggplot(data = p) + geom_point(aes(x = flipper.l , y = body.m)) + geom_smooth(aes(x = flipper.l , y = body.m)) point6 <- ggplot(data = p, aes(x = flipper.l , y = body.m)) + geom_point() + geom_smooth() grid.arrange(point5, point6, nrow = 1)
위 좌우 그래프는 완전히 동일한 그래프이다. 하지만 두 그래프의 소스 코드를 보면 두 그래프의 aes 매핑에 차이가 있음을 알 수 있다. 왼쪽의 그래프에서는 aes를 각각의 geom 객체에서 지정을 해준 반면, 오른쪽 그래프 ggplot()에서 aes를 한번만 매핑해 준것을알 수 있다. 맨 상위인 ggplot에서 aes 인자를 지정하면 인자의 효력이 이하 모든 geom에 공통적으로 적용이된다. 마치 전역변수가 모든 함수에 적용되는 것과 유사하다. 하지만 각각의 geom 객체에서 aes로 매핑한 인자는 해당 geom 객체에서만 적용이 된다. 이 개념을 활용하면 레이어 마다 다른 심미성을 갖는 그래프를 작성하는 것이 가능하다.
point7 <- ggplot(data = p, aes(x = flipper.l , y = body.m, color = species))+ geom_point() + geom_smooth() point8 <- ggplot(data = p, aes(x = flipper.l , y = body.m))+ geom_point(aes(color = species)) + geom_smooth() grid.arrange(point7,point8, nrow =1)
위 왼쪽의 산점도에서는 color = species 인자를 ggplot에서 지정하여 그 효력이 이하 모든 geom객체에 적용되어 geom_smooth의 평활선이 펭귄의 종류별로 각각 그려졌지만, 오른쪽 산점도에서는 color 인자를 geom_point에서 지정하여 geom_smooth 객체에서는 모든 종을 총괄하는 평활선이 그려지게 된것이다.
이제 플롯을 완성하는 기본적인 요소들을 추가하여 그래프의 완성도를 높여보자. 플롯의 데이터 컴포턴트 외에 여러가지 요소(제목, 축, 배경, 범례, 텍스트 등)들을 커스터마이징 하기 위해 사용하는 함수가 theme() 함수이다. theme() 함수를 이용하여 개인의 취향에 맞는 플롯을 작성 할 수 있다. theme를 활용하여 플롯을 다양하게 커스터마이징 하는 방법들은 별도로 다루도록 한다. * 제목의 지정(폰트 크기, 텍스트 정렬 등 ) * x축 및 y축의 지정 * 범례의 위치 조정 등 그래프를 용도에 맞게 수정하고 꾸미는 것은 다양한 방법들이 있으며, 자신에 맞는 방법을 찾아 연습할 필요가 있다. 다음에 제시한 것은 단순한 예이다.
ggplot(data = p, aes(x = flipper.l , y = body.m))+ geom_point(aes(color = species)) + geom_smooth() + ggtitle("펭귄 날개길이 대비 체중") + xlab("날개 길이(mm)") + ylab("펭귄 체중(g)") + theme(plot.title = element_text(family = "serif", face = "bold", hjust = 0.5, size = 15, color = "navyblue"), legend.position = "top")
4. 박스플롯(Box Plot)과 바이올린(Violin) 플롯
박스플롯은 위스커(Whisker)플롯 이라고도 하며 연속형 변수의 분포와 이상치 등을 알 아보기 위해 많이 활용하는 그래프이다.
펭귄 종류별 체중의 분포를 박스플롯으로 그려보자.box1 <- ggplot(data= p, aes(x = species, y = body.m)) + geom_boxplot() box2 <- ggplot(data= p, aes(x = species, y = body.m)) + geom_boxplot(aes(fill = species)) + theme(legend.position = "none") grid.arrange(box1, box2, nrow = 1)
박스플롯에 표시된 점은 이상치(Outlier)를 나타내는 점으로 엄밀하게 표현하면 75 퍼센타일 보다 위 아래로 1.5 IQR을 초과하는 관측치를 의미한다. 우측 플롯은 종별로 다른 색상을 표현하여 가독성을 높인 플롯이다. 박스플롯은 변수 분포에 대한 전체적인 윤곽을 파악하기 좋지만, 각각의 관측치 값들을 박스 플롯과 같이 표현하여 각개 관측치들의 분포 상태를 표현할 수도 있다.
box3 <- ggplot(data= p, aes(x = species, y = body.m)) + geom_boxplot(aes(fill = species)) + theme(legend.position = "none") box4 <- ggplot(data= p, aes(x = species, y = body.m)) + geom_boxplot(aes(fill = species)) + geom_jitter(alpha = 0.7)+ theme(legend.position = "none") grid.arrange(box3, box4, nrow = 1)
위 왼쪽 박스 플롯은 이상치만 점으로 표현되는 기본 boxPlot이다. 관측점들을 흩트려 분포시킴으로써 좀더 사실적인 표현이 되도록 만들때 사용하는 것이 geom_jitter 객체이다. geom_jitter에서 사용한 alpha 인자는 투명도를 나타내는 인자이다.
박스 플롯에 notch(칼로 베인 자국)을 표시하여 중간값의 신뢰구간을 표시할 수도 있다. 아래 플롯에서는 종류별 펭귄체중의 중앙값에 대한 신뢰구간을 의미하는 notch가 들어있다. 아델리 펭귄과 친스트랩 펭귄의 notch값이 겹치는 것으로 봐서 두 종의 체중의 중앙값에 차이가 있다고 볼 수는 없다고 판단할 수 있다. 아래 우측 플롯에서는 y축에 눈금표시 선(Rug)를 추가하여 분포가 밀집되는 지점을 알 수 있도록 하고 있다.box5 <- ggplot(data= p, aes(x = species, y = body.m)) + geom_boxplot(aes(fill = species), notch = TRUE) + theme(legend.position = "none") box6 <- ggplot(data= p, aes(x = species, y = body.m)) + geom_boxplot(aes(fill = species), notch = TRUE) + geom_jitter()+ geom_rug() + theme(legend.position = "none") grid.arrange(box5, box6, nrow = 1)
바이올린 플롯은 박스플롯에 밀도를 대칭형식으로 표현하여 분포를 좀 더 쉽게 가늠할 수 있도록 개발된 플롯이다.
vio1 <- ggplot(data= p, aes(x = species, y = body.m )) + geom_violin(aes(fill = species)) + theme(legend.position = "none") vio2 <- ggplot(data= p, aes(x = species, y = body.m )) + geom_violin(aes(fill = species)) + geom_boxplot(width = 0.2) + theme(legend.position = "none") grid.arrange(vio1, vio2, nrow = 1)
왼쪽은 펭귄 종류별 체중의 분포를 그린 바이올린 플롯이다. 오른쪽은 바이올린 플롯과 박스플롯을 중첩하여 플롯해서 데이터의 분포의 변화를 한눈에 쉽게 파악할 수 있도록 그린 플롯이다. geom_boxplot()함수의 인자로 width 인수를 지정하여 바이올린 안의 박스크기를 조정할 수 있다. 데이터 량이 많은 경우 박스프롯 보다 바이올린 플롯으로 가시화하면 분포를 쉽게 파악할 수 있다. 조사 데이터가 5만3천여건이 되는 diamonds 데이터의 절단(cut) 상태별 diamonds 중량(캐럿트)의 분포를 바이올린 플롯으로 그려보자. coord_flip() 함수는 그래프 x,y 좌표축을 종,횡으로 회전하는데 사용하는 함수이다.
ggplot(diamonds, aes(cut, carat)) + geom_violin(aes(fill = cut)) + geom_boxplot(width = 0.15) + theme(legend.position = "none") + coord_flip()
이번 글에서는 산점도, 박스플롯 위주로 aes 인자들의 종류, 적용방법 등 ggplot을 처음 익히는 독자들을 위한 내용을 다루었다. 다음 글에서는 막대그래프와 분포그래프를 활용한 가시화 및 플롯의 테마를 이용하여 각자의 취향에 맞게 플롯을 커스터마이징 하는 법을 다루도록 한다.
'데이터' 카테고리의 다른 글
Handling Factor Variable using forcats package (0) 2022.08.07 Tidymodels를 활용한 ML Process (0) 2022.07.23 ggplot을 이용한 데이터가시화 II (0) 2022.07.08 Analysis of FIFA 22 DATA (2) (0) 2022.06.10 Analysis of FIFA 22 DATA (0) 2022.05.28