Last chance! 50% off DataCamp Premium
Sale ends in
De R Apply-familie is een set functies in R waarmee je een functie kunt toepassen op elementen van een vector, lijst of matrix. Het wordt echter beschouwd als verouderde functionaliteit en zou je niet meer voor nieuwe code moeten gebruiken. In plaats daarvan wordt aangeraden om het purrr-pakket te gebruiken voor al het loopen in R. Het purrr-pakket biedt een consistente syntaxis voor het werken met functies die meerdere input- en outputwaarden aannemen, waardoor code schrijven en lezen makkelijker wordt. Daarnaast biedt purrr een reeks functies die zijn geoptimaliseerd voor moderne computeromgevingen, waardoor het sneller en efficiënter is dan de Apply-familie. Al met al, hoewel de R Apply-familie voor sommige toepassingen nog kan werken, is het verstandig om voor alle nieuwe R-projecten het purrr-pakket te gebruiken.
In deze post laat ik je zien hoe je de R apply()-functie kunt gebruiken, de varianten zoals mapply() en een paar van apply()’s verwanten, toegepast op verschillende datastructuren. Natuurlijk kunnen niet alle varianten aan bod komen, maar waar mogelijk maak je kennis met het gebruik van deze functies in combinatie, aan de hand van een paar iets uitgebreidere voorbeelden.
Ook kan het handig zijn om deze introductiecursus R te bekijken om lijsten, vectoren, arrays en dataframes beter te begrijpen, al hoef je de tutorial niet per se te hebben afgerond om deze post te volgen!
apply()-familieDe apply()-familie hoort bij het R base-pakket en bestaat uit functies om herhaaldelijk slices van gegevens uit matrices, arrays, lijsten en dataframes te bewerken. Deze functies laten je de data op verschillende manieren doorkruisen en vermijden expliciet gebruik van loop-constructies. Ze werken op een invoerlijst, matrix of array en passen een benoemde functie toe met één of meerdere optionele argumenten.
De aangeroepen functie kan zijn:
De apply()-functies vormen de basis voor complexere combinaties en helpen om operaties uit te voeren met heel weinig regels code. Concreet bestaat de familie uit de functies apply(), lapply() , sapply(), vapply(), mapply(), rapply() en tapply().
Maar hoe en wanneer gebruik je deze?
Dat hangt af van de structuur van de data waarop je wilt opereren en het formaat van de output die je nodig hebt.
Laten we beginnen met de peetvader van de familie, apply(), die werkt op arrays. Voor de eenvoud beperkt de tutorial zich tot 2D-arrays, ook wel matrices genoemd.
De R base-handleiding zegt dat je hem als volgt aanroept: apply(X, MARGIN, FUN, ...)
waarbij:
X een array is of een matrix als de dimensie van de array 2 is;MARGIN een variabele is die bepaalt hoe de functie wordt toegepast: wanneer MARGIN=1, wordt over de rijen gewerkt, terwijl bij MARGIN=2 over de kolommen wordt gewerkt. Let op: wanneer je de constructie MARGIN=c(1,2) gebruikt, wordt op zowel rijen als kolommen toegepast; enFUN de functie is die je op de data wilt toepassen. Dit kan elke R-functie zijn, inclusief een door de gebruiker gedefinieerde functie (UDF).Beginners hebben soms moeite om te visualiseren wat er gebeurt, dus een plaatje en wat code komen goed van pas om dit te doorgronden.
Laten we een 5 x 6-matrix construeren en stel dat je de waarden van elke kolom wilt optellen.
Je kunt zoiets schrijven als:
# Construct a 5 x 6 matrix
my_matrix <- matrix(1:30, nrow = 5, ncol = 6)
# Calculate the sum of each column
col_sums <- apply(my_matrix, 2, sum)
# Print the result
print(col_sums)
De functie matrix() maakt een 5 x 6-matrix met waarden van 1 tot 30. We gebruiken vervolgens de apply()-functie om de functie sum() toe te passen op elke kolom van de matrix (2 geeft aan dat we de functie op kolommen willen toepassen). De resulterende vector met kolomsommen wordt opgeslagen in de variabele col_sums en op de console afgedrukt.
Onthoud dat je in R een matrix kunt zien als een verzameling rijvectoren wanneer je de matrix van boven naar beneden doorkruist (langs de verticale lijn 1, die de dimensie of marge 1 specificeert), of als een lijst van kolomvectoren, van links naar rechts langs dimensie of marge 2.
Dat betekent dat de instructie die je zojuist hebt ingevoerd, weergegeven in figuur 1, neerkomt op: “pas de functie ‘sum’ toe op matrix X langs marge 2 (per kolom), waarbij je de waarden van elke kolom optelt.”
Merk op dat, om het plaatje overzichtelijk te houden, slechts één van de kolommen is gemarkeerd.
Je eindigt met een rijvector met de sommen van de waarden van elke kolom.
De output van de bovenstaande code, een rijvector, zou ook zijn gegeven als je langs de rijen van de matrix had opgeteld. Dit is hoe R het resultaat weergeeft.
Een kanttekening voor het volgende: in de meeste gevallen kan R een waarde teruggeven, zelfs als die niet is gespecificeerd, of preciezer: als de geretourneerde waarde van de functie niet aan een variabele is toegewezen. R retourneert simpelweg het laatst geëvalueerde object. In de praktijk is het echter, wanneer je de retourwaarde wilt controleren en wanneer je verdere bewerkingen op die waarden wilt doen, het beste om de resultaten van een functie expliciet aan een variabele toe te wijzen.
lapply()-functieJe wilt een bepaalde functie toepassen op elk element van een lijst en een lijst als resultaat krijgen. Wanneer je ?lapply uitvoert, zie je dat de syntaxis lijkt op die van de apply()-functie.
Het verschil is dat:
Om te zien hoe dit werkt, maak je een paar matrices en haal je uit elke matrix een bepaalde kolom.
Dit is een vrij gebruikelijke bewerking op echte data bij het vergelijken of aggregeren vanuit verschillende dataframes.
Ons speelgoedvoorbeeld, weergegeven in figuur 2, kan als volgt worden gecodeerd:

De bewerking staat weergegeven aan de linkerkant van figuur 2.
Opnieuw begin je met het specificeren van het object van interesse, de lijst Mylist. Je gebruikt de standaard R-selectie-operator [ en laat dan de eerste parameter weg (wat dus neerkomt op “alle”, daarom zie je de twee komma’s).
Vervolgens geef je de tweede parameter op, namelijk 2: onze marge is ‘kolom’. Je haalt dus de tweede kolom uit alle matrices in de lijst.
Een paar opmerkingen bij de bovenstaande code:
[-notatie is de select-operator. Onthoud bijvoorbeeld dat je om alle elementen van de derde rij van B te extraheren, nodig hebt: B[3,];[[ ]]-notatie drukt uit dat we met lijsten werken: [[2]] betekent het tweede element van de lijst. Dit zie je ook in de output die R geeft;lapply(MyList,"[", 1, 2)Rechts in figuur 2 zie je een alternatieve extractie: dit keer laat je de eerste parameter weg en krijg je de eerste rij uit elk van de matrices.
Probeer het zelf! Selecteer de tweede kolom uit elke matrix in de lijst:
sapply()-functieDe functie sapply() werkt als lapply(), maar probeert de output te vereenvoudigen tot de meest elementaire datastructuur die mogelijk is. En inderdaad, sapply() is een ‘wrapper’-functie voor lapply().
Een voorbeeld helpt: stel dat je de extractie van één enkel element uit het vorige voorbeeld wilt herhalen, maar nu het eerste element van de tweede rij (indexen 2 en 1) voor elke matrix neemt.
Het toepassen van de functie lapply() zou ons een lijst geven, tenzij je simplify=FALSE als parameter doorgeeft aan sapply(). Dan wordt er een lijst geretourneerd. Zie in het codeblok hieronder hoe dit werkt:
Omgekeerd kan een functie als unlist() lappy() vertellen om je een vector te geven!
Hoe dan ook, om verwarring te vermijden, is het het beste om deze functies in hun ‘natuurlijke formaat’ te gebruiken en conversies te vermijden tenzij strikt noodzakelijk.
rep()-functieIets wat vaak samen met apply()-functies wordt gebruikt, is rep(). Wanneer je deze toepast op een vector of factor x, dupliceert de functie de waarden een opgegeven aantal keren.
Laten we een van de vectoren gebruiken die je hierboven met lapply() in MyList hebt gegenereerd.
Dit keer selecteer je echter alleen de elementen van de eerste rij en eerste kolom uit elk element van de lijst MyList (en je gebruikt sapply() om een vector te krijgen):
Je ziet dat de bovenstaande code de waarden van Z een aantal keren repliceert zoals vastgesteld door c(3,1,2): drie keer de eerste, één keer de tweede en twee keer de derde:
Handig, toch?
mapply()-functieDe functie mapply() staat voor ‘multivariate’ apply. Het doel is om argumenten te vectoriseren voor een functie die normaal gesproken geen vectoren als argumenten accepteert.
Kort gezegd past mapply() een functie toe op meerdere lijst- of meerdere vectorargumenten.
Laten we een mapply()-voorbeeld bekijken waarin je een 4 x 4-matrix maakt met een herhaalde aanroep van de functie rep():
Maar je ziet dat er een efficiëntere manier is om de resultaten van de functie rep() te binden dan met c(): wanneer je mapply() aanroept, vectoriseer je de actie van de functie rep().
apply()Vergelijkbaar gestructureerde functies worden af en toe samen met elementen van de apply()-familie gebruikt: de tutorial geeft slechts een overzicht van enkele daarvan.
Sweep()-functieDe functie sweep() lijkt waarschijnlijk het meest op de apply()-familie. Je gebruikt hem wanneer je verschillende acties wilt herhalen op de MARGIN-elementen die je hebt gekozen (hier beperkt tot het matrixgeval).
Een typisch scenario doet zich voor bij clusteren, waar je herhaaldelijk genormaliseerde en gecentreerde of “gestandaardiseerde” data moet produceren.
Wat betekent dit?
Stel dat je een aantal datapunten hebt in een groep data. Je zoekt eerst het centrum van de data (“zwaartepunt”) en kijkt hoe verspreid deze data zijn ten opzichte van dit centrum. Twee basisgrootheden geven je deze informatie: het gemiddelde en de standaarddeviatie.
Stel dat je datapunten de kolomvectoren zijn in een matrix met je data en laten we de matrix B gebruiken die aan het begin van deze post is gemaakt, maar die je nu dataPoints noemt.
Je bepaalt eerst de gemiddelden per kolom en de spreiding of standaarddeviatie met behulp van een van de apply()-functies. Daarna verschuif je alle punten ten opzichte van hun centrum. Dit betekent dat het gemiddelde dat je als eerste hebt gevonden, wordt gebruikt om de data te normaliseren ten opzichte van hun standaarddeviatie:
Je hebt de gecentreerde punten geproduceerd met één aanroep van sweep(). Deze functie verwacht de volgende elementen:
2 om de kolommen aan te geven;mean); en“-” voor aftrekken.Dit betekent: “neem de elementen van de kolommen van de dataset MyPoints en trek het gemiddelde, dataPoints_means, van elk daarvan af”.
Nu roep je sweep() opnieuw aan om alle waarden die je zojuist hebt gevonden te delen door hun eigen standaarddeviatie. Deze stap heet “normalisatie”. Opnieuw selecteer je MARGIN=2, en geef je vervolgens de vector van de standaarddeviaties, dataPoints_sdev, mee als operand. Vervolgens geef je de ‘deel door’-operator, “/”, door.
Wat je aan R vraagt is het volgende: “neem de elementen van de kolommen van het nieuwe object dat je net hebt gemaakt, dataPoints_Trans1, en deel deze (“/”) door hun standaarddeviatie dataPoints_sdev.”
Natuurlijk had je hetzelfde resultaat sneller en bondiger kunnen verkrijgen (zoals vaak in R!) en zonder verschillende namen te gebruiken, met één regel code met een geneste aanroep van sweep():
Statistisch gezien heb je zojuist een correlatiematrix gecreëerd, en gestandaardiseerde data vormen de basis van verschillende meer geavanceerde procedures op data (zoals dimensionaliteitsreductie via PCA, signaalanalyse en andere).
aggregate()-functieDeze functie zit in het stats-pakket, en je gebruikt hem als volgt: aggregate(x, by, FUN, ..., simplify = TRUE).
Met andere woorden, hij werkt vergelijkbaar met de apply()-functie: je specificeert het object, de functie en je geeft aan of je wilt vereenvoudigen, net als bij de functie sapply(). Het cruciale verschil is het gebruik van de by-clausule, die de variabele of het dataframeveld instelt waarop we de aggregatie willen uitvoeren.
In de volgende sectie zie je hoe dit werkt.
aggregate()Beschouw de speelgoeddataset Mydf, die gegevens bevat over de verkoop van een product en waarin sommige waarden in de kolom DepPC zich herhalen.
Deze variabele classificeert de gegevens op een geografische locatie, zoals het deel van een postcode (hier komen de nummers overeen met de departementen van de Île-de-France, de regio rond Parijs).
Je wilt wat statistiek doen op de verkoopkolommen. Dat zijn DProgr, een oplopend nummer in toenemende tijdsvolgorde, en de verkopen van het product (de hoeveelheid Qty), plus een logische variabele, Delivered, die aangeeft of het product is geleverd (T) of niet (F).
Eerst kun je een aantal heel eenvoudige dingen doen om vertrouwd te raken met de dataset, anders dan alles tonen door simpelweg de naam te typen (hier hebben we slechts 120 records, maar stel je voor dat je dit doet voor een echt bestand met duizenden regels!).
Laten we de data verkennen:
Let op dat je, als je het aantal rijen en kolommen van het dataframe wilt zien, ook had kunnen aanroepen nrow(Mydf) en ncol(Mydf).
Veel meer vragen over de data zijn mogelijk.
Hier ben je bijvoorbeeld geïnteresseerd in waar het product het best verkoopt, in welk departement. Daarom moet je de data groeperen per departement, waarbij je de verkopen, Qty, per departement DepPC optelt met behulp van de functie aggregate():
Dus, aggregate() vertelt R dat je alle Qty wilt optellen die tot hetzelfde departement behoren.
Let op dat R de som aan een variabele ‘x’ heeft toegewezen, omdat je niets anders hebt aangegeven.
De output is vrij leesbaar zoals hij is, maar bij een groter aantal departementen kan dit minder overzichtelijk zijn. In zulke gevallen kun je een grafische weergave gebruiken: je plot de resultaten met een van R’s grafische systemen, samen met de functie aggregate():
Dit geeft ons de verkoop per departement.
Je kunt jezelf dezelfde vraag stellen, maar dan alleen voor de goederen die geleverd zijn. Om dit te doen subset je eerst de data waarvoor delivered waar (T) is met de inmiddels bekende subset-operator "[".
Let op dat je hier het resultaat toewijst aan een nieuwe variabele Y, een nieuw dataframe dat dezelfde kolomnamen erft van het bovenliggende dataframe Mydf. Dit doe je om te vermijden dat je de aggregate-instructie in de plot-aanroep moet herhalen voor de leesbaarheid:
Je had dus verschillende vragen aan de data kunnen stellen op een gevectoriseerde manier zoals met aggregate(), en dit doe je vaak in combinatie met een handig plotsysteem zoals ggplot2, zodat je het idee te pakken hebt.
Let op dat je hiervoor maar heel weinig regels code nodig had.
Je hebt een paar variaties op hetzelfde thema gezien, namelijk “voer herhaaldelijk acties uit op een gestructureerde set data”. In die zin kun je deze functies zien niet alleen als een alternatief voor loops, maar ook als een gevectoriseerde manier van werken.
“Gevectoriseerd” hier in losse zin; we gaan niet in op het debat of – en welke van de – apply()-functies daadwerkelijk gevectoriseerd zijn (zie bijvoorbeeld de discussie hier).
In de praktijk moet je, om te kiezen welke apply()-functie je gebruikt, het volgende overwegen:
FUN-functie die je wilt doorgevenDit zijn vrij algemene vragen die je ook kunt stellen voor de gerelateerde functies, waarvan we aggregate(), by(), sweep(), enz. hebben bekeken.
Maar er zijn er nog veel meer! Stop nu niet met ontdekken! Bekijk de DataCamp-tutorial Arrays in R.
Als vervolg op deze tutorial kun je de DataCamp-cursussen Introduction to R of Intermediate R overwegen.
Leer meer over R
Cursus
Cursus
Cursus

blog
Adel Nehme
15 min