library(tidyverse)Visualize
R for Data Science (2e) by Wickham, Çetinkaya-Rundel & Grolemund
Exploratory vs. Confirmatory Analysis
- 데이터 시각화는 탐색적 분석에 더 초점이 맞춰져 있음.
- 소위 data mining이라고 부르는 데이터 내의 숨겨진 패턴을 찾고 분석하는 탐색적 분석은 전통적인 통계에서 discouraging되어 왔음.
- 확률에 근거한 통계 이론은 데이터를 수집하기 전에 가설을 세우고 그 가설을 confirm하는 방식을 취함.
- 논란의 여지가 있지만, 원칙적으로 가설에 근거해 수집한 자료가 가설과 일치하는지를 확인하는 작업에서는 자료를 두 번 이상 들여다 보지 않아야 함.
- 그럼에도 불구하고, 탐색적 분석은 behind doors에서 이루어지거나 새로운 가설을 세우기 위한 방편으로 이용되었음.
- 또한, 매우 엄격한 잣대를 적용하는 상황에서도 통계 이론의 특성으로 인해 기본적인 탐색적 분석은 반드시 선행되어야 함.
- 연구 가설의 진위를 탐구할 때, 탐색적 분석에서 쉽게 빠질 수 있는 편향성(bias)는 항상 조심할 필요가 있고, 확신을 위해서는 새로이 자료를 수집해서 가설을 재검증할 필요가 있음.
탐색적 분석을 위해서는 다양한 시각화 기술이 요하나, 일반적인 통계 분석을 위해서 필요로하는 최소한으로 제한하고자 함.
또한, 복잡한 통계치를 살펴볼 때, 직접 시각화를 하기보다는 패키지가 알아서 시각화를 해주기 때문에 자세히 알지 못해도 무방함.
좀 더 상세한 내용에 대해서는
- R for Data Science (2판) / Visualize
- ggplot2 book (3판)
- ggplot2 extensions
통계치 표현: ggstatsplot, ggpubr - Data Visualization with R by Rob Kabacoff: 적절한 밸런스
- Posit cheatsheets: ggplot2 요약 정리
충분히 큰 데이터의 경우, 일정량의 데이터 가령 1/4을 따로 떼어놓고, 3/4만으로 탐색적 분석을 통해 모델을 만든 후, 따로 떼어놓은 1/4로 (가설)검증을 하는 cross-validation 방법이 있는데, machine learning분야에서는 기본적인 process.
Cross-validation 방식에는 여러 변형들이 있음; e.g. 데이터를 4등분하여 각각 4번 위의 방식을 반복하여 합치는 방식, 3가지 (training, validation, test sets)로 나누어 분석
Basics
penguins <- as_tibble(penguins) # R 4.5부터 base R에 포함된 데이터셋
penguins# A tibble: 344 × 8
species island bill_len bill_dep flipper_len body_mass 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 NA NA NA NA <NA> 2007
5 Adelie Torgersen 36.7 19.3 193 3450 female 2007
6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
# ℹ 338 more rows
glimpse(penguins)Rows: 344
Columns: 8
$ species <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Ad…
$ island <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgersen, Tor…
$ bill_len <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, 42.0, …
$ bill_dep <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, 20.2, …
$ flipper_len <int> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186, 180,…
$ body_mass <int> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, 4250, …
$ sex <fct> male, female, female, NA, female, male, female, male, NA, …
$ year <int> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007…
Variables:
species: a penguin’s species (Adelie, Chinstrap, or Gentoo)island: 관측된 섬 (Biscoe, Dream, Torgersen)bill_len,bill_dep: 부리의 길이, 두께 (mm)flipper_len: 지느러미발 길이 (mm)body_mass: 몸무게 (g)sex,year: 성별, 관측 연도
더 자세한 사항은 ?penguins
이전 판에서는 palmerpenguins 패키지의 penguins를 사용했는데, R 4.5부터 같은 데이터가 base R에 들어오면서 열 이름이 짧아졌음 (bill_length_mm → bill_len, body_mass_g → body_mass 등)
ggplot을 이용한 시각화는 주로 3가지 성분으로 나뉨
data: 사용할 데이터mapping: data의 변수들을 어떤 특성에 mapping할 것인지 specifygeom: 어떤 시각화 개체(graphical objects)로 데이터를 표현할 것인지 specify
# x, y축에 변수를 mapping
ggplot(
data = penguins,
mapping = aes(x = flipper_len, y = body_mass)
)
# point로 데이터를 표시: scatterplot
ggplot(
data = penguins,
mapping = aes(x = flipper_len, y = body_mass)
) +
geom_point()
#> Warning: Removed 2 rows containing missing values (`geom_point()`).실제로 is.na()함수를 이용해 missing을 확인해보면,
penguins |>
select(species, flipper_len, body_mass) |>
filter(is.na(body_mass) | is.na(flipper_len)) # true, false의 boolean type# A tibble: 2 × 3
species flipper_len body_mass
<fct> <int> <int>
1 Adelie NA NA
2 Gentoo NA NA
Adding aesthetics and layers
# species에 color (aesthetics)를 mapping
ggplot(
data = penguins,
mapping = aes(x = flipper_len, y = body_mass, color = species)
) +
geom_point()
위에서 species마다 다른 색을 입혀서 다른 패턴이 나타나는지 확인해 볼 수 있음
ggplot2는 + 기호로 연결하여 계속 layer를 추가할 수 있음.
다음은 trendline 혹은 fitted line이라고 부르는 경향성을 확인해 볼 수 있는 라인의 layer를 추가함
ggplot(
data = penguins,
mapping = aes(x = flipper_len, y = body_mass, color = species)
) +
geom_point() +
geom_smooth()
Data에 fitted curve를 구하는 방식에는 여러 방법이 있음
- Linear fit: 1차 함수형태인 직선으로 fit
- Smoothing fit
- Polynomial fit: n차 다항함수형태로 fit
- Loess/lowess: locally estimated/weighted scatterplot smoothing
- GAM: generalized additive model
- Spline: piece-wise polynomial regression
나중에 좀 더 자세히 알아봄
ggplot2는 플랏의 대상에 다음과 같은 속성을 부여할 수 있음
color, size, shape, fill, alpha
ggplot(
data = penguins,
mapping = aes(x = flipper_len, y = body_mass, color = species, shape = island)
) +
geom_point()
어떤 속성을 어떤 변수에 할당하는 것이 적절한지를 선택하는 것이 기술
Categorical vs. continuous
color와 같은 속성은 카테고리 변수가 좀 더 적절하나, 연속변수에서도 적용될 수 있음
ggplot(
data = penguins,
mapping = aes(x = flipper_len, y = body_mass, color = bill_len)
) +
geom_point()
반대로, x, y에 카테고리 변수를 mapping하여 scatterplot을 그리면 다음과 같은 overplotting의 문제가 생김
ggplot(
data = penguins,
mapping = aes(x = species, y = body_mass, color = sex)
) +
geom_point()
Overplotting
Overplotting의 문제를 해결하는 방식은 주로
alpha(투명도)를 조정하거나 랜덤하게 흐뜨려그리는geom_jitter()를 사용- 애초에 겹치지 않게 그리는 방법도 있음: e.g. beeswarm plot
ggplot(
data = penguins,
mapping = aes(x = species, y = body_mass, color = sex)
) +
geom_jitter(width = .2) # jitter의 정도: width, height
ggplot(
data = penguins,
mapping = aes(x = species, y = body_mass, color = sex)
) +
geom_jitter(width = .2, alpha = .5) # alpha: 투명도 0 ~ 1
Geometric objects
ggplot2는 40가지 넘는 geom objects를 제공함.
주로 통계를 위해 쓰일 geom들은
geom_point(),geom_smooth()geom_boxplot()geom_histogram(),geom_freqpoly(),geom_density()
Global vs. local mapping
ggplot(
data = penguins,
mapping = aes(x = flipper_len, y = body_mass)
) +
geom_point(mapping = aes(color = species)) + # color mapping은 geom_point에만 적용
geom_smooth() # 맨 위의 mapping에 있는 global mapping을 inherit
ggplot(
data = penguins,
mapping = aes(x = flipper_len, y = body_mass)
) +
geom_smooth(mapping = aes(linetype = sex), se = FALSE)
ggplot(
data = penguins,
mapping = aes(x = bill_dep, y = body_mass)
) +
geom_point(mapping = aes(color = species)) + # color mapping은 geom_point에만
geom_smooth(method = "lm") # method: fitted line의 종류
ggplot(
data = penguins,
mapping = aes(x = bill_dep, y = body_mass)
) +
geom_point(mapping = aes(color = species)) +
geom_smooth(method = "lm") + # 전체에 대한 직선
geom_smooth(mapping = aes(color = species), method = "lm") # 종별 직선
위 그림은 Simpson’s paradox의 전형적인 예임.
전체를 보면 부리 두께와 몸무게가 음의 관계로 보이나, 종별로 나누어 보면 모두 양의 관계임.
나중에 회귀분석에서 다시 다룸
aes() 내부, 외부에서의 mapping
ggplot(data = penguins, mapping = aes(x = flipper_len, y = body_mass)) +
geom_point(mapping = aes(color = species)) # aesthetic color에 변수를 mapping
ggplot(data = penguins, mapping = aes(x = flipper_len, y = body_mass)) +
geom_point(color = "skyblue") + # geom의 color 속성에 색을 지정
geom_smooth(color = "orangered")

Statistical transformations
ggplot2는 편의를 위해 통계치를 구해 표시해주는데,
경우에 따라 직접 통계치를 계산 후 새로 얻는 데이터로 그리는 것이 유리함
Distribution
geom_histogram(), geom_freqpoly(), geom_density()
# y축에 표시되는 통계치들이 계산됨
ggplot(data = penguins, mapping = aes(x = body_mass)) +
geom_histogram(binwidth = 100) # binwidth vs. binsggplot(data = penguins, mapping = aes(x = body_mass, colour = sex)) +
geom_freqpoly(binwidth = 100)ggplot(data = penguins, mapping = aes(x = body_mass, colour = sex)) +
geom_density(bw = 100) # bw: band width
위처럼 여러 그래프를 한 줄에 나란히 붙이려면 patchwork 패키지가 편리함
library(patchwork)
hist1 + hist2 + hist3 # 가로로 나열
hist1 / hist2 # 세로로 쌓기
(hist1 + hist2) / hist3Boxplot
Boxplot은 분포에 대한 정보은 줄어드나, 카테고리별로 간결하게 비교되는 장점


ggplot(data = penguins, mapping = aes(x = species, y = body_mass)) +
geom_boxplot()
ggplot(data = penguins, mapping = aes(x = species, y = body_mass)) +
geom_boxplot() +
geom_jitter(alpha = .6)
cps <- as_tibble(mosaicData::CPS85)
cps |>
filter(wage < 30) |>
ggplot(aes(x = as.factor(educ), y = wage)) + # as.factor(): numeric을 factor로 변환
geom_boxplot()
# color는 box의 테두리 색, fill은 내부색
ggplot(data = penguins, mapping = aes(x = species, y = body_mass, fill = sex)) +
geom_boxplot()
Barplot
Barplot은 여러방식으로 쓸 수 있는데, 문법이 조금 복잡하고, 수업에서 거의 사용하지 않을 예정이므로 웹사이트를 참조
R for Data Science / Layers / Statistical transformations
ggplot(data = penguins) +
geom_bar(mapping = aes(x = species)) # 개수
Discretize
연속 변수를 임의의 구간으로 나누어 카테고리처럼 적용할 수 있음
cut_width(), cut_number(), cut_interval()
cut_width(): 구간의 길이를 정함cut_number(): 동일한 갯수의 관측값을 갖는 n개의 그룹cut_interval(): 동일한 길이의 n개의 그룹
ggplot(
data = penguins,
mapping = aes(
x = bill_len, y = bill_dep,
color = cut_interval(body_mass, 3) # body_mass를 3개의 동일한 길이의 구간으로 나눔
)
) +
geom_point() +
geom_smooth(se = FALSE, span = 1) # span: smoothing 정도 조절
Facets
카테고리 변수들이 지니는 카테고리들(레벨)로 나누어 그리기
facet_wrap(), facet_grid()
ggplot(data = penguins, aes(x = body_mass, y = flipper_len)) +
geom_point() +
facet_wrap(~species) # species의 레벨로 나뉘어짐
facet_wrap()은 레벨이 많아지면 다음의 facet_grid()와는 다르게 화면크기에 맞춰 다음 줄로 넘어감
ggplot(data = penguins, aes(x = body_mass, y = flipper_len)) +
geom_point() +
facet_grid(sex ~ species) # 행과 열에 각각 sex, species
ggplot(
data = penguins,
aes(x = body_mass, y = flipper_len, color = sex) # color 추가
) +
geom_point(alpha = .6) +
facet_grid(island ~ species) # 행과 열에 각각 island, species
Facet과 color 중 어떤 방식으로 표현하는 것이 유리한가? 밸런스를 잘 선택!
ggplot(data = penguins, aes(x = body_mass, y = flipper_len)) +
geom_point() +
facet_wrap(~species)
ggplot(data = penguins, aes(x = body_mass, y = flipper_len, color = species)) +
geom_point()

Labels
labs() 안에 각 요소별로 지정
ggplot(
data = penguins,
mapping = aes(x = flipper_len, y = body_mass)
) +
geom_point(aes(color = species, shape = island)) +
geom_smooth() +
labs(
title = "Body mass and flipper length",
subtitle = "Dimensions for Adelie, Chinstrap, and Gentoo Penguins",
x = "Flipper length (mm)", y = "Body mass (g)",
color = "Species", shape = "Island"
)
앞으로는 pipe operator와 함께, 축약 형태로
data =대신 첫번째 argument 위치에 data frame이 위치mapping =은 두번째 argument 위치에aes()을 위치
ggplot(data = penguins, mapping = aes(x = flipper_len, y = body_mass)) +
geom_point()은 다음과 같이
ggplot(penguins, aes(x = flipper_len, y = body_mass)) +
geom_point()Pipe operator로 다음과 연결될 수 있음
penguins |>
filter(!is.na(sex) & island != "Torgersen") |> # 성별이 missing이 아니고, Torgersen섬은 제외
ggplot(aes(x = body_mass, y = flipper_len, color = sex)) +
geom_point() +
geom_smooth() +
facet_wrap(~island)
Examples
이전에 다뤘던 CPS85 데이터로 보면,
cps <- as_tibble(mosaicData::CPS85) # mosaicData package의 CPS85 데이터셋
cps# A tibble: 534 × 11
wage educ race sex hispanic south married exper union age sector
<dbl> <int> <fct> <fct> <fct> <fct> <fct> <int> <fct> <int> <fct>
1 9 10 W M NH NS Married 27 Not 43 const
2 5.5 12 W M NH NS Married 20 Not 38 sales
3 3.8 12 W F NH NS Single 4 Not 22 sales
4 10.5 12 W F NH NS Married 29 Not 47 clerical
5 15 12 W M NH NS Married 40 Union 58 const
6 9 16 W F NH NS Married 27 Not 49 clerical
# ℹ 528 more rows
cps |>
ggplot(aes(x = wage, color = married)) +
geom_freqpoly(binwidth = 1)
cps |>
ggplot(aes(x = wage, color = married)) +
geom_freqpoly(binwidth = 1) +
facet_wrap(~sex)
cps |>
ggplot(aes(x = married, y = wage)) +
geom_boxplot(width = .2) +
geom_jitter(width = .2, alpha = .2, color = "red") +
scale_y_continuous(labels = scales::label_dollar()) # y축 scale의 변경
cps |>
ggplot(aes(x = married, y = wage, fill = sex)) +
geom_boxplot()
cps |>
filter(wage < 30) |>
ggplot(aes(x = sector, y = wage, fill = sex)) +
geom_boxplot()
cps |>
filter(wage < 30) |>
ggplot(aes(x = sector, y = wage, fill = sex)) +
geom_boxplot() +
facet_grid(married ~ .)
plot <- cps |>
filter(wage < 30) |>
ggplot(aes(x = age, y = wage)) +
geom_point(alpha = .6) +
geom_smooth()
plot
확대, 축소 혹은 제한된 범위에서 보려면 다음 2가지를 구분해야 함
coord_cartesian() vs. xlim() or ylim()
plot + coord_cartesian(xlim = c(18, 40)) # zoom in
plot + xlim(18, 40) # data crop
xlim()은 범위 밖의 데이터를 삭제한 후 다시 계산하므로 trendline 자체가 달라짐.
단순히 확대해서 보려는 것이라면 반드시 coord_cartesian()을 쓸 것
cps |>
filter(wage < 30 & sector %in% c("manag", "manuf", "prof", "sales")) |>
ggplot(aes(x = age, y = wage, color = sex)) +
geom_point() +
geom_smooth(se = FALSE, span = 1) +
facet_wrap(~sector)
그래프의 저장
ggsave(): 마지막으로 그린 그래프(또는 지정한 객체)를 파일로 저장
ggsave("images/wage_by_age.png", width = 6, height = 4, dpi = 300)
ggsave("images/wage_by_age.png", plot = plot, width = 6, height = 4)- 확장자로 형식이 결정됨:
.png,.pdf,.svg - 논문/발표용은
.pdf나.svg(벡터)가 확대해도 깨지지 않아 유리함 width/height는 인치 단위. 저장 크기를 바꾸면 글자 크기의 상대적 비율이 달라지므로, 최종 크기로 저장해두고 문서에서 축소하지 말 것
Quarto 문서에서는 청크 옵션으로 크기를 지정함 (이 노트도 그렇게 작성되어 있음)
```{r}
#| fig-width: 6
#| fig-height: 4
```