jpskill.com
✍️ ライティング コミュニティ

r-package-development

Rパッケージの開発において、依存関係の管理、使いやすいAPI設計、品質を保証するテスト、そして分かりやすいドキュメント作成まで、一連の作業をサポートするSkill。

📜 元の英語説明(参考)

R package development guide covering dependencies, API design, testing, and documentation. Use when developing R packages.

🇯🇵 日本人クリエイター向け解説

一言でいうと

Rパッケージの開発において、依存関係の管理、使いやすいAPI設計、品質を保証するテスト、そして分かりやすいドキュメント作成まで、一連の作業をサポートするSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o r-package-development.zip https://jpskill.com/download/8787.zip && unzip -o r-package-development.zip && rm r-package-development.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/8787.zip -OutFile "$d\r-package-development.zip"; Expand-Archive "$d\r-package-development.zip" -DestinationPath $d -Force; ri "$d\r-package-development.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して r-package-development.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → r-package-development フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

R パッケージ開発の意思決定ガイド

R パッケージの依存関係、API 設計、テスト、ドキュメント、およびベストプラクティス

依存関係戦略

依存関係を追加するタイミング vs. base R

# 依存関係を追加するタイミング:
# - 大幅な機能向上
# - メンテナンス負荷の軽減
# - ユーザーエクスペリエンスの向上
# - 複雑な実装 (regex, dates, web)

# base R を使用するタイミング:
# - 単純なユーティリティ関数
# - パッケージが広く使用される (依存関係を最小限に抑える)
# - 依存関係が小さいメリットに対して大きい
# - base R の解決策が簡単

# 意思決定の例:
str_detect(x, "pattern")    # stringr 依存関係を追加する価値あり
length(x) > 0              # これには purrr は不要
parse_dates(x)             # lubridate 依存関係を追加する価値あり
x + 1                      # これには dplyr は不要

Tidyverse 依存関係のガイドライン

# コア tidyverse (通常は価値あり):
dplyr     # 複雑なデータ操作
purrr     # 関数型プログラミング、並列処理
stringr   # 文字列操作
tidyr     # データのリシェイプ

# 特殊な tidyverse (慎重に評価):
lubridate # 大量の date 操作を行う場合
forcats   # 多くのカテゴリ操作を行う場合
readr     # 特定のファイル読み込みが必要な場合
ggplot2   # パッケージが可視化を作成する場合

# 重い依存関係 (控えめに使用):
tidyverse # メタパッケージ、非常に重い
shiny     # インタラクティブなアプリのみ

DESCRIPTION での依存関係の指定

# 強力な依存関係 (必須)
Imports:
    dplyr (>= 1.1.0),
    rlang (>= 1.0.0)

# 推奨される依存関係 (オプション)
Suggests:
    testthat (>= 3.0.0),
    knitr,
    rmarkdown

# 拡張された機能 (オプションだが、利用可能な場合はロードされる)
Enhances:
    data.table

API 設計パターン

関数設計戦略

# 最新の tidyverse API パターン

# 1. 操作ごとのグループ化に .by を使用する
my_summarise <- function(.data, ..., .by = NULL) {
  # 最新のグループ化された操作をサポートする
}

# 2. ユーザーが指定した列に {{ }} を使用する
my_select <- function(.data, cols) {
  .data |> select({{ cols }})
}

# 3. 柔軟な引数に ... を使用する
my_mutate <- function(.data, ..., .by = NULL) {
  .data |> mutate(..., .by = {{ .by }})
}

# 4. 一貫した型を返す (data.frames ではなく tibbles)
my_function <- function(.data) {
  result |> tibble::as_tibble()
}

入力検証戦略

# 関数タイプ別の検証レベル:

# ユーザー向けの関数 - 包括的な検証
user_function <- function(x, threshold = 0.5) {
  # すべての入力を徹底的にチェックする
  if (!is.numeric(x)) stop("x must be numeric")
  if (!is.numeric(threshold) || length(threshold) != 1) {
    stop("threshold must be a single number")
  }
  # ... 関数の本体
}

# 内部関数 - 最小限の検証
.internal_function <- function(x, threshold) {
  # 入力が有効であると仮定する (仮定をドキュメント化する)
  # 重要な不変条件のみをチェックする
  # ... 関数の本体
}

# vctrs を使用したパッケージ関数 - 型安定検証
safe_function <- function(x, y) {
  x <- vec_cast(x, double())
  y <- vec_cast(y, double())
  # 自動型チェックと強制型変換
}

エラー処理パターン

# 適切なエラーメッセージ - 具体的で実行可能
if (length(x) == 0) {
  cli::cli_abort(
    "Input {.arg x} cannot be empty.",
    "i" = "Provide a non-empty vector."
  )
}

# エラーに関数名を含める
validate_input <- function(x, call = caller_env()) {
  if (!is.numeric(x)) {
    cli::cli_abort("Input must be numeric", call = call)
  }
}

# 一貫したエラースタイルを使用する
# ユーザーフレンドリーなメッセージには cli パッケージを使用する
# 開発者ツールには rlang を使用する

エラークラス

# プログラムによる処理のためのカスタムエラークラス
my_error <- function(message, ..., call = caller_env()) {
  cli::cli_abort(
    message,
    ...,
    class = "my_package_error",
    call = call
  )
}

# 特定のエラータイプ
validation_error <- function(message, ..., call = caller_env()) {
  cli::cli_abort(
    message,
    ...,
    class = c("validation_error", "my_package_error"),
    call = call
  )
}

内部関数とエクスポートされた関数を作成するタイミング

関数をエクスポートするタイミング

# エクスポートするタイミング:
# - ユーザーが直接呼び出す場合
# - 他のパッケージが拡張したい場合
# - コアパッケージ機能の一部
# - しばしば変更されない安定した API

# 例: メインのデータ処理関数
#' @export
process_data <- function(.data, ...) {
  # 包括的な入力検証
  # 完全なドキュメントが必要
  # 安定した API コントラクト
}

関数を内部に保持するタイミング

# 内部に保持するタイミング:
# - 変更される可能性のある実装の詳細
# - パッケージ内でのみ使用される
# - 複雑な実装ヘルパー
# - ユーザー向けの API を煩雑にする

# 例: ヘルパー関数 (no @export)
.validate_input <- function(x, y) {
  # 最小限のドキュメント
  # ユーザーを壊すことなく変更できる
  # 入力が事前に検証されていると仮定する
}

# 命名規則: 内部関数には . をプレフィックスとして付ける
.compute_metrics <- function(data) { ... }

テストとドキュメント戦略

テストレベル

# ユニットテスト - 個々の関数
test_that("function handles edge cases", {
  expect_equal(my_func(c()), expected_empty_result)
  expect_error(my_func(NULL), class = "my_error_class")
})

# 統合テスト - ワークフローの組み合わせ
test_that("pipeline works end-to-end", {
  result <- data |>
    step1() |>
    step2() |>
    step3()
  expect_s3_class(result, "expected_class")
})

# パッケージ関数のプロパティベースのテスト
test_that("function properties hold", {
  # 多くの入力にわたって不変条件をテストする
})

テストファイルの構成

tests/
  testthat/
    test-validation.R      # 入力検証テスト
    test-processing.R      # コア処理テスト
    test-output.R          # 出力形式テスト
    test-integration.R     # エンドツーエンドテスト
    helper-fixtures.R      # 共有テストフィクスチャ
  testthat.R              # テストランナー

スナップショットテスト


# 正確に指定するのが難しい複雑な出力の場合
test_that("summary output is correct", {
  expect_snapshot(summary(my_object))
})

# エラーの場合

(原文がここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

R Package Development Decision Guide

Dependencies, API design, testing, documentation, and best practices for R packages

Dependency Strategy

When to Add Dependencies vs Base R

# Add dependency when:
# - Significant functionality gain
# - Maintenance burden reduction
# - User experience improvement
# - Complex implementation (regex, dates, web)

# Use base R when:
# - Simple utility functions
# - Package will be widely used (minimize deps)
# - Dependency is large for small benefit
# - Base R solution is straightforward

# Example decisions:
str_detect(x, "pattern")    # Worth stringr dependency
length(x) > 0              # Don't need purrr for this
parse_dates(x)             # Worth lubridate dependency
x + 1                      # Don't need dplyr for this

Tidyverse Dependency Guidelines

# Core tidyverse (usually worth it):
dplyr     # Complex data manipulation
purrr     # Functional programming, parallel
stringr   # String manipulation
tidyr     # Data reshaping

# Specialized tidyverse (evaluate carefully):
lubridate # If heavy date manipulation
forcats   # If many categorical operations
readr     # If specific file reading needs
ggplot2   # If package creates visualizations

# Heavy dependencies (use sparingly):
tidyverse # Meta-package, very heavy
shiny     # Only for interactive apps

Dependency Specification in DESCRIPTION

# Strong dependencies (required)
Imports:
    dplyr (>= 1.1.0),
    rlang (>= 1.0.0)

# Suggested dependencies (optional)
Suggests:
    testthat (>= 3.0.0),
    knitr,
    rmarkdown

# Enhanced functionality (optional but loaded if available)
Enhances:
    data.table

API Design Patterns

Function Design Strategy

# Modern tidyverse API patterns

# 1. Use .by for per-operation grouping
my_summarise <- function(.data, ..., .by = NULL) {
  # Support modern grouped operations
}

# 2. Use {{ }} for user-provided columns
my_select <- function(.data, cols) {
  .data |> select({{ cols }})
}

# 3. Use ... for flexible arguments
my_mutate <- function(.data, ..., .by = NULL) {
  .data |> mutate(..., .by = {{ .by }})
}

# 4. Return consistent types (tibbles, not data.frames)
my_function <- function(.data) {
  result |> tibble::as_tibble()
}

Input Validation Strategy

# Validation level by function type:

# User-facing functions - comprehensive validation
user_function <- function(x, threshold = 0.5) {
  # Check all inputs thoroughly
  if (!is.numeric(x)) stop("x must be numeric")
  if (!is.numeric(threshold) || length(threshold) != 1) {
    stop("threshold must be a single number")
  }
  # ... function body
}

# Internal functions - minimal validation
.internal_function <- function(x, threshold) {
  # Assume inputs are valid (document assumptions)
  # Only check critical invariants
  # ... function body
}

# Package functions with vctrs - type-stable validation
safe_function <- function(x, y) {
  x <- vec_cast(x, double())
  y <- vec_cast(y, double())
  # Automatic type checking and coercion
}

Error Handling Patterns

# Good error messages - specific and actionable
if (length(x) == 0) {
  cli::cli_abort(
    "Input {.arg x} cannot be empty.",
    "i" = "Provide a non-empty vector."
  )
}

# Include function name in errors
validate_input <- function(x, call = caller_env()) {
  if (!is.numeric(x)) {
    cli::cli_abort("Input must be numeric", call = call)
  }
}

# Use consistent error styling
# cli package for user-friendly messages
# rlang for developer tools

Error Classes

# Custom error classes for programmatic handling
my_error <- function(message, ..., call = caller_env()) {
  cli::cli_abort(
    message,
    ...,
    class = "my_package_error",
    call = call
  )
}

# Specific error types
validation_error <- function(message, ..., call = caller_env()) {
  cli::cli_abort(
    message,
    ...,
    class = c("validation_error", "my_package_error"),
    call = call
  )
}

When to Create Internal vs Exported Functions

Export Function When

# Export when:
# - Users will call it directly
# - Other packages might want to extend it
# - Part of the core package functionality
# - Stable API that won't change often

# Example: main data processing functions
#' @export
process_data <- function(.data, ...) {
  # Comprehensive input validation
  # Full documentation required
  # Stable API contract
}

Keep Function Internal When

# Keep internal when:
# - Implementation detail that may change
# - Only used within package
# - Complex implementation helpers
# - Would clutter user-facing API

# Example: helper functions (no @export)
.validate_input <- function(x, y) {
  # Minimal documentation
  # Can change without breaking users
  # Assume inputs are pre-validated
}

# Naming convention: prefix with . for internal functions
.compute_metrics <- function(data) { ... }

Testing and Documentation Strategy

Testing Levels

# Unit tests - individual functions
test_that("function handles edge cases", {
  expect_equal(my_func(c()), expected_empty_result)
  expect_error(my_func(NULL), class = "my_error_class")
})

# Integration tests - workflow combinations
test_that("pipeline works end-to-end", {
  result <- data |>
    step1() |>
    step2() |>
    step3()
  expect_s3_class(result, "expected_class")
})

# Property-based tests for package functions
test_that("function properties hold", {
  # Test invariants across many inputs
})

Test File Organization

tests/
  testthat/
    test-validation.R      # Input validation tests
    test-processing.R      # Core processing tests
    test-output.R          # Output format tests
    test-integration.R     # End-to-end tests
    helper-fixtures.R      # Shared test fixtures
  testthat.R              # Test runner

Snapshot Testing

# For complex outputs that are hard to specify exactly
test_that("summary output is correct", {
  expect_snapshot(summary(my_object))
})

# For error messages
test_that("errors are informative",
  expect_snapshot(my_function(bad_input), error = TRUE)
})

Documentation Priorities

# Must document:
# - All exported functions
# - Complex algorithms or formulas
# - Non-obvious parameter interactions
# - Examples of typical usage

# Can skip documentation:
# - Simple internal helpers
# - Obvious parameter meanings
# - Functions that just call other functions

roxygen2 Documentation

#' Process and summarize data
#'
#' @description
#' Takes a data frame and computes summary statistics
#' for specified variables.
#'
#' @param data A data frame or tibble.
#' @param vars <[`tidy-select`][dplyr::dplyr_tidy_select]> Columns to summarize.
#' @param .by <[`data-masking`][dplyr::dplyr_data_masking]> Optional grouping variable.
#'
#' @return A tibble with summary statistics.
#'
#' @examples
#' mtcars |> process_data(mpg, .by = cyl)
#'
#' @export
process_data <- function(data, vars, .by = NULL) {
  # ...
}

Package Structure

Recommended Directory Layout

mypackage/
  DESCRIPTION
  NAMESPACE
  LICENSE
  README.md
  R/
    utils.R           # Internal utilities
    validation.R      # Input validation
    core.R            # Core functionality
    methods.R         # S3/S7 methods
    zzz.R             # .onLoad, .onAttach
  man/                # Generated by roxygen2
  tests/
    testthat/
    testthat.R
  vignettes/
    getting-started.Rmd
  inst/
    extdata/          # Example data files
  data/               # Package data (lazy-loaded)
  data-raw/           # Scripts to create package data

DESCRIPTION Best Practices

Package: mypackage
Title: What The Package Does (One Line)
Version: 0.1.0
Authors@R:
    person("First", "Last", email = "email@example.com",
           role = c("aut", "cre"))
Description: A longer description that spans multiple lines.
    Use four spaces for continuation lines.
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
Imports:
    dplyr (>= 1.1.0),
    rlang (>= 1.0.0)
Suggests:
    testthat (>= 3.0.0)
Config/testthat/edition: 3

Release Checklist

# Before release:
devtools::check()         # Must pass with 0 errors, warnings, notes
devtools::test()          # All tests pass
devtools::document()      # Documentation up to date
urlchecker::url_check()   # All URLs valid
spelling::spell_check_package()  # No typos

# Update version
usethis::use_version("minor")  # or "major", "patch"

# Update NEWS.md with changes

# Final checks
devtools::check(remote = TRUE, manual = TRUE)

Common Package Development Mistakes

# Avoid - Using library() in package code
library(dplyr)  # Never in package code!

# Good - Use namespace qualification
dplyr::filter(data, x > 0)

# Or import in NAMESPACE via roxygen2
#' @importFrom dplyr filter mutate

# Avoid - Modifying global state
options(my_option = TRUE)  # Side effect!

# Good - Restore state if you must modify
old_opts <- options(my_option = TRUE)
on.exit(options(old_opts), add = TRUE)

# Avoid - Hardcoded paths
read.csv("/home/user/data.csv")

# Good - Use system.file for package data
system.file("extdata", "data.csv", package = "mypackage")