トラック
/
R
R
/
演習
/
ログレベル
ログレベル

ログレベル

学習演習

はじめに

背景

文字列とは、引用符で囲まれたテキストのことです。 現在サポートされているどのバージョンの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のパッケージがあります。

base R

昔ながらの文字列関数を使っているレガシーコードは今もたくさんあります。 Exercismのコミュニティの解答の多くもこれに当たります。

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

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

nchar

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

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

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

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

  • Rの文字列は内部的にはCで実装されていますが、\0で終端されません(終端されていれば長さに1が加わります)。
  • マルチバイトのUnicode文字は、期待するとおり(あるいは願うとおり)に扱われます。"Hrōðgār"は7文字(「ルーン」や「コードポイント」とも呼ばれます)ですが、保存には10バイト必要です。
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"

Tidyverse

好意的に言えば、昔ながらの文字列関数は、Rが提供する最良のものとは言えません(あまり好意的でない見方もあります)。

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

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

主な特徴は次のとおりです。

  • すべての関数名にstr_という接頭辞が付き、目的が分かりやすくなるよう工夫されています。
  • 引数の順序は一貫するように設計されており、入力データが最初の引数になります。
  • 入力は、1つの文字列から複雑なデータ構造まで幅広く対応できます。関数はうまく適応し、最も妥当な処理をしようとします。

他のすべてのTidyverseパッケージと同様に、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_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つです:

  • INFO
  • WARNING
  • ERROR

タスクは3つあり、どれもログ行を受け取って、それを使って何かを行うものです。

base Rの従来の関数でも、stringrライブラリでも、好きな方を使えます。どちらを使っても、短く実用的な解答を書くことができます。

1. ログ行からメッセージを取り出す

message関数を実装して、ログ行のメッセージを返すようにしましょう:

message("[ERROR]: Invalid operation")
#> "Invalid operation"

先頭や末尾の空白は取り除きます:

message("[WARNING]:  Disk almost full\r\n")
#> "Disk almost full"

2. ログ行からログレベルを取り出す

log_level関数を実装して、ログ行のログレベルを返すようにしましょう。ログレベルは小文字で返します:

log_level("[ERROR]: Invalid operation")
#> "error"

3. ログ行を整形し直す

reformat関数を実装して、ログ行を整形し直しましょう。メッセージを先に、そのあとにログレベルを括弧(())に入れて書きます:

reformat("[INFO]: Operation completed")
#> "Operation completed (info)"
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
R Exercism

ログレベルを始める準備はできましたか?

Exercismに登録すれば、21個のコンセプト111個の演習、そして本物の人間によるメンタリングとともに、Rを学んでマスターできます。すべて無料です。