Tidyverse: readxl, dplyr, magrittr

Disclaimer
본 포스팅의 모든 내용과 예제들은 “https://rviews.rstudio.com/2017/09/25/survival-analysis-with-r/”에 개제된 것들이며 본 포스트는 내용을 단순히 한글로 번안한 것 입니다. 본 내용에 대한 일체의 저작권은 원저작자에게 귀속되어있음을 밝힙니다. 

readxl

readxl은 엑셀에서 R로 데이터를 쉽게 얻을 수 있도록 도와주는 패키지이다. 이전의 gdata, xlsx, xlsReadWrite 등의 다른 패키지들과 비교해서 readxl은 다른 외부 설치가 필요없으며 모든 시스템에 간단히 설치하고 사용할 수 있다. 또한 이것은 tabular 데이터와 함께 작업하도록 설계되어있다. readxl은 .xls와 xml_based .xlsx 포맷을 모두 지원한다.

Usage

readxl에서는 몇가지 예제 파일들이 내장되어있다. readxl_example()라고 변수값 없이 그냥 타이핑하면 내장되어있는 데이터파일의 이름을 볼 수 있다.

readxl_example()
##  [1] "clippy.xls"    "clippy.xlsx"   "datasets.xls"  "datasets.xlsx"
##  [5] "deaths.xls"    "deaths.xlsx"   "geometry.xls"  "geometry.xlsx"
##  [9] "type-me.xls"   "type-me.xlsx"
readxl_example("clippy.xls")
## [1] "/Library/Frameworks/R.framework/Versions/3.6/Resources/library/readxl/extdata/clippy.xls"

read_excel()은 .xls와 .xlsx 파일 모두를 다 불러들일 수 있다.

xlsx_example <- readxl_example("datasets.xlsx")
read_excel(xlsx_example)

excel_sheets()를 통하여 각각의 엑셀시트를 확인할 수 있다.

excel_sheets(xlsx_example)
## [1] "iris"     "mtcars"   "chickwts" "quakes"

각 엑셀시트의 이름이나 숫자를 통하여 불러내는 것이 가능하다.

read_excel(xlsx_example, sheet = "quakes")
read_excel(xls_example, sheet = 4)

엑셀의 각 셀들을 불러들이는 다양한 방식들이 있다.

read_excel(xlsx_example, n_max = 3)
## # A tibble: 3 x 5
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##                                   
## 1          5.1         3.5          1.4         0.2 setosa 
## 2          4.9         3            1.4         0.2 setosa 
## 3          4.7         3.2          1.3         0.2 setosa
read_excel(xlsx_example, range = "C1:E4")
## # A tibble: 3 x 3
##   Petal.Length Petal.Width Species
##                    
## 1          1.4         0.2 setosa 
## 2          1.4         0.2 setosa 
## 3          1.3         0.2 setosa
read_excel(xlsx_example, range = cell_rows(1:4))
## # A tibble: 3 x 5
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##                                   
## 1          5.1         3.5          1.4         0.2 setosa 
## 2          4.9         3            1.4         0.2 setosa 
## 3          4.7         3.2          1.3         0.2 setosa
read_excel(xlsx_example, range = cell_cols("B:D"))
## # A tibble: 150 x 3
##    Sepal.Width Petal.Length Petal.Width
##                         
##  1         3.5          1.4         0.2
##  2         3            1.4         0.2
##  3         3.2          1.3         0.2
read_excel(xlsx_example, range = "mtcars!B1:D5")
## # A tibble: 4 x 3
##     cyl  disp    hp
##     
## 1     6   160   110
## 2     6   160   110
## 3     4   108    93

만일 엑셀에 빈칸이 공백 이외의 것들로 지정이 되어있다면 이것을 na 로 지정할 수 있다.

read_excel(xlsx_example, na = "setosa")
## # A tibble: 150 x 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##                                    
##  1          5.1         3.5          1.4         0.2    
##  2          4.9         3            1.4         0.2    
##  3          4.7         3.2          1.3         0.2    

dplyr

dplyr을 이용한 데이터조작을 배우기 위하여 우리는 nycflights13::flights 데이터셋을 이용할 것입니다. 이 데이터셋은 2013년 뉴욕시를 출발한 336,776대의 비행기에 대한 정보를 담고 있습니다. 해당 데이터는 US Breau of Transportation Statistics에서 가져왔습니다.

library(nycflights13)
dim(flights)
#> [1] 336776     19
flights
#> # A tibble: 336,776 x 19
#>    year month   day dep_time sched_dep_time dep_delay arr_time
#>                            
#> 1  2013     1     1      517            515         2      830
#> 2  2013     1     1      533            529         4      850
#> 3  2013     1     1      542            540         2      923
#> 4  2013     1     1      544            545        -1     1004
#> # … with 336,772 more rows, and 12 more variables: sched_arr_time ,
#> #   arr_delay , carrier , flight , tailnum ,
#> #   origin , dest , air_time , distance , hour ,
#> #   minute , time_hour 

여기서 우리는 이 데이터를 tibble을 통하여 다룰 계획입니다. 이것은 흔히 사용하는 data.frame에 비하여 보통 데이터를 살펴볼 때 첫 몇 줄만 출력해주기 때문에 큰 데이터를 다룰 때 더욱 유용합니다. tibble에 대한 더욱 자세한 정보는 http://tibble.tidyverse.org에서 찾아볼 수 있으며 기존의 데이터프레임은 단순히 as_tibble() 을 통하여 쉽게 tibble 형태로 변환할 수 있습니다.

Single table verbs

dplyr는 다음의 기본적인 커맨드를 통하여 데이터를 조작할 수 있습니다.

  • filter() 데이터 테이블에서 특정 관찰값 (rows)를 선택합니다.
  • arrange() 데이터 테이블에서 특정 관찰값들의 위치를 변경합니다.
  • select(), rename() 벡터나 테이블에서 특정 변수를 선택합니다.
  • mutate(), transmute() 기본 변수에 함수를 적용하여 새 변수를 생성합니다.
  • summarise() 여러 관찰값들을 하나의 관찰값으로 변환합니다.
  • sample_n(), sample_frac() 랜덤샘플을 선택합니다.

Filter rows with filter()

filter()는 데이터셋에서 특정 rows를 선택합니다. 첫번째 값으로는 tibble 또는 데이터셋의 이름을 넣습니다. 두번째와 이후의 값으로는 데이터셋에서 변수의 이름을 넣습니다. 예를들어 flights에서 1월 1일에 날아간 비행기를 선택한다면 아래와 같습니다.

filter(flights, month == 1, day == 1)
#> # A tibble: 842 x 19
#>    year month   day dep_time sched_dep_time dep_delay arr_time
#>                            
#> 1  2013     1     1      517            515         2      830
#> 2  2013     1     1      533            529         4      850
#> 3  2013     1     1      542            540         2      923
#> 4  2013     1     1      544            545        -1     1004
#> # … with 838 more rows, and 12 more variables: sched_arr_time ,
#> #   arr_delay , carrier , flight , tailnum ,
#> #   origin , dest , air_time , distance , hour ,
#> #   minute , time_hour 

이것은 기존 R 코드로 한다면 아래와 같습니다.

flights[flights$month == 1 & flights$day == 1, ]

Arrange rows with arrange()

arrange() 는 filter()와 유사하게 작동합니다. 다만 이것은 특정 rows를 선택하는 것이 아니라 일정한 순서로 특정 columns에 있는 rows를 정렬합니다. 여러개의 변수들을 동시에 선택하고 복잡한 방식으로 정렬하는 것도 가능합니다. 여러개의 컬럼을 선택한다면 추가적인 컬럼은 이전 컬럼의 연결을 끊는 역할을 합니다.

arrange(flights, year, month, day)
#> # A tibble: 336,776 x 19
#>    year month   day dep_time sched_dep_time dep_delay arr_time
#>                            
#> 1  2013     1     1      517            515         2      830
#> 2  2013     1     1      533            529         4      850
#> 3  2013     1     1      542            540         2      923
#> 4  2013     1     1      544            545        -1     1004
#> # … with 336,772 more rows, and 12 more variables: sched_arr_time ,
#> #   arr_delay , carrier , flight , tailnum ,
#> #   origin , dest , air_time , distance , hour ,
#> #   minute , time_hour 

desc() 는 해당 컬럼을 내림차순으로 정렬합니다.

arrange(flights, desc(arr_delay))
#> # A tibble: 336,776 x 19
#>    year month   day dep_time sched_dep_time dep_delay arr_time
#>                            
#> 1  2013     1     9      641            900      1301     1242
#> 2  2013     6    15     1432           1935      1137     1607
#> 3  2013     1    10     1121           1635      1126     1239
#> 4  2013     9    20     1139           1845      1014     1457
#> # … with 336,772 more rows, and 12 more variables: sched_arr_time ,
#> #   arr_delay , carrier , flight , tailnum ,
#> #   origin , dest , air_time , distance , hour ,
#> #   minute , time_hour 

Select columns with select()

큰 데이터셋을 다루면서 일부 몇개 변수만 필요한 경우가 있습니다. 이런 경우 select() 를 이용하면 분석에 필요한 최소한의 데이터셋만을 선택할 수 있게 됩니다.

# Select columns by name
select(flights, year, month, day)
#> # A tibble: 336,776 x 3
#>    year month   day
#>     
#> 1  2013     1     1
#> 2  2013     1     1
#> 3  2013     1     1

# Select all columns between year and day (inclusive)
select(flights, year:day)
#> # A tibble: 336,776 x 3
#>    year month   day
#>     
#> 1  2013     1     1
#> 2  2013     1     1
#> 3  2013     1     1

# Select all columns except those from year to day (inclusive)
select(flights, -(year:day))
#> # A tibble: 336,776 x 16
#>   dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay
#>                                        
#> 1      517            515         2      830            819        11
#> 2      533            529         4      850            830        20
#> 3      542            540         2      923            850        3

select() 에는 starts_with(), ends_with(), matches(), contains() 등 여러가지 추가적인 기능이 내장되어있습니다. 이러한 기능을 이용해서 수많은 변수들 중에서 조건에 맞는 것들을 선택하는 것이 가능합니다. 또한 select() 를 통하여 변수의 이름을 바꾸는 것도 가능합니다.

select(flights, tail_num = tailnum)
#> # A tibble: 336,776 x 1
#>   tail_num
#>      
#> 1 N14228  
#> 2 N24211  
#> 3 N619AA  

하지만 select()는 자칫 언급하지 않은 다른 변수들을 모두 삭제하기 때문에 대신 rename()을 사용하는 것이 유용할 수도 있습니다.

rename(flights, tail_num = tailnum)
#> # A tibble: 336,776 x 19
#>    year month   day dep_time sched_dep_time dep_delay arr_time
#>                            
#> 1  2013     1     1      517            515         2      830
#> 2  2013     1     1      533            529         4      850
#> 3  2013     1     1      542            540         2      923

Add new columns with mutate()

특정 변수를 선택하는 것 이외에도 기존의 변수에 여러 함수를 이용하여 새로운 변수를 추가하는 것도 가능합니다. 이것은 mutate()가 담당합니다.

mutate(flights,
  gain = arr_delay - dep_delay,
  speed = distance / air_time * 60
)
#> # A tibble: 336,776 x 21
#>    year month   day dep_time sched_dep_time dep_delay arr_time
#>                            
#> 1  2013     1     1      517            515         2      830
#> 2  2013     1     1      533            529         4      850
#> 3  2013     1     1      542            540         2      923

mutate()는 기본 transform()과 유사하지만 방금 작성한 컬럼을 참조하여 사용할 수 있습니다.

mutate(flights,
  gain = arr_delay - dep_delay,
  gain_per_hour = gain / (air_time / 60)
)
#> # A tibble: 336,776 x 21
#>    year month   day dep_time sched_dep_time dep_delay arr_time
#>                            
#> 1  2013     1     1      517            515         2      830
#> 2  2013     1     1      533            529         4      850
#> 3  2013     1     1      542            540         2      923

만일 새로운 변수를 만들고 이전의 것은 바로 삭제하기를 원한다면 transmute()를 사용할 수 있습니다.

transmute(flights,
  gain = arr_delay - dep_delay,
  gain_per_hour = gain / (air_time / 60)
)
#> # A tibble: 336,776 x 2
#>    gain gain_per_hour
#>            
#> 1     9          2.38
#> 2    16          4.23
#> 3    31         11.6 

Summarise values with summarise()

마지막으로 summarise()는 여러 rows들을 단 하나의 row로 압축하여 나타낼 수 있습니다.

summarise(flights,
  delay = mean(dep_delay, na.rm = TRUE)
)
#> # A tibble: 1 x 1
#>   delay
#>   
#> 1  12.6

이것은 단독으로는 별로 쓰일일이 없지만 group_by() 와 결합하면 매우 유용하게 사용됩니다.

Randomly sample rows with sample_n() and sample_frac()

sample_n()과 sample_frac()을 이용하여 데이터셋의 rows에서 랜덤으로 샘플을 선택할 수 있다. sample_n()은 고정된 숫자를 선택하며 sample_frac()은 고정된 비율을 선택한다.

sample_n(flights, 10)
#> # A tibble: 10 x 19
#>    year month   day dep_time sched_dep_time dep_delay arr_time
#>                            
#> 1  2013     9     9     1440           1429        11     1649
#> 2  2013     5    15     1427           1429        -2     1728
#> 3  2013     8    14     1121           1120         1     1228


sample_frac(flights, 0.01)
#> # A tibble: 3,368 x 19
#>    year month   day dep_time sched_dep_time dep_delay arr_time
#>                            
#> 1  2013    11     7     1855           1900        -5     2238
#> 2  2013     3    10     1930           1935        -5     2213
#> 3  2013     2    22     1622           1615         7     1820

replace = TRUE를 선택하면 부트스트랩 샘플을 실행한다. 만일 필요하다면 weight 옵션을 이용하여 weighted sample을 선택할 수도 있다.

 

Commonalities

지금까지 설명한 함수들의 기능이 거의 대부분 비슷하다는 것을 알 수 있을 것이다.

  • 함수에서 첫번째 값은 데이터 프레임 이름이다.
  • 연이은 값들은 데이터프레임에서 무엇을 할지에 대한 설명이다. 이 경우 기존 데이터프레임 명령어들처럼 $를 사용하지 않고 바로 컬럼의 이름을 적을 수 있다.
  • 결과는 새로운 데이터프레임이 만들어지는 것이다.

이러한 특성을 잘 연결하면 간단한 동작으로 연이은 복잡한 기능을 한꺼번에 수행해서 결과를 만들어낼 수 있다. 위의 설명한 5가지의 문법들은 데이터를 조작하는 가장 기본이 된다.

 

Patterns of operations

dplyr의 명령어들은 수행하는 작업의 유형에 따라 구분될 수 있다. 가장 유용한 구분은 집단 그리고 비집단 기능의 차이이다. 만일 이것을 잘 구분한다면 select와 mutate가 실제작동하는 것의 차이를 쉽게 알아챌 것입니다.

Grouped operations

dplyr의 기능들은 특히 group_by() 같은 그룹분류를 고려해서 함께 사용하면 매우 강력하게 변합니다. 이것은 rows들을 특별한 group단위로 rows를 나누어 작동할 수 있게 합니다. 만일 dplyr의 기능에 group_by()를 함께 사용한다면 모든 결과는 그룹별로 각각 만들어지게 될 것입니다.

Grouping affects the verbs as follows:

  • 그룹화된 select()는 그룹화되지 않은 select()와 동일하지만 그룹화 변수는 항상 유지됩니다.
  • 그룹화된 arrange()는 그룹화되지 않은 것과 동일하지만, by_group = TRUE를 지정하면 그룹화 변수로 먼저 순서를 정합니다.
  • mutate(), filter()는 rank(), min(x) == x 같은 윈도우 기능과 더불어 강력하게 사용될 수 있습니다. 이것은 window-functions의 vignette를 통해 자세히 설명됩니다.
  • sample_n(), sample_frac()은 각각의 그룹마다 적용될 수 있습니다.
  • summarise()는 각 그룹의 요약값을 계산합니다.

다음 예제에서 우리는 전체 데이터셋을 개별적으로 분리한 다음 비행횟수 (count = n())을 요약하고 평균거리 (dist = mean(distance, na.rm = TRUE)와 도착지연 (delay = mean(arr_delay, na.rm = TRUE)를 계산할 것입니다. 그리고 이것은 ggplot2를 통해서 그래픽으로 출력할 수 있습니다.

by_tailnum <- group_by(flights, tailnum)
delay <- summarise(by_tailnum,
  count = n(),
  dist = mean(distance, na.rm = TRUE),
  delay = mean(arr_delay, na.rm = TRUE))
delay <- filter(delay, count > 20, dist < 2000)

# Interestingly, the average delay is only slightly related to the
# average distance flown by a plane.
ggplot(delay, aes(dist, delay)) +
  geom_point(aes(size = count), alpha = 1/2) +
  geom_smooth() +
  scale_size_area()

aggregate 함수들과 함께 summarise()를 사용하면 값의 벡터들을 가져와서 단일숫자로 변환합니다. min(), max(), mean(), sum(), sd(), median(), IQR() 같은 것들이 있습니다. 이외에도 다음과 같은 기능들을 제공합니다.

  • n()은 현재 그룹에서 관찰값들의 숫자를 알려줍니다.
  • n_distinct(x)sms x에서 특이값의 숫자를 알려줍니다.
  • first(x), last(x), nth(x, n) 이것들은 보통 데이터프레임에서의 x[1], x[length(x)], x[n]과 유사합니다. 하지만 만일 결측치가 있는 경우 훨씬 더 관리하기 용이합니다. 

예를들어 각각의 가능한 행선지에 따라서 비행기의 숫자와 비행횟수를 찾아내는데 사용할 수 있을 것입니다.

destinations <- group_by(flights, dest)
summarise(destinations,
  planes = n_distinct(tailnum),
  flights = n()
)
#> # A tibble: 105 x 3
#>   dest  planes flights
#>        
#> 1 ABQ      108     254
#> 2 ACK       58     265
#> 3 ALB      172     439

만일 여러 변수에 대해서 그룹화를 한다면 summary()는 그룹의 특성을 한층 더 드러낼 것이빈다. 그래서 데이터를 좀 더 쉽게 파악할수 있게 합니다.

daily <- group_by(flights, year, month, day)
(per_day   <- summarise(daily, flights = n()))
#> # A tibble: 365 x 4
#> # Groups:   year, month [12]
#>    year month   day flights
#>        
#> 1  2013     1     1     842
#> 2  2013     1     2     943
#> 3  2013     1     3     914

(per_month <- summarise(per_day, flights = sum(flights)))
#> # A tibble: 12 x 3
#> # Groups:   year [1]
#>    year month flights
#>       
#> 1  2013     1   27004
#> 2  2013     2   24951
#> 3  2013     3   28834

#> # … with 8 more rows
(per_year  <- summarise(per_month, flights = sum(flights)))
#> # A tibble: 1 x 2
#>    year flights
#>      
#> 1  2013  336776

하지만 summarise()를 이용해서 데이터를 요약해낼때는 조심해야합니다. 만일 총합이나 숫자 같은 것을 알아내기에는 문제가 없습니다만 weighted means이나 variances 같은 것은 정확한 median을 산출하기 어려울 수 있습니다.

 

Selecting operations

dplyr의 매력적인 부분은 일반적인 변수인 것처럼 tibble의 columns을 참조할 수 있다는 것 입니다. 그러나 단순히 columns의 이름을 참조하는 것는 각 기능들마다의 문법의 상이성을 감추는 역할을 하게 됩니다. 실제로 select()에서 컬럼과 mutate()에서의 컬럼은 약간 다른 의미를 가집니다.  select()를 위해서는 column의 이름과 위치가 필요합니다. 만일 당신이 select()를 통해 실제 변수명을 선택하면 이것은 tibble에서 실제 위치를 대표하게 됩니다.

# `year` represents the integer 1
select(flights, year)
#> # A tibble: 336,776 x 1
#>    year
#>   
#> 1  2013
#> 2  2013
#> 3  2013

select(flights, 1)
#> # A tibble: 336,776 x 1
#>    year
#>   
#> 1  2013
#> 2  2013
#> 3  2013

게다가 이것은 하나의 column으로서 같은 이름을 가지고 있다면 자신을 참조할 수 없게 만듭니다. 다음의 예에서 year는 5가 아닌 1을 대표합니다.

year <- 5
select(flights, year)

한가지 미요하지만 유용한 점은 실제 이름을 붙이고 선택하는 c(year, month, day) or year:day 같은 경우에만 적용된다는 점입니다. 다른 경우에는 데이터프레임의 열이 범위에 포함되지 않습니다. 이렇게 하면 선택 함수에서 상황변수들을 참조할 수 있습니다.

year <- "dep"
select(flights, starts_with(year))
#> # A tibble: 336,776 x 2
#>   dep_time dep_delay
#>           
#> 1      517         2
#> 2      533         4
#> 3      542         2

이러한 문법은 매우 직관적입니다. 하지만 약간 다른점이 있는 것을 기억해야합니다.

year <- 5
select(flights, year, identity(year))
#> # A tibble: 336,776 x 2
#>    year sched_dep_time
#>             
#> 1  2013            515
#> 2  2013            529
#> 3  2013            540

첫번째 값에서 year는 실제 자신의 위치 1을 대표합니다. 두번째 값에서 year는 주변상황으로 인하여 평가되고 다섯번째 열을 나타냅니다. 오랜기간 select()는 컬럼의 위치로서만 이해되어왔습니다. 하지만 dplyr에서는 커럼의 이름으로서 기능합니다. 이것은 select()를 통하여 코딩을 하는 것을 좀 더 편하게 해줍니다.

vars <- c("year", "month")
select(flights, vars, "day")
#> # A tibble: 336,776 x 3
#>    year month   day
#>     
#> 1  2013     1     1
#> 2  2013     1     1
#> 3  2013     1     1

위의 코드들은  tibble에 vars라는 이름의 컬럼을 추가하거나 해당 컬럼을 포함하는 다른 데이터프레임에 코드를 적용할 수 있기 때문이 다소 안전하지 않습니다. 이런 문제를 피하기 위하여 변수를 identity()를 이용해서 감쌀 수 있습니다. 이는 열 이름을 무시하기 때문입니다. 그러나 더욱 분명한 것은 모든 dplyr 명령어들은 !!와 함께 변수명을 직접 적어주는 것입니다. 이것은 데이터프레임은 무시하고 실제 컨텍스트만 보도록 합니다.

# Let's create a new `vars` column:
flights$vars <- flights$year

# The new column won't be an issue if you evaluate `vars` in the
# context with the `!!` operator:
vars <- c("year", "month", "day")
select(flights, !! vars)
#> # A tibble: 336,776 x 3
#>    year month   day
#>     
#> 1  2013     1     1
#> 2  2013     1     1
#> 3  2013     1     1

이러한 명령문은 매우 유용한데 특히 커스텀기능과 함께 사용할 때 더욱 그렇습니다. vignette(“programming”)을 참조해보십시오. 하지만 따옴표를 붙이지 않는 것에 대해서 이해해야할 중요한 것은 select()는 컬럼의 이름과 위치를 이용한다는 것입니다. mutate()의 경우는 조금 다릅니다.

 

Mutating operations

mutate()의 문법은 select()와 다소 다릅니다. select()는 컬럼의 이름과 위치를 이용하지만 mutate()는 컬럼의 벡터를 이용합니다. 아래처럼 작은 tibble을 만들어서 확인해봅시다.

df <- select(flights, year:dep_time)

select() 기능을 이용하면 컬럼의 이름은 tibble의 위치를 대표합니다. mutate()에서는 컬럼이름은 tibble에 저정되어있는 컬럼벡터를 대표합니다. 다음의 예를 통해 어떻게 되는지 살펴봅시다.

mutate(df, "year", 2)
#> # A tibble: 336,776 x 6
#>    year month   day dep_time `"year"`   `2`
#>              
#> 1  2013     1     1      517 year         2
#> 2  2013     1     1      533 year         2
#> 3  2013     1     1      542 year         2

mutate()는 데이터프레임에서 새로운 컬럼으로서의 1개 길이의 벡터를 가집니다. 이 벡터들은 재반복되어 전체 rows의 숫자와 맞추어집니다. 따라서 mutate()에 “year” + 10 같은 기능을 제공하는 것이 적절치 않습니다. 이것은 문자에 10을 더하는 것과 같은 것이 됩니다. 정확한 문법은 아래와 같습니다.

mutate(df, year + 10)
#> # A tibble: 336,776 x 5
#>    year month   day dep_time `year + 10`
#>                
#> 1  2013     1     1      517        2023
#> 2  2013     1     1      533        2023
#> 3  2013     1     1      542        2023

마찬가지 방식으로 따옴표 없이 변수를 사용할 수 있으며 이것은 컬럼을 의미합니다. 또한 이것의 길이는 1이어야하며 데이터테이블의 전체 행의 숫자만큼 복제됩니다. 다음의 예에서 새로운 벡터가 데이터프레임의 추가된 것을 볼 수 있습니다.

var <- seq(1, nrow(df))
mutate(df, new = var)
#> # A tibble: 336,776 x 5
#>    year month   day dep_time   new
#>          
#> 1  2013     1     1      517     1
#> 2  2013     1     1      533     2
#> 3  2013     1     1      542     3

이것은 group_by()의 예입니다.. 이것은 select()의 문법이라고 생각할 수 있지만 실제로는 mutate()의 문법입니다. 이것은 변경된 컬럼별로 그룹화할 수 있으므로 매우 편리합니다.

group_by(df, month)
#> # A tibble: 336,776 x 4
#> # Groups:   month [12]
#>    year month   day dep_time
#>         
#> 1  2013     1     1      517
#> 2  2013     1     1      533
#> 3  2013     1     1      542

group_by(df, month = as.factor(month))
#> # A tibble: 336,776 x 4
#> # Groups:   month [12]
#>    year month   day dep_time
#>         
#> 1  2013 1         1      517
#> 2  2013 1         1      533
#> 3  2013 1         1      542

group_by(df, day_binned = cut(day, 3))
#> # A tibble: 336,776 x 5
#> # Groups:   day_binned [3]
#>    year month   day dep_time day_binned
#>               
#> 1  2013     1     1      517 (0.97,11] 
#> 2  2013     1     1      533 (0.97,11] 
#> 3  2013     1     1      542 (0.97,11] 

group_by()에 컬럼이름을 부여하지는 못합니다. 이것은 전체 행의 갯수만큼 문자열을 반복하는 것을 하게 됩니다.

group_by(df, "month")
#> # A tibble: 336,776 x 5
#> # Groups:   "month" [1]
#>    year month   day dep_time `"month"`
#>              
#> 1  2013     1     1      517 month    
#> 2  2013     1     1      533 month    
#> 3  2013     1     1      542 month    

그룹화를 통해 관찰값들을 선택하는 것도 때로는 유용할 수 있고 group_by_at()을 사용할 수 있습니다. dplyr에서 약간 변형된 접미사인 _at()은 두번째 값을 선택하도록 도와줍니다. 여기서는 필요한 것을 vars()로 감싸주면 됩니다.

group_by_at(df, vars(year:day))
#> # A tibble: 336,776 x 4
#> # Groups:   year, month, day [365]
#>    year month   day dep_time
#>         
#> 1  2013     1     1      517
#> 2  2013     1     1      533
#> 3  2013     1     1      542

?scoped를 통해 _at(), _if()에 대해서 더 읽어보십시오.

 

Piping

dplyr API는 함수호출에 오류가 없다는 의미에서 기능적입니다. 항상 결과를 저장해야 합니다. 특히 많은 작업을 한꺼번에 처리하려는 경우 단계별로 해야합니다.

a1 <- group_by(flights, year, month, day)
a2 <- select(a1, arr_delay, dep_delay)
a3 <- summarise(a2,
  arr = mean(arr_delay, na.rm = TRUE),
  dep = mean(dep_delay, na.rm = TRUE))
a4 <- filter(a3, arr > 30 | dep > 30)

또한 과정의 중간 중간에 별도의 결과들을 만들고 싶지 않다면 각각의 기능들을 괄호로 감싸는 방법이 있습니다.

filter(
  summarise(
    select(
      group_by(flights, year, month, day),
      arr_delay, dep_delay
    ),
    arr = mean(arr_delay, na.rm = TRUE),
    dep = mean(dep_delay, na.rm = TRUE)
  ),
  arr > 30 | dep > 30
)
#> Adding missing grouping variables: `year`, `month`, `day`
#> # A tibble: 49 x 5
#> # Groups:   year, month [11]
#>    year month   day   arr   dep
#>       
#> 1  2013     1    16  34.2  24.6
#> 2  2013     1    31  32.6  28.7
#> 3  2013     2    11  36.3  39.1

하지만 이것은 작업순서가 뒤엉켜 있기 때문에 코드를 읽기 어렵습니다. 또한 값들이 함수와 멀리 떨어져 위치하게 됩니다. 이 문제를 해결하기 위해서 dplyr에서는 magrittr 패키지를 통해서 %>%를 제공합니다. x %>% f(y)는 f(x, y)로 변신합니다. 그러므로 여러개의 기능들을 사용하면서 단순히 왼쪽에서 오른쪽으로 또는 위에서 아래방향으로 읽을 수 있습니다.

flights %>%
  group_by(year, month, day) %>%
  select(arr_delay, dep_delay) %>%
  summarise(
    arr = mean(arr_delay, na.rm = TRUE),
    dep = mean(dep_delay, na.rm = TRUE)
  ) %>%
  filter(arr > 30 | dep > 30)

Other data sources

데이터프레임 뿐 아니라 dplyr은 데이터테이블, 데이터베이스, 다차워배열과 같은 다른 방법으로 저장된 데이터와도 잘 작동합니다.

Data table

dplyr은 dtplyr을 통해서 모든 기능에 대한 데이터테이블 기능을 제공합니다. 데이터테이블을 이미 사용하고 있는 경우 데이터 조작을 위하여 dplyr 문법을 사용하고 다른 작업을 위해서는 데이터테이블을 사용할 수 있습니다.

여러 연산의 경우 데이터테이블은 일반적으로 여러 기능을 동시에 사용하기 때문에 더 빠를 수 있습니다. 예를들어 데이터테이블을 사용한다면 단일단계에서 mutate, select를 수행할 수 있습니다. 이것은 제거할 행에 대한 변수들을 계산할 필요가 없다는 점에서 장점이 있습니다.

데이터테이블과 함께 dplyr을 사용하면 다음과 같은 이점이 있습니다.

  • 일반적인 데이터조작작업의 경우 테이터테이블의 문법으로부터 탈피해서 실수로 데이터를 망치는 일을 방지합니다.
  • “[]” 를 기반으로 하는 복잡한 방식 대신 보다 간결한 방식을 제공합니다.

Databases

dplyr은 원격 데이터베이스에 대해서 동일한 기능을 사용할 수 있습니다. SQL을 지속적으로 생성하므로 언어를 바꿔야하는 일을 피할 수 있습니다. 이러한 기능을 사용하려면 dbplyr 패키지를 설치하고 vignette(“dbplyr”)의 세부사항을 확인해보십시오.

 

Multidimensional arrays / cubes

tbl_cube()는 다차원배열이나 데이터큐브에 대한 실험적인 인터페이스를 제공합니다.

 

Comparisons

기존의 모든 옵션들과 비교해서 dplyr은

  • 데이터가 저장되는 방식을 추상화해서 동일한 기능세트를 사용하여 데이터프레임, 데이터테이블, 원격 데이터베이스로 작업할 수 있습니다. 이를 통하여 데이터 스토리지의 원리가 아닌 당신이 얻고자하는 그 자체에 집중할 수 있습니다.
  • print()라는 매우 사려깊은 기능을 기본으로 제공합니다. 이것은 데이터테이블을 자동으로 프린트하지 않습니다.

베이스 기능과 비교해서 dplyr은

  • 더욱 일관성이 있습니다. 모든 함수에는 동일한 인터페이스가 있습니다. 그래서 하나를 이해하면 쉽게 다른 것들을 습득할 수 있습니다.
  • 베이스 기능에서는 벡터를 기반으로 하는 경향이 있습니다. dplyr은 데이터테이블을 기본으로 합니다.

plyr과 비교하여 dplyr은

  • 더욱 빠릅니다.
  • joins의 셋이 잘 고려되어 있습니다.
  • 데이터프레임을 위한 기능들이 더 많습니다. 예를들어 dplyr은 ddply()에 여러 기능이 더해진 것과 동일합니다. do()는 dlply()와 동일합니다.

가상데이터프레임의 접근과 비교해서

  • 데이터프레임을 가지고 있는 것 처럼 보이지는 않습니다. 만일 lm 등을 실행하고 싶다면 수동으로 데이터를 받아야 합니다.
  • mean(), sum() 같은 R의 요약기능을 제공하지 않습니다.

magrittr

파이프 명령어인 %>%는 Stefan Milton Bache에 의해서 개발된 magrittr에 있는 문법이다. tidyverse를 불러들이면 %>%는 자동으로 사용할 수 있기 때문에 별도로 magrittr을 불러들일 필요는 없다.

library(magrittr)

Piping Alternatives

파이프를 짜는 주된 이유는 코드를 작성할 때 더욱 읽기 쉽고 이해하기 쉽게 하기 위해서이다. 동일한 코드를 짜기 위한 다른 방법들을 비교해본다면 파이프를 짜는 것이 얼마나 용이한지 이해할 수 있을 것이다. 다음의 이야기를 살펴보자

Little bunny Foo Foo  
Went hopping through the forest  
Scooping up the field mice  
And bopping them on the head  

노래를 상징하는 object를 하나 정의하자

foo_foo <- little_bunny()

우리는 각 문장의 주요동사를 이용할 것이다: hop(), scoop(), and bop(). 이 object와 동사들을 활용하여 4가지 방식의 코드를 작성해보겠다.

Intermediate Steps

가장 쉬운 방법은 새로운 object를 각각의 단계마다 저장하는 것이다.

foo_foo_1 <- hob(foo_foo, through = forest)  
foo_foo_2 <- scoop(foo_foo_1, up = field_mice)  
foo_foo_3 <- bop(foo_foo_2, on = head)  

이런식으로 중간에 이름을 할당할 수 있을 것이다. 적절한 이름이 따로 있는 것이 아니라면 이 방법이 좋을 것이다. 하지만 위의 예에서 보듯이 자연스러운 이름은 아니고 뒤에 숫자로 접미사를 붙여줘야한다. 이런 방식은 2가지 문제를 야기한다. (1) 중요하지 않은 단어이름들이 자꾸 생성된다. 접미사를 신경써서 계속 분배해야한다. 이렇게 되면 별 의미없는 objects를 계속 생성하고 또 기억해야한다. R에서는 이것을 데이터 프레임에 두고 사용할 수 있다.

Overwrite the Original

Intermediate objects를 생성하는 대신 original object를 덮어쓰기 하는 방법도 있다.

foo_foo <- hop(foo_foo, through = forest)
foo_foo <- scoop(foo_foo, up = field_mice)
foo_foo <- bop(foo_foo, on = head)

이렇게 하면 실수를 할 확률이 줄어든다. 하지만 여전히 두가지 문제가 발생한다. (1) 문제가 발생하면 처음부터 모든 파이프라인을 다시 돌려야하므로 디버깅 과정이 복잡해진다. (2) 각각의 라인에서 어떻게 변화가 되고 있는지 살피기 어렵다.

Function Composition

다른 방법은 object를 할당하는 것을 포기하고 그냥 각각 문자함수를 배분하는 것이다.

bop( scoop(
        hop(foo_foo, through = forest),
        up = field_mice
      ),
on = head )

이 방법은 코드가 위아래로 길어지면 읽는데 햇갈릴 수 있으며 일일이 줄을 맞춰주는 것이 어렵다.

Use the Pipe

 foo_foo %>%
      hop(through = forest) %>%
      scoop(up = field_mouse) %>%
      bop(on = head)
  my_pipe <- function(.) {
      . <- hop(., through = forest)
      . <- scoop(., up = field_mice)
      bop(., on = head)
  }
  my_pipe(foo_foo)

Easy example

a <- data.frame(1:5, 6:10)
summary(a)
##       X1.5       X6.10   
##  Min.   :1   Min.   : 6  
##  1st Qu.:2   1st Qu.: 7  
##  Median :3   Median : 8  
##  Mean   :3   Mean   : 8  
##  3rd Qu.:4   3rd Qu.: 9  
##  Max.   :5   Max.   :10
data.frame(1:5, 6:10) %>% summary()
##       X1.5       X6.10   
##  Min.   :1   Min.   : 6  
##  1st Qu.:2   1st Qu.: 7  
##  Median :3   Median : 8  
##  Mean   :3   Mean   : 8  
##  3rd Qu.:4   3rd Qu.: 9  
##  Max.   :5   Max.   :10