파이프 연산자
데이터 분석 코드를 처음 쓰면 이런 모습이 됩니다.
result <- round(mean(sqrt(abs(c(-4, 9, 16, -1, 25)))), 2)
왼쪽에서 오른쪽으로 읽히지 않습니다. 가장 안쪽의 c()부터 바깥으로 읽어야 합니다. 데이터가 어떤 변환을 거쳤는지 한눈에 보이지 않습니다.
파이프 연산자는 이 문제를 해결합니다. 이전 결과를 다음 함수의 첫 번째 인자로 넘기면서, 코드를 위에서 아래로, 왼쪽에서 오른쪽으로 읽을 수 있게 만들어줍니다.
magrittr의 %>%
tidyverse 패키지와 함께 널리 퍼진 파이프입니다. magrittr 패키지에서 왔고, dplyr을 로드하면 자동으로 사용 가능합니다.
# 파일: 05_pipe.R
library(magrittr) # 또는 library(dplyr)
c(-4, 9, 16, -1, 25) %>%
abs() %>%
sqrt() %>%
mean() %>%
round(2)
# 3.42
읽는 방향이 자연스럽습니다. "벡터를 만들고, 절댓값을 구하고, 제곱근을 구하고, 평균을 내고, 반올림한다." 생각하는 순서 그대로 코드가 됩니다.
R 4.1+의 네이티브 파이프 |>
R 4.1부터 추가 패키지 없이 쓸 수 있는 파이프입니다.
c(-4, 9, 16, -1, 25) |>
abs() |>
sqrt() |>
mean() |>
round(2)
# 3.42
%>%와 |> 모두 같은 일을 합니다. 어느 쪽을 써도 됩니다. 팀이나 프로젝트의 규칙을 따르면 됩니다.
%>%와 |>의 차이
두 파이프는 대부분의 상황에서 동일하게 동작합니다. 차이가 나는 경우를 알아두면 실수를 줄일 수 있습니다.
플레이스홀더
%>%는 .으로 이전 결과를 원하는 위치에 놓을 수 있습니다.
# 두 번째 인자로 넘기고 싶을 때
c(1, 2, 3) %>% paste("번", ., sep = "")
# "번 1" "번 2" "번 3"
# 여러 번 쓸 수도 있음
"hello" %>% gsub("l", "r", .) # "herro"
|>는 R 4.2부터 _를 플레이스홀더로 지원합니다. 단, 이름 있는 인자에만 씁니다.
# |>의 _는 이름 있는 인자에만 사용 가능
mtcars |> lm(mpg ~ cyl, data = _) # R 4.2+
성능
|>는 함수 호출로 변환되는 방식이 더 단순해서 미세하게 빠릅니다. 대용량 데이터를 다룰 때는 체감 차이가 있을 수 있습니다.
람다 함수
# %>% — 중괄호로 감싼 표현식
c(1, 2, 3) %>% { . * 2 + 1 }
# |> — 익명 함수를 명시해야 함
c(1, 2, 3) |> (\(x) x * 2 + 1)()
파이프라인 사고방식
파이프는 단순한 문법 설탕이 아닙니다. 데이터를 변환하는 파이프라인으로 분석을 설계하는 사고방식입니다.
중간 변수를 만들지 않고 처리 흐름을 표현합니다.
# 중간 변수를 만드는 방식
data1 <- c(88, 72, 95, 61, 83, 77, 90, 55, 68, 94)
data2 <- data1[data1 >= 70] # 70점 이상만
data3 <- sort(data2, decreasing = TRUE) # 내림차순 정렬
data4 <- head(data3, 3) # 상위 3개
result <- mean(data4) # 평균
# 파이프를 쓰는 방식
result <- c(88, 72, 95, 61, 83, 77, 90, 55, 68, 94) |>
(\(x) x[x >= 70])() |>
sort(decreasing = TRUE) |>
head(3) |>
mean()
중간 변수가 없으면 불필요한 이름을 고민할 필요가 없고, 처리 순서가 코드에 그대로 드러납니다.
체이닝 패턴
데이터 분석에서 파이프가 빛나는 순간은 dplyr과 함께 쓸 때입니다. PART 03에서 자세히 다루지만, 맛보기로 확인해봅니다.
library(dplyr)
# 학생 성적 데이터
students <- data.frame(
name = c("Alice", "Bob", "Charlie", "Dana", "Eve"),
score = c(85, 62, 91, 78, 55),
grade = c("2학년", "1학년", "3학년", "2학년", "1학년")
)
# 2학년 중 80점 이상인 학생을 점수 내림차순으로 정렬
result <- students %>%
filter(grade == "2학년") %>%
filter(score >= 80) %>%
arrange(desc(score)) %>%
select(name, score)
result
# name score
# 1 Alice 85
각 단계가 무엇을 하는지 한눈에 읽힙니다. "학생 데이터에서 2학년을 걸러내고, 80점 이상만 남기고, 점수로 내림차순 정렬하고, 이름과 점수만 선택한다."
파이프를 쓸 때와 쓰지 않을 때
파이프가 항상 좋은 것은 아닙니다.
# 단계가 하나라면 굳이 파이프를 쓸 필요 없음
mean(scores) # 충분히 간결
scores |> mean() # 오히려 장황
# 단계가 여러 개라면 파이프가 훨씬 읽기 좋음
scores |>
na.omit() |>
log() |>
mean() |>
exp()
파이프를 쓰면 좋은 상황.
- 3단계 이상의 변환이 연속될 때.
- 중간 결과를 따로 저장할 필요가 없을 때.
- 데이터의 흐름을 설명하고 싶을 때.
파이프를 쓰지 않는 것이 나은 상황.
- 단계가 하나나 둘일 때.
- 중간 결과를 디버깅하거나 재사용할 때.
- 파이프가 코드를 오히려 읽기 어렵게 만들 때.
RStudio 단축키
파이프를 매번 타이핑하는 것은 번거롭습니다. RStudio에서 단축키를 쓰면 편리합니다.
| 파이프 | Windows / Linux | macOS |
|---|---|---|
%>% |
Ctrl + Shift + M | Cmd + Shift + M |
| ` | >` | 설정에서 네이티브 파이프 선택 후 동일 단축키 |
|>를 기본 단축키로 쓰려면 RStudio에서 Tools > Global Options > Code > Use native pipe operator를 체크합니다.
왜 파이프가 중요한가
PART 03에서 배울 tidyverse 전체가 파이프를 기반으로 설계되었습니다. dplyr, tidyr, stringr, forcats 모두 첫 번째 인자로 데이터를 받고 변환된 데이터를 반환합니다. 파이프와 완벽하게 어울리는 구조입니다.
파이프를 자연스럽게 쓸 수 있다면 tidyverse 코드를 읽고 쓰는 것이 한결 쉬워집니다. 지금 익혀두면 이후 파트에서 분석 코드가 훨씬 편하게 느껴질 것입니다.