In deze tutorial leer je de basics van R. Je leert vooral zaken die een randvoorwaarde zijn om goed met de software te kunnen werken; je wordt klaargestoomd om jezelf verder in de software te verdiepen, zelfstandig of m.b.v. meer geavanceerde cursussen. Als je deze tutorial hebt doorlopen, weet je hoe je moet R en RStudio moet werken, wat zogenaamde objecten zijn en hoe je deze aanmaakt en bewerkt, hoe je een script bouwt om je data-analyse vorm te geven, hoe je functies en packages gebruikt, en ben je bekend met de beginselen van het verkennen en analyseren van data.

Je zult in deze tutorial een aantal dingen terug zien komen. Je gaat (programmeer)code tegenkomen, met daaronder veelal het resultaat van deze code. Je kunt deze code overtypen of kopiëren en plakken in je eigen RStudio-software. De ervaring leert dat het zelf typen van de code de materie beter doet beklijven dan kopiëren en plakken. Ook zul je af en toe een grijs vlak met een vraag langs zien komen, met twee benedenwaartse pijlen. Je kunt het antwoord op de vraag zien door éénmaal op het grijze vlak te klikken. Ten slotte vind je in de rechter kantlijn een korte omschrijving van in de tekst genoemde belangrijke begrippen.

1. Inleiding

1.1. Tips voor het doornemen

Deze tutorial kun je het beste bekijken met een browserscherm dat minimaal 1500 pixels breed is. Jouw scherm is pixels breed.

Het is handig als je bij aanvang van deze tutorial al wat basale kennis over statistiek hebt. We gaan het bijvoorbeeld hebben over gemiddelden en standaarddeviaties, kwartielen en de mediaan, en we voeren t-toetsen en een chi-kwadraat-toets uit. Mocht je niet met alle begrippen bekend zijn dan is dit echter geen probleem.

Misschien is het wat veel om de tutorial in één keer te doorlopen. Met de hele tutorial zul je al gauw een paar uur zoet zijn, wellicht is het na hoofdstuk 5 een mooi moment om op een volgend moment verder te gaan. De volgende keer is ’t mogelijk wel handig om kort de eerdere hoofdstukken nog een keertje door te nemen, ter opfrissing. Leren is herhalen.

Gedurende de tutorial zul je programmeercode te zien krijgen die je kunt overnemen. Schroom niet om hierin af en toe off-piste te gaan en te experimenteren door dingen te veranderen.

Je zult zo af en toe onderlijnde woorden tegenkomen. Als je hier met je muis overheen gaat, of als je erop drukt als je een tablet/telefoon gebruikt, krijg je een verduidelijking of herhaling van het onderwerpTer illustratie. te zien.

1.2. R en RStudio

Je gebruikt deze tutorial waarschijnlijk omdat je R wilt leren gebruiken. Nu is het zo dat de standaard software voor R nogal aftands en gebruiksonvriendelijk is. Gelukkig is er RStudio, dit is een IDE om R heen. Zie RStudio als een mooie schil om een wat ontoegankelijker stukje software heen. R en RStudio zijn dus twee aparte programma’s, waarbij het laatste programma het eerste programma als het ware in zich opneemt. Er zijn meerdere IDE’s voor R (bijv. RCommander, Jupyter Notebook, Tinn-R), maar RStudio is verreweg het populairst.

In onze tutorials zullen we de namen R en RStudio door elkaar gebruiken. Hierbij is het zo dat, waar RStudio genoemd wordt, het om iets in de IDE gaat (bijv., het bekijken van plots, het klikken op een knop). Waar R genoemd wordt, gaat het meer om de code die je geschreven hebt, en eventueel wat er zich “onder de motorkap” afspeelt.

R en RStudio zijn allebei gratis, je kunt ze vinden via je favoriete zoekmachine en op elk zinnig besturingssysteem installeren.

2. Overzicht van de software

Als je RStudio voor het eerst opent, ziet je scherm er ongeveer zo uit:



We zien links een paneel met daarin de console, en rechts twee panelen met elk verschillende tabbladen.

2.1. Console

In de console zie je de daadwerkelijke R-sessie. Zou je geen RStudio gebruiken, maar het basale R, dan zou dit het scherm zijn dat je ziet. Je ziet hier een cursor, waar je een commando kunt intypen. Typ je bijvoorbeeld 1 + 1 en druk je op enter, dan krijg je te zien:

> 1 + 1
[1] 2

In de console wordt je R-code uitgevoerd.

Dit is meteen een voorbeeld van wat je in deze tutorial veel gaat tegenkomen: in een grijs vlak valt een stukje code te zien, met daaronder de uitkomst daarvan. De uitkomst [1] 2 betekent zoveel als “de eerste uitkomst is 2”. Je mag in het algemeen spaties weglaten (1+1 werkt net zo goed als 1 + 1), maar voor de leesbaarheid van je code zijn spaties meestal wel handig. Als je print("hoi!") typt, en je drukt op enter, dan zie je:

> print("hoi!")
[1] "hoi!"

Je hebt zojuist je eerste functie gebruikt, de functie print(). Later leer je meer over functies.

2.2. Editor

In de praktijk zul je niet vaak op een directe manier met het console interacteren. Meestal zul je dit doen vanuit een R-script in de source editor. Om een nieuw R-script te maken, ga je in RStudio naar File -> New File -> R Script. Je scherm ziet er dan ongeveer als volgt uit:



Er is linksbovenin een vierde paneel bijgekomen. RStudio duidt dit scherm, waarin je een script kunt schrijven, afwisselend aan met de termen Source en Editor, wij gebruiken de laatste term.

In een script kun je het hele verloop van je data-analyseproject uiteenzetten. Denk hierbij aan het inladen van data, het bewerken ervan, de data-analyse en het eventueel comprimeren van de resultaten van je analyses. Een mooi uitgangspunt hierbij is dat je je script in 1 keer zou moeten kunnen runnen, telkens weer, en dat er telkens hetzelfde uit komt. We gaan de editor later in deze tutorial uitgebreid gebruiken. Mocht je bekend zijn met het gebruik van syntaxen in SPSS, of van do-bestanden in Stata: het bouwen van een R-script is hiermee vergelijkbaar.

2.3. Environment en History

In het paneel rechtsboven zie je drie tabbladen: Environment, History en Connections. In Environment zie je een overzicht van je workspace: variabelen die je hebt aangemaakt, datasets die je hebt geopend, functies die je hebt geschreven of anderszins ingeladen. Gaandeweg de tutorial zul je hier van alles in zien verschijnen. Het tabblad History bevat een overzicht van al je laatst gebruikte commando’s. Vanuit dit tabblad kun je eerder gebruikte commando’s naar de console transporteren (om meteen uit te voeren), of naar je editor laten verplaatsen, om het op te nemen in een script dat je aan het schrijven bent. Het tabblad Connections laten we even voor wat het is. In de praktijk zul je merken dat je vooral het tabblad Environment zult gebruiken.

Het tabblad Environment geeft een weergave van je workspace.

2.4. Files, Plots, Packages en Help

Het paneel rechtsonder bevat vijf tabbladen. Dit zijn Files, Plots, Packages, Help en Viewer. In Files kun je door je bestanden bladeren, en bijv. R-scripts openen. In Plots krijg je grafieken te zien die je R hebt laten tekenen. Onder Packages kun je zgn. packages installeren en inladen. Packages zijn extra bundels met functionaliteit die door derden zijn geschreven. Op de CRAN website kun je meer informatie vinden over packages. We gaan hier later verder op in. Verder is er nog het tabblad Help, waarin je meer info kunt krijgen over functies in R. Er is ook nog het tabblad Viewer, dat wordt vooral gebruikt om webcontent te bekijken die je genereert met iets wat RMarkdown heet. Zo is deze hele tutorial bijvoorbeeld met RMarkdown geschreven, dus in RStudio. Dit laat zien dat je met R niet alleen data-analyses kunt doen en handige functionaliteit kunt programmeren om op een efficiënte manier je data te lijf te gaan; je kunt ook integraal de resultaten van je analyses verwerken in bijvoorbeeld een verslag. We gaan in deze tutorial niet op het gebruik van RMarkdown in.

3. Objecten

R is een zgn. object-georiënteerde programmeertaal. Dit betekent dat je werkt met objecten die je maakt. Een heel simpel voorbeeld is de volgende code die je in de console kunt uitvoeren:

> a <- 3

Dit betekent zoiets als “maak een variabele genaamd a aan, en stop er de waarde 3 in.” In plaats van a <- 3 kun je ook a = 3 typen, maar de conventie is om het pijltje te hanteren. Je kunt overigens ook 3 -> a typen, wat een van de voordelen van zo’n pijl aangeeft: je ziet de richting van de bewerking. Als je een object aanmaakt, komt deze in je workspace te staan. Je ziet hem dan ook verschijnen in het vak rechtsboven, in het tabblad Environment.

Nu we het object a hebben aangemaakt, kunnen we daar bewerkingen op doen. Bijvoorbeeld, er iets bij optellen:

> a + 2
[1] 5

Of, iets erbij optellen, en dat in een nieuw object opslaan:

> b <- a + 2
> b
[1] 5

Ook b zie je nu staan in je Environment. Door b als apart commando uit te voeren, krijgen we de waarde in b te zien.

3.1. Vectoren

In bovenstaand voorbeeld hebben we een enkele waarde toegekend aan onze aangemaakte objecten. Meestal houden we ons echter bezig met een veelvoud aan getallen, we hebben immers data van meerdere personen. Sterker nog, van elk van deze personen hebben we een scala aan gegevens. Voor we met volledige datasets aan de gang gaan, is het goed om te weten hoe je met een verzameling getallen kunt werken. Een ééndimensionale rij getallen noemen we een vector. Voor R is een variabele met één waarde, zoals in bovenstaand voorbeeld, overigens een vector met lengte 1 (er zit maar één waarde in).

3.1.1. Een vector aanmaken

Om een vector met meer waardes te maken, gebruiken we de functie c() (wat staat voor combine). Typ in het console het volgende in:
> getallen1 <- c(3,5,2,8,7,1)
> getallen1
[1] 3 5 2 8 7 1

Een vector is een rij met getallen, aaneengekoppeld met c().

3.1.2. Rekenen met vectoren

Er is een vector aangemaakt met lengte 6 (er zitten zes waarden in). Tellen we bij deze vector een getal op, dan zien we het volgende:

> getallen1 + 3
[1]  6  8  5 11 10  4

Bij elk van de getallen wordt de waarde 3 opgeteld en je krijgt de uitkomst te zien. Dit wordt verder niet opgeslagen, we zien immers geen <-. Probeer eens het volgende:

> getallen2 <- c(1,2,1,3,2,17)
> getallen1 + getallen2
[1]  4  7  3 11  9 18

Je ziet dat we een nieuw object aanmaken, genaamd getallen2, waarin we ook zes waarden stoppen. Dit tellen we vervolgens op bij getallen1. R is blijkbaar slim genoeg om te snappen dat je de eerste elementen bij elkaar wilt optellen, de tweede elementen, etc. Probeer nu het volgende:

> getallen3 <- c(1,2,1,3,2,17,4)
> getallen1 + getallen3
Warning in getallen1 + getallen3: longer object length is not a multiple of
shorter object length
[1]  4  7  3 11  9 18  7

Merk op dat getallen3 zeven elementen bevat in plaats van zes. We krijgen hier een waarschuwing over: pas op, de vectoren zijn niet even lang. Of, iets preciezer: de lengte van de ene vector is niet een veelvoud van de lengte van de andere vector. Voor het laatste element in getallen3 is er geen element meer in getallen1. R begint voor het optellen weer met het eerste element van getallen1, dus bij de waarde 4 uit getallen3 wordt het eerste element (3) uit getallen1 hergebruikt. Dit is gewoonlijk niet wat je wilt, meestal kom je er met zo’n melding achter dat er iets niet klopt in je data - voor de ene variabele heb je meer observaties dan voor de andere variabele. Maar dit principe kun je soms gebruiken zoals in het volgende voorbeeld:

> getallen4 <- c(1,2)
> getallen1 + getallen4
[1]  4  7  3 10  8  3

Je moet er misschien even naar turen, maar hier gebeurt het volgende: de getallen uit de kleinere vector (getallen4) worden herhaaldelijk opgeteld bij de elementen uit de grotere vector (getallen1). We krijgen de waarschuwing ook niet meer: de lengte van de ene vector (getallen1 met zes elementen) is een veelvoud van de lengte van de andere vector (getallen4 met twee elementen).

> getallen5 <- c(3,7,18,2,5)
> (getallen5 + 2) * 3
[1] 15 27 60 12 21

Denk om de haakjes!

3.1.3. Informatie uit vectoren halen

Stel, we willen uit getallen1 het vierde element halen. Deze vector zag er als volgt uit:

> getallen1
[1] 3 5 2 8 7 1

Het vierde element (we zien dat dit de waarde 8 is) kunnen we als volgt uit deze vector halen:

> getallen1[4]
[1] 8

Binnen de blokhaken geven we het volgnummer aan.

3.2. Matrices

Zoals we hierboven bespraken, is een vector een rij met waarden. Een matrix bevat ook gegevens, maar dan in meerdere rijen en kolommen. Je zou een matrix kunnen zien als de tweedimensionale broer van een vector. Als je eenmaal met “echte data” aan de slag gaat, zul je vooral met data.frames aan de slag gaan. Die zien er hetzelfde uit als matrices, maar zijn wat “gemakkelijker in de omgang”. Hierover leer je meer in hoofdstuk 7. Omdat je in R wel regelmatig met matrices te maken zult krijgen, staan we er even bij stil.

3.2.1. Een matrix aanmaken

In het volgende voorbeeld maken we een kleine matrix mat.1 aan:

> mat.1 <- matrix(data = c(1,2,3,4,5,6,34,27,46,51,21,41,0,0,0,1,1,1), nrow= 6)
> mat.1
     [,1] [,2] [,3]
[1,]    1   34    0
[2,]    2   27    0
[3,]    3   46    0
[4,]    4   51    1
[5,]    5   21    1
[6,]    6   41    1

We gebruiken hiervoor de functie matrix() waarin we aangeven waaruit de data bestaat en hoeveel rijen deze matrix moet hebben. Er zijn achttien elementen, verdeeld over zes rijen, wat maakt dat er drie kolommen komen. In plaats van nrow=6 hadden we ook aan kunnen geven ncol=3. Of allebei, dat is niet fout maar wel dubbelop. Het past in ieder geval precies. Als het aantal elementen niet een veelvoud is van het gespecificeerde aantal rijen of kolommen, krijg je daar een waarschuwing over (probeer maar eens, door één of twee elementen uit de vector te schrappen).

Je zou hier al een heel kleine dataset in kunnen herkennen: een kolom met persoonsnummers (1 t/m 6), dan een kolom met een continue variabele (34, 27, 46, etc.), en een kolom met een zogenaamde dichotome variabele (0 en 1).

3.2.2. Een matrix samenstellen uit vectoren

In de praktijk zal het niet zo vaak (eigenlijk: nooit) voorkomen dat je een volledige dataset maakt op de manier van het bovenstaande voorbeeld. Wat wel eens kan gebeuren, is dat je een matrix samenstelt uit meerdere vectoren. Stel, je hebt de vectoren volgnummer (een deelnemersnummer), inkomen (inkomen in duizenden euro’s) en een groepsvariabele groep:

> volgnummer <- c(1, 2, 3, 4, 5, 6) 
> inkomen <- c(34, 27, 46, 51, 21, 41)
> groep <- c(0, 0, 0, 1, 1, 1)

Dan kunnen we dezelfde mat.1 ook aanmaken door deze vectoren aan elkaar te verbinden met cbind():

> mat.1 <- cbind(volgnummer, inkomen, groep)
> mat.1
     volgnummer inkomen groep
[1,]          1      34     0
[2,]          2      27     0
[3,]          3      46     0
[4,]          4      51     1
[5,]          5      21     1
[6,]          6      41     1

Je ziet dat het resultaat hetzelfde is, alleen heeft mat.1 nu ook de variabelenamen meegenomen als koppen voor de kolommen.

3.2.3. Informatie uit een matrix halen

Bij de vector zagen we dat we informatie konden extraheren door een getal tussen blokhaken achter de variabelenaam te plaatsen. Bij de matrix is dit niet voldoende: we moeten een rijnummer en een kolomnummer opgeven. Stel, we willen uit de derde rij de informatie uit de tweede kolom hebben (dit is het getal 46). Dan kunnen we dit als volgt doen:

> mat.1[3, 2]
inkomen 
     46 

Nu geven we tussen de blokhaken het rijnummer en het kolomnummer op. Altijd eerst het rijnummer, dan het kolomnummer. Wil je een complete rij opvragen, dan kan dat op de volgende manier:

> mat.1[3, ]
volgnummer    inkomen      groep 
         3         46          0 

Je specificeert voor de komma het rijnummer, en na de komma niets. En een volledige kolom doe je dus als volgt:

> mat.1[ , 2]
[1] 34 27 46 51 21 41

Je definieert geen rijnummer, alleen een kolomnummer (ná de komma).

De tweede en derde rij krijg je als volgt:

> mat.1[c(2,3), ]
     volgnummer inkomen groep
[1,]          2      27     0
[2,]          3      46     0

De eerste en derde kolom krijg je als volgt:

> mat.1[, c(1,3)]
     volgnummer groep
[1,]          1     0
[2,]          2     0
[3,]          3     0
[4,]          4     1
[5,]          5     1
[6,]          6     1

3.3. Karakters

Naast het werken met kwantitatieve data kun je in R ook met tekstdata werken. We gaan in deze tutorial niet in op het analyseren van tekstdata, maar willen wel even stil staan bij dit type gegevens.

Tekstdata worden in R (zoals eigenlijk in alle programmeertalen) in aanhalingstekens ingesloten. Het maakt hierbij niet uit of je enkele of dubbele aanhalingstekens gebruikt, zolang je maar hetzelfde type aanhalingsteken gebruikt om een stuk tekst heen. Voer in de console maar eens de volgende code uit:

> tekst1 <- "ik ben een stukje tekst"
> tekst2 <- 'ik ben ook een stukje tekst'
> tekst3 <- "ik ben een 'stukje' tekst"
> tekst1
[1] "ik ben een stukje tekst"
> tekst2
[1] "ik ben ook een stukje tekst"
> tekst3
[1] "ik ben een 'stukje' tekst"

De aanhalingsteken hebben een belangrijke functie. R verwacht namelijk op enig moment in de console een getal (bijv. 3), een naam van een object (bijv. getallen4) of een functie (bijv. c(1,2)), een aanhalingsteken (bijv. "ik ben een stukje tekst"), of een toewijzing aan een nieuwe variabele (bijv. a <- 3). Doe je het volgende:

> tekst4 <- ik ben een stukje tekst
Error: unexpected symbol in "tekst4 <- ik ben"

dan krijg je een foutmelding. R leest je code namelijk net als een mens: regel voor regel, en elke regel van links naar rechts. Als R ik tegenkomt, kan het nog zijn dat dat een nieuwe variabele wordt. De code tekst4 <- ik <- 3 zou er bijvoorbeeld voor zorgen dat de waarde 3 wordt gegeven aan variabele ik én aan de variabele tekst4. Maar als er dan na ik geen toewijzingsoperator (<- of =) wordt gevonden, maar ben, dan gaat ’t mis. Naar de rest van de code wordt niet eens meer gekeken: je krijgt een foutmelding en het uitvoeren van de rest van de code wordt gestaakt.

Het is belangrijk om te weten dat R (zoals elke programmeertaal) hier heel strikt in is. Als je data importeert, wil het nog wel eens voorkomen dat een variabele waarvan je denkt dat het een numerieke variabele is, als tekstvariabele wordt gezien. Bijvoorbeeld: je wilt het gemiddelde berekenen van de variabele age, waarvan je zou denken dat het een numerieke variabele is (een vector met alleen maar getallen). Dan blijkt vervolgens dat iemand van 37 jaar als leeftijd heeft staan “bijna 38!”. Dit is geen getal, maar tekst. En om deze reden wordt de hele variabele als tekstvariabele gezien. En daar kun je niet mee rekenen, je krijgt dan een foutmelding. Kijk maar (we gebruiken de functie mean() om een gemiddelde uit te rekenen, waarover later meer):

> age <- c(23,54,"bijna 38!",48,19,35,40,59,31,40,43,30,29,41,45)
> mean(age)
[1] NA
Warning message:
In mean.default(age) :
  argument is not numeric or logical: returning NA

In je Environment zie je dan ook, dat naast numerieke vectoren getallen1 t/m getallen4 (bijv num [1:6] 3 5 2 8 7 1 voor getallen1), de vector age bestaat uit karakters: er staan aanhalingstekens om de waardes, en i.p.v. num wordt de reeks voorafgegaan door chr (als afkorting voor “character”). De uitkomst NA staat voor not available.

3.4. Andere soorten objecten

Tot nu toe hebben we het gehad over vectoren, matrices en karakters als objecten. Dit soort objecten zijn de zogenaamde data types. Andere vormen van data types zijn data frames, lists, factors en arrays. Hiernaast zijn er nog andere soorten objecten in R, zoals functies, en de uitkomsten van een analyse. Een object kun je zien als “iets dat in het geheugen is geladen en waar je bewerkingen op of mee kunt doen.” Klinkt misschien nog wat vaag, maar gaandeweg deze tutorial gaan we er meer tegenkomen.

3.5. Tab completion

Je weet inmiddels dat we in R met objecten werken. Objecten hebben namen, en elke keer als je zo’n object wilt benaderen, moet je die naam intypen. Dat wordt natuurlijk best vervelend na een tijdje, zeker als de namen wat langer zijn. Gelukkig is daar tab completion. Stel, je wilt getallen3 oproepen. Als je dan begint met typen, je begint met de g, dan zie je dat RStudio al suggesties doet: er komt een popup-venstertje tevoorschijn. Als je nog even doorgaat met typen, e, t, a, dan zie je dat de vier vectoren die we hebben aangemaakt, ineens bovenaan staan. Tik twee keer met je pijltjesknop naar beneden om getallen3 te selecteren, druk op de Tab-toets, en voilà, je hoeft niet meer verder te typen.

Het principe van tab completion kun je ook gebruiken om namen van functies versneld in te voeren, zoals we later zullen zien. In het begin moet je misschien wat aan deze mogelijkheid wennen, maar als je eraan gewend bent geraakt dan wil je niet meer zonder.

Met tab completion typ je je code sneller, doordat R je tekstsuggesties doet wat betreft o.a. functies en objectnamen.

4. Scripts

Tot nu toe hebben we gewerkt vanuit de console. Eerder werd al genoemd dat je dit in de praktijk niet zoveel zult doen, maar vanuit een R-script zult werken. Hoog tijd om de daad bij het woord te voegen, en de editor eens te verkennen.

In R zul je meestal met een script werken.

Je kunt in de editor meerdere commando’s tegelijk selecteren, en bovenaan op Run klikken, of op CTRL+ENTER (op een Mac CMD + ENTER) drukken. De geselecteerde code wordt dan voor je in het console geplakt en voor je uitgevoerd. Probeer dit eens met de volgende code (het promptkarakter > is even weggehaald zodat je dit makkelijk kunt copypasten):

getallen1 <- c(3,5,2,8,7,1)
getallen2 <- c(1,2,1,3,2,17)
getallen1 + 3
getallen1 + getallen2

De code wordt regel voor regel in de console uitgevoerd, waar je ook de output te zien krijgt. In het vervolg geven we de in- en output als volgt weer:

> getallen1 <- c(3,5,2,8,7,1)
> getallen2 <- c(1,2,1,3,2,17)
> getallen1 + 3
[1]  6  8  5 11 10  4
> getallen1 + getallen2
[1]  4  7  3 11  9 18

Klein verschil met voorgaande output: vanaf nu maken we gebruik van de standaard code highlighting in de editor. En laten we eventuele prints (zoals [1]  6  8  5 11 10  4) zien waar ze tevoorschijn komen in de console. Belangrijk: als je de commando’s overtypt in de editor, laat je de > weg.

We stipten het eerder al aan: in de editor kun je het hele verloop van je analyseproject uiteenzetten. Je vertelt als het ware een verhaal, vanaf het inladen van de data en eventueel benodigde packages (waarover later meer), via het transformeren, opschonen en selecteren van je data tot het uitvoeren van je analyses, om vervolgens uitkomsten eventueel te comprimeren.
Ook handig: in je script kun je commentaarregels (comments) opnemen, om je code te verduidelijken. Dit doe je met een hekje (#). Probeer bijvoorbeeld eens het volgende script in zijn geheel te runnen (het promptkarakter > is voor het gemak weer even weggehaald, zo kun je het geheel makkelijk copypasten):

Met een # kun je commentaarregels opnemen in je code.

#we gaan wat vectoren aanmaken
getallen1 <- c(3,5,2,8,7,1)
getallen2 <- c(1,2,1,3,2,17)
getallen3 <- c(1,2,1,3,2,17,4)

#we tellen de eerste twee vectoren bij elkaar op
som1 <- getallen1 + getallen2

#en we laten de uitkomst zien
som1

#om het resultaat van een commando direct te zien,
#kunnen we er ook haakjes omheen zetten:
(som1 <- getallen1 + getallen2)
#de bewerking wordt uitgevoerd (som1 aangemaakt)
#en het resultaat verschijnt in de console. Dit scheelt 
#een regel code!

Als je de regel met het commentaar selecteert en runt, dan snapt R dat hij deze regel niet moet interpreteren - je krijgt dus geen melding dat hij variabelen niet kan vinden o.i.d., zoals we hierboven in het voorbeeld met variabele tekst4 zagen.

Je kunt je script opslaan via File -> Save / Save as…. Hij krijgt de bestandsextensie .R.

Een laatste opmerking over het gebruik van scripts, voor nu. Vaak werk je met externe databestanden en schrijf je dingen weg naar bestanden (denk aan grafiekjes en nieuwe datasets). Het is dan van belang om R te laten weten in welke map je bezig bent. Meestal ligt het voor de hand om je R-script in dezelfde map te hebben als je databestand. De map waarin je aan het werk bent, heet je working directory. Je huidige working directory kun je opvragen met de functie getwd(), en je kunt hem instellen met setwd(). Ter illustratie:

#de huidige working directory
getwd()
[1] "H:/Documenten/Cursussen/R/Tutorial I - basis"

#een nieuwe working directory instellen
setwd("H:/Documenten/Analyses/")

#even controleren
getwd()
[1] "H:/Documenten/Analyses"

5. Functies

We hebben al een aantal functies gezien: print(), c(), cbind(), matrix() en mean(). Zojuist zagen we ook nog getwd() en setwd(). Een functie is een object dat een commando, of een reeks commando’s, voor je uitvoert. Vaak is het zo dat je er iets (bijvoorbeeld een vector) “in stopt”, en dat je er dan een bewerking van terugkrijgt. In de functie mean() kun je bijvoorbeeld een vector stoppen, en je krijgt er de gemiddelde waarde van die vector voor terug. Je kunt je voorstellen dat dit sneller is dan zelf in R alle elementen van een vector bij elkaar op te tellen, en het resultaat te delen door het aantal elementen.

Een functie voert een reeks commando’s voor je uit.

Een functie kun je herkennen aan de haakjes die direct op de functienaam volgen. Tussen deze haakjes kun je een en ander specificeren voor het uitvoeren van de functie. Laten we kijken wat er zoal te ontdekken valt over het gebruik van functies.

We maken twee variabelen aan, om het gebruik van functies te illustreren. Dit hebben we eerder gezien, we gebruiken hiervoor c():

> age <- c(23,54,37,48,19,35,40,59,31,40,43,30,29,41,45)
> life_events <- c(4,8,6,9,4,7,5,7,2,5,6,5,6,5,6)

Bij de functie c() wordt tussen haakjes gespecificeerd welke waarden je in een vector wilt stoppen.

Merk op dat we de variabele age eerder al hebben aangemaakt (met “bijna 38!”, weet je nog?). Deze variabele wordt door de huidige bewerking overschreven. Omdat ’ie nu uit alleen maar cijfers bestaat, wordt het een numerieke vector in plaats van een vector van karakters. Een ander voorbeeld van een functie in R is de plot() functie.

> plot(x = age, y = life_events)

We zien dat de twee variabelen tegen elkaar afgezet worden in een grafiek. Blijkbaar is dit wat plot() doet: een grafiekje plotten. We hebben voor het uitvoeren van deze functie een x en een y gedefinieerd. Hier kunnen we alléén een = gebruiken, en niet de toewijzingsoperator <- - we maken immers geen nieuwe variabele aan. Als we meer willen weten over deze functie, kunnen we typen:

> ?plot

Je ziet dat in het vak rechtsonder het tabblad Help actief worden, waarin de documentatie (de handleiding) van de plot()-functie wordt weergegeven. De opties die je voor een functie kunt specificeren, zoals x en y, noemen we argumenten. Naast het definiëren van x en y kunnen we blijkbaar nog veel meer doen. Bijvoorbeeld, zelf labels opgeven voor de x- en y-assen:

> plot(x = age, y = life_events, xlab = "Leeftijd (jaren)", ylab = "Aantal life events")

Een functie heeft argumenten waarmee je opties kunt ingeven.

Als we in de documentatie doorklikken naar graphical parameters, zien we dat we eigenschappen van de grafiek kunnen veranderen zoals plotkleur (col), puntkarakter (pch), etc. In het volgende voorbeeld passen we een aantal eigenschappen aan. Probeer voor jezelf eens te ontdekken wat de ingevoerde argumenten voor resultaat hebben in de grafiek. Kleine opmerking over onderstaande code: je ziet dat de tweede regel niet begint met een > maar met een +. Dit is om aan te geven dat het commando nog niet was afgelopen aan het einde van de vorige regel. Het is een erg lang commando dat we over twee regels uitschrijven. Als je het commando overneemt in de editor, laat je echter net als >, ook de + weg.

> plot(x = age, y = life_events, xlab = "Leeftijd (jaren)", ylab = "Aantal life events", 
+      col = "red", pch = "A", xlim = c(0,70), ylim = c(0,10))

Soms wordt uit de documentatie niet direct duidelijk wat er nou bedoeld wordt met de verschillende argumenten. Vaak is de sectie Examples onderin de helpfile dan inzichtgevend. En mocht ’t dan nog niet duidelijk zijn: Google is your friend.

Wat misschien opvalt, is de manier waarop xlim en ylim worden gespecificeerd. Hiervoor wordt de c() functie wederom gebruikt. Er gaan twee waardes in: een ondergrens en een bovengrens. Dat is een vector met lengte twee. Zo zie je dat je een functie (in dit geval, c()) kunt gebruiken binnen een andere functie (hier: plot()).

Een laatste ding wat opvalt aan bovenstaande code, we noemden het al, is dat ’ie over twee regels is verdeeld. Dit kun je doen als een coderegel naar je gevoel wat te lang wordt. De witte ruimte tussen het begin van de regel en pch noemen we met een Engels woord indentation. Dit wordt door RStudio automatisch gedaan als je op ENTER drukt voor je het aanroepen van de functie hebt afgesloten met een haakje-sluiten, en is eveneens voor het leesgemak.

> plot(x = life_events, y = age, xlab = "Aantal life events", 
+      ylab = "Leeftijd in jaren", xlim = c(0,15), ylim = c(0,70), 
+      pch="+", col="blue")        

Let bij het gebruik van meerdere regels wel op dat je geen komma’s, aanhalingstekens of haakjes-sluiten vergeet!


We kunnen ook het gemiddelde aantal life-events berekenen. Hiervoor gebruiken we - dit weten we al - de functie mean():

> mean(life_events)
[1] 5.666667

Als je de documentatie van mean() leest, zou je misschien verwachten dat we moeten typen mean(x = life_events). Als je de argumentnamen niet expliciet opgeeft, gaat R er echter van uit dat je de argumenten invoert in de volgorde waarop ze expliciet in de documentatie staan. Bij plot() zou je x = en y = om die reden kunnen weglaten, maar xlab, ylab en de rest van de argumenten niet.

Je kunt tab completion ook gebruiken voor functienamen. Voor de functie mean() kun je beginnen met de eerste letters van het woord (me), en je krijgt direct suggesties voor alles wat er mogelijk is met die beginletters. Je ziet dat mean bovenaan staat. Als je op de Tab-knop drukt, wordt de functie mean() voor je neergezet met je cursor tussen de haakjes, en kun je met diezelfde tab completion het object life_events selecteren. Hieronder zie je goed dat je zo snel je commando’s kunt intypen:

Stel, dat we het gemiddelde willen afronden op drie decimalen, dan doen we het volgende:

> round(mean(life_events), 3)
[1] 5.667

De logica is hier: je wilt een afgerond gemiddelde hebben; hiervoor bereken je eerst het gemiddelde (de binnenste functie), en daarna rond je de uitkomst hiervan af (de buitenste functie, round()). De waarde 3 is hier een argument van de round()-functie.

> hist(age, border = "red", col = "green")       


Met breaks verander je het aantal staven:

> hist(age, border = "red", col = "green", breaks=1)

> hist(age, border = "red", col = "green", breaks=4)


5.1. Veelgemaakte fouten

Bij het gebruiken van functies zul je ongetwijfeld fouten maken. Zelfs de meest doorgewinterde R-programmeur maakt dagelijks fouten bij het gebruiken van functies. Een aantal fouten die je zult tegenkomen:

5.1.1. Fout argument

Soms vraag je van R om een argument op een functie toe te passen, dat niet bestaat. Soms gebeurt dit omdat je een typfout hebt gemaakt, soms omdat je denkt dat het argument bestaat, maar dat is dan kennelijk niet zo. Probeer de volgende voorbeelden maar eens:

> #typfout: het argument xlsb bestaat niet
> plot(x = age, y = life_events, xlsb = "Leeftijd (jaren)", ylab = "Aantal life events")
> 
> #de specificatie voor y bestaat niet (de variabelenaam is verkeerd getypt)
> plot(x = age, y = life_evens, xlab = "Leeftijd (jaren)", ylab = "Aantal life events")

In allebei de gevallen gaat het om een typfout. Het komt ook weleens voor dat je denkt dat een argument bestaat voor een functie, maar dat is dan niet zo (je verwart hem met een andere functie). Kijk dan in de helpfile van een functie om te achterhalen wat er mis gaat.

5.1.2. Aanhalingstekens

Zoals we eerder zagen, staat tekst tussen aanhalingstekens. Zorg ervoor dat voor elk aanhalingsteken dat je gebruikt om die aanduiding van een stukje tekst te openen, ook een aanhalingsteken aanwezig is om hem te sluiten. RStudio helpt je hierbij, door voor ieder aanhalingsteken dat je typt, meteen alvast een tweede neer te zetten, maar soms gaat dit toch nog fout. Probeer dit maar eens:

> plot(x = age, y = life_events, xlab = "Leeftijd (jaren), ylab = "Aantal life events")

We zijn het aanhalingsteken na Leeftijd (jaren) vergeten, waardoor R in eerste instantie denkt dat onze xlab moet zijn Leeftijd (jaren), ylab =. Daarna komt immers het aanhalingsteken-sluiten. Vervolgens staat daar ineens “Aantal”, maar daar wordt een komma (met daarna nog een argument) of een haakje-sluiten verwacht.

5.1.3. Haakjes

Een beetje hetzelfde als de fout met de aanhalingstekens, maar met een andere uitkomst. Voor elk haakje-openen moet er een haakje-sluiten zijn. Zolang deze er niet staat, blijft R op de afronding van je commando wachten. Probeer dit maar eens:

> plot(x = age, y = life_events, xlab = "Leeftijd (jaren)", ylab = "Aantal life events"

We zijn het haakje-sluiten aan het einde van de functie vergeten, waardoor R denkt dat je misschien nog meer wilt specificeren. Als je naar de console kijkt, zie je dat er aan het begin van de regel niet een >, maar een + staat. Meestal wil je zo’n commando afbreken, en het verbeteren. Dit kun je doen door naast het +-je in de console te klikken, en op de ESC-toets te drukken.

5.1.4. Nog meer haakjes

Bij het “nesten” van functies kan het zo zijn dat een haakje op een verkeerde plek komt te staan. Dit levert niet altijd een foutmelding op! Kijk hier eens naar:

> #dit is wat we zouden willen doen:
> round(mean(age), 3)
[1] 38.267
> 
> #maar we doen per ongeluk dit:
> round(mean(age, 3))
[1] 40

Het haakje-sluiten voor de mean()-functie staat op de verkeerde plek. Hierdoor wordt eerst mean(age, 3) uitgevoerd, en de waarde 3 dus als tweede argument gebruikt. De helpfile voor deze functie leert ons dat het tweede argument trim is, een waarde tussen 0 en 0.5. Dit geeft de proportie van buitenste waardes (dus de kleinste en grootste waardes) die weg moeten worden gelaten bij het berekenen van het gemiddelde. Wij hebben hier 3 ingevuld, wat kennelijk wordt veranderd in 0.5 - de dichtstbijzijnde uiterste waarde. Dus de onderste en bovenste 50% worden weggelaten. We houden alleen nog maar de middelst gemeten waarde over (de mediaan dus), dit is 38. Dit wordt vervolgens afgerond. We definiëren geen decimalen (door onze fout met de haakjes), waardoor de standaardwaarde voor het aantal decimalen wordt gebruikt (en dit is 0).

6. Packages

Een groot pluspunt van R is dat er online een gigantische community is van gebruikers die actief bezig zijn de functionaliteit te verbeteren en uit te breiden, en die hier blogs, plogs en vlogs over maken. Gewoonlijk is deze functionaliteit beschikbaar in de vorm van packages. R kan uit zichzelf al veel, en er worden ook al best wat packages standaard geïnstalleerd, maar toch zijn er altijd wel dingen te verzinnen die je wilt doen, die niet standaard in R zitten. Eerder noemden we al de website van R, CRAN, waar packages te vinden zijn.

M.b.v. packages breid je de functionaliteit van R uit.

Je kunt packages op twee manieren installeren. De meest omslachtige manier is om ze van CRAN te downloaden als zipbestand of tarball, en ze via het tabblad Packages, d.m.v. de knop Install te installeren. Veel handiger is de standaardoptie, om ze (onder dezelfde knop) direct uit de CRAN repository te downloaden - je hebt hier uiteraard een internetverbinding voor nodig. De omslachtige optie gebruik je bijvoorbeeld als je een oudere versie van een package nodig hebt. Rechtstreeks vanuit de CRAN repository wordt altijd de nieuwste versie geïnstalleerd.

Als je een package geinstalleerd hebt, kun je hem inladen met het de functie library(). Kijk maar eens in de lijst met geïnstalleerde packages, en kijk of foreign al is geïnstalleerd. Zo niet, doe dit dan alsnog. Je ziet dan in de console dat R ermee bezig is. Als je ziet dat hij klaar is (foreign staat dan tussen je packages), probeer dan:

> library(foreign)

In het scherm Packages zie je het package nu met een vinkje ervoor staan. We kunnen functies die in dit package zitten, nu gebruiken. In dit specifieke geval gaat het om functies waarmee je data kunt importeren uit een aantal statistiekprogramma’s, zoals SPSS, Stata, SAS en Minitab (eigenlijk allemaal software die je niet meer zou moeten willen gebruiken als je eenmaal wat kundiger bent in R 😉).

7. Data verkennen

Als het je is gelukt om dit punt in de tutorial te bereiken, dan ben je klaar voor het echte werk: het is tijd om met een volledig databestand aan de slag te gaan.

7.1. Data inladen

Download het volgende bestand: Databestand FDL.sav. Dit is een SPSS-databestand met daarin data die verzameld zijn met de Fictieve Depressie Lijst (de FDL). Zoals de naam al doet vermoeden, gaat het hier om verzonnen data. We hebben (zogenaamd) 100 mensen met een depressie gerandomiseerd in de groepen CGT (cognitieve gedragstherapie) en CGT+FT (CGT met farmacotherapie). Van deze mensen hebben we vastgelegd wat hun geslacht, leeftijd en sociaal-economische status is, hoeveel alcoholische consumpties ze in de afgelopen week hebben genuttigd, wat hun score is op een neuroticismeschaal en op een angstschaal, in welke interventiegroep ze zijn gerandomiseerd, en hun scores op de FDL vóór en na de behandeling.

Dit bestand kunnen we openen met de functie read.spss() uit het foreign package:

> library(foreign)
> #setwd("H:/Documenten/Analyses/")
> FDL <- read.spss("Databestand FDL.sav", to.data.frame=TRUE)

Zoals je ziet, laden we het package op de eerste regel. Op de tweede regel gebeurt iets geks: je ziet het setwd() commando, maar er staat een commentaarhekje voor. Dit is een handigheidje dat veelvuldig wordt gebruikt bij het programmeren: een stukje code dat even niet gebruikt wordt, of waar nog aan gesleuteld moet worden, wordt voorzien van een commentaarhekje (het wordt weggecomment), zodat het niet uitgevoerd wordt. Als je dan een lap code selecteert om te runnen, kun je deze regel mee selecteren zonder dat de code achter het hekje wordt meegenomen als daadwerkelijk uit te voeren code. Mocht je nog je working directory willen instellen (zorg dat het databestand in je working directory staat!), dan kun je het hekje weghalen voor je de code uitvoert (de regel uncommenten) en het gewenste pad invullen.





Code die je (tijdelijk) niet wilt uitvoeren, kun je met # wegcommenten.

Op de derde regel wordt het databestand ingelezen met de read.spss() functie. Een argument hierbij is to.data.frame. Dit moet je instellen op TRUE. Standaard staat dit argument op FALSE, en zoals je uit de documentatie (?read.spss) kunt opmaken, geeft deze optie weer of de inhoud van het databestand in een dataframe moet worden geladen. Specificeer je dit niet als TRUE, dan doet ’ie niks.

7.1.1. Andere bestandsformaten

Natuurlijk wil je ook data kunnen inlezen uit andere bestanden dan SPSS-bestanden. Bijvoorbeeld Excelbestanden, tekstbestanden of comma separated files. Voor de laatste twee is er standaard functionaliteit in R aanwezig: tekstbestanden kun je inlezen met read.table(), en voor .csv-bestanden is er read.csv voor wanneer de waardes gescheiden zijn door een komma, en read.csv2() voor wanneer de waardes gescheiden zijn door een puntkomma.

Voor het lezen van Excel-bestanden kun je gebruikmaken van de packages xlsx, openxlsx en readxl. Laatstgenoemde is iets veelzijdiger dan de eerste twee (deze kan bijvoorbeeld ook nog oude .xls-bestanden openen).

7.2. Data verkennen

Als het goed is, is FDL in je Environment komen te staan, onder het kopje Data. Klik je op het driehoekje ervoor, dan krijg je de variabelen te zien, en nog wat zgn. attributen (die behandelen we niet). Je ziet dat er twee typen variabelen in voorkomen: numerieke (num) en categorische (Factor). Als je een overzicht met gangbare statistieken van alle variabelen wilt, kun je dit krijgen met de functie summary():

> summary(FDL)
       id          Geslacht     Leeftijd            SES        Alcohol     
 Min.   :  1.00   man  :35   Min.   :23.00   gemiddeld:60   Min.   : 0.00  
 1st Qu.: 25.75   vrouw:65   1st Qu.:37.00   hoog     :14   1st Qu.: 6.00  
 Median : 50.50              Median :42.00   laag     :26   Median :13.00  
 Mean   : 50.50              Mean   :42.99                  Mean   :15.57  
 3rd Qu.: 75.25              3rd Qu.:48.25                  3rd Qu.:19.25  
 Max.   :100.00              Max.   :64.00                  Max.   :66.00  
  Neuroticisme       Angst       Interventie    FDL_voor         FDL_na     
 Min.   : 3.00   Min.   : 9.00   CGT   :50   Min.   :31.00   Min.   :15.00  
 1st Qu.:18.00   1st Qu.:17.00   CGT+FT:50   1st Qu.:40.00   1st Qu.:30.00  
 Median :25.00   Median :21.00               Median :44.00   Median :35.00  
 Mean   :24.87   Mean   :20.56               Mean   :43.69   Mean   :35.18  
 3rd Qu.:30.25   3rd Qu.:24.00               3rd Qu.:47.00   3rd Qu.:40.25  
 Max.   :56.00   Max.   :31.00               Max.   :59.00   Max.   :54.00  
(NB: de mate waarin de summary in dit document mooi wordt weergegeven, hangt samen met de breedte van je browserscherm en mogelijk de resolutie van je beeldscherm.)

Met summary() krijg je een samenvatting van alle variabelen in je dataset.

Je ziet dat er tellingen worden gedaan voor categorische variabelen, en dat voor numerieke variabelen de kwartielen, het gemiddelde en de mediaan (dit is het tweede kwartiel) worden gegeven. Voor het verkennen van individuele variabelen zou je de volgende functies kunnen gebruiken:

> #gemiddelde op de FDL op de voormeting
> mean(FDL$FDL_voor)
[1] 43.69
> 
> #standaarddeviatie op de voormeting
> sd(FDL$FDL_voor)
[1] 6.091383
> 
> #tellingen voor de sociaal-economische status
> table(FDL$SES)

gemiddeld      hoog      laag 
       60        14        26 

We zien hier iets nieuws. De variabelenaam wordt voorafgegaan door FDL$. Het dollarteken $ is belangrijk in R. Je geeft hiermee aan dat je een bepaalde component uit een object wilt oproepen. In dit geval is het object de dataset (formeel in R een dataframe), en de component is een variabele in het dataframe.

Met $ roep je een component uit een object op.

Ook hier is tab completion handig: als je intypt FDL$ en je drukt op de Tab-knop, krijg je een lijstje met alle mogelijkheden (in dit geval: alle variabelen binnen FDL).

Om een idee te krijgen van je dataset, kun je de head() functie gebruiken:

> head(FDL, 10)
   id Geslacht Leeftijd       SES Alcohol Neuroticisme Angst Interventie
1   1    vrouw       61 gemiddeld       7           25    16      CGT+FT
2   2    vrouw       46 gemiddeld      24           41    24      CGT+FT
3   3      man       36 gemiddeld       5           10    17      CGT+FT
4   4    vrouw       46 gemiddeld       2           27    19         CGT
5   5      man       35      laag       4           25    21      CGT+FT
6   6    vrouw       47      hoog       3           23    13      CGT+FT
7   7      man       38 gemiddeld      17           33    27      CGT+FT
8   8    vrouw       45 gemiddeld      16           26    24         CGT
9   9    vrouw       59      laag      15           24    29      CGT+FT
10 10    vrouw       57 gemiddeld       7           30    20         CGT
   FDL_voor FDL_na
1        32     26
2        46     41
3        41     27
4        43     33
5        43     32
6        45     37
7        47     43
8        48     40
9        41     27
10       37     24

Je kunt ook de complete dataset in een apart tabblad te zien krijgen. In het vak Environment kun je dit doen door op het half-doorzichtige rastertje rechts te klikken (op de regel met de objectnaam). Je kunt ook het volgende commando gebruiken:

> View(FDL) #let op: View is met hoofdletter V!

We kunnen ook alle waarden van een specifieke variabele opvragen. Dit doen we door bijvoorbeeld in te geven:

> FDL$Leeftijd
  [1] 61 46 36 46 35 47 38 45 59 57 37 53 34 41 51 33 31 44 43 27 64 53 50 53 39
 [26] 41 39 38 25 42 32 47 57 40 54 32 42 53 43 47 40 50 42 34 48 44 40 42 50 41
 [51] 37 43 43 43 41 42 36 51 50 36 53 37 42 43 31 61 28 44 41 46 40 36 46 31 42
 [76] 38 33 48 46 40 37 40 36 58 33 31 49 41 53 35 23 47 53 63 37 55 40 50 40 44

Dit zijn alle waarden in onze dataset voor Leeftijd. We hebben 100 observaties, dit zijn alle 100 leeftijden. Aan het begin van elke regel zie je een getal tussen blokhaken (bijvoorbeeld [1] en [26]) staan. Dit getal geeft aan welk volgnummer het eerste element in die rij heeft. Dit hebben we eerder gezien waar er alleen [1] stond; nu zie je dat er meer van dat soort indicatoren komen te staan als je een grotere verzameling getallen hebt.

We maken een kruistabel van geslacht en interventie

> table(FDL$Geslacht, FDL$Interventie)
       
        CGT CGT+FT
  man    16     19
  vrouw  34     31

Er zitten 16 mannen in de CGT-interventiegroep.

7.3. Rijen en kolommen

Zoals je in Environment kunt zien, bestaat onze dataset uit 100 observaties (rijen) van 10 variabelen (kolommen). We zeggen ook wel dat de dimensies van onze dataset 100 en 10 zijn. We kunnen dit verifiëren door de dim() functie:

> dim(FDL)
[1] 100  10

Stel, we willen specifiek van de zevende rij, de waarde in de vierde kolom (de variabele Leeftijd) weten. Dan zouden we dit op meerdere manieren kunnen doen:

> FDL[7,3]
[1] 38
> FDL$Leeftijd[7]
[1] 38

In beide gevallen gebruiken we blokhaken. De eerste manier zagen we al eerder, toen we het over matrices hadden. Op de tweede regel pakken we het iets anders aan. We willen een waarde uit een specifieke vector vissen. Een vector is ééndimensionaal, dus hoeven we maar één waarde op te geven; we willen de zevende observatie van leeftijd.

We kunnen tussen blokhaken ook een voorwaarde zetten. Stel, we willen de leeftijden hebben, maar wel alleen van de mannen:

> FDL$Leeftijd[FDL$Geslacht == "man"]
 [1] 36 35 38 34 41 51 33 44 41 38 25 42 32 57 54 32 53 40 50 42 36 51 28 41 40
[26] 46 38 40 36 31 49 41 23 47 55

De logica hierachter is in te zien, als we net als bij geneste functies van binnen naar buiten werken. Eerst kunnen we ons afvragen: voor welke observaties in FDL$geslacht geldt, dat er de waarde man staat? Dit kun je uitvinden door:

> FDL$Geslacht == "man"
  [1] FALSE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE
 [13]  TRUE  TRUE  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE
 [25] FALSE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE
 [37] FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
 [49]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE FALSE
 [61] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE
 [73]  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE
 [85] FALSE  TRUE  TRUE  TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE  TRUE
 [97] FALSE FALSE FALSE FALSE

Daar waar hier de waarde TRUE ziet staan, gaat het om mannen (dus bijvoorbeeld de eerste drie, dan twee niet, dan twee wel, enzovoorts). Die voldoen aan de voorwaarde FDL$Geslacht == "man", en alleen voor hen zal in het eerdere commando de leeftijd worden weergegeven.

Merk op dat we hier een dubbel is-gelijk-teken (==) gebruiken. Tezamen met groter-dan (>), kleiner dan (<), groter-of-gelijk-aan (>=), kleiner-of-gelijk-aan (<=), ongelijk aan (!=) en nog een aantal andere opties, noemen we dit relationele operatoren. Dergelijke operatoren kun je gebruiken om voorwaardelijkheden te gebruiken, zoals we zojuist zagen.

Je zou kunnen denken dat je dit doet met

> FDL$Leeftijd[FDL$Geslacht == "vrouw"]
 [1] 61 46 46 47 45 59 57 37 53 31 43 27 64 53 50 53 39 39 47 40 42 43 47 40 50
[26] 42 34 48 44 42 41 37 43 43 43 41 50 36 53 37 42 43 31 61 44 46 36 31 42 33
[51] 48 46 40 37 58 33 53 35 53 63 37 40 50 40 44

Strikt genomen is dit niet juist. Hiermee krijg je de leeftijden van de vrouwen. Het kan ook zo zijn dat er bijvoorbeeld de waarde “anders” is; juist met een variabele als geslacht kan deze situatie zich voordoen, zonder dat je daar direct aan denkt. Beter is de volgende code:

> FDL$Leeftijd[FDL$Geslacht != "man"]
 [1] 61 46 46 47 45 59 57 37 53 31 43 27 64 53 50 53 39 39 47 40 42 43 47 40 50
[26] 42 34 48 44 42 41 37 43 43 43 41 50 36 53 37 42 43 31 61 44 46 36 31 42 33
[51] 48 46 40 37 58 33 53 35 53 63 37 40 50 40 44

In het geval van onze data komt er hetzelfde uit, maar in principe is de tweede manier de juiste.


Stel, je wilt de gemiddelde leeftijd van mannen weten, dan stop je om bovenstaande commando in de mean() functie:

> mean(FDL$Leeftijd[FDL$Geslacht == "man"])
[1] 40.57143

Het komt regelmatig voor dat er waardes missen in je dataset. Deze fictieve dataset is compleet, dus we gaan er even een missende waarde in stoppen:

> FDL$Leeftijd[7] <- NA

We veranderen de waarde op de zevende rij, vierde kolom in NA. Op deze manier kun je dus - dan weet je dat ook meteen - waardes wegschrijven in dataframes. Als we nu het gemiddelde voor mannen willen uitrekenen, krijgen we

> mean(FDL$Leeftijd[FDL$Geslacht == "man"])
[1] NA

Die kan niet berekend worden, omdat er nu een missende waarde in zit (doe nog maar eens head(FDL, 10)). Om dit probleem te omzeilen, kunnen we een extra argument van de mean() functie gebruiken:

> mean(FDL$Leeftijd[FDL$Geslacht == "man"], na.rm=TRUE)
[1] 40.64706

We zetten de oorspronkelijke waarde weer even terug:

> FDL$Leeftijd[7] <- 38


Je moet hier aan twee voorwaarden voldoen: iemand moet man zijn én in de CGT-groep zitten. Dit doe je als volgt:

> mean(FDL$Leeftijd[FDL$Geslacht == "man" & FDL$Interventie == "CGT"])
[1] 41.9375

De crux zit ’m in [FDL$Geslacht == "man" & FDL$Interventie == "CGT"]. Het ampersand (&) combineert hier de voorwaarden. Elk van de voorwaarden heeft als uitkomst TRUE of FALSE. Het ampersandteken zorgt ervoor dat een observatie alleen in de berekening wordt meegenomen, als de beide voorwaardes TRUE zijn.


Je moet hier aan één van twee voorwaarden voldoen: iemand moet óf in SES-klasse “laag”, óf in SES-klasse "gemiddeld zitten. Dit doe je als volgt:

> mean(FDL$Leeftijd[FDL$SES == "laag" | FDL$SES == "gemiddeld"])
[1] 43.0814

Hier zit het ’m in [FDL$SES == "laag" | FDL$SES == "gemiddeld"]. Het sluisteken (|, in het engels vertical bar) zorgt ervoor dat er TRUE wordt teruggegeven als er aan tenminste één van de beide voorwaarden wordt voldaan.

8. Basale statistische analyses

Inmiddels ben je aardig op de hoogte van hoe je in R je data kunt inlezen en verkennen, hoe je met objecten kunt werken, en hoe je in RStudio met een script kunt werken. Tijd voor het echte werk: we gaan wat statistische analyses doen.

8.1. De onafhankelijke t-toets

Eerder hebben we in onze dataset gekeken wat de gemiddelde leeftijd is van de mannen en van de vrouwen. Stel dat we willen toetsen of deze twee groepen statistisch significant van elkaar verschillen. Hiervoor kunnen we, mits deze variabele voor allebei de groepen normaal verdeeld is, een onafhankelijke t-toets doen. We gaan eerst eens kijken naar de normaliteit van de variabele leeftijd, zowel voor de mannen als voor de vrouwen.

Om te beginnen, maken we voor de mannen en voor de vrouwen een histogram:

> par(mfrow = c(1,2))
> 
> hist(FDL$Leeftijd[FDL$Geslacht == "man"], main="", 
+      xlab="Leeftijd (mannen)", ylab="Frequentie")
> hist(FDL$Leeftijd[FDL$Geslacht == "vrouw"], main="", 
+      xlab="Leeftijd (vrouwen)", ylab="Frequentie")
> 
> par(mfrow = c(1,1))

Eerst wordt er een zogenaamde grafische parameter ingesteld: met mfrow = c(1,2) geven weer aan dat we de ruimte waarin we onze grafieken tekenen, in secties willen indelen. Ook hier geldt het principe van “eerst het aantal rijen, dan het aantal kolommen”, zoals we dat eerder zagen bij het inlezen van en wegschrijven naar een specifieke waarde in een dataframe. We willen dat ons plotveld 1 rij bevat en 2 kolommen. Zo komen er dus twee grafieken naast elkaar.

Vervolgens worden er twee histogrammen getekend. De extra argumenten die we hier meegeven en die je extra kunt specificeren, kun je natuurlijk opzoeken met ?hist. In de laatste regel zetten we de parameter terug naar de standaard: 1 rij en 1 kolom - dus 1 grafiek in je plotruimte.

Stel dat we tevreden zijn over de normaliteit bij elk van de groepen, dan kunnen we de onafhankelijke t-toets uitvoeren.

> t.test(x = FDL$Leeftijd[FDL$Geslacht=="man"], y = FDL$Leeftijd[FDL$Geslacht=="vrouw"])

    Welch Two Sample t-test

data:  FDL$Leeftijd[FDL$Geslacht == "man"] and FDL$Leeftijd[FDL$Geslacht == "vrouw"]
t = -2.1108, df = 68.411, p-value = 0.03845
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -7.2381287 -0.2036296
sample estimates:
mean of x mean of y 
 40.57143  44.29231 

Onze input bestaat uit twee vectoren: de leeftijden van de mannen (x) en de leeftijden van de vrouwen (y).

We kunnen de onafhankelijke t-toets ook doen door niet x en y te specificeren, maar door een zgn. formule op te geven (in de vorm variabele ~ groep). De volgende opties geven hetzelfde resultaat (probeer maar):

> t.test(x = FDL$Leeftijd[FDL$Geslacht=="man"], y = FDL$Leeftijd[FDL$Geslacht=="vrouw"])
> t.test(FDL$Leeftijd ~ FDL$Geslacht)
> t.test(Leeftijd ~ Geslacht, data=FDL)

Misschien is de derde optie het elegantst: er zit geen herhaling in wat betreft FDL$, en je hoeft niet met blokhaken te werken. En als je andere analyses gaat doen in R, zoals regressieanalyses, dan zul je zien dat deze benadering vaker gebruikt wordt.

Terug naar het resultaat. We zien dat er een t-test wordt uitgevoerd met een zogenaamde Welch-correctie. Een Welch Two Sample t-test. Deze pas je toe als je er niet van uit gaat dat de varianties in beide groepen gelijk zijn (als de spreiding ongelijk is). Deze toets levert ons een t-waarde op van -2.11, wat met 68.41 vrijheidsgraden een p-waarde van 0.04 oplevert. Omdat de p-waarde hier kleiner is dan 0.05, concluderen we hier dat de mannen en vrouwen statistisch significant verschillen in leeftijd. Verder wordt er een betrouwbaarheidsinterval van het verschil gegeven, en zien we de gemiddelden van de groepen terug.

We maken eerst twee histogrammen voor de angst-schaal: voor elke groep eentje. We zetten ze naast elkaar d.m.v. par(mfrow=c(1,2)), zoals we dat eerder zagen. Vervolgens voor we een onafhankelijke t-toets uit.

> par(mfrow=c(1,2))
> hist(FDL$Angst[FDL$SES=="laag"], breaks = 10)
> hist(FDL$Angst[FDL$SES=="gemiddeld"], breaks = 10)

> par(mfrow=c(1,1))
> t.test(FDL$Angst[FDL$SES=="laag"], FDL$Angst[FDL$SES=="gemiddeld"])

    Welch Two Sample t-test

data:  FDL$Angst[FDL$SES == "laag"] and FDL$Angst[FDL$SES == "gemiddeld"]
t = 1.8392, df = 46.608, p-value = 0.07226
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -0.1919464  4.2739977
sample estimates:
mean of x mean of y 
 21.80769  19.76667 

De groepen zijn niet significant verschillend in hun mate van angst (p = 0.07). Een andere, misschien wel elegantere, manier is:

> t.test(Angst~SES, data=FDL[FDL$SES != "hoog", ])

We hanteren hier de formule-vorm, en geven in data aan dat we FDL gebruiken, maar dan alleen de rijen waar SES ongelijk is aan “hoog” (we hebben al gezien dat dit de enige waarde is die voorkomt buiten “laag” en “gemiddeld”). Let op de komma na “hoog”, want FDL is natuurlijk een object met twee dimensies!

8.2. De gepaarde t-toets

Stel, we willen weten of er een verschil is tussen de voor- en de nameting van de FDL. Ook dan kijken we eerst naar de normaliteit rond beide gemiddelden:

> par(mfrow = c(1,2))
> hist(FDL$FDL_voor, main="", xlab="FDL voormeting", ylab="FDL-score")
> hist(FDL$FDL_na, main="", xlab="FDL nameting", ylab="FDL-score")
> par(mfrow = c(1,1))

Vervolgens, als we tevreden zijn, voeren we de gepaarde t-toets uit

> t.test(x = FDL$FDL_voor, y = FDL$FDL_na, paired = TRUE)

    Paired t-test

data:  FDL$FDL_voor and FDL$FDL_na
t = 15.886, df = 99, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 7.447066 9.572934
sample estimates:
mean of the differences 
                   8.51 

Wederom geven we twee vectoren in: de voormeting en de nameting. Met het argument paired geven we aan dat we een gepaarde t-toets willen op deze twee vectoren. De output vertelt ons dat er inderdaad een gepaarde t-toets is uitgevoerd. Een t-waarde van 15.89 geeft ons bij 99 vrijheidsgraden een p-waarde die heel dicht bij 0 ligt. Deze staat in de output zelf overigens in wetenschappelijke notatie, omdat ’ie zo dicht bij de nul ligt. Dit is te lezen als “2.2 maal 10 tot de macht -16”. We concluderen dat de gemiddelden statistisch significant van elkaar verschillen.

We kunnen hier niet met de formule-vorm werken zoals bij de onafhankelijke t-toets, en we kunnen de invoer ook niet vereenvoudigen met een data-argument (die werkt alleen bij de formule-variant, dus alleen bij de onafhankelijke t-toets). We kunnen eventueel wel het volgende doen:

> with(FDL, t.test(x = FDL_voor, y = FDL_na, paired = TRUE))

    Paired t-test

data:  FDL_voor and FDL_na
t = 15.886, df = 99, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 7.447066 9.572934
sample estimates:
mean of the differences 
                   8.51 

We stoppen de hele t.test() functie, zonder FDL$ bij de variabelenamen, in de with() functie, waarvan het eerste argument de FDL dataset is. We zeggen dus vrij letterlijk tegen R: met de FDL-data, voer een gepaarde t-toets uit met FDL_voor en FDL_na.

8.3. De chi-kwadraattoets

Stel, we willen weten of onze interventiegroepen verschillen in de man-vrouw-ratio. Hiervoor kunnen we een chi-kwadraattoets doen. Om deze te doen, moeten we eerst een kruistabel maken, zoals we hierboven al bij een opdracht gedaan hebben. Daarna volgt de chi-kwadraattoets.

> #eerst de kruistabel maken. 
> #net zoals eerder met haakjes eromheen om het resultaat direct te zien
> (kruistabel <- table(FDL$Interventie, FDL$Geslacht))
        
         man vrouw
  CGT     16    34
  CGT+FT  19    31
> 
> #vervolgens de chi-kwadraattoets
> chisq.test(kruistabel, correct=FALSE)

    Pearson's Chi-squared test

data:  kruistabel
X-squared = 0.3956, df = 1, p-value = 0.5294

Vanuit de variabele Interventie bekeken, zien we dat in de beide interventiegroepen vooral vrouwen zitten, de verhoudingen zijn ongeveer gelijk. Misschien iets meer vrouwen in de CGT-groep, maar het verschil is minimaal. Vanuit de variabele Geslacht bekeken, zien we dat er iets meer mannen in de CGT+FT-interventie zitten, en de vrouwen iets vaker in de CGT-groep, maar dat verschil is ook klein. Als we dit vervolgens statistisch toetsen, blijkt deze verdeling over de vier cellen in onze kruistabel niet statistisch significant. Om de percentages over de rijen (de behandelingen) en de kolommen te weten te komen, kunnen we de gebruikmaken van de functie prop.table():

> prop.table(kruistabel,1) # een 1 voor de rij-percentages
        
          man vrouw
  CGT    0.32  0.68
  CGT+FT 0.38  0.62
> prop.table(kruistabel,2) # een 2 voor de kolom-percentages
        
               man     vrouw
  CGT    0.4571429 0.5230769
  CGT+FT 0.5428571 0.4769231
Je geeft met een 1 aan dat je de percentages van de rijen wilt, met een 2 dat je de percentages van de kolommen wilt. Ook hier is de logica: eerst de rijen (1), dan de kolommen (2).

8.4. De correlatie

Als we de samenhang tussen twee continue variabelen willen onderzoeken, kunnen we een correlatietoets doen. Zijn beide variabelen in je vraag ongeveer normaal verdeeld, dan kunnen we een Pearson-correlatie berekenen, en als één van beide of allebei de variabelen niet normaal verdeeld zijn, kunnen we een Spearman rangcorrelatie berekenen als zogenaamd “nonparametrisch alternatief”.

Voor het eerste voorbeeld berekenen en toetsen we de correlatie tussen neuroticisme en de FDL-score bij de voormeting. Eerst gaan we eens kijken hoe ’t zit met de verdeling van beide variabelen.

> par(mfrow=c(1,2))
> hist(FDL$Neuroticisme, breaks=12) #kies het aantal breaks gerust zelf!
> hist(FDL$FDL_voor, breaks=12)
> par(mfrow=c(1,1))


Als we tevreden zijn over de normaalverdeeldheid van beide variabelen, kunnen we de correlatietoets doen:

> cor.test(FDL$Neuroticisme, FDL$FDL_voor)

    Pearson's product-moment correlation

data:  FDL$Neuroticisme and FDL$FDL_voor
t = 2.2588, df = 98, p-value = 0.02611
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.02722579 0.40133640
sample estimates:
      cor 
0.2224543 

De samenhang is niet heel groot (r = 0.22), maar wel statistisch significant (p = 0.03).

Voor Alcohol geldt dat deze niet normaal verdeeld is:

> hist(FDL$Alcohol,breaks=20)

Als we de correlatie tussen Alcohol en FDL_voor willen onderzoeken, doen we er goed aan om niet Pearson’s correlatie te gebruiken, maar Spearman’s rangcorrelatie. Dit doen we als volgt:

> cor.test(FDL$Alcohol, FDL$FDL_voor, method = "spearman")
Warning in cor.test.default(FDL$Alcohol, FDL$FDL_voor, method = "spearman"):
Cannot compute exact p-value with ties

    Spearman's rank correlation rho

data:  FDL$Alcohol and FDL$FDL_voor
S = 104123, p-value = 0.0001199
alternative hypothesis: true rho is not equal to 0
sample estimates:
      rho 
0.3752003 

We krijgen een melding “Cannot compute exact p-value with ties”. Deze melding kunnen we negeren, er wordt voor ons blijkbaar geen exacte p-waarde berekend, maar een benadering ervan. We vinden een redelijk (‘medium’) verband tussen alcoholgebruik en de voormeting van de FDL (\(\rho\) = 0.38), welke statistisch significant is (p < .001).

8.5. Meer toetsen

We hebben hierboven wat basale statistische tests besproken. Er zijn natuurlijk veel meer statistische tests beschikbaar. Denk aan nonparametrische alternatieven voor de t-toetsen hierboven, anova’s, regressie-analyses, enzovoorts. Je favoriete zoekmachine helpt je hier graag bij. Ook een aanrader is het boek van Field, Miles en Field (2012).

9. Een uitgebreid voorbeeld

Om je een beeld te geven van hoe een volledig script er uit zou kunnen zien, geven we je hieronder een voorbeeld.

### Analyse FDL-data

# Packages en data inladen
library(foreign)
FDL <- read.spss("Databestand FDL.sav", to.data.frame=TRUE)

## Data onderzoeken
summary(FDL)
dim(FDL) #hoeveel personen en hoeveel variabelen?
head(FDL)

# Normaliteit onderzoeken
par(mfrow = c(2,2))
hist(FDL$Leeftijd[FDL$Geslacht=="man"], main="", xlab="Leeftijd (m)", ylab="Frequentie")
hist(FDL$Leeftijd[FDL$Geslacht=="vrouw"], main="", xlab="Leeftijd (v)", ylab="Frequentie")
hist(FDL$FDL_voor, main="", xlab="FDL voormeting", ylab="FDL-score")
hist(FDL$FDL_na, main="", xlab="FDL nameting", ylab="FDL-score")
par(mfrow=c(1,1))

## Statistische toetsen

# Eerst een onafhankelijke t-toets voor leeftijdsverschil tussen mannen en vrouwen
t.test(Leeftijd ~ Geslacht, data=FDL)

# En een gepaarde t-toets voor de voor- en nameting
with(FDL, t.test(x = FDL_voor, y = FDL_na, paired = TRUE))

# Verschillen de interventiegroepen in ratio man/vrouw?
(kruistabel <- table(FDL$Interventie, FDL$Geslacht))
chisq.test(kruistabel, correct=FALSE)

# Is er een correlatie tussen alcoholgebruik en de FDL-voormeting?
hist(FDL$Alcohol,breaks=20)
cor.test(FDL$Alcohol, FDL$FDL_voor, method = "spearman")

10. Meer leren?

In deze tutorial heb je wat basale vaardigheden in R geleerd. Als je meer wilt weten: Google is your friend! Als je ergens tegenaan loopt, iets wilt in R maar je weet niet hoe: je bent hoogstwaarschijnlijk niet de eerste. Verder heb je bijvoorbeeld:

  • De website van Coursera, waar je diverse R-cursussen kunt volgen;
  • De zoekmachine rseek.org, die je laat zoeken binnen gerenommeerde R-websites;
  • R-bloggers, met willekeurige blogs over R-gerelateerde zaken;
  • Stack Overflow is een platform waarop vaak vragen over R gesteld en beantwoord worden;
  • Het boek R for Data Science, online gratis beschikbaar, met een schat aan informatie.