日々のつれづれ

不惑をむかえ戸惑いを隠せない男性の独り言

by関数について質問を受けたのでまとめてみた

先日、by関数について質問を受けたので、それを一例に少しまとめようと思います。

  • by関数はヘルプを見れば分かるように、tapply関数と同じ作用を示す関数です。
  • tapply関数は、名前から予想がつくようにapply関数の仲間です。(因みに、apply系の関数として、apply関数lapply関数sapply関数は既出です)

by関数は引数に、data、INDICES、FUN、simplyfyをとります。
それぞれを簡単に説明すると

  • data --- by関数で処理したいデータセットになります。data.frameを想定していますが、matrixも使えます。
  • INDICES --- dataに対して処理したいsubsetを与えます。引数dataに指定したdata.frameにfactorのデータ系列があったりすると、それをsubsetに指定できるので便利です。tapply巻子ではINDEXになります。
  • FUN --- 引数dataに対して、適用したい関数を指定します。これはapply系関数で共通する概念ですね。察しのとおり、FUNで指定した関数に引数がある場合は、by関数の中に引数で追加できます。引数FUNの後につけることも忘れずに。
  • simplify --- 処理結果の返値のデータ形式を指定します。TRUEならscalar、FALSEならlistで返します。

で、irisのデータで簡単な例を作ってみます。
irisはSpeciesがfactorで、 setosa、versicolor、virginicaがそれぞれ50サンプルあります。

> str(iris)
'data.frame':	150 obs. of  5 variables:
 $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
 $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
> summary(iris)
  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  

で、by関数はデータセットのsubsetに対して、FUNで指定する処理を実行できます。
例えば、Speciesの3系統のそれぞれの統計量を算出したいときは、

> by(iris, INDICES=iris$Species, FUN=summary)
iris$Species: setosa
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
 Min.   :4.300   Min.   :2.300   Min.   :1.000   Min.   :0.100  
 1st Qu.:4.800   1st Qu.:3.200   1st Qu.:1.400   1st Qu.:0.200  
 Median :5.000   Median :3.400   Median :1.500   Median :0.200  
 Mean   :5.006   Mean   :3.428   Mean   :1.462   Mean   :0.246  
 3rd Qu.:5.200   3rd Qu.:3.675   3rd Qu.:1.575   3rd Qu.:0.300  
 Max.   :5.800   Max.   :4.400   Max.   :1.900   Max.   :0.600  
       Species  
 setosa    :50  
 versicolor: 0  
 virginica : 0  
                
                
                
------------------------------------------------------------ 
iris$Species: versicolor
  Sepal.Length    Sepal.Width     Petal.Length   Petal.Width          Species  
 Min.   :4.900   Min.   :2.000   Min.   :3.00   Min.   :1.000   setosa    : 0  
 1st Qu.:5.600   1st Qu.:2.525   1st Qu.:4.00   1st Qu.:1.200   versicolor:50  
 Median :5.900   Median :2.800   Median :4.35   Median :1.300   virginica : 0  
 Mean   :5.936   Mean   :2.770   Mean   :4.26   Mean   :1.326                  
 3rd Qu.:6.300   3rd Qu.:3.000   3rd Qu.:4.60   3rd Qu.:1.500                  
 Max.   :7.000   Max.   :3.400   Max.   :5.10   Max.   :1.800                  
------------------------------------------------------------ 
iris$Species: virginica
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
 Min.   :4.900   Min.   :2.200   Min.   :4.500   Min.   :1.400  
 1st Qu.:6.225   1st Qu.:2.800   1st Qu.:5.100   1st Qu.:1.800  
 Median :6.500   Median :3.000   Median :5.550   Median :2.000  
 Mean   :6.588   Mean   :2.974   Mean   :5.552   Mean   :2.026  
 3rd Qu.:6.900   3rd Qu.:3.175   3rd Qu.:5.875   3rd Qu.:2.300  
 Max.   :7.900   Max.   :3.800   Max.   :6.900   Max.   :2.500  
       Species  
 setosa    : 0  
 versicolor: 0  
 virginica :50  

となります。

次にFUNとFUNに対する引数で指定する例を示します。
例えば、colMeans関数を使ってSepal.LengthとSepal.Widthの系列毎の平均値を求めるときに、データに欠損値がある例を示します。

> set.seed(123)
> data <- iris
> data$Sepal.Length[sample(nrow(iris),10)] <- data$Sepal.Width[sample(nrow(iris),10)] <- NA
> 
> by(data[,c("Sepal.Length", "Sepal.Width")], INDICES=data$Species, FUN=colMeans)
data$Species: setosa
Sepal.Length  Sepal.Width 
          NA           NA 
------------------------------------------------------------ 
data$Species: versicolor
Sepal.Length  Sepal.Width 
          NA           NA 
------------------------------------------------------------ 
data$Species: virginica
Sepal.Length  Sepal.Width 
          NA           NA 
> 
> by(data[,c("Sepal.Length", "Sepal.Width")], INDICES=data$Species, FUN=colMeans, na.rm=TRUE)
data$Species: setosa
Sepal.Length  Sepal.Width 
    4.997826     3.427083 
------------------------------------------------------------ 
data$Species: versicolor
Sepal.Length  Sepal.Width 
    5.950000     2.780435 
------------------------------------------------------------ 
data$Species: virginica
Sepal.Length  Sepal.Width 
    6.582609     2.952174 

colMeansの引数na.rmを引数FUNの後につけることができ、NAをomitした結果を得ることができました。

ここまでくると、お気づきと思いますが、by関数、tapply関数はラッパー関数です。
colMeansの例は次のように記述できます。

> lapply(unique(data$Species), function(i) colMeans(subset(data, subset=Species==i, select=c("Sepal.Length", "Sepal.Width")), na.rm=TRUE))
[[1]]
Sepal.Length  Sepal.Width 
    4.997826     3.427083 

[[2]]
Sepal.Length  Sepal.Width 
    5.950000     2.780435 

[[3]]
Sepal.Length  Sepal.Width 
    6.582609     2.952174 

コードをシンプルにできる点でapply系関数は非常に便利です。
FUNの部分に自作関数を持ってくると、複雑な処理もシンプルなコードで記述できるのがよいところだと思います。