10 文字型・文字列型データ

本章では、stringr パッケージを使用して、文字(character)と文字列(string)を評価し、処理する方法を紹介します。

  1. 結合、並べ替え、分割 - str_c(), str_glue(), str_order(), str_split()

  2. 整理と標準化

  3. 位置による評価と抽出 - str_length(), str_sub(), word()

  4. パターン認識

  5. 正規表現(“regex”)

使用例を簡単に表示するため、ほとんどの例で短い文字ベクトルを用いていますが、本章の内容はデータフレーム内の列にも簡単に適用できます。

本章は、こちらの stringr のウェブサイト を参考に作成されました。

10.1 準備

パッケージの読み込み

stringr およびその他の tidyverse パッケージをインストールまたは読み込みます。

# パッケージのインストール・読み込み
pacman::p_load(
  stringr,    # 文字列を扱うための多くの関数
  tidyverse,  # データ操作のための追加の関数
  tools)      # タイトルケースに変更するための関数

データの読み込み

エボラ出血熱の流行をシミュレートしたデータセットをインポートします。お手元の環境でこの章の内容を実行したい方は、こちら をクリックして「前処理された」ラインリスト(linelist)をダウンロードしてください(.rds 形式で取得できます)。 データは rio パッケージの import() を利用してインポートしましょう(import() は、.xlsx、.csv、.rdsなど、様々な形式のファイルを扱うことができます)。インポートの詳細については、データのインポート・エクスポート の章を参照してください。

# 症例ラインリストの読み込み 
linelist <- import("linelist_cleaned.rds")

インポートしたラインリストの最初の 50 行を以下に表示します。

10.2 結合、分割、配列

このセクションでは以下の項目について説明します。

文字列の結合

複数の文字列を 1 つの文字列に結合する場合は、stringrstr_c を使うことをおすすめします。結合するそれぞれの文字列を、コンマで区切って指定します。

str_c("String1", "String2", "String3")
## [1] "String1String2String3"

引数 sep = は、与えられた各引数の間に指定した文字値を挿入します(例:コンマ、スペース、改行 "\n")。

str_c("String1", "String2", "String3", sep = ", ")
## [1] "String1, String2, String3"

引数 collapse =str_c() に複数の値をベクトルで入力する場合に使用します。これは、コード実行後に出力されるベクトルの要素を区切るために使用され、出力されるベクトルが 1 つの長い文字列のみを要素として持つようにします。

以下の例では、2 つのベクトルを 1 つにまとめています(姓と名)。また、似たような例として、管轄区域とその症例数があります。姓名を使用する例では、

  • 姓と名の間に sep = で指定した値が表示されます
  • 各姓名の間に collapse = で指定した値が表示されます
first_names <- c("abdul", "fahruk", "janice") 
last_names  <- c("hussein", "akinleye", "okeke")

# sep には入力された文字列(ここでは姓と名)を繋ぐ際間に挿入される文字(列)を指定
# collapse には繋がれた文字列同士(ここでは各姓名)の間に挿入される文字(列)を指定
str_c(first_names, last_names, sep = " ", collapse = ";  ")
## [1] "abdul hussein;  fahruk akinleye;  janice okeke"

注釈:出力結果をどのように表示したいかによって、正しく表示されるようにstr_c() を使用したコード全体を cat() で囲む必要があります。例えば、以下のように、結合された文字列を改行して表示したい場合などです。

# 改行を正しく表示するためにフレーズ全体をcat()で囲む
cat(str_c(first_names, last_names, sep = " ", collapse = ";\n"))
## abdul hussein;
## fahruk akinleye;
## janice okeke

動的文字列

str_glue() を使うと、文字列に動的な R コードを加えることができます。以下のように、動的なプロットキャプション(見出しや説明文など)を作成する際に非常に便利な関数です。

  • すべての内容を二重引用符で囲んでください。str_glue("")
  • 動的なコードやあらかじめ定義された値への参照は、二重引用符の中の波括弧 {} に入力します。1つの str_glue() コマンド中に複数の波括弧を入れることができます。
  • 文字として引用符 ’’ を表示したい場合は、二重引用符の中に一重引用符を使用します(例:日付の書式を指定する場合など。以下の例を参照ください)。
  • ヒント:\n で改行することができます。
  • ヒント:format() で日付の表示を調整し、Sys.Date() で現在の日付を表示することができます。

以下に、動的なプロットキャプションの簡単な例を示します。

str_glue("Data include {nrow(linelist)} cases and are current to {format(Sys.Date(), '%d %b %Y')}.")
## Data include 5888 cases and are current to 28 Dec 2023.

もう一つの方法は、以下のように波括弧内に仮置きの変数を挿入し、str_glue() の最後にそれらを定義する方法です。これにより、表示したいプロットキャプションが長い場合でも、コードが読みやすくなります。

str_glue("Linelist as of {current_date}.\nLast case hospitalized on {last_hospital}.\n{n_missing_onset} cases are missing date of onset and not shown",
         current_date = format(Sys.Date(), '%d %b %Y'),
         last_hospital = format(as.Date(max(linelist$date_hospitalisation, na.rm=T)), '%d %b %Y'),
         n_missing_onset = nrow(linelist %>% filter(is.na(date_onset)))
         )
## Linelist as of 28 Dec 2023.
## Last case hospitalized on 30 Apr 2015.
## 256 cases are missing date of onset and not shown

データフレームからの抜き出し

データフレームからデータを取り出して、文字列としてまとめておくと便利な場合があります。以下にデータフレームの例を示します。これを使って、管轄区域、新規および総症例数についての要約文を作成します。

# 症例データフレームの作成
case_table <- data.frame(
  zone        = c("Zone 1", "Zone 2", "Zone 3", "Zone 4", "Zone 5"),
  new_cases   = c(3, 0, 7, 0, 15),
  total_cases = c(40, 4, 25, 10, 103)
  )

str_glue_data() を使用して、データフレームの各行から取り出した情報を文字列にまとめることができます。

case_table %>% 
  str_glue_data("{zone}: {new_cases} ({total_cases} total cases)")
## Zone 1: 3 (40 total cases)
## Zone 2: 0 (4 total cases)
## Zone 3: 7 (25 total cases)
## Zone 4: 0 (10 total cases)
## Zone 5: 15 (103 total cases)

異なる行の文字列を結合する

複数の行の値を結合して 1 つの行にまとめる場合など、データフレームの値を列に沿って結合したい場合は、重複データの排除 の章内の ロールアップした値 のセクションを参照してください。

データフレームを 1 行の文字列にまとめる

str_c() でデータフレーム名と列名を指定し、sep =collapse = の引数を与えることで、データフレーム内の情報を 1 行に表示させることができます。

str_c(case_table$zone, case_table$new_cases, sep = " = ", collapse = ";  ")
## [1] "Zone 1 = 3;  Zone 2 = 0;  Zone 3 = 7;  Zone 4 = 0;  Zone 5 = 15"

さらにもう一度 str_c() で先に書いた str_c() を包むことで、文の先頭に文字列 “New Cases:” を追加することができます(“New Cases:” が元の str_c() 内にあった場合、複数回表示されます)。

str_c("New Cases: ", str_c(case_table$zone, case_table$new_cases, sep = " = ", collapse = ";  "))
## [1] "New Cases: Zone 1 = 3;  Zone 2 = 0;  Zone 3 = 7;  Zone 4 = 0;  Zone 5 = 15"

列の結合

データフレーム中の複数の列を文字列にまとめるには、tidyr パッケージの unite() を用います。これは separate() の逆の操作にあたります。

まず新しく統合される列の名前を指定します。次に結合したい列の名前を指定します。

  • デフォルトではアンダースコア _ によって各列の値が結合されますが、これは sep = によって変更できます。
  • remove = は入力に用いた列をデータフレームから削除します(デフォルトでは TRUE)。
  • na.rm = は結合時に欠損値を除外します(デフォルトでは FALSE)。

以下に、例として小さいなデータフレームを作成して説明します。

df <- data.frame(
  case_ID = c(1:6),
  symptoms  = c("jaundice, fever, chills",     # 患者 1
                "chills, aches, pains",        # 患者 2 
                "fever",                       # 患者 3
                "vomiting, diarrhoea",         # 患者 4
                "bleeding from gums, fever",   # 患者 5
                "rapid pulse, headache"),      # 患者 6
  outcome = c("Recover", "Death", "Death", "Recover", "Recover", "Recover"))
df_split <- separate(df, symptoms, into = c("sym_1", "sym_2", "sym_3"), extra = "merge")
## Warning: Expected 3 pieces. Missing pieces filled with `NA` in 2 rows [3, 4].

以下に、作成したデータフレームを表示します。

以下のコマンドで、症状が入力された 3 つの列を統一します。

df_split %>% 
  unite(
    col = "all_symptoms",         # 新しく作られる列の名前
    c("sym_1", "sym_2", "sym_3"), # 結合する列
    sep = ", ",                   # 区切りに使われる結合子
    remove = TRUE,                # TRUEの場合、結合元の列をデータフレームから削除する
    na.rm = TRUE                  # TRUEの場合、欠損値を無視して結合する
  )
##   case_ID                all_symptoms outcome
## 1       1     jaundice, fever, chills Recover
## 2       2        chills, aches, pains   Death
## 3       3                       fever   Death
## 4       4         vomiting, diarrhoea Recover
## 5       5 bleeding, from, gums, fever Recover
## 6       6      rapid, pulse, headache Recover

分割

パターンに基づいて文字列を分割するには、str_split() を使います。文字(列)を評価し、新たに分割された値からなる文字ベクトルの list を返します。

以下の簡単な例では、1 つの文字列を 3 つに分割しています。デフォルトでは、最初に与えられた文字列ごとに 1 つの要素(文字ベクトル)を持つ list を返します。simplify = TRUE の場合は、文字行列を返します。

この例では、1 つの文字列が与えられ、コマンドを実行すると、1 つの要素を持つリスト (つまり 3 つの値を持つ文字ベクトル)が出力されます。

str_split(string = "jaundice, fever, chills",
          pattern = ",")
## [[1]]
## [1] "jaundice" " fever"   " chills"

出力が保存されていれば、角括弧 [ ] を使って任意の値(n 番目の値)にアクセスし、抽出できます。特定の値を抽出するには、次のような構文を使用できます。the_returned_object[[1]][2]

この例では、最初に評価された文字列から 2 番目の値(“fever”)を抽出します。要素の抽出の詳細については、R の基礎 の章をご覧ください。

pt1_symptoms <- str_split("jaundice, fever, chills", ",")

pt1_symptoms[[1]][2]  # リスト内の1つ目(かつここでは唯一)の要素内にある2つ目の値を返す
## [1] " fever"

str_split() で複数の文字列を指定した場合、出力されるリストには複数の要素が含まれます。

symptoms <- c("jaundice, fever, chills",     # 患者 1
              "chills, aches, pains",        # 患者 2 
              "fever",                       # 患者 3
              "vomiting, diarrhoea",         # 患者 4
              "bleeding from gums, fever",   # 患者 5
              "rapid pulse, headache")       # 患者 6

str_split(symptoms, ",")                     # 各患者の症状を分割する
## [[1]]
## [1] "jaundice" " fever"   " chills" 
## 
## [[2]]
## [1] "chills" " aches" " pains"
## 
## [[3]]
## [1] "fever"
## 
## [[4]]
## [1] "vomiting"   " diarrhoea"
## 
## [[5]]
## [1] "bleeding from gums" " fever"            
## 
## [[6]]
## [1] "rapid pulse" " headache"

出力結果を後にデータフレームの列にしたい場合、次のように simplify = TRUE を設定し、文字行列を出力すると便利です。

str_split(symptoms, ",", simplify = TRUE)
##      [,1]                 [,2]         [,3]     
## [1,] "jaundice"           " fever"     " chills"
## [2,] "chills"             " aches"     " pains" 
## [3,] "fever"              ""           ""       
## [4,] "vomiting"           " diarrhoea" ""       
## [5,] "bleeding from gums" " fever"     ""       
## [6,] "rapid pulse"        " headache"  ""

また n = を使って、分割する数を調整することもできます。例えば、以下の例は分割数を 2 に制限するもので、それ以上のコンマは 2 番目の値の中に残ります。

str_split(symptoms, ",", simplify = TRUE, n = 2)
##      [,1]                 [,2]            
## [1,] "jaundice"           " fever, chills"
## [2,] "chills"             " aches, pains" 
## [3,] "fever"              ""              
## [4,] "vomiting"           " diarrhoea"    
## [5,] "bleeding from gums" " fever"        
## [6,] "rapid pulse"        " headache"

注釈:str_split_fixed() を使用しても同じ結果が出力されますが、その場合は simplify 引数を与えず、代わりに列数(n)を指定しなければなりません。

str_split_fixed(symptoms, ",", n = 2)

列の分割

データフレームの列を分割する場合は、dplyr パッケージの separate() が最適です。この関数は、文字列を要素に持つ 1 つの列を複数の列に分割することができます。

例えば、先述の列の結合のセクションで作成した簡単なデータフレーム df を見てみましょう。このデータフレームには case_ID の列、多くの症状をまとめた文字列からなる列、そして予後を示す列が含まれています。ここでは、複数の症状が含まれている symptoms 列を症状ごとの列に分割し、各列が単一の症状を表すようにすることを目的とします。

データを separate() で処理する場合、まず分離する列を指定します。次に、以下のように新しい列名を含むベクトル c()into = 引数に指定します。

  • sep = 分割する位置を、文字または数字で指定します。

  • remove = デフォルトではFALSE。入力された列を削除します。

  • convert = デフォルトではFALSE。文字列 “NA”を NA に変換します。

  • extra = 分離によって作成された値の数が、指定された新規列の名前よりも多い場合の動作を制御します。

    • extra = "warn" は、警告を表示し、過剰な値は削除されます(デフォルト)。
    • extra = "drop" は、警告を表示せずに余分な値を削除します。
    • extra = "merge" は、into = で指定された新しい列の数だけ分割します。- この設定では全ての入力データが保存されます。

extra = "merge" を使用した例を以下に示します(ここではデータは失われません)。2 つの新しい列が定義され、3 つ目の症状は 2 つ目の列に入ります。

# 3つ目の症状は2つ目の新しい列に組み込まれる
df %>% 
  separate(symptoms, into = c("sym_1", "sym_2"), sep=",", extra = "merge")
## Warning: Expected 2 pieces. Missing pieces filled with `NA` in 1 rows [3].
##   case_ID              sym_1          sym_2 outcome
## 1       1           jaundice  fever, chills Recover
## 2       2             chills   aches, pains   Death
## 3       3              fever           <NA>   Death
## 4       4           vomiting      diarrhoea Recover
## 5       5 bleeding from gums          fever Recover
## 6       6        rapid pulse       headache Recover

デフォルトの extra = "drop" に設定した場合、以下のように警告が表示されます。

# 3番目の症状は失われる
df %>% 
  separate(symptoms, into = c("sym_1", "sym_2"), sep=",")
## Warning: Expected 2 pieces. Additional pieces discarded in 2 rows [1, 2].
## Warning: Expected 2 pieces. Missing pieces filled with `NA` in 1 rows [3].
##   case_ID              sym_1      sym_2 outcome
## 1       1           jaundice      fever Recover
## 2       2             chills      aches   Death
## 3       3              fever       <NA>   Death
## 4       4           vomiting  diarrhoea Recover
## 5       5 bleeding from gums      fever Recover
## 6       6        rapid pulse   headache Recover

注意:into = に入力された値の数(新規列の名前)が不足していると、データが切り捨てられる可能性があります。

アルファベット順に並べる

複数の文字列をアルファベット順に並べることができます。str_order() を使用すると、順序が返され、str_sort() はその順序に並べ替えた文字列を返します。

# 文字列の定義
health_zones <- c("Alba", "Takota", "Delta")

# アルファベット順を返す
str_order(health_zones)
## [1] 1 3 2
# アルファベット順に並び替える
str_sort(health_zones)
## [1] "Alba"   "Delta"  "Takota"

英語以外の他のアルファベットを使用したい場合は、引数 locale = を追加します。R コンソールで stringi::stri_locale_list() と入力すると、指定可能なアルファベットの一覧を表示することができます。

R の基本関数

R の base パッケージに含まれている関数である paste()paste0() は、与えられたベクトルのすべての要素を文字に変換した後、結合して 1 つの文字列を返すためによく使われます。これらの関数は str_c() と似ていますが、使用される構文はより複雑で、括弧の中では各部分がコンマで区切られています。括弧内の各部分は、引用符で囲まれた文字テキストまたはすでに定義されたコードオブジェクト(引用符なし)です。例えば、以下のようになります。

n_beds <- 10
n_masks <- 20

paste0("Regional hospital needs ", n_beds, " beds and ", n_masks, " masks.")
## [1] "Regional hospital needs 10 beds and 20 masks."

sep =collapse = の引数を指定できます。paste() は、paste0()sep = " "(半角スペース)を使用した場合と同じです。

10.3 整理と標準化

大文字小文字の変更

管轄区域の名称など、大文字小文字を変更しなければならない場合がよくあります。その場合は、以下のように stringr に含まれる str_to_upper()str_to_lower()str_to_title() を使用すると便利です。

str_to_upper("California")
## [1] "CALIFORNIA"
str_to_lower("California")
## [1] "california"

base R の toupper()tolower() を使用しても同じ処理をすることができます。

語頭を大文字にする

各単語の語頭を大文字にしたい場合は、str_to_title() を使います。

str_to_title("go to the US state of california ")
## [1] "Go To The Us State Of California "

tools パッケージの toTitleCase() を使うことで、より厳密に(“to”、“the”、“of” のような単語は大文字化されずに)語頭を大文字にすることができます。

tools::toTitleCase("This is the US state of california")
## [1] "This is the US State of California"

また、str_to_sentence() を使うと、文字列の最初の文字の語頭のみを大文字にすることができます。

str_to_sentence("the patient must be transported")
## [1] "The patient must be transported"

文字列を伸長する

str_pad() を用いると、文字列に特定の文字を足すことで、指定した長さまで文字列を伸ばすことができますデフォルトではスペース(空白)が追加されますが、pad = によってピリオドなど他の文字を指定することも可能です。

# 異なる長さのICDコード
ICD_codes <- c("R10.13",
               "R10.819",
               "R17")

# 最低7文字になるよう右側に空白を足したICDコード
str_pad(ICD_codes, 7, "right")
## [1] "R10.13 " "R10.819" "R17    "
# 空白の代わりにピリオドで文字列を伸長
str_pad(ICD_codes, 7, "right", pad = ".")
## [1] "R10.13." "R10.819" "R17...."

例えば、pad = "0" により先頭に 0 を足すことで、時間や分のように数字で構成される文字列の長さが少なくとも 2 になるように処理することができます。

# 先頭に0を足して2桁にする(例:時間・分の表示)
str_pad("4", 2, pad = "0") 
## [1] "04"
# "hours"という名前の数字列を作る場合
# hours <- str_pad(hours, 2, pad = "0")

文字列を短縮する

str_trunc() で文字列の長さの最大値を設定できます。与えられた文字列の長さが指定した最大値を超える場合、その文字列は短縮され、省略記号(…)が挿入されます。このとき、省略記号は文字数にカウントされることに注意してください。使用される省略記号は ellipsis = で指定することができます。また side = によって、省略記号がどこに挿入されるか(“left”、 “right”、 “center”)を指定することができます。

original <- "Symptom onset on 4/3/2020 with vomiting"
str_trunc(original, 10, "center")
## [1] "Symp...ing"

長さの標準化

文字列の長さの最大値を str_trunc() で指定し、さらに str_pad() を用いて短い文字列をその長さまで伸長することができます。以下の例では、最大値を 6 に設定し(1 つの文字列が短縮されます)、次に設定された最大値 6 に満たない文字列がその長さまで伸長されます。

# 異なる長さのICDコード
ICD_codes   <- c("R10.13",
                 "R10.819",
                 "R17")

# 最大の長さが6になるよう文字列を短縮
ICD_codes_2 <- str_trunc(ICD_codes, 6)
ICD_codes_2
## [1] "R10.13" "R10..." "R17"
# 最小の長さが6になるよう文字列を伸長
ICD_codes_3 <- str_pad(ICD_codes_2, 6, "right")
ICD_codes_3
## [1] "R10.13" "R10..." "R17   "

先頭・末尾の空白を削除する

str_trim() を用いて文字列の端にある空白、改行(\n)、タブ (\t)を削除できます。"right""left"、または "both" を指定することにより、どちらの端から削除するかを選択することができます(例:str_trim(x, "right"))。

# 右端に余分な空白を持つID
IDs <- c("provA_1852  ", # 2つの余分な空白あり
         "provA_2345",   # 余分な空白なし
         "provA_9460 ")  # 1つの余分な空白あり

# 右端から空白を削除
str_trim(IDs)
## [1] "provA_1852" "provA_2345" "provA_9460"

繰り返される空白の削除

str_squish() を用いて文字列の内部に連続して現れる空白を削除できます。例えば、2 つ続きになっている空白を 1 つの空白に変更できます。str_trim() と同様、文字列の端にある空白、改行、タブを削除することもできます。

# 元の文字列は内部に余分な空白を含む
str_squish("  Pt requires   IV saline\n")
## [1] "Pt requires IV saline"

R コンソールに ?str_trim?str_pad と入力すると、詳細を確認できます。

指定の文字数で文章を改行する

str_wrap() を用いることで、長い文章を指定した文字数で改行し整理することができます。任意の文字数を指定すれば、アルゴリズムにより以下のように文章中に改行(\n)が挿入されます。

pt_course <- "Symptom onset 1/4/2020 vomiting chills fever. Pt saw traditional healer in home village on 2/4/2020. On 5/4/2020 pt symptoms worsened and was admitted to Lumta clinic. Sample was taken and pt was transported to regional hospital on 6/4/2020. Pt died at regional hospital on 7/4/2020."

str_wrap(pt_course, 40)
## [1] "Symptom onset 1/4/2020 vomiting chills\nfever. Pt saw traditional healer in\nhome village on 2/4/2020. On 5/4/2020\npt symptoms worsened and was admitted\nto Lumta clinic. Sample was taken and pt\nwas transported to regional hospital on\n6/4/2020. Pt died at regional hospital\non 7/4/2020."

basecat() に上記のコマンドを入力することで、改行された文章を表示することができます。

cat(str_wrap(pt_course, 40))
## Symptom onset 1/4/2020 vomiting chills
## fever. Pt saw traditional healer in
## home village on 2/4/2020. On 5/4/2020
## pt symptoms worsened and was admitted
## to Lumta clinic. Sample was taken and pt
## was transported to regional hospital on
## 6/4/2020. Pt died at regional hospital
## on 7/4/2020.

10.4 位置による操作

位置を指定して文字を抽出する

str_sub() は文字列の一部を返します。この関数は次の 3 つの引数を取ります。

  1. 文字ベクトル
  2. 開始位置
  3. 終了位置

位置を定義する際にいくつかの注意点があります:

  • 位置番号が正のとき、位置は文字列の左端からカウントされます。
  • 位置番号が負のとき、位置は文字列の右端からカウントされます。
  • 開始・終了位置は選択範囲に含まれます。
  • 文字列の長さを超えて選択された範囲は無視されます。

以下に文字列 “pneumonia” を用いた例を示します:

# 左から3文字目を開始および終了位置に設定
str_sub("pneumonia", 3, 3)
## [1] "e"
# 位置0は存在しません
str_sub("pneumonia", 0, 0)
## [1] ""
# 左から6文字目を開始位置、右から1文字目を終了位置に設定
str_sub("pneumonia", 6, -1)
## [1] "onia"
# 右から5文字目を開始位置、右から2文字目を終了位置に設定
str_sub("pneumonia", -5, -2)
## [1] "moni"
# 左から4文字目を開始位置、文字列の長さを超えた位置を終了位置に設定
str_sub("pneumonia", 4, 15)
## [1] "umonia"

位置を指定して単語を抽出する

n 番目の単語を抽出したい場合は、stringr パッケージの word() を用います。文字列、開始位置、終了位置を引数に取ります。

初期設定では空白で区切られた部分が単語と認識されます。区切りは sep = で変更でき、例えば sep = "_" と指定すると下線で区切られた部分が単語と認識されます。

# 評価したい文字列
chief_complaints <- c("I just got out of the hospital 2 days ago, but still can barely breathe.",
                      "My stomach hurts",
                      "Severe ear pain")

# 1から3番目の単語を抽出
word(chief_complaints, start = 1, end = 3, sep = " ")
## [1] "I just got"       "My stomach hurts" "Severe ear pain"

位置を指定して文字を入れ替える

str_sub() と代入演算子(<-)を組み合わせて文字列の一部を変更できます。

word <- "pneumonia"

# 3番目と4番目の文字をXに変更する
str_sub(word, 3, 4) <- "XX"

# 結果を表示
word
## [1] "pnXXmonia"

データフレームの列など、複数の文字列に対して用いる場合の例を示します。以下の例では、コマンド実行後に “HIV” の文字列が長くなることに注意してください。

words <- c("pneumonia", "tubercolosis", "HIV")

# 3番目と4番目の文字をXに変更する
str_sub(words, 3, 4) <- "XX"

words
## [1] "pnXXmonia"    "tuXXrcolosis" "HIXX"

長さを評価する

str_length("abc")
## [1] 3

base R の nchar() でも同様の操作が可能です。

10.5 パターン

stringr に含まれる関数の多くは、与えられた文字列から特定のパターンを見つけ、その位置を特定したり、抽出・入れ替えなどの操作を行うことができます。

パターンを見つける

以下の例のように str_detect() を使うことで、文字列中に特定のパターンが存在するか否かを判別することができます。初めに検索する文字列を与え(string =)、さらに探したいパターンを入力します(pattern =)。 初期設定では大文字小文字が区別されることに注意してください!

str_detect(string = "primary school teacher", pattern = "teach")
## [1] TRUE

引数 negate =TRUE に設定することで、パターンが存在「しない」かどうかを判定できます。

str_detect(string = "primary school teacher", pattern = "teach", negate = TRUE)
## [1] FALSE

大文字小文字を無視したい場合は、パターンを regex() で包み、regex() 中に ignore_case = TRUE(もしくは短く T)と指定します。

str_detect(string = "Teacher", pattern = regex("teach", ignore_case = T))
## [1] TRUE

str_detect() で検索する文字列が文字ベクトルやデータフレームの列である場合は、各要素ごとに TRUE または FALSE が返されます。

# 職業のベクトル・列
occupations <- c("field laborer",
                 "university professor",
                 "primary school teacher & tutor",
                 "tutor",
                 "nurse at regional hospital",
                 "lineworker at Amberdeen Fish Factory",
                 "physican",
                 "cardiologist",
                 "office worker",
                 "food service")

# 各文字列中に"teach"が含まれるかを判別する - TRUE/FALSE が出力される
str_detect(occupations, "teach")
##  [1] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

TRUE がいくつあるかを数えたい場合は、出力された値を sum() に入力します。

sum(str_detect(occupations, "teach"))
## [1] 1

複数のパターンを探したい場合は、以下のように各パターンを OR 記号(|)で区切り、pattern = で指定します。

sum(str_detect(string = occupations, pattern = "teach|professor|tutor"))
## [1] 3

より多くのパターンを同時に探したいときは、 str_c()sep = | を組み合わせた出力を文字オブジェクトとして先に定義することで、コードをより簡潔にすることができます。以下の例では、医療従事者の職業リストをパターンとして検索します。

# 検索するパターン
occupation_med_frontline <- str_c("medical", "medicine", "hcw", "healthcare", "home care", "home health",
                                "surgeon", "doctor", "doc", "physician", "surgery", "peds", "pediatrician",
                               "intensivist", "cardiologist", "coroner", "nurse", "nursing", "rn", "lpn",
                               "cna", "pa", "physician assistant", "mental health",
                               "emergency department technician", "resp therapist", "respiratory",
                                "phlebotomist", "pharmacy", "pharmacist", "hospital", "snf", "rehabilitation",
                               "rehab", "activity", "elderly", "subacute", "sub acute",
                                "clinic", "post acute", "therapist", "extended care",
                                "dental", "dential", "dentist", sep = "|")

occupation_med_frontline
## [1] "medical|medicine|hcw|healthcare|home care|home health|surgeon|doctor|doc|physician|surgery|peds|pediatrician|intensivist|cardiologist|coroner|nurse|nursing|rn|lpn|cna|pa|physician assistant|mental health|emergency department technician|resp therapist|respiratory|phlebotomist|pharmacy|pharmacist|hospital|snf|rehabilitation|rehab|activity|elderly|subacute|sub acute|clinic|post acute|therapist|extended care|dental|dential|dentist"

以下の例では、先に定義した職業リストの中に含まれる医療職(occupation_med_frontline)の数を返します。

sum(str_detect(string = occupations, pattern = occupation_med_frontline))
## [1] 2

文字列検索のための基本 R 関数

base R の grepl()str_detect() と同様、パターンの有無を判定しロジカルベクトルを返します。基本構文は grepl(pattern, strings_to_search, ignore.case = FALSE, ...) です。grepl() を使用する利点の 1 つとして、regex() を使わずに ignore.case = を指定するだけで大文字小文字を区別するかを変更することができます。

同様に、base R の sub()gsub()str_replace() に近い働きをします。 基本構文は gsub(pattern, replacement, strings_to_search, ignore.case = FALSE) です。sub() は一致したパターンのうち最初に現れるものだけを、gsub() は一致したパターンの全てを入れ替えます。

コンマをピリオドに入れ替える

以下に gsub() を用いてコンマをピリオドに入れ替える例を示します。これは米国や英国以外で収集されたデータを扱う際に役に立つかもしれません。

内部の gsub()lengths 内のピリオドを除いて ““(空白無し)に入れ替えます。ピリオド”.” をパターンとして指定する際は、その前に 2 つバックスラッシュを置くことで「エスケープ」する必要があります。これは正規表現において “.” が “全ての文字” を意味するためです。 ここで出力された結果(ピリオドは削除されカンマのみを含む)は外側の gsub() に渡され、コンマがピリオドに置き換わります。

lengths <- c("2.454,56", "1,2", "6.096,5")

as.numeric(gsub(pattern = ",",                # コンマを見つける
                replacement = ".",            # ピリオドに入れ替える
                x = gsub("\\.", "", lengths)  # ピリオド(エスケープして指定)を削除する
                )
           )                                  # 出力を数字ベクトルに変更

全て入れ替える

str_replace_all() は「検索と置換」ツールとして使用できます。検索する文字列、パターン、置き換える値をそれぞれ string =pattern =replacement = で指定します。 以下の例では、全ての “dead” を “deceased” に入れ替えます。大文字小文字が区別されることに注意してください。

outcome <- c("Karl: dead",
            "Samantha: dead",
            "Marco: not dead")

str_replace_all(string = outcome, pattern = "dead", replacement = "deceased")
## [1] "Karl: deceased"      "Samantha: deceased"  "Marco: not deceased"

注釈:

  • 指定したパターンを NA で置き換えるには str_replace_na() を用います。
  • str_replace() は与えられた各文字列中に現れる最初のパターンだけを入れ替えます。

パターンの有無による条件分岐

case_when() との組み合わせ

str_detect()dplyr パッケージの case_when() と組み合わせて用いられることが多いです。例として、occupations がラインリスト内の列であるとします。以下の mutate()case_when() による条件分岐に従って新しい列 is_educator を作ります。case_when() についての詳細は、データクリーニングと主要関数 の章を参照してください。

df <- df %>% 
  mutate(is_educator = case_when(
    # occupation内でパターン検索(大文字小文字は区別しない)
    str_detect(occupations,
               regex("teach|prof|tutor|university",
                     ignore_case = TRUE))              ~ "Educator",
    # その他全て
    TRUE                                               ~ "Not an educator"))

negate = F を用いて除外したいパターンを指定できることも覚えておきましょう。

df <- df %>% 
  # 新しい列is_educatorの値を条件分岐により決定する
  mutate(is_educator = case_when(
    
    # "Educator"の値を取るためには2つの基準を満たす必要がある:
    # 指定されたパターンを持ち、かつ除外されたパターンを持たない

    # 指定されたパターンを持つ
    str_detect(occupations,
               regex("teach|prof|tutor|university", ignore_case = T)) &
    
    # 指定されたパターンを持たない
    str_detect(occupations,
               regex("admin", ignore_case = T),
               negate = TRUE                        ~ "Educator"
    
    # 上記の条件を満たさない全ての行
    TRUE                                            ~ "Not an educator"))

パターンの位置を特定する

指定したパターンが与えられた文字列中に最初に現れる位置を特定するためには、str_locate() を用います。パターンの最初と最後の文字の位置を返します。

str_locate("I wish", "sh")
##      start end
## [1,]     5   6

その他の str 関数と同様、str_locate() にも “_all” バージョンが存在します(str_locate_all())。与えられた各文字列中に現れるパターンの全ての位置を list 形式で返します。

phrases <- c("I wish", "I hope", "he hopes", "He hopes")

str_locate(phrases, "h" )     # *最初に*現れるパターンの位置
##      start end
## [1,]     6   6
## [2,]     3   3
## [3,]     1   1
## [4,]     4   4
str_locate_all(phrases, "h" ) # *全ての*パターンの位置
## [[1]]
##      start end
## [1,]     6   6
## 
## [[2]]
##      start end
## [1,]     3   3
## 
## [[3]]
##      start end
## [1,]     1   1
## [2,]     4   4
## 
## [[4]]
##      start end
## [1,]     4   4

一致したパターンを抽出する

str_extract_all() はマッチしたパターンそのものを返します。これは “OR(|)” を用いて複数のパターンを検索したときに特に有用です。例えば、職業の文字列リスト(前のセクションを参照)内で “teach”、“prof”、“tutor” のいずれかのパターンに合致する単語を探したいとします。

str_extract_all() 与えられた文字列内の一致するパターン全ての list を返します。以下の例では、出力されたリストのうち 3 つ目の要素において 2 つのパターンが一致していることに注目してください。

str_extract_all(occupations, "teach|prof|tutor")
## [[1]]
## character(0)
## 
## [[2]]
## [1] "prof"
## 
## [[3]]
## [1] "teach" "tutor"
## 
## [[4]]
## [1] "tutor"
## 
## [[5]]
## character(0)
## 
## [[6]]
## character(0)
## 
## [[7]]
## character(0)
## 
## [[8]]
## character(0)
## 
## [[9]]
## character(0)
## 
## [[10]]
## character(0)

一方、str_extract()最初に一致したパターンのみを返します。従って、出力結果は与えられた各文字列に対して 1 つのパターンが文字ベクトルで返されます。 一致するパターンがない場合は NA が返されます。この NAstr_extractna.exclude() で包むことで消去することもできます。以下の例では、3 つ目の文字列の 2 つ目の一致パターンが表示されないことに注意してください。

str_extract(occupations, "teach|prof|tutor")
##  [1] NA      "prof"  "teach" "tutor" NA      NA      NA      NA      NA     
## [10] NA

サブセットと数え上げ

ここでは str_subset()str_count() を扱います。

str_subset() は一致したパターンだけでなく、そのパターンを含む文字列全体を返します:

str_subset(occupations, "teach|prof|tutor")
## [1] "university professor"           "primary school teacher & tutor"
## [3] "tutor"

str_count() は検索した文字列中に指定したパターンが現れる合計回数をベクトルで返します。

str_count(occupations, regex("teach|prof|tutor", ignore_case = TRUE))
##  [1] 0 1 2 1 0 0 0 0 0 0

正規表現グループ

作成中

10.6 特殊文字

バックスラッシュ \ によるエスケープ

バックスラッシュ \ はその直後に現れる文字の意味を「エスケープ」するために使われます。例えば、二重引用符の中で使用されている引用符の前にバックスラッシュを置くと(\")、その引用符が二重引用符の中で表示されます。その際、中で挟まれる引用符はそれを囲む引用符とは干渉しません。

注釈:バックスラッシュを文字として表示したい場合は、その前にもう 1 つのバックスラッシュを置いて、エスケープ記号としての役割を回避する必要があります。つまり、\\ と書くことでバックスラッシュを1 つ表示できます。

特殊文字の例

特殊文字 意味
"\\" バックスラッシュ
"\n" 改行
"\"" 二重引用符中での二重引用符の表示
'\'' 一重引用符中での一重引用符の表示
"\`" バッククオート
"\r" キャリッジリターン
"\t" タブ
"\v" バーティカルタブ
"\b" バックスペース

?"'" コマンドを R コンソールで実行すると、特殊文字の一覧が表示できます(RStudio ではヘルプ画面に表示されます)。

10.7 正規表現 (regex)

10.8 正規表現と特殊文字

正規表現(または regex)とは、文字列パターンを記述するために用いられる言語規則のことです。馴染みがない人にとっては、まるで他の星の言葉のように映るかもしれません。ここでは、その正規表現に対するハードルを少し下げることを目標にします。

このセクションで扱う内容の多くは こちらのチュートリアルこちらのチートシート を参考に作成されました。ここでは、このハンドブックがインターネットへのアクセスが無く他のチュートリアルを見れない方からも利用されうることを念頭に、これらの参考文献から内容を選んで参考にしています。

正規表現は決まった構造を持たない文章(例えば、診療録、主訴、既往歴やデータフレーム中の文字列など)から特定のパターンを抜き出すためによく使われます。

正規表現を構築するために使われる基本ツールは以下の 4 つです。

  1. 文字セット
  2. メタ文字
  3. 数量詞
  4. グループ

文字セット

文字セットは角括弧 [ ] を用いて文字のリストを表現します。角括弧中に含まれる文字のいずれかが対象の文字列中に含まれる場合、一致パターンとして扱われます。例えば、母音のいずれかを検索したい場合 “[aeiou]” と表現できます。以下によく使われる文字セットを示します。

文字セット 検索される文字
"[A-Z]" 全ての大文字アルファベット
"[a-z]" 全ての小文字アルファベット
"[0-9]" 全ての数字
[:alnum:] 全てのアルファベットおよび数字
[:digit:] 全ての数字
[:alpha:] 全ての大文字および小文字アルファベット
[:upper:] 全ての大文字アルファベット
[:lower:] 全ての小文字アルファベット

複数の文字セットを 1 つの角括弧中に(空白なしで!)組み合わせることができます。例えば "[A-Za-z]" は全ての大文字および小文字アルファベットを、"[t-z0-5]" は t から z までの小文字アルファベットと 0 から 5 までの数字を表します。

メタ文字

メタ文字は文字セットの省略表記です。以下にいくつかの重要な例を示します:

メタ文字 検索される文字
"\\s" 1 つの空白
"\\w" 全てのアルファベットおよび数字(A-Z、a-z、または 0-9)
"\\d" 全ての数字(0-9)

数量詞

多くの場合、検索したいパターンは 2 つ以上の文字を含みます。数量詞により一致を探す文字や数字の長さを規定できます。

数量詞は数を指定したいパターンの後に波括弧 { } を置き、その中に数字を書くことで定義されます。 例えば、

  • "A{2}" は大文字 A 2 つを示します。
  • "A{2,4}"2 から 4 つの大文字 A を示します(波括弧中に空白は置かれません!)。
  • "A{2,}"2 つ以上の大文字 A を示します。
  • "A+"1 つ以上の大文字 A を示します(他の文字が現れるまで、連続した A は全てグループに含まれます)。
  • アスタリスク * を前におくと 0 個以上のという意味が付加されます(そのパターンが存在するか不確かなときに有用です)。

+ シンボルを使うと、他の文字が出てくるまでの連続した同一の文字が全て一致するパターンとして扱われます。例えば、"[A-Za-z]+" という表現は全ての単語(アルファベットのみでできた文字列)に対応します。

# 数量詞をテストするための文字列
test <- "A-AA-AAA-AAAA"

数量詞 {2} を用いた場合、2 つ続きの A のみがマッチとして扱われます。AAAA からは 2 つのマッチが返されます。

str_extract_all(test, "A{2}")
## [[1]]
## [1] "AA" "AA" "AA" "AA"

数量詞 {2,4} を用いた場合、2 つから 4 つ続きの A がマッチとして扱われます。

str_extract_all(test, "A{2,4}")
## [[1]]
## [1] "AA"   "AAA"  "AAAA"

数量詞 + を用いた場合、1 つ以上の連続した A がマッチとして扱われます:

str_extract_all(test, "A+")
## [[1]]
## [1] "A"    "AA"   "AAA"  "AAAA"

相対的位置

ここで紹介する構文を用いることで、探したいパターンの直前または直後に来る文字ないしはパターンを指定することができます。例えば、「ピリオドが直後に来る 2 つ続きの数字」というパターンを表現したいときは (?<=\.)\s(?=[A-Z])

str_extract_all(test, "")
## [[1]]
##  [1] "A" "-" "A" "A" "-" "A" "A" "A" "-" "A" "A" "A" "A"
位置構文 マッチするパターン
"(?<=b)a" “b” が直前に来る “a”
"(?<!b)a" “b” が直前に来ない “a”
"a(?=b)" “b” が直後に来る “a”
"a(?!b)" “b” が直後に来ない “a”

グループ

正規表現の中でグループを用いることで、パターン検索をより整理された形で行うことができます。

正規表現の例

ここでは、以下の文章から正規表現を用いて有用な情報を抽出する例を示します。

pt_note <- "Patient arrived at Broward Hospital emergency ward at 18:00 on 6/12/2005. Patient presented with radiating abdominal pain from LR quadrant. Patient skin was pale, cool, and clammy. Patient temperature was 99.8 degrees farinheit. Patient pulse rate was 100 bpm and thready. Respiratory rate was 29 per minute."

以下の正規表現は、全ての単語(連続したアルファベットのみからなるまとまり)を抽出します。

str_extract_all(pt_note, "[A-Za-z]+")
## [[1]]
##  [1] "Patient"     "arrived"     "at"          "Broward"     "Hospital"   
##  [6] "emergency"   "ward"        "at"          "on"          "Patient"    
## [11] "presented"   "with"        "radiating"   "abdominal"   "pain"       
## [16] "from"        "LR"          "quadrant"    "Patient"     "skin"       
## [21] "was"         "pale"        "cool"        "and"         "clammy"     
## [26] "Patient"     "temperature" "was"         "degrees"     "farinheit"  
## [31] "Patient"     "pulse"       "rate"        "was"         "bpm"        
## [36] "and"         "thready"     "Respiratory" "rate"        "was"        
## [41] "per"         "minute"

正規表現 "[0-9]{1,2}" は 1 つのみもしくは 2 つ続きの数字にマッチします。"\\d{1,2}" もしくは "[:digit:]{1,2}" と書くこともできます。

str_extract_all(pt_note, "[0-9]{1,2}")
## [[1]]
##  [1] "18" "00" "6"  "12" "20" "05" "99" "8"  "10" "0"  "29"

こちらのチートシート の 2 ページ目に、有用な正規表現のリストやヒントがありますので、参照してください。

こちらのチュートリアル も参考にしてください。

10.9 参考資料

stringr に含まれる関数の詳細は こちら を参照してください。

stringr に関する簡単な説明は こちら から読むことができます。