文字

文字列 の R

2個の演習

文字列について

背景

文字列は、引用符で囲まれたテキストのことです。

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()のように、個々の文字列を操作することももちろん可能ですが、それには特定の関数が必要で、これについては以下で説明します。

エスケープ

上で見たように、一部の句読点は、バックスラッシュ\を前に付けて「エスケープ」する必要があります。

この話題は言語間でかなり標準化されているため、経験豊富なプログラマーの多くにとって、そのリストは驚くようなものではないでしょう。

引用符(上記参照)に加えて、リストには改行\n、タブ\t、バックスラッシュ\\、そして\Uや\uで始まるUnicode記号があります。

エスケープ文字をたくさん使わずに済むように、r"( )"の中で生の文字列(raw string)を定義できます。

r"(raw "string")"
#> [1] "raw \"string\""

括弧( )を含む生の文字列には、他にも多くの構文オプションがあります。

ロケール

現在サポートされているどのバージョンのRも文字列をUTF-8でエンコードするため、世界中のほとんどの言語を扱えます。

"आधुनिक मानक हिन्दी"
#> [1] "आधुनिक मानक हिन्दी"

さらに、多くのR関数はlocaleを認識しており、文字列の並べ替え、大文字・小文字への変換、日付や時刻などについて、地域の慣習に従おうとします。

このシラバスでは、主に米国英語の例を示します。これは一般的なデフォルトであることと、シラバスの著者がたまたま米国在住であることの両方が理由です。

ロケールは複雑な話題なので、ご自身の状況に合ったドキュメントを探してみてください。

文字列関数

base Rでもほとんどのことは_可能_ですが、多くのプログラマーは文字列操作のサポートが限られていて分かりにくいと感じています。

幸いなことに、tidyverseにはもっと直感的で柔軟、かつ一貫性のあるパッケージがあります。

Base R

古いスタイルの文字列関数を使ったレガシーコードがまだたくさんあります。 これには、Exercism上のコミュニティの解答の多くも含まれます。

また、小さなタスクでは、大きなライブラリを読み込む代わりに組み込み関数を使うと便利なことがあります。 Rトラックのメンテナーは、GitHubリポジトリで演習を検証するための模範解答を書くときに、この方法を取ることが多いです。

そのため、古い方法についてもある程度知っておく価値はあります。

以下に示す関数は最もシンプルな形で紹介していますが、ほとんどには省略可能な引数もあります。 詳しくはドキュメントを参照してください。

nchar

文字列の文字数を返します。

nchar("string")
#> [1] 6

nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7

「文字」とは何かという(複雑な!)話題は、ここでの範囲を超えています。 上の例から、いくつかの点だけ押さえておきましょう。

  • Rの文字列は内部的にはCで実装されていますが、\0で終端されていません(終端されていれば長さに1が加算されるはずです)。
  • マルチバイトのUnicode文字は、期待するとおりに扱われます。"Hrōðgār"は7文字(「ルーン」または「コードポイント」とも呼ばれます)ですが、格納には10バイト必要です。

ncharに何を渡すかには注意してください。 character以外の値では、結果が予測できないことがありますが、最近のRのバージョンでは改善されています。

grep

文字列のベクトルの中からパターンを検索し、マッチした箇所のインデックスを返します。

grep("Jav", c("Java", "Python", "Javascript"))
#> [1] 1 3
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")パラメータがあります。

substr

1つの文字列を操作し、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"
regexprとregexpr

「regular expression(正規表現)」を略した名前で、これらの関数は文字列内でパターンの出現箇所を検索し、(場合によっては)役立つデータのリストを返します。 ここでは網羅性のために挙げているだけです。もっと良い選択肢があります!

regexprは最初の出現箇所だけを返し、gregexprはすべての出現箇所を返します("g"は「global」検索の意味です)。

名前が示すとおり、パターンには正規表現を使えます。これについては後の概念で扱います。

Advanced

なぜこれらの古い文字列関数はUnixのシェルコマンドのように見えるのでしょうか?

Rは1993年に作られましたが、S言語との後方互換性を重視して設計されました。

Sはベル研究所で設計され、1976年に最初に登場しました。 ベル研究所は、Unix(1969年から)とC言語(1972年)も生み出しています。

ずっと早い時代の、まさに凄腕のチームです! 彼らが互いに話し合い、アイデアを交換していたとしか考えられません。

Tidyverse

好意的に解釈すれば、古いスタイルの文字列関数はRが提供する最良のものとは言えません(もっと辛口の見方もあります)。

この状況を改善するため、stringrライブラリは2009年から開発が続けられており、今ではTidyverseコレクションの中核を担っています。

library(stringr)またはlibrary(tidyverse)で名前空間に読み込めます。

主な特徴をいくつか挙げます。

  • すべての関数名はstr_という接頭辞を持ち、目的が分かりやすいようになっています。
  • 引数の順序は一貫性があり、入力データが最初の引数になります。
  • 入力は単一の文字列から複雑なデータ構造まで多岐にわたります。関数はそれに適応し、最も理にかなった処理を行おうとします。

すべてのTidyverseパッケージと同様に、stringrには充実したドキュメントがあり、通常はパッケージの作者によって書かれています。

stringrには何十もの関数があり、多くの場合、さまざまな省略可能な引数を持っています。 最初は少し圧倒されるかもしれませんが、そんなときはドキュメントが頼りです。

何を入力したいのか(単一の文字列、ベクトル、それとももっと複雑なもの?)、そして何を出力したいのかを考えてみましょう。

正しい関数と正しい引数を見つけられれば、やりたいことを実現する簡単な方法がきっとあるはずです。

余談:パイプの簡単な入門

関数をつなぐパイプ|>の使い方は、シラバスの他の部分でより詳しく説明します。

今のところ、次のことを知っておくと役立ちます。

  • パイプの左側の結果は、右側の関数の最初の引数になります(ここで「右側」とは、省略可能な改行を無視したものを指します)。
  • 残りの引数は、関数呼び出しに含めることができます。
  • Rでは、パイプの先には関数の_名前_だけでなく、関数_呼び出し_を指定する必要があるため、常に括弧が必要です(これは他の言語とは異なります)。

以下の例で構文を説明します。 個々の関数については後で説明します。

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_sub_all takes vector input, gives list output
str_sub_all(c(s, "ijklmnop"), c(1, 4, 6), c(2, 5, -1))
[[1]]
#> [1] "ab"  "de"  "fgh"

[[2]]
#> [1] "ij"  "lm"  "nop"
文字列を結合する

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にシンプルな文字列補間をもたらします。 波括弧{ }の中の式が評価され、そのテキスト表現に置き換えられます。

複数の式文字列を指定した場合は、str_flattenと同様に結合されます。 また、文字列の先頭や末尾の空白を任意で削除することもできます。

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

str_glueは柔軟(かつかなり複雑)ですが、その基盤となるglueライブラリには、さらに幅広い可能性があります。

文字列を分割する

Rの非常によくある用途は、乱雑なデータを分析しやすい一貫した形式に整えることです。 これを助けるために、テキストを部分文字列に分割する方法がいくつかあります。

str_split_1は最もシンプルで、1つの文字列を、指定したパターン(デフォルトのパターンはありません)で分割した要素のベクトルに変換します。

s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R"      "Julia"  "Python"

str_split_iは文字ベクトルとインデックスnを取り、各入力文字列のn番目の部分文字列のベクトルを返します。

s2 <- c(s, "C Kotlin, F#")  #> [1] "R Julia Python" "C Kotlin, F#"
str_split_i(s2, " ", 2)  # 2nd element of each
#> [1] "Julia"   "Kotlin,"

str_splitは文字ベクトルを取り、ベクトルのリストを返します。 私たちのシラバスにはリストの概念があり、その出力について説明しています。

str_split(s2, " ")
[[1]]
#> [1] "R"      "Julia"  "Python"

[[2]]
#> [1] "C"       "Kotlin," "F#"     

str_split_fixedもあり、こちらは行列を返します。 行列については、後の概念で扱います。

文字列を比較する

文字列全体を比較するために、str_equal()は「等しい」のさまざまな定義を使えます。 最も単純には、デフォルトでは大文字と小文字を区別しますが、変更できます。 ロケールやUnicodeによって、さらに多くの可能性が生まれます。

str_equal("Odin", "odin")
#> [1] FALSE
str_equal("Odin", "odin", ignore_case = TRUE)
#> [1] TRUE

他にも多くの関数があり、正規表現の概念でより詳しく説明します。

文字列を変換する

一部の言語(主にヨーロッパの言語)では、大文字と小文字を区別します。 これらを相互に変換するための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"
その他

str_dupは複製を作り、str_uniqueは重複を削除します。

デフォルトではstr_dupに区切り文字はありませんが、指定することもできます。

str_dup("xyz", 4, sep = ", ")
#> [1] "xyz, xyz, xyz, xyz"

str_uniqueはunique()を拡張したものです。 どちらもベクトルから重複する要素を削除しますが、stringrの関数は大文字と小文字の区別や、ロケール固有のさまざまなマッチングも扱えます。

inp <- c("some", "strings", "Some", "Strings")
str_unique(inp)
#> [1] "some"    "strings" "Some"    "Strings"
str_unique(inp, ignore_case = TRUE)
#> [1] "some"    "strings"
Advanced

stringrライブラリは、より低レベルな関数を集めたstringiライブラリに基づいています。

上で説明した以外に特別なニーズがある場合は、次のようにしてください。

  1. stringrのドキュメントを確認してください。ここまで長い概念のドキュメントでも触れきれないほど多くのオプションを網羅しています。
  2. stringiのドキュメントを確認してください。さらに多くの機能が説明されています。
GitHubで編集 リンクは新しいウィンドウまたはタブで開きます

文字列を学習する

練習はロックされています

文字列を練習するには、あと1個の演習のロックを解除してください