【研究者からの質問】 > 電子カルテやアンケートから抽出したcsvデータをRに読み込ませたら、エラーが出て解析が止まってしまいました……。「120 mmHg」などの単位付きデータはどう処理すればいいですか?
【統計ERからの回答】 > 臨床データ解析に費やす総時間の7〜8割は、統計解析そのものではなく『データクレンジング(前処理)』と言われています。あせって
as.numeric()をかけるとデータが消えてしまう(NA化する)罠があるので、まずは正しい文字列操作のテクニックをマスターしましょう!
臨床研究において電子カルテやアンケートからデータを抽出した際、「さあ、さっそくt検定や多変量解析を行おう」とRを立ち上げたものの、エラーが発生して解析が停止した経験はないだろうか。
データを確認してみると、「120 mmHg」のように単位が混在していたり、全角と半角が不統一であったり、IDの数値の桁数が揃っていなかったりするケースが多発する。
本記事では、難解な「正規表現(Regular Expression)の文法理論」に深入りすることなく、臨床データで頻発する文字列トラブルをコピペで解決できるRコード(主に直感的な stringr パッケージ)を解説する。
データ前処理をサクッと10分で終わらせ、本題である研究の統計解析へ進もう。
なぜRで文字列操作が必要なのか?(データ型とNAの罠)
文字型(character)と数値型(numeric)の違い
Rで統計解析を行う際、最も基本的な原則は「数値データは文字やスペースを含まない純粋な数値型(numeric)でなければならない」という点である。
たとえば、血圧データに「120」と入力されていればRは数値として扱うが、カルテからエクスポートしたデータに「120 mmHg」や「 120」(先頭に空白が存在する)と書かれていると、Rは一律で文字型(character)として認識する。
文字型データのままでは、mean()(平均値)や t.test()(t検定)を実行しても、以下のようなエラーメッセージが出力され解析が中断する。
# エラー例
Error in mean.default(data$sbp) : argument is not numeric or logical
クレンジングせずに as.numeric() を実行する危険性
ここで「as.numeric() 関数で強制的に数値型へ変換すればよい」と焦って処理を行うのは非常に危険である。
⚠️ 注意!データ消滅のトラップ > 文字列(「mmHg」など)が混在したまま
as.numeric()を実行すると、Rは文字を数値に変換できず、データを自動的にNA(欠損値)へ置き換えてしまう。
sbp <- c("120 mmHg", "135", "142 mmHg")
as.numeric(sbp)
# [1] NA 135 NA
# 警告メッセージ: NAs introduced by coercion
「120 mmHg」や「142 mmHg」といった元々データが存在していた症例が、すべて NA(欠損値)に変換され消滅してしまうのだ。
貴重な臨床データが意図せず消失するトラブルを防ぐためにも、事前に文字列を綺麗にクレンジングするステップが不可欠である。
実務でそのまま使える!R文字列操作の鉄板5大テクニック
Rで文字列操作を行う際、基本関数(Base R)でも記述可能だが、本記事では tidyverse パッケージ群に含まれる stringr パッケージ の使用を推奨する。
stringr の関数はすべて str_ から始まるため視認性が高く、第一引数が「対象の文字列データ」で統一されているため、パイプ演算子(%>% や |>)との相性が抜群である。
あらかじめパッケージを読み込んでおく。
# パッケージの読み込み(tidyverseに含まれている)
library(tidyverse)
# または個別読み込み:library(stringr)
1. 文字列の結合(str_c / paste0):ID作成や施設コードの結合
施設コード「A」と患者通し番号「001」が別々の列に入っている場合、これらを連結して固有の「解析用症例ID(A-001)」を作成したい場面は多い。
使用する関数:str_c() (基本関数であれば paste0())
facility <- c("A", "A", "B")
patient_id <- c("001", "002", "001")
# 区切り文字(sep)を指定して結合
sub_id <- str_c(facility, patient_id, sep = "-")
print(sub_id)
# 出力結果:
# [1] "A-001" "A-002" "B-001"
📌 鉄板ルール > 区切り文字を挿入したい場合は
str_c(..., sep = "-")、区切りなしで直結したい場合はstr_c(...)またはpaste0(...)を使用する。
2. 文字列の切り出し(str_sub / substr):日付やコードの特定部分を抽出
「2026-07-24」という文字列から「2026(年)」や「07(月)」のみを抽出し、年度ごとの解析を行いたい場合に使用する。
使用する関数:str_sub(x, start, end)
dates <- c("2026-07-24", "2026-08-01", "2026-12-15")
# 1文字目から4文字目(年)を取り出す
years <- str_sub(dates, start = 1, end = 4)
print(years)
# [1] "2026" "2026" "2026"
# 6文字目から7文字目(月)を取り出す
months <- str_sub(dates, start = 6, end = 7)
print(months)
# [1] "07" "08" "12"
3. パターンの検出とフラグ化(str_detect / grep):病名・症状の有無を抽出
カルテの既往歴自由記述欄から、「DM」や「糖尿病」というキーワードが含まれる患者を検索し、あり=1、なし=0の二値フラグ(ダミー変数)を作成する際の必須テクニックである。
使用する関数:str_detect(x, pattern)
history <- c("Hypertension, DM", "Dyslipidemia", "DM, CKD", "Healthy")
# "DM" という文字列が含まれているかチェック(TRUE/FALSEが返る)
has_dm <- str_detect(history, "DM")
print(has_dm)
# [1] TRUE FALSE TRUE FALSE
# ifelseと組み合わせて 1/0 の数値フラグに変換する
dm_flag <- ifelse(str_detect(history, "DM"), 1, 0)
print(dm_flag)
# [1] 1 0 1 0
💡 査読・実務のワンポイント:部分一致のトラップ > 単に
"DM"で検索すると、"ADM"(例:Acute Disseminated Encephalomyelitis)など別の単語が含まれる場合に誤ってヒットする危険がある。完全一致や単語境界を厳密にしたい場合はregex("\\bDM\\b")などの単語境界指定や、ignore_case = TRUEオプションを活用しよう。
4. 不要な文字・単位の置換と除去(str_replace_all / gsub):数値化への下準備
データに入り込んだ「mmHg」「mg/dL」「<」などの単位や比較記号を消去し、数値型へ安全に変換する。
使用する関数:str_replace_all(x, pattern, replacement)
raw_sbp <- c("120 mmHg", "135mmHg", "<100 mmHg")
# " mmHg" や "mmHg" や "<" を消去(空文字 "" に置換)
clean_sbp <- raw_sbp %>%
str_replace_all("mmHg", "") %>%
str_replace_all("<", "") %>%
str_trim() %>% # 余計な空白を消去
as.numeric() # 最後に数値化
print(clean_sbp)
# [1] 120 135 100
⚠️ 査読者の視点:不等号データ(<100)の取り扱い注意 > 検出限界以下(Limit of Detection: LOD)を示す
<100などのデータから<を削除して単に100として扱う処理は、査読(Peer Review)で指摘されやすいポイントである。「一律100とするのか」「半分の50とするのか」「感度分析(Sensitivity Analysis)を行うのか」は、事前にプロトコルで定義しておくことが望ましい。
5. IDの桁揃え・ゼロ埋め(str_pad / sprintf):ソート順の崩れを防ぐ
症例IDが 1, 2, ..., 10 のように入力されていると、文字列としてソートした際に 1, 10, 2, 3... と順番が崩れてしまう。これを 001, 002, ..., 010 のように「ゼロ埋め(パディング)」して桁数を統一する。
使用する関数:str_pad(x, width, side, pad)
raw_ids <- c("1", "2", "10", "100")
# 桁数を「3桁」に揃え、足りない左側(left)を "0" で埋める
padded_ids <- str_pad(raw_ids, width = 3, side = "left", pad = "0")
print(padded_ids)
# 出力結果:
# [1] "001" "002" "010" "100"
臨床研究データあるある!トラップと即効対処法
トラップ①:目に見えない前後の「余計な空白・改行」
電子カルテからコピーしたデータには、文字の後ろに目に見えないスペース "DM " が付与されているケースが頻繁にある。
一見「DM」に見えても、R内部では "DM" と "DM " は別物と判定されるため、x == "DM" などの条件抽出で漏れが発生する。
dirty_text <- c(" DM", "DM ", " DM ")
clean_text <- str_trim(dirty_text)
print(clean_text)
# [1] "DM" "DM" "DM"
トラップ②:日本の電子カルテ特有の「全角英数・全角スペース」混入
「120」(全角数字)や「 」(全角スペース)が混入している場合、通常の置換関数のみでは十分に処理しきれない。
# stringiパッケージの関数を利用
library(stringi)
japanese_raw <- c("120 m m H g", "135")
# 全角をすべて半角に統一(Unicode正規化)
normalized <- stri_trans_nfkc(japanese_raw)
print(normalized)
# [1] "120 mm H g" "135"
【まとめ】コピペで使えるデータクレンジング一括コード
最後に、今回紹介したテクニックを組み合わせた「データフレーム前処理の決定版パイプラインコード」を提示する。
library(tidyverse)
library(stringi)
# サンプル臨床データの作成
df_raw <- tibble(
patient_id = c("1", "2", "3"),
facility = c("A", "A", "B"),
sbp_raw = c("120 mmHg", " 135 mmHg ", "<90 mmHg"),
history = c("HT, DM", "Dyslipidemia", "DM(Type2)")
)
# --- 一括クレンジング処理 ---
df_clean <- df_raw %>%
mutate(
# 1. 統一IDの作成(A-001形式)
id_padded = str_pad(patient_id, width = 3, side = "left", pad = "0"),
study_id = str_c(facility, id_padded, sep = "-"),
# 2. 全角の半角化 & 単位・記号の削除 & 数値化
sbp_clean = sbp_raw %>%
stri_trans_nfkc() %>% # 全角を半角化
str_replace_all("mmHg", "") %>% # 単位除去
str_replace_all("<", "") %>% # 記号除去
str_trim() %>% # 空白除去
as.numeric(), # 数値型変換
# 3. 既往歴テキストから「DM」の有無を判定(1/0)
dm_flag = ifelse(str_detect(history, "DM"), 1, 0)
)
# 結果の確認
print(df_clean)
📝 論文の Methods(解析方法)欄での英語記載テンプレート > 論文投稿時、使用したパッケージを記載する際の英文例:
“Data processing and string manipulations were performed using R version 4.3.0 and the stringr package (version 1.5.0).”
データクレンジングの手法を一度習得してしまえば、今後どのようなデータに出会っても焦る必要はなくなる。
煩雑な文字列処理はRコードで自動化し、研究の本質である統計解析や考察の執筆に貴重な時間を投資していこう。





コメント