https://gomguard.tistory.com/243 파이프의 종류 4가지
%>% (파이프)
%T% (티 파이프)
%<>% (병합 파이프)
%$% (노출 파이프)
파이프는 일련의 작업을 명확하게 표현할 수 있는 강력한 도구이다. 지금까지 파이프가 어떻게 작동하는지, 또 어떤 대안이 있는지 모른 채 사용했다. 파이프를 자세히 살펴보고, 파이프의 대안, 파이프를 사용하지 말아야 할 경우, 유용한 관련도구에 대해 배워보자.
파이프 %>%는 스테판 밀턴 배치(Stefan Milton Bache)의 magrittr패키지에서 가져온 것이다. tibyverse패키지는 자동으로 %>%를 로드하므로 보통 명시적으로 로드하지 않는다. 그러나 여기서 우리는 파이프에 집중하며 다른 패키지를 로드하지 않을 것이므로 명시적으로 magrittr를 로드한다.
install.packages("magrittr")
library(magrittr)
파이프의 요점은 읽고 쉽고 이해하기 쉽고 코드를 작성하도록 돕는 것이다. 왜 파이프가 유용한지를 보기 위해 동일한 코드를 여러 가지 방법을 살펴보겠다. 코드를 이용하여 foo foo 라는 이름의 작은토끼에 관해 이야기해보자.
작은 토끼 Foo Foo,
숩속으로 뛰어서,
들쥐들을 뽑아서,
머리를 꽁꽁치네
율동이 있는 동시이다.
작은 토끼 Foo Foo를 표현할 객체를 정의해 보자.
foo_foo <- little_bunny()
이제 각 핵심 동사에 대해 핵심 함수 hop(), scoop() 및 bop()을 사용하자. 앞의객체에 동사들을 써서 코드로 이야기하는 방법은 (적어도) 네 가지이다.
가장 간단한 방법은 각 단계를 새로운 객체로 저장하는 것이다.
foo_foo_1 <- hop(foo_foo, through = forest)
foo_foo_2 <- scoop(foo_foo_1, up = field_mice)
foo_foo_3 <- bop(foo_foo_2, on = head)
이 방식의 주요 단점은 각 중간 요소의 이름을 지정해야 한다는 것이다. 자연스러운 이름들이 있다면 이 방법은 나쁘지 않으며 오히려 그렇게 해야한다. 그러나 이 예제에서 보는 것처럼 자연스러운 이름이 없는 경우가 많으며 숫자 접미사를 추가하여 이름을 고유하게 만들게 된다. 이렇게 하면 두 가지 문제가 발생한다.
중요하지 않은 이름들 때문에 코드가 복잡해진다.
각 줄마다 접미사를 신중하게 늘려야 한다.
이러한 방법을 쓰면 많은 데이터 사본이 만들어지면서, 많은 양의 메모리가 필요할 것이라고 우려하는 사람이 있을지 모르겠다. 놀랍게도 이것은 사실이 아니다. 우선 메모리에 대해 사전에 걱정하는 데 시간을 쓰는 것은 바랍직한 방법이 아니다. 사전이 아니라, 문제가 되면 (즉, 메모리가 부족한 경우)그때 걱정하라. 둘째, R은 바보가 아니며 가능한 경우 데이터프레임끼리 열을 공유한다. ggplot2::diamonds에 새 열을 추가하는 실체 데이터 조작 파이프 라인을 살펴보자.
# install.packages("pryr")
library(pryr)
diamonds <- ggplot2::diamonds
diamonds2 <- diamonds %>%
dplyr::mutate(price_per_carat = price / carat)
pryr::object_size(diamonds)
## 3.46 MB
pryr::object_size(diamonds2)
## 3.89 MB
pryr::object_size(diamonds, diamonds2)
## 3.89 MB
pryr::object_size()는 인수 전체가 차지하는 메모리를 제공한다.
어떻게 된 것일까? diamonds2는 diamond와 열 10개를 공유한다. 데이터를 모두 복제할 필요는 없고 두 개의 데이터프레임은 공유하는 변수가 있다. 이러한 변수는 사용자가 수정하는 경우에만 복사된다. 다음 예제에서는 diamond$carat값 하나를 수정한다. 즉, 두 데이터프레임은 carat변수를 더 이상 공유할 수 없으며 사본이 만들어져야 한다.
diamonds$carat[1] <- NA
pryr::object_size(diamonds)
## 3.46 MB
pryr::object_size(diamonds2)
## 3.89 MB
pryr::object_size(diamonds, diamonds2)
## 4.32 MB
(여기서는 내장함수 object.size()가 아닌 pryr::object_size()를 사용했다. objecr.size()는 객체 여러개를 공유하는 데이터의 크기를 계산할 수 없다.)
각 단계에서 중간 개체를 생성하는 대신 원본 객체를 덮어쓸 수도 있다.
foo_foo <- hop(foo_foo, through = forest)
foo_foo <- scoop(foo_foo, up = field_mice)
foo_foo <- dop(foo_foo, on = head)
이 방법은 타이핑이 줄어들어(그리고 생각할 필요가 줄어들아), 실수할 가능성도 줄어든다. 그러나 두 가지 문제가 있다.
디버깅이 고통스럽다. 실수하면 처음부터 전체 파이프라인을 다시 실행해야 한다.
변환되는 객체가 반복되어서 (위에서 foo_foo를 6번 썼다!), 각 행에서 정작 무엇이 변경되는지 알아보기 어렵다.
또 다른 접근법은 할당을 하지 않고, 함수 호출을 함께 묶는 것이다.
bop(
scoop(
hop(foo_foo, through = forest),
up = field_mice
),
on = head
)
여기서 단점은 오른쪽에서 왼쪽으로, 안쪽부터 읽어야 한다는 것과, 인수가 너무 멀리 떨어지게 된다는 것이다 (유창한 말로는 대그우드(Dagwood)센드위치 문제 라고 한다). 요약하면 이런코드는 알아보기 어렵다. *https://en.wikipedia.org/wiki/Dagwood_sandwich
마지막으로 파이프를 사용할 수 있다.
foo_foo %>%
hop(through = forest) %>%
scoop(up = field_mice)%>%
bop(on = head)
이 방법은 명사가 아닌 동사에 초점을 맞춥니다. 일련의 명령형처럼 읽을 수 있다. Foo Foo는 깡총뛰고(hop), 그 다음 뽑고(scoop), 콩 쳐라(bop). 단점은 물론 파이프에 익숙해져야 한다는 것이다. 전에 %>%를 본 적이 없다면 이 코드가 무엇을 하는지 전혀 알 수 없다. 다행히 대부분의 사람들은 이 방법을 빨리 이해하므로 파이프에 익숙하지 않은 다른 사람들과 코드를 공유할 때도 쉽게 가르칠 수 있다.
파이프는 ’어휘 변환 (lexical transformation)’을 수행하여 작동한다. 즉, 안 보이는 곳에서 magrittr는 파이프로 된 코드를, 중간 객체를 덮어 쓰는 형식으로 다시 어셈블한다. 앞과 같은 파이프를 실행하면 magrittr는 다음과 같이 처리한다.
my_pipe <- function(.){
. <- hop(., through = forest)
. <- scoop(., up = fiedmice)
bop(., on = head)
}
my_pipe(foo_foo)
이는 파이프가 다음과 같은 두 가지 종류의 함수에 대해 작동하지 않는다는 것을 의미한다.
assign("x", 10)
x
## [1] 10
"x" %>% assign(100)
x
## [1] 10
assign은 %>%이 사용하는 임시 환경에 할당하므로, 파이프와 함께 assign을 사용하면 작동하지 않는다.
그래도 assign을 파이프와 함께 꼭 사용하고 싶다면, 환경에 대해 명시적이어야 한다.
env <- environment()
"x" %>% assign(100, envir = env)
x
## [1] 100
이와 같은 문제가 생기는 함수에는 get()과 load()등이 있다.
이것이 문제가 되는 곳 중 하나는 오류를 캡쳐하고 처리하는 tryCatch()이다.
tryCatch(stop("!"), error = function(e) "An error")
## [1] "An error"
stop("!") %>%
tryCatch(error = function(e) "An error")
Error in eval(lhs, parent, parent) : !
?try
?tryCatch
try(try는 선언된 내부 코드중 Error가 있을 경우 이를 그냥 skip)
tryCatch(예외처리, Error가 발생했더라도 후속 조치)
즉, 이 함수들은 요류를 다루기 위한 함수들입니다.
베이스R의 try(), suppressMessges(), suppressWarnings()등, 비교적 광범위한 함수들이 이러한 동작을 한다.
파이프는 강력한 도구이지만 마음껏 사용할 수 있는 유일한 도구는 아니며 모든 문제를 해결하지 못한다.
파이프는 짧은 선형 순서의 작업을 다시 작성하는 데 가장 유용하다.
다음과 같은 경우에 사용해야 할 것이다.
파이프 길이가 약 10단계보다 긴 경우이다. 이때는 의미 있는 이름을 가진 중간 객체를 만들라. 중간 결과를 더 쉽게 확인할 수 있기 때문에 디 버깅이 쉬워지고, 변수 이름이 의사소통을 도와주기 때문에 코드를 더 쉽게 이해할 수 있다.
다중 입력 또는 출력인 경우이다. 주 객체 하나가 변환되는 것이 아니라, 두 개 이상의 객체가 함께 조합되는 경우에는 파이프를 사용하지 않는다.
복잡한 종속 구조가 있는 유형 그래프(directed graph)을 고려하는 경우이다. 파이프는 근본적으로 선형이며 이를 이용하여 복잡한 관계를 표현하며 일반적으로 코드가 혼란스러워진다.
tidyverse 의 모든 패키지는 자동으로 %>% 를 사용 가능하게 하므로 보통 magrittr 을 명시적으로 로드할 필요가 없다.
그러나 magrittr 안에는 사용해볼 만한 유용한 도구들이 더 있다.
문제를 해결하려면 ’티’파이프를 사용하면 된다. %T>%는 오른쪽 대신 왼쪽을 반환한다는 점을 제외하고 %>%처럼 작동한다. 문제 그대로 T자 모양의 파이프이기때문에 ’티’라고 불린다.
rnorm(100) %>%
matrix(ncol = 2) %>%
plot() %>%
str()
## NULL
rnorm(100) %>%
matrix(ncol = 2) %T>%
plot() %>%
str()
## num [1:50, 1:2] 0.122 1.5 -0.133 -0.169 -1.788 ...
-데이터프레임 기반 API가 없는 함수로 작업하는 경우 (즉, 데이터프레임이 아닌 개별 백터를 전달하고, 표현식을 데이터 프레임 컨텍스트에서 평가할 경우)%$% 이 유용하다. 데이터프레임의 변수를 명시적으로 참조할 수 있도록 변수를 추출한다. 이 방법은 베이스R함수 여러개로 작업할 때 유용하다.
mtcars %$%
cor(disp, mpg)
## [1] -0.8475514
-할당의 경우 magrittr은 %<>% 연산자를 제공하므로 다음과 같은 코드를 대체할 수 있다.
mtcars <- mtcars %>%
transform(cyl = cyl * 2)
위의 코드를 다음과 대체
mtcars %<>% transform(cyl = cyl * 2)
https://www.datacamp.com/community/tutorials/pipe-r-tutorial