データの構造

概要

講義: 40 分
演習: 15 分
質問
  • Rにデータをどう読み込ませばいいですか?

  • R の基本的なデータタイプにはどのようなものがありますか?

  • R のカテゴリー情報はどのように表現しますか ?

目標
  • 様々なタイプのデータがあることを理解しましょう。

  • データフレームの基本を理解し、そして、データフレームがベクター、ファクターおよびリストとどのように関係しているかを理解しましょう。

  • オブジェクトのタイプ、クラス、および構造の確認を R で行えるようになりましょう。

Rのすごい特徴のひとつは、表形式のデータ(既に手元にあるようなスプレッドシートやCSVファイル)が扱えることです。 まず、 data/ ディレクトリに feline-data.csv というお試しのデータセットを作ってみましょう。

cats <- data.frame(coat = c("calico", "black", "tabby"), 
                    weight = c(2.1, 5.0,3.2), 
                    likes_string = c(1, 0, 1))
write.csv(x = cats, file = "data/feline-data.csv", row.names = FALSE)

新しいファイル feline-data.csv の内容:

coat,weight,likes_string
calico,2.1,1
black,5.0,0
tabby,3.2,1

ヒント:Rを使ったテキスト形式のファイルの編集

あるいは、テキストエディタ(Nano)またはをRStudioのメニューからFile -> New File -> Text Fileを使って data/feline-data.csv を作成することができます。

以下を使って作ったデータをRへ読み込ませることができます:

cats <- read.csv(file = "data/feline-data.csv")
cats
    coat weight likes_string
1 calico    2.1            1
2  black    5.0            0
3  tabby    3.2            1

このread.table関数は、CSVファイル(csv = comma-separated values)のように、 データの列が区読文字で分けられたテキストファイルに収められた表形式データを 読み込むために使われます。 タブとコンマは、csvファイルでデータ点を区切る、又は分けるために使われる 最も一般的な句読文字です。 便宜上、Rでは、他に2つのread.tableのバージョンが提供されています。 ひとつは、データがコンマで分けられているファイルのための read.csv 、 データがタブで分けられているファイルのための read.delim です。 これら3つの関数のうち、read.csv が最も広く使われています。 必要であれば、 read.csvread.delim、両方の デフォルトの句読記号を置き換えることができます。

演算子 $ を使って列を指定し、列を抜き出すことで、すぐにデータセットの探索を始めることができます:

cats$weight
[1] 2.1 5.0 3.2
cats$coat
[1] calico black  tabby 
Levels: black calico tabby

列に他の操作をすることもできます:

## 計測器が実際の重さより2キロ少なく測っていることが分かったとしたら:
cats$weight + 2
[1] 4.1 7.0 5.2
paste("My cat is", cats$coat)
[1] "My cat is calico" "My cat is black"  "My cat is tabby" 

でも、こうしたらどうだろう

cats$weight + cats$coat
Warning in Ops.factor(cats$weight, cats$coat): '+' not meaningful for
factors
[1] NA NA NA

ここで何が起こったかを理解することが、データをRでうまく分析する鍵となります。

データ型

最後のコマンドがエラーを返すのは 2.1 足す "black" はナンセンスだからだろうと思ったとしたら、 それは正解で、既にプログラミングにおけるデータ型という重要な概念をある程度分かっていると言えます。 データ型が何かを知るには、以下を使います:

typeof(cats$weight)
[1] "double"

主な型は5つあります:double(浮動小数点型)integer(整数型)complex(複素数型)logical(論理型)、そしてcharacter(文字型)

typeof(3.14)
[1] "double"
typeof(1L) # Rはデフォルトでは浮動小数を使いますが、Lの接尾辞により、この数は整数型になります
[1] "integer"
typeof(1+1i)
[1] "complex"
typeof(TRUE)
[1] "logical"
typeof('banana')
[1] "character"

どんなに分析が複雑になっても、 Rにある全てのデータは、この基本データ型のいずれかとして解釈されます。 この厳格性によって、とても重要なことが後々起こることもあります。

あるユーザーが他の猫の詳細を加えたとします。 情報は data/feline-data_v2.csv ファイルにあるものです。

file.show("data/feline-data_v2.csv")
coat,weight,likes_string
calico,2.1,1
black,5.0,0
tabby,3.2,1
tabby,2.3 or 2.4,1

先ほどのように、この新しい猫の情報を読み込み、 weight の列が、 どんなデータ型が確認してみましょう。

cats <- read.csv(file="data/feline-data_v2.csv")
typeof(cats$weight)
[1] "integer"

なんと、この weight はdouble型ではないじゃありませんか! 前と同じように計算をしようとすると、 やっかいなことになります:

cats$weight + 2
Warning in Ops.factor(cats$weight, 2): '+' not meaningful for factors
[1] NA NA NA NA

何が起こったのでしょう?Rは、csvファイルを読み込む際、 列にある全てのものが同じ基本の型であるべきだと主張します。もし、列の全てが、 double型であることが確認できない場合、その列のだれもdouble型にならないのです。 Rが読み込んだ猫のデータ表は、data.frame(データフレーム)と呼ばれるもので、 data structure(データ構造)、つまりRが基本的なデータ型から構築できるデータの最初の例です。 どう作成すればよいか知っているものでした。

data.frameであることを確かめるには、class関数を使います:

class(cats)
[1] "data.frame"

データをうまくRで使うためには、基礎的なデータ構造、そしてその構造がどう作用するか を理解する必要があります。より理解を深めるために、とりあえず猫のデータから 追加した1行を削除し、再読み込みしましょう。

feline-data.csv:

coat,weight,likes_string
calico,2.1,1
black,5.0,0
tabby,3.2,1

RStudioに戻ります:

cats <- read.csv(file="data/feline-data.csv")

ベクトル及び型強制

この動作をより理解するために、もう一つのデータ構造ベクトルを紹介します。

my_vector <- vector(length = 3)
my_vector
[1] FALSE FALSE FALSE

Rのベクトルは、本来、ベクトルの中の全てのものは同じ基本データ型でなければいけないという特別な条件のある、 順番を付けられたもののリストです。 もし、データ型を選ばなければ、デフォルトでlogicalになりますが、好きなデータ型を持つ空のベクトルを 宣言することもできます。

another_vector <- vector(mode='character', length=3)
another_vector
[1] "" "" ""

以下を使えばベクトルかどうかを確かめられます:

str(another_vector)
 chr [1:3] "" "" ""

このコマンドから出てきた暗号みたいなアウトプットによると、このベクトルの基本データ型(ここでは chr (文字型))、 数(実際には、ベクトルの添字、この場合 [1:3] )、 そして中身のいくつかの例示(この場合、空の文字列)が示されています。 cats$weightに同じようなことをすると:

str(cats$weight)
 num [1:3] 2.1 5 3.2

ここでcats$weight もまたベクトルであることが分かります。 Rのデータフレームに読み込まれたデータの列は全てベクトルで、 Rが全ての列を同じ基本データ型にする理由です。

議論1

なぜRは、データの列に何を置くのかについて意固地なのでしょう。 これがどう役立つのでしょう?

議論1

列の全てを同じにすることで、データについて簡単に仮定することができます。 列のひとつを数値と解釈できたら、 全て を数値と解釈することができるのです。 つまり、毎回確認する必要がないのです。 人々がきれいなデータと話している時は、このように整合性のあるデータであることを意味しているのです。 長期的には、この厳格な整合性が後にRを使うのが楽になることに繋がります。

合成関数で明確な内容を持つベクトルを作ることもできます:

combine_vector <- c(2,6,3)
combine_vector
[1] 2 6 3

これまで学んだことを踏まえて、以下は何を生み出すでしょうか。

quiz_vector <- c(2,6,'3')

これは、型強制と言われるものです。これが多くの驚きの素であり、 なぜ起こるのか、理解するには基本データ型とRがそれをどう解釈するかを知っておく必要があります。 Rが複数の型(ここでは、数値と文字型)がひとつのベクトルに合わさる場面に遭遇した際、 全てを強制的に同じ型にします。例えば:

coercion_vector <- c('a', TRUE)
coercion_vector
[1] "a"    "TRUE"
another_coercion_vector <- c(0, TRUE)
another_coercion_vector
[1] 0 1

強制化のルールは、logical -> integer -> numeric -> complex -> character です。ここで、 -> は、~が変換されるのは~という意味です。 この流れに逆らう強制化も、as. 関数を使ってできます:

character_vector_example <- c('0','2','4')
character_vector_example
[1] "0" "2" "4"
character_coerced_to_numeric <- as.numeric(character_vector_example)
character_coerced_to_numeric
[1] 0 2 4
numeric_coerced_to_logical <- as.logical(character_coerced_to_numeric)
numeric_coerced_to_logical
[1] FALSE  TRUE  TRUE

ご覧のとおり、Rがある基本のデータ型を他へ変換すると、驚くことが起こります。 型強制の核心はさておき、ポイントは:もし、データが思っていたものと違っている場合、 型強制が原因かもしれないという事です。ベクトルの中、データフレームの列を全て同じ型にすること、 さもなくば、いやなサプライズに会う羽目になるかもしれません。

しかし、型強制が役に立つこともあります。例えば、あの猫のデータの中にある、 likes_string は数値型ですが、私達は1と0が 一般的にTRUEFALSEを示すことを知っています。 ここで、データが将に意味するところのTRUE 又は FALSE を持つ logical データ型を使ったほうがいいに決まっています。 as.logical 関数を使い、この列を logical へ 「強制」 できます:

cats$likes_string
[1] 1 0 1
cats$likes_string <- as.logical(cats$likes_string)
cats$likes_string
[1]  TRUE FALSE  TRUE

合成関数 c()は、既存のベクトルに追加することもできます:

ab_vector <- c('a', 'b')
ab_vector
[1] "a" "b"
combine_example <- c(ab_vector, 'SWC')
combine_example
[1] "a"   "b"   "SWC"

数列を作ることもできます:

mySeries <- 1:10
mySeries
 [1]  1  2  3  4  5  6  7  8  9 10
seq(10)
 [1]  1  2  3  4  5  6  7  8  9 10
seq(1,10, by=0.1)
 [1]  1.0  1.1  1.2  1.3  1.4  1.5  1.6  1.7  1.8  1.9  2.0  2.1  2.2  2.3
[15]  2.4  2.5  2.6  2.7  2.8  2.9  3.0  3.1  3.2  3.3  3.4  3.5  3.6  3.7
[29]  3.8  3.9  4.0  4.1  4.2  4.3  4.4  4.5  4.6  4.7  4.8  4.9  5.0  5.1
[43]  5.2  5.3  5.4  5.5  5.6  5.7  5.8  5.9  6.0  6.1  6.2  6.3  6.4  6.5
[57]  6.6  6.7  6.8  6.9  7.0  7.1  7.2  7.3  7.4  7.5  7.6  7.7  7.8  7.9
[71]  8.0  8.1  8.2  8.3  8.4  8.5  8.6  8.7  8.8  8.9  9.0  9.1  9.2  9.3
[85]  9.4  9.5  9.6  9.7  9.8  9.9 10.0

ベクトルについて、いくつか質問することもできます:

sequence_example <- seq(10)
head(sequence_example, n=2)
[1] 1 2
tail(sequence_example, n=4)
[1]  7  8  9 10
length(sequence_example)
[1] 10
class(sequence_example)
[1] "integer"
typeof(sequence_example)
[1] "integer"

ベクトルの要素に名前を付けることもできます:

my_example <- 5:8
names(my_example) <- c("a", "b", "c", "d")
my_example
a b c d 
5 6 7 8 
names(my_example)
[1] "a" "b" "c" "d"

チャレンジ1

1から26までの数値を持つベクトルを作るところから始め、 ベクトルに2を掛けましょう。そして、そのベクトルにAからZまでの名前を 与えます(ヒント:LETTERS という備え付けのベクトルがあります)

チャレンジ1の解答

x <- 1:26
x <- x * 2
names(x) <- LETTERS

データフレーム

先ほどお伝えしたとおり、データフレームの列はベクトルです:

str(cats$weight)
 num [1:3] 2.1 5 3.2
str(cats$likes_string)
 logi [1:3] TRUE FALSE TRUE

これは納得といったところでしょうが、次はどうでしょう

str(cats$coat)
 Factor w/ 3 levels "black","calico",..: 2 1 3

順序なし因数

もうひとつの重要なデータ構造として、順序なし因子(factor)があります。 順序なし因子は、文字型データのように見えますが、一般的にはカテゴリー情報を表すために使われます。 例えば、我々の研究の全ての猫の毛色にラベル付けする文字列のベクトルを作ってみましょう:

coats <- c('tabby', 'tortoiseshell', 'tortoiseshell', 'black', 'tabby')
coats
[1] "tabby"         "tortoiseshell" "tortoiseshell" "black"        
[5] "tabby"        
str(coats)
 chr [1:5] "tabby" "tortoiseshell" "tortoiseshell" "black" "tabby"

ベクトルを順序なし因子に変換するには:

CATegories <- factor(coats)
class(CATegories)
[1] "factor"
str(CATegories)
 Factor w/ 3 levels "black","tabby",..: 2 3 3 1 2

ここでRは、我々のデータに3つのカテゴリーが存在することに気づきました。 しかし、それと同時に意外なこともしました。入力した文字列が表示される代わりに、 数字の羅列が出てきました。Rは、人が読めるカテゴリーを、 内部で番号を振られた添字に変換したのです。 こうする必要があるのは、統計的な計算で、カテゴリーデータをこのように数字として表して扱うからです。

typeof(coats)
[1] "character"
typeof(CATegories)
[1] "integer"

チャレンジ2

我々の cats データフレームには、順序なし因子がありますか?列の名前は何ですか? ?read.csv を使って、どうしたら文字の列を順序なし因子ではなく、文字型ベクトルのまま 読み込む方法を見つけ出して下さい。そして、catsの中の順序なし因子がこの方法で読み込まれたとき、 順序なし因子ではなく文字型ベクトルであることを証明するコマンドを、ひとつかふたつ書いて下さい。

チャレンジ2の解答

一つ目の解答は、stringAsFactors を引数として使うことです。

cats <- read.csv(file="data/feline-data.csv", stringsAsFactors=FALSE)
str(cats$coat)

もう一つの解答は、更に洗練した操作ができる引数 colClasses を使うことです。

cats <- read.csv(file="data/feline-data.csv", colClasses=c(NA, NA, "character"))
str(cats$coat)

補足:初めての方は、ヘルプファイルを難しく感じるかもしれません。これはよくあることですので、 自信がなくても、書かれたことの意味をうまく予測してみるようにしましょう。

モデリングの関数を使う際、基準となっている水準の順位がどれかを知っておくことが大事です。 最初の順序なし因子が基準値であると仮定されていますが、デフォルトでは、順序なし因子は アルファベット順に並べられています。水準の順位を指定することで、順序なし因子の順位を変更できます:

mydata <- c("case", "control", "control", "case")
factor_ordering_example <- factor(mydata, levels = c("control", "case"))
str(factor_ordering_example)
 Factor w/ 2 levels "control","case": 2 1 1 2

ここでは、 “control” は1、”case” は2で表されるべきであるとRにはっきり示しています。 この指定が、統計モデルの結果を解釈する際、とても重要になります。

リスト

覚えておきたいもう一つのデータ構造は、 list です。 リストは、他の種類よりも、ある意味シンプルです。その理由は、入れたいものを なんでも入れることができるからです:

list_example <- list(1, "a", TRUE, 1+4i)
list_example
[[1]]
[1] 1

[[2]]
[1] "a"

[[3]]
[1] TRUE

[[4]]
[1] 1+4i
another_list <- list(title = "Numbers", numbers = 1:10, data = TRUE )
another_list
$title
[1] "Numbers"

$numbers
 [1]  1  2  3  4  5  6  7  8  9 10

$data
[1] TRUE

これで、data.frameの驚くべき特徴を理解することができます。もし以下を走らせたらどうなるでしょう:

typeof(cats)
[1] "list"

data.frame は、 実際のところリストと同じように見えます。data.frame は、 実際のところベクトルと順序なし因子のリストであり、これらから形成しなければならないからです。 data.frame が、ベクトルと順序なし因子が混ざった列を含む全ての列をひとまとめにし、 なじみのある表にするためには、ベクトルよりも柔軟な構造が必要だったのです。 つまり、 data.frame は、全てのベクトルの長さが同じでなければならない特別なリストなのです。

我々の cats の例では、整数型(integer)、浮動小数型(double)、論理型(logical)の変数があります。 既に見たように、data.frame のそれぞれの列はベクトルです。

cats$coat
[1] calico black  tabby 
Levels: black calico tabby
cats[,1]
[1] calico black  tabby 
Levels: black calico tabby
typeof(cats[,1])
[1] "integer"
str(cats[,1])
 Factor w/ 3 levels "black","calico",..: 2 1 3

それぞれの行は、異なる変数の observation(観測値) であり、それ自体が data.frame であるため、 異なる種類の要素で構成されることができます。

cats[1,]
    coat weight likes_string
1 calico    2.1         TRUE
typeof(cats[1,])
[1] "list"
str(cats[1,])
'data.frame':\t1 obs. of  3 variables:
 $ coat        : Factor w/ 3 levels "black","calico",..: 2
 $ weight      : num 2.1
 $ likes_string: logi TRUE

チャレンジ3

data.frame から、変数、観測値、要素を呼び出す方法はいくつかあります:

  • cats[1]
  • cats[[1]]
  • cats$coat
  • cats["coat"]
  • cats[1, 1]
  • cats[, 1]
  • cats[1, ]

上記の例を試してみて、それぞれの例で何が返ってくるかを説明してみましょう。

ヒント: どういった値が返ってくるかを確かめるために、関数 typeof() を使いましょう。

チャレンジ3の解答

cats[1]
    coat
1 calico
2  black
3  tabby

データフレームを、ベクトルのリストと考えることができます。この角括弧 [1] は、リストの最初の一切れを別のリストとして返します。ここでは、 データフレームの最初の列となっています。

cats[[1]]
[1] calico black  tabby 
Levels: black calico tabby

この二重角括弧 [[1]] は、リスト項目の内容を返します。ここでは、 最初の列の内容、順序なし因子 の種類の_ベクトル_です。

cats$coat
[1] calico black  tabby 
Levels: black calico tabby

この例では、 $ 記号を、項目を名前で呼ぶために使っています coat が、データフレームの最初の列であり、これもまた_順序なし因子_ の種類の_ベクトル_です。

cats["coat"]
    coat
1 calico
2  black
3  tabby

ここでは、列の名前の要素番号の代わりに角括弧 ["coat"] を使っています。 例1のように、返ってくるオブジェクトは、_リスト_です。

cats[1, 1]
[1] calico
Levels: black calico tabby

この例は、角括弧を使っていますが、ここでは行と列の座標を指定します。 返ってくるオブジェクトは、行1、列1の値です。このオブジェクトは、 整数 ですが、_順序なし因数_の一種の_ベクトル_の一部なので、Rは、 整数値と紐づけられた「calico」のラベルを表示します。

cats[, 1]
[1] calico black  tabby 
Levels: black calico tabby

前の例と同じように、角括弧を使い、行と列の座標を指定します。 行の座標は指定されていません。Rは、この欠けている値を、 この_列_の_ベクトル_の全ての要素と解釈します。

cats[1, ]
    coat weight likes_string
1 calico    2.1         TRUE

同じ様に、行と列の座標の入った角括弧を使っています。列の座標は 指定されていません。返ってくる値は、最初の行にある全ての値の_リスト_ です。

行列

最後に、行列を紹介します。全てが0の行列を宣言できます:

matrix_example <- matrix(0, ncol=6, nrow=3)
matrix_example
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    0    0    0    0    0    0
[2,]    0    0    0    0    0    0
[3,]    0    0    0    0    0    0

そして、他のデータ構造と同様に、行列に関することを尋ねることもできます:

class(matrix_example)
[1] "matrix"
typeof(matrix_example)
[1] "double"
str(matrix_example)
 num [1:3, 1:6] 0 0 0 0 0 0 0 0 0 0 ...
dim(matrix_example)
[1] 3 6
nrow(matrix_example)
[1] 3
ncol(matrix_example)
[1] 6

チャレンジ4

次の結果は何になるでしょうか。 length(matrix_example)? 試してみて下さい。 合っていましたか?なぜ答えが合っていた・合っていなかったのでしょうか?

チャレンジ4の解答

次の結果は何になるでしょうか。 length(matrix_example)?

matrix_example <- matrix(0, ncol=6, nrow=3)
length(matrix_example)
[1] 18

行列は、次元が追加されたベクトルですので、lengthは 行列の総要素数を教えてくれます。

チャレンジ5

もう一つ行列を作ってみましょう、今回は、1:50の数を含むもので、 5行、10列を持つ行列にしましょう。 この matrix 関数は、デフォルトでは、行か列、どちらから 行列を埋めましたか? どうしたらこのデフォルト動作を変更できるか探してみて下さい。 (ヒント: matrix のドキュメントを読んでみましょう!)

チャレンジ5の解答

もう一つ行列を作ってみましょう、今回は、1:50の数を含むもので、 5行、10列を持つ行列にしましょう。 この matrix 関数は、デフォルトでは、行か列、どちらから 行列を埋めましたか? これがどう変化したか理解したか確認してみましょう。 (ヒント: matrix のドキュメントを読んでみましょう!)

x <- matrix(1:50, ncol=5, nrow=10)
x <- matrix(1:50, ncol=5, nrow=10, byrow = TRUE) # to fill by row

チャレンジ6

ワークショップの現パート、それぞれのセクションのために、二つの文字型ベクトルが含まれるリストを作って下さい:

  • データ型
  • データ構造

それぞれの文字ベクトルをこれまでみてきたデータ型と データ構造で埋めてください。

チャレンジ6の解答

dataTypes <- c('double', 'complex', 'integer', 'character', 'logical')
dataStructures <- c('data.frame', 'vector', 'factor', 'list', 'matrix')
answer <- list(dataTypes, dataStructures)

補足:ボードや壁に、全ての型と構造を大きな文字で列挙したものを 貼っておくのは良い方法です -これらの基本の重要性をみなさんに繰り返し伝えるため、 ワークショップが終わるまで残しておきましょう。

チャレンジ7

下記の行列のRアウトプットを見てみましょう:

     [,1] [,2]
[1,]    4    1
[2,]    9    5
[3,]   10    7

この行列を書くために使ったコマンドは何でしたか? それぞれのコマンドを確かめて、打ち込む前に正しいものが何か分かるようにしましょう。 他のコマンドでは、どのような行列が作られるかを考えてみましょう。

  1. matrix(c(4, 1, 9, 5, 10, 7), nrow = 3)
  2. matrix(c(4, 9, 10, 1, 5, 7), ncol = 2, byrow = TRUE)
  3. matrix(c(4, 9, 10, 1, 5, 7), nrow = 2)
  4. matrix(c(4, 1, 9, 5, 10, 7), ncol = 2, byrow = TRUE)

チャレンジ7の解答

下記の行列のRアウトプットを見てみましょう:

     [,1] [,2]
[1,]    4    1
[2,]    9    5
[3,]   10    7

この行列を書くために使ったコマンドは何でしたか? それぞれのコマンドを確かめて、打ち込む前に正しいものが何か分かるようにしましょう。 他のコマンドでは、どのような行列が作られるかを考えてみましょう。

matrix(c(4, 1, 9, 5, 10, 7), ncol = 2, byrow = TRUE)

まとめ

  • R のテーブル型のデータの読み込みには read.csv を使う。

  • R の基本的なデータタイプにはdouble(浮動小数点型)integer(整数型)complex(複素数型)logical(論理型)、そしてcharacter(文字型)がある。

  • R では、データのカテゴリーを表すのにfactor (ファクター) を使う。