ほくそ笑む

R言語と統計解析について

ggforce パッケージの geom_mark_* 関数が便利な件について

今年の5月に『Rによるインタラクティブなデータビジュアライゼーション』という本を出しました。

この本は、主に plotly パッケージを使ってインタラクティブなグラフを作成するためのノウハウについて書かれています。

一方、この本にはビジュアライゼーションのちょっとした小技やテクニックが所々に散りばめられていて非常に勉強になります。

例えば、第1章には、外れ値を強調するために ggforce パッケージの geom_mark_hull() という関数を使う例が載っています。

ggforce パッケージには、この類似機能として以下の4つの関数が含まれています。

  • geom_mark_rect()
  • geom_mark_circle()
  • geom_mark_ellipse()
  • geom_mark_hull()

これらの関数は使ってみるとめっちゃ便利です。 今回はこれらの関数について紹介したいと思います。

まずはパッケージの読み込みと使うデータ (iris) の確認をします。

library(ggplot2)
library(ggforce)

head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

ふつうにプロットするとこんな感じです。

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_point()

geom_mark_rect

4つの関数は基本的に使い方は同じなので、geom_mark_rect を代表として使い方をざっと見ていきましょう。

geom_mark_rect は、注目させたい領域を四角で囲む geom です。

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_rect(aes(fill = Species)) +
  geom_point()

引数 label を指定することで、かっちょいいラベルを付けることができます。

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_rect(aes(fill = Species, label = Species)) +
  geom_point()

引数 filter に条件を指定することで、一部のみを強調することもできます。 これで外れ値のみを強調することができますね。

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_rect(aes(fill = Species, 
                     filter = Species == "setosa")) +
  geom_point()

以上が基本的な使い方です。

geom_mark_circle

geom_mark_circle は、注目させたい領域を円で囲む geom です。

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_circle(aes(fill = Species)) +
  geom_point()

geom_mark_rect と同じく、labelfilter も使えます。

geom_mark_ellipse

geom_mark_ellipse は、注目させたい領域を楕円で囲む geom です。

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_ellipse(aes(fill = Species)) +
  geom_point()

こちらも labelfilter が使えます。

geom_mark_hull

geom_mark_hull は、注目させたい領域に沿った形状で囲む geom です。

この関数を使うには concaveman パッケージをインストールする必要があります。

install.packages("concaveman")
ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_hull(aes(fill = Species)) +
  geom_point()

geom_mark_hulllabelfilter が使えます。さらに、concavity によって、どれくらい細かく領域の形状に沿うかを指定することもできます。concavity はデフォルトは 2 で、大きくすると大雑把になります。

ggplot(iris, aes(Petal.Length, Petal.Width)) +
  geom_mark_hull(aes(fill = Species), concavity = 10) +
  geom_point()

Accelerate your plots with ggforce · R Views には、geom_mark_hull 関数を地図上のプロットに使う例が載っていて良いなと思いました。

以上、ggforce パッケージの geom_mark_* 関数は便利なのでみなさんも使ってみてはどうでしょうという話でした。

enjoy!

参考

rviews.rstudio.com

R言語こぼれ話(1) パッケージとライブラリの違い

R言語を使っていると、「パッケージ」や「ライブラリ」という言葉をよく目にすると思います。 多くの人は、パッケージとライブラリを同じものだと思っているのではないでしょうか。 Rを普通に使う上では、これらを同じものだと思っていてもほとんど支障はありません。 しかし、R言語において、パッケージとライブラリという用語には明確な違いがあります。

このシリーズでは、R言語を使う上で知っていても知らなくても特に問題にならないようなこぼれ話をしていく予定です。 今回のこの記事では、R言語における「パッケージ」と「ライブラリ」の違いについて説明したいと思います。

パッケージとは

パッケージは、「特定の機能を実現するための関数群をまとめたもの」を指す用語です。 例えば、ggplot2 パッケージは、「エレガントなグラフを描画する」という機能を実現するための関数群を提供します。 また、dplyr パッケージは、「統一的な方法でデータ操作を行う」という機能を実現するための関数群を提供します。

パッケージを利用するには、まずインストールする必要があります。例えば、

install.packages("ggplot2")

を実行すると、ggplot2 パッケージをインストールすることができます。 インストールしたパッケージを使用するには、パッケージを読み込む必要があります。 例えば、

library(ggplot2)

を実行することで、ggplot2 パッケージが読み込まれ、このパッケージが提供する関数群を使用できるようになります。

パッケージのインストールと読み込みの違いは、電球に例えられることがあります。 この電球の例えによれば、インストールは電球の取り付け作業にあたります。 一方、パッケージの読み込みはスイッチをオンすることにあたります。 照明(パッケージの機能)を使うには、電球を取り付けて(パッケージをインストール)して、スイッチをオンする(パッケージを読み込む)必要があります。

照明に電球が取り付けられていなければ、どんなにスイッチをオンオフしても照明は光りません。 これは、パッケージを利用するには、読み込みの前にインストールする必要があることに対応します。

また、電球は一度取り付ければ、スイッチをオンオフすることで何度でも照明をつけたり消したりできます。 これは、インストールは一回だけ行えばよいのに対して、読み込みは使うたびに毎回行う必要があることに対応します(Rを終了すると自動的にスイッチオフされちゃうんですね)。

なぜインストールは一回だけで済むのかというと、パッケージをインストールするときに、そのパッケージを構成するファイル群を自分の PC にダウンロードするからです。 ダウンロードされたファイル群は、読み込みが可能な状態に展開されて、パッケージごとに作られたディレクトリに格納されます。 パッケージごとに作成された個々のディレクトリは「インストール済みパッケージ」と呼ばれます。

この、インストール済みパッケージが格納される場所のことを、R言語では「ライブラリ」と呼びます。

ライブラリとは

Rの公式マニュアルの1つである Writing R Extensions には、はっきりとこう書かれています。

A package is not a library.
訳: パッケージはライブラリではない

それでは、ライブラリとは何なのでしょうか? 続きを読んでみます。

A directory into which packages are installed, e.g. /usr/lib/R/library
(ライブラリとは)パッケージがインストールされるディレクトリのことである(例:/usr/lib/R/library

in that sense it is sometimes referred to as a library directory or library tree
この意味では、「ライブラリディレクトリ」や「ライブラリツリー」とも呼ばれる

パッケージがインストールされるディレクトリ、つまり、「インストール済みパッケージ」が格納されるディレクトリのことを「ライブラリ」と呼ぶのです。 Rの公式マニュアルを読むことで、パッケージとライブラリが全く別のものであるということがわかりました。

R言語の内部でも、パッケージとライブラリという用語は異なる概念として使い分けされています。 例えば、install.packages() は「パッケージ」をインストールする関数であり、lib という引数に「ライブラリ」を指定できます。

install.packages("ggplot2", lib = "C:\\mylib\\")

これは、好きなディレクトリにパッケージをインストールできる、つまり、好きなディレクトリをライブラリにできることを表しています。

install.packages()lib 引数に何も指定しない場合、パッケージはデフォルトのライブラリにインストールされます。 デフォルトの「ライブラリ」を調べるには .libPaths() を実行します。

.libPaths()
[1] "C:/Users/hoxom/AppData/Local/R/win-library/4.2"
[2] "C:/Program Files/R/R-4.2.1/library"   

私の環境では2つのライブラリが出力されました。 1つめは「ユーザーライブラリ」といって、PCの自分のアカウントのみが使えるパッケージが格納されるライブラリです。インストールされたパッケージは基本的にこちらに格納されます。 2つめは「システムライブラリ」といって、このPCのすべてのアカウントで共通のパッケージが格納されるライブラリです。他のユーザーに影響が出るため、こちらに格納されたパッケージを変更するときは細心の注意を払う必要があります。

パッケージを読み込むために library() を実行すると、.libPaths() の1つめのライブラリからそのパッケージを探して、該当パッケージがあればそれを読み込み、無ければ次のライブラリを探すというルールで読み込むパッケージを探します。

特定のライブラリに含まれる「パッケージ」は .packages() 関数で取得できます。 「ライブラリ」の場所は lib.loc 引数で指定します。

.packages(TRUE, lib.loc = "C:\\mylib\\")
[1] "cli"       "ggplot2"   "lifecycle" "rlang"     "vctrs"

このように、R言語では「パッケージ」と「ライブラリ」は明確に区別されて使われています。

混同の原因

R言語において、「パッケージ」と「ライブラリ」が全く異なる概念であるということがわかりました。 しかし、多くの Rユーザーがこれらを混同して使っています。 それはなぜでしょうか?

その原因はおそらく library() という関数にあります。 library() は「パッケージ」を読み込む関数なのに、「ライブラリ」という名前が付けられているのです。

このことについて、Writing R Extensions の脚注 には次のように書かれています。

although this is a persistent mis-usage. It seems to stem from S,
(パッケージとライブラリの混同は)根強い誤用である。これはS言語に由来すると思われる

whose analogues of R’s packages were officially known as library sections and later as chapters
Rにおける「パッケージ」に類するものは、Sの公式では「ライブラリセクション」として知られ、後に「チャプター」となった

but almost always referred to as libraries.
しかし、ほとんど常に「ライブラリ」と呼ばれていた。

R言語の元となったS言語において、パッケージは「ライブラリ」と呼ばれていました。 そのため、R言語でもパッケージを読み込む関数が library() となり、今の混同を招いていると思われます。

まとめ

R言語において、パッケージとライブラリは全く異なる概念でした。 しかし、多くのRユーザーはこれらを同じものだと思っています。 それでも普通のRユーザーは困ることはないでしょう。 この記事に書いてあることは明日には忘れてしまって構いません。 ただし、デフォルトとは異なるライブラリを使おうとするときには、この記事の知識が少しは役に立つかもしれません。

rstan の plot のパラメータ名(縦軸のラベル)を変更する

rstan を使っていると、プロットのパラメータ名を変更したくなるときがあります。

例えば、パラメータをベクトルで定義しているが、個々のパラメータに意味がある場合などです。

具体例を見てみましょう。

以下の例は、正規分布のパラメータを配列 theta で定義しています。 一方で、それぞれのパラメータは、 theta[1] は平均、theta[2] は標準偏差という意味を持っています。

library(rstan)

set.seed(314)
# 平均0,標準偏差1の正規分布から20個のデータを生成
x <- rnorm(20, mean = 0, sd = 1)

# 正規分布の推定コード
model_code <- 
"
data {
  int N;
  vector[N] x;
}
parameters {
  real theta[2];
}
model {
  // 正規分布。平均 theta[1], 標準偏差 theta[2]
  x ~ normal(theta[1], theta[2]);
}
"

# Stan に渡すデータ
data <- list(N = 20, x = x)

# 推定の実行
fit <- stan(model_code = model_code, data = data)

このとき、パラメータ分布をプロットすると、パラメータ名は theta に固定されてしまいます。

# パラメータの事後分布の描画
plot(fit)

これのプロットのパラメータ名を、無機質な theta から、意味のある musigma に変えたい。

それには次のようにします。

# 変えたいパラメータ名を指定
param_names <- c("mu", "sigma")

nparams <- 2  # パラメータ数
breaks <- seq(nparams, 1, by = -1)
plot(fit) +
  ggplot2::scale_y_continuous(breaks = breaks, labels = param_names, 
                              limits = c(0.5, nparams + 1))

rstan の plot のパラメータ名を変えることができました。

ポイントは、scale_y_discrete ではなく scale_y_continuous を使うところです。

ちなみに、y軸のスケールを上書きするので次のようなメッセージが出ます。

Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.

これを出さないようにする(無視する)関数 quietgg というのが rstan にはあるようです。

quietgg(
  plot(fit) +
    ggplot2::scale_y_continuous(breaks = breaks, labels = param_names, 
                                limits = c(0.5, nparams + 1))
)

enjoy

参考

R 4.2 にバージョンアップしたら rstan が動かなくなってしまいました。 そのとき助けられたブログを紹介しておきます。

developer.mamezou-tech.com

ggplot2 で axes を消す方法

最近、次の本を読んで日本地図を描くのにハマっている。

ggplot2 でシンプルに日本地図を描くとこんな感じになる。

library(tidyverse)
library(sf)
library(NipponMap)

shp <- system.file("shapes/jpn.shp", package = "NipponMap")
pref <- read_sf(shp) %>% filter(name != "Okinawa")

ggplot(pref) + geom_sf()

このとき、デフォルトでは x軸と y軸に経度と緯度が表示される。

これ、いらなくないですか? しかもこれ (axes) を消すのはけっこう面倒なコードを書く必要がある。

ggplot(pref) + geom_sf() + 
  theme(axis.ticks = element_blank(),  # tickの線を消す
        axis.text = element_blank(),   # tickの数字を消す
        axis.title = element_blank(),  # 軸のラベルを消す
        axis.line = element_blank())   # 軸の線を消す

今回はこれ (axes) を消す方法をいろいろと調べてみたので紹介する。

1. シンプルなテクニック

axes を消すシンプルなテクニックがある。 guides(x = "none", y = "none") とする方法だ。

ggplot(cars) + 
  geom_point(aes(speed, dist)) + 
  guides(x = "none", y = "none")

しかし、これはなぜか geom_sf() で書いた地図ではうまくいかない。

# なぜかうまくいかない
ggplot(pref) + geom_sf() + 
  guides(x = "none", y = "none")

代わりに、coord_sf()label_axes を指定することで axes を消すことができる。

ggplot(pref) + geom_sf() + 
  coord_sf(label_axes = "-")

こういったシンプルなテクニックを知っていれば axes は容易に消すことができる。

ただし、この方法の問題点として、コードを読んだとき、ぱっと見なにをしているのか分かりにくいということがある。

2. ggeasy パッケージ

先日、yutannihilation さんの YouTube 配信を聞いていて知った ggeasy パッケージには、簡単に axes を消す関数が用意されている。

library(ggeasy)
ggplot(pref) + geom_sf() + 
  easy_remove_axes()

この関数はけっこう柔軟に axes の一部だけ消すことができる。

# tickの線だけ消す
ggplot(pref) + geom_sf() + 
  easy_remove_axes(what = "ticks")

# x軸の axes だけを消す
ggplot(pref) + geom_sf() + 
  easy_remove_axes(which = "x")

# x軸の tick の線と y軸の tick の数字だけを消す
ggplot(pref) + geom_sf() + 
  easy_remove_axes(which = "x", what = "ticks") +
  easy_remove_axes(which = "y", what = "text")

3. テーマを変える

上記の方法では柔軟に一部分だけ消すというようなことができた。

そんなことはできなくていいという人には、テーマ (theme) を変えて消すという方法もある。

ggplot(pref) + geom_sf() + 
  theme_void()

背景が真っ白になってしまったが、地図の場合はこれで困る人はいないだろう。

他にも cowplot パッケージの theme_nothing() というのもある。

ggplot(pref) + geom_sf() + 
  cowplot::theme_nothing()

cowplot パッケージには地図用のテーマがあり、これを使っても消すことができる。

ggplot(pref) + geom_sf() + 
  cowplot::theme_map()

追記: ggforce パッケージには theme_no_axes() という関数がある。

ggplot(pref) + geom_sf() + 
  ggforce::theme_no_axes(base.theme = theme_minimal())

参考

カロリンスカ眠気尺度とは

カロリンスカ眠気尺度 (Karolinska Sleepiness Scale) は、被験者が今どれくらい眠いのかを評価するための眠気の自己評価尺度である。 カロリンスカ眠気尺度は、被験者の眠気を 1 ~ 9 の数値で表す。 被験者はこの数値のうち、自分の状態に最も近いものを選択する。 1 ~ 9 の数値のうち、奇数には「説明 (verbal descriptions)」がつけられている。 この説明は、基本的には英語であるが、日本語版もある [Kaida et al., 2006]。 カロリンスカ眠気尺度の質問は次のようになる。

あなたの眠気の状態をもっともよく表した数字に○をつけてください.

数値 説明(英語) 説明(日本語)
1 Extremely alert 非常にはっきり目覚めている
2
3 Alert 目覚めている
4
5 Neither alert nor sleepy どちらでもない
6
7 Sleepy, but no difficulty remaining awake 眠い
8
9 Extremely sleepy, fighting sleep とても眠い(眠気と戦っている)

カロリンスカ眠気尺度はリッカート尺度として、通常の統計手法を用いて分析できる。

主観的眠気尺度と客観的眠気尺度

カロリンスカ眠気尺度は主観的な (subjective) 眠気尺度と呼ばれる。 主観的な眠気尺度は他にも次のようなものがある。

  • スタンフォード眠気尺度 (Stanford Sleepiness Scale)
  • (眠気の)視覚アナログ尺度 (Visual Analog Scale)

客観的な (objective) 眠気の指標としては、一般的に、個人が眠りに落ちるまでの速度が使われる。 これは、リラックスした状態の覚醒時脳波のアルファ活動(8~12Hz)が睡眠の第1段階のシータ活動(4~8Hz)へ進むまでの時間として定められる [Dement & Carskadon (1982)]。 このような検査は、睡眠潜時検査 (sleep latency test) と呼ばれる。

参考文献

  • Torbjörn Åkerstedt & Mats Gillberg (1990) Subjective and Objective Sleepiness in the Active Individual, International Journal of Neuroscience, 52:1-2, 29-37, DOI: 10.3109/00207459008994241
  • Kosuke Kaida, Masaya Takahashi, Torbjörn Åkerstedt, Akinori Nakata, Yasumasa Otsuka, Takashi Haratani, Kenji Fukasawa, Validation of the Karolinska sleepiness scale against performance and EEG variables, Clinical Neurophysiology, Volume 117, Issue 7, 2006, Pages 1574-1581, ISSN 1388-2457, https://doi.org/10.1016/j.clinph.2006.03.011

f:id:hoxo_m:20220221214111p:plain

R でテンソルのインデックス行列のイテレータを書いた

R でテンソルのインデックス行列のイテレータを書く必要があったので書きました。

例えば、2 x 3 の行列のインデックス行列は expand.grid() 関数を使えば次のように簡単に得られます。

> expand.grid(1:2, 1:3)
  Var1 Var2
1    1    1
2    2    1
3    1    2
4    2    2
5    1    3
6    2    3

しかし、2000 x 2000 x 2000 のテンソルのインデックス行列を同じ方法で得ようとすると、メモリが足りずにエラーが出ます。

> expand.grid(1:2000, 1:2000, 1:2000)
エラー:  サイズ 29.8 Gb のベクトルを割り当てることができません

そこで、メモリに載るサイズに分割してインデックス行列を得ることを考えます。 これを実現するイテレータを今回実装しました(関数定義は記事の最後に載せます)。

> iter <- iter_index_matrix(dims = c(2000, 2000, 2000), chunk_size = 1000)
> index_matrix <- iter$nextElem()
> head(index_matrix)
  Var1 Var2 Var3
1    1    1    1
2    2    1    1
3    3    1    1
4    4    1    1
5    5    1    1
6    6    1    1
> nrow(index_matrix)
[1] 1000  # チャンクサイズ分のインデックス行列が得られた

イテレータをチャンクサイズを指定して作成することで、nextElem() メソッドを呼び出すごとにチャンクサイズ分だけのインデックス行列が得られます。

もう一度 nextElem() を呼び出すと次のチャンクサイズ分のインデックス行列が得られます。

> head(iter$nextElem())
  Var1 Var2 Var3
1 1001    1    1
2 1002    1    1
3 1003    1    1
4 1004    1    1
5 1005    1    1
6 1006    1    1
> head(iter$nextElem())
  Var1 Var2 Var3
1    1    2    1
2    2    2    1
3    3    2    1
4    4    2    1
5    5    2    1
6    6    2    1

次のチャンクを持つかどうかを判定するための hasNext() メソッドも持っています。

> iter$hasNext()
[1] TRUE

使い方としては、while ループの中でインデックス行列を処理することを想定しています。

iter <- iter_index_matrix(dims, chunk_size)
while (iter$hasNext()) {
  index_matrix <- iter$nextElem()
  # なんらかの処理
}

最後に、iter_index_matrix() の定義を載せておきます。

iter_index_matrix <- function(dims, chunk_size = 1L) {
  f_split <- cumprod(dims) <= chunk_size
  has_next <- TRUE
  if (all(f_split)) {
    next_element <- function() {
      if (!has_next) stop("StopIteration")
      has_next <<- FALSE
      expand.grid(lapply(dims, seq_len))
    }
  } else {
    split_dims <- split(dims, f_split)
    in_block_dims <- split_dims[["TRUE"]]
    iter_dim <- split_dims[["FALSE"]][1L]
    max_out_block_dims <- split_dims[["FALSE"]][-1L]
    out_block_dims <- rep(1L, length(max_out_block_dims))
    increment_out_block_dims <- function() {
      increment <- function(dims, max_dims) {
        if (length(dims) == 0L) {
          has_next <<- FALSE
          dims <- vector("numeric")
        } else if (dims[1L] < max_dims[1L]) {
          dims[1] <- dims[1] + 1L
        } else {
          dims <- c(1L, increment(dims[-1L], max_dims[-1L]))
        }
        dims
      }
      out_block_dims <<- increment(out_block_dims, max_out_block_dims)
      invisible(NULL)
    }
    step_size <- floor(chunk_size / prod(in_block_dims))
    inds <- c(seq.int(1L, iter_dim, by = step_size), iter_dim + 1L)
    in_block_dims <- lapply(in_block_dims, seq_len)
    i <- 2L
    next_element <- function() {
      if (!has_next) stop("StopIteration")
      start <- inds[i - 1L]
      end <- inds[i] - 1L
      args <- c(in_block_dims, list(start:end), out_block_dims)
      i <<- i + 1L
      if (i > length(inds)) {
        i <<- 2L
        increment_out_block_dims()
      }
      expand.grid(args)
    }
  }
  obj <- list(nextElem = next_element, hasNext = function() has_next)
  class(obj) <- c("ihasNext", "abstractiter", "iter")
  obj
}

このイテレータは iterators パッケージの nextElem() 関数に対応しています。

library(iterators)
index_matrix <- nextElem(iter)

また、itertools パッケージの hasNext() 関数にも対応しています。

library(itertools)
hasNext(iter)

大規模なテンソルのインデックス行列を生成したいときはぜひご活用ください。

Enjoy!

R で 1文字置換を行うときは chartr を使う

次のようなデータがあるとする。

N <- 100
x <- c("Apple", "Banana", "Candy")
x <- rep(x, N)
head(x)
[1] "Apple"  "Banana" "Candy"  "Apple"  "Banana" "Candy" 

このデータの中の全角文字を半角にしたい。

1. gsub を使う

base 関数にある gsub() を使うと次のように書ける。

zenkaku <- c("A", "B", "C")
hankaku <- c("A", "B", "C")

use_gsub <- function(string, pattern, replacement) {
  for (i in seq_along(pattern)) {
    string <- gsub(pattern[i], replacement[i], string)
  }
  string
}
result <- use_gsub(x, zenkaku, hankaku)
head(result)
[1] "Apple"  "Banana" "Candy"  "Apple"  "Banana" "Candy" 

2. str_replace を使う

stringr パッケージの str_replace_all() を使っても同様に書ける。

library(stringr)

use_str_replace <- function(string, pattern, replacement) {
  for (i in seq_along(pattern)) {
    string <- str_replace_all(string, pattern[i], replacement[i])
  }
  string
}
result <- use_str_replace(x, zenkaku, hankaku)
head(result)
[1] "Apple"  "Banana" "Candy"  "Apple"  "Banana" "Candy" 

速度を比較してみよう。

library(microbenchmark)

microbenchmark(
  use_gsub = use_gsub(x, zenkaku, hankaku),
  use_str_replace = use_str_replace(x, zenkaku, hankaku)
)
Unit: microseconds
            expr      min        lq      mean   median       uq      max neval
        use_gsub 2723.201 2977.7515 3116.3581 3172.952 3229.102 3511.901   100
 use_str_replace  833.201  881.5005  938.3599  913.200  954.601 1331.201   100

str_replace の方が 3倍ほど速いようである。

3. chartr を使う

base 関数には1文字置換を行う関数として chartr() がある。 例えば次のように使う。

chartr(c("Apple", "Banana", "Candy"), old = "ABC", new = "ABC")
[1] "Apple"  "Banana" "Candy" 

oldnew に同じ長さの文字列を渡すことで、同じ場所にある文字が置換される。 上の例で言えば、old の1番目の文字 new の1番目の文字 A に置換される。 2番目3番目も同様である。

これを使うと次のように書ける。

zenkaku_string <- paste(zenkaku, collapse = "")
hankaku_string <- paste(hankaku, collapse = "")

use_chartr <- function(string, old, new) {
  chartr(string, old = old, new = new)
}
result <- use_chartr(x, zenkaku_string, hankaku_string)
head(result)
[1] "Apple"  "Banana" "Candy"  "Apple"  "Banana" "Candy" 

速度を比較してみる。

microbenchmark(
  use_gsub = use_gsub(x, zenkaku, hankaku),
  use_str_replace = use_str_replace(x, zenkaku, hankaku),
  use_chartr = use_chartr(x, zenkaku_string, hankaku_string)
)
Unit: microseconds
            expr      min       lq      mean    median        uq      max neval
        use_gsub 2637.301 2664.200 2724.2630 2703.4010 2741.4510 3214.301   100
 use_str_replace  816.502  834.501  866.4410  852.5505  881.7510 1176.602   100
      use_chartr  100.801  103.251  106.9551  106.7010  108.4505  148.902   100

str_replace の 10倍、gsub の 30倍くらい速い。

4. str_replace の別解

str_replace ではこんなことができるそうだ。

これを使えば次のように書ける。

library(stringr)

pattern <- hankaku
names(pattern) <- zenkaku
use_str_replace2 <- function(string, pattern) {
  str_replace_all(string, pattern)
}
result <- use_str_replace2(x, pattern)
head(result)
[1] "Apple"  "Banana" "Candy"  "Apple"  "Banana" "Candy" 

速度を比較してみる。

microbenchmark(
  use_gsub = use_gsub(x, zenkaku, hankaku),
  use_str_replace = use_str_replace(x, zenkaku, hankaku),
  use_str_replace2 = use_str_replace2(x, pattern),
  use_chartr = use_chartr(x, zenkaku_string, hankaku_string)
)
Unit: microseconds
             expr      min        lq     mean    median        uq      max neval
         use_gsub 2746.401 2794.5510 3060.284 2912.0010 3235.4015 5110.902   100
  use_str_replace  830.901  890.4515  944.777  914.8505  964.2015 1290.900   100
 use_str_replace2  485.400  501.2510  557.874  525.4015  566.4010 2074.200   100
       use_chartr  104.401  111.3510  120.510  115.2510  128.8010  228.401   100

通常の str_replace の2倍くらい速くなったが、chartr には敵わなかった。

5. まとめ

1文字置換を行うときは chartr を使うと良い。

Enjoy!