Rで解析:研究の作業手順で再利用しやすい汎用処理を集めた「thisutils」パッケージ
研究や解析のコードでは、行列から値の大きい要素を取り出す、分類結果を評価する、リスト形式の列を平坦化するといった細かな処理が繰り返し登場します。しかし、こうした処理をそのつど書き起こすと、実装のばらつきや動作確認に手間がかかります。
「thisutils」パッケージは、研究の作業手順で再利用しやすい汎用処理をまとめたパッケージです。疎行列や密行列から行・列ごとに上位k個を抽出する処理や、予測ラベルと正解ラベルから正解率・マクロF値・調整ランド指数などをまとめて算出する処理が可能です。また、リスト列を持つデータフレームの平坦化や、近傍ラベルの多様性を表すLISI指標の計算も収録されています。さらに、タイムスタンプや色を付けたメッセージ出力や、文字列に埋め込んだ式の評価も可能です。
本パッケージの利用で、解析コードの共通部分を整理し、簡単に再利用できるのではないかと考えます。
パッケージバージョンは0.5.0。Windows 11 x64 (build 26200)のR version 4.6.1で確認しています。
パッケージのインストール
下記コマンドを実行してください。
# パッケージのインストール
install.packages("thisutils")
# パッケージの読み込み
library("thisutils")コマンド例
詳細はコメント、パッケージのヘルプを確認してください。
このパッケージの関数は、疎行列や密行列の要素選択、分類結果の評価、リスト列の展開など、互いに独立した処理の集まりです。
文中の「疎行列」は多くの要素が0の行列、「上位k個の抽出」は各行または各列で値の大きい順にk個だけ残す操作を指します。「LISI指標」は、ある地点の近傍にどれだけ多様なラベルが混ざっているかを表す値で、1に近いほど周囲が同じラベルで占められ、対象ラベル数に近いほど均等に混ざっていることを示します。
疎な行列の上位k個の選択:run_sparse_topk_storedコマンド
疎行列の各列について、保存されている非ゼロ要素の中から値の大きい上位k個を残し、その行番号(idx)と値(value)を返します。
| オプション | 意味 | 初期値 |
|---|---|---|
| x | dgCMatrix型または変換可能なオブジェクト | なし |
| k | 各列または行に残す上位要素の個数 | なし |
| by | 選択の方向。”col”は列ごと、”row”は行ごとに順位付けする | c(“col”, “row”) |
| decreasing | 値の大きい順に並べるかどうかの指定 | TRUE |
# 市区町村間の観光客の移動を疎行列で表現
# 行が出発地、列が到着地
area <- c("左京区", "右京区", "伏見区", "山科区")
flow <- Matrix::sparseMatrix(
i = c(1, 1, 2, 3, 3, 4),
j = c(2, 3, 1, 1, 4, 3),
x = c(120, 80, 95, 60, 150, 45),
dims = c(4, 4),
dimnames = list(area, area)
)
# 到着地ごとに流入の多い上位2地区を残す
run_sparse_topk_stored(flow, k = 2, by = "col")
$idx
[,1] [,2]
[1,] 2 3
[2,] 1 NA
[3,] 1 4
[4,] 3 NA
$value
[,1] [,2]
[1,] 95 60
[2,] 120 NA
[3,] 80 45
[4,] 150 NA結果の各行は到着地(入力の列)に対応します。1行目の左京区には行番号2の右京区(95)と行番号3の伏見区(60)から流入があり、値の大きい順に並んでいます。該当がない位置はNAになります。
分類指標の算出:classification_metrics_computeコマンド
予測したクラスラベルと正解のクラスラベルを比較し、正解率やマクロF値、純度、正規化相互情報量、調整ランド指数などの評価指標をまとめて返します。
| オプション | 意味 | 初期値 |
|---|---|---|
| predicted | 予測したクラスラベルの文字ベクトル | なし |
| truth | 正解のクラスラベルの文字ベクトル。predictedと同じ長さ | なし |
| rare_threshold | 希少クラスとみなす出現割合の閾値。この値以下のクラスがrare_recallの対象になる | 0.05 |
# 宇治茶の等級を自動判定した結果
predicted <- c("特級", "特級", "一級", "二級", "一級", "二級", "特級", "一級")
# 実際の等級
truth <- c("特級", "一級", "一級", "二級", "一級", "一級", "特級", "二級")
# 判定結果の評価指標をまとめて算出
classification_metrics_compute(predicted, truth)
$accuracy
[1] 0.625
$macro_f1
[1] 0.6238095
$purity
[1] 0.625
$nmi
[1] 0.3666953
$ari
[1] 0
$rare_recall
[1] NA
$class_table
class precision recall f1 support
1 一級 0.6666667 0.5 0.5714286 4
2 特級 0.6666667 1.0 0.8000000 2
3 二級 0.5000000 0.5 0.5000000 2希少クラスが存在しないため、rare_recallはNAになります。クラスごとの適合率・再現率・F値はclass_tableにまとまります。
リスト列の展開:unnest_funコマンド
リスト形式の列を持つデータフレームを、リストの各要素が1行になるように展開します。
| オプション | 意味 | 初期値 |
|---|---|---|
| data | データフレーム | なし |
| cols | 展開する列 | なし |
| keep_empty | 長さ0の要素を欠損値1行として残すかどうかの指定 | FALSE |
# 直売所ごとの取扱品目をリスト列に持つデータフレーム
sales <- data.frame(
直売所 = c("大原", "宇治田原", "和束"),
地域 = c("左京区", "綴喜郡", "相楽郡"),
stringsAsFactors = FALSE
)
# 品目をリスト列として追加
sales$品目 <- list(
c("九条ねぎ", "聖護院かぶ"),
c("宇治茶"),
c("宇治茶", "丹波栗", "山科なす")
)
# 品目を1行ずつに展開
unnest_fun(sales, cols = "品目")
直売所 地域 品目
1 大原 左京区 九条ねぎ
2 大原 左京区 聖護院かぶ
3 宇治田原 綴喜郡 宇治茶
4 和束 相楽郡 宇治茶
5 和束 相楽郡 丹波栗
6 和束 相楽郡 山科なす
# 一部の直売所を品目未登録にしたデータ
sales2 <- sales
sales2$品目 <- list(
c("九条ねぎ", "聖護院かぶ"),
character(0),
c("丹波栗")
)
# 既定では未登録の直売所は結果から外れる
unnest_fun(sales2, cols = "品目")
直売所 地域 品目
1 大原 左京区 九条ねぎ
2 大原 左京区 聖護院かぶ
3 和束 相楽郡 丹波栗
# keep_empty = TRUE で未登録の直売所を欠損値として残す
unnest_fun(sales2, cols = "品目", keep_empty = TRUE)
直売所 地域 品目
1 大原 左京区 九条ねぎ
2 大原 左京区 聖護院かぶ
3 宇治田原 綴喜郡 <NA>
4 和束 相楽郡 丹波栗
LISI指標の計算:compute_lisiコマンド
各観測点の近傍に含まれるラベルの多様性を、LISI指標として計算します。埋め込み座標とメタデータを渡すと、指定した列ごとに近傍の混ざり具合を評価できます。
| オプション | 意味 | 初期値 |
|---|---|---|
| X | 行に観測点、列に埋め込み座標や特徴量を持つ行列状のオブジェクト | なし |
| meta_data | 観測点1つにつき1行のデータフレーム | なし |
| label_colnames | meta_dataのうち評価対象とする列名の文字ベクトル | なし |
| perplexity | 近傍の有効なサイズ | 30 |
| tol | 目標のperplexityを二分探索する際の許容誤差 | 1e-05 |
| max_iter | 二分探索の最大反復回数 | 50 |
| knn_algorithm | 厳密な近傍探索の方式。”auto”はデータ数に応じてclusteredかbrute_forceを選ぶ | c(“auto”, “brute_force”, “clustered”) |
| n_threads | C++ワーカーのスレッド数。NULLで自動選択 | NULL |
| max_dense_bytes | 密な入力とそのコピーに許容する推定バイト数の上限 | Inf |
# 擬似データ:観測点の2次元埋め込み座標
set.seed(2024)
coords <- rbind(
matrix(stats::rnorm(60, mean = 0), ncol = 2),
matrix(stats::rnorm(60, mean = 3), ncol = 2)
)
# 各観測点の調査班と主な栽培品目
meta <- data.frame(
調査班 = rep(c("北班", "南班"), each = 30),
品目 = sample(c("宇治茶", "九条ねぎ"), 60, replace = TRUE)
)
# 近傍に含まれるラベルの多様性をLISIで評価
lisi <- compute_lisi(coords, meta, c("調査班", "品目"), perplexity = 10)
# 先頭6行を表示
head(lisi)
調査班 品目
1 1.000489 1.466986
2 1.000088 1.414883
3 1.000422 1.442324
4 1.065254 1.660980
5 1.007146 1.917521
6 1.058346 1.513733調査班は座標がまとまって配置されているため近傍が同じ班で占められ、値が1に近くなります。品目は座標と無関係に割り振ったため近傍で混ざり、ラベル数の2に近い値になります。
インライン式の解析:parse_inline_expressionsコマンド
文字列の中に波かっこで書いた式を評価し、その結果を埋め込んだ文字列を返します。cliパッケージの記法(.valや.pkgなど)はそのまま残ります。
| オプション | 意味 | 初期値 |
|---|---|---|
| text | 評価する式を含む文字列 | なし |
| env | 式を評価する環境 | parent.frame() |
# レポート文に作付面積の値を差し込む
menseki <- 3.2
parse_inline_expressions("作付面積は {menseki} ha")
[1] "作付面積は 3.2 ha"
# 式を書くとその計算結果が入る
shuryo <- 180
parse_inline_expressions("10aあたり {shuryo / menseki} kg")
[1] "10aあたり 56.25 kg"
# cliの記法は展開されずに残る
chiku <- "和束"
parse_inline_expressions("{.val {chiku}}")
[1] "{.val 和束}"
近傍の探索:run_biocneighbors_knnコマンド
参照行列の各行について、最近傍k個の行番号(idx)と距離(dist)を求めます。BiocNeighborsパッケージを利用するため、未導入の場合は BiocManager::install(“BiocNeighbors”) で先に導入してください。
| オプション | 意味 | 初期値 |
|---|---|---|
| reference | 行に観測値を持つ数値の参照行列 | なし |
| query | 同じ列数を持つ数値のクエリ行列。省略可 | NULL |
| k | 求める近傍点の数 | なし |
| metric | 距離の種類。”euclidean”または”cosine” | c(“euclidean”, “cosine”) |
| exclude_self | queryがNULLのときに自分自身を近傍から除くかどうかの指定 | FALSE |
| n_threads | BiocNeighborsが使うスレッド数 | 1L |
# 圃場ごとの気温と土壌水分の測定値(1列目が気温、2列目が水分)
field <- matrix(
c(12.1, 12.4, 18.9, 19.2, 12.6, 19.5, 25.1, 24.7, 25.4, 18.7,
5.2, 5.0, 8.1, 7.9, 5.4, 8.3, 2.1, 2.4, 2.0, 7.7),
ncol = 2
)
# 各圃場に測定値が近い上位2圃場を探す
run_biocneighbors_knn(field, k = 2)
$idx
[,1] [,2]
[1,] 2 5
[2,] 1 5
[3,] 4 10
[4,] 3 6
[5,] 2 1
[6,] 4 3
[7,] 9 8
[8,] 7 9
[9,] 7 8
[10,] 3 4
$dist
[,1] [,2]
[1,] 0.3605551 0.5385165
[2,] 0.3605551 0.4472136
[3,] 0.3605551 0.4472136
[4,] 0.3605551 0.5000000
[5,] 0.4472136 0.5385165
[6,] 0.5000000 0.6324555
[7,] 0.3162278 0.5000000
[8,] 0.5000000 0.8062258
[9,] 0.3162278 0.8062258
[10,] 0.4472136 0.53851651行目の圃場に最も近いのは行番号2の圃場(距離0.36)、次が行番号5の圃場です。exclude_selfが既定のFALSEでも、queryを省略した場合は自分自身が近傍から除かれます。
密な行列の上位k個の選択:run_dense_topkコマンド
密な数値行列について、各列または各行の要素から上位k個を残し、その位置(行番号または列番号)と値を返します。
| オプション | 意味 | 初期値 |
|---|---|---|
| x | 数値行列または変換可能なオブジェクト | なし |
| k | 各列または行に残す要素の個数 | なし |
| by | 選択の方向。”col”は列ごと、”row”は行ごとに順位付けする | c(“col”, “row”) |
| decreasing | 値の大きい順に並べるかどうかの指定 | TRUE |
# 月別・観光地別の入込客数(単位:千人)
spot <- c("嵐山", "清水寺", "伏見稲荷", "天橋立")
month <- c("4月", "5月", "6月")
visitors <- matrix(
c(320, 210, 90,
280, 260, 110,
350, 300, 130,
150, 180, 60),
nrow = 4, byrow = TRUE,
dimnames = list(spot, month)
)
# 各月で入込客数の多い上位2地点(結果の各行が月に対応)
run_dense_topk(visitors, k = 2, by = "col")
$idx
[,1] [,2]
[1,] 3 1
[2,] 3 2
[3,] 3 2
$value
[,1] [,2]
[1,] 350 320
[2,] 300 260
[3,] 130 110
# 各地点で入込客数の多い上位2か月(結果の各行が地点に対応)
run_dense_topk(visitors, k = 2, by = "row")
$idx
[,1] [,2]
[1,] 1 2
[2,] 1 2
[3,] 1 2
[4,] 2 1
$value
[,1] [,2]
[1,] 320 210
[2,] 280 260
[3,] 350 300
[4,] 180 150by = “col” の結果は入力の列(月)ごと、by = “row” の結果は入力の行(地点)ごとの並びです。1つ目の結果の1行目は4月で、行番号3の伏見稲荷(350)と行番号1の嵐山(320)が上位2地点です。
書式付きメッセージの出力:log_messageコマンド
進捗や結果を、種別に応じた記号やインデント、タイムスタンプを付けて表示します。verbose = FALSE やオプション設定で出力の有無をまとめて切り替えられます。
| オプション | 意味 | 初期値 |
|---|---|---|
| … | 出力するメッセージ本体 | なし |
| expr | 標準出力やメッセージを捕捉し、log_messageの書式で再表示する式 | NULL |
| verbose | メッセージを表示するかどうかの指定 | NULL |
| message_type | メッセージの種別 | c(“info”, “success”, “warning”, “error”, “running”, “ask”) |
| cli_model | cliパッケージを使って表示するかどうかの指定 | TRUE |
| level | インデントの深さ。1は字下げなし | 1 |
| symbol | インデントに使う記号。指定するとlevelより優先される | ” “ |
| text_color | 文字色。Rの色名や16進表記を指定 | NULL |
| back_color | 背景色。指定方法はtext_colorと同じ | NULL |
| text_style | 文字装飾。”bold”や”italic”などを1つ以上指定 | NULL |
| multiline_indent | 複数行のとき各行に同じ書式を付けるかどうかの指定 | FALSE |
| timestamp | 先頭に現在時刻を表示するかどうかの指定 | TRUE |
| timestamp_format | タイムスタンプの書式文字列 | paste0(“[“, format(Sys.time(), “%Y-%m-%d %H:%M:%S”), “] “) |
| timestamp_style | タイムスタンプに本文と同じ装飾を適用するかどうかの指定 | FALSE |
| plain_text | 本文のみを出力し、記号や時刻などを省くかどうかの指定 | FALSE |
| .envir | 呼び出しを評価する環境 | parent.frame() |
| .frame | エラー報告に使うフレーム | .envir |
# 集計処理の開始を記録
log_message("宇治茶の出荷量集計を開始します", timestamp = FALSE)
ℹ 宇治茶の出荷量集計を開始します
# レベル2のインデントを付けて内訳を出力
log_message("対象は左京区・宇治市・和束町の3地区です", timestamp = FALSE, level = 2)
ℹ 対象は左京区・宇治市・和束町の3地区です
# 実行中の処理として出力
log_message("地区別に集計しています", message_type = "running", timestamp = FALSE)
◌ 地区別に集計しています
# 完了を成功メッセージとして出力
log_message("集計が完了しました", message_type = "success", timestamp = FALSE)
✔ 集計が完了しました
# verbose = FALSE を指定した行は出力されない
log_message("この行は表示されません", verbose = FALSE, timestamp = FALSE)
# 既定では先頭にタイムスタンプが付く
log_message("処理を終了します")
ℹ [2026-09-04 06:24:31] 処理を終了します最終行のタイムスタンプ(角かっこ内の日付と時刻)は実行した時点の値になるため、環境によって表示が変わります。
# exprに渡した式の標準出力を、log_messageの書式でまとめ直す
log_message(
expr = {
amount <- c(120, 95, 80)
cat("合計出荷量:", sum(amount), "kg\n")
},
message_type = "running",
timestamp = FALSE
)
◌ 合計出荷量: 295 kg
この記事が誰かの役に立ちますように。