함수와 파이프
Shiny 앱을 잘 만들려면 함수를 적극적으로 활용해야 합니다. 복잡한 서버 로직을 함수로 분리하면 코드가 훨씬 깔끔해지고 테스트도 쉬워집니다.
function() — 함수 만들기
R에서 함수는 function() 키워드로 만듭니다.
# 기본 형태
함수이름 <- function(인자1, 인자2) {
# 함수 본문
결과값
}
간단한 예시입니다.
# 점수를 등급으로 변환하는 함수
to_grade <- function(score) {
case_when(
score >= 90 ~ "A",
score >= 80 ~ "B",
score >= 70 ~ "C",
TRUE ~ "F"
)
}
to_grade(85) # "B"
to_grade(92) # "A"
기본값(Default) 설정
인자에 기본값을 설정하면 호출 시 생략할 수 있습니다.
# threshold의 기본값은 0.5
filter_high <- function(data, col, threshold = 0.5) {
data[data[[col]] > threshold, ]
}
# 기본값 사용
filter_high(df, "score")
# 기본값 변경
filter_high(df, "score", threshold = 0.8)
여러 값 반환 — list 활용
R 함수는 하나의 값만 반환합니다. 여러 값을 반환하려면 리스트로 묶습니다.
summary_stats <- function(x) {
list(
mean = mean(x, na.rm = TRUE),
median = median(x, na.rm = TRUE),
sd = sd(x, na.rm = TRUE),
n = length(x)
)
}
result <- summary_stats(c(85, 92, 78, 95, 88))
result$mean # 87.6
result$sd # 6.35...
파이프 연산자 |>
파이프는 앞 단계의 출력을 다음 단계의 첫 번째 인자로 전달합니다. 코드를 왼쪽에서 오른쪽으로 읽을 수 있게 해줍니다.
# 파이프 없이 — 안쪽에서 바깥쪽으로 읽어야 합니다
result <- arrange(select(filter(students, dept == "통계"), name, score), desc(score))
# 파이프 사용 — 위에서 아래로 읽힙니다
result <- students |>
filter(dept == "통계") |>
select(name, score) |>
arrange(desc(score))
R 4.1부터 |>가 내장되어 있습니다. tidyverse의 %>%와 기능이 거의 동일하고, 대부분의 경우 서로 바꿔 쓸 수 있습니다.
%>% vs |>
# magrittr 파이프 (tidyverse 제공)
library(magrittr)
x %>% sqrt()
# 내장 파이프 (R 4.1+)
x |> sqrt()
두 파이프의 가장 큰 차이는 .(점) 플레이스홀더 지원 여부입니다.
# %>%는 . 플레이스홀더를 지원합니다
mtcars %>% lm(mpg ~ cyl, data = .)
# |>는 R 4.2+부터 _ 플레이스홀더를 지원합니다
mtcars |> lm(mpg ~ cyl, data = _)
이 교재에서는 내장 파이프 |>를 사용합니다.
Shiny에서 함수가 중요한 이유
Shiny 앱이 커지면 서버 코드가 금방 복잡해집니다. 함수를 잘 활용하면 세 가지 장점이 있습니다.
1. 재사용성
같은 데이터 처리 로직을 여러 반응식에서 사용할 때 함수로 분리합니다.
# 함수로 분리
prepare_data <- function(raw_data, dept_filter, min_score) {
raw_data |>
filter(dept == dept_filter, score >= min_score) |>
arrange(desc(score))
}
server <- function(input, output, session) {
output$table <- renderTable({
prepare_data(students, input$dept, input$min_score)
})
output$plot <- renderPlot({
d <- prepare_data(students, input$dept, input$min_score)
ggplot(d, aes(x = name, y = score)) + geom_col()
})
}
2. 테스트 가능성
Shiny 앱 밖에서 함수를 단독으로 테스트할 수 있습니다.
# 앱을 실행하지 않고도 함수만 테스트
prepare_data(students, "통계", 80)
3. 가독성
함수 이름이 문서 역할을 합니다. filter(students, dept == "통계", score >= 80) |> arrange(desc(score)) 보다 prepare_data(students, "통계", 80)이 의도를 더 명확하게 전달합니다.
익명 함수 (Lambda)
간단한 변환에는 이름 없는 익명 함수를 바로 사용합니다.
# R 4.1+ 단축 표기 (\(x) 형태)
numbers <- c(1, 4, 9, 16)
sapply(numbers, \(x) sqrt(x))
# [1] 1 2 3 4
# 또는 function(x) 전통적인 표기
sapply(numbers, function(x) sqrt(x))
Shiny의 observe()나 reactive() 안에도 함수 본문이 들어가므로, 이 형태가 익숙해지면 Shiny 코드를 작성하는 게 훨씬 자연스러워집니다.
observe({
# 이 안이 함수 본문입니다
cat("input$value가 바뀌었습니다:", input$value, "\n")
})
PART 03에서 이 패턴을 본격적으로 사용합니다.