R 패키지 개발부터 CRAN 제출까지의 여정

나만의 R 패키지를 만들어보자

R
rpackage
CRAN
githubpage
Author
Published

October 20, 2025

Introduction

이번 포스트에서는 내부 의료연구 프로젝트에서 출발해 생존분석 모델 성능평가 R 패키지(survC)를 개발하고, GitHub에 배포하고, CRAN에 제출하기까지의 과정을 공유합니다.

아이디어 선정과 방향 설정 - 어떤 패키지를 개발할 것인가?

패키지 개발의 첫 걸음은 “무엇을 만들 것인가?”를 결정하는 과정입니다. 단순하게 새로운 기능을 추가하는 것이 아닌, 사용자의 불편함을 해결하는 것에서 출발합니다. 내가 ‘평소에 R을 사용하면서 어떤 부분에서 이러한 불편함이 있었지?’ 라는 질문을 스스로에게 묻고 답을 해나가며 개발 아이디어를 얻을 수 있습니다.

고려할 사항들

  1. 문제 중심의 접근
    • 내가 자주 반복하는 과정 중 자동화되면 좋을 부분은 무엇인가?
  2. 기존 패키지의 공백 찾기
    • CRAN, GitHub에서 비슷한 기능의 패키지를 찾아보고,
    • 어떤 부분이 불편하게 구현되어 있는가?
    • 어떤 작업은 빠져 있는가?를 비교한다.
  3. 최종 사용자가 누구인가?
    • 통계 전문가
    • 의료 연구가
    • 비전공 분석가 등
  4. 확장성 및 지속 가능성
    • 향후 버전에서 확장 가능한 구조인지 고려한다.

저는 제가 사용하면서 불편했던 점들을 하나씩 적어보았고, 다른 사람들이 R이나 R Studio를 사용하면서 불편했던 점들도 물어보며 아이디어를 적었습니다.

그 과정에서 나왔던 불편한 점들 중에 몇가지를 적어보았습니다:

  • 의료 데이터의 전처리 및 변수 설정을 매번 해야해서 불편함이 있다
  • 의료 데이터에서 배제 포함 조건을 적용한 뒤의 N수를 플로우 차트로 보여주기 어렵다.
  • 의학통계 교육을 진행할 때 필요한 데이터가 여러군데 나뉘어 있어 참조하기 번거롭다.
  • R studio에서 파일 관리가 번거롭다 등

기획 배경

의학 연구에서 생존분석 연구를 진행할 때 cox 모델을 만들고 원하는 분석을 진행하려면 여러 패키지를 따로 적용했어야 했으며, 그 과정이 복잡해서 이를 한번에 통합해서 쉽게 결과를 얻을 수 있는 패키지가 있으면 좋겠다고 생각했습니다. 그래서 여러가지 다른 시간대를 기준으로 다른 모델들과의 비교를 위한 ROC 와 Harrell’s c-index를 한번에 확인하는 패키지를 만들고 싶었습니다. 그래서 이 결과와 그래프를 보여주는 패키지를 개발하게 되었습니다.

다른 패키지와의 차이점

survival의 패키지는 생존분석의 모델을 만드는 것에 중점적이지만 survC 패키지는 ROC와 C-index 기반의 모델 평가 산출에 초점이 맞춰져있습니다. riskRegression과 같은 패키지의 경우 모델의 예측 성능을 검증하는데 사용되는데 survC 패키지의 경우 concordance를 중심으로 경량화해서 설계된 패키지라는 것에 차이점이 있습니다. survC 패키지의 장점으로는 원하는 결과값을 한번에 파워포인트 형식으로 추출하고, 이를 파워포인트에서 직접 수정할 수 있다는 점에서 장점이 있습니다.

개발 단계

각 시간별로 달라지는 생존분석과 이 결과를 나타내는 time-dependent ROC를 구하고 이를 test set과 validation set을 비교했었던 이전에 진행했던 연구를 바탕으로 패키지 개발을 진행하였습니다.

관련 함수들

calc_risk_score 함수는 coxph 모델을 기반으로 risk score를 구하는 함수 입니다. cindex_calc 함수는 Harrell’s C‑index을 구할때 사용되는 함수입니다. validation_report 함수는 원하는 결과값을 test와 validate을 비교할 수 있도록 ppt 형식으로 ROC 그래프를 그려주는 함수입니다.

library(survC)
library(survival)

set.seed(1234)
lung <- survival::lung
lung <- lung[complete.cases(lung[, c("time", "status", "age", "ph.ecog")]), ]

# Split into training and validation cohorts
split_ids <- sample(seq_len(nrow(lung)))
train_idx <- split_ids[1:110]
val_idx <- split_ids[111:200]

train_df <- lung[train_idx, ]
val_df <- lung[val_idx, ]

# Fit a simple Cox model on the training data
cox_fit <- survival::coxph(
  survival::Surv(time, status == 2) ~ age + ph.ecog,
  data = train_df,
  x = TRUE
)

# Linear predictor / risk scores
train_lp <- calc_risk_score(cox_fit) # risk score 구하기
val_lp <- calc_risk_score(cox_fit, data = val_df) # validation dataset risk score 구하기

#  Harrell's concordance on the validation cohort
cindex_calc(cox_fit) # train dataset의 C-index 구하기
Cindex  Lower  Upper 
 0.600  0.534  0.666 
c_index_val <- cindex_calc(cox_fit, newdata = val_df) # validation의 C-index 구하기

# validation report 추출하기
horizons <- c(200, 400)

validation_report(
  train_data = transform(train_df, time = time, status = as.integer(status == 2)),
  val_data = transform(val_df, time = time, status = as.integer(status == 2)),
  model = cox_fit,
  time_col = "time",
  status_col = "status",
  times = horizons,
  time_unit = "days",
  output = "validation_report.pptx"
)

사용한 패키지

survival, stats, timeROC, officer, rvg 등

패키지 만들기

패키지 기본 구조 만들기

devtools로 패키지의 기본 구조를 만듭니다.

library(devtools)
create_package("myPackage")  # 원하는 패키지 이름을 정합니다.

DESCRIPTION, NAMESPACE, R/ 폴더 등을 포함한 패키지의 틀을 자동으로 갖출 수 있게 해줍니다. 그리고 패키지에 필요한 함수들을 R 폴더 아래에 작성합니다.

DESCRIPTION은 패키지의 설명을 적은 파일입니다. 어떤 패키지인지, 누가 만들었는지, 다른 패키지는 무엇이 필요한지 명시되어 있습니다.

패키지 설명 추가

roxygen2를 사용하여 각 함수에 주석을 자동으로 달아줍니다.

devtools::document()

위의 함수를 사용해서, NAMESPACEman/ 폴더에 R Markdown 문서들을 자동으로 생성합니다.

NAMESPACE는 패키지의 설계도와 같아서 어떤 함수들이 공개되고, 다른 패키지의 어떤 함수들을 import 할지 적어둔 파일입니다.

man/ 폴더는 패키지의 각 함수에 대한 설명을 저장하는 폴더입니다.

예시는 아래와 같습니다.

#' Compute risk scores from a fitted survival model
#'
#' This helper wraps `stats::predict()` for `coxph` objects so that package users
#' can easily obtain linear predictors (default) or risk scores to feed into
#' downstream metrics such as time-dependent ROC or Harrell's C-index.
#'
#' @param model A fitted `coxph` object.
#' @param data Optional dataset on which to score the model. Defaults to the
#'   training data stored within `model`.
#' @param type Scale of the predictions to return. Either `"lp"` (linear
#'   predictor, the default) or `"risk"`. If `NULL` or omitted, `"lp"` is used.
#' @param ... Additional arguments passed to [stats::predict()].
#'
#' @return A numeric vector containing the requested risk scores.
#' @export
#'
#' @examples
#' if (requireNamespace("survival", quietly = TRUE)) {
#'   fit <- survival::coxph(survival::Surv(time, status) ~ age, data = survival::lung)
#'   # Linear predictor on the training data
#'   calc_risk_score(fit)
#'
#'   # Risk scale predictions on new data
#'   calc_risk_score(fit, survival::lung, type = "risk")
#' }

패키지 테스트

테스트 코드 작성

testthat 패키지를 사용하여 unit test를 추가합니다.

usethis::use_testthat()
usethis::use_test("function_name") # 테스트 하고 싶은 함수 이름을 입력합니다.

위와 같이 프로그램을 실행한다면, tests/testthat/test-function_name.R 파일이 자동으로 생성됩니다. 생성된 파일에 아래와 같이 unit test를 추가할 수 있습니다.

test_that("calc_risk_score matches predict() on training data", {
  library(survival)
  lung <- survival::lung
  lung <- lung[complete.cases(lung[, c("time", "status", "age")]), ]
  model <- survival::coxph(survival::Surv(time, status) ~ age, data = lung, x = TRUE)

  expect_equal(
    calc_risk_score(model),
    as.numeric(stats::predict(model, type = "lp"))
  )
})

기대하는 값과, 실행하는 값의 결과에 따라 테스트의 성공 및 실패 여부를 가릴 수 있습니다. 아래 코드를 실행해서 테스트를 실행할 수 있습니다.

devtools::test() # 테스트 실행

패키지 검사

위와 같은 과정을 모두 마쳤다면, 전체 패키지를 테스트 합니다.

devtools::check() 

테스트를 진행하면서 ERROR / WARNING / NOTE 메시지를 보고 수정해야합니다. CRAN에 제출하기 위해서는 ERROR, WARNING 없음이 기준입니다.

그 외

패키지 소개문서 작성

usethis::use_readme_rmd()

use_readme_rmd() 함수를 사용하여 README 파일을 자동으로 생성하고, 필요에 따라 설명 부분을 직접 작성합니다.

pkdown패키지를 사용하여 GitHub의 웹페이지를 만드는 것은 https://blog.zarathu.com/posts/2023-03-17-pkgdown/index.html 를 참조

오픈소스 운영 및 GitHub 관리

Git 생성 및 GitHub 연결

usethis::use_git()    # git 생성
usethis::use_github() # github 연결
gitcreds::gitcreds_set() # GitHub Personal Access Token 입력

패키지의 Git을 생성하고 GitHub 연결합니다.

test coverage

library(covr)
package_coverage() # 패키지 폴더에서 실행

test coverage(테스트 커버리지) 는 “테스트 코드가 실제 함수 코드를 얼마나 실행했는가”를 측정하는 지표입니다. 즉, 전체 코드 중 테스트 코드가 다룬 부분의 비율(%)을 보여주는 것입니다. 이 값이 높을수록 테스트가 코드 전체를 잘 검증하고 있다고 볼 수 있습니다.

[![Codecov test coverage](https://codecov.io/gh/USERNAME/REPO/branch/main/graph/badge.svg)](https://app.codecov.io/gh/USERNAME/REPO)

위의 코드를 수정하여 GitHub의 패키지 README 파일에 입력하면, 패키지의 코드 커버리지 결과가 홈페이지에 반영됩니다.

GitHub 자동화

usethis::use_github_action_check_standard()   # R CMD check 자동화
usethis::use_github_action("pkgdown")         # 사이트 자동 빌드
usethis::use_github_action("test-coverage")   # 테스트 커버리지 자동추적

위의 코드를 실행하면, 패키지를 자동으로 체크하고, 패키지 사이트를 자동으로 빌드하며, 테스트 커버리지도 자동으로 추척합니다. 자동화를 설정한 경우에, 업데이트한 경우에 각 단계를 하나씩 확인할 필요 없이, 모든 것을 자동으로 처리할 수 있어 편리합니다.

CRAN에 패키지 제츨하기

패키지 기본 검사

# 패키지 폴더의 상위 디렉토리에서 실행
R CMD build myPackage # 이름이 myPackage인 패키지를 빌드. tar.gz 파일 생성 
R CMD check myPackage_0.1.0.tar.gz --as-cran # 패키지 버전이 0.1.0을 확인

0 errors | 0 warnings | 0 notes 결과가 나와야 CRAN에 제출할 수 있습니다. 주로 점검해야 하는 항목은 아래와 같습니다.

  • 예제 코드가 너무 오래 걸리지 않게 작성합니다.
  • 모든 함수에 Rd 문서가 존재해야 합니다.
  • 의존 패키지는 DESCRIPTION의 Imports/Suggests에 명시헤야 합니다.

PDF 메뉴얼 생성 확인

# pdf 메뉴얼 생성 확인
R CMD Rd2pdf .

pdf 파일의 경우 LaTeX 환경이 설정되어야 합니다. Ubuntu의 경우에는 아래와 같은 코드를 실행하여 LaTeX 환경을 설치할 수 있습니다.

sudo apt install texlive texlive-fonts-extra texinfo

패키지 제출

https://cran.r-project.org/submit.html 상기의 링크에 위의 테스트들을 통과한 .tar.gz 파일을 업로드 합니다. 그리고 이메일 인증을 완료하면, CRAN에서 기본적인 테스트를 진행합니다. 오류가 발생한 경우에는 오류 내용과 함께 오류 내용을 수정해서 재제출 하라는 이메일을 10분 내외로 받게 됩니다. 특별한 오류가 발생하지 않은 경우에는, 약 10일 전후로 해서 CRAN의 리뷰 결과를 받게 됩니다.

느낀 점

처음 패키지를 개발하면서 어디서부터 시작해야할지 모르겠다는 막막함이 있었지만, 패키지 개발에 도움을 주는 많은 라이브러리들이 있다는 사실에 놀랐습니다. devtoolsusethis와 같은 라이브러리의 도움을 받아 패키지의 기본 틀을 갖출 수 있었고 이를 토대로 패키지를 개발해서 생각보다 수월했습니다. 그리고 pkgdownroxygen2도 사용을 해서 라이브러리의 웹페이지 제작 및 설명을 쉽게 만들수 있었습니다. 패키지의 수정된 부분들을 간단한 함수들을 사용해서 자동으로 업데이트 할 수 있다는 점에서 생각보다 빠른 속도로 패키지를 개발하고 업데이트 할 수 있었습니다.

패키지를 개발하면서 가장 큰 어려움을 겪었던 것은 ‘어떤 패키지를 개발해야 할까?’ 에 대한 답을 찾는 과정이었습니다. digital AI coding assistant를 이용한다면, 원하는 패키지의 개발은 그리 어렵지 않지만, R을 사용하며 불편함을 느꼈던 부분이나, 자동화 혹은 간소화 하고 싶은 부분들은 경험이 많지 않은 사람에게는 쉽지 않겠다는 생각을 했습니다.

처음 패키지를 개발해서 CRAN에 업로드 하는 것보다 앞으로 이 패키지를 유지 보수하고 다른 새로운 기능을 업데이트 하는것이 더 중요하겠다는 생각이 듭니다. 처음에는 미약하지만 R의 생태계를 이해하고 오픈소스의 발전에 기여했다는 점에서 뿌듯함을 느꼈습니다.

Reuse

Citation

BibTeX citation:
@online{kim2025,
  author = {Kim, Minhyuk},
  title = {R {패키지} {개발부터} {CRAN} {제출까지의} {여정}},
  date = {2025-10-20},
  url = {https://blog.zarathu.com/posts/2025-10-20-R_package/},
  langid = {en}
}
For attribution, please cite this work as:
Kim, Minhyuk. 2025. “R 패키지 개발부터 CRAN 제출까지의 여정.” October 20, 2025. https://blog.zarathu.com/posts/2025-10-20-R_package/.