Facteurs et tableaux

R inclut un certain nombre de fonctions pratiques pour travailler avec des tables et des facteurs. Nous en discuterons deux ici: aggregate () et cut ().
NOTE Le package de remodelage de Hadley Wickham «vous permet de restructurer et d'agréger de manière flexible les données en utilisant seulement deux fonctions: fondre et couler». Ce package peut prendre un certain temps pour apprendre, mais il est extrêmement puissant. Son paquet de plyr est aussi assez polyvalent. Vous pouvez télécharger les deux packages depuis le référentiel CRAN de R. Voir l'annexe B pour plus de détails sur le téléchargement et l'installation des paquets.


1 La fonction aggregate ()
La fonction aggregate () appelle tapply () une fois pour chaque variable d'un groupe. Par exemple, dans les données sur les abalones, nous pouvons trouver la médiane de chaque variable, ventilée par sexe, comme suit:

  1. > aggregate(aba[,-1],list(aba$Gender),median)
  2. Group.1 Length Diameter Height WholeWt ShuckedWt ViscWt ShellWt Rings
  3. 1 F 0.590 0.465 0.160 1.03850 0.44050 0.2240 0.295 10
  4. 2 I 0.435 0.335 0.110 0.38400 0.16975 0.0805 0.113 8
  5. 3 M 0.580 0.455 0.155 0.97575 0.42175 0.2100 0.276 10
Source code
Le premier argument, aba [, - 1], est l'ensemble du bloc de données à l'exception de la première colonne, qui est le genre lui-même. Le deuxième argument, qui doit être une liste, est notre facteur genre comme avant. Enfin, le troisième argument indique à R de calculer la médiane sur chaque colonne dans chacune des trames de données générées par le sous-groupe correspondant à nos facteurs. Il y a trois sous-groupes dans notre exemple ici et donc trois lignes dans la sortie de aggregate ().


2 La fonction cut ()
Une manière courante de générer des facteurs, en particulier pour les tables, est la fonction de cut (). Vous lui donnez un vecteur de données x et un ensemble de cases définies par un vecteur b. La fonction détermine alors à quel bin chacun des éléments de x tombe.
Voici la forme de l'appel que nous utiliserons ici:

  1. y <- cut(x,b,labels=FALSE)
Source code

où les casiers sont définis comme étant les intervalles semi-ouverts (b [1], b [2]], (b [2], b [3]], - Voici un exemple:

  1. > z
  2. [1] 0.88114802 0.28532689 0.58647376 0.42851862 0.46881514 0.24226859 0.05289197
  3. [8] 0.88035617
  4. > seq(from=0.0,to=1.0,by=0.1)
  5. [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
  6. > binmarks <- seq(from=0.0,to=1.0,by=0.1)
  7. > cut(z,binmarks,labels=F)
  8. [1] 9 3 6 5 5 3 1 9
Source code

Cela indique que z [1], 0,88114802, est tombé dans la cellule 9, qui était (0,0,0,1); z [2], 0,28532689, est tombé dans la cellule 3 et ainsi de suite.
Cela renvoie un vecteur, comme vu dans le résultat de l'exemple. Mais nous pouvons le convertir en un facteur et éventuellement l'utiliser pour construire une table. Par exemple, vous pouvez imaginer utiliser cette fonction pour écrire votre propre fonction d'histogramme spécialisé. (La fonction R findInterval () serait également utile pour cela).

Pour commencer à explorer les tables R, considérez cet exemple:

  1. > u <- c(22,8,33,6,8,29,-2)
  2. > fl <- list(c(5,12,13,12,13,5,13),c("a","bc","a","a","bc","a","a"))
  3. > tapply(u,fl,length)
  4. a bc
  5. 5 2 NA
  6. 12 1 1
  7. 13 2 1
Source code
Ici, tapply () interrompt temporairement u en sous-secteurs, comme vous l'avez vu plus tôt, puis applique la fonction length () à chaque sous-vecteur. (Notez que ceci est indépendant de ce qui est dans u.) Notre attention est maintenant purement sur les facteurs.) Ces longueurs de sous-vecteurs sont les comptes des occurrences de chacune des 3 x 2 = 6 combinaisons des deux facteurs. Par exemple, 5 sont survenus deux fois avec "a" et pas du tout avec "bc"; d'où les entrées 2 et NA dans la première ligne de la sortie. En statistique, cela s'appelle un tableau de contingence.
Il y a un problème dans cet exemple: la valeur NA. Il devrait vraiment être 0, ce qui signifie que dans aucun cas le premier facteur n'a le niveau 5 et le second le niveau "bc". La fonction table () crée des tables de contingence correctement.
  1. > table(fl)
  2. fl.2
  3. fl.1 a bc
  4. 5 2 1
  5. 12 1 1
  6. 13 1 0
Source code
Le premier argument d'un appel à table () est un facteur ou une liste de facteurs. Les deux facteurs étaient (5,12,13,12,13,5,13) et («a», «bc», «a», «a», «bc», «a», «a») . Dans ce cas, un objet qui peut être interprété comme un facteur est compté comme un.
Typiquement, une trame de données sert d'argument de données table (). Supposons par exemple que le fichier ct.dat se compose de données d'interrogation d'élection, dans lesquelles le candidat X est en cours de réélection. Le fichier ct.dat ressemble à ceci:
  1. "Vote for X" "Voted For X Last Time"
  2. "Yes" "Yes"
  3. "Yes" "No"
  4. "No" "No"
  5. "Not Sure" "Yes"
  6. "No" "No"
Source code

Selon la méthode statistique habituelle, chaque ligne de ce fichier représente un sous-projet à l'étude. Dans ce cas, nous avons posé à cinq personnes les deux questions suivantes:
• Prévoyez-vous voter pour le candidat X?
• Avez-vous voté pour X aux dernières élections?
Cela nous donne cinq lignes dans le fichier de données.
Lisons dans le fichier:

  1. > ct <- read.table("ct.dat",header=T)
  2. > ct
  3. Vote.for.X Voted.for.X.Last.Time
  4. 1 Yes Yes
  5. 2 Yes No
  6. 3 No No
  7. 4 Not Sure Yes
  8. 5 No No
Source code
Nous pouvons utiliser la fonction table () pour calculer la table de contingence pour ces données:

  1. > cttab <- table(ct)
  2. > cttab
  3. Voted.for.X.Last.Time
  4. Vote.for.X No Yes
  5. No 2 0
  6. Not Sure 0 1
  7. Yes 1 1
Source code
Voté.à.X.Vote.à.temps.par.X Non Oui Non 2 0
Pas sûr 0 1 Oui 1 1
Le 2 dans le coin supérieur gauche du tableau montre que nous avons eu, par exemple, deux personnes qui ont répondu «non» aux première et deuxième questions. Le 1 au milieu à droite indique qu'une personne a répondu «pas sûr» à la première question et «oui» à la deuxième question.
Nous pouvons également obtenir des dénombrements unidimensionnels, qui sont des dénombrements sur un seul facteur, comme suit:

  1. > table(c(5,12,13,12,8,5))
  2. 5 8 12 13
  3. 2 1 2 1
Source code
Voici un exemple d'une table tridimensionnelle impliquant les génies des électeurs, la race (blanche, noire, asiatique et autre) et les opinions politiques (libérales ou conservatrices):

  1. > v # the data frame
  2. gender race pol
  3. 1 M W L
  4. 2 M W L
  5. 3 F A C
  6. 4 M O L
  7. 5 F B L
  8. 6 F B C
  9. > vt <- table(v)
  10. > vt
  11. , , pol = C
  12. race
  13. gender A B O W
  14. F 1 1 0 0
  15. M 0 0 0 0
  16. , , pol = L
  17. race
  18. gender A B O W
  19. F 0 1 0 0
  20. M 0 0 1 2
Source code
R imprime une table tridimensionnelle sous la forme d'une série de tables bidimensionnelles. Dans ce cas, il génère un tableau de genre et de race pour les conservateurs puis un tableau correspondant pour les libéraux. Par exemple, la deuxième table à deux dimensions dit qu'il y avait deux libéraux blancs.

 

1 Opérations Matrix / Array-Like sur les tables
Tout comme la plupart des opérations matricielles / matricielles (non mathématiques) peuvent être utilisées sur les trames de données, elles peuvent également être appliquées aux tables. (Cela n'est pas surprenant, étant donné que la partie de nombre de cellules d'un objet de table est un tableau.)
Par exemple, nous pouvons accéder au nombre de cellules de la table en utilisant la notation matricielle. Appliquons ceci à notre exemple de vote de la section précédente.
  1. > class(cttab)
  2. [1] "table"
  3. > cttab[1,1]
  4. [1] 2
  5. > cttab[1,]
  6. No Yes
  7. 2 0
Source code
Dans la deuxième commande, même si la première commande avait montré que cttab avait la classe "cttab", nous l'avons traitée comme une matrice et imprimé son "élément [1,1]". En continuant cette idée, la troisième commande imprimait la première colonne de cette "matrice".
Nous pouvons multiplier la matrice par un scalaire. Par exemple, voici comment changer le nombre de cellules en proportions:
  1. > ctt/5
  2. Voted.for.X.Last.Time
  3. Vote.for.X No Yes
  4. No 0.4 0.0
  5. Not Sure 0.0 0.2
  6. Yes 0.2 0.2
Source code
En statistique, les valeurs marginales d'une variable sont celles obtenues lorsque cette variable est maintenue constante alors que d'autres sont additionnées. Dans l'exemple de vote, les valeurs marginales de la variable Vote.for.X sont 2 + 0 = 2, 0 + 1 = 1, et
1 + 1 = 2. On peut bien sûr les obtenir via la matrice la fonction apply ():
  1. > apply(ctt,1,sum)
  2. No Not Sure Yes
  3. 2 1 2
Source code
Notez que les étiquettes ici, telles que Non, proviennent des noms de lignes de la matrice, dont table () produit.
Mais R fournit une fonction addmargins () à cet effet, c'est-à-dire pour trouver des totaux marginaux. Voici un exemple:
  1. > addmargins(cttab)
  2. Voted.for.X.Last.Time
  3. Vote.for.X No Yes Sum
  4. No 2 0 2
  5. Not Sure 0 1 1
  6. Yes 1 1 2
  7. Sum 3 2 5
Source code
Ici, nous avons obtenu les données marginales pour les deux dimensions à la fois, superposées conve- nablement sur la table originale.
Nous pouvons obtenir les noms des dimensions et des niveaux à travers dimnames (), comme suit:
  1. > dimnames(cttab)
  2. $Vote.for.X
  3. [1] "No" "Not Sure" "Yes"
  4. $Voted.for.X.Last.Time
  5. [1] "No" "Yes"
Source code

Avec des facteurs, nous avons encore un autre membre de la famille des fonctions d'application, tapply. Nous examinerons cette fonction, ainsi que deux autres fonctions couramment utilisées avec les facteurs: split () et par ().


1 La fonction tapply ()
En tant que motivation, supposons que nous avons un vecteur x des âges des électeurs et un facteur f montrant un trait non-uniforme de ces électeurs, comme l'affiliation du parti (démocrate, républicain, non affilié). Nous souhaitons trouver les âges moyens dans x dans chacun des groupes du parti.
Dans l'utilisation typique, l'appel tapply (x, f, g) a x comme vecteur, f comme facteur ou liste de facteurs, et g comme fonction. La fonction g () dans notre petit exemple ci-dessus serait la fonction mean() de R intégrée. Si nous voulions regrouper par les deux partis et un autre facteur, disons genre, nous aurions besoin de f pour constituer les deux facteurs, le parti et le genre.
Chaque facteur dans f doit avoir la même longueur que x. Cela a du sens à la lumière de l'exemple de l'électeur ci-dessus; nous devrions avoir autant d'affiliations de partis que d'âges. Si un composant de f est un vecteur, il sera forcé dans un facteur en lui appliquant le factor ().
L'opération effectuée par tapply () consiste à  diviser x en groupes, chaque groupe correspondant à un niveau du facteur (ou une combinaison de niveaux des facteurs dans le cas de plusieurs facteurs), puis appliquer g ( ) aux sous-vecteurs résultants de x. Voici un petit exemple:

  1. > ages <- c(25,26,55,37,21,42)
  2. > affils <- c("R","D","D","R","U","D")
  3. > tapply(ages,affils,mean)
  4. D R U
  5. 41 31 21
Source code

Examinons ce qui s'est passé. La fonction tapply () a traité le vecteur ("R", "D", "D", "R", "U", "D") comme facteur avec les niveaux "D", "R" et "U" . Il a noté que "D" se produisait dans les indices 2, 3 et 6; "R" s'est produit dans les indices 1 et 4; et "U" se sont produits dans l'indice 5. Pour plus de commodité, on se réfère respectivement aux trois vecteurs d'index (2,3,6), (1,4) et (5) comme x, y et z. Ensuite, tapply () compécialisé signifie (u [x]), signifie (u [y]) et signifie (u [z]) et renvoyé ces moyens dans un vecteur à trois éléments. Et les noms d'éléments de ce vecteur sont "D", "R" et "U", reflétant les niveaux de facteur utilisés par tapply ().
Et si nous avons deux ou plusieurs facteurs? Ensuite, chaque facteur produit un ensemble de groupes, comme dans l'exemple précédent, et les groupes sont gérés ensemble.
À titre d'exemple, supposons que nous disposions d'un ensemble de données économiques qui comprend des variables pour le genre, l'âge et le revenu. Ici, l'appel tapply (x, f, g) pourrait avoir x comme revenu et f comme une paire de facteurs: l'un pour le genre et l'autre codant si la personne est plus âgée ou plus jeune que 25. Nous pourrions être intéressés par trouver un revenu moyen, ventilé selon le sexe et l'âge. Si nous définissons g () comme mean(), tapply () renverra les revenus moyens dans chacun des quatre sous-groupes:
• Homme et moins de 25 ans
• Femmes et moins de 25 ans
• Homme et plus de 25 ans
• Femmes et plus de 25 ans
Voici un exemple de jouet de ce paramètre:

  1. > d <- data.frame(list(gender=c("M","M","F","M","F","F"),
  2. + age=c(47,59,21,32,33,24),income=c(55000,88000,32450,76500,123000,45650)))
  3. > d
  4. gender age income
  5. 1 M 47 55000
  6. 2 M 59 88000
  7. 3 F 21 32450
  8. 4 M 32 76500
  9. 5 F 33 123000
  10. 6 F 24 45650
  11. > d$over25 <- ifelse(d$age > 25,1,0)
  12. > d
  13. gender age income over25
  14. 1 M 47 55000 1
  15. 2 M 59 88000 1
  16. 3 F 21 32450 0
  17. 4 M 32 76500 1
  18. 5 F 33 123000 1
  19. 6 F 24 45650 0
  20. > tapply(d$income,list(d$gender,d$over25),mean)
  21. 0 1
  22. F 39050 123000.00
  23. M NA 73166.67
Source code
Nous avons spécifié deux facteurs, le sexe et l'indicateur variable pour un âge supérieur ou inférieur à 25. Étant donné que chacun de ces facteurs a deux niveaux, tapply () a divisé les données sur le revenu en quatre groupes, un pour chaque combinaison de genre et d'âge, puis appliqué à la fonction mean () à chaque groupe.

 

2 La fonction split ()
Contrairement à tapply (), qui divise un vecteur en groupes, puis applique une fonction spécifiée sur chaque groupe, split () s'arrête à cette première étape, en formant uniquement les groupes.
La forme de base, sans cloches et sifflets, est divisée (x, f), avec x et f jouant des rôles semblables à ceux de l'appel tapply (x, f, g); c'est-à-dire x étant un vecteur ou un cadre de données et f étant un facteur ou une liste de facteurs. L'action consiste à diviser x en groupes, qui sont renvoyés dans une liste. (Notez que x est autorisé à être un cadre de données avec split () mais pas avec tapply ().)
Essayons avec notre exemple précédent.

  1. > d
  2. gender age income over25
  3. 1 M 47 55000 1
  4. 2 M 59 88000 1
  5. 3 F 21 32450 0
  6. 4 M 32 76500 1
  7. 5 F 33 123000 1
  8. 6 F 24 45650 0
  9. > split(d$income,list(d$gender,d$over25))
  10. $F.0
  11. [1] 32450 45650
  12. $M.0
  13. $F.1
  14. [1] 123000
  15. $M.1
  16. [1] 55000 88000 76500
Source code

La sortie de split () est une liste, et rappelons que les composants de la liste sont indiqués par des signes de dollar. Ainsi, le dernier vecteur, par exemple, a été nommé "M.1" pour indiquer que c'était le résultat de la combinaison de "M" dans le premier facteur et 1 dans la seconde.
Dans une autre illustration, considérez notre exemple d'abeille à partir de la section 2.9.2. Nous voulions déterminer les indices des éléments vectoriels correspondant aux hommes, aux femmes et aux nourrissons. Les données contenues dans ce petit exemple sont composées du vecteur d'observation sept ("M", "F", "F", "I", "M", "M", "F"), affecté à g. Nous pouvons le faire en un éclair avec split ().

  1. > g <- c("M","F","F","I","M","M","F")
  2. > split(1:7,g)
  3. $F
  4. [1] 2 3 7
  5. $I
  6. [1] 4
  7. $M
  8. [1] 1 5 6
Source code
Les résultats montrent que les cas féminins sont dans les enregistrements 2, 3 et 7; l'affaire du nourrisson est au record 4; et les cas masculins sont dans les enregistrements 1, 5 et 6.
Disons cela étape par étape. Le vecteur g, pris comme facteur, comporte trois niveaux: "M", "F" et "I". Les indices correspondant au premier niveau sont 1, 5 et 6, ce qui signifie que g [1], g [5] et g [6] ont tous la valeur "M". Donc, R définit la composante M de la sortie aux éléments 1, 5 et 6 de 1: 7, qui est le vecteur (1,5,6).
Nous pouvons adopter une approche similaire pour simplifier le code dans notre exemple de concen- tration de texte de la section 4.2.4. Là, nous avons souhaité entrer un fichier texte, déterminer les mots dans le texte, puis afficher une liste indiquant les mots et leurs emplacements dans le texte. Nous pouvons utiliser split () pour rédiger le code comme suit:

  1. 1 findwords <- function(tf) {
  2. 2 # read in the words from the file, into a vector of mode character
  3. 3 txt <- scan(tf,"")
  4. 4 words <- split(1:length(txt),txt)
  5. 5 return(words)
  6. 6 }
Source code

L'appel de la fonction scan() renvoie une liste txt des mots lus à partir du fichier tf. Ainsi, txt [[1]] contiendra la première entrée de mot du fichier, txt [[2]] contiendra le deuxième mot, et ainsi de suite; longueur (txt) sera donc le nombre total de mots lus. Supposons pour le concret que ce chiffre soit 220.
Pendant ce temps, txt lui-même, comme deuxième argument en split () ci-dessus, sera considéré comme un facteur. Les niveaux de ce facteur seront les différents mots dans le fichier. Si, par exemple, le fichier contient le mot monde 6 fois et le climat était là 10 fois, alors «monde» et «climat» seront deux des niveaux de txt.
L'appel de split() déterminera alors où ces et les autres mots apparaissent dans txt.


3 La fonction by ()
Supposons que dans l'exemple de l'ormeau, nous souhaitons effectuer des analyses de régression de diamètres contre la longueur séparément pour chaque code de genre: mâles, femelles et nourrissons. Au début, cela semble être adapté pour tapply (), mais le premier argument de cette fonction doit être un vecteur, pas une matrice ou un cadre de données. La fonction à appliquer peut être multivariée - par exemple, gamme () - mais l'entrée doit être un vecteur. Pourtant, l'entrée pour la régression est une matrice (ou une trame de données) avec au moins deux colonnes: une pour la variable prédite et une ou plusieurs pour les variables prédictives. Dans notre application de données d'ormeaux, la matrice serait constituée d'une colonne pour les données de diamètre et d'une colonne de longueur.
La fonction by () peut être utilisée ici. Il fonctionne comme tapply () (qu'il appelle en interne, en fait), mais il est appliqué aux objets plutôt qu'aux vecteurs. Voici comment l'utiliser pour les analyses de régression souhaitées:

  1. > aba <- read.csv("abalone.data",header=TRUE)
  2. > by(aba,aba$Gender,function(m) lm(m[,2]~m[,3]))
  3. aba$Gender: F
  4. Call:
  5. lm(formula = m[, 2] ~ m[, 3])
  6. Coefficients:
  7. (Intercept) m[, 3]
  8. 0.04288 1.17918
Source code

  1. aba$Gender: I
  2. Call:
  3. lm(formula = m[, 2] ~ m[, 3])
  4. Coefficients:
  5. (Intercept) m[, 3]
  6. 0.02997 1.21833
Source code

  1. aba$Gender: M
  2. Call:
  3. lm(formula = m[, 2] ~ m[, 3])
  4. Coefficients:
  5. (Intercept) m[, 3]
  6. 0.03653 1.19480
Source code

Les appels à par () ressemblent très bien aux appels à tapply (), avec le premier argument spécifiant nos données, le second étant le facteur de regroupement et le troisième la fonction à appliquer à chaque groupe.
Tout comme tapply () forme des groupes d'indices d'un vecteur selon les niveaux d'un facteur, cet appel par () trouve des groupes de nombres de lignes de la base de données aba. Cela crée trois cadres de sous-données: un pour chaque niveau de genre de M, F et I.
La fonction anonyme que nous avons définie regroupe la deuxième colonne de son argument matriciel m contre le t Troisième colonne. Cette fonction sera appelée trois fois, une fois pour chacune des trois images de sous-données créées précédemment, produisant ainsi les trois analyses de régression.

 

 

 

Un facteur R peut être considéré simplement comme un vecteur avec un peu plus d'informations ajoutées (cependant, comme on le voit ci-dessous, il est différent de celui-ci en interne). Cette information supplémentaire consiste en un enregistrement des valeurs distinctes dans ce vecteur, appelé niveaux. Voici un exemple:
  1. > x <- c(5,12,13,12)
  2. > xf <- factor(x)
  3. > xf
  4. [1] 5 12 13 12
  5. Levels: 5 12 13
Source code
Les valeurs distinctes dans xf 5, 12 et 13 sont les niveaux. Jetons un coup d'oeil à l'intérieur:
  1. > str(xf)
  2. Factor w/ 3 levels "5","12","13": 1 2 3 2
  3. > unclass(xf)
  4. [1] 1 2 3 2
  5. attr(,"levels")
  6. [1] "5" "12" "13"
Source code
C'est révélateur. Le noyau de xf  n'est pas (5,12,13,12), mais plutôt (1,2,3,2). Ce dernier signifie que nos données sont d'abord une valeur de niveau 1, puis des valeurs de niveau 2 et de niveau 3, et enfin une autre valeur de niveau 2. Donc, les données ont été recodées par niveau. Les niveaux eux-mêmes sont également enregistrés.
La longueur d'un facteur est toujours définie en fonction de la longueur des données:
  1. > length(xf)
  2. [1] 4
Source code
Nous pouvons anticiper les nouveaux niveaux futurs, comme on le voit ici:
  1. > x <- c(5,12,13,12)
  2. > xff <- factor(x,levels=c(5,12,13,88))
  3. > xff
  4. [1] 5 12 13 12
  5. Levels: 5 12 13 88
  6. > xff[2] <- 88
  7. > xff
  8. [1] 5 88 13 12
  9. Levels: 5 12 13 88
Source code
À l'origine, xff ne contenait pas la valeur 88, mais en la définissant, nous avons autorisé cette possibilité future. Plus tard, nous avons effectivement ajouté la valeur.
De la même façon, vous ne pouvez pas se faufiler dans un niveau "illégal". Voici ce qui se passe lorsque vous essayez:
  1. > xff[2] <- 28
  2. Warning message:
  3. In `[<-.factor`(`*tmp*`, 2, value = 28) :
  4. invalid factor level, NAs generated
Source code