StatsBeginner: 初学者の統計学習ノート

統計学およびR、Pythonでのプログラミングの勉強の過程をメモっていくノート。たまにMacの話題。

Rでよく忘れる、よく間違える書き方(随時追記)

よく忘れることのメモです。

  • NAかどうかの判定にはx==NAとかではなくis.na(x)を使う
  • 要素に含まれるかどうかの判定は、%in%かis.element()を使う。これはデータ全体の中から何かを抽出するときの条件を複数条件にしたい場面でも使えるときがある。(たとえば簡単なところでは、subset()でデータフレームから条件付の抽出をするとき、&で条件を並べなくてもよくなったりとか)
> is.element(3, c(1,2,3))
[1] TRUE
> 3 %in% c(1,2,3)
[1] TRUE
  • {dplyr}でデータフレームを操作した後、as.data.frame()で標準のデータフレームに戻してからじゃないと、別の関数に与えたときにエラーが出る場合がある
  • {data.table}のfread()関数でcsvとかを読み込んだ場合も、それを標準のデータフレームにしてから使わないとエラーが出ることがある
  • factor型のデータをnumericに変えると、factorの中身ではなくレベル(水準)番号が数字になる。
> x <- as.factor(c(1,10,100,1,10,100))
> as.numeric(x)
[1] 1 2 3 1 2 3
  • リストに要素をアペンドするとき、c()関数でできるのだが、付け加える方にlist()をかけとかないといけない
> x1 <- c(1,2,3)
> x2 <- c('a','b','c')
> l1 <- list(x1, x2)
> print(X)
[[1]]
[1] 1 2 3

[[2]]
[1] "a" "b" "c"

[[3]]
[1] "あ"

[[4]]
[1] "い"

[[5]]
[1] "う"

> x3 <- c('あ','い','う')
> l2 <- c(l1, x3)  # これはダメ
> print(l2)
[[1]]
[1] 1 2 3

[[2]]
[1] "a" "b" "c"

[[3]]
[1] "あ"

[[4]]
[1] "い"

[[5]]
[1] "う"

> l3 <- c(l1, list(x3))  # こうする
> print(l3)
[[1]]
[1] 1 2 3

[[2]]
[1] "a" "b" "c"

[[3]]
[1] "あ" "い" "う"
  • パネルデータをロング型にしたりワイド型にしたりするのには{tidyr}を使う。ワイドをロングにするのはgather()関数で、ロングをワイドにするのはspread()関数で。
  • ||とか&&は、or/and条件を入れ子にするときに使う。
  • ベクトルの要素が「全て◯◯という値である」という条件を書くシンプルな方法はたぶんない?最大と最小が一致するみたいな書き方をいつもしているのだが。
  • データを「中心化」(平均ゼロにする)したいときは、center()じゃなくて、scale()関数のオプションでscale=Fに設定する。変な話だが。
  • apply()の第一引数は小文字のxではなく大文字のX。まあ、書かなければいいともいえるが、書くときによく間違える。
  • logicalのベクトルでTRUEの個数を数えるには、length(x[x==TRUE])とかsum(x==TRUE)で数えられる。
  • 欠損値のない行にしぼりたいときは、d[complete.cases(d),]でOK。
  • plotで散布図の記号を文字(国の名前とか人の名前とか)にしたいなら、散布図の方は色を"white"にして消して、text(x, y, labels=)とすればよい。
  • 離散変数の値ごとのヒストグラムを得たいとき、hist(x)としてしまうと、離散値に対応するバーが目盛りの左側に来てしまう。これを回避するには、barplot(table(x))とすればよい。
  • 回帰係数の信頼区間の出し方をいつも忘れるが、confint(model, level=0.95)
  • 主成分分析の結果は符号が想定とは逆になってることがあり、必要ならマイナスをかけて戻すのを忘れないように
  • applyでFUNに第二引数以降をわたしたいときは、FUNの後ろにそのまま続けていけばよい。
d <- data.frame(
  A = c(1,2,3,4,5),
  B = c(2,4,NA,8,10),
  C = c(1,3,5,7,9)
  )
apply(d, MARGIN=1, FUN=mean, na.rm=T)
  • dplyrでパイプライン%>%を使うとき、関数定義から埋め込みたい場合は、%>% (functon(p){hoge}) %>%と、()でかこむ。
  • 意外と検索しても見つからないのだが、ggplot2で凡例が小さすぎて点線とか意味わからないのは、theme(legend.key.width=unit(2,"cm"))として解決。
  • dplyrのmutateで、複数の列に一気に同じ処理をしたいときは、mutate_at(var(hoge), funs(f(.)))とし、hogeのところにはstart_withとかが使えるし、ある変数以外を指定したいなら-をつければよい。
  • たまにRでエクセルのファイルを読みたい時は、今なら、gdataパッケージがいいのではないかと思う。
library(gdata)
df <- read.xls('ファイル名')
  • dplyrのなかで差分を取りたいときは、diffじゃなくてlagをつかって、mutate(x = x - lag(x))とする。
  • 正規表現でエスケープするとき、バックスラッシュ\は2回重ねないといけない。
  • dplyrで、少なくともselect、lead、lagは毎回"dplyr::"つけたほうがいい。
  • 空のデータフレームをつくるとき、matrixをas.data.frame()するのがよいが、これに1行目のデータを与えるときにrbindをつかってしまうと列名称が変わってしまうので、インデックスを指定して与えるようにする必要がある。
as.data.frame(matrix(c(NA,NA),nrow=1))
  • read.csv()するとき、stringsAsFactors = Fオプションをとにかく付けるように習慣づけないと、おもわぬエラーが起きる。
  • データフレームにscale()を適用すると、各列を標準化してくれるが、返り値がmatrixになってしまうので、たとえばlm関数のdata欄に直接与えることができない。すこしめんどうだが、numeric型の列を抽出し標準化してデータフレームに直す必要があるので、以下のような感じになる。

lm(y~x, data=d %>% select_if(is.numeric) %>% scale() %>% as.data.frame())

  • RStudioでggplot2を使っている時に、
Error in grid.Call(C_convert, x, as.integer(whatfrom), as.integer(whatto),  : 
  Viewport has zero dimension(s)

というようなエラーがでるときがあるが、コードが間違っているのではなく、plotが出るペインの掃除ボタン(ほうきのマーク)を押すと解消したりする。

  • dplyrでcomplete.cases()をやりたいときは、filter(complete.cases(.))とする。
  • AとBに挟まれた部分を取る正規表現
"(?<=A)(.*)(?=B)"
  • warningを消したい時、単にinvisible()をかければいいのではなくて、以下のようにcapture.output()を入れ子にする。
invisible(capture.output())
  • ggplot2で"Error in .Call.graphics(C_palette2, .Call(C_palette2, NULL)) : invalid graphics state"などよくわからないエラーがでたときは、とりあえずRStudioのplot欄を掃除する(clear all plots)
  • 特定のオブジェクトだけ残して自作のオブジェクトを全部消したいときは、
rm(list=ls()[which(ls() != 'hogehoge')])
  • forループの進捗を表示させたいとき、以下のような感じにすればよい。(1000周ごとに表示)
 if(i %% 1000 == 0) {message('\r', paste(i, ' / ', length(xxx)), appendLF=FALSE)}
  • glmでたとえばロジスティック回帰をやったとき、予測値は0,1ではなく0-1の少数の形で以下のコードで得られる。typeのところはデフォルトでは'link'になるが、これは線形予測子を得るもの。type='response'にしておくと、リンク関数を経たものが得られる。
predict(model, type='response')
predict(model, type='response')
  • lmとかglmオブジェクトから、「投入したデータフレーム」は$modelで取り出せる。stepしている場合は、最終的に選択されたモデルのデータフレームが取り出せる。で、ポイントとしては、目的変数が必ず1列目に来るようになっていることと、NAはオミットされてcomplete.casesになっているということ。predict関数を使うときに(newdataに与えるので)覚えておくと便利。
  • 重み付き最小二乗法をやるときはlm(weights=)という引数をつけるが、predictで予測値を出すときは、predictの中にweightsとか書かなくてもよい。lmオブジェクトの$model内の最後に(weights)っていう列ができててそれが勝手に考慮される。predict内にweightsって書いても無視されてるっぽい(変な値を与えても影響がない)。
  • 自作関数の引数でデータフレームの列を指定したい場合に、関数内でデータフレームをattach()して以下のようにすればよいという意見をツイッターでみた。私は、変数名はdata[,'var']と文字列型で指定するようにしているが。
get_hist <- function(data, var) {
  attach(data)
  hist(var)
  detach(data)
}
  • 文字列のベクトルを連結して1つの文字列にしたいときは、sep=ではなくcollapse = を使う。そうしないと、ベクトルを与えると要素を別々に扱ってベクトルを返そうとする。
  • 行列やベクトルの掛け算をするとき、*を使うと、線形代数的な積ではなく、要素同士の掛け算になる。挙動は以下のとおり。
> # 2×6の行列の場合
> mt1 <- matrix(c(1,3,5,6,2,3), ncol=3, byrow=F)
> vec1 <- c(10,100)
> vec2 <- c(10,100,1000)
> 
> mt1*vec1     # vec1の次元がmt1の行数と一致するので各行をvec2の要素倍する
     [,1] [,2] [,3]
[1,]   10   50   20
[2,]  300  600  300
> mt1*vec2     # vec2の次元がmt1の列数と一致するので各列をvec2の要素倍する
     [,1] [,2] [,3]
[1,]   10 5000  200
[2,]  300   60 3000
> mt1 %*% vec2 # vec2は縦ベクトルとして扱われ、積を計算する
     [,1]
[1,] 2510
[2,] 3630
> mt1 %*% vec1 # vec1の次元がmt1の列数に一致しないので計算できない
Error in mt1 %*% vec1 : non-conformable arguments
> 
> # 3×3の正方行列の場合
> mt2 <- matrix(c(1,3,5,6,2,3,7,7,8), ncol=3, byrow=F)
> mt2*vec2     # 行数とも列数とも一致するが基本的に縦扱いなので、各行をvec2の要素倍する
     [,1] [,2] [,3]
[1,]   10   60   70
[2,]  300  200  700
[3,] 5000 3000 8000
> mt2*t(vec2)  # 転置しても各列を要素倍するようになってくれるわけではなくエラーに
Error in mt2 * t(vec2) : non-conformable arrays
> t(t(mt2)*vec2)  # こうすると各列を要素倍してくれたことになる
     [,1] [,2] [,3]
[1,]   10  600 7000
[2,]   30  200 7000
[3,]   50  300 8000
> 
> # 行列同士をかける場合
> mt3 <- matrix(c(1,3,5,6), ncol=2, byrow=F)
> mt4 <- matrix(c(3,7,7,8), ncol=2, byrow=F)
> 
> mt3*mt4   # これは各要素同士の積
     [,1] [,2]
[1,]    3   35
[2,]   21   48
> mt3%*%mt4  # これは行列の積
     [,1] [,2]
[1,]   38   47
[2,]   51   69
  • 「Error: vector memory exhausted (limit reached?)」というメモリが足りないエラーは、RStudioの設定が問題になっている可能性がある(参考リンク)(参考ついったー
  • たくさんのデータフレームをlistでつないであって、それを全部一気にrbindしたいときの方法がこのページに載っていて参考になる。以下のどちらの書き方でも速い。
single.df <- bind_rows(list.of.dfs)  # これはdplyrの関数
single.df <- do.call("rbind", list.of.dfs)
  • 1列しかないデータフレームについて、行番号で抽出を行うと、単なるベクトルになってしまう。
> d <- data.frame(Term=c('春','夏','秋','冬'))
> class(d)
[1] "data.frame"
> class(d[1:2,])
[1] "character"
  • dplyrでの昇順・降順のソートはまじでクソ覚えにくい。昇順ならarrange(col)で、降順の場合はarrange(desc(col))
  • dplyrで「行の合計」に基づいてフィルターしたい場合はrowSums(.)を使えば良い。
df <- data.frame(x1=c(1,2,3),x2=c(2,3,4),x3=c(1,2,4))
df %>% filter(rowSums(.)>=7)
df %>% filter(rowSums(.)==7)
  • dplyrのgroup_byで複数のカラムを設定し、summariseすると、以下のよう謎のメッセージが出るのだが、あまり気にしなくていいようだ。summariseすると「最後に設定したカラムでのグループ化」が解除されるという仕様になっている(参考1参考2)。この警告自体は、意図どおりに2つのカラムでグループ化&サマライズできなかったという意味ではなく、グループ化が1個解除されてこうなりましたという意味かな。
`summarise()` has grouped output by 'xxxxx'. You can override using the `.groups` argument.
  • 行列の列を-インデックスで除いた場合、除いた結果が1列になる場合は行列型ではなく単なるベクトルになり、結果が0列になる(全部消える)が場合は行列のままとなる。前者に注意が必要で、1列になることもある処理があるときに、行列に対するコードを適用してるとエラーになる。
y1 <- cbind(c(1,2,3,4,5), c(6,7,8,9,10))
y2 <- y1[,-2]
class(y2)
y3 <- y1[,-c(1,2)]
class(y3)
  • リストの要素にインデックスでアクセスするときはlist1iとやるわけだが、ここにベクトルを与えると、複数の要素を取ってくるのではなく、再帰的なアクセスになる。どういうことかというと、list1c(1,2)となっている場合、list1の1つ目と2つ目の要素を取るのではなく、「1つ目の要素の中の2つ目の要素」を取りに行く。仮にlist11が行列だった場合、2個目の値ということで、その行列の1列目の2行目の値を取りに行く。list1の1つ目の要素と2つ目の要素を取りたい場合は、[1:2]と一重カッコにする(ややこしい!)。そもそも、リストの要素にアクセスするときにiとしているのは、Rのリストは中身の要素がリスト型でくるまれている(だからc()で追加するときに追加する要素をlist()する)からで、[i]だと返り値はリストになる。list1[1][1]とかにしてもムダで、list1[1]とlist[1][1][1][1][1][1]は同じ結果になる。list11とすることにより、アクセスできる。
> list1 <- list(matrix(c(1,2,3,4), nrow=2, byrow=T), 
+               matrix(c(5,6,7,8), nrow=2, byrow=T))
> list1[[1]]
     [,1] [,2]
[1,]    1    2
[2,]    3    4
> list1[[2]]
     [,1] [,2]
[1,]    5    6
[2,]    7    8
> list1[[c(1,2)]]
[1] 3
> list1[[1:2]]
[1] 3
> list1[1:2]
[[1]]
     [,1] [,2]
[1,]    1    2
[2,]    3    4

[[2]]
     [,1] [,2]
[1,]    5    6
[2,]    7    8
  • 0を0で割るとNaNになり、0以外の数字を0で割るとInfか-Infになる。これを忘れてると、たとえば「0で割る」ことになってしまうことがあり得ることを想定して、該当箇所を何かでreplaceするという処理をするときに、NaNとInfを両方探さないといけないのを忘れることになる。
>   0/0
[1] NaN
>   1/0
[1] Inf
>   -1/0
[1] -Inf
  • csvの文字コードを変換したいとき、read.csvで読み込む際はファイルに合わせてfileEncoding='CP932'とか指定し、write.csvするときにエンコーディングを何も指定せずにやると、綺麗にUTF-8に変換される。
  • Rの正規表現でURLを抽出する方法は以下のとおり。perlをTRUEにするのを忘れないように。
grep('https?://[\\w!\\?/\\+\\-_~=;\\.,\\*&@#\\$%\\(\\)\'\\[\\]]+', x, perl=TRUE)
  • 正規表現で「AAAを含み、かつBBBを含み、かつCCCを含む」(AND条件)を表現する方法は以下の通り。肯定先読みを使う。
grep('^(?=.*AAA)(?=.*BBB)(?=.*CCC).*$', text_all, perl = T)
  • 正規表現で「AAAを含み、かつBBBを含まない」は否定先読みを使う。
grep('^(?=.*AAA)(?!=.*BBB).*$', text_all, perl = T)
  • whichをつかって「◯◯であるものを除く」という絞り込みをするとき、whichでヒットするものが1つもない場合、全部の要素が残ってくれるのではなく、全部消えてしまう。なので、1つもない可能性が少しでもあるなら、「◯◯でないものを残す」ように書かないとダメ。
> x <- c('あ','い','う')
> x[which(x != '')]
[1] "あ" "い" "う"
> x[-which(x == '')]
character(0)
  • オブジェクトを保存するときはsaveRDS()とreadRDS()を使う。