이번 포스팅에서는 dplyr 패키지의 bind_rows() 함수와 bind_cols() 함수에 대해서 알아보겠습니다.

 

dplyr 패키지의 bind_rows() 함수는 두개 이상의 데이터 프레임을 행 기준(위 - 아래 - 아래 ...)로 합칠 때 사용하는 함수이며, {base] 패키지의 rbind() 함수와 유사한 기능을 수행합니다.

 

dplyr 패키지의 bind_cols() 함수의 두개 이상의 데이터 프레임을 열 기준(왼쪽 - 오른쪽 - 오른쪽 ...)로 합칠 때 사용하는 함수이며, {base} 패키지의 cbind() 함수와 유사한 기능을 수행합니다.

 

dplry 패키지는 데이터 프레임의 데이터 구조에 특화된 패키지이므로 아래에 제시한 예시는 모두 데이터 프레임에만 해당이 됩니다.

(참고로, {base} 패키지의 rbind(), cbind() 는 두 개 이상의 vector에 대해서도 실행이 되며, vector를 rbind(), cbind() 할 경우 matrix 를 반환합니다.  {dplyr} 의 bind_rows(), bind_cols()를 vector 에 적용하면 실행 안됨.)

 

 

[ bind_rows(), bind_cols() in {dplyr}
: 효과적으로 다수의 데이터 프레임 행 기준, 열 기준으로 합치기 ]

 

 

 

 

1. bind_rows() : 다수의 데이터 프레임을 행 기준으로 합치기 (binding multiple data frames by row)

 

기본적인 사용법은 bind_rows(dataframe 1, dataframe 2, ...) 입니다.  {base} 패키지의 rbind()와 동일합니다.

 

 

> ##------------------------------------------------------ > ## R {dplyr} package > ## bind() : Efficiently bind multiple data frames by row and column. > ##------------------------------------------------------ > # install.packages("dplyr") > library(dplyr) > > # making 2 data.frame examples > df_1 <- data.frame(x = 1:3, y = 1:3) > df_2 <- data.frame(x = 4:6, y = 4:6) > > # rbind() in {base} package > rbind(df_1, df_2) x y 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 > > # bind_rows in {dplyr} package > bind_rows(df_1, df_2) x y 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6

 

 

 

 

{base} 패키지의 rbind()와 동일한 결과를 반환한다면 왜 굳이 dplyr 패키지의 bind_rows() 함수를 써야할 필요가 있을까 싶을 것입니다. 

 

{base} 패키지의 rbind() 대비 dplyr 패키지의 bind_rows() 가 좋은 점 세가지를 소개하겠습니다.

(꼭 dplyr 패키지 영업사원 된 듯한 기분...ㅋㅋ)

 

  • (1-1) 열(columns)이 서로 동일하지 않아도 행(rows) 기준으로 합칠 수 있음
    (--> 합치는 과정에서 열이 달라서 빈 자리는 NA 값 처리됨)
  • (1-2) 'id' 매개변수를 사용해 합쳐지기 전 데이터 프레임의 원천을 알 수 있음
    (--> source를 알 수 있는 새로운 변수 생성함)
  • (1-3) dplyr 패키지의 처리 속도가 {base} 패키지 대비 상대적으로 엄청나게 빠름
    (100배 이상 빠름!!!)

 

위 세가지 dplyr 패키지의 bind() 함수의 좋은 점 세 가지를 예를 들어서 설명하겠습니다.

 

 

 

(1-1) 열(columns)이 서로 동일하지 않아도 행(rows) 기준으로 합칠 수 있음
       (--> 합치는 과정에서 열이 달라서 빈 자리는 NA 값 처리됨)

 

 

> # In case columns do not match b/w data frames
> df_1 <- data.frame(x = 1:3, y = 1:3)
> df_3 <- data.frame(x = 7:9, z = 7:9)
> 
> # rbind(): Columns need to match
> # if not, Error in match.names(clabs, names(xi))
> rbind(df_1, df_3) # Not run
Error in match.names(clabs, names(xi)) : 
  names do not match previous names
> 
> # bind_rows(): Columns don't need to match when row-binding
> bind_rows(df_1, df_3)
  x  y  z
1 1  1 NA
2 2  2 NA
3 3  3 NA
4 7 NA  7
5 8 NA  8
6 9 NA  9

 

 

 

 

 

(1-2) 'id' 매개변수를 사용해 합쳐지기 전 데이터 프레임의 원천을 알 수 있음
(--> source를 알 수 있는 새로운 변수 생성함)

 

 

> # When you supply a column name with the `.id` argument, a new
> # column is created to link each row to its original data frame
> df_1 <- data.frame(x = 1:3, y = 1:3)
> df_2 <- data.frame(x = 4:6, y = 4:6)
> df_3 <- data.frame(x = 7:9, z = 7:9)
> 
> bind_rows(list(grp_1 = df_1, grp_2 = df_2, grp_3 = df_3), .id="group_id")
  group_id x  y  z
1    grp_1 1  1 NA
2    grp_1 2  2 NA
3    grp_1 3  3 NA
4    grp_2 4  4 NA
5    grp_2 5  5 NA
6    grp_2 6  6 NA
7    grp_3 7 NA  7
8    grp_3 8 NA  8
9    grp_3 9 NA  9

 

 

 

 

 

 

(1-3) dplyr 패키지의 처리 속도가 {base} 패키지 대비 상대적으로 엄청나게 빠름
(100배 이상 빠름!!!)

 

system.time() 함수를 사용해서 {base} 패키지의 rbind() 함수와 {dplyr} 패키지의 bind_rows() 함수의 CPU 실행시간을 알아보겠습니다.(사용자 + 시스템 = elapsed 이므로 elapsed 결과를 비교하면 됨. 실행을 시킬 때마다 숫자가 조금씩 달라지기는 하지만, 차이가 워낙 커서 경향성이 뒤집히지는 않을 것이므로 한번만 실행시켜보고 비교해보겠음

 

데이터 프레임의 행의 개수가 너무 작으면 차이가 티가 잘 안나므로, 백만개의 행을 가진 데이터 프레임 두 개를 만들어서 비교해보겠습니다.

 

{base} 패키지의 rbind() 가 7.85초 걸렸고, {dplyr} 패키지의 bind_rows()는 0.03초가 걸렸으니 261배 차이가 났군요. (2.6배나 26배가 아니라 261배 차이임. 안 놀라는 사람은 뭡니까? ⊙⊙;)

 

크기가 작은 데이터라면 rbind()와 bind_rows() 함수의 실행 시간 차이를 아마 거의 느끼지 못할 것입니다만, 대용량 데이터의 경우 C 기반으로 짜여진 {dplyr} 패키지가 훨~씬 빠르고 처리 속도 차이가 피부로 느껴질 것입니다.  

 

 

> # system.time : rbind() in {base} vs bind_rows() in {dplyr} > # System operation time of dplyr bind_rows is extremely shorter than that of rbind > one <- data.frame(c(x = c(1:1000000), y = c(1:1000000))) > two <- data.frame(c(x = c(1:1000000), y = c(1:1000000))) > > system.time(rbind(one, two)) # elapsed 7.85 사용자 시스템 elapsed 7.02 0.16 7.85 > > system.time(bind_rows(one, two)) # elapsed 0.03 사용자 시스템 elapsed 0.02 0.02 0.03

> 7.85/0.03
[1] 261.6667

 

 

 

 

2. bind_cols() : 다수의 데이터 프레임을 열 기준으로 합치기 (binding multiple data frames by columns)

 

cbind()와 기능 및 활용법은 유사하며, 기본 활용법은 bind_cols(dataframe 1, dataframe 2, ...) 입니다.

 

 

> # binding data frames by column
> bind_cols(df_1, df_2, df_3)
  x y x y x z
1 1 1 4 4 7 7
2 2 2 5 5 8 8
3 3 3 6 6 9 9

 

 

 

 

{base} 패키지의 cbind()와 {dplyr} 패키지의 bind_cols() 의 CPU 실행시간을 비교해보겠습니다. 

(참고로, 실행시킬 때마다 아주 조금씩 달라짐)

 

{base} 패키지의 cbind()는 0.61초, {dplyr} 패키지의 bind_cols() 는 0.0001초가 걸린걸 보면, 역시 {dplyr} 패키지가 월등히 빠름을 알 수 있습니다.

 

 

> # system.time comparison : cbind() vs. bind_cols()
> one <- data.frame(c(x = c(1:1000000), y = c(1:1000000)))
> two <- data.frame(c(x = c(1:1000000), y = c(1:1000000)))
> 
> system.time(cbind(one, two)) # elapsed 0.61
 사용자  시스템 elapsed 
   0.47    0.03    0.61 
> system.time(bind_cols(one, two)) #elapsed 0
 사용자  시스템 elapsed 
      0       0       0

 

 

 

 

bind_cols() 함수는 서로 합칠 데이터 프레임의 행(rows)의 개수가 서로 같아야만 하므로 주의를 요합니다.  만약 행의 개수가 서로 다를 경우에는 "Error in eval(substitute(expr), envir, enclos) :  incompatible number of rows (4, expecting 3)" 에러가 납니다.

 

> # bind_cols() : Rows do need to match when column-binding > bind_cols(data.frame(x = 1:3), data.frame(y = 1:3)) # run x y 1 1 1 2 2 2 3 3 3 > > bind_cols(data.frame(x = 1:3), data.frame(y = 1:4)) # Not run Error in eval(substitute(expr), envir, enclos) : incompatible number of rows (4, expecting 3)

 

 

 

많은 도움이 되었기를 바랍니다.

 

이번 포스팅이 도움이 되셨다면 아래의 '공감 ♡'를 눌러주세요. ^^

 

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

R의 dplyr 패키지에 대해서 연재를 하고 있는데요, dplyr 패키지 매뉴얼에 보니 유용한 함수들이 여럿 더 있네요. 그동안 dplyr 패키지 함수들에 대해서 소개했던것 외에 눈에 띄는 함수들을 서너번 더 나누어서 소개할까 합니다.

 

두 개의 데이터 프레임이 있을 때 "동일한 값을 가진 데이터 프레임인가?" 아니면 "서로 다른 값을 가진 데이터 프레임인가?"를 확인할 때 사용할 수 있는 {dplyr} 패키지의 all_equal() 함수를 소개하겠습니다.

 

비교하고자 하는 두 개의 데이터 프레임에 관측치와 변수가 모두 몇 개씩 밖에 안된다면 "눈으로 확인"하는 것도 가능할 것입니다.  하지만 관측치가 몇 백개를 넘어가거나, 혹은 변수가 몇 백개를 넘어간다면 육안으로 일일이 확인한다는게 피같은 시간을 낭비하는 것이 되며, 또 실수를 할 수도 있습니다.  더욱이 "데이터셋의 값은 동일한데 관측치의 순서만 다르건", 혹은 "데이터셋의 값은 동일한테 변수의 순서만 다른" 경우, 혹은 "데이터셋의 값은 동일한데 관측치의 순서와 변수의 순서만 서로 다른" 경우, 이건 뭐 육안으로 확인한다는게... 미치는거지요. ㅋㅋ

 

 

 

 

 

이때 간단하게 두 데이터 프레임 내의 값들이 서로 같은지를 간단하게 비교해서 TRUE, FALSE 를 반환해주는 함수가 dplyr 패키지의 all_equal() 함수입니다.

 

 

# all_equal() usage

 

all_equal(targetcurrent, # two data frame to compare

            ignore_col_order = TRUE, # Should order of columns be ignored?

            ignore_row_order = TRUE, # Should order of rows be ignored?
            convert = FALSE # Should similar classes be converted?)

 

 

 

 

{datasets} 패키지에 내장되어 있는 (1) mtcars 데이터 프레임 원본(target)과 (2) mtcars 의 행과 열을 임의로 순서만 바꾼 (단, 데이터는 변동없이 동일함) ran_sam_mtcars 데이터 프레임(current)을 만들어서 all_equal() 함수를 사용하여 비교를 해보겠습니다.

 

 

(1) 비교할 기준 target 데이터 프레임 : mtcars (원본)

 

 

 

> ##-----------------------------------------------------------
> ## R {dplyr} package 
> ## > all_equal : flexible equality comparison for data frames
> ##-----------------------------------------------------------
> # install.packages("dplyr") # for the first time user of dplyr package
> library(dplyr)
> 
> # original data frame : mtcars
> mtcars
                     mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Mazda RX4           21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag       21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Datsun 710          22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive      21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout   18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
Merc 240D           24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
Merc 230            22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
Merc 280            19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
Merc 280C           17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
Merc 450SE          16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
Merc 450SL          17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
Merc 450SLC         15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
Chrysler Imperial   14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
Fiat 128            32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic         30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla      33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
Toyota Corona       21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
Dodge Challenger    15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
AMC Javelin         15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
Camaro Z28          13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
Pontiac Firebird    19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
Fiat X1-9           27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
Porsche 914-2       26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
Lotus Europa        30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
Ford Pantera L      15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
Ferrari Dino        19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
Maserati Bora       15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8
Volvo 142E          21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2

 

 

 

 

 

(2) 비교하고자 하는 현재(current) 데이터 프레임 만들기 : ran_sam_mtcars

(행과 열을 무작위로 섞어 놓았으며, 데이터 삭제나 추가 등의 변동은 없이 동일함)

 

위의 (1)번 원본 target과 동일한 데이터 프레임인데요, 눈으로 동일한 데이터셋인지 확인하라면 어떤 마음이 들까요? (노트북 던지고 싶은 마음? -,-;;;)

 

 

> # random sampled dataframe : ran_sam_mtcars
> random_sample <- function(x) x[sample(nrow(x)), sample(ncol(x))]
> 
> set.seed(1234)
> ran_sam_mtcars <- random_sample(mtcars)
> ran_sam_mtcars
                    cyl  qsec    wt drat gear  hp  mpg vs carb  disp am
Fiat 128              4 19.47 2.200 4.08    4  66 32.4  1    1  78.7  1
Merc 230              4 22.90 3.150 3.92    4  95 22.8  1    2 140.8  0
Lotus Europa          4 16.90 1.513 3.77    5 113 30.4  1    2  95.1  1
Maserati Bora         8 14.60 3.570 3.54    5 335 15.0  0    8 301.0  1
Camaro Z28            8 15.41 3.840 3.73    3 245 13.3  0    4 350.0  0
Merc 240D             4 20.00 3.190 3.69    4  62 24.4  1    2 146.7  0
Duster 360            8 15.84 3.570 3.21    3 245 14.3  0    4 360.0  0
Hornet Sportabout     8 17.02 3.440 3.15    3 175 18.7  0    2 360.0  0
Valiant               6 20.22 3.460 2.76    3 105 18.1  1    1 225.0  0
Porsche 914-2         4 16.70 2.140 4.43    5  91 26.0  0    2 120.3  1
Fiat X1-9             4 18.90 1.935 4.08    4  66 27.3  1    1  79.0  1
Hornet 4 Drive        6 19.44 3.215 3.08    3 110 21.4  1    1 258.0  0
Mazda RX4             6 16.46 2.620 3.90    4 110 21.0  0    4 160.0  1
Pontiac Firebird      8 17.05 3.845 3.08    3 175 19.2  0    2 400.0  0
Cadillac Fleetwood    8 17.98 5.250 2.93    3 205 10.4  0    4 472.0  0
Ford Pantera L        8 14.50 3.170 4.22    5 264 15.8  0    4 351.0  1
Volvo 142E            4 18.60 2.780 4.11    4 109 21.4  1    2 121.0  1
Merc 450SL            8 17.60 3.730 3.07    3 180 17.3  0    3 275.8  0
Toyota Corolla        4 19.90 1.835 4.22    4  65 33.9  1    1  71.1  1
Ferrari Dino          6 15.50 2.770 3.62    5 175 19.7  0    6 145.0  1
Toyota Corona         4 20.01 2.465 3.70    3  97 21.5  1    1 120.1  0
Merc 450SE            8 17.40 4.070 3.07    3 180 16.4  0    3 275.8  0
Lincoln Continental   8 17.82 5.424 3.00    3 215 10.4  0    4 460.0  0
Mazda RX4 Wag         6 17.02 2.875 3.90    4 110 21.0  0    4 160.0  1
Dodge Challenger      8 16.87 3.520 2.76    3 150 15.5  0    2 318.0  0
Chrysler Imperial     8 17.42 5.345 3.23    3 230 14.7  0    4 440.0  0
AMC Javelin           8 17.30 3.435 3.15    3 150 15.2  0    2 304.0  0
Honda Civic           4 18.52 1.615 4.93    4  52 30.4  1    2  75.7  1
Merc 280C             6 18.90 3.440 3.92    4 123 17.8  1    4 167.6  0
Merc 280              6 18.30 3.440 3.92    4 123 19.2  1    4 167.6  0
Datsun 710            4 18.61 2.320 3.85    4  93 22.8  1    1 108.0  1
Merc 450SLC           8 18.00 3.780 3.07    3 180 15.2  0    3 275.8  0

 

 

 

 

 

(3) 두 개의 데이터 프레임, 즉 (1) target 데이터 프레임 mtcars 와, (2) current 데이터 프레임 ran_sam_mtcars 이 동일한 데이터를 가지고 있는건지 아닌지를 {dplyr} 패키지의 all_equal() 함수를 써서 확인해보기

 

 

> # all_equal() : flexible equality comparison for data frames
> # By default, ordering of rows and columns ignored
> all_equal(mtcars, ran_sam_mtcars)
[1] TRUE

 

 

행과 열의 순서만 바뀌었을 뿐 값 자체에 대한 변동은 없으므로 당연히 "TRUE"라고 나와야 겠지요.

 

비교하는 두 개의 데이터 프레임이 행의 순서가 같은지(ignore_row_order = TRUE), 혹은 열의 순서가 같은지(ignore_col_order = TRUE)는 확인하지 않고 무시하게끔 default 옵션이 설정되어 있습니다.

 

 

 

 

(4) 행의 순서가 같은지 확인해보기
    : all_equal
(target, current, ignore_row_order = FALSE),

    열의 순서가 같은지 확인해보기
    : all_equal(
target, current, ignore_col_order = FALSE)

 

 

> # To check the equality of row's sequence : ignore_row_order = F
> all_equal(mtcars, ran_sam_mtcars, ignore_row_order = FALSE)
[1] "Same row values, but different order"
> 
> 
> # To check the equality of column's sequence : ignore_col_order = F
> all_equal(mtcars, ran_sam_mtcars, ignore_col_order = FALSE)
[1] "Same column names, but different order"

 

 

 

 

마지막으로, convert 매개변수는 두 개의 데이터 프레임 내 동일한 변수 이름을 가지고 있고, 값도 서로 같은데, 속성만 하나는 "factor", 하나는 "character"인 경우 "factor"를 "character"로 변경해주어서 => all_equal() 함수 최종 평가 결과를 "TRUE"로 반환해줄 수 있도록 자동으로 속성 변환해서 비교해주는 기능을 수행합니다.

 

[Reference] https://cran.r-project.org/web/packages/dplyr/dplyr.pdf

 

 

많은 도움 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~ ♡'를 눌러주세요. ^^

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

지난번 포스팅에서는 R dplyr 패키지의 Window function 중에서 행 전체를 위로 올릴 때 사용하는 lead() 함수, 행 전체를 아래로 내릴 때 사용하는 lag() 함수에 대해서 알아보았습니다.

(☞ 바로 가기 : http://rfriend.tistory.com/242 )

(* 참고 : Window function : n개의 행을 input으로 받아서 n개의 행을 output으로 반환하는 함수)

 

이번에는 R dplyr 패키지의 Window funciton 에 대한 마지막 포스팅으로

 - (3) Cumulative aggregates : cumall() 함수, cumany() 함수, cummean() 함수와 

 - (4) Recycled aggrerates 에 대해서 소개하겠습니다.

 

 

[ Types of Window functions in {dplyr} package ]

 

 

 

예제로 사용할 데이터를 먼저 간단히 소개하고 나서 cumulative aggretages, recycled aggregates 함수로 넘어가겠습니다. 예제로 사용할 데이터는 MASS 패키지에 내장되어 있는 Cars93 데이터프레임의 차종(Type), 가격(Price) 변수입니다.

 

 
> ##------------------------------------------------
> ## R dplyr package 
> ## > window function - Cumulative aggregates 
> ##  : cumany(), cumall(), cummean()
> ##------------------------------------------------
> 
> # install.packages("dplyr")
> library(dplyr)
> library(MASS)
> 
> str(Cars93)
'data.frame':	93 obs. of  27 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
> table(Cars93$Type)

Compact   Large Midsize   Small  Sporty     Van 
     16      11      22      21      14       9

 

 

 

 

직관적으로 결과를 비교하기 편하도록 Cars93 데이터프레임에서 (a) 차종(Type), 가격(Price) 의 두개의 변수만 선별하고, (b) 차종(Tpye) 중에서 관측치 개수가 적은 'Large'와 'Van' 만 남긴 후에, (c) 차종(Type), 가격(Price) 를 기준으로 오름차순으로 정렬하여 "Cars93_1" 이라는 새로운 데이터프레임을 만들어 보겠습니다.

 

 
> # select 'Type', 'Price' variable from Cars93 dataframe
> select <- dplyr::select # to avoid conflict select() function b/w dplyr and MASS
> 
> Cars93_1 <- Cars93 %>% 
+   select(Type, Price) %>% 
+   filter(Type %in% c("Large", "Van")) %>% 
+   arrange(Type, Price)
> 
> Cars93_1
    Type Price
1  Large  18.4
2  Large  18.8
3  Large  19.3
4  Large  20.7
5  Large  20.8
6  Large  20.9
7  Large  23.7
8  Large  24.4
9  Large  29.5
10 Large  34.7
11 Large  36.1
12   Van  16.3
13   Van  16.6
14   Van  19.0
15   Van  19.1
16   Van  19.1
17   Van  19.5
18   Van  19.7
19   Van  19.9
20   Van  22.7

 

 

 

 

간소화해서 새로 만든 Cars93_1 데이터프레임에서 차종(Type) 별로 최소(min) 가격, 평균(mean) 가격, 중앙값(median) 가격, 최대(max) 가격을 구해보겠습니다.

 

> # calculating min/mean/median/max Price by Type
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   summarise(n = n(), # number by Type
+             Price_min = min(Price, na.rm = T), 
+             Price_mean = mean(Price, na.rm = T), 
+             Price_median = median(Price, na.rm = T), 
+             Price_max = max(Price, na.rm = T))
# A tibble: 2 x 6
    Type     n Price_min Price_mean Price_median Price_max
  <fctr> <int>     <dbl>      <dbl>        <dbl>     <dbl>
1  Large    11      18.4       24.3         20.9      36.1
2    Van     9      16.3       19.1         19.1      22.7

 

 

 

 

자, 이제 데이터셋 준비가 다 되었으니 본론으로 넘어가보겠습니다. 

 

함수에 대해서 말로 설명해놓기는 했습니다만, 잘 안 와닿을 것 같습니다.  예제를 보면서 각 함수가 어떤 기능을 하는지 찬찬히 살펴보시면 도움이 될거 같아요.  위의 요약통계량을 보니 가격 최소값 '18'을 조건으로 cumall()과 cumany()를 사용하면 "Large"와 "Van"이 서로 다른 결과를 반환하겠네요.

 

 

 (1) Cumulative aggregates : cumall() 함수, cumany() 함수, cummean() 함수

 

(1-1) cumall () 함수 : 조건을 모두 만족(cumulative &&)하는 (그룹의) 전체 행 반환

 

 
> # cumall()
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   filter(cumall(Price > 18))
Source: local data frame [11 x 2]
Groups: Type [1]

     Type Price
   <fctr> <dbl>
1   Large  18.4
2   Large  18.8
3   Large  19.3
4   Large  20.7
5   Large  20.8
6   Large  20.9
7   Large  23.7
8   Large  24.4
9   Large  29.5
10  Large  34.7
11  Large  36.1

 

 

 

좀더 이해하기 쉽도록 아래에 원래의 Cars93_1 데이터프레임과 차종(Type)별 cumall(Price > 18) 조건으로 선별(filtering)을 한 후의 결과를 비교해놓았습니다.  "Van" 차종의 경우 가격(Price)이 18 이하인 관측치(12번, 13번) 2개 존재하므로 cumall(Price > 18) 에서 제시한 "모든 관측치가 만족(%%)" 조건을 만족하지 않으므로 "모든 행이 제외"되었습니다.

 

 

 

 

(1-2) cumany() 함수 : 조건을 만족(cumulative ||)하는 (그룹 내) 행만 반환

 

 
> # cumany()
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   filter(cumany(Price > 18))
Source: local data frame [18 x 2]
Groups: Type [2]

     Type Price
   <fctr> <dbl>
1   Large  18.4
2   Large  18.8
3   Large  19.3
4   Large  20.7
5   Large  20.8
6   Large  20.9
7   Large  23.7
8   Large  24.4
9   Large  29.5
10  Large  34.7
11  Large  36.1
12    Van  19.0
13    Van  19.1
14    Van  19.1
15    Van  19.5
16    Van  19.7
17    Van  19.9
18    Van  22.7

 

 

 

 

좀더 이해하기 쉽도록 아래에 원래의 Cars93_1 데이터프레임과 차종(Type)별 cumany(Price > 18) 조건으로 필터링한 결과를 비교해놓았습니다. 12번째, 13번째 행의 "Van" 차종의 관측치가 cumany(Price > 18) 조건을 만족하지 않으므로 제외되었습니다.

 

 

 

 

 

(1-3) cummean() 함수 : (그룹별로) 행을 하나씩 이동해가면서 누적으로 평균 반환

 

mutate() 함수와 함께 사용해서 새로운 cummean.Price 변수를 만들어보겠습니다.

 

 
> # cummean()
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   mutate(cummean.Price = cummean(Price))
Source: local data frame [20 x 3]
Groups: Type [2]

     Type Price cummean.Price
   <fctr> <dbl>         <dbl>
1   Large  18.4      18.40000
2   Large  18.8      18.60000
3   Large  19.3      18.83333
4   Large  20.7      19.30000
5   Large  20.8      19.60000
6   Large  20.9      19.81667
7   Large  23.7      20.37143
8   Large  24.4      20.87500
9   Large  29.5      21.83333
10  Large  34.7      23.12000
11  Large  36.1      24.30000
12    Van  16.3      16.30000
13    Van  16.6      16.45000
14    Van  19.0      17.30000
15    Van  19.1      17.75000
16    Van  19.1      18.02000
17    Van  19.5      18.26667
18    Van  19.7      18.47143
19    Van  19.9      18.65000
20    Van  22.7      19.10000

 

 

 

 

group_by(Type) 함수를 같이 써서 차종(Type)별로 행을 하나씩 아래로 내려가면서 그룹 내 첫 행부터 행당 행까지의 누적 관측치를 모두 사용해서 평균(cumulative mean)을 구했음을 알 수 있습니다. (말로 설명하려니 힘든데요, 말로 된 설명만 봐서는 무슨 말이지 좀 어렵지요? ^^;;;  아래 설명 그림 참고하세요)

 

 

 

 

Cumulative aggregates 소개는 마치고, 이제 Recycled aggregates로 넘아가보겠습니다.

평균이나 중앙값과 같이 (그룹별) 요약통계량을 생성한 후에, 이 요약통계량 벡터를 재활용(Recycling)해서 조건을 부여해 filtering 하는 방법을 아래에 소개합니다.  이름은 Recycled aggregates 라고 거창하게 붙이긴 했는데요, 아래의 예시를 보시면 뭐 별거 없습니다.

 

 

(2) Recycled aggregates

     : group_by(factor) %>% filter(dataframe, x > mean(x)),
     : group_by(factor) %>% filter(dataframe, x > median(x))
 

 

(2-1) group_by(factor) %>% filter(dataframe, x > mean(x))
       : 그룹별로 평균 값(mean)보다 큰 행(rows)만 선별

 

 

> ##------------------------------------------------ > ## R dplyr package > ## > window function - Recycled aggregates > ## : filter(dataframe, x > mean(x)) > ## : filter(dataframe, x > median(x)) > ##------------------------------------------------ > Cars93_1 %>% + group_by(Type) %>% + summarise(n = n(), # number by Type + Price_mean = mean(Price, na.rm = T), + Price_median = median(Price, na.rm = T)) # A tibble: 2 x 4 Type n Price_mean Price_median <fctr> <int> <dbl> <dbl> 1 Large 11 24.3 20.9 2 Van 9 19.1 19.1 > > > # filter(dataframe, x > mean(x)) > Cars93_1 %>% + group_by(Type) %>% + filter(Price > mean(Price)) Source: local data frame [8 x 2] Groups: Type [2] Type Price <fctr> <dbl> 1 Large 24.4 2 Large 29.5 3 Large 34.7 4 Large 36.1 5 Van 19.5 6 Van 19.7 7 Van 19.9 8 Van 22.7 >

 

 

 

 

 

(2-2) group_by(factor) %>% filter(dataframe, x > median(x))

      : 그룹별로 중앙값(median) 보다 큰 행(rows)만 선별

 

 

> # filter(dataframe, x > median(x))
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   filter(Price > median(Price))
Source: local data frame [9 x 2]
Groups: Type [2]

    Type Price
  <fctr> <dbl>
1  Large  23.7
2  Large  24.4
3  Large  29.5
4  Large  34.7
5  Large  36.1
6    Van  19.5
7    Van  19.7
8    Van  19.9
9    Van  22.7

 

 

 

많은 도움 되었기를 바랍니다.

 

 

참고로, {base} package에 기본으로 내장되어 있는 함수들인
 - 누적 합 (cumulative sums) : cumsum()
 - 누적 곱 (cumulative products) : cumprod()
 - 누적 최소값 (cumulative minima) : cummin()
 - 누적 최대값 (cumulative maxima) : cummax()

에 대해서는 여기( ☞ http://rfriend.tistory.com/231 )를 참고하세요.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡'를 눌러주세요. ^^

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

window function은 n개의 행을 input으로 받아서 n개의 행을 가진 output을 반환하는 함수를 말합니다.

 

지난번 포스팅에서는 dplyr package의 window function 중에서 Ranking and Ordering을 하는 함수들로서 row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), ntile() 에 대해서 알아보았습니다. (바로가기 http://rfriend.tistory.com/241)

 

이번 포스팅에서는 dplyr 패키지의 window function 두번째 시간으로서 특정 칼럼의 행을 위로 올리거나(Lead) 아니면 내리는(Lag) 함수에 대해서 알아보겠습니다.

 

lead() 나 lag() 함수는 시계열 데이터를 분석할 때 많이 사용하는 편입니다. 특정 그룹id와 날짜/시간 기준으로 정렬(sorting)을 해놓은 다음에, lead() 나 lag() 함수를 가지고 행을 하나씩 내리구요, 직전 날짜/시간 대비 이후의 값의 변화, 차이(difference)를 구하는 식으로 말이지요.

(시계열분석에 특화된 package를 사용하면 더 편하기 하지만.... 데이터 프레임을 가지고 dplyr 패키지의 lead()나 lag() 함수 알아놓는것도 유용해요)

 

 

 

 

 

먼저 간단한 벡터를 가지고 lead()와 lag() 사용법을 소개하겠습니다.

 

 

(1) lead(x, n = 1L, default = NA, ...) in {dplyr} package

 

lead() 함수는 벡터 값을 n = 1L (양의 정수값) 의 값 만큼 앞에서 제외하고, 제일 뒤의 n = 1L 값만큼의 값은 NA 로 채워놓은 값을 반환합니다.  이때, n  표기는 생략할 수 있습니다.

 

 

> ##-------------------------------------------------
> ## R dplyr package : window function - lead and lag
> ##-------------------------------------------------
> 
> library(dplyr)
> 
> # lead()
> x <- c(1:10)
> 
> lead(x, n = 1)
 [1]  2  3  4  5  6  7  8  9 10 NA
> 
> lead(x, 2)
 [1]  3  4  5  6  7  8  9 10 NA NA

 

 

 

 

(2) lag(x, n = 1L, default = NA, ...) in {dplyr} package

 

lag() 함수는 lead() 함수와 정반대로 생각하시면 됩니다.  lag() 함수의 n = 1L(양의 정수값) 만큼 제일 앞자리부터 뒤로 옮기고, n = 1L 개수 만큼의 자리에 NA 값을 채워넣은 값을 반환합니다.

 

default = "." 처럼 특정 값을 설정해주면 NA 대신 새로 설정해준 값 혹은 기호가 채워진 값을 반환합니다. (아래 세번째 예의 경우 "."으로 빈 자리가 채워지면서 모든 값에 큰 따옴표("")가 붙으면서 character 형태로 바뀌었습니다)

 

 

> # lag()
> x <- c(1:10)
> 
> lag(x, n = 1)
 [1] NA  1  2  3  4  5  6  7  8  9
> 
> lag(x, 2)
 [1] NA NA  1  2  3  4  5  6  7  8
> 
> lag(x, 2, default = ".")
 [1] "." "." "1" "2" "3" "4" "5" "6" "7" "8"

 

 

 

 

 


 

[문제] 위의 x_df 데이터 프레임의 group 별로 직전 대비 직후 값의 차이가 가장 큰 값(max)과 가장 작은 값을 각각 구하시오. (What are the max and min difference values between x and lag(x) of x_df dataframe by group?)

 

 

(0) 예제 데이터 프레임 만들기

 

분석할 때 보통 벡터 보다는 데이터 프레임을 가지고 많이 하므로 예제 데이터 프레임을 하나 만들어보겠습니다.  'group'이라는 요인(factor)형 변수와 seq_no 이라는 시간 순서를 나타내는 변수, 그리고 각 group별로 5개씩의 관찰값을 가진 숫자형(numeric) 변수 x로 구성된 데이터 프레임입니다.

 

 

> ##-- make data frame as an example
> group <- rep(c("A", "B"), each = 5)
> seq_no <- rep(1:5, 2)
> set.seed(1234)
> x <- round(100*runif(10), 1)
> 
> x_df <- data.frame(group, seq_no, x)
> x_df
   group seq_no    x
1      A      1 11.4
2      A      2 62.2
3      A      3 60.9
4      A      4 62.3
5      A      5 86.1
6      B      1 64.0
7      B      2  0.9
8      B      3 23.3
9      B      4 66.6
10     B      5 51.4

 

 

 

 

(1) lag() 하려고 하는 기준대로 정렬이 안되어 있으면 -> 먼저 정렬(sorting) 부터!

 

예제로 사용하려고 sample(nrow()) 함수로 무작위로 순서를 섞어서 x_df_random 이라는 데이터 프레임을 만들어보았습니다.  dplyr 패키지의 arrange() 함수를 가지고 group, seq_no 기준으로 정렬을 해보겠습니다. 

 

 

> # if data frame is not ordered properly, 
> # then arrnage it first by lag criteria
> x_df_random <- x_df[sample(nrow(x_df)),]
> x_df_random
   group seq_no    x
7      B      2  0.9
5      A      5 86.1
3      A      3 60.9
10     B      5 51.4
2      A      2 62.2
9      B      4 66.6
6      B      1 64.0
1      A      1 11.4
8      B      3 23.3
4      A      4 62.3
> 
> x_df_seq <- arrange(x_df_random, group, seq_no)
> x_df_seq
   group seq_no    x
1      A      1 11.4
2      A      2 62.2
3      A      3 60.9
4      A      4 62.3
5      A      5 86.1
6      B      1 64.0
7      B      2  0.9
8      B      3 23.3
9      B      4 66.6
10     B      5 51.4

 

 

 

 

(2) mutate() 함수와 lag() 함수로 group_x, x_lag 변수 만들기

 

 

> # making lagged variable at data frame with mutate() and lag() > x_df_seq_lag <- mutate(x_df_seq, + group_lag = lag(group, 1), + x_lag = lag(x, 1)) > > x_df_seq_lag group seq_no x group_lag x_lag 1 A 1 11.4 <NA> NA 2 A 2 62.2 A 11.4 3 A 3 60.9 A 62.2 4 A 4 62.3 A 60.9 5 A 5 86.1 A 62.3 6 B 1 64.0 A 86.1 <- need to delete this row 7 B 2 0.9 B 64.0 8 B 3 23.3 B 0.9 9 B 4 66.6 B 23.3 10 B 5 51.4 B 66.6

 

 

 

 

(3) group 과 group_lag 의 값이 서로 다르면 그 행(row)은 filter() 함수로 제외하기

 

 

> # if group and group_lag are different, then delete the row
> x_df_seq_lag_2 <- x_df_seq_lag %>% 
+   filter(group == group_lag)
> 
> x_df_seq_lag_2
  group seq_no    x group_lag x_lag
1     A      2 62.2         A  11.4
2     A      3 60.9         A  62.2
3     A      4 62.3         A  60.9
4     A      5 86.1         A  62.3
5     B      2  0.9         B  64.0
6     B      3 23.3         B   0.9
7     B      4 66.6         B  23.3
8     B      5 51.4         B  66.6

 

 

 

 

(4) group별로 x와 x_lag 값의 차이가 가장 큰 값(max)과 가장 작은 값(min) 구하기

 

지지난번 포스팅에서 소개했던 group_by()와 summarise(max()), summarise(min()) 함수를 이용하면 되겠습니다.

 

> # select max and min of difference between x and x_lag
> x_df_seq_lag_2 %>% 
+   group_by(group) %>% 
+   summarise(max_lag = max(x - x_lag, na.rm = TRUE), 
+             min_lag = min(x - x_lag, na.rm = TRUE))
# A tibble: 2 x 3
   group max_lag min_lag
  <fctr>   <dbl>   <dbl>
1      A    50.8    -1.3
2      B    43.3   -63.1

 

 

 

그리 어렵지 않지요? 

많은 도움이 되었기를 바랍니다.

 

다음번 포스팅에서는 dplry package window function 세번째로 시간으로 Cumulative aggregates 를 하는데 사용하는 cumall(), cumany(), cummean() 함수, 그리고 Recycled aggregates 에 대해서 알아보겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡'를 눌러주세요.

(Tistory 가 포스팅별로 조회수를 알려주는 기능이 없다보니 '공감♡' 개수로 참고하려고)

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

R dplyr 패키지에 대해서 연재를 하고 있는데요, 번 포스팅에서는 Window Function 에 대해서 소개를 하겠습니다.

 

Window function 은 n개의 input을 받아서 n 개의 output을 반환하는 함수입니다.  순위(rank)를 구하는 함수나 누적합(cumulative sum)을 구하는 함수가 대표적인 Window function입니다.

 

이와 대비되는 Aggregation function n개의 input을 받아서 1개의 output 을 반환합니다.  요약통계량의 평균(mean), 합계(sum), 개수 세기(count) 등이 Aggregation function 에 속합니다.

 

 

[ Window function vs. Aggregation function ]

 

 

 

 

R의 {dplyr} package에서 사용할 수 있는 Window function에는 (1) Ranking and Ordering, (2) Lead and Lag, (3) Cumulative aggregates, (4) Recycled aggregates 등의 4가지 유형이 있습니다.

 

 

[ Types of Window functions in R {dplyr} package ]

 

 

 

차례대로 설명을 할 건데요, 한꺼번에 모두를 소개하기는 양이 꽤 많으니 이번에는 (1) Ranking and Ordering 의 row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), ntile() 함수에 대해서만 우선 소개해드리겠습니다.

 

 - row_number(), min_rank(), dense_rank() 의 3개 함수는 순위에 대한 index를 반환하고,  

 - cume_dist(), percent_rank() 의 2개 함수는 순위에 대한 '0~1' 사이의 비율을 반환하며,

 - ntile() 은 n개의 동일한 개수로 데이터셋을 나누어줍니다.

 

 

먼저 순위(ranking) 에 대한 index 를 반환해주는 함수 3개를 살펴보겠습니다. 동일한 값이 있을 경우에 이를 처리하는 방법이 함수별로 차이가 있습니다. 

 

말로 일일이 설명하기가 좀 어려운데요, 아래에 c(1, 1, 1, 5, 5, 9, 7) 의 동일한 값 '1'이 3개, '5'가 2개 존재하는 간단한 벡터를 가지고 함수별로 어떤 결과를 반환하는지 비교해보는 예를 준비했습니다.  색깔 칠해놓은 동일 값들을 어떻게 index 처리하는지 유심히 비교해보시고요, 분석 목적에 맞는 함수를 선택해서 사용하시기 바랍니다.

 

 

(1-1) row_number() : 순위(ranking) index 반환, 동일값에 대해서는 '1, 2, 3, ...' 처리

 

 

##--------------------------------------------
## Window Functions in {dplyr} package
## (1) ranking and ordering window functions
##--------------------------------------------

library(dplyr)

# if there are ties, ties can be handled in several ways
x <- c(1, 1, 1, 5, 5, 9, 7)

# (1-1) row_number()
row_number(x)

 

 

 

[1] 1 2 3 4 5 7 6

 

 

 

 

(1-2) min_rank() : 순위(ranking) index 반환, 동일값에 대해서는 '1, 1, 1, 4, 4,...' 처리

 

 

# (1-2) min_rank()
x <- c(1, 1, 1, 5, 5, 9, 7)

min_rank(x)

 

 

[1] 1 1 1 4 4 7 6

 

 

 

 

(1-3) dense_rank() : 순위(ranking) index 반환, 동일값에 대해서는 '1, 1, 1, 2, 2,...' 처리

 

 

# (1-3) dense_rank()
x <- c(1, 1, 1, 5, 5, 9, 7)

 

dense_rank(x)

 

 

[1] 1 1 1 2 2 4 3

 

 

 

참고로 {base} 패키지의 rank() 함수도 동일한 기능을 제공하는데요, ties.method = c("average", "first", "random", "max", "min") 매개변수 별로 어떤 결과가 나오는 지는 http://rfriend.tistory.com/28  포스팅의 제일 하단 예시를 참고하시기 바랍니다.

 

 


 

 

아래 두개의 cume_dist(), percent_rank() 함수는 순위(ranking)에 대한 0~1 사이의 비율을 반환하는데요, 둘 사이에 미묘한 차이가 있으니 유심히 살펴보시기 바랍니다.

 

(1-4) cume_dist() : 현재 값보다 작거나 동일한 값의 순위(ranking) 상의 비율 (0~1)

 

 

# (1-4) cume_dist()
# : the proportion of values less than or equal to the current value
# : proportional value from 0 to 1
x <- c(1, 1, 1, 5, 5, 9, 7)
cume_dist(x)

 

 

[1] 0.4285714 0.4285714 0.4285714 0.7142857 0.7142857 1.0000000 0.8571429

 

 

 

위의 결과가 어떻게 나왔냐 하면요, 전체 7개의 숫자 중에서 첫번째 순위에 속하는 '1'이 3개 있으므로 3/7 = 0.4285714 가 나온 것입니다.  두번째 순위에 속하는 '5'는 2개가 있으며, 5보다 작은 수로 첫번째 순위의 '1'이 3개가 있으므로 '5'의 2개와 ''1'의 3개를 합친 5개를 총 개수인 7로 나눈 5/7 = 0.7142857이 나온 것입니다. (말로 설명하기가 참 어렵네요. -_-;)

 

 

> 3/7 # ordering like as c(1, 1, 1)
[1] 0.4285714
> 5/7 # ordering like as c(1, 1, 1, 5, 5)
[1] 0.7142857
> 6/7 # ordering like as c(1, 1, 1, 5, 5, 7)
[1] 0.8571429
> 7/7 # ordering like as c(1, 1, 1, 5, 5, 7, 9)
[1] 1

 

 

 

참고로, "패키지명:::함수명"을 실행하면 R 함수의 내부 소스 코드 (generic function)를 볼 수 있습니다.

cume_dist() 함수는 {base} 패키지의 rank(x, ties.method = "max") 를 가져다고 총 개수로 나누어준 것임을 알 수 있습니다.

 

> dplyr:::cume_dist
function (x) 
{
    rank(x, ties.method = "max", na.last = "keep")/sum(!is.na(x))
}
<environment: namespace:dplyr>

 

 

 

 

 

(1-5) percent_rank() : min_rank() 기준의 순위(ranking)에 대한 비율(0~1)

 

 

# (1-5) percent_rank()
# : the percentage of the rank
# : proportional value from 0 to 1
x <- c(1, 1, 1, 5, 5, 9, 7)

 

percent_rank(x)

 

 

[1] 0.0000000 0.0000000 0.0000000 0.5000000 0.5000000 1.0000000 0.8333333

 

 

 

percent_rank() 함수의 내부 소스 코드를 들여다보면 아래와 같이 dplyr패키지의 min_rank() 값에서 1을 뺀 후에, 이를 총 관측값 개수에서 1을 뺀 값으로 나누었군요.  소스 코드를 보지 않고서는 연산 로직을 알기가 쉽지 않은 경우입니다. (위 결과가 어떻게 나온건지 한참을 고민했네요... -_-;)

 

이제 왜 첫번째 순위의 '1'이 '0.0000000'이 나왔는지 아시겠지요?  min_rank(x) 가

[1] 1 1 1 4 4 7 6

의 값을 반환하므로 분자에 해당하는 (min_rank(x) - 1) = 1 - 1 = 0 이 되어, 전체 값이 '0'이 된 것입니다.

 

 

> dplyr:::percent_rank
function (x) 
{
    (min_rank(x) - 1)/(sum(!is.na(x)) - 1)
}
<environment: namespace:dplyr>

 

 

 

 


 

 

 (1-6) ntile() : 동일한 개수를 가진 n개의 sub 데이터로 나누기

 

Cars93 데이터프레임의 가격(Price)를 기준으로 정렬(ordering)이 된 상태에서 동일한 개수를 가진 4개의 sub group으로 나누려고 할 때 ntile() 함수를 쓰면 됩니다. mutate() 함수를 사용해서 quartile 이라는 새로운 칼럼을 생성한 후에 Cars93_quartile 이라는 새로운 데이터프레임을 생성해보는 예제를 만들어보았습니다.

 

 

# (1-6) ntile() : divides the data up into n evenly sized buckets
#Price per Price Quartiles (4 evenly sized buckets)

library(MASS) # to use Cars93 data frame

 

Cars93_quartile <- Cars93[ ,c("Manufacturer", "Model", "Type", "Price")] %>%
  mutate(quartile = ntile(Price, 4))

 

 

 

 

 

 

quartile 이라는 새로운 칼럼에 가격(Price)를 기준으로 순위 정렬이 된 상태에서 4등분된 buckets별로 몇 개씩 자동차가 들어가 있는지 한번 세어보겠습니다.  (summarise(n = n()), tally(), count() 함수 중에 편한거 사용하면 되겠습니다. )  1번째 bucket 에는 24개 자동차가 들어가 있고, 나머지 3개 bucket에는 23개씩 동일하게 들어가 있습니다. (총 93개로 정확히 4등분 할 수 없는 경우라서 그렇습니다)

 

 

# counting up by quartiles
Cars93_quartile %>% group_by(quartile) %>% summarise(n = n())

 

# A tibble: 4 x 2
  quartile     n
     <int> <int>
1        1    24
2        2    23
3        3    23
4        4    23

 

Cars93_quartile %>% group_by(quartile) %>% tally() # same result
Cars93_quartile %>% count(quartile) # same result

 

 

 

이상으로 dplyr 패키지의 Window function 중에서 첫번째로 ranking and ordering 관련 함수들인 row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), ntile() 에 대한 소개를 마치겠습니다.  많은 도움이 되었기를 바랍니다.

 

다음번 포스팅에서는 dplyr 패키지의 Window function 의 두번째 시간으로 'Lead and Lag'에 대해서 알아보겠습니다.

 

 

이번 포스팅에 도움이 되었다면 아래의 '공감 ~ ♡'를 꾹 눌러주세요. ^^

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

그동안 R dplyr package 의 기본 함수와 chaining operator (%>%) 에 대해서 알아보았습니다.

이번 포스팅에서는 R dplyr package를 사용해서 그룹별로 행의 개수 세기 (counting rows up by group using dplyr package)를 해보겠습니다.  counting 하는 것은 기본 중의 기본이라서 탐색적분석(Exploratory Data Analysis) 할 때 수시로 사용하므로 알아두면 유용하겠지요?!

혹시 chaining operator (%>%, shift+ctr+M) 에 대해서 모르는 분은 http://rfriend.tistory.com/236 포스팅을 참고하세요.  

 R dplyr package 의 summarise(n = n()), summarise(dist_n = distinct_n(factor)), tally(), count() 함수에 대해서 하나씩 예를 들면서 설명하겠습니다.  summarise(n = n()), summarise(dist_n = distinct_n(factor)), tally() 함수는 group_by()를 chaining 해서 사용하며, 마지막의 count() 함수만 group_by() chaining 없이 사용합니다.

 

[ 그룹별로 행의 개수 세기 (counting rows up by group using dplyr package) ]

 

예시에 사용할 데이터는 MASS package에 내장되어 있는 Cars93 데이터 프레임입니다.

 ##--------------------------------------------------
## counting things up by group, using dplyr package
##--------------------------------------------------

library(dplyr)
library(MASS)

str(Cars93)

 

차종(Type)별로 행의 개수 (차량의 개수)를 세어보겠습니다.

 
'data.frame':	93 obs. of  28 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
 $ sub_yn            : num  0 0 0 0 0 0 0 0 0 0 ...

 

 

자, 이제 dplyr 패키지를 사용해서 실습을 해보시지요.

 

(1)  dataframe %>% group_by(factor) %>% summarise(n = n())

먼저 summarise(n = n()) 함수입니다. group_by() 와 함께 chaining 해서 사용하는 예시입니다.

 

# using summarise(n = n()) in {dplyr} package
Cars93 %>%
  group_by(Type) %>%
  summarise(n = n())

 

 

# A tibble: 6 x 2
     Type     n
   <fctr> <int>
1 Compact    16
2   Large    11
3 Midsize    22
4   Small    21
5  Sporty    14
6     Van     9

 

 

 

 (2) dataframe %>% group_by(factor) %>% summarise(n = n(), n_dist = n_distinct())

차종(Type)별로 자동차 대수(행의 개수)와 더불어서, 차종별로 유일한 제조회사(Manufacturer)의 개수를 세어서 n_distinct_maker 라는 새로운 변수를 추가해보겠습니다.

 

# adding the number of distinct manufacturers
# by using multiple summaries inside summarise()
# like summarize(n = n(), n_distinct = n_distinct())
Cars93 %>%
  group_by(Type) %>%
  summarize(n = n(),
            n_distinct_maker = n_distinct(Manufacturer)) 

 

 

# A tibble: 6 x 3
     Type     n       n_distinct_maker
   <fctr> <int>            <int>
1 Compact    16               15
2   Large    11               10
3 Midsize    22               20
4   Small    21               16
5  Sporty    14               12
6     Van     9                8

 

 

 

 (3) dataframe %>% group_by(factor) %>% tally()

 R dplyr에는 summarise(n = n()) 함수와 동일한 기능, 동일한 결과를 반환하는 또 다른 함수로 tally() 가 있습니다.  group_by() 와 함께 chaining 해서 사용합니다.

 

# using tally() in {dplyr} package
Cars93 %>%
  group_by(Type) %>%
  tally()

 

 

# A tibble: 6 x 2
     Type     n
   <fctr> <int>
1 Compact    16
2   Large    11
3 Midsize    22
4   Small    21
5  Sporty    14
6     Van     9

 

 

 (4) dataframe %>% count(factor)

마지막으로 소개할 count(factor) 함수는 group_by(factor) chaining 없이 사용합니다.  위의 3개 보다 좀더 간단하긴 한데요, 해석이나 가독성 면에서 group_by() 가 들어가게 프로그램 짜는 것을 더 선호하는 사용자도 있을 듯 합니다. 

 

# using count() in {dplyr} package}
Cars93 %>%
  count(Type) # doing both grouping and counting (no need for group_by())

 

 

# A tibble: 6 x 2
     Type     n
   <fctr> <int>
1 Compact    16
2   Large    11
3 Midsize    22
4   Small    21
5  Sporty    14
6     Van     9

 

 


 

참고로, dplyr 패키지 말고도 {base} package의 table() 함수나, {sqldf} package의 sqldf() 함수를 사용해도 그룹별 관측치 개수 세기가 가능합니다.  아래 참고하세요.

 

(대안 1) {base} package의 table() 함수

counting 결과 제시 포맷이 위의 dplyr 패키지를 사용했을 때와는 다릅니다.  dplyr 패키지를 사용한 그룹별 행의 개수 세기에서는 차종(Type)이 별도 행, count 개수 n이 별도 행으로 제시가 되었었는데요, base 패키지의 table() 함수는 아래의 예시처럼 옆으로 차종이 죽~ 늘어서 있습니다.

 

## alternative : table()

# using table() in {base} package
table(Cars93$Type)

 

 

Compact   Large Midsize   Small  Sporty     Van 
     16      11      22      21      14       9 

 

 

table() 분석 결과는 데이터 프레임이 아니라 'table'입니다.

> str(table(Cars93$Type))
 'table' int [1:6(1d)] 16 11 22 21 14 9
 - attr(*, "dimnames")=List of 1
  ..$ : chr [1:6] "Compact" "Large" "Midsize" "Small" ...

 

 

(대안 2) {sqldf} 패키지의 sqldf() 함수

 

## alternative : sqldf() 

# using {sqldf} package
install.packages("sqldf")
library(sqldf)
sqldf('
      select Type, count(*) as n
      from Cars93
      group by Type
      order by Type
      ')

 

 
     Type  n
1 Compact 16
2   Large 11
3 Midsize 22
4   Small 21
5  Sporty 14
6     Van  9

 

 

sqldf 패키지의 sqldf() 함수에 대한 좀더 자세한 사용법은 http://rfriend.tistory.com/79  포스팅을 참고하세요.

많은 도움 되었기를 바랍니다.

 

이번 포스팅이 도움이 되셨다면 아래의 '공감 ~ ♡'를 눌러주세요. ^^

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

아래의 예제와 같이 콤마로 구분되어 있는 문자형 변수(caracter variable with comma seperator delimiter)를 가진 데이터 프레임이 있다고 합시다.

 

두번째의 item 변수에 콤마로 구분되어 들어있는 다수의 항목들을 구분자 콤마(',')를 기준으로 분리(split)한 후에 => 이를 동일한 name을 key로 해서 세로로 주욱~ 늘여서 재구조화하여야 할 필요가 생겼다고 합시다.

 

데이터 구조변환의 전/후 모습을 말로 설명하려니 좀 어려운데요, 아래의 'Before (original dataset) => After (transformation)' 의 그림을 참고하시기 바랍니다.

 

 

 

이걸 R로 하는 방법을 소개합니다.

 


 

먼저 위의 Before 모양으로 간단한 데이터 프레임을 만들어보았습니다.

 


 ##--------------------------------------------------------
## splitting character of dataframe and reshaping dataset
##--------------------------------------------------------

 

# (1) creating sample dataframe
name_1 <- c("John")
item_1 <- c("Apple,Banana,Mango")

name_2 <- c("Jane")
item_2 <- c("Banana,Kiwi,Tomato,Mango")

name_3 <- c("Tom")
item_3 <- c("Apple,Tomato,Cherry,Milk,IceCream")

tr_1 <- cbind(name_1, item_1)
tr_2 <- cbind(name_2, item_2)
tr_3 <- cbind(name_3, item_3)
mart <- data.frame(rbind(tr_1, tr_2, tr_3))

 

# rename
install.packages("dplyr")
library(dplyr)
mart <- rename(mart,
               name = name_1,
               item = item_1)

 

#-------------

> mart
  name                              item
1 John                Apple,Banana,Mango
2 Jane          Banana,Kiwi,Tomato,Mango
3  Tom Apple,Tomato,Cherry,Milk,IceCream
 

 

 

 


 

위의 mart 라는 이름의 'Before' 데이터프레임을 'After' 모양의 데이터 프레임으로 구조 변환시키는 절차 및 R script는 아래와 같습니다.

 

 

[ 문자형 분리(split) 및 데이터 재구조화(reshape) 절차 ]

 

(1) mart_new <- data.table() : mart_new 라는 비어있는 데이터 테이블, 데이터 프레임을 생성  (<= 여기에 차곡차곡 loop돌린 결과를 rbind()로 쌓아가게 됨)

 

(2) n <- nrow(mart) : original dataset인 mart 의 총 행의 개수(nrow(mart))를 n 이라는 벡터로 할당 (<= 이 개수만큼 loop를 돌릴겁니다. 위 예제는 총 3개군요. John, Jane, Tom 이렇게요)

 

(3) for (i in 1:n)  : 1부터 n (즉, 3)까지 반복 수행 <= 매 행별로 아래의 문자열을 구분자를 기준으로 분리 후 세로로 재구조화하는 작업을 반복할 겁니다

 

(4) print(i) : 반복수행 loop 문이 어디쯤 돌고 있나 콘솔 창에서 확인하려고 집어넣었습니다. 굳이 없어도 됩니다.

 

(5) name_index <- as.character(mart[i, 1])  : i번째 행(row)의 1번째 열("name")을 indexing 해옵니다. 즉, i의 순서 1, 2, 3 별로 "John", "Jane", "Tom", 이렇게 순서대로 indexing 해옵니다.  

 

(6) item_index <- as.character(mart[i, 2])  : i번째 행(row)의 2번째 열("item")을 indexing 해옵니다. 

 

(7) item_index_split_temp <- data.frame(strsplit(item_index, split = ',')) : (6)번에서 i번째 행별로 indexing해 온 item을 구분자 'comma (',')'를 기준으로 문자열을 분리(split)한 후에, 이것을 데이터 프레임으로 생성합니다.

 

(8) mart_temp <- data.frame(cbind(name_index, item_index_split_temp))  : (5)번과 (7)번 결과물을 cbind()로 묶은 후에, 이것을 데이터 프레임으로 생성합니다.  cbind()로 결합할 때 name은 1개인데 item이 2개 이상일 경우에는 1개밖에 없는 name이 자동으로 반복으로 item의 개수만큼 재할당이 됩니다.

 

(9) names(mart_temp) <- c("name", "item")  : mart_temp의 변수 이름을 첫번째 변수는 "name", 두번째 변수는 "item"으로 지정해줍니다.

 

(10) mart_new <- rbind(mart_new, mart_temp) : (1)번에서 생성한 (처음에는 비어있는) 데이터프레임에 loop를 돌 때마다 생성이되는 (8, 9)번 데이터프레임을 차곡 차곡 rbind()로 반복적으로 쌓아줍니다.

(11) rm(name_index, item_index, item_index_split_temp, mart_temp)  :  중간 임시 데이터셋 삭제

 

끝.

 

 

[ R script ]

 

 

# (2) splitting charater by delimeter, reshaping data structure by loop programming

install.packages("data.table")
library(data.table)

mart_new <- data.table() # blank data.table

 

n <- nrow(mart) # total number of rows

 

for (i in 1:n){
 

  print(i) # to check progress
 
  name_index <- as.character(mart[i, 1])
  item_index <- as.character(mart[i, 2])
 
  item_index_split_temp <- data.frame(strsplit(item_index, split = ','))
  mart_temp <- data.frame(cbind(name_index, item_index_split_temp))
 
  names(mart_temp) <- c("name", "item")
 
  mart_new <- rbind(mart_new, mart_temp)
}
 

rm(name_index, item_index, item_index_split_temp, mart_temp) # delete temp dataset

 

# ----------------

> mart_new
    name     item
 1: John    Apple
 2: John   Banana
 3: John    Mango
 4: Jane   Banana
 5: Jane     Kiwi
 6: Jane   Tomato
 7: Jane    Mango
 8:  Tom    Apple
 9:  Tom   Tomato
10:  Tom   Cherry
11:  Tom     Milk
12:  Tom IceCream
 

 

 

 


 

Ashtray 님께서 댓글로 남겨주신 R script를 여러 사람이 좀더 쉽게 널리 공유할 수 있도록 아래에 공유합니다 (Ashtray님, R script 공유해주셔서 감사합니다. ^^).

 

구분자가 "\\^"를 기준으로 문자열을 분리하고, for loop 문을 사용해서 세로로 세워서 재구조화하는 절차는 같습니다.  대신 변수 개수가 다르다보니 for loop문이 조금 다르구요, data table이 아니라 list()를 사용했다는점도 다릅니다. 

 

R script 짜다보면, 그리고 다른 분께서 R script 짜놓은것을 보면 "동일한 input과 동일한 output이지만 가운데 process는 방법이 단 한가지만 있는게 아니구나" 하고 저도 배우게 됩니다.

 

 

names(data) <- c("number", "PK", "Call_ID", "Keyword_Count")
data$Keyword_Count <- strsplit(data$Keyword_Count, split="\\^")

datalist <- list()
k <- 1
for(i in 1:nrow(data)){
    for(j in 1:length(unlist(data$Keyword_Count[i]))){
        datalist[[k]] <- list(data$number[i], data$PK[i], data$Call_ID[i],
        data$Keyword_Count[[i]][j])
    k <- k+1
    }
}

data2 <- rbindlist(datalist)
data2[[4]] <- unlist(data2[[4]])

 

 

 


 

혹시 Spark을 사용하는 분이라면 (key, value) pair RDD로 구성된 데이터셋에 대해 flatMapValues() 라는 pair operation 을 사용하면 쉽게 key를 기준으로 세로로 재구성한 RDD를 만들 수 있습니다.  참고하세요.

 

 

 

많은 도움이 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~ ♡'를 꾸욱 눌러주세요.

 

 

[Reference]

1. R 문자함수 : http://rfriend.tistory.com/37

2. R 반복 연산 loop 프로그래밍 : http://rfriend.tistory.com/90

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

R dplyr 패키지의 select() 함수를 쓰다보면 'Error in select() : Unused arguments in select() error' 가 발생하는 경우가 있는데요, 그 이유와 해결방법 3가지를 소개하겠습니다.

 

 

 

 

먼저 R dplyr 패키지를 로딩하고, 실습 예제로 사용할 MASS 패키지의 Cars93 데이터셋에 대해 살펴보면 아래와 같습니다.  (Cars93은 자동차 93개의 관측치, 차량 정보 27개의 변수를 가진 데이터 프레임).

 

> library(dplyr)
> library(MASS) # dataframe Cars93
> str(Cars93)
'data.frame':	93 obs. of  27 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...

 

 

 

 

(1) dplyr 패키지 select() 함수, unused arguments() error 가 왜 발생하나?

 

> # Error in select() of dplyr package
> select(Cars93, Price, MPG.highway)
Error in select(Cars93, Price, MPG.highway) : 
  unused arguments (Price, MPG.highway) 

 

dplyr select() 함수를 사용해서 변수를 선택하고 싶은데 위처럼 'Error in select() : unused arguments()' 에러가 발생하는 이유는 'MASS' 패키지의 select()함수와 'dplyr' 패키지의 select() 함수가 충돌하기 때문입니다.

 

 

 

해결 방법은 3가지가 있습니다.

 

(방법 1) dplyr::select()   <= select는 dplyr 패키지의 select 임을 명시적으로 지정

 

> # (a) 1st solution for Error in select() of dplyr package > dplyr::select(Cars93[c(1:6), ], Price, MPG.highway) Price MPG.highway 1 15.9 31 2 33.9 25 3 29.1 26 4 37.7 26 5 30.0 30 6 15.7 31 

 

만약 MASS 패키지의 select() 함수를 쓰고 싶다면 MASS::select() 라고 해주면 됩니다.  매번 select() 함수 앞에 패키지명을 붙이려면 좀 번거롭겠지요?  

 

아래의 (방법 2)로 설정해주면 select() 함수는 계속 dplyr 패키지의 것으로 사용할 수 있게 됩니다.  

 

 

 

 

(방법 2) select <- dplyr::select    <= select는 dplyr 패키지의 select 임을 명시적으로 지정

 

> # (b) 2nd solution for Error in select() of dplyr package > select <- dplyr::select > head(select(Cars93, Price, MPG.highway)) Price MPG.highway 1 15.9 31 2 33.9 25 3 29.1 26 4 37.7 26 5 30.0 30 6 15.7 31 

 

 

 

 

(방법 3) MASS 패키지를 먼저 로딩하고, dplyr 패키지를 나중에 로딩하기

 

> # (c) 3rd solution for Error in select() of dplyr package
> library(MASS) 
> library(dplyr) # dplyr loading after MASS
> head(select(Cars93, Price, MPG.highway))
  Price MPG.highway
1  15.9          31
2  33.9          25
3  29.1          26
4  37.7          26
5  30.0          30
6  15.7          31 

 

어떤 때는 에러가 없다가도 어떤 때는 에러가 발생하는 이유가 패키지 로딩 순서에도 있습니다.  이렇다 보니 R 사용자 입장에서는 '어? 지난번에는 에러가 안났는데...이거 뭐가 잘못된거지?' 하면서 당황하게 되는거 같습니다.

 

MASS 패키지의 select() 함수, pylr 패키지의 summarise() 함수, stats 패키지의 filter() 함수 등이 dplyr 패키지의 동일한 이름의 함수와 충돌하곤 합니다.  패키지간 함수 충돌로 인한 'Error in function : unused arguments'에러가 발생하면 위의 3가지 방법을 이용해서 해결해보세요.

 

 

많은 도움이 되었기를 바랍니다.

 

이번 포스팅이 도움이 되셨다면 아래의 '공감 ~ ♡'를 꾸욱 눌러주세요. ^^

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

지난번 포스팅에서는 dplyr 패키지의

 

(1) filter(), slice(), arrange(), select(), rename() 함수 ( ☞ 바로 가기)

(2) distinct(), sample_n(), sample_frac(), mutate(), transmute(), summarise(), summarise_each(), group_by() 함수 ( ☞ 바로 가기) 에 대해서 알아보았습니다.

 

이번 포스팅에서는 dplyr 패키지의 chain operations (Operator %>%, shift+ctrl+M)에 대해서 알아보겠습니다. 

 

저는 R의 다른 함수를 사용하다가 처음으로 dplyr 패키지의 chain operation (%>%), pipe operator (%>%)을 접하면 '어, 이거 뭐지? 오타인가?' 하는 당혹감이 들었었습니다.  그런데 이게 쓰다보니 참 편하고 프로그램 가독성이 좋더군요.  R로 프로토타입핑 해놓고 시스템화를 위해 개발자에게 설명해줄 때도 개발자가 쉽게 이해를 하고요.

 

 

마치 ggplot2가 시각화를 문법으로 체계화했듯이 dplyr도 그동안 dplyr (1), (2)번 포스팅에서 다루었던 dplyr 함수들을 이번 시간에 소개할 chain operation으로 굴비 꿰듯이 엮어서 물 흐르듯이 데이터 조작, 전처리를 할 수 있는 매끈한 문법을 생성하게 됩니다.

 

 

 

먼저 R dplyr 패키지를 로딩하고, 실습 예제로 사용할 MASS 패키지의 Cars93 데이터셋에 대해 살펴보면 아래와 같습니다.  (Cars93은 자동차 93개의 관측치, 차량 정보 27개의 변수를 가진 데이터 프레임).

 

> library(dplyr)
> library(MASS) # dataframe Cars93
> str(Cars93)
'data.frame':	93 obs. of  27 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...

 

 

 

R dplyr Chain operations (Chaining) : Operator %>% (단축키 shift+ctrl+M)

 

 

(1) 어떨 때 chain operations 이 유용한가?

 

(1-1) 여러 단계의 절차를 필요로 해서 중간 결과를 저정 후 그 객체를 후속 절차에서 받아서 사용해야 하는 경우의 프로그래밍의 경우 chain operation %>%이 유용합니다.

 

예를 들어, "Cars93 데이터프레임에서 차생산국가(Origin), 차종(Type), 실린더개수(Cylinders)별로 차가격(Price)과 고속도로연비(MPG.highway)의 평균을 구하되, 차가격 평균 10 초과 & 고속도로연비 25 초과하는 경우만 선별해서 제시하시오"라는 문제가 있다고 합시다.

 

이 예제 문제를 단계별로 중간 산출물을 저장하고, 이를 후속 단계에서 받아서 프로그래밍해보면 아래와 같습니다.  프로그래밍이 길기도 하고, 단계가 하나씩 늘어날수록 헷갈리고 복잡해지지요?

 

 

# why chaining?  when to use chain operation?

# : (1) step-by-step operation, saving the intermediate results
 

select <- dplyr::select # assigning select() functon of dplyr package explicitly

 

a1 <- group_by(Cars93, Origin, Type, Cylinders)
a2 <- select(a1, Price, MPG.highway)
a3 <- summarise(a2,
                Price_m = mean(Price, na.rm = TRUE),
                MPG.highway_m = mean(MPG.highway, na.rm = TRUE))
a4 <- filter(a3, Price_m > 10 | MPG.highway_m > 25)

 

 

> a4
Source: local data frame [25 x 5]
Groups: Origin, Type [11]

   Origin    Type Cylinders  Price_m MPG.highway_m
   <fctr>  <fctr>    <fctr>    <dbl>         <dbl>
1     USA Compact         4 12.82857      30.57143
2     USA   Large         6 22.40000      27.28571
3     USA   Large         8 27.62500      25.75000
4     USA Midsize         4 15.87500      29.50000
5     USA Midsize         6 22.84000      27.20000
6     USA Midsize         8 40.10000      25.00000
7     USA   Small         4 10.04286      33.85714
8     USA  Sporty         4 14.60000      28.75000
9     USA  Sporty         6 19.53333      26.66667
10    USA  Sporty         8 38.00000      25.00000
# ... with 15 more rows

 

 

 

 

(1-2) 여러 단계의 각 단계를 괄호로 싸서 프로그래밍하다보니 깊이가 깊어져서 해석하고 이해하기 어려운 경우, dplyr의 chain operation %>% (pipe operator) 이 유용합니다.

 

아래의 R script는 (1-1)과 동일한 결과를 내는 프로그래밍인데요, 4 depth 까지 들어가다보니 안쪽에서 부터 바깥쪽으로 순서대로 해독하기가 많이 어렵습니다. @@~

 

 

# why chaining?  when to use chain operation?
# : (2) Wrapping the function calls inside each other is hard to interpret

filter(
  summarise(
    select(
      group_by(Cars93, Origin, Type, Cylinders),
      Price, MPG.highway
    ),
    Price_m = mean(Price, na.rm = TRUE),
    MPG.highway_m = mean(MPG.highway, na.rm = TRUE)
  ),
  Price_m > 10 | MPG.highway_m > 25

 

 

Adding missing grouping variables: `Origin`, `Type`, `Cylinders`
Source: local data frame [25 x 5]
Groups: Origin, Type [11]

   Origin    Type Cylinders  Price_m MPG.highway_m
   <fctr>  <fctr>    <fctr>    <dbl>         <dbl>
1     USA Compact         4 12.82857      30.57143
2     USA   Large         6 22.40000      27.28571
3     USA   Large         8 27.62500      25.75000
4     USA Midsize         4 15.87500      29.50000
5     USA Midsize         6 22.84000      27.20000
6     USA Midsize         8 40.10000      25.00000
7     USA   Small         4 10.04286      33.85714
8     USA  Sporty         4 14.60000      28.75000
9     USA  Sporty         6 19.53333      26.66667
10    USA  Sporty         8 38.00000      25.00000
# ... with 15 more rows

 

 

 

 

(2) dplyr 패키지의 chain operations은 어떻게 사용하는가? 

 

chain(pipe) operator 는 %>% 이며, 단축키는 shift+ctrl+M 입니다.

 

dataframe %>% group_by() %>% select() %>% summarise() %>% filter() 의 순서로 사용하시면 됩니다.  의도하는 분석 결과를 논리적인 순서대로 찬찬히 생각해보면서 프로그래밍하시면 됩니다. 

 

예를 들면 아래처럼요,

 

"(a) Cars93 데이터프레임에서  %>%  (b) 제조생산국(Origin), 차종(Type), 실린더개수(Cylinders)별로   %>%   (c) 차 가격(Price)과 고속도로 연비(MPG.highway) 변수에 대해   %>%   (d)  (결측값은 제외하고) 평균을 구하는데,   %>%   (e) 단, 가격 평균은 10을 넘고 & 고속도로 연비는 25를 넘는 것만 알고 싶다"

 

 

# How to use dplyr's chain operations %>%
# : dataframe %>% group_by() %>% select() %>% summarise() %>% filter()

Cars93 %>%  # dataframe name
  group_by(Origin, Type, Cylinders) %>%  # group_by()
  select(Price, MPG.highway) %>%  # select() columns
  summarise(
    Price_m = mean(Price, na.rm = TRUE),
    MPG.highway_m = mean(MPG.highway, na.rm = TRUE)  # summarise()
  ) %>%
  filter(Price_m > 10 | MPG.highway_m > 25)  # filter() condition

 

 

Adding missing grouping variables: `Origin`, `Type`, `Cylinders`
Source: local data frame [25 x 5]
Groups: Origin, Type [11]

   Origin    Type Cylinders  Price_m MPG.highway_m
   <fctr>  <fctr>    <fctr>    <dbl>         <dbl>
1     USA Compact         4 12.82857      30.57143
2     USA   Large         6 22.40000      27.28571
3     USA   Large         8 27.62500      25.75000
4     USA Midsize         4 15.87500      29.50000
5     USA Midsize         6 22.84000      27.20000
6     USA Midsize         8 40.10000      25.00000
7     USA   Small         4 10.04286      33.85714
8     USA  Sporty         4 14.60000      28.75000
9     USA  Sporty         6 19.53333      26.66667
10    USA  Sporty         8 38.00000      25.00000
# ... with 15 more rows 

 

 

 

 

(3) dplyr chain operations 는 지켜야 할 순서가 있는가?

 

네, 지켜야 할 순서가 있습니다. 

 

아래에 group_by()를 제일 마지막, 혹은 summarise() 함수 뒤에 위치시켰더니 "unknown variable to group by : Origin'이라고 에러가 났습니다.

 

filter() 조건에 group_by()한 summarise() 중간 결과의 변수가 들어있는 경우에, filter()를 summarise() 함수 앞에 위치시켰더니 'Price_m'이라는 새로운 변수를 찾을 수 없다고 역시 에러가 났습니다.

 

# keep in mind the sequence order of chain operator in dplyr

> Cars93 %>%
+   select(Price, MPG.highway) %>%
+   summarise(
+     Price_m = mean(Price, na.rm = TRUE),
+     MPG.highway_m = mean(MPG.highway, na.rm = TRUE)
+   ) %>%
+   filter(Price_m > 10 | MPG.highway_m > 25) %>% 
+   group_by(Origin, Type, Cylinders)
Error in eval(substitute(expr), envir, enclos) : unknown variable to group by : Origin
> Cars93 %>%
+   select(Price, MPG.highway) %>%
+   summarise(
+     Price_m = mean(Price, na.rm = TRUE),
+     MPG.highway_m = mean(MPG.highway, na.rm = TRUE)
+   ) %>%
+   group_by(Origin, Type, Cylinders) %>% 
+   filter(Price_m > 10 | MPG.highway_m > 25)
Error in eval(substitute(expr), envir, enclos) : unknown variable to group by : Origin
> Cars93 %>%
+   group_by(Origin, Type, Cylinders) %>% 
+   select(Price, MPG.highway) %>%
+   filter(Price_m > 10 | MPG.highway_m > 25) %>% 
+   summarise(
+     Price_m = mean(Price, na.rm = TRUE),
+     MPG.highway_m = mean(MPG.highway, na.rm = TRUE)
+   )
Adding missing grouping variables: `Origin`, `Type`, `Cylinders` Error in eval(substitute(expr), envir, enclos) : object 'Price_m' not found

 

 

 

많은 도움이 되었기를 바랍니다.

 

 

참고로, Python이나 Scala로 Spark 사용할 때 보면 '.' (dot) 을 이용해서 chain operation 하는게 있는데요, R의 '%>%'과 사용방법이 유사합니다. 

 

 [Spark에서 Python으로 map과 reduceByKey를 chaining 해서 단어 세기] 예시

 

rdd = sc.textFile("file")

words = rdd.flatMap(lambda x: x.split(" "))

result = words.map(lambda x: (x, 1)).reduceByKey(lambda x, y: x + y)

 

 

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~ ♡'를 꾸욱 눌러주세요.

 

[Reference]

1. Introduction to dplyr : http://127.0.0.1:26182/library/dplyr/doc/introduction.html

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

데이터 전처리, 조작을 위한 dplyr 패키지의 단일 테이블에 대한 함수(one-table verbs)로서 지난번 포스티에서는

 

 - 행(row) 선별 : filter(), slice()

 - 행(row) 정렬 : arrange()

 - 열(column) 선별 : select()

 - 열(column) 조건부 선별 : select(df, starts_with()), select(df, ends_with()), select(df, contains()),

select(df, matchs()), select(df, one_of()), select(df, num_range()) 

 - 변수 이름 변경 : rename()

 

함수에 대해서 알아보았습니다. (☞ 바로 가기 http://rfriend.tistory.com/234)

 

 

 

[ R dplyr package : a grammar of data manipulation ]

 

 

 

 

이번 포스팅에서는 dplyr 패키지에 대한 두번째 소개 포스팅으로서, distinct(), sample_n(), sample_frac(), mutate(), transmute(), summarise() 함수에 대해서 알아보겠습니다.

 

 dplyr verbs

 description

 similar {package} function

 filter() 

 Filter rows with condition

 {base} subset

 slice()

 Filter rows with position

 {base} subset 

 arrange()

 Re-order or arrange rows

 {base} order

 select()

 Select columns

 {base} subset
 > select(df, starts_with())  Select columns that start with a prefix   
 > select(df, ends_with())  Select columns that end with a prefix  
 > select(df, contains())  Select columns that contain a character string  
 > select(df, matchs())  Select columns that match a regular expression  
 > select(df, one_of())  Select columns that are from a group of names  
 > select(df, num_range())  Select columns from num_range a to n with a prefix  

 rename()

 Rename column name

 {reshape} rename
 distinct()  Extract distinct(unique) rows  {base} unique
 sample_n()  Random sample rows for a fixed number  {base} sample
 sample_frac()  Random sample rows for a fixed fraction  {base} sample

 mutate()

 Create(add) new columns. 
 mutate() allows you to refer to columns that you’ve just created

 {base} transform 
 transmute()

 Create(add) new columns.

 transmute() only keep the new columns.

 {base} transform

 summarise()

 Summarise values

 {base} summary

 

 

 

 

(1) 중복없는 유일한 값 추출 : distinct() 

 

distinct(dataframe, 기준 var1, 기준 var2, ...) 의 형식으로 중복없는 유일한(distinct, unique) 값을 추출하고자 하는 기준 변수를 기입해주면 됩니다.

 

{base} 패키지의 unique() 함수와 매우 기능을 수행하며, dplyr 패키지의 distinct() 가 C언어로 짜여져서 속도는 훨씬 빠릅니다.

 

[문제] Cars93 데이터 프레임에서 '차종(Type)'과 '생산국-미국여부(Origin)' 변수를 기준으로 중복없는 유일한 값을 추출하시오.

 

library(dplyr)
library(MASS) # to use Cars93 dataframe

 

> names(Cars93)
 [1] "Manufacturer"       "Model"              "Type"               "Min.Price"         
 [5] "Price"              "Max.Price"          "MPG.city"           "MPG.highway"       
 [9] "AirBags"            "DriveTrain"         "Cylinders"          "EngineSize"        
[13] "Horsepower"         "RPM"                "Rev.per.mile"       "Man.trans.avail"   
[17] "Fuel.tank.capacity" "Passengers"         "Length"             "Wheelbase"         
[21] "Width"              "Turn.circle"        "Rear.seat.room"     "Luggage.room"      
[25] "Weight"             "Origin"             "Make"

 

> # distinct(dataframe, var1, var2) :  find unique values in a table
> 
>
distinct(Cars93, Origin) Origin 1 non-USA 2 USA >
> distinct(Cars93, Type) Type 1 Small 2 Midsize 3 Compact 4 Large 5 Sporty 6 Van > > distinct(Cars93, Origin, Type) Origin Type 1 non-USA Small 2 non-USA Midsize 3 non-USA Compact 4 USA Midsize 5 USA Large 6 USA Compact 7 USA Sporty 8 USA Van 9 USA Small 10 non-USA Sporty 11 non-USA Van

 

참고로, {base} 패키지의 unique() 함수로는 unique(Cars93[, c("Origin", "Type")])  이렇게 입력해주면 되며, 위의 distinct() 함수와는 달리 rownames 가 dataset의 row 번호로 반환되는 차이점이 있습니다.  dplyr 패키지가 전반적으로 문법이 깔끔하다는 생각이 드실겁니다.

 

({base} 패키지의 unique() 함수, duplicated() 함수 포스팅 보러가기

         : http://rfriend.tistory.com/165 )

 

 

 

 

(2) 무작위 표본 데이터 추출 : sample_n(), sample_frac()

 

(2-1) sample_n(dataframe, a fixed number) : 특정 개수만큼 무작위 추출

 

[문제] Cars93 데이터 프레임(1~5번째 변수만 사용)에서 10개의 관측치를 무작위로 추출하시오.

 

> # sample_n() : randomly sample rows for a fixed number > sample_n(Cars93[, 1:5], 10) Manufacturer Model Type Min.Price Price 68 Oldsmobile Achieva Compact 13.0 13.5 38 Ford Crown_Victoria Large 20.1 20.9 75 Pontiac Firebird Sporty 14.0 17.7 59 Mercedes-Benz 300E Midsize 43.8 61.9 70 Oldsmobile Silhouette Van 19.5 19.5 49 Lexus ES300 Midsize 27.5 28.0 47 Hyundai Sonata Midsize 12.4 13.9 93 Volvo 850 Midsize 24.8 26.7 2 Acura Legend Midsize 29.2 33.9 41 Honda Prelude Sporty 17.0 19.8 >
> # random sampling one more time
> sample_n(Cars93[, 1:5], 10)
Manufacturer Model Type Min.Price Price 69 Oldsmobile Cutlass_Ciera Midsize 14.2 16.3 64 Nissan Sentra Small 8.7 11.8 44 Hyundai Excel Small 6.8 8.0 78 Saab 900 Compact 20.3 28.7 39 Geo Metro Small 6.7 8.4 22 Chrysler Imperial Large 29.5 29.5 7 Buick LeSabre Large 19.9 20.8 9 Buick Riviera Midsize 26.3 26.3 27 Dodge Dynasty Midsize 14.8 15.6 91 Volkswagen Corrado Sporty 22.9 23.3

 

 

 

 

(2-2) sample_frac(dataframe, a fixed fraction) : 특정 비율만큼 무작위 추출

 

[문제] Cars93 데이터 프레임(1~5번째 변수만 사용)에서 10%의 관측치를 무작위로 추출하시오.

 

> # sample_frac() : randomly sample rows for a fixed fraction
> nrow(Cars93)
[1] 93
> 
>
nrow(Cars93)*0.1 [1] 9.3 >
> sample_frac(Cars93[ , 1:5], 0.1) Manufacturer Model Type Min.Price Price 72 Plymouth Laser Sporty 11.4 14.4 8 Buick Roadmaster Large 22.6 23.7 80 Subaru Justy Small 7.3 8.4 31 Ford Festiva Small 6.9 7.4 75 Pontiac Firebird Sporty 14.0 17.7 90 Volkswagen Passat Compact 17.6 20.0 30 Eagle Vision Large 17.5 19.3 41 Honda Prelude Sporty 17.0 19.8 76 Pontiac Grand_Prix Midsize 15.4 18.5

 

 

Cars93 데이터 프레임은 관측치가 93개 이며, 10%는 9.3개에 해당하므로, 위 예제에서 sample_frac(Cars93, 0.1)은 총 9개의 무작위 샘플을 추출했습니다.

 

 

 

(2-3) smaple_n(dataframe, n, replace = TRUE) : 복원 추출

 

위의 두개의 예제는 한번 추출한 표본은 다시 추출하지 않는 '비복원 추출(sampling with replacement)'이었습니다. (눈을 감고 주머니에서 한번 뽑았으면, 뽑힌 공은 다시 주머니에 넣지 않고 옆에 따로 빼어놓고, 다시 눈을 감고 주머니에서 공을 뽑음)

 

dplyr 패키지의 sample_n(), sample_frac() 함수의 default 는 비복원추출이며, 만약 '복원추출(sampling with replacement, bootstrap sampling)'을 하고 싶다면 'replace = TRUE' 옵션을 설정해주면 됩니다. (눈을 감고 주머니에서 공을 뽑고, 뽑힌 공을 다시 주머니에 넣은 후에, 눈을 감고 다시 주머니에서 공을 뽑음) 

 

 

[문제] Cars93 데이터 프레임(1~5번까지 변수만 사용)에서 20개의 관측치를 무작위 복원추출 하시오.

 

> # sample_n(dataframe, n, replace = TRUE) : random sampling with replacement
> sample_n(Cars93[, 1:5], 20, replace = TRUE) # a bootstrap sample of 20 records
      Manufacturer      Model    Type Min.Price Price
53           Mazda        323   Small       7.4   8.3
49           Lexus      ES300 Midsize      27.5  28.0
92           Volvo        240 Compact      21.8  22.7
48        Infiniti        Q45 Midsize      45.4  47.9
64          Nissan     Sentra   Small       8.7  11.8
56           Mazda        MPV     Van      16.6  19.1
23           Dodge       Colt   Small       7.9   9.2
25           Dodge     Spirit Compact      11.9  13.3
68      Oldsmobile    Achieva Compact      13.0  13.5
43           Honda     Accord Compact      13.8  17.5
17       Chevrolet      Astro     Van      14.7  16.6
70      Oldsmobile Silhouette     Van      19.5  19.5
10        Cadillac    DeVille   Large      33.0  34.7
81          Subaru     Loyale   Small      10.5  10.9
58   Mercedes-Benz       190E Compact      29.0  31.9
52         Lincoln   Town_Car   Large      34.4  36.1
31            Ford    Festiva   Small       6.9   7.4
43.1         Honda     Accord Compact      13.8  17.5
47         Hyundai     Sonata Midsize      12.4  13.9
17.1     Chevrolet      Astro     Van      14.7  16.6

 

 

위 무작위 표본 추출 결과를 보면 17번 Cehvrolet Astro와 43번 Honda Accord가 중복으로 추출되었음을 알 수 있습니다.

 

 

 

(2-4) dataframe %>% group_by(factor_var) %>% sample_n(size) : 집단별 층화 표본 추출

 

분석을 하다 보면 집단, 그룹별로 동일한 수의 표본을 무작위 추출해서 분석해야 하는 경우가 있습니다.  특히 분석 주제 혹은 분석에 큰 영향을 미치는 요인 변수에 대한 집단 분포(distribution)가 한쪽 그룹으로 심하게 편향된 모집단(biased, unbalanced population)의 경우 층화 무작위 표본 추출(stratified random sampling)이 필요합니다.

 

[예제] Cars93 데이터 프레임에서 '제조국가_미국여부(Origin)'의 'USA', 'non-USA' 요인 속성별로 각 10개씩의 표본을 무작위 비복원 추출하시오.

 

> # dataframe %>% group_by(factor_var) %>% sample_n(size) : random sampling by group > Cars93[ , c("Manufacturer", "Model", "Origin")] %>% group_by(Origin) %>% sample_n(10) Source: local data frame [20 x 3] Groups: Origin [2] Manufacturer Model Origin <fctr> <fctr> <fctr> 1 Chrylser Concorde USA 2 Lincoln Town_Car USA 3 Ford Crown_Victoria USA 4 Oldsmobile Eighty-Eight USA 5 Oldsmobile Achieva USA 6 Lincoln Continental USA 7 Chrysler Imperial USA 8 Eagle Summit USA 9 Buick Riviera USA 10 Pontiac Bonneville USA 11 Volvo 240 non-USA 12 Mercedes-Benz 190E non-USA 13 Volkswagen Fox non-USA 14 Geo Metro non-USA 15 Honda Accord non-USA 16 Mazda 626 non-USA 17 Mazda Protege non-USA 18 Lexus SC300 non-USA 19 Acura Legend non-USA 20 Mercedes-Benz 300E non-USA

 

 

위의 '%>%' (단축키 : shift + ctrl + M)의 chaining 에 대해서는 다음번 포스팅에서 별도로 소개하겠으니 지금 궁금하시더라도 조금만 참아주세요. ^^;

 

 

 

 

(3) 새로운 변수 생성 : mutate(), transmute()

 

(3-1) mutate(dataframe, 새로운변수 = 기존변수 조합한 수식, ...)
       : 기존 변수 + 신규 변수 모두 keep 

 

새로운 변수를 생성할 때 mutate() 함수를 사용하며, 이는 {base} 패키지의 transform() 함수와 유사합니다만, 조금 더 유용합니다.

 

[문제] Cars93 데이터프레임에서 최소가격(Min.Price)과 최대가격(Max.Price)의 범위(range), 최소가격 대비 최대가격의 비율(=Max.Price/Min.Price) 의 새로운 변수를 생성하시오.

 

> # mutate(dataframe, new_var = operation of old vars, ...) : Create(add) new columns > Cars93_1 <- Cars93[c(1:10), c("Model", "Min.Price", "Max.Price")] # subset for better printing > Cars93_1 Model Min.Price Max.Price 1 Integra 12.9 18.8 2 Legend 29.2 38.7 3 90 25.9 32.3 4 100 30.8 44.6 5 535i 23.7 36.2 6 Century 14.2 17.3 7 LeSabre 19.9 21.7 8 Roadmaster 22.6 24.9 9 Riviera 26.3 26.3 10 DeVille 33.0 36.3 > > > Cars93_1 <- mutate(Cars93_1, + Price_range = Max.Price - Min.Price, + Price_Min_Max_ratio = Max.Price / Min.Price) > > > Cars93_1 Model Min.Price Max.Price Price_range Price_Min_Max_ratio 1 Integra 12.9 18.8 5.9 1.457364 2 Legend 29.2 38.7 9.5 1.325342 3 90 25.9 32.3 6.4 1.247104 4 100 30.8 44.6 13.8 1.448052 5 535i 23.7 36.2 12.5 1.527426 6 Century 14.2 17.3 3.1 1.218310 7 LeSabre 19.9 21.7 1.8 1.090452 8 Roadmaster 22.6 24.9 2.3 1.101770 9 Riviera 26.3 26.3 0.0 1.000000 10 DeVille 33.0 36.3 3.3 1.100000

 

 

 

 

{dplyr} 패키지의 mutate() 함수하나의 함수 명령문 안에서 신규로 생성한 변수를 바로 다른 신규 생성 변수의 input 변수로 사용할 수 있어서 편리합니다

 

반면에, {base} 패키지의 transform() 함수는 하나의 명령문 안에 신규 변수를 다른 신규 변수의 input 변수로 사용할 수 없습니다.  R 초급자가 transform() 함수 쓰다가 '객체가 없다고? 이게 왜 안되고 에러가 나지?'하면서 에러 메시지의 의미를 잘 이해를 못하고 어찌 조치를 취해야 할지 몰라 애를 먹는 부분이기도 합니다. 아래의 예제를 참고하시면 이해가 될 것입니다.

 

> # comparison with {dplyr} mutate() and {base} transform()
> # : mutate() allows you to refer to columns that you’ve just created
> Cars93_1 <- Cars93[c(1:10), c("Model", "Min.Price", "Max.Price")] # subset for better printing
> Cars93_1
        Model Min.Price Max.Price
1     Integra      12.9      18.8
2      Legend      29.2      38.7
3          90      25.9      32.3
4         100      30.8      44.6
5        535i      23.7      36.2
6     Century      14.2      17.3
7     LeSabre      19.9      21.7
8  Roadmaster      22.6      24.9
9     Riviera      26.3      26.3
10    DeVille      33.0      36.3
> 
> Cars93_2 <- mutate(Cars93_1, 
+                    Price_range = Max.Price - Min.Price, 
+                    Price_range_cd = ifelse(Price_range >= 5, 1, 0))
> 
> Cars93_2
        Model Min.Price Max.Price Price_range Price_range_cd
1     Integra      12.9      18.8         5.9              1
2      Legend      29.2      38.7         9.5              1
3          90      25.9      32.3         6.4              1
4         100      30.8      44.6        13.8              1
5        535i      23.7      36.2        12.5              1
6     Century      14.2      17.3         3.1              0
7     LeSabre      19.9      21.7         1.8              0
8  Roadmaster      22.6      24.9         2.3              0
9     Riviera      26.3      26.3         0.0              0
10    DeVille      33.0      36.3         3.3              0
> 
>
# {base} transform() > Cars93_3 <- transform(Cars93_1, + Price_range = Max.Price - Min.Price, + Price_range_cd = ifelse(Price_range >= 5, 1, 0))

Error in ifelse(Price_range >= 5, 1, 0) : object 'Price_range' not found

 

 

위의 예제에서 {base} 패키지의 transform() 함수로 Price_range_cd 신규 변수를 만들려면 (1) Price_range 신규 변수를 먼저 생성하고 -> (2) Price_range_cd 신규 변수를 생성하는 두단계 절차(multi steps)를 나누어서 밟아야 합니다.  mutate() 함수 대비 조금 더 불편하지요?  

 

 

 

 

(3-2) transmute(dataframe, 새로운 변수 = 기존 변수 조합한 수식, ...)
       : 신규 변수만 keep

 

위의 mutate() 함수가 기존 변수와 신규 변수를 모두 가지고 있는데 반해서, transmute() 함수는 신규 변수만 저장을 하고 기존 변수는 날려버립니다.

 

최소가격(Min.Price)과 최대가격(Max.Price)의 범위(range), 최소가격 대비 최대가격의 비율(=Max.Price/Min.Price) 의 새로운 변수를 생성한 후에, 이들 2개의 신규변수만 남겨두시오.

 

> # transmute() : Create(add) new columns > Cars93_1 <- Cars93[c(1:10), c("Model", "Min.Price", "Max.Price")] # subset for better printing > Cars93_1 Model Min.Price Max.Price 1 Integra 12.9 18.8 2 Legend 29.2 38.7 3 90 25.9 32.3 4 100 30.8 44.6 5 535i 23.7 36.2 6 Century 14.2 17.3 7 LeSabre 19.9 21.7 8 Roadmaster 22.6 24.9 9 Riviera 26.3 26.3 10 DeVille 33.0 36.3 > > Cars93_4 <- transmute(Cars93_1, + Price_range = Max.Price - Min.Price, + Price_Min_Max_ratio = Max.Price / Min.Price) > > Cars93_4 Price_range Price_Min_Max_ratio 1 5.9 1.457364 2 9.5 1.325342 3 6.4 1.247104 4 13.8 1.448052 5 12.5 1.527426 6 3.1 1.218310 7 1.8 1.090452 8 2.3 1.101770 9 0.0 1.000000 10 3.3 1.100000

 

 

 

 

 

 

 (4) 값 요약 : summarise()

 

(4-1) summarise(dataframe, mean, sd, ...) : 수치형 값에 대한 요약 통계량 계산

 

summarise() 함수이 제공하는 수치형 데이터에 대한 요약 통계량 옵션을 소개하자면요,

 

 - mean(x, na.rm = TRUE) : 평균, 결측값을 제외하고 계산하려면 na.rm = TRUE 추가

 - median(x, na.rm = TRUE) : 중앙값

 - sd(x, na.rm = TRUE) : 표준편차

 - min(x, na.rm = TRUE) : 최소값

 - max(x, na.rm = TRUE) : 최대값

 - IQR(x, na.rm = TRUE) : 사분위수 (Inter Quartile Range = Q3 - Q1)

 - sum(x, na.rm = TRUE) : 합, 결측값을 제외하고 계산하려면 na.rm = TRUE 추가

 

 

[예제] Cars93 데이터 프레임에서 가격(Price)의 (a) 평균, (b) 중앙값, (c) 표준편차, (d) 최소값, (e) 최대값, (f) 사분위수(IQR), (g) 합계를 구하시오. (단, 결측값은 포함하지 않고 계산함)

 

> # summarise() : Summarise numeric values
> #  > mean(), median(), sd(), min(), max(), IQR(), sum()
> # IQR : IQR(Inter quartile Range) = Upper Quartile - Lower Quartile
> summarise(Cars93, 
+           Price_mean = mean(Price, na.rm = TRUE), # mean of Price
+           Price_median = median(Price, na.rm = TRUE), # median of Price
+           Price_sd = sd(Price, na.rm = TRUE), # standard deviation of Price
+           Price_min = min(Price, na.rm = T), # min of Price
+           Price_max = max(Price, na.rm = T), # max of Price
+           Price_IQR = IQR(Price), na.rm = T, # IQR of Price
+           Price_sum = sum(Price, na.rm = TRUE)) # sum of Price

  Price_mean Price_median Price_sd Price_min Price_max Price_IQR na.rm Price_sum
1   19.50968         17.7  9.65943       7.4      61.9      11.1  TRUE    1814.4

 

 

 

 

 

(4-2) summarise(dataframe, n(), n_distinct(x), first(x), last(x), nth(x, n))
  : 개수 계산, 관측값 indexing

 

 - n() : 관측치 개수 계산, x 변수 입력하지 않음

 - n_distinct(x) : 중복없는 유일한 관측치 개수 계산, 기준이 되는 x변수 입력함

 - first(x) : 기준이 되는 x변수의 첫번째 관측치

 - last(x) : 기준이 되는 x변수의 마지막 관측치

 - nth(x, n) : 기준이 되는x변수의 n번째 관측치

 

 

[예제] Cars93_1 데이터 프레임에서 (a) 총 관측치의 개수, (b) 제조사(Manufacturer)의 개수(유일한 값), (c) 첫번째 관측치의 제조사 이름, (d) 마지막 관측치의 제조사 이름, (e) 5번째 관측치의 제조사 이름은?

 

> # summarise() : n(), n_distinct(), first(), last(), nth() > Cars93_1 <- Cars93[c(1:10), c("Manufacturer", "Model", "Type")] # subset for better print > Cars93_1 Manufacturer Model Type 1 Acura Integra Small 2 Acura Legend Midsize 3 Audi 90 Compact 4 Audi 100 Midsize 5 BMW 535i Midsize 6 Buick Century Midsize 7 Buick LeSabre Large 8 Buick Roadmaster Large 9 Buick Riviera Midsize 10 Cadillac DeVille Large > > summarise(Cars93_1, + tot_cnt = n(), # counting the number of all observations + Manufacturer_dist_cnt = n_distinct(Manufacturer), # distinct number of var + First_obs = first(Manufacturer), # first observation + Last_obs = last(Manufacturer), # last observation + Nth_5th_obs = nth(Manufacturer, 5)) # n'th observation



tot_cnt Manufacturer_dist_cnt First_obs Last_obs Nth_5th_obs 1 10 5 Acura Cadillac BMW

 

 

 

 

 

(4-3) summarise(group_by(dataframe, factor_var), mean, sd, ...) : 그룹별 요약 통계량 계산

 

그룹별 계산(Grouped operations)을 위해서 group_by() 함수를 이용합니다.

 

[예제] Cars93 데이터 프레임에서 '차종(Type)' 별로 구분해서 (a) 전체 관측치 개수, (b) (중복 없이 센) 제조사 개수, (c) 가격(Price)의 평균과 (d) 가격의 표준편차를 구하시오. (단, 결측값은 포함하지 않고 계산함)

 

> # summarise by group
> grouped <- group_by(Cars93, Type)
> summarise(grouped,
+           tot_conut = n(), # counting the number of cars
+           Manufacturer_dist_cnt = n_distinct(Manufacturer), # distinct number of var
+           Price_mean = mean(Price, na.rm = TRUE), # mean of Price
+           Price_sd = sd(Price, na.rm = TRUE) # standard deviation of Price
+           )
# A tibble: 6 x 5
     Type tot_conut Manufacturer_dist_cnt Price_mean  Price_sd
   <fctr>     <int>                 <int>      <dbl>     <dbl>
1 Compact        16                    15   18.21250  6.686890
2   Large        11                    10   24.30000  6.337507
3 Midsize        22                    20   27.21818 12.264841
4   Small        21                    16   10.16667  1.953288
5  Sporty        14                    12   19.39286  7.974716
6     Van         9                     8   19.10000  1.878164

 

 

 

 

(4) summarise_each() : 다수의 변수에 동일한 summarise 함수 적용

 

[문제] Cars93 데이터 프레임의 (i) 가격(Price) 변수와 (ii) 고속도로연비(MPG.highway) 의 두개의 변수에 대해 (a) 평균(mean), (b) 중앙값(median), (c) 표준편차(standard deviation) 의 3개의 함수를 동시에 적용하여 계산하시오.

 

> # summarize_each() : applies the same summary function(s) to multiple variables
> summarise_each(Cars93, funs(mean, median, sd), Price, MPG.highway)
  Price_mean MPG.highway_mean Price_median MPG.highway_median Price_sd MPG.highway_sd
1   19.50968         29.08602         17.7                 28  9.65943       5.331726

 

 

 

[Reference]

 - Introduction to dplyr (http://127.0.0.1:21980/library/dplyr/doc/introduction.html)

 - dplyr functions for single dataset (http://stat545.com/block010_dplyr-end-single-table.html)

 - dplyr tutorial (http://genomicsclass.github.io/book/pages/dplyr_tutorial.html)

 

많은 도움이 되었기를 바랍니다.

 

이전 포스팅과 이번 포스팅으로서 dplyr 패키지의 데이터 전처리, 조작을 위한 기본기를 익혔습니다.

다음번 포스팅에서는 R dplyr 패키지의 심화 함수로서, chain operations (Operator %>%, shift+ctrl+M) 에 대해서 소개하겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡'를 꾸욱 눌러주세요.

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend


티스토리 툴바