Day 3: 데이터 탐색과 시각화

Author

이상일(서울대학교 지리교육과)
김세창(서울대학교 지리교육과 석사)
김우형(서울대학교 지리교육과 석사과정)

Modified

August 6, 2024

오늘 사용할 패키지

  1. tidyverse

  2. ggrepel

  3. patchwork

  4. RColorBrewer

1 데이터 변형

1.1 실습 준비

사용할 데이터는 World Bank가 gapmider.org를 통해 무료로 배포하는 것으로 gapmider 패키지에 포함되어 있다. gapmider 패키지를 인스톨하고 불러온다. 데이터가 어떻게 구성되어 있는지 살펴본다.

library(gapminder)
View(gapminder)

1.2 행 함수

1.2.1 filter() 함수

특정 열(변수)과 관련된 조건을 만족하는 행을 선정한다. 행의 길이가 준다.

# continent의 값이 Europe인 행만 선택
gapminder |> 
  filter(continent == "Europe") 
# A tibble: 360 × 6
   country continent  year lifeExp     pop gdpPercap
   <fct>   <fct>     <int>   <dbl>   <int>     <dbl>
 1 Albania Europe     1952    55.2 1282697     1601.
 2 Albania Europe     1957    59.3 1476505     1942.
 3 Albania Europe     1962    64.8 1728137     2313.
 4 Albania Europe     1967    66.2 1984060     2760.
 5 Albania Europe     1972    67.7 2263554     3313.
 6 Albania Europe     1977    68.9 2509048     3533.
 7 Albania Europe     1982    70.4 2780097     3631.
 8 Albania Europe     1987    72   3075321     3739.
 9 Albania Europe     1992    71.6 3326498     2497.
10 Albania Europe     1997    73.0 3428038     3193.
# ℹ 350 more rows
 # pop이 5천만, gdpPercap이 3만을 초과하는 행만 선택
gapminder |> 
  filter(pop > 50000000 & gdpPercap > 30000)
# A tibble: 9 × 6
  country        continent  year lifeExp       pop gdpPercap
  <fct>          <fct>     <int>   <dbl>     <int>     <dbl>
1 France         Europe     2007    80.7  61083916    30470.
2 Germany        Europe     2002    78.7  82350671    30036.
3 Germany        Europe     2007    79.4  82400996    32170.
4 Japan          Asia       2007    82.6 127467972    31656.
5 United Kingdom Europe     2007    79.4  60776238    33203.
6 United States  Americas   1992    76.1 256894189    32004.
7 United States  Americas   1997    76.8 272911760    35767.
8 United States  Americas   2002    77.3 287675526    39097.
9 United States  Americas   2007    78.2 301139947    42952.
# year이 2007이고, lifeExp가 82를 초과하거나 gdpPercap이 4만을 초과하는 행만 선택
gapminder |> 
  filter(year == 2007 & (lifeExp > 82 | gdpPercap > 40000))
# A tibble: 7 × 6
  country          continent  year lifeExp       pop gdpPercap
  <fct>            <fct>     <int>   <dbl>     <int>     <dbl>
1 Hong Kong, China Asia       2007    82.2   6980412    39725.
2 Ireland          Europe     2007    78.9   4109086    40676.
3 Japan            Asia       2007    82.6 127467972    31656.
4 Kuwait           Asia       2007    77.6   2505559    47307.
5 Norway           Europe     2007    80.2   4627926    49357.
6 Singapore        Asia       2007    80.0   4553009    47143.
7 United States    Americas   2007    78.2 301139947    42952.

1.2.2 slice() 함수

filter() 함수와 마찬가지로 행의 숫자를 줄인다. slice() 함수는 slice_head(), slice_tail(), slice_max(), slice_min()과 같은 패밀리 함수가 더 널리 사용된다. 그런데 이 함수들은 작동 방식에 따라 두 가지로 구분된다.

몇 번째에서 몇 번째 사이의 행만을 골라낸다.

# 1~5행만을 선택
gapminder |> 
  slice(1:5)
# A tibble: 5 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       1952    28.8  8425333      779.
2 Afghanistan Asia       1957    30.3  9240934      821.
3 Afghanistan Asia       1962    32.0 10267083      853.
4 Afghanistan Asia       1967    34.0 11537966      836.
5 Afghanistan Asia       1972    36.1 13079460      740.

가장 앞에 위치한 몇 개(n)의 행만을 골라낸다. 실질적으로 위와 동일하다.

# 가장 앞의 5개 행을 선택
gapminder |> 
  slice_head(n = 5)
# A tibble: 5 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       1952    28.8  8425333      779.
2 Afghanistan Asia       1957    30.3  9240934      821.
3 Afghanistan Asia       1962    32.0 10267083      853.
4 Afghanistan Asia       1967    34.0 11537966      836.
5 Afghanistan Asia       1972    36.1 13079460      740.
Tip

Base R 함수인 head()또한 같은 역할을 할 수 있다.

gapminder |> 
  head(5)
# A tibble: 5 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       1952    28.8  8425333      779.
2 Afghanistan Asia       1957    30.3  9240934      821.
3 Afghanistan Asia       1962    32.0 10267083      853.
4 Afghanistan Asia       1967    34.0 11537966      836.
5 Afghanistan Asia       1972    36.1 13079460      740.

가장 뒤에 위치한 몇 개(n)의 행만을 골라낸다.

# 가장 뒤의 5개 행을 선택
gapminder |> 
  slice_tail(n = 5)
# A tibble: 5 × 6
  country  continent  year lifeExp      pop gdpPercap
  <fct>    <fct>     <int>   <dbl>    <int>     <dbl>
1 Zimbabwe Africa     1987    62.4  9216418      706.
2 Zimbabwe Africa     1992    60.4 10704340      693.
3 Zimbabwe Africa     1997    46.8 11404948      792.
4 Zimbabwe Africa     2002    40.0 11926563      672.
5 Zimbabwe Africa     2007    43.5 12311143      470.
Tip

Base R 함수인 tail()또한 같은 역할을 할 수 있다.

gapminder |> 
  tail(5)
# A tibble: 5 × 6
  country  continent  year lifeExp      pop gdpPercap
  <fct>    <fct>     <int>   <dbl>    <int>     <dbl>
1 Zimbabwe Africa     1987    62.4  9216418      706.
2 Zimbabwe Africa     1992    60.4 10704340      693.
3 Zimbabwe Africa     1997    46.8 11404948      792.
4 Zimbabwe Africa     2002    40.0 11926563      672.
5 Zimbabwe Africa     2007    43.5 12311143      470.

특정 열(변수)에 따라 값이 가장 큰 몇 개(n)의 행만을 골라낸다.

# 2007년에 gdpPercap이 가장 큰 5개국 찾기
gapminder |> 
  filter(year == 2007) |> # year이 2007인 행만 선택
  slice_max(gdpPercap, n = 5) # gdpPercap이 가장 큰 5개 행 선택
# A tibble: 5 × 6
  country       continent  year lifeExp       pop gdpPercap
  <fct>         <fct>     <int>   <dbl>     <int>     <dbl>
1 Norway        Europe     2007    80.2   4627926    49357.
2 Kuwait        Asia       2007    77.6   2505559    47307.
3 Singapore     Asia       2007    80.0   4553009    47143.
4 United States Americas   2007    78.2 301139947    42952.
5 Ireland       Europe     2007    78.9   4109086    40676.

특정 열(변수)에 따라 값이 가장 작은 것들 중 주어진 비중(prop)에 해당하는 행만을 골라낸다.

# 2007년 아시아에서 lifeExp가 하위 10%인 국가 찾기
gapminder |> 
  filter(year == 2007 & continent == "Asia") |> # year이 2007이고 continent가 Asia인 행만 선택
  slice_min(lifeExp, prop = 0.1) # lifeExp가 작은 순으로 10% 행만 선택
# A tibble: 3 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       2007    43.8 31889923      975.
2 Iraq        Asia       2007    59.5 27499638     4471.
3 Cambodia    Asia       2007    59.7 14131858     1714.

1.2.3 arrange() 함수

특정 열(변수)과 관련된 조건에 의거해 행의 순서를 바꾼다. 행의 길이에는 변화가 없다.

# lifeExp 오름차순으로 행 정렬
gapminder |> 
  arrange(lifeExp)
# A tibble: 1,704 × 6
   country      continent  year lifeExp     pop gdpPercap
   <fct>        <fct>     <int>   <dbl>   <int>     <dbl>
 1 Rwanda       Africa     1992    23.6 7290203      737.
 2 Afghanistan  Asia       1952    28.8 8425333      779.
 3 Gambia       Africa     1952    30    284320      485.
 4 Angola       Africa     1952    30.0 4232095     3521.
 5 Sierra Leone Africa     1952    30.3 2143249      880.
 6 Afghanistan  Asia       1957    30.3 9240934      821.
 7 Cambodia     Asia       1977    31.2 6978607      525.
 8 Mozambique   Africa     1952    31.3 6446316      469.
 9 Sierra Leone Africa     1957    31.6 2295678     1004.
10 Burkina Faso Africa     1952    32.0 4469979      543.
# ℹ 1,694 more rows

desc() 도우미 함수(helper function)는 내림차순으로 행을 배열한다.

# year 오름차순으로 행 정렬 후 같은 year 안에서 lifeExp 내림차순으로 행 정렬
gapminder |> 
  arrange(year, desc(lifeExp))
# A tibble: 1,704 × 6
   country        continent  year lifeExp      pop gdpPercap
   <fct>          <fct>     <int>   <dbl>    <int>     <dbl>
 1 Norway         Europe     1952    72.7  3327728    10095.
 2 Iceland        Europe     1952    72.5   147962     7268.
 3 Netherlands    Europe     1952    72.1 10381988     8942.
 4 Sweden         Europe     1952    71.9  7124673     8528.
 5 Denmark        Europe     1952    70.8  4334000     9692.
 6 Switzerland    Europe     1952    69.6  4815000    14734.
 7 New Zealand    Oceania    1952    69.4  1994794    10557.
 8 United Kingdom Europe     1952    69.2 50430000     9980.
 9 Australia      Oceania    1952    69.1  8691212    10040.
10 Canada         Americas   1952    68.8 14785584    11367.
# ℹ 1,694 more rows

1.2.4 distinct() 함수

특정 열(변수)에 의거해 중복이 없이 고유한 행만을 골라낸다. 행의 길이가 준다.

# country 변수 안에 어떤 값들이 포함되어 있는지 확인
gapminder |> 
  distinct(country)
# A tibble: 142 × 1
   country    
   <fct>      
 1 Afghanistan
 2 Albania    
 3 Algeria    
 4 Angola     
 5 Argentina  
 6 Australia  
 7 Austria    
 8 Bahrain    
 9 Bangladesh 
10 Belgium    
# ℹ 132 more rows

.keep_all 아규먼트를 이용하면 나머지 열도 함께 나타낼 수 있다. 같은 값을 가진 행이 다수 존재한다면 가장 앞선 행을 보여준다.

# continent 변수 안에 어떤 값들이 포함되어 있는지 나머지 열과 함께 확인
gapminder |> 
  distinct(continent, .keep_all = TRUE)
# A tibble: 5 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       1952    28.8  8425333      779.
2 Albania     Europe     1952    55.2  1282697     1601.
3 Algeria     Africa     1952    43.1  9279525     2449.
4 Argentina   Americas   1952    62.5 17876956     5911.
5 Australia   Oceania    1952    69.1  8691212    10040.

1.3 열 함수

1.3.1 select() 함수

열(변수)의 일부를 선택한다. 열의 길이가 준다.

# year, country, gdpPercap 변수만 선택
gapminder |> 
  select(year, country, gdpPercap)
# A tibble: 1,704 × 3
    year country     gdpPercap
   <int> <fct>           <dbl>
 1  1952 Afghanistan      779.
 2  1957 Afghanistan      821.
 3  1962 Afghanistan      853.
 4  1967 Afghanistan      836.
 5  1972 Afghanistan      740.
 6  1977 Afghanistan      786.
 7  1982 Afghanistan      978.
 8  1987 Afghanistan      852.
 9  1992 Afghanistan      649.
10  1997 Afghanistan      635.
# ℹ 1,694 more rows

열(변수)의 일부를 선택하지 않는다. 역시 열의 길이가 준다. 실질적으로 위와 동일하다.

# lifeExp, continent, pop을 제외한 변수들만 선택
gapminder |> 
  select(-c(lifeExp, continent, pop))
# A tibble: 1,704 × 3
   country      year gdpPercap
   <fct>       <int>     <dbl>
 1 Afghanistan  1952      779.
 2 Afghanistan  1957      821.
 3 Afghanistan  1962      853.
 4 Afghanistan  1967      836.
 5 Afghanistan  1972      740.
 6 Afghanistan  1977      786.
 7 Afghanistan  1982      978.
 8 Afghanistan  1987      852.
 9 Afghanistan  1992      649.
10 Afghanistan  1997      635.
# ℹ 1,694 more rows

starts_with(), ends_with(), contains()와 같은 도우미 함수를 잘 활용하면 효율적으로 필요한 변수만을 선정할 수 있다.

# 이름이 "c"로 시작하는 변수들만 선택
gapminder |> 
  select(starts_with("c"))
# A tibble: 1,704 × 2
   country     continent
   <fct>       <fct>    
 1 Afghanistan Asia     
 2 Afghanistan Asia     
 3 Afghanistan Asia     
 4 Afghanistan Asia     
 5 Afghanistan Asia     
 6 Afghanistan Asia     
 7 Afghanistan Asia     
 8 Afghanistan Asia     
 9 Afghanistan Asia     
10 Afghanistan Asia     
# ℹ 1,694 more rows

1.3.2 mutate() 함수

기존의 열(변수)에 기반하여 새로운 변수를 생성한다. 열의 길이가 는다.

# 새롭게 정의한 gdp_billion 변수 추가
gapminder |> 
  mutate(
    gdp_billion = gdpPercap * pop / 10^9
  )
# A tibble: 1,704 × 7
   country     continent  year lifeExp      pop gdpPercap gdp_billion
   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>       <dbl>
 1 Afghanistan Asia       1952    28.8  8425333      779.        6.57
 2 Afghanistan Asia       1957    30.3  9240934      821.        7.59
 3 Afghanistan Asia       1962    32.0 10267083      853.        8.76
 4 Afghanistan Asia       1967    34.0 11537966      836.        9.65
 5 Afghanistan Asia       1972    36.1 13079460      740.        9.68
 6 Afghanistan Asia       1977    38.4 14880372      786.       11.7 
 7 Afghanistan Asia       1982    39.9 12881816      978.       12.6 
 8 Afghanistan Asia       1987    40.8 13867957      852.       11.8 
 9 Afghanistan Asia       1992    41.7 16317921      649.       10.6 
10 Afghanistan Asia       1997    41.8 22227415      635.       14.1 
# ℹ 1,694 more rows

여러개의 변수를 동시에 생성할 수 있다. row_number() 도우미 함수는 값에 순위를 부여하는 것이고, .keep = "used"는 결과에 변수 생성에 동원된 변수만을 포함시키게 해 준다.

gapminder |> 
  filter(year == 2007) |> # year이 2007인 행만 선택 
  mutate(
    gdpPercap_rank = row_number(gdpPercap), # gdpPercap에 순위 부여
    lifeExp_highlow = lifeExp > 30, # lifeExp가 30을 넘으면 TRUE, 아니면 FALSE
    .keep = "used" # 새로운 변수 생성에 사용한 변수들만 표시
  )
# A tibble: 142 × 4
   lifeExp gdpPercap gdpPercap_rank lifeExp_highlow
     <dbl>     <dbl>          <int> <lgl>          
 1    43.8      975.             19 TRUE           
 2    76.4     5937.             70 TRUE           
 3    72.3     6223.             72 TRUE           
 4    42.7     4797.             64 TRUE           
 5    75.3    12779.            101 TRUE           
 6    81.2    34435.            130 TRUE           
 7    79.8    36126.            132 TRUE           
 8    75.6    29796.            122 TRUE           
 9    64.1     1391.             30 TRUE           
10    79.4    33693.            128 TRUE           
# ℹ 132 more rows

1.3.3 rename() 함수

변수의 이름을 바꾼다. 열의 길이에는 변화가 없다.

# 변수의 이름 변경
gapminder |> 
  rename(
    gdp_percap = gdpPercap,
    left_exp = lifeExp
  )
# A tibble: 1,704 × 6
   country     continent  year left_exp      pop gdp_percap
   <fct>       <fct>     <int>    <dbl>    <int>      <dbl>
 1 Afghanistan Asia       1952     28.8  8425333       779.
 2 Afghanistan Asia       1957     30.3  9240934       821.
 3 Afghanistan Asia       1962     32.0 10267083       853.
 4 Afghanistan Asia       1967     34.0 11537966       836.
 5 Afghanistan Asia       1972     36.1 13079460       740.
 6 Afghanistan Asia       1977     38.4 14880372       786.
 7 Afghanistan Asia       1982     39.9 12881816       978.
 8 Afghanistan Asia       1987     40.8 13867957       852.
 9 Afghanistan Asia       1992     41.7 16317921       649.
10 Afghanistan Asia       1997     41.8 22227415       635.
# ℹ 1,694 more rows

패밀리 함수인 rename_with()를 이용하면 다른 것도 가능하다.

# "l"로 시작하는 변수를 전부 소문자로 변경
gapminder |> 
  rename_with(
    tolower, starts_with("l")
  )
# A tibble: 1,704 × 6
   country     continent  year lifeexp      pop gdpPercap
   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
 1 Afghanistan Asia       1952    28.8  8425333      779.
 2 Afghanistan Asia       1957    30.3  9240934      821.
 3 Afghanistan Asia       1962    32.0 10267083      853.
 4 Afghanistan Asia       1967    34.0 11537966      836.
 5 Afghanistan Asia       1972    36.1 13079460      740.
 6 Afghanistan Asia       1977    38.4 14880372      786.
 7 Afghanistan Asia       1982    39.9 12881816      978.
 8 Afghanistan Asia       1987    40.8 13867957      852.
 9 Afghanistan Asia       1992    41.7 16317921      649.
10 Afghanistan Asia       1997    41.8 22227415      635.
# ℹ 1,694 more rows

1.3.4 relocate() 함수

변수의 위치를 바꾼다. 열의 길이에는 변화가 없다.

# year 변수의 위치를 continent 앞으로 이동
gapminder |> 
  relocate(year, continent)
# A tibble: 1,704 × 6
    year continent country     lifeExp      pop gdpPercap
   <int> <fct>     <fct>         <dbl>    <int>     <dbl>
 1  1952 Asia      Afghanistan    28.8  8425333      779.
 2  1957 Asia      Afghanistan    30.3  9240934      821.
 3  1962 Asia      Afghanistan    32.0 10267083      853.
 4  1967 Asia      Afghanistan    34.0 11537966      836.
 5  1972 Asia      Afghanistan    36.1 13079460      740.
 6  1977 Asia      Afghanistan    38.4 14880372      786.
 7  1982 Asia      Afghanistan    39.9 12881816      978.
 8  1987 Asia      Afghanistan    40.8 13867957      852.
 9  1992 Asia      Afghanistan    41.7 16317921      649.
10  1997 Asia      Afghanistan    41.8 22227415      635.
# ℹ 1,694 more rows

.before.after 아규먼트를 사용하여 해당 변수를 어떤 변수의 앞이나 뒤로 보낼 수 있다.

# pop 변수의 위치를 lifeExp 앞으로 이동
gapminder |> 
  relocate(pop, .before = lifeExp )
# A tibble: 1,704 × 6
   country     continent  year      pop lifeExp gdpPercap
   <fct>       <fct>     <int>    <int>   <dbl>     <dbl>
 1 Afghanistan Asia       1952  8425333    28.8      779.
 2 Afghanistan Asia       1957  9240934    30.3      821.
 3 Afghanistan Asia       1962 10267083    32.0      853.
 4 Afghanistan Asia       1967 11537966    34.0      836.
 5 Afghanistan Asia       1972 13079460    36.1      740.
 6 Afghanistan Asia       1977 14880372    38.4      786.
 7 Afghanistan Asia       1982 12881816    39.9      978.
 8 Afghanistan Asia       1987 13867957    40.8      852.
 9 Afghanistan Asia       1992 16317921    41.7      649.
10 Afghanistan Asia       1997 22227415    41.8      635.
# ℹ 1,694 more rows

1.4 그룹 함수

1.4.1 group_by() 함수

특정 범주 열(변수)에 의거해 행을 분할한다. 행의 길이는 변하지 않는다.

하나의 범주 변수에 의거해 그룹화한다. 산출물을 보면 year에 의거해 행이 12개의 그룹으로 나누어졌음이 나타나 있다(두 번째 줄: Group: year [12]).

# year을 기준으로 그룹화
gapminder |> 
  group_by(year)
# A tibble: 1,704 × 6
# Groups:   year [12]
   country     continent  year lifeExp      pop gdpPercap
   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
 1 Afghanistan Asia       1952    28.8  8425333      779.
 2 Afghanistan Asia       1957    30.3  9240934      821.
 3 Afghanistan Asia       1962    32.0 10267083      853.
 4 Afghanistan Asia       1967    34.0 11537966      836.
 5 Afghanistan Asia       1972    36.1 13079460      740.
 6 Afghanistan Asia       1977    38.4 14880372      786.
 7 Afghanistan Asia       1982    39.9 12881816      978.
 8 Afghanistan Asia       1987    40.8 13867957      852.
 9 Afghanistan Asia       1992    41.7 16317921      649.
10 Afghanistan Asia       1997    41.8 22227415      635.
# ℹ 1,694 more rows

두 개 이상의 범주 변수에 의거해 그룹화할 수도 있다.

# year, continent를 기준으로 그룹화
gapminder |> 
  group_by(year, continent)
# A tibble: 1,704 × 6
# Groups:   year, continent [60]
   country     continent  year lifeExp      pop gdpPercap
   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
 1 Afghanistan Asia       1952    28.8  8425333      779.
 2 Afghanistan Asia       1957    30.3  9240934      821.
 3 Afghanistan Asia       1962    32.0 10267083      853.
 4 Afghanistan Asia       1967    34.0 11537966      836.
 5 Afghanistan Asia       1972    36.1 13079460      740.
 6 Afghanistan Asia       1977    38.4 14880372      786.
 7 Afghanistan Asia       1982    39.9 12881816      978.
 8 Afghanistan Asia       1987    40.8 13867957      852.
 9 Afghanistan Asia       1992    41.7 16317921      649.
10 Afghanistan Asia       1997    41.8 22227415      635.
# ℹ 1,694 more rows

1.4.2 summarize() 함수

주어진 열(변수)에 대한 통계 요약값을 계산하고 그것으로 이루어진 새로운 데이터 프레임을 생성한다. 엄밀히 말해 기존 열(변수)을 변형한다기 보다는 기존 데이터 프레임으로부터 새로운 데이터 프레임을 생성한다고 볼 수 있다. summarize() 함수는 대부분의 경우 group_by() 함수와 함께 사용된다. 다음의 둘을 비교해 보라.

# 2007년 전체의 gdpPercap의 평균 계산
gapminder |> 
  filter(year == 2007) |> # year이 2007인 행만 선택
  summarize(mean_gdpPercap = mean(gdpPercap)) # gdpPercap의 평균값 계산
# A tibble: 1 × 1
  mean_gdpPercap
           <dbl>
1         11680.
# 2007년 대륙별 gdpPercap의 평균 계산
gapminder |> 
  filter(year == 2007) |> # year이 2007인 행만 선택
  group_by(continent) |>  # continent를 기준으로 그룹화
  summarize(mean_gdpPercap = mean(gdpPercap)) # gdpPercap의 평균값 계산
# A tibble: 5 × 2
  continent mean_gdpPercap
  <fct>              <dbl>
1 Africa             3089.
2 Americas          11003.
3 Asia              12473.
4 Europe            25054.
5 Oceania           29810.

좀 더 복잡한 확장이 가능하다. 마지막의 n()은 자동으로 생성되는 그룹별 빈도값이다.

# continent, year별로 다양한 통계값 산출
gapminder |> 
    group_by(continent, year) |> # continent, year을 기준으로 그룹화
    summarize(
      mean_gdpPercap = mean(gdpPercap), # gdpPercap의 평균값 계산
      sd_gdpPercap = sd(gdpPercap), # gdpPercap의 표준편차 계산
      mean_pop = mean(pop), # pop의 평균값 계산
      sd_pop = sd(pop), # pop의 표준편차 계산
      n = n() # 그룹에 포함된 행의 개수
      )
# A tibble: 60 × 7
# Groups:   continent [5]
   continent  year mean_gdpPercap sd_gdpPercap  mean_pop    sd_pop     n
   <fct>     <int>          <dbl>        <dbl>     <dbl>     <dbl> <int>
 1 Africa     1952          1253.         983.  4570010.  6317450.    52
 2 Africa     1957          1385.        1135.  5093033.  7076042.    52
 3 Africa     1962          1598.        1462.  5702247.  7957545.    52
 4 Africa     1967          2050.        2848.  6447875.  8985505.    52
 5 Africa     1972          2340.        3287.  7305376. 10130833.    52
 6 Africa     1977          2586.        4142.  8328097. 11585184.    52
 7 Africa     1982          2482.        3243.  9602857. 13456243.    52
 8 Africa     1987          2283.        2567. 11054502. 15277484.    52
 9 Africa     1992          2282.        2644. 12674645. 17562719.    52
10 Africa     1997          2379.        2821. 14304480. 19873013.    52
# ℹ 50 more rows

group_by()arrange()를 결합하는 경우, .by_group = TRUE를 하면 그룹별로 행을 배열할 수 있다.

# year, continent 그룹별로 gdpPercap 내림차순 정렬
gapminder |> 
  group_by(year, continent) |> # year, continent를 기준으로 그룹화
  arrange(desc(gdpPercap), .by_group = TRUE) # 그룹별로 gdpPercap 내림차순 정렬
# A tibble: 1,704 × 6
# Groups:   year, continent [60]
   country      continent  year lifeExp      pop gdpPercap
   <fct>        <fct>     <int>   <dbl>    <int>     <dbl>
 1 South Africa Africa     1952    45.0 14264935     4725.
 2 Gabon        Africa     1952    37.0   420702     4293.
 3 Angola       Africa     1952    30.0  4232095     3521.
 4 Reunion      Africa     1952    52.7   257700     2719.
 5 Djibouti     Africa     1952    34.8    63149     2670.
 6 Algeria      Africa     1952    43.1  9279525     2449.
 7 Namibia      Africa     1952    41.7   485831     2424.
 8 Libya        Africa     1952    42.7  1019729     2388.
 9 Congo, Rep.  Africa     1952    42.1   854885     2126.
10 Mauritius    Africa     1952    51.0   516556     1968.
# ℹ 1,694 more rows

.by_group = TRUE를 붙이지 않으면 그냥 arrange()를 사용한 것과 같은 결과가 나온다.

# .by_group = TRUE를 사용하지 않은 경우
gapminder |> 
  group_by(year, continent) |> 
  arrange(desc(gdpPercap))
# A tibble: 1,704 × 6
# Groups:   year, continent [60]
   country   continent  year lifeExp     pop gdpPercap
   <fct>     <fct>     <int>   <dbl>   <int>     <dbl>
 1 Kuwait    Asia       1957    58.0  212846   113523.
 2 Kuwait    Asia       1972    67.7  841934   109348.
 3 Kuwait    Asia       1952    55.6  160000   108382.
 4 Kuwait    Asia       1962    60.5  358266    95458.
 5 Kuwait    Asia       1967    64.6  575003    80895.
 6 Kuwait    Asia       1977    69.3 1140357    59265.
 7 Norway    Europe     2007    80.2 4627926    49357.
 8 Kuwait    Asia       2007    77.6 2505559    47307.
 9 Singapore Asia       2007    80.0 4553009    47143.
10 Norway    Europe     2002    79.0 4535591    44684.
# ℹ 1,694 more rows
gapminder |> 
  arrange(desc(gdpPercap))
# A tibble: 1,704 × 6
   country   continent  year lifeExp     pop gdpPercap
   <fct>     <fct>     <int>   <dbl>   <int>     <dbl>
 1 Kuwait    Asia       1957    58.0  212846   113523.
 2 Kuwait    Asia       1972    67.7  841934   109348.
 3 Kuwait    Asia       1952    55.6  160000   108382.
 4 Kuwait    Asia       1962    60.5  358266    95458.
 5 Kuwait    Asia       1967    64.6  575003    80895.
 6 Kuwait    Asia       1977    69.3 1140357    59265.
 7 Norway    Europe     2007    80.2 4627926    49357.
 8 Kuwait    Asia       2007    77.6 2505559    47307.
 9 Singapore Asia       2007    80.0 4553009    47143.
10 Norway    Europe     2002    79.0 4535591    44684.
# ℹ 1,694 more rows

아래는 연도별/대륙별로 일인당 GDP가 가장 높은 국가를 추출한 것이다. 코드를 생각해 보라. Code를 누르면 답을 확인할 수 있다.

# A tibble: 60 × 6
# Groups:   year, continent [60]
   country       continent  year lifeExp       pop gdpPercap
   <fct>         <fct>     <int>   <dbl>     <int>     <dbl>
 1 South Africa  Africa     1952    45.0  14264935     4725.
 2 United States Americas   1952    68.4 157553000    13990.
 3 Kuwait        Asia       1952    55.6    160000   108382.
 4 Switzerland   Europe     1952    69.6   4815000    14734.
 5 New Zealand   Oceania    1952    69.4   1994794    10557.
 6 South Africa  Africa     1957    48.0  16151549     5487.
 7 United States Americas   1957    69.5 171984000    14847.
 8 Kuwait        Asia       1957    58.0    212846   113523.
 9 Switzerland   Europe     1957    70.6   5126000    17909.
10 New Zealand   Oceania    1957    70.3   2229407    12247.
# ℹ 50 more rows
gapminder |> 
  group_by(year, continent) |> 
  slice_max(gdpPercap)

group_by() 함수가 한 번 적용되면, 그 뒤의 모든 오퍼레이션에 그룹 분할이 적용되기 때문에 예기치 못한 일이 발생할 수 있다. 이것을 회피하기 위해 두 가지 옵션이 있다. 첫번째 방법은 마지막에 upgroup() 함수를 첨가하는 것이다.

# 그룹화를 해제하고 year, continent별로 gdpPercap이 가장 높은 국가 선택
gapminder |> 
  group_by(year, continent) |> # year, continent를 기준으로 그룹화
  slice_max(gdpPercap) |> # 그룹에서 gdpPercap이 가장 높은 행 선택
  ungroup() # 그룹화 해제
# A tibble: 60 × 6
   country       continent  year lifeExp       pop gdpPercap
   <fct>         <fct>     <int>   <dbl>     <int>     <dbl>
 1 South Africa  Africa     1952    45.0  14264935     4725.
 2 United States Americas   1952    68.4 157553000    13990.
 3 Kuwait        Asia       1952    55.6    160000   108382.
 4 Switzerland   Europe     1952    69.6   4815000    14734.
 5 New Zealand   Oceania    1952    69.4   1994794    10557.
 6 South Africa  Africa     1957    48.0  16151549     5487.
 7 United States Americas   1957    69.5 171984000    14847.
 8 Kuwait        Asia       1957    58.0    212846   113523.
 9 Switzerland   Europe     1957    70.6   5126000    17909.
10 New Zealand   Oceania    1957    70.3   2229407    12247.
# ℹ 50 more rows

두 번째 방법은 group_by() 함수 대신 by 아규먼트를 사용하는 것이다. 결과가 달라보이겠지만 정렬의 차이일 뿐 동일하다.

# year, continent 기준으로 gdpPercap이 가장 높은 행 선택
gapminder |> 
  slice_max(gdpPercap, by = c(year, continent)) 
# A tibble: 60 × 6
   country      continent  year lifeExp      pop gdpPercap
   <fct>        <fct>     <int>   <dbl>    <int>     <dbl>
 1 Kuwait       Asia       1952    55.6   160000   108382.
 2 Kuwait       Asia       1957    58.0   212846   113523.
 3 Kuwait       Asia       1962    60.5   358266    95458.
 4 Kuwait       Asia       1967    64.6   575003    80895.
 5 Kuwait       Asia       1972    67.7   841934   109348.
 6 Kuwait       Asia       1977    69.3  1140357    59265.
 7 Saudi Arabia Asia       1982    63.0 11254672    33693.
 8 Kuwait       Asia       1987    74.2  1891487    28118.
 9 Kuwait       Asia       1992    75.2  1418095    34933.
10 Kuwait       Asia       1997    76.2  1765345    40301.
# ℹ 50 more rows

1.4.3 count() 함수

특정 범주 열(변수)에 의거한 빈도를 빠르게 계산해 준다.

# year, continent별 행 개수 계산
gapminder |> 
  count(year, continent)
# A tibble: 60 × 3
    year continent     n
   <int> <fct>     <int>
 1  1952 Africa       52
 2  1952 Americas     25
 3  1952 Asia         33
 4  1952 Europe       30
 5  1952 Oceania       2
 6  1957 Africa       52
 7  1957 Americas     25
 8  1957 Asia         33
 9  1957 Europe       30
10  1957 Oceania       2
# ℹ 50 more rows

wt 아규먼트를 사용하면 빈도가 아니라 범주별 특정 변수의 합산값을 구할 수 있다.

# year, continent별 pop의 합산값 계산
gapminder |> 
  count(year, continent, wt = pop)
# A tibble: 60 × 3
    year continent          n
   <int> <fct>          <dbl>
 1  1952 Africa     237640501
 2  1952 Americas   345152446
 3  1952 Asia      1395357351
 4  1952 Europe     418120846
 5  1952 Oceania     10686006
 6  1957 Africa     264837738
 7  1957 Americas   386953916
 8  1957 Asia      1562780599
 9  1957 Europe     437890351
10  1957 Oceania     11941976
# ℹ 50 more rows

위의 두 개를 한 번에 실행할 수 있다.

gapminder |> 
  group_by(year, continent) |> # year, continent를 기준으로 그룹화
  summarize(
    n = n(), # 그룹별 행 개수 계산
    sum_pop = sum(pop) # 그룹별 pop의 합산값 계산
  )
# A tibble: 60 × 4
# Groups:   year [12]
    year continent     n    sum_pop
   <int> <fct>     <int>      <dbl>
 1  1952 Africa       52  237640501
 2  1952 Americas     25  345152446
 3  1952 Asia         33 1395357351
 4  1952 Europe       30  418120846
 5  1952 Oceania       2   10686006
 6  1957 Africa       52  264837738
 7  1957 Americas     25  386953916
 8  1957 Asia         33 1562780599
 9  1957 Europe       30  437890351
10  1957 Oceania       2   11941976
# ℹ 50 more rows

1.4.4 across() 함수

다수의 열(변수)에 동일한 함수를 적용할 수 있다.

# lifeExp와 gdpPercap을 반올림
gapminder |> 
  mutate(
    across(c(lifeExp, gdpPercap), round)
  )
# A tibble: 1,704 × 6
   country     continent  year lifeExp      pop gdpPercap
   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
 1 Afghanistan Asia       1952      29  8425333       779
 2 Afghanistan Asia       1957      30  9240934       821
 3 Afghanistan Asia       1962      32 10267083       853
 4 Afghanistan Asia       1967      34 11537966       836
 5 Afghanistan Asia       1972      36 13079460       740
 6 Afghanistan Asia       1977      38 14880372       786
 7 Afghanistan Asia       1982      40 12881816       978
 8 Afghanistan Asia       1987      41 13867957       852
 9 Afghanistan Asia       1992      42 16317921       649
10 Afghanistan Asia       1997      42 22227415       635
# ℹ 1,694 more rows

이것은 다음과 동일하다.

gapminder |> 
  mutate(
    lifeExp = round(lifeExp), # lifeExp를 반올림한 새로운 변수 생성
    gdpPercap = round(gdpPercap) # gdpPercap을 반올림한 새로운 변수 생성
  )
# A tibble: 1,704 × 6
   country     continent  year lifeExp      pop gdpPercap
   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
 1 Afghanistan Asia       1952      29  8425333       779
 2 Afghanistan Asia       1957      30  9240934       821
 3 Afghanistan Asia       1962      32 10267083       853
 4 Afghanistan Asia       1967      34 11537966       836
 5 Afghanistan Asia       1972      36 13079460       740
 6 Afghanistan Asia       1977      38 14880372       786
 7 Afghanistan Asia       1982      40 12881816       978
 8 Afghanistan Asia       1987      41 13867957       852
 9 Afghanistan Asia       1992      42 16317921       649
10 Afghanistan Asia       1997      42 22227415       635
# ℹ 1,694 more rows
Tip

새롭게 생성한 변수가 기존 변수와 이름이 같은 경우 기존 변수를 대체한다.

summarize() 함수와 결합하여 선택된 변수에 특정 함수를 적용하고 그 결과의 이름을 변수명과 함수명을 사용하여 부여할 수 있다.

gapminder |> 
  group_by(year, continent) |> # year, continent를 기준으로 그룹화
  summarize(across(c(lifeExp, gdpPercap), mean, # lifeExp와 gdpPercap의 평균값 계산
                   .names = "mean_{.col}")) # 새롭게 생성된 변수의 이름을 "mean_기존 이름"으로 설정
# A tibble: 60 × 4
# Groups:   year [12]
    year continent mean_lifeExp mean_gdpPercap
   <int> <fct>            <dbl>          <dbl>
 1  1952 Africa            39.1          1253.
 2  1952 Americas          53.3          4079.
 3  1952 Asia              46.3          5195.
 4  1952 Europe            64.4          5661.
 5  1952 Oceania           69.3         10298.
 6  1957 Africa            41.3          1385.
 7  1957 Americas          56.0          4616.
 8  1957 Asia              49.3          5788.
 9  1957 Europe            66.7          6963.
10  1957 Oceania           70.3         11599.
# ℹ 50 more rows

1.4.5 c_across() 함수

group_by() 함수와 across() 함수가 결합하는 것과 정반대로, rowwise() 함수와 c_across() 함수를 결합하며, 행별 통계값을 산출할 수 있다. 물론 여기서 sd 값은 아무런 의미가 없다.

gapminder |> 
  rowwise() |> # 행별로 그룹화
  mutate(
    sd = sd(c_across(where(is.numeric))) # 각 행에서 숫자인 값들의 표준편차 계산
  )
# A tibble: 1,704 × 7
# Rowwise: 
   country     continent  year lifeExp      pop gdpPercap        sd
   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>     <dbl>
 1 Afghanistan Asia       1952    28.8  8425333      779.  4212207.
 2 Afghanistan Asia       1957    30.3  9240934      821.  4619999.
 3 Afghanistan Asia       1962    32.0 10267083      853.  5133067.
 4 Afghanistan Asia       1967    34.0 11537966      836.  5768510.
 5 Afghanistan Asia       1972    36.1 13079460      740.  6539272.
 6 Afghanistan Asia       1977    38.4 14880372      786.  7439719.
 7 Afghanistan Asia       1982    39.9 12881816      978.  6440408.
 8 Afghanistan Asia       1987    40.8 13867957      852.  6933499.
 9 Afghanistan Asia       1992    41.7 16317921      649.  8158513.
10 Afghanistan Asia       1997    41.8 22227415      635. 11113262.
# ℹ 1,694 more rows

2 데이터 시각화

실습을 위해 ggplot2 패키지 속에 포함되어 있는 mpg 데이터와 diamonds 데이터를 사용한다. 각 데이터에서 눈여겨 볼 변수는 아래와 같다.

  • mpg

    • displ : 배기량(displacement)

    • class : 자동차 유형(compact/midsize/suv/2seater/minivan/pickup/subcompact)

    • hwy : 고속도로(highway) 연비

  • diamonds

    • price : 가격

    • carat : 캐럿

    • color : 다이아몬드 색깔

    • clarity : 투명도

    • cut : 가공의 품질

2.1 핵심 레이어: 심미성과 기하

이 실습은 R로 데이터사이언스를 하는 과정 중 데이터 시각화하기(visualizing)를 다룬다. 데이터 시각화하기는 tidyverse의 핵심 패키지 중의 하나인 ggplot2 에서 제공된다.

ggplot2의 gg가 ’그래프의 문법(grammar of graphic)’을 의미하는 것에서 알 수 있는 것처럼, ggplot2는 그래프 제작의 일반 원리를 정교하게 구현하기 위해 만들어졌다. 아래 그림에서 볼 수 있듯, 모든 그래프는 8개의 주요 구성요소로 이루어져 있고, ggplot2는 각각의 구성요소를 마치 레이어(layer)를 쌓는 것과 같은 방식으로 구현한다(+ 사인을 이용).

이 8개 구성요소 중 가장 중요한 것은 ‘심미성(aesthetics)’ 혹은 ‘심미성 매핑(aesthetic mapping)’와 ’기하(geometries)’ 혹은 ’기하 객체(geometric objects)’이다. 기하가 그래프의 전체 구조 혹은 형식을 규정하는 것이라면, 심미성은 기하의 외견을 규정한다. 결국 기하는 그래프의 유형(예: 막대 그래프, 산포도 등)과 관련되고, 심미성은 그래프의 시각적 속성(예: x축, y축, 컬러, 크기, 모양 등)과 관련된다. 이 두 가지는 독립적인 요소이지만, 어느 정도는 관련되어 있기도 하다. 모든 기하가 모든 심미성과 결합할 수 있는 것은 아니다. 특정한 기하는 오로지 특정한 심미성과만 결합한다. 예를 들어 포인트 기하 객체(geom_point())는 크기(size) 심미성과 관련되지만, 라인 기하 객체(geom_line())는 크기 심미성과는 관련되지 않고 라인폭(linewidth) 심미성과만 관련되는 식이다.

2.1.1 기초 예제

그래프를 그리기 위해 반드시 필요한 것은 데이터, 심미성, 기하이다. 이들을 차례로 하나씩 추가해본다.

Figure 1 를 보면, 빈 화면만 출력되는 것을 볼 수 있다. 데이터만 올라왔으므로, 그릴 수 있는 것이 없다. Figure 2 에서는 x축과 y축이 나타났다. 그러나 이 재료를 가지고 무슨 그래프를 그릴지는 지정하지 않았으므로 아무 그래프도 나타나지 않는다. Figure 3 에서야 비로소 그래프가 나타나는데, 이는 어떤 데이터로부터 어떤 변수를 사용할지, 그리고 그것을 어떤 방식으로 그릴지를 모두 지정해주었기 때문이다.

# 데이터만 추가
ggplot(data=mpg)
Figure 1: 데이터만 추가
ggplot(data=mpg, aes(x=displ, y=hwy))
Figure 2: 데이터 + 심미성(x, y축)
ggplot(data=mpg, aes(x=displ, y=hwy)) +
  geom_point()
Figure 3: 데이터 + 심미성(x, y축) + 기하

2.1.2 심미성 매핑

심미성 매핑이란 다양한 시각적 속성 혹은 재료를 그래프에 적용 혹은 부여하는 과정을 의미한다. displhwy의 관계가 class에 따라 어떻게 달라지는지를 시각화한다. 다음의 두 그래프를 비교해 본다.

ggplot(mpg, aes(x = displ, y = hwy, color = class)) +
  geom_point()
Figure 4: 심미성: 컬러
ggplot(mpg, aes(x = displ, y = hwy, shape = class)) +
  geom_point()
Figure 5: 심미성: 형태

Figure 4Figure 5 중 어느 것이 더 효과적인 시각화라고 생각하는가? 컬러(color)와 형태(shape)라는 심미성 요소 외에 크기(size)와 투명도(alpha) 요소를 동일한 데이터에 적용해 본다.

ggplot(mpg, aes(x = displ, y = hwy, size = class)) +
  geom_point()
Figure 6: 심미성: 크기
ggplot(mpg, aes(x = displ, y = hwy, alpha = class)) +
  geom_point()
Figure 7: 심미성: 투명도

크기와 투명도는 양적인 차이를 나타내는데 적합한 심미성이기 때문에 class라는 정성적인 범주의 차이를 보여주는데는 적합하지 않다. 심미성 부여에서 가장 중요한 것은 결국 얼마나 적절한 심미성 요소, 혹은 시각 변수(visual variables)를 선택하느냐에 달려 있다.

2.1.3 기하 객체

Figure 8Figure 9 이 다르게 보이는 것은 기하 객체가 하나는 포인트(point)이고 다른 하나는 완만한 선(smooth)이기 때문이다.

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point()
Figure 8: 기하: geom_point()
ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_smooth()
Figure 9: 기하: geom_smooth()

Figure 4Figure 9 두 개를 결합해 본다.

ggplot(mpg, aes(x = displ, y = hwy, color = class)) + 
  geom_point() + 
  geom_smooth()
Figure 10: 기하: geom_point() + geom_smooth() 1

원하는 것이 아니다. 왜 이런 결과가 나왔으며, 어떻게 하면 원하는 것을 얻을 수 있을지 생각해 본다.

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point(aes(color = class)) + 
  geom_smooth()
Figure 11: 기하: geom_point() + geom_smooth() 2

두 결과의 차이는 color 심미성을 글로벌하게 적용하느냐 로컬하게 적용하느냐(포인트 기하에만 적용)에 달린 것이다. 글로벌한 심미성은 ggplot()속에서 설정하고, 국지적인 심미성은 개별 기하(geom_point()) 속에서 설정한다. 매우 중요한 사항이니 꼭 기억하도록 한다.

다양한 기하 객체는 동일한 데이터를 다양한 방식으로 탐색할 수 있게 해준다. 다음의 세가지 기하 객체는 탐색적 데이터 분석에서 널리 사용되는 것이다.

ggplot(mpg, aes(x = hwy)) +
  geom_histogram(binwidth = 2)
Figure 12: 기하: geom_histogram()
ggplot(mpg, aes(x = hwy)) +
  geom_density()
Figure 13: 기하: geom_density()
ggplot(mpg, aes(x = hwy)) +
  geom_boxplot()
Figure 14: 기하: geom_boxplot()

2.2 다른 레이어

2.2.1 스케일

스케일(scales)은 심미성이 구체적으로 어떻게 구현될지를 결정한다. 예를 들어 color 심미성이 적용되었다 하더라도 어떤 색상이 선정되어 어떻게 배열되는지에 따라 최종 그래프의 모습은 매우 달라질 수 있다. 그래프를 다시 나타낸다. 스케일이 어느 부분에 어떻게 적용되었는지 생각해 본다.

ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point() 
Figure 15

Figure 15 은 아래에서 보는 것처럼, ggplot2가 자동적으로 적용한 세 가지의 스케일 설정에 의거해 만들어진 것이다.

ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point() +
  scale_x_continuous() +
  scale_y_continuous() +
  scale_color_discrete()

수정하여 다음과 같이 적용할 수 있다. scale 함수의 아규먼트가 어떤 역할을 하는지 생각해 본다.

ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point() +
  scale_x_continuous(labels = NULL) +
  scale_y_continuous(breaks = seq(15, 40, by = 5)) +
  scale_color_brewer(palette = "Set1", labels = c("4" = "4-wheel", "f" = "front", "r" = "rear"))
Figure 16

scale_color_brewer() 함수는 ColorBrewer 컬러 스케일을 사용한 것인데 익히고 있으면 많은 도움이 된다. 살펴보면 양적인 변수에 적용하기 좋은 팔레트가 있고, 질적인 변수에 적용하기 좋은 팔레트도 있다. 한번 마음에 드는 팔레트를 골라보자.

par(mar=c(0.1, 3, 0.1, 1))
display.brewer.all()

직접 색상 지정하는 방법

한편, 내가 원하는 색상을 골라 직접 지정하는 방법도 있다. scale_color_brewer() 대신 scale_color_manual() 함수를 사용하면 된다. 또한 RGB 색상에 대한 html 코드를 사용해도 되고, R에서 부여한 657개의 이름 중에서 골라 사용해도 된다. 색상 이름 및 html 코드는 다음 사이트를 참고하라.

ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point() +
  scale_x_continuous(labels = NULL) +
  scale_y_continuous(breaks = seq(15, 40, by = 5)) +
  scale_color_manual(values = c("sienna1", "slateblue4", "#698B22"))

2.2.2 패싯

패싯(facets) 레이어는 다면생성(faceting) 과정을 통해 하나의 플롯을 여러개의 하위 플롯으로 쪼갬으로서 생성된다.

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point() + 
  facet_wrap(~cyl)
Figure 17: 패싯: facet_wrap()

두 개의 변수에 의거해 패싯을 생성할 수도 있다.

ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point() + 
  facet_grid(drv ~ cyl)
Figure 18: 패싯: facet_grid()

2.2.3 통계적 변환

어떤 시각화 과정은 필연적으로 통계적 변환(statistical transformation)을 수반한다.

ggplot(diamonds, aes(x = cut)) + 
  geom_bar()
Figure 19: 통계적 변환: geom_bar()

after_stat()이라고 하는 도우미 함수를 사용하면, 이 그래프를 절대 빈도가 아닌 상대 빈도 그래프로 변환할 수 있다. 이 역시 통계적 변환 과정이 숨어 있는 것이다.

ggplot(diamonds, aes(x = cut, y = after_stat(prop), group = 1)) + 
  geom_bar()
Figure 20: 통계적 변환: geom_bar()
geom_bar() vs. geom_col()

geom_bar() 함수는 자동으로 통계적 변환을 한 후 결과를 반환한다. 그래서 y축을 지정하지 않아도 된다. 반면 geom_col() 함수는 마찬가지로 막대그래프이지만, x축과 그에 상응하는 y축의 값을 바탕으로 그래프를 생성한다. 아래의 두 코드를 살펴보고 결과를 비교해보자. geom_bar()의 통계적 변환이 무엇을 한 것인지, 두 함수의 차이가 무엇이지 이해할 수 있을 것이다. 참고로 두 번째 코드는 지난 실습 때 다룬 count() 함수를 활용한 것이다. 다른 코드이지만 동일한 결과가 출력됨됨을 알 수 있다.

diamonds |> 
  ggplot(aes(x = cut)) +
  geom_bar()

diamonds |>
  count(cut) |> 
  ggplot(aes(x = cut, y = n)) +
  geom_col()

막대 그래프에 심미성을 가미하고, position 아규먼트를 통한 위치 조정(position adjustment)을 시도한다.

ggplot(mpg, aes(x = drv, fill = class)) + 
  geom_bar()
Figure 21: position: stack

기하 객체에 색상을 지정하고 싶을 때, 0차원(point)과 1차원(line) 객체에는 color라는 심미성을 적용하지만, 막대 그래프와 같은 2차원(area) 객체에는 fill이라는 심미성을 적용한다. 자주 혼돈이 되는 부분이다.

위치 조정을 위해 position 아규먼트를 사용하는데, 네 가지 옵션이 있다.

  • position = "stack"

  • position = "identity"

  • position = "dodge"

  • position = "fill"

Figure 21 에는 디폴트로 position = "stack"이 적용된 것이다. Figure 22position = "dodge"를 적용한 것이다 . 나머지 옵션도 적용해보고 차이가 무엇인지 알아본다.

ggplot(mpg, aes(x = drv, fill = class)) + 
  geom_bar(position = "dodge")
Figure 22: position: dodge
position 아규먼트의 종류와 차이

position 아규먼트에 따른 차이는 아래의 그래프가 잘 보여준다. patchwork도 활용했다.

g1 <- ggplot(mpg, aes(x = drv, fill = class)) + 
  geom_bar(position = "stack") +
  labs(title = "'stack' graph")

g2 <- ggplot(mpg, aes(x = drv, fill = class)) + 
  geom_bar(position = "dodge") +
  labs(title = "'dodge' graph")

g3 <- ggplot(mpg, aes(x = drv, fill = class)) + 
  geom_bar(position = "fill") +
  labs(title = "'fill' graph")

g4 <- ggplot(mpg, aes(x = drv, fill = class)) + 
  geom_bar(position = "identity") +
  labs(title = "'identity' graph")

g1+g2+g3+g4 +
  plot_annotation(
    title = "How does position argument work?",
    subtitle = "Graph differences by position argument"
  )

2.2.4 좌표

좌표(coordinates) 레이어 혹은 좌표계(coordinate systems)는 그래픽 요소들의 위치 결정에 기준이 되는 준거체계이다. 특히 두 가지가 함수가 유용하다. coord_flip() 함수는 축을 전환한다.

ggplot(mpg, aes(x = drv, fill = class)) + 
  geom_bar(position = "fill") +
  coord_flip()
Figure 23: coord_clip() 함수

coord_fixed() 함수는 두 축의 스케일을 절대화하여 동일하게 적용한다. 아규먼트로 x축 한 단위 대비 y축 한 단위의 비(y/x)를 받으며, 생략할 경우 디폴트로 1을 지정한다. 무슨 의미인지 알아본다.

ggplot(mpg, aes(cty, hwy)) +
  geom_point() +
  coord_fixed()
Figure 24: coord_fixed() 함수
coord_fixed() 함수를 사용할까?

coord_fixed()를 사용하지 않아도 ggplot은 적당한 비율을 찾아 그래프를 그려준다. 그러나 간혹 사용자가 원하는 가로-세로 비율이 있을 때가 있다. 예를 들어, 우리나라가 동고서저의 지형임을 보여주는 그래프를 그려보자.

# 가상 데이터
data <- tibble(x=seq(0, 170, by=10),
               y=c(10, 62, 108, 162, 245, 330, 469, 608, 780, 942,
                   1125, 1307, 1500, 1707, 1324, 849, 394, 0))
# 비율 지정 안하면?
ggplot(data, aes(x=x, y=y)) +
  geom_point()+
  geom_line()+
  labs(x="서울-강릉(km)", y="고도(m)")

비율을 지정하지 않으니 조금 이상하다. 시각적 효과를 위해서, 가로 한 단위와 세로 한 단위의 비율을 적절히 맞춰보자.

# 적정 비율 지정하기
ggplot(data, aes(x=x, y=y)) +
  geom_point()+
  geom_line()+
  labs(x="서울-강릉(km)", y="고도(m)") +
  coord_fixed(ratio = 0.02)

아까보다 조금 더 가독성이 좋아짐을 확인할 수 있다.

2.2.5 테마

디폴트인 회색빛 배경이 마음에 들지 않았다면 Figure 25 처럼 흑백 테마(theme_bw())를 적용할 수도 있다. 다른 테마도 적용해 보고 그 차이를 알아본다.

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class)) +
  geom_smooth(se = FALSE) +
  theme_bw()
Figure 25: theme_bw() 함수

이렇게 한꺼번에 그래프의 외관을 바꿀 수도 있지만 theme() 함수를 통해 그래프의 개별 요소 하나씩을 모두 수정할 수 있다. 어떤 요소를 바꿀 수 있는지 다음을 참고한다.

Figure 26 는 몇 가지 요소를 수정한 사례이다.

ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point() +
  labs(
    title = "Larger engine sizes tend to have lower fuel economy",
    caption = "Source: https://fueleconomy.gov."
  ) +
  theme(
    legend.position = c(0.6, 0.7),
    legend.direction = "horizontal",
    plot.title = element_text(face = "bold"),
    plot.title.position = "plot",
    plot.caption.position = "plot",
    plot.caption = element_text(hjust = 0)
  )
Figure 26: 개별 요소의 수정: theme() 함수

2.3 기타 사항

2.3.1 라벨과 주석

lab() 함수를 활용하면 그래프의 다양한 종류의 라벨을 설정할 수 있다.

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class)) +
  geom_smooth(se = FALSE) +
  labs(
    x = "Engine displacement (L)",
    y = "Highway fuel economy (mpg)",
    color = "Car type",
    title = "Fuel efficiency generally decreases with engine size",
    subtitle = "Two seaters (sports cars) are an exception because of their light weight",
    caption = "Data from fueleconomy.gov"
  )
Figure 27: 라벨링

기하 함수인 geom_text() 혹은 geom_label()를 통해 그래프 속에 텍스트를 삽입할 수 있다. 주석이 겹치는 것을 방지하기 위해 ggrepel 패키지가 유용하게 사용될 수 있다.

ggplot(mpg, aes(displ, hwy)) + 
  geom_point(colour = "red") +
  geom_label_repel(data = mpg |> slice_sample(prop = 0.1), aes(label = class))

2.3.2 레이아웃

레이아웃(layout)은 복수의 그래프를 적절히 배치하여 하나의 그래픽으로 융합하는 과정을 의미한다. 수 많은 ggplot2확장 패키지(ggplot2 extensions) 중 하나이 patchwork 패키지를 활용할 수 있다.

p1 <- ggplot(mpg, aes(x = displ, y = hwy)) + 
  geom_point() + 
  labs(title = "Plot 1")
p2 <- ggplot(mpg, aes(x = drv, y = hwy)) + 
  geom_boxplot() + 
  labs(title = "Plot 2")
p1 + p2
Figure 28: 레이아웃: patchwork 패키지
patchwork() 더 살펴보기

patchwork() 패키지는 그래프의 배치를 적용하는데 매우 유용하다. 좌우배치를 하되 자동으로 줄넘김을 원한다면 ‘A+B’, 무조건 좌우배치를 원할 때는 ‘A|B’, 상하배치를 원하다면 ’A/B’의 형식을 사용하면 된다. 매우 직관적이다. 위에서 position 아규먼트를 공부할 때 사용한 코드를 재사용해보자.

g1+g2+g3+g4

(g1+g3+g4)/g2

2.3.3 그래프의 저장

두 가지 방식이 있다.

첫 번째 방식은 Output 창의 Plots 탭에 있는 Export 버을 이용하는 것이다. 다양한 그래픽 포멧 뿐만 아니라 pdf 형식으로도 저장할 수 있다.

두 번째 방식은 ggplot2ggsave() 함수를 이용하는 것이다. 결과물의 폰트 크기, 가로세로비, 해상도 등을 종합적으로 고려하여 최적의 세팅값을 찾아야 한다. 자신의 디바이스에 따라 동일한 세팅값이 다른 결과를 산출할 수도 있다.

my_plot <- ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point() +
  labs(
    title = "Larger engine sizes tend to have lower fuel economy",
    caption = "Source: https://fueleconomy.gov."
  ) +
  theme(
    legend.position = c(0.6, 0.7),
    legend.direction = "horizontal",
    plot.title = element_text(face = "bold"),
    plot.title.position = "plot",
    plot.caption.position = "plot",
    plot.caption = element_text(hjust = 0)
  )
ggsave(filename = "my_plot.png", plot = my_plot, width = 8, height = 8 * 0.618, dpi = 600)