본문 바로가기
  • plotly로 바로쓰는 동적시각화 in R & 파이썬
데이터 전처리

파이프(%>%) in R

by 아참형인간 2021. 5. 31.
pipes.utf8

magrittr

tidyverse 생태계(echosystem)의 일부인 magrittr 패키지는 코드를 다음과 같은 방법을 통해 보다 읽기 쉽게 만들어주는 연산자(operator)를 제공하는 패키지이다.

  • 왼쪽에서 오른쪽으로 데이터 작업이 이루어지는 구조화 시퀀스

  • 중첩 함수 호출의 방지

  • 로컬 변수와 함수 호출의 최소화

  • 코드안의 어디서든 작업 시퀀스를 추가할 수 있는 쉬운 방법의 제공

파이프(%>%)란?

magrittr 패키지에서 추구하는 짧은 코드, 읽기 쉬운 코드를 구현하기 위해 가장 핵심적으로 사용하는 기능이 바로 %>%로 표현되는 파이프 연산자이다. 이 파이프 연산자는 다음과 같은 기능을 통해 magrittr의 목표를 달성하게 한다. 파이프 연산자를 사용하기 위해서는 먼저 magrittr 패키지를 로딩해야 한다.

library(tidyverse)

파이프 연산자는 다음과 같이 사용할 수 있다.

1. 첫번째 매개변수로 전달

파이프(%\>%)는 파이프의 왼쪽(Left-Hand Side, LHS)에 기술된 객체(Object) 또는 함수의 실행 결과 객체를 오른(Right-Hand Side, RHS)쪽에 기술된 함수의 첫번째 매개변수로 전달하는 역할을 한다. 아래의 코드는 diamonds 데이터프레임을 파이프를 통해 arrange의 첫번째 매개변수로 전달하게 되며 결국 carat 열을 기준으로 정렬한 결과를 출력하게 된다.

## 파이프를 이용한 코드
diamonds %>%
  arrange(carat)
## # A tibble: 53,940 x 10
##    carat cut       color clarity depth table price     x     y     z
##    <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
##  1   0.2 Premium   E     SI2      60.2    62   345  3.79  3.75  2.27
##  2   0.2 Premium   E     VS2      59.8    62   367  3.79  3.77  2.26
##  3   0.2 Premium   E     VS2      59      60   367  3.81  3.78  2.24
##  4   0.2 Premium   E     VS2      61.1    59   367  3.81  3.78  2.32
##  5   0.2 Premium   E     VS2      59.7    62   367  3.84  3.8   2.28
##  6   0.2 Ideal     E     VS2      59.7    55   367  3.86  3.84  2.3 
##  7   0.2 Premium   F     VS2      62.6    59   367  3.73  3.71  2.33
##  8   0.2 Ideal     D     VS2      61.5    57   367  3.81  3.77  2.33
##  9   0.2 Very Good E     VS2      63.4    59   367  3.74  3.71  2.36
## 10   0.2 Ideal     E     VS2      62.2    57   367  3.76  3.73  2.33
## # ... with 53,930 more rows

위의 코드는 아래의 코드와 동일하다. 하지만 코드를 보다 쉽게 이해할 수 있게 코딩할 수 있다.

## 파이프를 사용하지 않고 위의 코드와 동일한 코드
arrange(diamonds, carat)
## # A tibble: 53,940 x 10
##    carat cut       color clarity depth table price     x     y     z
##    <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
##  1   0.2 Premium   E     SI2      60.2    62   345  3.79  3.75  2.27
##  2   0.2 Premium   E     VS2      59.8    62   367  3.79  3.77  2.26
##  3   0.2 Premium   E     VS2      59      60   367  3.81  3.78  2.24
##  4   0.2 Premium   E     VS2      61.1    59   367  3.81  3.78  2.32
##  5   0.2 Premium   E     VS2      59.7    62   367  3.84  3.8   2.28
##  6   0.2 Ideal     E     VS2      59.7    55   367  3.86  3.84  2.3 
##  7   0.2 Premium   F     VS2      62.6    59   367  3.73  3.71  2.33
##  8   0.2 Ideal     D     VS2      61.5    57   367  3.81  3.77  2.33
##  9   0.2 Very Good E     VS2      63.4    59   367  3.74  3.71  2.36
## 10   0.2 Ideal     E     VS2      62.2    57   367  3.76  3.73  2.33
## # ... with 53,930 more rows

2. 중첩된 함수 사용을 방지

위의 코드와 같이 하나의 파이프 연산자를 사용한 코드는 함수의 중첩이라기 보다는 오히려 함수 호출을 위한 매개변수처럼 보이지만 두개이상의 함수를 연달아 사용하는 경우는 코드를 이해하기가 어려워진다.

## 파이프를 이용한 코드
diamonds %>%
  filter(cut == 'Good') %>%
  select(carat, cut, color, clarity) %>%
  arrange(carat)
## # A tibble: 4,906 x 4
##    carat cut   color clarity
##    <dbl> <ord> <ord> <ord>  
##  1  0.23 Good  E     VS1    
##  2  0.23 Good  F     VS1    
##  3  0.23 Good  E     VS1    
##  4  0.23 Good  F     VVS2   
##  5  0.23 Good  F     VVS2   
##  6  0.23 Good  D     VVS1   
##  7  0.23 Good  G     VVS2   
##  8  0.23 Good  E     VS2    
##  9  0.23 Good  F     VS2    
## 10  0.23 Good  E     VVS1   
## # ... with 4,896 more rows

위의 코드와 같이 세개 이상의 코드가 파이프로 연결되는 경우는 파이프 연산자를 사용하면 순서대로 이해하기가 편하지만 아래와 같이 중첩 함수의 형태로 사용된다면 이해하기도 어렵고 코딩하기도 어려워진다.

## 함수 중첩을 이용한 코드

arrange(select(filter(diamonds, cut == 'Good'), carat, cut, color, clarity), carat)

## # A tibble: 4,906 x 4
##    carat cut   color clarity
##    <dbl> <ord> <ord> <ord>  
##  1  0.23 Good  E     VS1    
##  2  0.23 Good  F     VS1    
##  3  0.23 Good  E     VS1    
##  4  0.23 Good  F     VVS2   
##  5  0.23 Good  F     VVS2   
##  6  0.23 Good  D     VVS1   
##  7  0.23 Good  G     VVS2   
##  8  0.23 Good  E     VS2    
##  9  0.23 Good  F     VS2    
## 10  0.23 Good  E     VVS1   
## # ... with 4,896 more rows

3. . 의 사용

파이프 연산자를 사용할 때 간혹 RHS의 첫번째 매개변수 위치가 아닌 다른 위치에 LHS를 사용해야 할 때가 있다. 이때는 LHS가 들어가야할 위치에 .을 표기함으로써 LHS를 사용할 수 있다.

## .을 사용한 파이프의 예

diamonds %>%
filter(cut == 'Good') %>%
aggregate(. ~ color, data = ., FUN = . %>% mean %>% round(2))

##   color carat cut clarity depth table   price    x    y    z
## 1     D  0.74   2    3.19 62.37 58.54 3405.38 5.62 5.63 3.50
## 2     E  0.75   2    3.50 62.20 58.78 3423.64 5.62 5.63 3.50
## 3     F  0.78   2    3.63 62.20 58.91 3495.75 5.69 5.71 3.54
## 4     G  0.85   2    3.91 62.53 58.47 4123.48 5.85 5.86 3.65
## 5     H  0.91   2    3.55 62.50 58.61 4276.25 5.97 5.97 3.73
## 6     I  1.06   2    3.79 62.48 58.77 5078.53 6.25 6.26 3.90
## 7     J  1.10   2    3.67 62.40 58.81 4574.17 6.38 6.39 3.98

위의 코드는 diamonds 데이터 프레임을 먼저 cut이 ’Good’인 데이터만 필터링 한 후, color 열을 기준으로 모든 열의 평균값을 구해서 소수점 2자리에서 반올림한 결과를 산출하는 코드이다.

세번째 라인의 코드에서 .은 세번 등장하는데 파이프를 통해 산출된 LHS를 사용하는 .은 두번째와 세번째이다. 두번째 .data 매개변수로 앞서 파이프로 산출된 cut이 ’Good’인 데이터를 data 매개변수로 사용한다는 의미이고 세번째 .은 함수정의(FUN)의 매개변수로 앞서 파이프로 산출된 cut이 ’Good’인 데이터의 평균을 구하고 이를 소수점 두번째 자리에서 반올림하는 함수를 정의하는 코드이다.

여기서 주의해야 할 것은 첫번째 .인데 이 .은 파이프 연산자의 결과를 가져오는 .를 의미하는 것이 아니고 함수식에서 사용하는 .으로 전체 열을 의미한다. 결국 이 코드를 파이프 연산자 없이 코딩하면 다음과 같다.

##파이프 없는 코딩

aggregate(. ~ color, data = filter(diamonds, cut == 'Good'), FUN = function(x) round(mean(x), 2))

##   color carat cut clarity depth table   price    x    y    z
## 1     D  0.74   2    3.19 62.37 58.54 3405.38 5.62 5.63 3.50
## 2     E  0.75   2    3.50 62.20 58.78 3423.64 5.62 5.63 3.50
## 3     F  0.78   2    3.63 62.20 58.91 3495.75 5.69 5.71 3.54
## 4     G  0.85   2    3.91 62.53 58.47 4123.48 5.85 5.86 3.65
## 5     H  0.91   2    3.55 62.50 58.61 4276.25 5.97 5.97 3.73
## 6     I  1.06   2    3.79 62.48 58.77 5078.53 6.25 6.26 3.90
## 7     J  1.10   2    3.67 62.40 58.81 4574.17 6.38 6.39 3.98

4. 빈괄호나 함수명만 사용

앞선 예에서 파이프 연산자를 통해 전달하는 RHS는 모두 두개 이상의 매개변수가 필요한 함수였고 파이프 연산자로 생성되는 LHS는 RHS의 첫번째 매개변수로 전달되었다. 하지만 RHS에 단 하나의 매개변수만이 필요한 경우는 빈 ()를 사용하거나, 함수명만 사용하거나, .을 사용해서 코딩할 수 있다.

diamonds %>%
  filter(cut == 'Good') %>%
  aggregate(. ~ color, data = ., FUN = . %>% mean %>% round(2)) %>%
  head
##   color carat cut clarity depth table   price    x    y    z
## 1     D  0.74   2    3.19 62.37 58.54 3405.38 5.62 5.63 3.50
## 2     E  0.75   2    3.50 62.20 58.78 3423.64 5.62 5.63 3.50
## 3     F  0.78   2    3.63 62.20 58.91 3495.75 5.69 5.71 3.54
## 4     G  0.85   2    3.91 62.53 58.47 4123.48 5.85 5.86 3.65
## 5     H  0.91   2    3.55 62.50 58.61 4276.25 5.97 5.97 3.73
## 6     I  1.06   2    3.79 62.48 58.77 5078.53 6.25 6.26 3.90

이 코드는 아래의 코드와 동일하다.

diamonds %>%
  filter(cut == 'Good') %>%
  aggregate(. ~ color, data = ., FUN = . %>% mean %>% round(2)) %>%
  head()
diamonds %>%
  filter(cut == 'Good') %>%
  aggregate(. ~ color, data = ., FUN = . %>% mean %>% round(2)) %>%
  head(.)

5. 익명함수(anonymous function) 사용

R에서 함수를 생성하기 위해서는 함수 코드를 생성한 후 함수 코드 블럭을 function 키워드를 사용하여 함수 객체를 생성하여 필요한 위치에서 호출하여 사용한다. 그러나 한번만 사용하는 간단한 함수를 작성하거나 간단한 코드 블럭을 생성하는 경우 파이프 연산자를 사용할 수 있다.

diamonds %>%
  (function(x) {
    x %>% filter(cut == 'Good') %>%
      head
  })
## # A tibble: 6 x 10
##   carat cut   color clarity depth table price     x     y     z
##   <dbl> <ord> <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1  0.23 Good  E     VS1      56.9    65   327  4.05  4.07  2.31
## 2  0.31 Good  J     SI2      63.3    58   335  4.34  4.35  2.75
## 3  0.3  Good  J     SI1      64      55   339  4.25  4.28  2.73
## 4  0.3  Good  J     SI1      63.4    54   351  4.23  4.29  2.7 
## 5  0.3  Good  J     SI1      63.8    56   351  4.23  4.26  2.71
## 6  0.3  Good  I     SI2      63.3    56   351  4.26  4.3   2.71

위의 코드는 cut이 ’Good’인 데이터만 필터링 한 결과를 처음 6행만 보여주는 익명 함수를 생성하는 코드이다. 이 함수는 재사용되는 함수가 아니기 때문에 함수명을 지정할 필요가 없고 함수의 매개변수로 전달되는 x에 LHS인 diamonds가 바로 전달되어 수행된 결과가 출력된다.

이렇게 RHS를 함수 정의로 표현할 수 있지만 이와 동등한 코드로 함수의 형태가 아닌 다음과 같이 짧은 코드 블럭의 형태로도 작성될 수 있다.

diamonds %>%
  { 
    filter(., cut == 'Good') %>% 
      head
  }
## # A tibble: 6 x 10
##   carat cut   color clarity depth table price     x     y     z
##   <dbl> <ord> <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1  0.23 Good  E     VS1      56.9    65   327  4.05  4.07  2.31
## 2  0.31 Good  J     SI2      63.3    58   335  4.34  4.35  2.75
## 3  0.3  Good  J     SI1      64      55   339  4.25  4.28  2.73
## 4  0.3  Good  J     SI1      63.4    54   351  4.23  4.29  2.7 
## 5  0.3  Good  J     SI1      63.8    56   351  4.23  4.26  2.71
## 6  0.3  Good  I     SI2      63.3    56   351  4.26  4.3   2.71

댓글