Chapter 10 Data Transformation

10.1 Introduction

Dplyr 패키지로 데이터를 다루는 법을 학습

Dplyr 는 ggplot2 과 유사하지만, 그래픽 문법을 제공하는 대신 데이터 조작 문법을 제공

여기서 배울 dplyr의 동사는 다음과 같다

  • filter()
  • mutate()
  • group_by , summarise()

데이터 변환 파이프라인: %>% 를 사용(ggplot2의 + 와 비슷한 역할

10.2 Filter Observations

데이터의 일부를 추출하여 검사할 수 있음? 즉, 이상치를 추출하는 데도 유용하다

약 5만 개 점이 있는 diamonds 데이터를 플롯팅해보면, 소수의 이상치가 발견된다. 값이 0인 다이아몬드들은 확실히 잘못된 값이므로 찾아서 제거해야한다 이때 filter 를 사용해보자!

install.packages(“dplyr”) install.packages(“ggplot2”) install.packages(“Lahman”)

library(ggplot2)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(Lahman)


head(diamonds)
## # A tibble: 6 x 10
##   carat cut       color clarity depth table price     x     y     z
##   <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23  Ideal     E     SI2      61.5    55   326  3.95  3.98  2.43
## 2 0.21  Premium   E     SI1      59.8    61   326  3.89  3.84  2.31
## 3 0.23  Good      E     VS1      56.9    65   327  4.05  4.07  2.31
## 4 0.290 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
## 5 0.31  Good      J     SI2      63.3    58   335  4.34  4.35  2.75
## 6 0.24  Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
ggplot(diamonds, aes(x, y)) +  geom_bin2d()

filter(diamonds, x == 0 | y == 0)
## # A tibble: 8 x 10
##   carat cut       color clarity depth table price     x     y     z
##   <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1  1.07 Ideal     F     SI2      61.6    56  4954     0  6.62     0
## 2  1    Very Good H     VS2      63.3    53  5139     0  0        0
## 3  1.14 Fair      G     VS1      57.5    67  6381     0  0        0
## 4  1.56 Ideal     G     VS2      62.2    54 12800     0  0        0
## 5  1.2  Premium   D     VVS1     62.1    59 15686     0  0        0
## 6  2.25 Premium   H     SI2      62.8    59 18034     0  0        0
## 7  0.71 Good      F     SI2      64.1    60  2130     0  0        0
## 8  0.71 Good      F     SI2      64.1    60  2130     0  0        0

이것은 기본 R코드인 diamonds[diamonds$x == 0 | diamonds&y == 0, ] 과 같지만 데이터플레임에서 단순히 x를 찾는다는 것을 filter() 가 알고 있음

Subset() vs filter() subset() 에서는 관찰값과 변인을 모두 선택할 수 있음 Filter()는 관찰값만으로 작동한다

단, filter() 가 약간 더 빠르다

데이터 품질 문제의 근본 원인을 파악할수 있는지 확인하기 위해 이상치를 더욱 상세히 볼 수 있음 이상치를 버리고 남은 것에 집중 하기 위하여 filter()를 사용해보자

diamonds_ok <- filter(diamonds, x > 0, y > 0, y < 20)
ggplot(diamonds_ok, aes(x, y)) +
  geom_bin2d() +
  geom_abline(slope = 1, colour = "white", size = 1, alpha = 0.5)

x~y 간 강항 상관 관계를 볼수 있음! 추세선을 추가하여 더욱 명확하게 확인해봄

이 플롯에는 2가지 문제가 있다 - 대다수의 데이터가 대각선을 따라 있기 때문에 플롯은 대부분 비어있다 - 명확한 2 변인 이상치들이 있지만, 단순 필터로 선택하기 어렵다

이 문제에 대해서는 뒤에서 다루어 볼것이다

10.2.1 Useful Tools

filter() 의 첫번쨰 인수는 데이터프레임 두번쨰 및 후속 인수는 논리 벡터(filter는 모든 논리가 TRUE인 결과를 수행한다) + 논리 벡터는 항상 데이터프레임과 길이가 같아야 함 if else 에러 발생!!!

비교 연산자 사용

논리 연산자 사용

  • filter()는 함수 표현을 내재시킬 수 있음

10.2.2 Missing Values

결측값을 이해하는 가장 중요한 점은 감영성?이 있다는 점 즉, 결측값을 포함하여 분석하면 그 결과도 결측값이 될 수 있음!!!

  • 예시
x <- c(1, NA, 2)
x == 1
## [1]  TRUE    NA FALSE
x > 2
## [1] FALSE    NA FALSE
x + 10
## [1] 11 NA 12
is.na(x)
## [1] FALSE  TRUE FALSE

R을 처음 배울때 == 를 사용해서 결측값을 찾으려는 유혹을 느낀다(너만 그런거 같은데…;;)

하지만 아래와 같이 전혀 효과가 없다 이유: 두개의 알려지지 않은 값이 같을 이유가 없기 떄문

x  == NA
## [1] NA NA NA
x != NA
## [1] NA NA NA

대신 is.na(x)를 사용해 결측되었는지 판별은 가능하다

is.na(x)
## [1] FALSE  TRUE FALSE

filter()는 모든 인수가 TRUE인 값만 관찰값으로 사용하므로, NA 값은 자동으로 삭제된다 결측값을 포함하여면 x >10 | is.na(x) 와같은 식으로 명시해야한다

10.3 Create New Variables

x, y 의 관계를 더 잘 탐색하려면 데이터가 대각선이 아닌 평면에 있도록 ’회전시키면 유용’하다

다이아몬드 데이터에서는 x, y의 차이를 나타내는 변인(다이아몬드의 대칭을 나타냄)과 크기(대각선 길이)를 나타내는 변인 두개를 만들어 볼수 있다

mutate()를 사용하여 만들어보자(filter()와 마찬가지로 이름만으로 변수를 참조 가능)

diamonds_ok2 <- mutate(diamonds_ok,
                       sym = x - y,
                       # 교재 코드 오타: sam으로 써있음.
                       size = sqrt(x*x + y*y)
                       # 교재의 코드는 size = sqrt(x^2 + y^2) 인데 안돌아감...Why...?
)
diamonds_ok2
## # A tibble: 53,930 x 12
##    carat cut    color clarity depth table price     x     y     z      sym
##    <dbl> <ord>  <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>    <dbl>
##  1 0.23  Ideal  E     SI2      61.5    55   326  3.95  3.98  2.43 -0.0300 
##  2 0.21  Premi… E     SI1      59.8    61   326  3.89  3.84  2.31  0.05   
##  3 0.23  Good   E     VS1      56.9    65   327  4.05  4.07  2.31 -0.02   
##  4 0.290 Premi… I     VS2      62.4    58   334  4.2   4.23  2.63 -0.03   
##  5 0.31  Good   J     SI2      63.3    58   335  4.34  4.35  2.75 -0.01000
##  6 0.24  Very … J     VVS2     62.8    57   336  3.94  3.96  2.48 -0.02   
##  7 0.24  Very … I     VVS1     62.3    57   336  3.95  3.98  2.47 -0.0300 
##  8 0.26  Very … H     SI1      61.9    55   337  4.07  4.11  2.53 -0.04   
##  9 0.22  Fair   E     VS2      65.1    61   337  3.87  3.78  2.49  0.09   
## 10 0.23  Very … H     VS1      59.4    61   338  4     4.05  2.39 -0.0500 
## # ... with 53,920 more rows, and 1 more variable: size <dbl>
ggplot(diamonds_ok2, aes(size, sym)) +
  stat_bin2d()

위 차트로 다이아몬드가 따르는 패턴을 더 쉽게 볼 수 있으며, 이상치를 쉽게 선택할 수 있다. 이상치가 양인지 음인지는 중요하지 않고, 절대값 개념으로 0선에서 멀어질수록 이상치로 분류할 수 있다. 따라서 절대값을 조건을 걸어 이상치를 제거할 수 있다

아래에서 한계치를 0.20으로 주었다 익상치가 제거되고 깔끔한 그래프가 그려진다

ggplot(diamonds_ok2, aes(abs(sym))) +
  geom_histogram(binwidth = 0.10)

diamonds_ok3 <- filter(diamonds_ok2, abs(sym) < 0.20)
ggplot(diamonds_ok3, aes(abs(sym))) +
  geom_histogram(binwidth = 0.01)

10.3.1 Useful Tools

로그변환

상대적 차분

10.4 Group-wise Summaries

통찰력 있는 시각화를 위해서는 전체 데이터셋을 의미 있는 요약으로 축소하는 것이 중요 ggplot2는 요약하는데 쓸만한 기하객체를 많이 제공한다

dplyr는 요약을 두 단계로 수행한다 1. group_by() : 변인들을 그룹화하는 방법을 정의 2. summarize() : 단일 행으로 각 그룹을 요약

예시 투명도 당 평균 가격을 살펴보기 위하여, 투명도 별로 그룹을 분류한 뒤 요약

by_clarity <- group_by(diamonds, clarity)
sum_clarity <- summarise(by_clarity, price = mean(price))
sum_clarity
## # A tibble: 8 x 2
##   clarity price
##   <ord>   <dbl>
## 1 I1      3924.
## 2 SI2     5063.
## 3 SI1     3996.
## 4 VS2     3925.
## 5 VS1     3839.
## 6 VVS2    3284.
## 7 VVS1    2523.
## 8 IF      2865.
ggplot(sum_clarity, aes(clarity, price)) +
  geom_line(aes(group = 1), colour = "grey80") +
  geom_point(size = 2)

투명도가 좋은 다이아몬드의 가격이 더 낮다!!!(11장2절에서 왜그런지 알랴줌)

여러 변인을 바탕으로 삼아 그룹을 만들려면 group_by()에 추가 변수를 제공하면 된다 예시 컷과 깊이가 어떻게 상호 작용하는지 보여주는 주파수 다각형을 계산 * n() : 요약함수로서. 각 그룹의 관찰 수를 계산해줌

cut_depth <- summarise(group_by(diamonds, cut, depth), n = n())
cut_depth <- filter(cut_depth, depth > 55, depth < 70)
cut_depth
## # A tibble: 455 x 3
## # Groups:   cut [5]
##    cut   depth     n
##    <ord> <dbl> <int>
##  1 Fair   55.1     3
##  2 Fair   55.2     6
##  3 Fair   55.3     5
##  4 Fair   55.4     2
##  5 Fair   55.5     3
##  6 Fair   55.6     4
##  7 Fair   55.8     7
##  8 Fair   55.9     9
##  9 Fair   56       5
## 10 Fair   56.1     5
## # ... with 445 more rows
ggplot(cut_depth, aes(depth, n, colour = cut)) +
  geom_line()

mutate()룰 사용해 개수를 비율로 변환할 수도 있다 summarize()는 한 수준의 그룹화를 제거하므로, cut_depth()는 컷으로 그룹화 한다

cut_depth <- mutate(cut_depth, prop = n / sum(n))
ggplot(cut_depth, aes(depth, prop, colour = cut)) +
  geom_line()

10.4.1 Useful Tools

개수세기…

논리 벡터를 숫자로 처리하면 TRUE 는 1, FALSE 는 0

sum() 은 TRUE 수를 알려주고 mean() 은 TRUE의 비율을 알려줌

예시 캐럿이 4보다 크거나 같은 다이아몬드의 수와 1,000달러 미만인 다이아몬드의 비율을 계산

summarise(diamonds,
          n_big = sum(carat >= 4),
          prop_cheap = mean(price < 1000)
)
## # A tibble: 1 x 2
##   n_big prop_cheap
##   <int>      <dbl>
## 1     6      0.269

na.rm = TRUE : 요약 함수 중 하나로, 요약 전 결측값을 제거할 떄 사용

10.4.2 Statistical Considerations

예시 이전 그룹의 평균가격을 투명성으로 확장해 각 그룹 및 상위 및 하위 분위의 관찰수도 포함

by_clarity <- diamonds %>%
  group_by(clarity) %>%
  summarise(
    n = n(),
    mean = mean(price),
    lq = quantile(price, 0.25),
    uq = quantile(price, 0.75)
  )
by_clarity
## # A tibble: 8 x 5
##   clarity     n  mean    lq    uq
##   <ord>   <int> <dbl> <dbl> <dbl>
## 1 I1        741 3924. 2080  5161 
## 2 SI2      9194 5063. 2264  5777.
## 3 SI1     13065 3996. 1089  5250 
## 4 VS2     12258 3925.  900  6024.
## 5 VS1      8171 3839.  876  6023 
## 6 VVS2     5066 3284.  794. 3638.
## 7 VVS1     3655 2523.  816  2379 
## 8 IF       1790 2865.  895  2388.
ggplot(by_clarity, aes(clarity, mean)) +
  geom_linerange(aes(ymin = lq, ymax = uq)) +
  geom_line(aes(group = 1), colour = "grey50") +
  geom_point(aes(size = n))

예시 Lehman 패키지의 MLB 데이터

data(Batting, package = "Lahman")
batters <- filter(Batting, AB > 0)
per_player <- group_by(batters, playerID)
ba <- summarise(per_player,
                ba = sum(H, na.rm = TRUE) / sum(AB, na.rm = TRUE)
)
ggplot(ba, aes(ba)) +
  geom_histogram(binwidth = 0.01)

아래와 같이 평균타수를 보여줌으로써 조정할 수 있다

ba <- summarise(per_player,
                ba = sum(H, na.rm = TRUE) / sum(AB, na.rm = TRUE),
                ab = sum(AB, na.rm = TRUE)
)
ggplot(ba, aes(ab, ba)) +
  geom_bin2d(bins = 100) +
  geom_smooth()
## `geom_smooth()` using method = 'gam'

가장 높은 타율 평균은 타격 수가 가장 적은 선수에게서 발생 이것은 어려운 것이 아니므로 배제시켜야 한다

타수가 10이하인 것을 제거해보자

ggplot(filter(ba, ab >= 10), aes(ab, ba)) +
  geom_bin2d() +
  geom_smooth()
## `geom_smooth()` using method = 'gam'

10.5 Transformation Pipelines

실제 분석에서는 ㄴㅇㅁㅇㅁㄴㅇㅁㄴㅇ를 함꼐 써야한다

예시

# By using intermediate values
cut_depth <- group_by(diamonds, cut, depth)
cut_depth <- summarise(cut_depth, n = n())
cut_depth <- filter(cut_depth, depth > 55, depth < 70)
cut_depth <- mutate(cut_depth, prop = n / sum(n))

위 코드는 데이터프레임을 너무 많이 반복해서 좋지 않다 아래와 같이 순차적인 함수를 이용하는 것은 어떨까?

# By "composing" functions
mutate(
  filter(
    summarise(
      group_by(
        diamonds,
        cut,
        depth
      ),
      n = n()
    ),
    depth > 55,
    depth < 70
  ),
  prop = n / sum(n)
)
## # A tibble: 455 x 4
## # Groups:   cut [5]
##    cut   depth     n    prop
##    <ord> <dbl> <int>   <dbl>
##  1 Fair   55.1     3 0.00192
##  2 Fair   55.2     6 0.00384
##  3 Fair   55.3     5 0.00320
##  4 Fair   55.4     2 0.00128
##  5 Fair   55.5     3 0.00192
##  6 Fair   55.6     4 0.00256
##  7 Fair   55.8     7 0.00448
##  8 Fair   55.9     9 0.00575
##  9 Fair   56       5 0.00320
## 10 Fair   56.1     5 0.00320
## # ... with 445 more rows

이것도 구리다…

dlpyr 는 파이프라인 %>%를 써서 다르게 접근이 가능하다

cut_depth <- diamonds %>%
  group_by(cut, depth) %>%
  summarise(n = n()) %>%
  filter(depth > 55, depth < 70) %>%
  mutate(prop = n / sum(n))

그룹화-요약-선별-변이 를 %>%를 통해 작성할 수 있다

%>% 는 ‘then’, 즉 ~한 다음에 로 기억 하면 좋다

f(x, y) # is the same as x %>% f(y) g(f(x, y), z) # is the same as x %>% f(y) %>% g(z)

```

더배우기