文字列とは、引用符で囲まれたテキストのことです。 現在サポートされているどのバージョンのRも文字列をUTF-8でエンコードしているので、世界のほとんどの言語を扱えます。
mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"
残念ながら、この用語は、他の言語に慣れたプログラマーには戸惑うものかもしれません。
typeはcharacterですが、Rは1文字(他のいくつかの言語ではcharと呼ばれますが、Rでは_そうではありません_)と長い文字列を区別しません。
どちらも1つの要素であり、シングルクォート' 'とダブルクォート" "のどちらで囲んでも同じです。
スタイルガイドでは、文字列の中でダブルクォートをエスケープせずに使いたい場合を除いて、ダブルクォートを使うことを勧めています。
str2 <- 'String with "quotes"' # convenience format
str2
#> [1] "String with \"quotes\"" # standard format
したがって、character vectorは文字列のベクトルであり、各文字列が1つの要素になります。
s <- c("some", "strings")
s
#> [1] "some" "strings"
length(s)
#> [1] 2
length(s[1]) # the number of vector elements, not the string length
#> [1] 1
nchar(s[1]) # the number of characters in the string "some"
#> [1] 4
上の例では、length(s[1])はlength("some")と同じで、_文字列の長さ_ではなく_ベクトルの要素の数_を返します。
個々の文字列を操作することは明らかに可能で、上の例のnchar()がそうですが、そのためには特定の関数が必要で、それについては後述します。
base Rでもほとんどのことは_可能_ですが、多くのプログラマーは、文字列操作のサポートがかなり限られていて分かりにくいと感じています。
幸いなことに、もっと直感的で柔軟、かつ一貫性のあるtidyverseのパッケージがあります。
昔ながらの文字列関数を使っているレガシーコードは今もたくさんあります。 Exercismのコミュニティの解答の多くもこれに当たります。
また、小さなタスクでは、大きなライブラリを読み込むよりも組み込み関数を使ったほうが便利なことがあります。 Rトラックのメンテナーは、演習を検証するための模範解答を書くときに、この方法を取る傾向があります。
そのため、古いやり方についてもある程度知っておく価値はあります。
nchar文字列の文字数を返します。
nchar("string")
#> [1] 6
nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7
「文字」とは何かという(ややこしい!)話題は、ここでの範囲を超えています。 上の例から、次の2点だけ押さえておいてください。
\0で終端されません(終端されていれば長さに1が加わります)。pasteとpaste0
任意の数の文字列を1つの文字列に結合します。
pasteでは、区切り文字を指定することもできます。デフォルトはスペースです。
paste0(v)はpaste(v, sep = "")と同じです。
paste("bits", "of", "a", "string")
#> [1] "bits of a string"
paste0("bits", "of", "a", "string")
#> [1] "bitsofastring"
sprintf文字列の組み立てをより細かく制御するために、RはC(および後発のいくつかの言語。Wikipediaには約30の言語が挙げられています)からsprintf関数を取り入れています。
r <- 5.3
pi <- 3.14159
sprintf("A circle of radius %.1f has area %.2f", r, pi * r^2)
#> [1] "A circle of radius 5.3 has area 88.25"
trimws先頭や末尾の空白は、trimws()で取り除けます。
s <- " messy string "
trimws(s)
#> [1] "messy string"
両端をまとめて取り除きたくない場合は、which = "left"(または"right")という引数があります。
substr1つの文字列に対して動作し、2つの位置(両端を含む)の間の部分文字列を返します。
substr("Exercism", 5, 7)
#> [1] "cis"
strsplit区切り文字列に基づいて、文字列をRのリストに分割します。 区切り文字は必須で(デフォルトはありません)、空文字列にすることもできます。
strsplit("R, Python, Julia", ", ")
[[1]]
#> [1] "R" "Python" "Julia"
strsplit("Exercism", "")
[[1]]
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
リストについては、別のコンセプトで扱います。
まだシラバスのその部分に到達していない場合は、今のところ、unlist()関数が(この場合)リストをベクトルに変換することだけ知っておいてください。
unlist(strsplit("Exercism", ""))
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
好意的に言えば、昔ながらの文字列関数は、Rが提供する最良のものとは言えません(あまり好意的でない見方もあります)。
この状況を改善するため、stringrライブラリは2009年から開発が続けられており、今ではTidyverseコレクションの中核的な一部になっています。
library(stringr)またはlibrary(tidyverse)で名前空間に読み込みます。
主な特徴は次のとおりです。
str_という接頭辞が付き、目的が分かりやすくなるよう工夫されています。他のすべてのTidyverseパッケージと同様に、stringrには充実したドキュメントがあり、たいていはパッケージの作者によって書かれています。
関数をつなぐパイプ|>の使い方については、シラバスの別の場所で詳しく説明します。
今のところ、次のことを知っておくと役立ちます。
次の例は、この構文を示しています。 個々の関数については後で説明します。
library(stringr)
"Monday-25-February" |>
str_split("-") |>
unlist() |> # [1] "Monday" "25" "February"
str_sub(1, 3) |> # [1] "Mon" "25" "Feb"
str_to_upper() |> # [1] "MON" "25" "FEB"
str_flatten(collapse = ", ")
#> [1] "MON, 25, FEB"
str_lengthはコードポイントを数えます(ncharsと似ています)。
ほかにも、str_trim()やstr_squish()など、空白を操作する関数がいろいろあります。
これは、どんなデータサイエンスのプロジェクトでも、最初に行う重要なクリーニング作業になることがよくあります。
# The first 3 kernels in JuPyteR notebooks:
str_length(c("Julia", "Python", "R")) #> [1] 1 6 5
#> [1] 5 6 1
str_pad(c("Julia", "Python", "R"), 8, "right") # add spaces if necessary
#> [1] "Julia " "Python " "R "
str_trim("Julia Python R ") # remove leading & trailing whitespace
#> [1] "Julia Python R"
str_squish("Julia Python R ") # trim, collapse multiple spaces
#> [1] "Julia Python R"
str_sub(string, start, end)関数は、startからendまで(両端を含む)の部分文字列を返し、str_sub_all()は複数の文字列に対して操作できます。
柔軟性はとても高いです。
startとendは、それぞれ最初と最後の文字がデフォルトです。
これらにはインデックスのベクトルを指定できます。
ベクトルのインデックスとはうれしい違いで、Python流に、負の値は文字列の末尾から数えます。
s <- "abcdefgh"
str_sub(s, 2, 4)
#> [1] "bcd"
str_sub(s, 5)
#> [1] "efgh"
str_sub(s, -3, -2)
#> [1] "fg"
str_sub(s, c(1, 4), c(2, 5))
#> [1] "ab" "de"
str_sub(s, c(1, 4, 6), c(2, 5, -1))
#> [1] "ab" "de" "fgh"
s |> str_sub(2, 4) # these functions are designed for pipes
#> [1] "bcd"
str_flattenは、文字ベクトルを1つの文字列に変換します。
collapse引数は各要素の間に挿入され、デフォルトは""です。
c("R", "Julia", "Python") |> str_flatten()
#> [1] "RJuliaPython"
c("R", "Julia", "Python") |> str_flatten(collapse = " - ")
#> [1] "R - Julia - Python"
str_cは、複数の文字ベクトルを1つの文字ベクトルに変換します。必要に応じてリサイクルされます。
str_c(LETTERS[1:8], 1:8, sep = ":")
#> [1] "A:1" "B:2" "C:3" "D:4" "E:5" "F:6" "G:7" "H:8"
str_glueは、Rに簡単な文字列補間をもたらします。
波括弧{ }の中の式が評価され、そのテキスト表現に置き換わります。
r <- 5.3
pi <- 3.14159
str_glue("A circle of radius {r} has area {pi * r^2}")
A circle of radius 5.3 has area 88.2472631
Rの非常によくある使い方は、雑然としたデータを分析しやすい一貫した形式に整えることです。 そのために、テキストを部分文字列に分割する方法がいくつかあります。 そのほとんどは、ここでの範囲を超えています。
str_split_1が最も簡単で、1つの文字列を、指定したパターン(デフォルトのパターンはありません)で分割した断片のベクトルに変換します。
s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R" "Julia" "Python"
一部の言語(主にヨーロッパの言語)では、大文字と小文字を区別します。
これらを相互に変換するstringrの関数がいくつかあり、ロケールに適した方法で変換します。
str_to_lower()str_to_upper()str_to_title():各単語の先頭文字を大文字にstr_to_sentence():文の先頭文字を大文字にstr_to_upper("elixir") # defaults to locale = "en"
#> [1] "ELIXIR"
str_to_upper("ελληνικά") # infers Greek
#> [1] "ΕΛΛΗΝΙΚΆ"
str_to_title("the cat sat on the mat")
#> [1] "The Cat Sat On The Mat"
str_to_sentence("the cat sat on the mat")
#> [1] "The cat sat on the mat"
パターンマッチに基づく部分文字列の置換では、str_replace()はパターンの最初の出現を変更し、str_replace_all()はすべての出現を変更します。
文字のインデックスに基づく部分文字列の置換には、str_sub()の代入形式があります。
s <- "abcde"
str_sub(s, 3, 4) <- "yz"
s
#> [1] "abyze"
この演習では、ログ行を処理します。
各ログ行は、"[<LEVEL>]: <MESSAGE>"という形式の文字列です。
ログレベルは次の3つです:
INFOWARNINGERRORタスクは3つあり、どれもログ行を受け取って、それを使って何かを行うものです。
base Rの従来の関数でも、stringrライブラリでも、好きな方を使えます。どちらを使っても、短く実用的な解答を書くことができます。
message関数を実装して、ログ行のメッセージを返すようにしましょう:
message("[ERROR]: Invalid operation")
#> "Invalid operation"
先頭や末尾の空白は取り除きます:
message("[WARNING]: Disk almost full\r\n")
#> "Disk almost full"
log_level関数を実装して、ログ行のログレベルを返すようにしましょう。ログレベルは小文字で返します:
log_level("[ERROR]: Invalid operation")
#> "error"
reformat関数を実装して、ログ行を整形し直しましょう。メッセージを先に、そのあとにログレベルを括弧(())に入れて書きます:
reformat("[INFO]: Operation completed")
#> "Operation completed (info)"