La famille apply en une phrase
Chaque membre de la famille apply fait le même travail : prendre une fonction et l'exécuter sur chaque élément d'une structure, en collectant les résultats. Ce qui varie, c'est la forme d'entrée et la forme de sortie. La carte :
apply(m, MARGIN, FUN)- lignes ou colonnes d'une matrice.lapply(x, FUN)- chaque élément d'une liste ou d'un vecteur ; renvoie toujours une liste.sapply(x, FUN)- commelapply, puis simplifie en vecteur ou en matrice quand c'est possible.vapply(x, FUN, FUN.VALUE)-sapplyavec un type de retour déclaré et imposé.mapply(FUN, x, y, ...)- parcourt plusieurs entrées en parallèle, élément par élément.tapply(values, groups, FUN)- appliqueFUNà l'intérieur de chaque groupe : un résultat par groupe.
Toutes acceptent les fonctions que tu écris toi-même, y compris anonymes, et c'est de là que la famille tire sa puissance.
apply() : lignes et colonnes d'une matrice
apply prend une matrice, un MARGIN (1 pour les lignes, 2 pour les colonnes) et une fonction :
La matrice se remplit colonne par colonne, donc les lignes sont 1 3 5 et 2 4 6 : les sommes par ligne s'affichent 9 12 et les sommes par colonne 3 7 11. Pour les deux cas les plus courants, R de base livre des helpers dédiés et plus rapides - rowSums, colSums, rowMeans, colMeans - alors réserve apply aux fonctions qui n'en ont pas, comme apply(m, 2, max).
Un piège : apply sur un data frame le convertit silencieusement en matrice d'abord, ce qui force toutes les colonnes vers un type commun. Pour les data frames, traite les colonnes comme une liste et utilise plutôt lapply/sapply.
lapply() donne toujours une liste, sapply() simplifie
lapply applique une fonction à chaque élément et renvoie une liste de même longueur, quoi qu'il arrive :
Même calcul, deux formes : lapply rend une liste contenant 85 et 80 ; sapply remarque que chaque résultat est de longueur 1 et simplifie en un vecteur numérique nommé - bien plus agréable à lire et à réinjecter dans d'autres calculs.
Cette commodité a toutefois un tranchant : le type de retour de sapply dépend des données. Si ne serait-ce qu'un élément produit une longueur différente, la simplification échoue et tu récupères silencieusement une liste :
En interactif, on hausse les épaules ; dans un script, ça signifie qu'un code qui marchait toute l'année casse le jour où les données changent de forme. C'est toute la raison d'être de vapply.
vapply() : le sapply à type sûr
vapply ajoute un troisième argument, FUN.VALUE : un gabarit déclarant à quoi doit ressembler un résultat. integer(1) signifie « chaque appel renvoie exactement un entier » :
Tu obtiens un vecteur d'entiers nommé - 5 6 6 - et, plus important, une garantie : si nchar renvoyait un jour deux valeurs ou du texte, vapply s'arrêterait sur une erreur au site d'appel au lieu de laisser un résultat mal formé filer plus loin. La déclaration sert aussi de documentation :
vapply(words, nchar, character(1))
# Error in vapply(words, nchar, character(1)) : values must be type 'character'
Règle empirique : sapply en console, vapply dans les fonctions et les scripts qui doivent tourner sans surveillance.
mapply() et tapply() : entrées parallèles et groupes
lapply parcourt une structure. Quand chaque appel a besoin d'un élément de plusieurs structures à des positions correspondantes, utilise mapply - note que la fonction vient en premier :
Chaque élément de sortie associe les premières valeurs, puis les deuxièmes, et ainsi de suite : 10 200 3000 40000.
tapply est le membre travaillant par groupes : il découpe values selon groups et applique la fonction dans chaque groupe, renvoyant un résultat par groupe :
Le groupe a a une moyenne de 30, le groupe b de 40. C'est la réponse de R de base à « moyenne par catégorie » - la même forme de question à laquelle répond group_by + summarize pour les data frames, donc si tu es déjà en territoire dplyr utilise ça ; tapply brille quand tu as deux vecteurs nus et que tu veux une seule ligne.
Deux commodités valent pour toute la famille. Les arguments supplémentaires placés après la fonction lui sont transmis à chaque appel :
Et les fonctions anonymes s'insèrent dès qu'aucune fonction toute faite ne convient - sapply(x, \(v) max(v) - min(v)) calcule une étendue par élément sans avoir à nommer un helper d'abord.
Famille apply ou boucles for
Tu entendras dire que les boucles for sont lentes en R et que la famille apply est rapide. C'est surtout un mythe. Ce qui est réellement lent, c'est de faire grandir un résultat dans une boucle - result <- c(result, new_value) recopie tout le vecteur à chaque passage. Une boucle qui préalloue sa sortie se comporte très bien :
Choisis donc selon la lisibilité, pas la performance. La version apply dit quoi en une ligne - « élève chaque élément au carré » - et gère l'allocation pour toi, ce qui en fait l'idiome pour le travail élément par élément direct. Une boucle for gagne sa place quand les itérations dépendent des résultats précédents, quand tu as besoin de sorties anticipées avec break, ou quand le corps est assez long pour qu'une lambda fasse plus de mal que de bien. Les deux sont du R légitime.
Ce que tu retiens
- Toute la famille repose sur une seule idée : exécuter une fonction sur chaque élément, collecter les résultats.
applyest pour les lignes (MARGIN = 1) et les colonnes (MARGIN = 2) de matrice ; préfèrerowSums/colMeansquand ils existent.lapplyrenvoie toujours une liste ;sapplysimplifie quand il peut - ce qui veut dire que son type de retour peut changer avec les données.vapplyverrouille le type de retour ; utilise-le dans du code qui ne doit pas te surprendre.mapplyassocie plusieurs entrées ;tapplyagrège les valeurs à l'intérieur de groupes.- Les boucles for ne sont pas intrinsèquement lentes - faire grandir des vecteurs, si. Choisis l'écriture qui se lit le mieux.
Prochaine étape : les pipes - l'opérateur qui enchaîne ces appels en pipelines lisibles, étape par étape.
Questions fréquentes
Quelle est la différence entre lapply et sapply en R ?
lapply renvoie toujours une liste, sans exception. sapply fait le même calcul puis essaie de simplifier le résultat - en vecteur si chaque élément est de longueur 1, en matrice s'ils partagent une longueur, et en liste s'il n'y arrive pas. sapply est plus agréable en interactif ; lapply (ou vapply) est plus sûr dans les scripts parce que son type de retour ne change jamais.
Que fait apply() en R ?
apply(m, MARGIN, FUN) exécute FUN sur une matrice : MARGIN = 1 l'applique à chaque ligne, MARGIN = 2 à chaque colonne. apply(m, 1, sum) donne les sommes par ligne, apply(m, 2, mean) les moyennes par colonne. C'est pour les matrices et les tableaux - pour les listes et les vecteurs, utilise plutôt lapply/sapply.
La famille apply est-elle plus rapide qu'une boucle for en R ?
Généralement pas de beaucoup - c'est un mythe. Une boucle for bien écrite avec un vecteur de résultats préalloué est comparable. Les boucles qui ont donné mauvaise réputation à for font grandir leur résultat un élément à la fois, en recopiant tout à chaque passage. Choisis les fonctions de la famille apply pour la concision et l'intention, pas pour la vitesse.
À quoi sert vapply en R ?
vapply, c'est sapply avec un contrat : tu déclares le type et la longueur de chaque résultat, par exemple vapply(x, nchar, integer(1)). Si la fonction renvoie autre chose, R lève immédiatement une erreur au lieu de te tendre silencieusement une structure inattendue. Préfère-le dans du code qui doit continuer à tourner sans surveillance.