Rにおけるオブジェクトとその要素へのアクセス

特殊講義「インターネットを活用した経済データの分析」講義資料

2/16/2017

1 はじめに

Rでの作業はエクセルのようなソフトウェアとは異なり,「アイコンのクリック」等ではなく,Rのコンソールへのコマンド入力によって行なう.一見敷居が高いように思えるが,一旦慣れてしまえば,毎回キーボードから手を離し「ポチポチ」しなくてはならないエクセルのようなソフトウェアよりも作業が大幅に楽になる.Rのコンソールに逐一実行したい作業を記述したコードを入力あるいはコピーしてもよいし,(ファイル名).Rとして保存したRのソースコードを実行させてもよい. 以下では,自然言語や他の人工 (プログラミング) 言語と比較したR言語の注意点を簡単に整理した上で,Rを用いた統計処理の基礎となる,データ (オブジェクト) の扱い方を解説する.

2 R言語の注意点

Rは統計処理用の人工言語の1つだが,他の言語と同様に独自の特徴もある.R言語を用いて解析を行なう上では,特に次の天に注意する必要がある.

2.1 大文字と小文字,全角文字と半角文字は「異なるもの」

Rでは,大文字と小文字,全角文字と半角文字は「4つの異なる文字」として扱われる. たとえば,小文字 a と 大文字Aは異なり,同様に全角文字と半角文字Aは異なる文字として,Rは認識する.

後述のように,Rで統計処理を行なう上では,読み込んだデータや解析結果を「名前をつけた入れ物」([R]オブジェクト) に保存することが頻繁に必要になる. たとえば,1+1の結果をaに,2+2の結果をAというオブジェクトに格納したとしよう. この計算は,次のコードで実行できる.

a = 1 + 1
A = 2 + 2

正しく計算されていれば,aには2が,Aには4が格納されている.コンソールに aA を入力し,正しく実行されているか確かめてみる.

a
## [1] 2
A
## [1] 4

このように,R言語は小文字と大文字を「異なる文字」として区別して扱う.この点は,他のプログラミング言語の中でも目立つR言語の特徴である.たとえば,Java言語であれば大文字と小文字は区別されない.

なお,半角文字と全角文字も同様に異なる文字として認識される.現在のバージョンのRでは,次のように全角文字,たとえば漢字・平仮名も扱える.

一足す一 = 1 + 1
二の二乗 = 2^2

各自,コンソールに「一足す一」と「二の二乗」を入力して確かめてみよう.

このように,日本語をRで扱うことは「できるにはできる」が,Rでコードを書く上では日本語の使用を避けることが望ましい.理由は次の点にある.

こうした問題が生じることに加えて,そもそも日本語を含むRコードを見ることに苦痛を覚えるRユーザも多く存在する.特に本講義ではRコードに日本語を絶対含めないよう注意してほしい.

2.2 改行とカッコ

Rでは,1つの命令と別の命令は,原則として改行で区別される.上記の例でも,「1+1aに格納する」という命令と「2+2Aに格納する」という命令は,改行で区切られている. ただし,関数を用いる場合など,カッコでくくられた内部で改行を加える場合はその限りではない.たとえば,次の場合のように,(に対応する)までが1つの命令として認識される.

data(iris)
summary(
    iris 
) ## 関数なので,()で括られた部分が1つの命令として認識されている
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
## 

2.3 「1から始まる」

Rの言語の特徴に「1から始まる」点がある.たとえば,10の長さをもつ次のベクトル型のRオブジェクトで考える.

(vec = seq(from=11, to=20))
##  [1] 11 12 13 14 15 16 17 18 19 20

このベクトルの最初の要素を取り出したい場合,Rではvec[1]と入力する.ついでに最後の要素もみてみる

vec[1]
## [1] 11
vec[10]
## [1] 20

一見「当たり前」と思えるかもしれないが,他のプログラミング言語では「0から始まる」ことが一般的である.他の言語を知っていると,このR言語の特徴は意図しないミスの原因になり得るので注意が必要になる.「気持ち悪さ」は仕様なので我慢しよう.

3 Rのオブジェクト

Rにおいて「オブジェクト」といった場合,「何かしらの変数や関数等を保持する,名前をつけた入れ物」と理解していればよい.たとえば,上の例で作成したaも立派なオブジェクトである.なお,aのような単一の値を保持するオブジェクトは変数と呼ぶ.

Rでは,変数に加えていくつかの主要なオブジェクトを頻繁に用いる.本講義では,差し当たり次のオブジェクトを理解してもらいたい.

3.1 ベクトル

ベクトルを作成する上では,c()関数を多用する.“c”はcombinationの頭文字の意味で,c()関数は引数に与えた値からなるベクトルを生成する.なお,等差数列や循環数列を作成するseq()関数やrep()関数もあるので,各自Googleに聞いてみること (ちなみに,これらの関数の引数は文字列でもよい). c()関数は「実数でも文字列でも,どんな型の要素からなるベクトルでも作れる」非常に便利な関数である.

(vec_int <- c(1, 2, 3)) ## 整数
## [1] 1 2 3
(vec_dbl <- c(.1, .2, .543)) ## 実数
## [1] 0.100 0.200 0.543
(vec_chr <- c("a", "b", "c")) ## 文字列
## [1] "a" "b" "c"
(vec_lgl <- c(TRUE, FALSE, TRUE)) ## 論理値
## [1]  TRUE FALSE  TRUE
(vec_cmb <- c(seq(3,9), rep(1, 8), seq(4,10))) ## c(), seq(), rep()関数を組み合わせる
##  [1]  3  4  5  6  7  8  9  1  1  1  1  1  1  1  1  4  5  6  7  8  9 10

このように,要素の「型」が異なるベクトルを簡単に生成できる. なお,上の例で命令を出している各行を()で括っているのは,「命令を実行するだけでなく,その結果も見せること」というメタな命令をRに出すためで,各自実行するときには省略してもよい.

こうした数列・ベクトル生成用の関数についての解説はウェブ上にも転がっている.たとえば,このサイトを参照.

3.2 行列

行列オブジェクトは,複数のベクトルを束ねたものといえる. 上で作ったベクトルを束ねて,行列オブジェクトを作るには次のようにすればよい.

(mat = cbind(vec_int, vec_dbl))
##      vec_int vec_dbl
## [1,]       1   0.100
## [2,]       2   0.200
## [3,]       3   0.543
(mat = rbind(vec_int, vec_dbl))
##         [,1] [,2]  [,3]
## vec_int  1.0  2.0 3.000
## vec_dbl  0.1  0.2 0.543

ベクトルを束ねるために使ったcbind()関数とrbind()関数は,それぞれ引数として与えられたベクトルを「列」として束ねる関数と,「行」として束ねる関数である.

なお,既存のベクトルからではなく,0から行列を作るにはmatrix()関数を用いる

(mat1 <- matrix(1:9, nrow = 3, byrow = TRUE))
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
## [3,]    7    8    9
(mat2 <- matrix(1:9, nrow = 3, byrow = FALSE))
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    2    5    8
## [3,]    3    6    9

なお,上の2行のコードはbyrowという引数に与える値を除いて等しいが,生成される行列の「要素の並び方」に違いがある. mat1のようにbyrow=TRUEとすると行 (row, 横方向) 単位でセル (要素) を埋めていくが,mat2のようにbyrow=FALSEとすると列 (column, 縦方向) 単位で埋めるという指定になる.その結果,生成される行列の中身が異なっている.出力結果からも直感的にわかる通り,1:9という引数は行列の要素を指定している.

3.3 データフレーム

さて,上記の通り,データフレームは行列オブジェクトよりもフレキシブルなデータの扱いを可能にしてくれる. そもそも行列だけではフレキシブルな扱いができないことを実感するために,次のコードを試してみる.

(mat3 = cbind(vec_int, vec_dbl, vec_chr, vec_lgl))
##      vec_int vec_dbl vec_chr vec_lgl
## [1,] "1"     "0.1"   "a"     "TRUE" 
## [2,] "2"     "0.2"   "b"     "FALSE"
## [3,] "3"     "0.543" "c"     "TRUE"

なにやら,二重引用符で全ての要素がくくられてしまっている.vec_chrの中身からもわかる通り,これは全ての要素が文字列型として扱われている (変換されてしまった) ことを示す. 行列オブジェクトではこのような型の変換・統一が強制的に行われてしまい,たとえば1列目は実数 (e.g., 身長),2列目は文字列 (e.g., 出身地), 3列目は整数 (e.g., 年齢) といったデータを保持することはできない.もっとも,本来文字列の変数を数値に置き換えてやれば行列オブジェクトでも問題なく扱えるが,やや手間がかかり,人間が見て分かりにくくなってしまう (数値に置き換えて行列オブジェクトで扱うことが必要な場合もあるが,今は分かりやすさを優先する).

データフレームでは,この問題が解決できる.データフレームを作成するには,data.frame()関数を用いる.

(df = data.frame(vec_int, vec_dbl, vec_chr, vec_lgl, stringsAsFactors = FALSE))
##   vec_int vec_dbl vec_chr vec_lgl
## 1       1   0.100       a    TRUE
## 2       2   0.200       b   FALSE
## 3       3   0.543       c    TRUE
str(df)
## 'data.frame':    3 obs. of  4 variables:
##  $ vec_int: num  1 2 3
##  $ vec_dbl: num  0.1 0.2 0.543
##  $ vec_chr: chr  "a" "b" "c"
##  $ vec_lgl: logi  TRUE FALSE TRUE

str()関数は,引数として与えられたオブジェクトの型を返す.出力からわかる通り,1列目と2列目は数値num,3列目は文字列chr,4列目は論理値logiとして扱われている.

用いる関数にもよるが,.csvその他のファイルをRに読みむと,多くの場合データフレームやその類似オブジェクトとして読み込まれる.

3.4 リスト

リストオブジェクトは,Rのオブジェクトを「なんでも」格納できる便利なオブジェクトで重宝する. リストの中身はなんでもよく,たとえばある単一のリストの中に複数のデータフレームオブジェクトを格納することもできれば,データフレームオブジェクトとベクトルオブジェクトを混在させることもできる.

たとえば,上で作成したデータフレームオブジェクトdfを1番目の要素に,ベクトルオブジェクトvec_cmbを2番目の要素にもつリストオブジェクトは,list()関数を用いて次のように作成できる.

(list_sample = list(df, vec_cmb))
## [[1]]
##   vec_int vec_dbl vec_chr vec_lgl
## 1       1   0.100       a    TRUE
## 2       2   0.200       b   FALSE
## 3       3   0.543       c    TRUE
## 
## [[2]]
##  [1]  3  4  5  6  7  8  9  1  1  1  1  1  1  1  1  4  5  6  7  8  9 10

4 オブジェクトへのアクセス

4.1 各要素へのアクセス

上記のように作成したオブジェクトの各要素には,「要素の位置」を特定することでアクセスできる.たとえば,ベクトルであれば一次元配列なので1つの値で,行列やデータフレームであれば二次元配列なので2つの値で「要素の位置」を特定できる.

まず,復習も兼ねてvec_chrベクトルの最初の要素を取り出す

vec_chr[1]
## [1] "a"

2番目と3番目の要素を一度に取り出すには,次のように「要素の位置」自体をベクトルとして与えてやる.

vec_chr[c(2,3)]
## [1] "b" "c"

リストオブジェクトの場合は,[]ではなく二重の[[]]の中に要素の位置を指定する.

list_sample[[1]]
##   vec_int vec_dbl vec_chr vec_lgl
## 1       1   0.100       a    TRUE
## 2       2   0.200       b   FALSE
## 3       3   0.543       c    TRUE
list_sample[[2]]
##  [1]  3  4  5  6  7  8  9  1  1  1  1  1  1  1  1  4  5  6  7  8  9 10

行列やデータフレームの特定の要素を取り出すには,(1行目,1列目)のように,「行」・「列」の順番で「要素の位置」を指定する.たとえば,上で作成したdf (データフレーム) の特定の要素を取り出すには,次のようにすればよい.

df[1,1]; df[3,1]; df[3,2]
## [1] 1
## [1] 3
## [1] 0.543

予想通りの値が出力されているか各自確認すること.なお,;で3つの命令をつなぐことで,一度に3つの命令を実行させている (;の代わりに改行を挟んでもよい).

なお,「特定の列」や「特定の行」を取り出すには,いずれかの位置だけを指定する.なお,取り出された列あるいは行はいずれも,ベクトルとして扱われる.

df[1,] ## dfの1行目を取り出す
##   vec_int vec_dbl vec_chr vec_lgl
## 1       1     0.1       a    TRUE
df[,2] ## dfの2列目を取り出す
## [1] 0.100 0.200 0.543

また,「列」を取り出すには,列の名前を用いることもできる.例として用いているdfの列名は,vec_int, vec_dbl, vec_chr, vec_lglである.dfからvec_lgl列を取り出すには,[]ではなく$を用いる.$の左にデータフレーム名を,$の右に列名を与える.

df$vec_lgl
## [1]  TRUE FALSE  TRUE

vec_lgl変数はdfオブジェクトの4列目に位置しているので,df[,4]としても,df$vec_lglとまったく同じ結果が得られる.これは,各自確認しておくこと.

4.2 特定の条件を満たすデータを取り出す

さて,特にデータフレームオブジェクトを扱っている場合,「男性のデータ」「日本のデータ」のように,特定の条件を満たすデータのみを抽出したい場合があるだろう.この操作は,デフォルトのsubset()関数でも可能だが,より洗練されたdplyr::filter()関数を用いることを推奨する.まずは,サンプルデータを読み込む.

library(tidyverse)
data(iris)
(iris = tbl_df(iris))
## # A tibble: 150 × 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
## 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
## 7           4.6         3.4          1.4         0.3  setosa
## 8           5.0         3.4          1.5         0.2  setosa
## 9           4.4         2.9          1.4         0.2  setosa
## 10          4.9         3.1          1.5         0.1  setosa
## # ... with 140 more rows

Species変数からわかる通り,このデータセットにはsetosa, versicolor, virginicaの3種類の花の種類が含まれている. このデータセットから,setosaという種類のデータのみを抽出するには,次のようにすればよい.

(dat_setosa = iris %>% filter(Species == "setosa")) ## 書き方1
## # A tibble: 50 × 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
## 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
## 7           4.6         3.4          1.4         0.3  setosa
## 8           5.0         3.4          1.5         0.2  setosa
## 9           4.4         2.9          1.4         0.2  setosa
## 10          4.9         3.1          1.5         0.1  setosa
## # ... with 40 more rows
(dat_setosa = filter(iris, Species == "setosa")) ## 書き方2
## # A tibble: 50 × 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
## 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
## 7           4.6         3.4          1.4         0.3  setosa
## 8           5.0         3.4          1.5         0.2  setosa
## 9           4.4         2.9          1.4         0.2  setosa
## 10          4.9         3.1          1.5         0.1  setosa
## # ... with 40 more rows

setosaまたはversicolorを抽出するには,

(dat_setosa = iris %>% filter(Species == "setosa" | Species == "versicolor"))
## # A tibble: 100 × 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
## 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
## 7           4.6         3.4          1.4         0.3  setosa
## 8           5.0         3.4          1.5         0.2  setosa
## 9           4.4         2.9          1.4         0.2  setosa
## 10          4.9         3.1          1.5         0.1  setosa
## # ... with 90 more rows

のように,条件を|でつなぐ (or条件という).

これとは反対に,「かつ」という複数条件を設定したい場合は,,で条件をつなぐ (and条件という). たとえば,setosaかつSepal.Lengthが5以下のデータを抽出するには,次のコードを実行する.

(dat_setosa = iris %>% filter(Species == "setosa" , Sepal.Length <= 5))
## # A tibble: 28 × 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
## 1           4.9         3.0          1.4         0.2  setosa
## 2           4.7         3.2          1.3         0.2  setosa
## 3           4.6         3.1          1.5         0.2  setosa
## 4           5.0         3.6          1.4         0.2  setosa
## 5           4.6         3.4          1.4         0.3  setosa
## 6           5.0         3.4          1.5         0.2  setosa
## 7           4.4         2.9          1.4         0.2  setosa
## 8           4.9         3.1          1.5         0.1  setosa
## 9           4.8         3.4          1.6         0.2  setosa
## 10          4.8         3.0          1.4         0.1  setosa
## # ... with 18 more rows

ここでは条件が2つだけの場合を紹介したが,3以上の複数条件も同様に扱える.

4.3 dplyrパッケージ

なお,filter()関数を提供するdplyrパッケージは,データの操作・集計・分析をする上で非常に強力なツールになる.本講義でも,データ操作の際に適宜解説しながらdplyrパッケージを多用する.dplyrについては,「dplyr 使い方」とGoogle先生に聞けば良記事が多数出てくる.日本語での解説 (のまとめ) として,たとえば下記の記事があるので各自自習することを強く勧める (Rデフォルトのオブジェクトの要素へのアクセスが「気持ち悪い」人は,最初からdplyrを使ってしまえばいい).