lapplyとsapplyの使い方について

この記事はRstudioのSwirlの「10: lapply and sapply」を参照しています。
まずFlagsというデータセットをRに読み込みます(Swirlを使用している場合、自動的に読み込まれます。)。
データ名:Flags




name

landmass

zone

area

population

language

religion

bars

stripes

colours

red

green

blue

gold

white

black

orange

mainhue

circles

crosses

saltires

quarters

sunstars

crescent

triangle

icon

animate

text

topleft

botright

1

Afghanistan

5

1

648

16

10

2

0

3

5

1

1

0

1

1

1

0

green

0

0

0

0

1

0

0

1

0

0

black

green

2

Albania

3

1

29

3

6

6

0

0

3

1

0

0

1

0

1

0

red

0

0

0

0

1

0

0

0

1

0

red

red

3

Algeria

4

1

2388

20

8

2

2

0

3

1

1

0

0

1

0

0

green

0

0

0

0

1

1

0

0

0

0

green

white

4

American-Samoa

6

3

0

0

1

1

0

0

5

1

0

1

1

1

0

1

blue

0

0

0

0

0

0

1

1

1

0

blue

red

5

Andorra

3

1

0

0

6

0

3

0

3

1

0

1

1

0

0

0

gold

0

0

0

0

0

0

0

0

0

0

blue

red

6

Angola

4

2

1247

7

10

5

0

2

3

1

0

0

1

0

1

0

red

0

0

0

0

1

0

0

1

0

0

red

black

7

Anguilla

1

4

0

0

1

1

0

1

3

0

0

1

0

1

0

1

white

0

0

0

0

0

0

0

0

1

0

white

blue

8

Antigua-Barbuda

1

4

0

0

1

1

0

1

5

1

0

1

1

1

1

0

red

0

0

0

0

1

0

1

0

0

0

black

red

9

Argentina

2

3

2777

28

2

0

0

3

2

0

0

1

0

1

0

0

blue

0

0

0

0

0

0

0

0

0

0

blue

blue

10

Argentine

2

3

2777

28

2

0

0

3

3

0

0

1

1

1

0

0

blue

0

0

0

0

1

0

0

0

0

0

blue

blue

とりあえず1~10までのデータを示しています。
全部で194行(194データ数)、30列(30の変数)あります。194の国とその国旗に関する情報が30あるということです。

> class(flags)
[1] "data.frame"

データのデータタイプはデータフレームであることが分かります。
それではそれぞれの列に対し今行ったのと同様にclass関数を繰り返し適用するにはどうすればよいのでしょうか。
このような何かを繰り返し行う場合*apply関数が役に立ちます。

lapply
まず初めに*apply関数の基礎となるlapplyを見ていきます。
lapplyはリストのデータタイプに対し、ある関数をそれぞれの列に適用することができます(lapplyのlははlistを表しています。)。
flagsはデータフレームですが、データフレームはベクターのリストであるため、lapply関数を適用することができます。
まずflagsの各列にclass関数を適用し、cls_listにデータを保管します。

> cls_list <- lapply(flags, class)

結果を見てみます。

> cls_list
$`name` [1] "factor"
$landmass [1] "integer"
$zone [1] "integer"
$area [1] "integer"
$population [1] "integer"
$language [1] "integer"
$religion [1] "integer"
$bars [1] "integer"
$stripes [1] "integer"
$colours [1] "integer"
$red [1] "integer"
$green [1] "integer"
$blue [1] "integer"
$gold [1] "integer"
$white [1] "integer"
$black [1] "integer"
$orange [1] "integer"
$mainhue [1] "factor"
$circles [1] "integer"
$crosses [1] "integer"
$saltires [1] "integer"
$quarters [1] "integer"
$sunstars [1] "integer"
$crescent [1] "integer"
$triangle [1] "integer"
$icon [1] "integer"
$animate [1] "integer"
$text [1] "integer"
$topleft [1] "factor"
$botright [1] "factor"

このようにそれぞれの列に対し、class関数が適用された結果を得ることができます。
ただし、結果がリストであるためこのように長くなってしまいます。そこでこの結果をキャラクターベクターに変換します。

> as.character(cls_list)
 [1] "factor"  "integer" "integer" "integer" "integer" "integer" "integer" "integer" "integer" "integer"
[11] "integer" "integer" "integer" "integer" "integer" "integer" "integer" "factor"  "integer" "integer"
[21] "integer" "integer" "integer" "integer" "integer" "integer" "integer" "integer" "factor"  "factor" 
sapply

次にsapply関数を見ていきますが、sapplyは先ほど行ったキャラクターベクターに変換するところまでを自動で行ってくれます(sはsimplifyを表す)。flagsの各列にclass関数を適用し、結果をcls_vectに保管します。

> cls_vect <- sapply(flags, class)
> cls_vect 
      name   landmass       zone       area population   language   religion       bars    stripes 
  "factor"  "integer"  "integer"  "integer"  "integer"  "integer"  "integer"  "integer"  "integer" 
   colours        red      green       blue       gold      white      black     orange    mainhue 
 "integer"  "integer"  "integer"  "integer"  "integer"  "integer"  "integer"  "integer"   "factor" 
   circles    crosses   saltires   quarters   sunstars   crescent   triangle       icon    animate 
 "integer"  "integer"  "integer"  "integer"  "integer"  "integer"  "integer"  "integer"  "integer" 
      text    topleft   botright 
 "integer"   "factor"   "factor" 

sapplyは通常、結果がリストでそれぞれの要素の長さが1の場合、ベクターを結果として返します。もし結果がリストで長さが同じ(2以上)の場合、sapplyはマトリックスを結果として返します。上記以外の場合、sapplyはリストを結果として返します(すなわちlapplyと同じ結果になります。)。

ここからは少し実際にlapplyとsapplyを練習してみます。
flagsの変数11から17番目の列には、それぞれの国旗がどの色を含んでいるかを示しています。例えば国旗がオレンジ色(17列目)を含んでいれば、結果は1となる。

> sum(flags$orange)
[1] 26

オレンジ色を含む国旗は合計26あることがわかります。同様の佐合を11から17番目の列それぞれに対し行う場合lapplyとsapplyを使用できます。まず、flagsから11~17番目の列だけを抜き出しflag_colorsとして保存します。

> flag_colors <- flags[, 11:17]
> head(flag_colors)
  red green blue gold white black orange
1   1     1    0    1     1     1      0
2   1     0    0    1     0     1      0
3   1     1    0    0     1     0      0
4   1     0    1    1     1     0      1
5   1     0    1    1     0     0      0
6   1     0    0    1     0     1      0

次にflag_colorsに対しlapply関数を用い、それぞれの列の合計を計算します。

> lapply(flag_colors, sum)
$`red` [1] 153
$green [1] 91
$blue [1] 99
$gold [1] 91
$white [1] 146
$black [1] 52
$orange [1] 26

153の国旗が赤色を含み、91の国旗が緑色を含んでいることが分かります。
結果はリストで返されており、それぞれの長さが1であるため、sapplyを使えば単純化(キャラクターベクターに変換)できます。

> sapply(flag_colors,sum)
   red  green   blue   gold  white  black orange 
   153     91     99     91    146     52     26 

同様にmean関数を用いれば、各色を含む国旗の割合を知ることができる。

> sapply(flag_colors,mean)
      red     green      blue      gold     white     black    orange 
0.7886598 0.4690722 0.5103093 0.4690722 0.7525773 0.2680412 0.1340206 

赤色を含む国旗は79%、緑を含む国旗は47%であることが分かります。

次にsapplyがマトリックスを結果として返す例を見てみます。
flagsのデータセットの19~23列目は特徴的な形が国旗の中に何回現れるかを示しています。例えば星(★)の数(23列目)はアメリカ合衆国であれば50となり、日本であれば0となります。まず、flagsから19~23番目の列だけを抜き出し、結果をflag_shapesとして保存します。

> flag_shapes <- flags[, 19:23]

そしてlapplyを使用し、range関数を各列に適用します。range関数はそれぞれのベクターの範囲(最小値と最大値)を返します。

> lapply(flag_shapes, range)
$`circles` [1] 0 4
$crosses [1] 0 2
$saltires [1] 0 1
$quarters [1] 0 4
$sunstars [1]  0 50

結果から、sunstars(★)であれば、最小は0で最高は50となっていることが分かります。
次にsapply関数を同様に適用し、結果をshape_matに保管します。

> shape_mat <- sapply(flag_shapes, range)

shape_matの結果を見てみるとマトリックスとなっていることが分かります。これは結果のリストのそれぞれの要素が同じ長さ(2)だからです。([1,]が最小値、[2,]が最大値を表している。)

> shape_mat
     circles crosses saltires quarters sunstars
[1,]       0       0        0        0        0
[2,]       4       2        1        4       50

> class(shape_mat)
[1] "matrix"

最後にsapplyが結果を単純化できない場合を見てみます。実際にsapply関数を使う前に、unique関数の意味と使い方を見てみます。

> unique(c(3, 4, 5, 5, 5, 6, 6))
[1] 3 4 5 6

このようにunique関数は、ダブりを除いた結果を返します。
まず、lapplyでunique関数をflagsの各列に対して行った結果をunique_valsに保存します。

> unique_vals <- lapply(flags, unique)

unique_valsの結果は長くなるため省略しますが、sapplyとlength関数を用いれば、それぞれの要素の長さを見ることができます。

> sapply(unique_vals, length)
      name   landmass       zone       area population   language   religion       bars    stripes 
       194          6          4        136         48         10          8          5         12 
   colours        red      green       blue       gold      white      black     orange    mainhue 
         8          2          2          2          2          2          2          2          8 
   circles    crosses   saltires   quarters   sunstars   crescent   triangle       icon    animate 
         4          3          2          3         14          2          2          2          2 
      text    topleft   botright 
         2          7          8 

このように、リスト内の要素はそれぞれ異なったベクターの長さであることが分かります。この場合sapplyを適用すると、単純化することはできずlapplyと同じ結果を返します。

> sapply(flags, unique)
# 結果は省略