지난번 포스팅에서는 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 )를 참고하세요.

 

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

 

 

728x90
반응형
Posted by Rfriend
,

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

 

 

 


(5) 위의 1~4를 chain operator와 group_by() 를 사용해서 한번에 모두 처리하기



library(dplyr)


## sample data

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)


## max and min of (x - x_lag)

x_df %>% 

  arrange(group, seq_no) %>% 

  group_by(group) %>% 

  summarise(max_lag = max(x - lag(x, 1), na.rm = TRUE), 

            min_lag = min(x - lag(x, 1), na.rm = TRUE))


# # A tibble: 2 x 3

# group max_lag min_lag

# <chr>   <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 가 포스팅별로 조회수를 알려주는 기능이 없다보니 '공감♡' 개수로 참고하려고)

 

 

728x90
반응형
Posted by Rfriend
,

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'에 대해서 알아보겠습니다.

 

 

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

 

728x90
반응형
Posted by Rfriend
,

그동안 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  포스팅을 참고하세요.

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

 

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

728x90
반응형
Posted by Rfriend
,

아래의 예제와 같이 콤마로 구분되어 있는 문자형 변수(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

 

 

728x90
반응형
Posted by Rfriend
,

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가지 방법을 이용해서 해결해보세요.

 

 

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

 

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

 

728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 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 초과 or 고속도로연비 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을 넘거나 or 고속도로 연비는 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

 


728x90
반응형
Posted by Rfriend
,

데이터 전처리, 조작을 위한 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) 에 대해서 소개하겠습니다.

 

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

 

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 데이터프레임(dataframe)을 위한 데이터 전처리, 조작(data pre-processing, data manipulation)을 쉽고! 빠르게! 할 수 있도록 해주는 dplyr 패키지에 대해서 알아보겠습니다.

 

R help에 검색해보면 dplyr 패키지를 아래와 같이 소개하고 있습니다.

 

dplyr은 유연한 데이터 조작의 문법을 제공합니다.  이것은 plyr의 차기작으로서, 데이터프레임을 집중적으로 다루는 툴입니다

 

dplyr provides a flexible grammar of data manipulation. It's the next iteration of plyr, focused on tools for working with data frames (hence the d in the name)

 

 

데이터 조작을 위한 문법으로 체계화를 해서 한번 배워놓으면 쉽다는 점과 더불어, C언어로 만들어서 매우 빠르다는 점도 dplyr 패키지의 크나큰 장점 중의 하나입니다.

 

 

 

아마 R을 좀 다룬 분이라면은 그래프/시각화를 문법으로 승화시켜서 체계를 잡아놓은 "ggplot2" package ( "ggplot is an implementation of the grammer of Graphics in R")를 알고 계실텐데요, 이 ggplot2를 만든 그 유명한 Hadley Wickham 이 dplyr 패키지도 만들었습니다.  

 

아래에 소개한 것처럼, r-bloggers.com에 소개되어 있는 Hadley 인 인터뷰를 보면, 기존 통계학에서는 데이터 전처리(Data munging and manipulation)를 "내 일이 아니야"라고 무시했었다고 합니다. 그런데 Hadley Wickham이 보기에는 모델링과 시각화로 부터 통찰을 끄집에 내는데 있어서 데이터 조작, 전처리가 매우 중요하고 또 어려운 영역이라고 보기에 Hadley가 직접 나서서 이를 도와줄 수 있는 R packages를 만들었다고 나오네요.  ^^b

"Hadley Wickham on why he created all those R packages"

July 27, 2015 By David Smith

 

“There are definitely some academic statisticians who just don’t understand why what I do is statistics, but basically I think they are all wrong . What I do is fundamentally statistics. The fact that data science exists as a field is a colossal failure of statistics.

 

To me, that is what statistics is all about. It is gaining insight from data using modelling and visualizationData munging and manipulation is hard and statistics has just said that’s not our domain.”

* source : https://www.r-bloggers.com/hadley-wickham-on-why-he-created-all-those-r-packages/

 

 

Hadley Wickham의 Github repository 주소는요 https://github.com/hadley?tab=repositories  입니다. 여기 가보시면 엄청나게 많은 R packages들에 입이 쩍 벌어질겁니다. 만약에 노벨상에 R community에 기여한 공로를 치하하는 상으로 '노벨 R 상'이 있다면 Hadley Wickham이 그 첫번째 수상자가 된다고 해도 전혀 이상할게 없을 정도로 정말 R의 확산에 지대한 공헌을 하신 분입니다.

 

서론이 길었습니다.  ggplot2가 시각화/그래프에 관한한 우리를 실망시키지 않았듯이, dplyr 또한 데이터 전처리에 관한한 coverage의 방대함과 문법의 명료함이 우리를 매료시킬 것이라고 생각하며, 하나씩 예를 들어 설명을 시작해 보겠습니다.

 

아래의 설명은 browseVignettes(package = "dplyr") 치면 팝업으로 나오는 "Introduction to dplyr" HTML 페이지를 참조하였습니다.

 

예제로 사용할 데이터는 MASS 패키지에 들어있는 Cars93 데이터프레임입니다.  원래는 93개의 자동차 관측치에 27개의 변수를 가지고 있는데요, 예시들기 편하도록 앞에서부터 변수 8개만 선택해서 사용하겠습니다.  (Cars93_1 dataframe) 

 

> library(MASS)
> # subset Cars93
> Cars93_1 <- Cars93[, 1:8]
> str(Cars93_1)
'data.frame':	93 obs. of  8 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 ...

 

 

 

 

단일 테이블을 대상으로 하는 dplyr 패키지의 함수들(Single table verbs)을 표로 정리해보면 아래와 같습니다.

 

 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 keeps the new columns.

 {base} transform

 summarise()

 Summarise values

 {base} summary

 

 

하나씩 설명을 이어가보면요,

 

(1) 데이터 프레임의 행 부분집합 선별 (select row subset from data frame) : filter(), slice()

 

 

(1-1) filter(dataframe, filter condition 1, filter condition 2, ...)

    : &(AND) 조건으로 row 데이터 부분집합 선별 (select a subset of rows in a data frame with 'AND' condition)

 

Cars93_1 데이터 프레임에 차종(Type)별로 보면 Compact 차종이 총 16개 있음을 알 수 있습니다. 

 

[문제] 차종(Type)이 "Compact"이면서 & 최대가격(Max.Price)이 20 백$ 이하이고 & 고속도로 연비(MPG.highway) 가 30 이상인 관측치를 선별하시오.

 

위 문제는 아래와 같이 dplyr의 filter() 함수를 사용하면 됩니다.  참고로, subset() 함수의 subset과 동일한 기능을 합니다.

 

> # number of cars by Type
> table(Cars93_1$Type)

Compact   Large Midsize   Small  Sporty     Van 
     16      11      22      21      14       9 
> 
> install.packages("dplyr")
> library(dplyr)
> # filter() : select a subset of rows in a data frame
> filter(Cars93_1, Type == c("Compact"), Max.Price <= 20, MPG.highway >= 30)
  Manufacturer    Model    Type Min.Price Price Max.Price MPG.city MPG.highway
1    Chevrolet Cavalier Compact       8.5  13.4      18.3       25          36
2    Chevrolet  Corsica Compact      11.4  11.4      11.4       25          34
3        Mazda      626 Compact      14.3  16.5      18.7       26          34
4       Nissan   Altima Compact      13.0  15.7      18.3       24          30
5   Oldsmobile  Achieva Compact      13.0  13.5      14.0       24          31
6      Pontiac  Sunbird Compact       9.4  11.1      12.8       23          31

 

 

 

 

(1-2) filter(dataframe, filter condition 1 | filter condition 2 | ...)

    : |(OR) 조건으로 row 데이터 부분집합 선별 (select a subset of rows in a data frame with 'OR' condition)

 

OR(또는) 조건으로 부분집합을 선별하려면 '|'를 사용하면 됩니다. (subset() 함수와 동일)

 

[문제] 차종(Type)이 "Compact"이거나 |(OR) 최대가격(Max.Price)이 20 백$ 이하이거나 |(OR) 고속도로 연비(MPG.highway) 가 30 이상인 관측치를 선별하시오.

 

위 문제는 래와 같이 dplyr의 filter() 함수를 사용하면 됩니다.  

 

> # filter(dataframe, condition1 | condition2) : or
> filter(Cars93_1, Type == c("Compact") | Max.Price <= 20 | MPG.highway >= 30)
    Manufacturer         Model    Type Min.Price Price Max.Price MPG.city MPG.highway
1          Acura       Integra   Small      12.9  15.9      18.8       25          31
2           Audi            90 Compact      25.9  29.1      32.3       20          26
3            BMW          535i Midsize      23.7  30.0      36.2       22          30
4          Buick       Century Midsize      14.2  15.7      17.3       22          31
5      Chevrolet      Cavalier Compact       8.5  13.4      18.3       25          36
6      Chevrolet       Corsica Compact      11.4  11.4      11.4       25          34
7      Chevrolet        Camaro  Sporty      13.4  15.1      16.8       19          28

     .....  이하 생략 (총 58개 관측치) ..... 

 

 

 

 

(1-3) slice(dataframe, from, to) : 위치를 지정해서 row 데이터 부분집합 선별하기 

 

filter()가 조건에 의한 선별이었다면, 위치(position)를 사용해서 부분집합 선별은 slice() 함수를 사용합니다.

 

[문제] Cars93_1 데이터프레임의 6번째에서 10번째 행(row)의 데이터를 선별하시오.

 

> # slice() : select rows by position
> slice(Cars93_1, 6:10)
  Manufacturer      Model    Type Min.Price Price Max.Price MPG.city MPG.highway
1        Buick    Century Midsize      14.2  15.7      17.3       22          31
2        Buick    LeSabre   Large      19.9  20.8      21.7       19          28
3        Buick Roadmaster   Large      22.6  23.7      24.9       16          25
4        Buick    Riviera Midsize      26.3  26.3      26.3       19          27
5     Cadillac    DeVille   Large      33.0  34.7      36.3       16          25

 

 

 

 

 (2) 데이터 프레임 행 정렬하기 (arrange rows of data frame) : arrange()

 

(2) arrange(dataframe, order criterion 1, order criterion 2, ...)

 

데이터 프레임을 정렬할 때 arrange() 함수를 쓰면 매우 편리합니다. 

여러개의 기준에 의해서 정렬을 하고 싶으면 기준 변수를 순서대로 나열하면 됩니다.

기본 정렬 옵셥은 오름차순(ascending)이며, 만약 내림차순(descending) 으로 정렬을 하고 싶다면 desc()를 입력해주면 됩니다.

 

[문제] 고속도로 연비(MPG.highway) 가 높은 순서대로 정렬을 하시오.  만약 고속도로 연비가 동일하다면 최고가격(Max.Price)가 낮은 순서대로 정렬하시오.  (난 연비가 높고 가격은 낮은 차가 좋아~)

 

> # arrange() : reorder rows of data frame
> arrange(Cars93_1, desc(MPG.highway), Max.Price)
    Manufacturer          Model    Type Min.Price Price Max.Price MPG.city MPG.highway
1            Geo          Metro   Small       6.7   8.4      10.0       46          50
2          Honda          Civic   Small       8.4  12.1      15.8       42          46
3         Suzuki          Swift   Small       7.3   8.6      10.0       39          43
4        Pontiac         LeMans   Small       8.2   9.0       9.9       31          41
5         Saturn             SL   Small       9.2  11.1      12.9       28          38
6          Mazda            323   Small       7.4   8.3       9.1       29          37
7         Subaru          Justy   Small       7.3   8.4       9.5       33          37
8         Toyota         Tercel   Small       7.8   9.8      11.8       32          37
9          Mazda        Protege   Small      10.9  11.6      12.3       28          36
10           Geo          Storm  Sporty      11.5  12.5      13.5       30          36

   .... 이하 생략 ....

 

 

 

 

참고로, arrange() 함수 말고도 아래처럼 order() 함수를 사용해서 indexing 하는 방법도 있습니다만, 아무래도 arrange() 함수가 더 깔끔하고 해석하기에 좋습니다.

 

> Cars93[order(-Cars93_1$MPG.highway, Cars93_1$Max.Price), ]
    Manufacturer          Model    Type Min.Price Price Max.Price MPG.city MPG.highway
39           Geo          Metro   Small       6.7   8.4      10.0       46          50
42         Honda          Civic   Small       8.4  12.1      15.8       42          46
83        Suzuki          Swift   Small       7.3   8.6      10.0       39          43
73       Pontiac         LeMans   Small       8.2   9.0       9.9       31          41
79        Saturn             SL   Small       9.2  11.1      12.9       28          38
53         Mazda            323   Small       7.4   8.3       9.1       29          37
80        Subaru          Justy   Small       7.3   8.4       9.5       33          37
84        Toyota         Tercel   Small       7.8   9.8      11.8       32          37
54         Mazda        Protege   Small      10.9  11.6      12.3       28          36
40           Geo          Storm  Sporty      11.5  12.5      13.5       30          36

 

 

 


 

(3) 데이터 프레임 변수 선별하기 : select()

 

(3-1) select(dataframe, VAR1, VAR2, ...) : 선별하고자 하는 변수 이름을 기입

 

[문제] Cars93_1 데이터 프레임으로부터 제조사명(Manufacturer), 최대가격(Max.Price), 고속도로연비(MPG.highway) 3개 변수(칼럼)를 선별하시오.

 

> # select() : Select columns by name
> select(Cars93_1, Manufacturer, Max.Price, MPG.highway)
    Manufacturer Max.Price MPG.highway
1          Acura      18.8          31
2          Acura      38.7          25
3           Audi      32.3          26
4           Audi      44.6          26
5            BMW      36.2          30

   .... 이하 생략 ....

 

 

 

 

(3-2) select(dataframe, VAR_a:VAR_n, ...) : a번째부터 n번째 변수 선별

 

서로 인접해서 줄지어서 있는 변수들을 선별하고자 할 때는 아래의 예시처럼 ':'를 사용하면 됩니다.

 

[문제] Cars93_1 데이터 프레임에서 1번째에 위치한 제조사(Manufacturer) ~ 5번째에 위치한 가격(Price)까지의 서로 이어저 있는 총 5개의 변수들을 선별하시오.

 

> select(Cars93_1, Manufacturer:Price)
    Manufacturer          Model    Type Min.Price Price
1          Acura        Integra   Small      12.9  15.9
2          Acura         Legend Midsize      29.2  33.9
3           Audi             90 Compact      25.9  29.1
4           Audi            100 Midsize      30.8  37.7
5            BMW           535i Midsize      23.7  30.0

     .... 이하 생략 ....

 

 

아래와 같이 서로 인접해서 줄지어서 있는 변수들의 위치를 알고 있으면 (가령 a부터 n번째 위치) 'a:n'처럼 숫자를 직접 입력해주면 바로 위의 결과와 동일한 결과를 얻을 수 있습니다.

 

> select(Cars93_1, 1:5)
    Manufacturer          Model    Type Min.Price Price
1          Acura        Integra   Small      12.9  15.9
2          Acura         Legend Midsize      29.2  33.9
3           Audi             90 Compact      25.9  29.1
4           Audi            100 Midsize      30.8  37.7
5            BMW           535i Midsize      23.7  30.0

     .... 이하 생략 ....

 

 

 

참고로, dplyr 패키지의 select() 함수는 base패키지에 내장되어 있는 subset(dataframe, select=...) 함수와 기능이 같습니다.  아래 subset() 함수 결과가 위와 동일합니다. (동일한 기능을 하는 함수가 여러개 있으니깐 헷갈리지요? ^^;)

 

> subset(Cars93_1, select = c(Manufacturer:Price))
    Manufacturer          Model    Type Min.Price Price
1          Acura        Integra   Small      12.9  15.9
2          Acura         Legend Midsize      29.2  33.9
3           Audi             90 Compact      25.9  29.1
4           Audi            100 Midsize      30.8  37.7
5            BMW           535i Midsize      23.7  30.0

   .... 이하 생략 .... 

 

 

 

 

(3-3) select(dataframe, -(VAR_a:VAR_n)) : a번째부터 n번째 변수는 쏙 빼고 선별

 

다시 dplyr 패키지로 돌아와서요, select() 함수에서 변수 앞에 '-'(minus) 부호를 사용하면 그 변수는 빼고(to drop variables) 선별하라는 뜻입니다.

 

> # select(dataframe, -var1, -var2, ...) : to drop variables
> select(Cars93_1, -(Manufacturer:Price)) Max.Price MPG.city MPG.highway 1 18.8 25 31 2 38.7 18 25 3 32.3 20 26 4 44.6 19 26 5 36.2 22 30

     .... 이하 생략 ....

 

 

 

 

(3-4) select(dataframe, starts_with("xx_name")) : "xx_name"으로 시작하는 모든 변수 선별

 

select() 함수 안에 starts_with() 를 사용해서 "xx_name"으로 시작하는 모든 변수를 선별할 수 있는 재미있는 기능도 가지고 있습니다.

 

[문제] Cars93_1 데이터 프레임에서 "MPG"로 시작하는 모든 변수를 선별하시오.

 

 

> # select(dataframe, starts_with("xx_name"))





> # : select all variables, starting with a "xx_name" prefix
> select(Cars93_1, starts_with("MPG"))
MPG.city MPG.highway 1 25 31 2 18 25 3 20 26 4 19 26 5 22 30
    .... 이하 생략 ....

 

"MPG"로 시작하는 변수가 "MPG.city"(도시 연비), "MPG.highway"(고속도로 연비) 두 개가 있군요.

 

 

 

 

(3-5) select(dataframe, ends_with("xx_name")) : "xx_name"으로 끝나는 모든 변수 선별

 

starts_with가() 있으면 ends_with()도 있지 않을까 싶지요?  네, 맞습니다.  "xx_name"으로 끝나는 모든 변수를 골라내고 싶다면 select() 함수 안에다가 ends_with() 를 추가해주면 됩니다.

 

[문제] Cars93_1 데이터 프레임에서 "Price"로 끝나는 모든 변수를 선별하시오.

 

> # select(dataframe, ends_with("xx_name"))
> #   : select all variables, ending with a "xx_name" prefix
> select(Cars93_1, ends_with("Price"))
   Min.Price Price Max.Price
1       12.9  15.9      18.8
2       29.2  33.9      38.7
3       25.9  29.1      32.3
4       30.8  37.7      44.6
5       23.7  30.0      36.2

    .... 이하 생략 .... 

 

"Price"로 끝나는 변수가 "Min.Price", "Price", "Max.Price" 총 3개가 있군요.

 

 

 

 

(3-6) select(dataframe, contains("xx_name")) : "xx_name"을 포함하는 모든 변수 선별

 

select() 함수에 contains() 를 사용하면 특정 이름을 포함하는 모든 변수를 선별할 수 있습니다. 이때 "xx_name"은 대소문자를 구분하지 않습니다.

 

[문제] Cars93_1 데이터 프레임에 있는 변수들 중에서 "P"를 포함하는 모든 변수를 선별하시오.

 

> # select(dataframe, contains("xx_string"))
> #   : select all variables which contains a "xx_string" literal string
> select(Cars93_1, contains("P"))
      Type Min.Price Price Max.Price MPG.city MPG.highway
1    Small      12.9  15.9      18.8       25          31
2  Midsize      29.2  33.9      38.7       18          25
3  Compact      25.9  29.1      32.3       20          26
4  Midsize      30.8  37.7      44.6       19          26
5  Midsize      23.7  30.0      36.2       22          30

   .... 이하 생략 ....

 

"P"를 포함하는 변수로는 "Type"(<- 이거는 소문자 'p'로서, 대소문자 구분 안함), "Min.Price", "Price", "Max.Price", "MPG.city", "MPG.highway"의 6개 변수가 있군요.

 

 

 

 

(3-7) select(dataframe, matches(".xx_string.")) : 정규 표현과 일치하는 문자열이 포함된 모든 변수 선별

 

역시 대소문자는 구분하지 않습니다.

 

[문제] 변수 문자열 중간에 "P"를 포함하는 변수를 모두 선별하시오

 

> # select(dataframe, matches(".xx_string."))
> #   : Select columns that match a regular expression
> head(select(Cars93_1, matches(".P.")))
     Type Min.Price Max.Price MPG.city MPG.highway
1   Small      12.9      18.8       25          31
2 Midsize      29.2      38.7       18          25
3 Compact      25.9      32.3       20          26
4 Midsize      30.8      44.6       19          26
5 Midsize      23.7      36.2       22          30
6 Midsize      14.2      17.3       22          31

 

> head(select(Cars93_1, matches("P"))) # exactly the same with contains("P")
     Type Min.Price Price Max.Price MPG.city MPG.highway
1   Small      12.9  15.9      18.8       25          31
2 Midsize      29.2  33.9      38.7       18          25
3 Compact      25.9  29.1      32.3       20          26
4 Midsize      30.8  37.7      44.6       19          26
5 Midsize      23.7  30.0      36.2       22          30
6 Midsize      14.2  15.7      17.3       22          31

 

위에 match() 옵션 안에다가 앞에 예제에는 (".P.")를, 뒤의 예제에는 점이 없이 ("P")를 사용했는데요, 그 결과를 보고 차이를 아시겠는지요?  앞뒤로 '.'(dot) 을 붙이면 시작과 끝 말고 변수명 중간에만 특정 문자열이 포함된 변수만 선별하라는 뜻입니다.  matches(".P.") 로 한 경우에는 "P"로 시작하는 "Price" 변수가 없는 반면에, 그냥 matches("P")로 한 경우는 "P"로 시작하는 "Price"변수가 포함되어 있습니다.

 

참고로, '.'(dot) 이 없이 matches()를 쓰면 contains() 와 동일한 결과를 반환합니다.

 

 

 

 

(3-8) select(dataframe, one_of(vars)) : 변수 이름 그룹에 포함된 모든 변수 선별

 

[문제] "Manufacturer", "MAX.Price", "MPG.highway" 의 3개 변수이름을 포함하는 변수 그룹이 있다고 할 때, Cars93 데이터 프레임에서 이 변수 그룹에 있는 변수가 있다면(<- 즉, 있을 수도 있지만 없을 수도 있다는 뜻임!) 모두 선별하시오.

 

> # select(dataframe, one_of(vars))
> #   : Select columns that are from a group of names
> vars <- c("Manufacturer", "MAX.Price", "MPG.highway")
> head(select(Cars93_1, one_of(vars)))
  Manufacturer MPG.highway
1        Acura          31
2        Acura          25
3         Audi          26
4         Audi          26
5          BMW          30
6        Buick          31
Warning message:
In one_of(vars) : Unknown variables: `MAX.Price`

 

 

위의 결과를 보니 "MAX.Price"라는 변수는 "Unknown variables"라고 해서 Warning mesage가 뜨는군요.  Cars93에 보면 "Max.Price"라는 변수는 있어도 "MAX.Price"라는 변수는 없거든요.  이처럼 변수 그룹 vars 에 나열된 이름 중에서 데이터 프레임에 포함된 변수는 선별해서 반환을 해주고, 만약 해당 이름의 변수가 없다면 그냥 Warning message를 제시해주는 것으로 잘 실행이 됩니다.

 

 

반면에 그냥 select() 함수로 위의 변수 그룹을 선별하라고 해보면요, 아래처럼  "object 'MAX.Price' not found" error 메시지와 함께 아예 실행이 안되요.  one_of() 함수가 어떤 때 쓰는건지 이제 이해하시겠지요?!

 

> select(Cars93_1, Manufacturer, MAX.Price, MPG.highway)
Error in eval(expr, envir, enclos) : object 'MAX.Price' not found

 

 

 

 

 

(3-9) select(dataframe, num_range("V", a:n)) : 접두사와 숫자 범위를 조합해서 변수 선별

 

변수 이름이 동일하게 특정 접두사로 시작하는 데이터 프레임의 경우 유용하게 사용할 수 있는 함수입니다.

 

[문제] "V1", "V2", "V3", "V4"의 4개 변수를 가진 df 데이터 프레임에서 "V2", "V3" 변수를 선별하시오. 단, 이때 접두사 "V"와 숫자 범위 2:3 을 조합해서 쓰는 num_range() 옵션을 사용하시오.

 

> # select(df, num_range("V", a:n))
> #   : Select columns from num_range a to n with a prefix
> V1 <- c(rep(1, 10))
> V2 <- c(rep(1:2, 5))
> V3 <- c(rep(1:5, 2))
> V4 <- c(rep(1:10))
> 
> df <- data.frame(V1, V2, V3, V4)
> df
   V1 V2 V3 V4
1   1  1  1  1
2   1  2  2  2
3   1  1  3  3
4   1  2  4  4
5   1  1  5  5
6   1  2  1  6
7   1  1  2  7
8   1  2  3  8
9   1  1  4  9
10  1  2  5 10
> 
> select(df, num_range("V", 2:3))
   V2 V3
1   1  1
2   2  2
3   1  3
4   2  4
5   1  5
6   2  1
7   1  2
8   2  3
9   1  4
10  2  5

 

 

 

 

 

 

(4) 데이터 프레임 변수 이름 변경하기 : rename()

 

dpylr 패키지의 rename() 함수는 rename(dataframe, new_var1 = old_var1, new_var2 = old_var2, ...) 의 형식으로 사용합니다. 

 

   - 새로운 변수 이름을 앞에, 이전 변수이름을 뒤에 위치시킵니다.

   - 큰 따옴표 안씁니다.  그냥 변수 이름만 쓰면 됩니다.

   - 이름을 변경하고자 하는 변수가 여러개 일 경우 ',' (comma)로 구분해서 연속해서 써줍니다.

 

 

[문제] Cars93_1 데이터 프레임의 8개 변수명 앞에 'New_' 라는 접두사(prefix)를 붙여서 변수 이름을 바꾸시오.

 

> # rename() : rename column name
> names(Cars93_1) 
[1] "Manufacturer" "Model"        "Type"         "Min.Price"    "Price"        "Max.Price"   
[7] "MPG.city"     "MPG.highway" 
> 

> # rename(dataframe, new_var1 = old_var1, new_var2 = old_var2, ...)
> Cars93_2 <- rename(Cars93_1,
+ New_Manufacturer = Manufacturer, + New_Model = Model, + New_Type = Type, + New_Min.Price = Min.Price, + New_Price = Price, + New_Max.Price = Max.Price, + New_MPG.city = MPG.city, + New_MPG.highway = MPG.highway) > > names(Cars93_2) [1] "New_Manufacturer" "New_Model" "New_Type" "New_Min.Price" [5] "New_Price" "New_Max.Price" "New_MPG.city" "New_MPG.highway"

 

 

 

이전에 plyr 패키지의 rename() 함수나 reshaple 패키지의 rename() 함수를 사용하던 분이라면 완전 헷갈리실 겁니다.  큰 따옴표("var_name")를 써야 하는건지 말아야 하는건지, 새로운 변수 이름(new_var)과 이전 변수 이름(old_var)의 위치가 앞인지 뒤인지, 변수가 여러개인 경우 c() 로 묶어주어야 하는지 아닌지가 패키지별로 조금씩 다르거든요. (참고  링크=> http://rfriend.tistory.com/41 ) 

 

저도 매번 헷갈립니다. -_-;  그래프는 ggplot2로 단일화해 나가듯이... 데이터 전처리는 dplyr 패키지로 단일화해 나가는 것도 헷갈림을 줄일 수 있는 좋은 전략이라고 생각합니다. (기존에 익혔던 함수가 아깝고 불편함을 못느낀다면 현상 유지도 물론 ok.)

 


dplyr 패키지의 distinct(), sample_n(), sample_frac(), mutate(), transmute(), summarise() 함수에 대해서는 다음번 포스팅에서 소개하겠습니다.

 

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

 

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

 

 

 

 

 

728x90
반응형
Posted by Rfriend
,

혼합모델 군집화(mixture model clustering)의 혼합 모델에는 어떠한 분포도 가능하지만 일반적으로 정규분포(Gaussian normal distribution)을 가장 많이 사용하며, 다음번 포스팅에서는 가우시안 혼합 모델(Gaussian misture model, 줄여서 GMM) 군집화를 다루겠습니다. .

 

혼합모델 군집화(mixture model clustering)에 들어가기에 앞서, 이번 포스팅에서는 정규분포(Gaussian normal distribution)에 대해서 먼저 몸풀기 차원에서 짚어보고 넘아가겠습니다.  준비운동 안하고 혼합모델 군집화에 바로 투입됐다가는 자칫 머리에 쥐가 나거나 OTL 하게 되는 불상사가 예상되기 때문입니다. ^^; 

 

이번 포스팅은 쉬어 가는 코너, 한번에 다 몰라도 좌절하지 않기 코너, 생각날 때 찾아보고 복기하는 코너로 여기면 좋겠습니다.

 

기계학습, 다변량 통계분석 책을 보다 보면 수식, 표기법 때문에 많이 어려워했던 기억이 있을 겁니다.  가장 기본이 되고 많이 사용하는 개념, 수식, 표기법을 중심으로 소개해보겠습니다.

 

 

1) 모집단(Population) vs. 표본집단(Sample)

 

알고자 하는 '전체 객체, 성분, 관측치를 포함하는 집단'을 모집단이라고 하며, 보통은 시간, 비용 상의 이슈로 인해서 모수(parameter)가 알려지지 않은(unknown) 경우가 많습니다.

 

모집단의 특성을 알기 위해 시간과 비용을 감당할 수 있는 범위 내에서 모집단으로 부터 모집단을 잘 대표할 수 있도록 표본(sample)을 추출해서 분석을 하게 됩니다.

 

 

모집단과 표본집단을 알았으니, 각 집단의 기대값과 공분산, 상관행렬, 상관계수는 어떻게 표기하는지 비교해서 살펴보겠습니다.

 

 

2) 모집단(Population)의 기대값과 공분산, 상관행렬, 상관계수

 

성분들이 확률변수로 이루어진 p x 1 확률벡터 X에 대한 평균 벡터(mean vector), 공분산 행렬(covariance matrix), 상관행렬(correlation matrix), 상관계수(correlation coefficient) 는 아래와 같습니다. 

 

공분산 행렬(covariance matrix)을 'Σ' 로 표기한다는 점 유념하시기 바랍니다.  다변량 정규분포 확률분포를 표기할 때 'Σ'가 나옵니다.  (* 주의 : 모두 summation 하라는 뜻이 아님 -.-").

 

 

 

위의 모집단(population)에 대한 표기법을 아래의 표본집단(sample)에 대한 표기법과 비교를 해가면서 유심히 살펴보시고 기억해놓으면 좋겠습니다. 

(강의 듣다보면 혼동해서 잘못쓰는 강사나 교재도 가끔 있음 -_-;) 

 

 

 

(3) 표본집단의 기대값과 공분산, 상관행렬, 상관계수

 

모집단으로부터 n개의 확률표본 X1, X2, ..., Xn에 대해 각 n x p 확률벡터 Xi = (Xi1, Xi2, ..., Xip)' , i = 1, 2, ..., n 의 표본 평균 벡터(sample mean vector), 표본 공분산 행렬(sample covariance matrix), 표본 상관행렬(sample correlation matrix), 표본 상관계수(sample correlation coefficient)는 아래와 같습니다.

 

표본 상관행렬의 우상(rigth upper)과 좌하(left down)의 값은 상호 대칭으로 같습니다. (가령, r12r21은 같은 값을 가짐)

 

 

 


 

 

정규분포는 변수의 개수(1개, 2개, p개)에 따라서 '일변량 정규분포 (univariate normal distribution)', '이변량 정규분포 (bivariate normal distribution)', 다변량 정규분포 (multivariate normal distribution)'로 구분할 수 있습니다.

 

일변량/이변량/다변량 정규분포 확률밀도함수(probability density function)의 표기법과 수식, 그리고 그래프를 살펴보도록 하겠습니다.

 

 

(4) 일변량 정규분포 (univariate normal distribution) vs. 다변량 정규분포 (multivariate normal distribution)

 

일변량 정규분포의 확률변수 X에 대한 확률밀도함수(probability density function) 식은 기억하기가 좀 복잡하고 어려운데요, 자꾸 보다보면 어느샌가 익숙해지고, 더 자주 보다보면 자기도 모르게 기억될 날이 올겁니다. (기억을 했다가도... 인간은 위대한 망각의 동물인지라 두어달만 안보면 다시... ㅋㅋ)

 

 

위의 표기에서 일변량 정규분포 대비 다변량 정규분포의 확률밀도함수의 어디가 서로 다른지 유심히 살펴보시기 바랍니다.  특히 다변량 정규분포의 확률밀도함수 표기법이 통계를 전공하지 않았거나, 선형대수 기본을 모르면 매우 생소하고 어렵게만 느껴질 것 같습니다. (왜 저런거냐고 묻는 댓글 질문은 정중히 사양해염... -_-*)

 

 

 

(5) 이변량 정규분포 (bivariate normal distribution)

 

3차원 이상 넘어가면 사람 머리로 인식하기가 곤란하니, 시각화로 살펴보기에 그나마 가능한 2개의 변수를 가지는 2차원의 '이변량 정규분포(bivariate normal distribution)'의 확률밀도함수(probability density function)에 대해서 알아보겠습니다.  위의 다변량 정규분포의 확률밀도함수 식에서 p = 2 를 대입하면 됩니다.

 

[ 이변량 정규분포 확률밀도함수 - 식 1 ]

 

 

 

위의 이변량 정규분포의 확률밀도함수를 다시 정리해보면 아래와 같습니다.

 

[ 이변량 정규분포 확률밀도함수 - 식 2 ]

 

 

 

'이변량 정규분포 확률밀도함수   식 2'를 보면 이건 뭐... 외계어가 따로 없습니다.  도대체 이렇게 복잡하면서도 쓰임새가 어마무시 많은 요물단지 정규분포를 정리한 천재들이 누구인지 궁금하시지요?

 

위키피디아를 찾아보니, 위대한 수학자 가우스(carl Friedrich Gauss)가 정규분포를 발견했고(그래서 영어로 Gaussian Normal Distribution 이라고 함), 라플라스(Marquis de Laplace)가 그 유명하고 중요하며 시험문제로 반드시 나오는 '중심극한의 정리(Central Limit Theorem)'를 증명하였습니다. 피어슨(Karl Pearson)이 20세기 들어서 '정규분포(Normal Distribution)'이라고 명명하는 것을 확산시키는데 일조했다고 나오네요.  가우스, 라플라스, 피어슨... 역시 천재 거물들 맞군요.

(Normal distribution 이외의 분포는 그럼 비정상(Abnormal) 이라는 소리? -_-?)

 

 

 

 

(* source : https://en.wikipedia.org/wiki/Normal_distribution)

 

 

 

수식으로만 봐서는 저게 뭔가 싶으실텐데요, R의 base 패키지에 내장되어 있는 persp() 함수를 가지고 위의 '이변량 정규분포 - 식 2'를 사용하여 3D plot 으로 그려보겠습니다.


먼저 "mvtnorm" 패키지의 dmvnorm() 함수로 다변량 정규분포 함수를 정의해서 3차원 그래프를 그리는 방법은 아래와 같습니다. 



# Multi-variable Normal Distribution

install.packages("mvtnorm")

library(mvtnorm)


x <- seq(from = -5, to = 5, by = 0.2)

y <- x


gaussian_func <- function(x, y) {

  dmvnorm(cbind(x, y))

}


persp(x, y, outer(x, y, gaussian_func), theta = 30, phi = 10)


 




이번에는 위 포스팅에서 소개한 다변량 정규분포 공식을 이용해서 직접 gaussian_func() 를 정의해보고, 이 함수를 사용해서 persp() 의 3D plot에 manipulate 패키지를 접목해서 동적(interactive plotting)으로 상하(phi), 좌우(theta) 로 돌려가면서 살펴보겠습니다. 

 

아래 첫번째 그래프의 노란색으로 표시해 놓은 단추를 누르면 interactive plot 의 slider 가 나타납니다.  theta(좌~우), phi(상~하)를 조절해가면서 3-D 그래프를 돌려서 보시면 '아, 이변량 정규분포 확률밀도함수가 이런거구나. 느낌 오네~' 하실겁니다.

 

 

##---------------------------------------
## (Gaussian) Mixture Model Clustering
##---------------------------------------

 

# bivariate nomral probability density function
mu1 <- 0   # setting the expected value of x1
mu2 <- 0   # setting the expected value of x2

 

s11 <- 3   # setting the variance of x1
s12 <- 4  # setting the covariance between x1 and x2
s22 <- 3   # setting the variance of x2

 

rho12 <- s12/(sqrt(s11)*sqrt(s22)) # setting the correlation coefficient between x1 and x2
rho12

 

x1 <- seq(-5, 5, length = 50) # generating the vector series x1
x2 <- seq(-5, 5, length = 50) # generating the vector series x2


# multivariate normal density function
gaussian_func <- function(x1, x2){
  term1 <- 1/(2*pi*sqrt(s11*s22*(1-rho12^2)))
  term2 <- -1/(2*(1-rho12^2))
  term3 <- (x1 - mu1)^2/s11
  term4 <- (x2 - mu2)^2/s22
  term5 <- 2*rho12*((x1 - mu1)*(x2 - mu2))/(sqrt(s11)*sqrt(s22))
  term1*exp(term2*(term3 + term4 - term5))
}


# calculating the density values
z_score <- outer(x1, x2, gaussian_func)
head(z_score)

 

 

##------------
# 3-D normal distribution density plot : persp() of {base} package
# with interactive plotting with Manipulate package in RStudio

 

install.packages("manipulate")
library(manipulate)

 

manipulate(persp(x1, x2, z_score,
                 theta = theta_x, # theta gives the azimuthal viewing direction
                 phi = phi_x, # phi gives the colatitude viewing direction
                 main = "Two dimensional Normal Distribution"), 
           theta_x = slider(10, 90, initial = 35), 
           phi_x = slider(10, 90, initial = 10))

 


 

# click the button at yellow circle position

 

[ theta : 35,   phi : 10 ]

 


 

[ theta : 60,   phi : 10 ]

 


[ theta : 80,   phi : 10 ]


[ theta : 90,   phi : 10 ]


 

[ theta : 35,   phi : 30 ]


[ theta : 35,   phi: 50 ]


 

[ theta : 35,   phi: 70 ]


 

[ theta : 35,   phi: 90 ]

 

 

 

 

마지막 그림이 phi = 90 으로서, 꼭대기 상단에서 비행기 타고 내려다본 그림인데요, 이걸 2-D 의 등고선 그림(contour plot)으로 그려보면 아래와 같습니다.

 

 

# 2-D contour plot
contour(x1, x2, z_score, xlab = "x1", ylab = "x2",
        main = "Bivariate(2 dimensional) Normal Distribution : contour plot")

 

 

 

 

 

참고로, 일변량 정규분포 그래프는 http://rfriend.tistory.com/102 의 이전 포스팅을 참고하세요.

 

[Reference]

1. 김재희, 'R 다변량 통계분석', 교우사, 2011

2. Wikipedia :  https://en.wikipedia.org/wiki/Normal_distribution

 

 

이번 포스팅에서 살펴본 '다변량 정규분포'에 대한 기본기를 바탕으로 해서, 다음번 포스팅에서는 '가우시언 혼합 분포 군집화 (Gaussian Mixture Model Clustering)'에 대해서 알아보겠습니다.

 

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

 

728x90
반응형
Posted by Rfriend
,