R言語

関数・エラー・テスト

分析処理を小さな関数へ分け、想定外の入力へ対応し、結果を自動確認します。

01

入力と出力を決める

よい関数は一つの役割を持ち、外側の変数を勝手に書き換えず、結果を戻り値として返します。

平均点を返すR
safe_mean <- function(x) {
  mean(x, na.rm = TRUE)
}
result <- safe_mean(c(70, NA, 90))
result
実行結果OUTPUT
[1] 80
02

入力を検証する

関数の前提を満たさない値は早い段階で止め、直し方が分かるメッセージを出します。

割合を検証R
discount_price <- function(price, rate) {
  if (!is.numeric(price) || length(price) != 1 || is.na(price) || price < 0) {
    stop("priceには0以上の数値を1つ指定してください")
  }
  if (!is.numeric(rate) || length(rate) != 1 || is.na(rate) || rate < 0 || rate > 1) {
    stop("rateには0以上1以下の数値を指定してください")
  }
  floor(price * (1 - rate))
}
discount_price(2000, 0.2)
実行結果OUTPUT
[1] 1600
03

全件欠損を区別する

na.rm = TRUEだけでは、すべて欠損の入力でNaNになる場合があります。用途に応じて明示的に扱います。

全件欠損ならNAを返すR
safe_mean <- function(x) {
  if (!is.numeric(x)) stop("数値ベクトルを指定してください")
  if (length(x) == 0 || all(is.na(x))) return(NA_real_)
  mean(x, na.rm = TRUE)
}
safe_mean(c(NA, NA))
safe_mean(c(10, NA, 20))
実行結果OUTPUT
[1] NA
[1] 15
04

エラーを処理する

tryCatch()で予想できる失敗へ対応できます。原因を隠さず、記録または利用者向けメッセージへ変換します。

CSV読み込みを試すR
read_sales <- function(path) {
  tryCatch(
    read.csv(path, fileEncoding = "UTF-8"),
    warning = function(w) stop("CSVの形式を確認してください: ", conditionMessage(w)),
    error = function(e) stop("CSVを読めませんでした: ", conditionMessage(e))
  )
}
すべてを無視しない try(..., silent = TRUE)で失敗を黙らせるだけでは原因を見失います。何を回復できるか決めてから処理します。
05

標準機能で結果を確認する

stopifnot()は条件が偽なら停止します。正常値だけでなく境界値や欠損も試します。

関数を確認R
stopifnot(discount_price(2000, 0.2) == 1600)
stopifnot(discount_price(0, 0.2) == 0)
stopifnot(is.na(safe_mean(c(NA, NA))))
print("すべて成功")
実行結果OUTPUT
[1] "すべて成功"
06

testthatでテストを書く

テストが増えたらtestthatで期待値とエラーを読みやすく記述します。

テスト例R
library(testthat)
test_that("値引き価格を計算できる", {
  expect_equal(discount_price(2000, 0.2), 1600)
  expect_error(discount_price(-1, 0.2), "0以上")
  expect_error(discount_price(1000, 1.2), "0以上1以下")
})
07

エラーを調べる

最初のエラーメッセージを読み、入力の型・長さ・欠損を確認します。traceback()は直前のエラーまでの呼び出し、browser()は関数内で値を確認する助けになります。

確認用の出力R
inspect_input <- function(x) {
  str(x)
  print(length(x))
  print(sum(is.na(x)))
  invisible(x)
}
inspect_input(c(10, NA, 20))
表示の一部OUTPUT
 num [1:3] 10 NA 20
[1] 3
[1] 1
PRACTICE

ミニ課題:合計金額関数

単価と数量の同じ長さの数値ベクトルを受け取り、合計を返す関数を作ってください。長さが違う、負の値がある、欠損がある場合の仕様を決め、テストを書きます。

最低限のテスト
  • 通常の複数商品
  • 値が1件だけ
  • 長さが違う入力
  • 負の単価・数量
  • 欠損値