Heavy Watal

RプログラミングTips

整然データを意識してtidyverseパッケージ群を使う

tidy datasets are all alike but every messy dataset is messy in its own way
Hadley Wickham

tidyverse はそういう思想に基いて互いに連携するようデザインされたパッケージ群で、 R標準の関数よりも遥かに分かりやすく安全で高機能なものを提供してくれている。

紙の本でも公開オンライン版でもいいのでとにかく R for Data Science を読むのが一番。

確率分布

https://cran.r-project.org/web/views/Distributions.html

https://en.wikibooks.org/wiki/R_Programming/Probability_Distributions

関数の種類

d___(x, ...)
確率密度関数 (PDF)。 $P[X = x]$
p___(q, ..., lower.tail=TRUE, log.p=FALSE)
累積分布関数 (CDF)。 デフォルトでは左からqまでの積分 $P[X \leq q]$ 。 lower.tail=FALSE とするとqより右の積分(相補CDF) $P[X > q]$ 。

第一引数やパラメータ...はvector処理されるが、 後半の論理値引数はvectorを与えても先頭のみが使われる。

離散分布の場合は境界の値を含むか含まないかでバー1本分の差が出るので注意。 ホントは lower.tail=FALSE でも境界を含むべきだと思うんだけど。

1 - pnorm(...)log(pnorm(...))のほうが直感的に分かりやすいので、 lower.tail=FALSElog.p=TRUEは不要なようにも思われるが、 これらの引数で内部処理させたほうが浮動小数点型の限界付近での計算が正確。

# complementary
1 - pnorm(10, 0, 1)                # 0
pnorm(10, 0, 1, lower.tail=FALSE)  # 7.619853e-24

# log
log(pnorm(10, 0, 1))               # 0
pnorm(10, 0, 1, log.p=TRUE)        # -7.619853e-24
q___(p, ..., lower.tail=TRUE, log.p=FALSE)

累積分布関数の逆関数。 左からの累積確率が p となる確率変数の値。 p___() の逆関数。 lower.tail=FALSE とすると右から。

r___(n, ...)

乱数を n 個生成する

> dnorm(c(0, 1.96))
[1] 0.39894228 0.05844094
> pnorm(c(0, 1.96))
[1] 0.5000000 0.9750021
> qnorm(c(0.5, 0.975))
[1] 0.000000 1.959964
> rnorm(4)
[1] -1.77327259  0.95713346  0.27941121  0.08387267

分布の種類

離散

_binom(size, prob)
_geom(prob)
_hyper(m, n, k)
_nbinom(size, prob, mu)
_pois(lambda)
_signrank(n)
_wilcox(m, n)

連続

_beta(shape1, shape2)
_cauchy(location=0, scale=1)
_chisq(df)
_exp(rate=1)
_f(df1, df2)
_gamma(shape, rate=1, scale=1/rate)
_lnorm(meanlog=0, sdlog=1)
_logis(location=0, scale=1)
_norm(mean=0, sd=1)
_t(df)
_unif(min=0, max=1)
_weibull(shape, scale=1)

関数を作る

基本

my_add = function(x, y=1) {
   x + y
}

my_add(13, 29)  # 42
my_add(3)       # 4   (2つめの引数を省略すると1)

引数の選択肢を指定・チェック match.arg()

省略すると先頭の要素が採用される

great_scott = function(name=c('Marty', 'Emmett', 'Biff')) {
    name = match.arg(name)
    print(sprintf('I am %s.', name))
}
great_scott('Marty')     # OK
great_scott('DeLorean')  # Error
great_scott()            # OK, Marty

引数の有無をチェック

困ったことに、引数不足で呼び出した瞬間にはエラーを出してくれず、 関数の中でその引数の中身を参照しようとしたところで初めてエラーが出て止まる

my_func = function(x) {
    print('WTF! This line is printed anyway.')
    print(x)
}
my_func()

関数の中で missing() を使えばエラーを出さずに有無をチェックできる。 これを利用して引数省略可能な関数を作ることもできるが、 それなら素直にデフォルト引数を使ったほうがシンプルだし、 利用者は引数を見るだけで意図を読み取れる

good_func = function(x, y=x) {
    x + y
}

bad_func = function(x, y) {
    if (missing(y)) {y = x}
    x + y
}

good_func(3)  # 6
bad_func(3)   # 6

可変長引数 (...)

最後の引数をピリオド3つにし、list(...)c(...) で受け取る

func = function(x, y, ...){
    for (i in c(...)) {
        cat(i, "\n")
    }
}

引数を中身ではなく名前として評価

value = 42
fun1 = function(x) x
fun2 = function(x) substitute(x)
fun3 = function(x) deparse(substitute(x))

> fun1(value)
[1] 42
> fun1(quote(value))
value
> fun2(value)
value
> fun3(value)
[1] "value"

詳しくは non-standard expression (NSE) で調べる:

attach() するべからず

あるdata.frameの column_a というカラムにアクセスしたいとき、 普通はダラー$を使って some_data$column_a のようにする。 attach(some_data) すると、data.frame内のすべてのカラムが独立した変数のように扱われて、 column_a だけでアクセスできるようになる (detach() するまで)。 でもそうすると、ワークスペースは変数でいっぱいになってしまうし、 名前の衝突による不具合が起こる危険性がある。 そこで、範囲限定で一時的に attach() 状態を作り出す with() を使う。 安全で楽チン。

half = iris$Sepal.Length / 2

with(iris, {
    ## このブレースの中では
    ## attach(iris)されたのと同じように、
    ## 列の名前でダイレクトにアクセスできる。

    half = Sepal.Length / 2
})

最適化・高速化

最新の R を使う

ベクトル化されてる関数・演算子を使う

Rのナマのforループは絶望的に遅い。 四則演算のみならず様々な処理がベクトルのまま行えるようになっているので、そっちを使う。

vec = seq_len(60)
vec + vec
vec * 2

ifelse(vec %% 3,
       ifelse(vec %% 5, vec, 'buzz'),
       ifelse(vec %% 5, 'fizz', 'fizzbuzz'))

list, data.frame, matrix

並列化

See foreach #parallel

イテレータでメモリ節約

See foreach #iterators

ボトルネックを知る

> Rprof()           # start profiling
> some_hard_work()
> Rprof(NULL)       # end profiling
> summaryRprof()

実行時間の計測

> system.time(測定したいコマンド)

関連書籍

統計解析やグラフ描画ではなく、 プログラミング言語としてのRを学びたいときに: