public bigdata

R4DS (R FOR DATASCIENCE) 9장 tidyr로 하는 타이디 데이터 본문

R programming/R4DS (R for DataScience)

R4DS (R FOR DATASCIENCE) 9장 tidyr로 하는 타이디 데이터

public bigdata 2020. 7. 12. 20:34

※ tidy한 데이터란?

출처 : R4DS

데이터셋을 타이디하게 만드는, 서로 연관된 세 가지 규칙

  1. 변수마다 해당되는 열이 있어야 한다.
  2. 관측값마다 해당되는 행이 있어야 한다.
  3. 값마다 해당하는 하나의 셀이 있어야 한다.

위의 3가지 규칙들은 서로 연관되어 있기에, 셋 중 두가지만 충족시키는 것은 불가능하다고 한다.

※ tidy 데이터의 장점

  • 일관된 데이터 구조를 사용하여, 적용할 도구들이 공통성을 가지게 되어 배우기 쉽다.
  • 변수를 열에 배치하여 R의 벡터화 속성이 잘 발휘된다.

9.3.1 gather()로 모으기

아래와 같은 데이터 형태가 있을 때 english, math, science 세 가지 열 이름들은 과목이라는 변수의 값들이라고 본다면 tidy하지 않은 형태라고 할 수 있다.(tidy데이터의 조건을 만족하기는 한다. 보기에 따라 다르다)

 

 

# 예제 데이터 #
> stocks
# A tibble: 10 x 4
   time       english   math science
   <date>       <dbl>  <dbl>   <dbl>
 1 2009-01-01 -0.741   0.293   4.34 
 2 2009-01-02 -0.265   0.268  -4.42 
 3 2009-01-03 -1.13   -3.89   -0.936
 4 2009-01-04 -1.08    1.62    6.46 
 5 2009-01-05  0.514   0.545   3.25 
 6 2009-01-06  0.208  -2.60   -3.29 
 7 2009-01-07  2.07    1.70    3.97 
 8 2009-01-08 -0.840  -1.13   -2.92 
 9 2009-01-09  0.0673 -2.04   12.3  
10 2009-01-10 -0.427   4.36   -1.65 

gather 함수를 통해 각 과목별로 넓게 펼쳐져 있는 함수를 아래와 같이 더 좁고 길게 만들어 주면 1) 일자별 평균 점수 2) 일자별 각 과목의 평균 점수등을 group_by함수를 통해서 간편히 계산할 수 있는 장점이 생긴다.

> gather(stocks, key = "category", value = "score", english, math, science)
# A tibble: 30 x 3
   time       category   score
   <date>     <chr>      <dbl>
 1 2009-01-01 english  -0.741 
 2 2009-01-02 english  -0.265 
....중략....
11 2009-01-01 math      0.293 
12 2009-01-02 math      0.268 
....중략 ....
21 2009-01-01 science   4.34  
22 2009-01-02 science  -4.42  
....중략 ....

9.3.2 spread()로 펼치기

위 데이터를 다시 원래대로 spread 함수를 이용해 복원한다.

> spread(stocks_gather, key = "category", value = "score")
# A tibble: 10 x 4
   time       english   math science
   <date>       <dbl>  <dbl>   <dbl>
 1 2009-01-01 -0.741   0.293   4.34 
 2 2009-01-02 -0.265   0.268  -4.42 
 3 2009-01-03 -1.13   -3.89   -0.936
 4 2009-01-04 -1.08    1.62    6.46 
 5 2009-01-05  0.514   0.545   3.25 
 6 2009-01-06  0.208  -2.60   -3.29 
 7 2009-01-07  2.07    1.70    3.97 
 8 2009-01-08 -0.840  -1.13   -2.92 
 9 2009-01-09  0.0673 -2.04   12.3  
10 2009-01-10 -0.427   4.36   -1.65 

다만 주의할 점은 gather를 통해 데이터를 좁고 길게 만들고 spread를 통해서 짧고 넓게 만드는 과정에서 데이터가 완벽하게 원래의 데이터로 돌아오지 않는다. 해당 과정에서 원래는 숫자였던 값들이 열의 이름으로 가면서 문자열로 바뀌고 다시 열의 이름이 값으로 돌아가는 과정에서 원래 숫자였던 정보는 존재하지 않으므로 문자열 형태로 들어가게 되므로 원래의 데이터와는 완벽히 일치하지는 않는다.

이를 위해서 gather, spread 함수 모두 convert 인수를 가지고 있어서, 변환된 데이터에서 다시 컬럼 별로 데이터 타입을 판단하여 최종 변환한다.

 

9.3.3 연습문제

※ 주의할 점은 패키지 버전업이 되면서 gather, spread는 pivot_longer, pivot_wider로 변경되었다.

 

연습문제 중에서 1번, 2번, 3번 문제가 중요하다. 해당 링크를 통해 정답 확인.

특히 3번 문제 row_number 함수 활용 중요하다고 생각된다.

9.4.1 separate로 분리하기

df <- data.frame(x = c(NA, "a.b", "a.d", "b.c"))
df %>% separate(x, c("A", "B"))
#>      A    B
#> 1 <NA> <NA>
#> 2    a    b
#> 3    a    d
#> 4    b    c

기본값은 알파벳이 아닌 문자를 기준으로 분리하게 되어 있다. 기능이 다양하므로 공식 예제를 살펴보는 것이 좋다

링크

9.4.2 unite로 결합하기

unite는 separate의 반대의 기능을 한다. 자세한 내용은 링크 참조

※ 기타

분리하는 함수 두개 separate, extract(정규식으로 분리하는 함수)가 있는데, 결합하는 방법은 unite 한가지이다. 여러 열을 하나로 합치기 때문에 하나면 충분하다.

9.5 결측값

결측값은 두가지 방식으로 나타난다. 

  • 명시적인 NA
  • 암묵적으로 데이터가 존재하지 않음

1) 결측이 있는 경우 gather 함수의 na.rm=TRUE를 설정하여 데이터의 결측값을 제거할 수도 있다.

현재 원 데이터인 stocks에는 명시적 결측 1건과 2016년 1분기에 대한 데이터가 암묵적으로 결측이다.

해당 데이터를 spread 함수로 길게 늘어트려서 명시적인 결측들로 만들어 준 뒤에 gather 함수로 다시 복원하는 과정에서 na.rm=TRUE를 통해 NA인 경우를 제거한다.

> stocks <- tibble(
+   year = c(2015, 2015, 2015, 2015, 2016, 2016, 2016),
+   qtr = c(1, 2, 3, 4, 2, 3, 4),
+   return = c(1.88, 0.59,0.35,NA, 0.92, 0.17, 2.66)
+ )

> stocks
# A tibble: 7 x 3
   year   qtr return
  <dbl> <dbl>  <dbl>
1  2015     1   1.88
2  2015     2   0.59
3  2015     3   0.35
4  2015     4  NA   
5  2016     2   0.92
6  2016     3   0.17
7  2016     4   2.66

> stocks %>% spread(key = year, value = return)
# A tibble: 4 x 3
    qtr `2015` `2016`
  <dbl>  <dbl>  <dbl>
1     1   1.88  NA   
2     2   0.59   0.92
3     3   0.35   0.17
4     4  NA      2.66

> stocks %>% spread(key = year, value = return) %>% gather(year, return, `2015`:`2016`, na.rm = TRUE)
# A tibble: 6 x 3
    qtr year  return
  <dbl> <chr>  <dbl>
1     1 2015    1.88
2     2 2015    0.59
3     3 2015    0.35
4     2 2016    0.92
5     3 2016    0.17
6     4 2016    2.66

2) 타이디 데이터에서 결측값을 명시적으로 표현해주는 complete 함수도 있다.

> stocks
# A tibble: 7 x 3
   year   qtr return
  <dbl> <dbl>  <dbl>
1  2015     1   1.88
2  2015     2   0.59
3  2015     3   0.35
4  2015     4  NA   
5  2016     2   0.92
6  2016     3   0.17
7  2016     4   2.66

> stocks %>% complete(year, qtr)
# A tibble: 8 x 3
   year   qtr return
  <dbl> <dbl>  <dbl>
1  2015     1   1.88
2  2015     2   0.59
3  2015     3   0.35
4  2015     4  NA   
5  2016     1  NA   
6  2016     2   0.92
7  2016     3   0.17
8  2016     4   2.66

complete는 열 집합을 입력으로 받아서 해당 열들의 고유한 조합을 모두 찾는다. 그런 다음 원 데이터에 모든 값이 포함되도록 필요한 곳에 NA를 명시적으로 채운다.

 

3) fill 함수

fill 함수는 결측값을 가장 최근의 비결측값으로 치환하고자 하는 열을 입력으로 한다. 

> stocks %>% fill()
# A tibble: 7 x 3
   year   qtr return
  <dbl> <dbl>  <dbl>
1  2015     1   1.88
2  2015     2   0.59
3  2015     3   0.35
4  2015     4  NA   
5  2016     2   0.92
6  2016     3   0.17
7  2016     4   2.66
> stocks %>% fill(return)
# A tibble: 7 x 3
   year   qtr return
  <dbl> <dbl>  <dbl>
1  2015     1   1.88
2  2015     2   0.59
3  2015     3   0.35
4  2015     4   0.35
5  2016     2   0.92
6  2016     3   0.17
7  2016     4   2.66

9.5.1 연습문제

1) spread, complete 함수의 fill 인자를 비교하면?

> spread 함수의 fill은 NA의 대체값을 무엇으로 할지 정한다면, complete 함수의 fill은 fill=list(value=0)의 형식을 통해서 열마다 결측값을 다르게 치환할 수 있다.

> df <- tibble(
+   group = c(1:2, 1),
+   item_id = c(1:2, 2),
+   item_name = c("a", "b", "b"),
+   value1 = 1:3,
+   value2 = 4:6
+ )

> df %>% complete(group, nesting(item_id, item_name))
# A tibble: 4 x 5
  group item_id item_name value1 value2
  <dbl>   <dbl> <chr>      <int>  <int>
1     1       1 a              1      4
2     1       2 b              3      6
3     2       1 a             NA     NA
4     2       2 b              2      5
> # You can also choose to fill in missing values

> df %>% complete(group, nesting(item_id, item_name), fill = list(value1 = 0, value2 = 0))
# A tibble: 4 x 5
  group item_id item_name value1 value2
  <dbl>   <dbl> <chr>      <dbl>  <dbl>
1     1       1 a              1      4
2     1       2 b              3      6
3     2       1 a              0      0
4     2       2 b              2      5

여기서 nesting은 item_id, item_name두 변수를 묶어서 하나의 조합으로 본다는 것이다. 두 변수를 따로 조합을 계산하면 총 4가지의 조합이 생기지만 두 변수를 묶어서 하나의 조합으로 생각하면 (1, a), (2, b) 두 가지의 조합이 존재한다. 

9.6 사례연구 ~ 9.6.1 연습문제 skip

연습문제 중에서는 특히 1번, 3번이 중요하다. 책 본문을 읽고서 해설집까지 같이 읽어보면서 공부하면 좋겠다.