フロミンドローム

自由気ままに勉強します。

【R】クラスとデータ構造

Rで扱うデータの構造についてまとめます。

クラス(class)

クラス(class)の種類

Rで扱う値にはクラス(class)が存在します。クラスとは、データのタイプを意味しています。
データのクラスは class() で調べることができます。

これは裏パラメタのようなもので、コンソール画面では表示されません。

しかし、この違いを理解していないと、実際のデータ処理でエラーを連発することになります。
なぜなら、関数にインプットされる値には、関数ごとに特定のクラスが想定されているからです。
例えば、文字列である「こんにちは」の平均を求めることは不可能です。
実際に、平均値を求める mean() では、以下に記述する数値型(numeric)のデータが想定されています。

それでは、クラスの一覧とルールを説明します。

  1. 論理型(logical)
  2. 整数型(integer)  *数値型(numeric)
  3. 小数点型(double) *数値型(numeric)
  4. 複素数型(complex)
  5. 文字型(character)

上から順に優先度が高く、下から順にデータの柔軟性が高い。
なお、整数型(integer)と小数点型(double)はRのクラス名表記では同じ数値型(numeric)として扱われる。

*優先度
クラスを指定しなかった場合に、上から順番に当てはまるかを確かめ、最初に当てはまったクラスが値に適用される。例えば「5.0」は整数型(integer)の「5」として扱われるが、「5.1」は小数点型(double)の「5.1」として扱われる。一方で、「"こんにちは"」などは文字列(character)として扱うしかない。

*柔軟性
クラスは下に行くほど当てはめられる値の範囲が広い。例えば、文字列(character)は「"こんにちは"」だけでなく、「5.0」を「"5.0"」の文字列として扱うこともできる。なお、文字型の特徴として、値は " " に囲まれている。

> a <-  TRUE
> class(a)
[1] "logical"
> b <- 5.0
> class(b)
[1] "numeric"
> c <- 5.1
> class(c)
[1] "numeric"
> d <- 1i
> class(d)
[1] "complex"
> e <- "こんにちは"
> class(e)
[1] "character"
> f <- "5.1"
> class(f)
[1] "character"

クラスの変換

クラスの変換は、as.クラス() の名前の関数によって行うことができます。
ただし、 "こんにちは" のような文字列を数値(numeric)にすることはできません。

> g <- 3.14
> class(g)
[1] "numeric"
> h <- as.character(g)
> h
[1] "3.14"
> g <- 3.14
> #クラスがnumericであることを確認
> class(g)
[1] "numeric"
> #文字型に変換する。
> h <- as.character(g)
> #文字型の証拠である""が確認できる。
> h
[1] "3.14"
> #もう一度、数値型に戻す。
> i <- as.numeric(h)
> i
[1] 3.14

> #文字列は数値型に変換できない。
> j <- "こんにちは"
> as.numeric(j)
[1] NA
Warning message:
NAs introduced by coercion 

論理型(logical)である TRUE/FALSE は数値型としては、 1/0 として扱われます。
文字列として変換する場合にはそのまま "TRUE" "FALSE" として扱われます。

> k <- TRUE
> l <- FALSE
> as.numeric(k)
[1] 1
> as.numeric(l)
[1] 0
> as.character(k)
[1] "TRUE"
> as.character(l)
[1] "FALSE"

因子型(factor)について

ここまで触れてきませんでしたが、 因子型(factor) というクラスが存在します。
これは、通常の代入において適用されるクラスではありません。

> g <- 3.14
> k <- TRUE
> as.factor(g)
[1] 3.14
Levels: 3.14
> as.factor(k)
[1] TRUE
Levels: TRUE

因子型(factor)は、主にデータ処理においてカテゴリカルなデータを扱う際に用いるクラスです。
入力値を文字列のように計算不可能なものとして扱います。
見た目の上では 文字型(character)と似ています。

ただし、因子型(factor)には水準(levels)というパラメタが存在します。
値を単に文字列として扱うのではなく、識別するためのパラメタです。

例えば、「そう思う」「少しそう思う」「どちらでもない」「あまりそう思わない」「そう思わない」という5件法のアンケートを行った結果、得られた順に、「そう思う」が2件、それ以外が1件ずつの回答が得られたとします(計6件)。
これをベクトルデータとして保存して、文字型(character)と因子型(factor)それぞれで扱った際の出力を確認します。

すると、因子型(factor)のみ水準のパラメタが含まれていることがわかります。

> x <- c("そう思う","そう思う","少しそう思う","どちらでもない","あまりそう思わない","そう思わない")
> x
[1] "そう思う"           "そう思う"           "少しそう思う"       "どちらでもない"     "あまりそう思わない"
[6] "そう思わない"      
> as.character(x)
[1] "そう思う"           "そう思う"           "少しそう思う"       "どちらでもない"     "あまりそう思わない"
[6] "そう思わない"      
> as.factor(x)
[1] そう思う           そう思う           少しそう思う       どちらでもない     あまりそう思わない そう思わない      
Levels: あまりそう思わない そう思う そう思わない どちらでもない 少しそう思う

また、factor(x, levels, ordered) の関数とオプションによって、水準に順序を与えることができます。
これによって、順序性のある尺度の分析が行いやすくなります。
順序のパラメタがないと、例えば今後の記事で触れる latticeパッケージによるの histogram() で因子ごとの度数表を作成したい時などに、順序がバラバラになってしまって見栄えが悪くなります。

> x2 <- factor(x,levels=c("そう思わない","あまりそう思わない","どちらでもない","少しそう思う","そう思う"),ordered = TRUE)
> x2
[1] そう思う           そう思う           少しそう思う       どちらでもない     あまりそう思わない そう思わない      
Levels: そう思わない < あまりそう思わない < どちらでもない < 少しそう思う < そう思う

もちろん、与えるオブジェクトは直入力してもOKです。

> y <- factor(c("そう思う","そう思う","少しそう思う","どちらでもない","あまりそう思わない","そう思わない"), levels=c("そう思わない","あまりそう思わない","どちらでもない","少しそう思う","そう思う"), ordered = TRUE)
> y
[1] そう思う           そう思う           少しそう思う       どちらでもない     あまりそう思わない そう思わない      
Levels: そう思わない < あまりそう思わない < どちらでもない < 少しそう思う < そう思う

データ構造

ここまで基本的に単一の値(value)を変数とすることを前提として説明しました。
しかし、ある値はデータとしてみた時の最小単位です。
ほとんどの場合、データは、1つ以上の値の集合からなります。
そして、Rでは、それらを x などの変数の中に代入して利用します。

この変数の中での値の結合の仕方ががデータ構造です。
ですから、変数の中にある値には、値自体のクラスと、属するデータ群(オブジェクト)としてのパラメタをもっています。

データ構造の種類

Rで扱われるオブジェクト(変数xとして想定されるデータ)には以下の種類とルールがあります。

  • ベクトル(vector) :複合データの最小単位

 ー1次元データを扱う。
 ー構成される値のクラスが異なる場合、柔軟性の高いクラスに合わせて統一される。

  • リスト(list):1つ以上のベクトルの合成

 ー2つ以上のベクトルが結合される際に、それぞれの長さが異なってもよい。

 ー最上段の列に変数名としての役割は想定されていない。
 ーデフォルトでは列名は設定されない。
 ー列の違いは属性の違いとしての意味をもたない。
 ー列を縦結合したり、行を横結合したりする操作が可能。
 ー平均を求めると全データの平均が求められる。

  • データフレーム(data.frame):1つ以上のベクトル・リストを2次元に合成したもの

 ー2つ以上のベクトル・リストが結合される際に、それぞれの長さは同じでなければならない。
 ークラスは列ごとに異なってよい。
 ー基本的に、行は個票、列は属性を表す。
 ―平均を求めると列ごとの平均が求められる。

  • 配列(array):3次元を超える配列データ
  • 度数表(table):list内の値の出現数をカウントしたデータ

 ー単体ではなく、table(list)の形で用いる。

データ構造ごとの用途とR上での表記

値(value)

単なる任意の値です。データ構造ではなく、データの最小単位です。
例) 100

ベクトル(vector)

複数の値を並べたものです。一次元のデータです。イメージは以下の通り。

例)1 3 5 の3つの値をもつベクトル

その他のデータ構造は、分解するとベクトルになります。
逆に言うと、ベクトルを組み合わせ、その他のデータ構造を作成するために用いることができます。

なお、構成される値のクラスは統一されます。その際、当てはまる最も柔軟性の高いクラスが適用されます。

> x <- c(1, 3, 5)
> x
[1] 1 3 5
> y <- c(1, 3, 5, "こんにちは")
> y
[1] "1"          "3"          "5"          "こんにちは"

xでは、いずれも数値型(numeric)ですから、 1 3 5 は数値として評価されます。
yでは、文字型(character)が混在したため、 "1" "3" "5" 即ち文字として評価されています。

リスト(list)

複数のベクトルを結合したものです。ベクトルの長さは異なっていても構いません。イメージは以下です。

例)「1 3 5」のベクトルと「2 4 6 8」のベクトルを結合したリスト

ベクトルはあくまで一次元のデータです。
「1 3 5」「2 4 6 8」両方の値を格納する際、「1 3 5 2 4 6 8」という1つのまとまりとしてしか扱えません。

> x <- c(1, 3, 5)
> x
[1] 1 3 5
> y <- c(2, 4, 6, 8)
> y
[1] 2 4 6 8
> z <- c(x, y)
> z
[1] 1 3 5 2 4 6 8

これに対して、リスト(list)はそれぞれを別個のベクトルとして扱うことができます。
そのため、種類の異なる複数のベクトルを変数に持たせたい際に便利です。

> w <- list(x, y)
> w
[[1]]
[1] 1 3 5

[[2]]
[1] 2 4 6 8
マトリックス(matrix)

値をマトリックス(行列)に配置したものです。配置しただけ、というのがポイントです。
例えば1~6までの値を2×3の行列に配置するとイメージはこうなります。

一見すると、後述するデータフレーム(data.frame)と似ているのですが、混同すると痛い目に合います。
マトリックス(matrix)は値を行列に配置しているだけなので、行や列に意味はありません。
ですから、デフォルトでは列名も存在しません。
値の集合でしかないので、行数と列数を変えて配置を組み直してもよいわけです。
同じく値の集合でしかないので、平均を求めると全ての値の平均を出力します。
このあたりは、データの作成と編集として別途まとめたいと思います。

ともかく、R上での表記を確認します。

> z <-matrix(1:6, nrow = 3, ncol = 2)
> z
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6
データフレーム(data.frame)

1つ以上のベクトル・リストを行列に表したものです。
同じ行列でも、マトリックス(matrix)とは意味合いが変わります。

イメージとしては次のように、行は個票、列は属性を表します。
実務上も、R上もよく見られる形です。

例えばアンケート結果を分析したいとき、上記の行列の意味は無視できないです。
勝手に行数と列数を変えて値を配置し直す、なんてことはあってはならないわけです。
平均を求める際にも、列ごとの平均に意味があるわけです。
そんなわけで、マトリックス(matrix)とは明確に異なる用途があることになります。

R上での表記は以下の通りです。

> x <- c("田中", "佐藤")
> y <- c(31, 28)
> z <- c(168, 177)
> w <- data.frame( "名前" = x, "年齢" = y, "身長" = z)
> w
  名前 年齢 身長
1 田中   31  168
2 佐藤   28  177

見ての通り、列ごとに値のクラスが統一されていれば、列同士のクラスは異なっていても構いません。

配列(array)

少し特殊で、2次元データを並列でいくつも持つことができる結合の仕方です。
例えば次のように、2次元のマトリックスのデータがx,y,zと3つあるとします。

  
x : 1~6を2×3に配置  y : 7~12を2×3に配置  z:13~18を2×3に配置

これらのマトリックスを識別する3次元目の座標をもち、結合するのが配列(array)です。
さらにこれらの3次元データを識別する4時限目の座標をもたせれば、4次元データとなります。

例えば、複数の標本データを1つの変数の中に格納しておくことが可能です。

とりあえずは、上記の x,y ,z を結合した配列(array)のR上での表記を確認します。

> x <- matrix(1:6, nrow = 3, ncol =2)
> y <- matrix(7:12, nrow = 3, ncol =2)
> z <- matrix(13:18, nrow = 3, ncol =2)
> w <- array(c(x,y,z), dim = c(3,2,3))
> w
, , 1

     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6

, , 2

     [,1] [,2]
[1,]    7   10
[2,]    8   11
[3,]    9   12

, , 3

     [,1] [,2]
[1,]   13   16
[2,]   14   17
[3,]   15   18
テーブル(table)

これも少し特殊な形で、単体で成立するのではなく、特定のデータの度数を集計して表現した形です。

アンケートの回答結果を集計したい時などに便利です。
例えば、次のようなアンケート結果があったとします。

これをもとに出身地の度数を集計をしたらこうなります。

この操作を行うのが table() 関数であり、その結果の形式がテーブル(table)構造です。

R上では次のように表記されます。

> x <- data.frame("名前"=c("田中","佐藤","山田"), "出身地" = c("青森", "秋田", "青森"))
> x
  名前 出身地
1 田中   青森
2 佐藤   秋田
3 山田   青森
> y <- table(x$出身地)
> y

秋田 青森 
   1    2 

もちろん、クロス集計を行うこともできます。
このデータだと出力結果は不自然ですが、データの見え方はこのようになります。

> z <- table(x)
> z
      出身地
名前 秋田 青森
  佐藤    1    0
  山田    0    1
  田中    0    1

今回扱っていませんが、tibbleというtidyverseパッケージで登場するdata.frameに類似したデータ構造もあります。
これはまた別途整理していきます。

以上、クラスとデータ構造について今の理解をまとめておきました。
正直、正確な理解という意味では次のようなリンクの方が厳密でしょう。

ベクトル、行列、データフレーム、リスト、配列、テーブルの違い

ただ、ここでは、自分の言葉でかみ砕いて記録しておくことができればよしとしたいと思います。

クラスとデータ構造の違いを理解しておかないと、データを作成したり、操作したりする段階で躓きます。
次回は、ここでの理解をもとに、実際にデータをR上で扱っていきます。