Jakob Richter

apply() hoch 3

Nach dem Beitrag zu lapply() und sapply() möchte ich hier nun das etwas mächtigere (?) apply() vorstellen, was man in R wohl kaum missen will.

Ich werde kurz zeigen wie mit apply() Matrizen und data.frames zeilen- sowie spaltenweise Ausgewertet werden können. Nach dieser sehr leichten Übung widmen wir uns einem etwas trickreicherem Beispiel, in dem wir mehrere gleichartige Tabellen (also Matrizen bzw. data.frames) der gleichen Zeilen- und Spaltenanzahl vorliegen haben. Hier möchten wir die Informationen aus immer den gleichen Zellen zusammenfassen.

apply auf einer Matrix

Wer lapply() verstanden hat, der wird auch hiermit kein Problem haben.
apply(X, MARGIN, FUN, ...)
X ist eine Matrix oder ein data.frame
MARGIN gibt an, ob jede Zeile MARGIN=1 transponiert als Vektor der Funktion übergeben werden soll oder jede Spalte MARGIN=2 an die Funktion (FUN) übergeben wird. Es gibt noch weitere Möglichkeiten – dazu später.

Einfache Beispiele:

1
2
3
4
5
6
7
8
9
10
11
USPersonalExpenditure
#                      1940   1945  1950 1955  1960
#Food and Tobacco    22.200 44.500 59.60 73.2 86.80
#Household Operation 10.500 15.500 29.00 36.5 46.20
#Medical and Health   3.530  5.760  9.71 14.0 21.10
#Personal Care        1.040  1.980  2.45  3.4  5.40
#Private Education    0.341  0.974  1.80  2.6  3.6
 
apply(USPersonalExpenditure,2,sum)
#  1940    1945    1950    1955    1960
#37.611  68.714 102.560 129.700 163.140

Aus den Ausgaben der US-Bürger in Milliarden USD in den verschiedenen Kategorien, haben wir jetzt also berechnet, wie viele Milliarden sie in den Kategorien zusammen ausgegeben Haben.

Finden wir nun heraus, wie die Steigerung zum Vorjahr ausfällt. Dazu müssen wir die Daten natürlich Zeilenweise auswerten.

13
14
15
16
17
18
19
20
21
22
23
apply(USPersonalExpenditure,1,function(x){
	n <- length(x)
	ratio <- x[2:n]/x[1:(n-1)]
	res <- c('1940'=NA,round(ratio,2))
})
#      Food and Tobacco Household Operation Medical and Health Personal Care Private Education
# 1940               NA                  NA                 NA            NA                NA
# 1945             2.00                1.48               1.63          1.90              2.86
# 1950             1.34                1.87               1.69          1.24              1.85
# 1955             1.23                1.26               1.44          1.39              1.44
# 1960             1.19                1.27               1.51          1.59              1.40

Hier sieht man auch sehr gut, dass apply() die Funktionsergebnisse (hier Vektoren) wieder schön in einer Matrix zusammenfasst, wenn es dazu in der Lage ist.

apply() hoch 3

Schauen wir uns einmal so eine „dreidimensionale“ Tabelle an.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HairEyeColor
# , , Sex = Male
#
# Eye
# Hair    Brown Blue Hazel Green
# Black    32   11    10     3
# Brown    53   50    25    15
# Red      10   10     7     7
# Blond     3   30     5     8
#
# , , Sex = Female
#
# Eye
# Hair    Brown Blue Hazel Green
# Black    36    9     5     2
# Brown    66   34    29    14
# Red      16    7     7     7
# Blond     4   64     5     8

Auch hier funktioniert apply() wie geschmiert.
Wie oft kommt welche Haarfarbe vor?

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#Wie oft kommt welche Haarfarbe vor?
apply(HairEyeColor,1,sum)
#Black Brown   Red Blond
#  108   286    71   127
 
#Wie oft welche Augenfarbe?
apply(HairEyeColor,2,sum)
#Brown  Blue Hazel Green
#  220   215    93    64
 
#Die Tabelle für beide Geschlechter zusammengefasst:
apply(HairEyeColor,1:2,sum)
#        Eye
# Hair    Brown Blue Hazel Green
# Black    68   20    15     5
# Brown   119   84    54    29
# Red      26   17    14    14
# Blond     7   94    10    16

Wieso MARGIN=c(1,2)? Man kann sich das ganz einfach so vorstellen: Für die Erzeugung des Vektors, welcher an die Funktion übergeben wird, bleibt jeweils die Zeile (1) und die Spalte (2) fest. In der dritten Dimension ist also „frei“. Im zweidimensionalen Fall ist es genau so.

Wenn man es verinnerlicht hat, sollte man sich auch denken können, was folgende Aufrufe erzeugen? Was denkst du?

38
39
apply(HairEyeColor,c(1,3),sum)
apply(HairEyeColor,2:3,sum)

Außer für solch einfache Beispiele kann das auch von Nutzen sein um evtl. die Varianz eines Wertes in einer Kontingenztafel zu berechnen, wenn mehrere solcher Tafeln vorliegen. So kann es vorkommen, dass man mehrere Kontingenztafel simuliert und sich dann für die Streuung der Werte in den einzelnen Zellen interessiert.

Wie erzeugt man solch dreidimensionale Tabellen?


Ganz einfach z.B. mit table():

1
table(mtcars[,c("am","gear","cyl")])

Etwas komplizierter, eine kleine Simulationsstudie zu Vierfeldertafeln.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ktafeln <- replicate(1000, table(runif(10)>0.5,runif(10)>0.5))
 
tafArray <- function(taflist){
	#Konvertiert eine Liste von Matrizen mit gleichen Dimensionen (AxB) ein einen 3D-Array
	#Baut die 3te Dimension nacheinander auf.
	dimension <- c(dim(taflist[[1]]),1) #Die Dimension ist jetzt also (AxBx1)
	Array <- array(taflist[[1]], dim=dimension)
	for(i in 2:length(taflist)){
		dimension <- dimension + c(0,0,1)
		#Füge neue Matrix an (AxBxi)
		Array <- array(c(Array,taflist[[i]]),dim=dimension)
	}
	return(Array)
}
 
matrix3d <- tafArray(ktafeln)
res <- apply(matrix3d,1:2,function(x) quantile(x, probs=c(0.05,0.95)))
res #nicht so übersichtlich
aperm(res,c(2,3,1)) #Stellt die Quantile an letzte Stelle der Dimensionen
#, , 5%
#
#     [,1] [,2]
#[1,]    0    1
#[2,]    0    0
#
#, , 95%
#
#     [,1] [,2]
#[1,]    5    5
#[2,]    5    5

Das sind natürlich keine Konfidenzintervalle an denen sich etwas testen ließe. Nur eine Spielerei und zur Verdeutlichung.

Leave a Reply