-
ggplot을 이용한 데이터가시화 II데이터 2022. 7. 8. 21:39
첫번째 ggplot 강의에 이어 두번째 강의 주제는 다음과 같다.
- 막대그래프류(geom_bar(), geom_col(), geom_histogram())
- 밀도그래프류(geom_density())
이번에도 penguns 데이터를 기본으로 하되, 필요에 따라 그래표를 효과적으로 설명할 수 있는 데이터를 사용하도록 한다. 데이터 가시화에 관련되어 저자에 따라 플롯, 그래프 혹은 차트 등 사용하는 용어들이 다양하지만 graph, chart, plot에 대한 의미 차이에 대한 권위있는 해석과 정의는 찾기 어려웠다. 여기서는 그래프와 차트를 동의어로 간주하고 구분없이 사용하기로 한다.
먼저 데이터 분석 및 머신러닝 분야에서 isris 데이터 만큼이나 많이 사용하는 팔퍼 펭귄데이터를 살펴보고 코딩의 편의상 변수명을 간략화 시켜 사용한다.p <- penguins names(p) <- c("species", "island", "bill.l", "bill.d","flipper.l", "body.m","sex","year") p <- p %>% drop_na() glimpse(p)
Rows: 333 Columns: 8 $ species <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adel… $ island <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgersen, Torge… $ bill.l <dbl> 39.1, 39.5, 40.3, 36.7, 39.3, 38.9, 39.2, 41.1, 38.6, 34.6, … $ bill.d <dbl> 18.7, 17.4, 18.0, 19.3, 20.6, 17.8, 19.6, 17.6, 21.2, 21.1, … $ flipper.l <int> 181, 186, 195, 193, 190, 181, 195, 182, 191, 198, 185, 195, … $ body.m <int> 3750, 3800, 3250, 3450, 3650, 3625, 4675, 3200, 3800, 4400, … $ sex <fct> male, female, female, female, male, female, male, female, ma… $ year <int> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, …
1. 막대그래프 류
막대(Bar) 그래프는 막대로 데이터의 통계량을 표시하는 그래프이다. 막대 그래프의 대표적인 geom은 geom_bar()와 geom_col() 및 geom_histogram()이다. 우리는 직관적인 geom_bar()를 통해 막대 그래프를 그리고 다루는 법을 공부한다. 처음 ggplot을 배우는 학생들이 혼돈스러워 하는 부분은 막대의 의미를 표현하는 통계량에 대한 부분이다. 즉 막대의 높이가 의미하는 양이 무엇인가에 대한 문제인다. 예를 들어 펭귄 데이터의 펭귄 종류별 개체수일 수 도 있고, 펭귄 종류별 평균 체중이 될 수 도 있다. 또한 홍길동 학생의 국어 점수 같이 특정 데이터의 변수에 포함된 값 자체가 될 수도 있다. 일반적으로 종류별 개체수 같이 빈도를 표현 할때는 geom_bar()를 사용하고 변수의 값 자체를 표현 하는 경우에는 geom_col()을 사용한다. 이는 geom 객체별로 표현하고자 하는 통계량을 계산해주는 stat(스탯)을 가지고 있기 때문이다. 물론 stat을 지정하여 다른 통계량을 표현하는 것도 가능하다. 지금부터 막대 그래프의 종류와 형식에 대해서 공부해보자.
먼저 펭귄 종류별 개체수(빈도)를 나타내는 막대그래를 그려보자.bar1 <- ggplot(p, aes(x = species)) + geom_bar() bar2 <- ggplot(p, aes(x = species, fill = species)) + geom_bar() + theme(legend.position = "none") grid.arrange(bar1, bar2, nrow = 1)
두 막대 그래프는 fill 인자를 지정하여 종을 색깔로 구분 한 것 이외에는 동일하다. 두 그래프의 x축은 species, y축의 레이블은 count로 표시되어 있는 것을 볼 수 있다. 우리가 x 인자를 팩터형 변수인 species로 지정하였으니, x축은 species가 되는 것은 당연하지만, y축은 우리가 특정 통계량을 지정하지 않았음에도 불구하고, geom_bar()에서 자동으로 species별 개체수를 카운트하여 표시하였음을 알 수 있다. 즉 geom_bar()가 사용하는 디폴트 통계량(stat)이 빈도(count)임을 알 수 있다. ?geom_bar()로 도움말 문서를 확인해 보면 geom_bar()의 stat인자가 “count”로 설정되어 있는것을 확인 수 있다. 즉 aes의 x 인자로 지정된 빈도를 통계량으로 하는 막대그래플 작성한다는 것이다. geom_bar()의 stat 인자를 “identity”로 지정하여 빈도수 외에 데이터 값 자체를 표현 하게 할 수도 있다. 예를들어 펭귄 종류별 평균 체중을 막대 그래프로 그리는 경우를 geom_bar()를 사용하여 그린다고 생각해보자. 먼저 펭귄 종류별 체중의 평균을 구한 다음, 그것을 이용하여 막대그래프를 그려야 한다.
p_bodymean <- p %>% group_by(species) %>% summarise(ave = mean(body.m)) ggplot(p_bodymean, aes(x = species, y = ave, fill = species)) + geom_bar(stat = "identity") + ggtitle("펭귄 종류별 체중") + xlab("펭귄 종류 ") + ylab("펭귄 체중(g)")
위 예에서는 먼저 종별 평균체중으로 요약된 p_bodymean 티블을 생성한 이후 이를 ggplot의 데이터 인자로 하여 막대그래프를 작성하였다. geom_bar()의 stat 인자를 “identity”로 지정하여 geom_bar()의 디폴트 stat인 count 대신 y인자 값(체중의 평균값) 그대로를 막대 그래프로 표현하였다.
실제로 필드에서는 이런경우, geom_bar(stat = “identity”) 대신 geom_col()을 사용하다. geom_col()은 aes인자로 지정된 인자의 count를 계산해서 출력하는 것이 아니라 y 인자의 값을 출력하게된다.ggplot의 데이터를 지정하는 방법은 위와 같이 데이터를 먼저 만든 다음 이를 ggplot 함수의 데이터 인자로 지정하는 하는 방법 보다는 다음과 같이 tidyverse의 파이프라이닝 연산자를 이용하여 간략한 형태로 코딩을 한다. 아래에서 제시하는 두가지 형태의 코드는 동일한 코드이다. tidyverse 관점에서 파이프라이닝 %>% 연산자로 연산을 수행하는 방법은 tidyverse를 활용한 데이터 주무르기 편에서 자세하게 설명한다.
# 데이터 지정 방법 1 ggplot(p %>% group_by(species) %>% summarise(ave = mean(body.m)), aes(x = species, y = ave, fill = species)) + geom_bar(stat = "identity") + ggtitle("펭귄 종류별 체중") + xlab("펭귄 종류 ") + ylab("펭귄 체중(g)") # 데이터 지정 방법 2 p %>% group_by(species) %>% summarise(ave = mean(body.m)) %>% ggplot(aes(x = species, y = ave, fill = species)) + geom_bar(stat = "identity") + ggtitle("펭귄 종류별 체중") + xlab("펭귄 종류 ") + ylab("펭귄 체중(g)")
펭귄 종류별 빈도수를 성별로 구분해서 표시하고 자 할 때는 aes의 fill 인자를 sex로 지정하여 성별로 다른 색깔을 표현하게 함으로써 구분할 수 있다.
bar3 <- ggplot(p, aes(x = species, fill = sex)) + geom_bar(position = "dodge") + ggtitle("펭귄 종류 성별 체중") + xlab("펭귄 종류 ") + ylab("펭귄 체중(g)") bar4 <- ggplot(p, aes(x = species, fill = sex)) + geom_bar(position = "stack") + ggtitle("펭귄 종류 성별 체중") + xlab("펭귄 종류 ") + ylab("펭귄 체중(g)") grid.arrange(bar3, bar4, nrow =1)
막대 그래프를 배치하는 인자가 position 인자이다. position 인자에 “dodge”를 지정하면 막대 그래프를 비켜서 나란히 위치시키고, “stack”을 지정하면 누적 막대그래프 형태로 “fill”은 누적된 형태이지만, 동일한 높이로 하여 그룹들 사이의 비율을 비교하기 쉽게 표현해준다.
막대 그래프를 작성하는데 있어서 반드시 필요한 테크닉이 있다. 막대그래프를 크기 순이나 원하는 순서대로 정렬 시키는 방법이 그것이다. mpg 데이터를 이용한 다음의 예를 보자. mpg 데이터는 자동차 메이커, 모델별로 자동차 연비에 대한 데이터 이다. 자동차 제조사별 자동차 빈도에 대한 막대 그래프를 보자.
mpg <- mpg %>% mutate_if(is.character, as.factor) %>% # charatcter 변수를 factor 변수로 glimpse()
Rows: 234 Columns: 11 $ manufacturer <fct> audi, audi, audi, audi, audi, audi, audi, audi, audi, aud… $ model <fct> a4, a4, a4, a4, a4, a4, a4, a4 quattro, a4 quattro, a4 qu… $ displ <dbl> 1.8, 1.8, 2.0, 2.0, 2.8, 2.8, 3.1, 1.8, 1.8, 2.0, 2.0, 2.… $ year <int> 1999, 1999, 2008, 2008, 1999, 1999, 2008, 1999, 1999, 200… $ cyl <int> 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 8, 8, … $ trans <fct> auto(l5), manual(m5), manual(m6), auto(av), auto(l5), man… $ drv <fct> f, f, f, f, f, f, f, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, r, … $ cty <int> 18, 21, 20, 21, 16, 18, 18, 18, 16, 20, 19, 15, 17, 17, 1… $ hwy <int> 29, 29, 31, 30, 26, 26, 27, 26, 25, 28, 27, 25, 25, 25, 2… $ fl <fct> p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, r, … $ class <fct> compact, compact, compact, compact, compact, compact, com…
mpg %>% ggplot(aes(x = manufacturer)) + geom_bar() + ggtitle("자동차 제조사 별 시험 참가 자동차 수") + xlab("제조사 ") + ylab("자동차 수(대)")
위 그래프는 자동차 제조사별 테스트에 참여한 자동차수에 대한 막대 그래프이다. 보는 바와 같이 길이가 정리가 안된상태로 출력이 되어 미관상 좋지 않다. 이렇게 출력된 이유는 x 변수로 지정된 manufacture변수의 알파벳 순으로 정리가 되었기 때문이다. 이 그래프의 순서를 크기순으로 정렬시키도록 개선시켜 보자.
mpg %>% mutate(manufacturer = fct_infreq(manufacturer)) %>% ggplot(aes(x = manufacturer)) + geom_bar(aes(fill = manufacturer)) + ggtitle("자동차 제조사 별 시험 참가 자동차 수") + xlab("제조사 ") + ylab("자동차 수(대)")
mpg %>% mutate(manufacturer = fct_infreq(manufacturer)) %>% ggplot(aes(x = manufacturer)) + geom_bar(aes(fill = manufacturer)) + ggtitle("자동차 제조사 별 시험 참가 자동차 수") + xlab("제조사 ") + ylab("자동차 수(대)")+ coord_flip()
위 코드를 보면 ggplot에 파이프라인 연산자로 데이터를 넘겨주기 전에, mutate 함수와 fct_infreq함수를 이용해 팩터 순서를 바꿔주는 것을 볼 수 있다. fct_infreq함수는 변수의 빈도순으로 팩터의 레벨을 변경시켜 주는 함수로 forcats 패키지에 포함되어있다. forcats 패키지는 팩터형 변수을 다루는 다양한 함수들을 포함하고 있다. 팩터형 변수 다루기는 tidyvers 생태계를 활용한 데이터 주무르기 편에서 자세히 다룬다. X 축의 레이블이 많아 겹쳐지는 경우, coord_flip()을 이용하여 그래프를 회전 시키면 가독성이 좋아진다.
geom_bar() 함수는 이산형 변수에 대한 빈도를 막대 그래프로 작성하였지만, geom_col()은 이산형 x변수의 관측된(특정된) y 값을 막대의 크기로 막대 그래프를 작성한다. 다음의 예를 통해 펭귄의 종류별 평균 체중을 막대 그래프로 작성하는 경우와, mpg 데이터의 class별 연비 평균을 막대 그래프로 그리는 경우를 사례로 알아보자.
col1 <- p %>% group_by(species, sex) %>% summarise(ave = mean(body.m)) %>% ggplot(aes(x = species, y = ave, fill = sex)) + geom_col(position = "dodge") + ggtitle("펭귄 종류별 성별 평균체중") + xlab("펭귄종류 ") + ylab("평균체중(대)") col2<- mpg %>% group_by(class) %>% summarise(ave = mean(cty + hwy)) %>% mutate(class = fct_reorder(class, ave)) %>% ggplot(aes(x = class, y = ave, fill = class)) + geom_col() + ggtitle("자동차 클래스별 평균연비 ") + xlab("자동차 클래스 ") + ylab("평균 연비(mile/galon)") + theme(legend.position = "none") grid.arrange(col1, col2, nrow = 1)
geom_histogram() 돗수분포표 그래프 히스토그램은 데이터의 분포를 돗수분포 막대그래프로 가시화 하는 가장 기본적인 도구이다. 히스토그램은 하나의 연속형변수에 대해 그 연속형 변수를 정해진 구간(빈)으로 나누었을때 해당하는 구간에 속하는 관측값의 돗수(빈도)를 표현해주는 그래프이다. geom_bar()와 같은 막대 그래프는 x가 이산형변수(discrete)일 때 빈도나 연속형 값을 표현 한다. 따라서 이산형 데이터 각각을 나타내는 x축의 막대들은 서로를 구분하기 위해 분리된 형태를 갖게되지만 히스토그램은 연속형변수를 구간(bin)으로 나누어 해당 구간에 속한 데이터의 빈도를 나타내기 때문에 x축의 막대들이 서로 붙어 있는 형태를 띄게된다. 펭귄 체중 분포를 히스토그램으로 작성해보자.
bin30 <- ggplot(p, aes(x = body.m)) + geom_histogram() + ggtitle("펭귄 체중 분포(bin = 30)") + xlab("구간 ") + ylab("돗수") bin10 <- ggplot(p, aes(x = body.m)) + geom_histogram(bins = 10) + ggtitle("펭귄 체중 분포(bin = 10") + xlab("구간 ") + ylab("돗수") grid.arrange(bin30, bin10, nrow =1)
geom_histogram은 기본적으로 bins = 30으로 설정되어있다. bin의 수에 따라 히스토그램의 형태를 파악하기위해 bin을 30개와 10개로 나눈 히스토그램을 각각 제시하였다.
2. 밀도 그래프
연속형 변수의 분포를 나타내는 또 다른 방법은 밀도로 분포로 나타내는 것이다 히스토그램이 구간에 대한 빈도(돗수)를 막대 그래프로 표시한 반면 밀도 그래프는 구간의 돗수대신 밀도(구간돗수/구간)로 분포를 표현한다. geom_density() 함수를 통해 펭귄 체중과 관련한 밀도함수를 그려보자.
den1 <- p %>% ggplot(aes(x = body.m)) + geom_density() + ggtitle("펭귄 체중 분포") + xlab("체중 ") + ylab("밀도") den2 <- p %>% ggplot(aes(x = body.m, color = species)) + geom_density() + ggtitle("펭귄 종류별 체중 분포") + xlab("체중") + ylab("밀도") grid.arrange(den1, den2, nrow = 1)
펭귄 체중의 분포를 density plot으로 작성해봤다. geom_density()는 분포 밀도를 선으로 그려준다. 펭귄의 종류별로 구분하여 분포도를 작성 할 수도 있다.
den3 <- p %>% ggplot(aes(x = body.m, fill = species)) + geom_density(alpha = 0.5) + ggtitle("펭귄 종류별 체중 분포") + xlab("체중") + ylab("밀도") den4 <- p %>% ggplot(aes(x = body.m, fill = species)) + geom_density(alpha = 0.5) + ggtitle("펭귄 체중 분포") + xlab("체중 ") + ylab("밀도") + facet_grid(species ~ . ) + theme(legend.position = "none") grid.arrange(den3, den4, nrow = 1)
위 좌측의 그래프는 종별 체중의 분포를 하나의 플롯으로 작성하고, 투명도(alplha) 값을 0.5로 지정하여 구분하였으나, 우측의 그래프에서는 facet_grid() 함수를 이용하여 종별로 면을 분할하여 작성하였다. ggplot에서 면 분할 할때 쓰는 함수는 facet_grid()와 facet_wrap()이 있다. 아래에서 보는 그래프에서는 펭귄의 종류별 성별 체중의 분포에 대한 밀도 그래프를 면을 종과 횡으로 구분하여 작성하였다.
p %>% ggplot(aes(x = body.m, fill = sex, alpha = 0.5)) + geom_density( ) + facet_grid(species ~ . ) + # 3개 행으로 labs(title = "펭귄 종류별 성별 체중 분포", x = "펭귄체중(g)", y ="밀도")
p %>% ggplot(aes(x = body.m, fill = sex, alpha = 0.5)) + geom_density() + facet_wrap(~ species) + labs(title = "펭귄 종류별 체중 분포", x = "펭귄체중", y ="밀도")
지금까지 두번에 걸쳐 ggplot을 이용한 데이터 가시화에 필요한 기본적인 geom들과 이를 이를 응용하여 그래프를 원하는 형태로 작성하는 법을 알아보았다. ggplot에는 이들 외에도 매우 다양한 종류의 geom들이 존재하며, 같은 그래프라도 자신이 구상하는 대로 그래프의 요소를 섬세하게 조정하여 심미성을 높이는 방법들에 대한 공부는 각자에게 맡겨두기로 하고 ggplot에 대한 강의는 여기서 마무리한다.
'데이터' 카테고리의 다른 글
Handling Factor Variable using forcats package (0) 2022.08.07 Tidymodels를 활용한 ML Process (0) 2022.07.23 ggplot을 이용한 데이터 가시화 I (0) 2022.06.26 Analysis of FIFA 22 DATA (2) (0) 2022.06.10 Analysis of FIFA 22 DATA (0) 2022.05.28