8  Funcions

A aquestes alçades del mòdul, ja hem conegut més d’una dotzena de funcions com tibble(), c() length(), ls() o class(), però encara no hem tractat amb profunditat la naturalesa de les funcions. Les funcions d’R ens permeten realitzar accions, normalment, sobre determinats objectes. Per conèixer què és exactament una funció, la millor manera és aprendre a crear-ne una de nova.

Funcions i necessitats

Per analitzar dades amb R no és necessari saber crear funcions. Això ho deixarem per als programadors i per als analistes de dades més avançats. Però hem de saber que la creació de funcions és una de les principals fortaleses dels programaris lliures com R:

  • Un usuari té una necessitat.
  • Crea una funció que cobreixi aquesta necessitat.
  • Posa la funció a disposició de tothom a dins d’un paquet.

Totes les funcions que necessitem les trobarem als paquets de base d’R o a paquets que anirem instal·lant i carregant quan ens facin falta. Però de la mateixa manera que anteriorment hem vist com es crea un marc de dades per comprendre més fàcilment quina és la seva estructura, també ens resultarà útil pel mateix motiu veure com es crea una funció. Seguidament, veurem diverses funcions i descobrirem les maneres de familiaritzar-nos ràpidament amb les seves característiques.

8.1 Crear una funció

Igual que amb els objectes, les funcions també es creen amb el símbol <-. Però a continuació posarem function() i seguit de les indicacions de l’acció que durem a terme a dins dels parèntesis {}. Fixem-nos en l’exemple següent:

  • Creem la funció per_tres().
  • La funció té un únic argument: x.
  • L’ordre o acció que deixem guardada és: multiplica x pel valor 3.
per_tres <- function(x) {x * 3}

Un cop haguem creat la funció per_tres(), anem a testar-la. Indicarem quin és el valor de la funció que ha de substituir per x:

per_tres(x = 4)
## [1] 12
per_tres(x = elections$turnout)
## [1] 164.94 167.91 229.74 141.99 246.72
per_tres(x = 1:20)
##  [1]  3  6  9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60

La majoria de funcions fan el que acabem d’observar: transformen objectes i ens retornen el resultat de la transformació. En el primer cas, la funció per_tres() retorna el resultat 12. Això és perquè les indicacions de la funció eren molt clares: multiplica x per 3. En el segon i el tercer cas, multipliquem per tres cada nombre del vector numèric indicat a dins de la funció.

Els arguments són cada una de les indicacions que podem donar a dins d’una funció. La funció per_tres() té un sol argument: x. Però podríem dissenyar una funció amb varis arguments:

  • Creem la funció per_tres_resta().
  • Primer li diem que multipliqui l’argument x per tres.
  • Després li diem que resti l’argument y, que per defecte serà zero.
per_tres_resta <- function(x, y = 0) {x * 3 - y}
Una funció per dins

Com està feta la funció factor()? Examina alguna de les funcions que has après en els apartats anteriors. A l’R Script, només has de prémer Ctrl i fer clic al nom de la funció.

Ara testarem la funció per_tres_resta(), que multiplicarà per tres el valor d’x i al resultat obtingut hi restarà y:

per_tres_resta(x = 10, y = 5)
## [1] 25

Hi ha quatre idees clau addicionals que hem de saber sobre les funcions:

  1. No és necessari posar a dins de les funcions el nom dels arguments. Podem posar directament els valors de cada argument, sempre i quan respectem el seu ordre intern: primer l’argument x i després l’argument y.
per_tres_resta(10, 5)
## [1] 25
  1. Podem consultar l’ordre dels arguments amb funcions com args().
args(per_tres_resta)
## function (x, y = 0) 
## NULL
  1. Alguns arguments tenen a vegades valors per defecte. En la funció per_tres_resta() hem indicat explíticament que si no indiquem cap valor, l’argument y tindrà associat per defecte el valor 0.
per_tres_resta(10)
## [1] 30
  1. Les funcions també poden operar a dins d’altres funcions. La funció que actua primer és sempre la de dins, i el seu resultat serà utilitzat com a argument de la funció de fora.

Observem els dos exemples:

La funció per_tres_resta() multiplica el valor 10 per tres i hi resta el valor cinc, de manera que el resultat és 25. Aquest resultat l’utilitzem com a argument de la funció per_tres(), que multiplicarà 25 per tres, obtenint com a resultat final el valor 75.

per_tres(per_tres_resta(10, 5))
## [1] 75

La funció per_tres() multiplica per tres el valor 10. El resultat 30 és utilitzat com a primer argument de per_tres_resta(), que multiplica per tres i n’hi resta el valor cinc, marcat com a segon argument.

per_tres_resta(per_tres(10), 5)
## [1] 85

8.2 Quines funcions tenim?

Totes les funcions d’R segueixen la mateixa estructura:

funcio(argument1, argument2, argument3, ...)

La part complicada és saber quina acció realitza cada funció, quins arguments té i quins d’aquests arguments tenen associats valors per defecte. Aprendre tot això no té cap altre misteri que practicar i passar-se una bona estona aprenent la funcionalitat i l’estructura interna de cada funció. És per això que és molt important que cada estudiant es vagi elaborant pel seu compte una llista de funcions i vagi classificant-les de la manera que cregui més convenient.

A continuació hem establert una classificació de tres tipus de funcions a les quals els aplicarem el contingut de l’objecte nig, que és una versió reduïda d’una enquesta realitzada per Afrobarometer a la població nigeriana l’any 2021 (Afrobarometer, 2021).

Exercici 8.1 (Prepara les dades d’Afrobarometer) Descarrega aquí l’arxiu nig.csv i importa’l a R a través d’algun dels procediments que coneixes, com la funció read_csv() del paquet tidyr.

nig <- read_csv("Datasets/nig.csv")

8.2.1 Funcions sense arguments

Un primer bloc de funcions són les que normalment utilitzarem sense introduir-hi cap argument. Són funcions que executen accions relacionades amb la interfície d’R, com consultar el Global Environment, els paquets instal·lats o els paquets carregats.

ls()
installed.packages()
search()
getwd()

8.2.2 Funcions amb un argument

Un altre grup de funcions són aquelles que normalment utilitzarem introduint un sol argument. Cal dir que pràcticament totes aquestes funcions accepten més arguments, però en la gran majoria dels casos només hi posarem un argument, que acostumarà a ser un objecte, com un vector o un marc de dades. Aquí tenim varis exemples de com les funcions ens poden respondre a preguntes simples sobre el marc de dades nig o els vectors que el conformen:

  • Què conté el marc de dades nig? Amb glimpse() fem una ullada a les dades:
glimpse(nig)
## Rows: 1,599
## Columns: 15
## $ age                        <chr> "26", "25", "35", "79", "19", "34", "30", "…
## $ language                   <chr> "Igbo", "Other", "Hausa", "Other", "English…
## $ urban_rural                <chr> "Urban", "Rural", "Rural", "Rural", "Rural"…
## $ region                     <chr> "IMO", "FCT ABUJA", "FCT ABUJA", "FCT ABUJA…
## $ electricity_nearby         <chr> "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "…
## $ piped_water_nearby         <chr> "Yes", "Can't determine", "Can't determine"…
## $ sweage_system_nearby       <chr> "Yes", "No", "No", "No", "No", "Yes", "No",…
## $ post_office_nearby         <chr> "No", "No", "No", "No", "No", "No", "No", "…
## $ school_nearby              <chr> "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "…
## $ soldiers_nearby            <chr> "Yes", "No", "No", "No", "No", "Yes", "No",…
## $ customs_checkpoints_nearby <chr> "No", "No", "No", "No", "No", "No", "No", "…
## $ situation_country          <chr> "Very Bad", "Very Bad", "Fairly Good", "Nei…
## $ situation_personal         <chr> "Fairly Good", "Fairly Bad", "Very good", "…
## $ armed_forces_trust         <chr> "Somewhat", "Just a little", "A lot", "Not …
## $ courts_law_trust           <chr> "Not at all", "Not at all", "Somewhat", "No…
  • Quina és la llengua dels enquestats? La funció unique() ens retorna els valors únics del vector nig$language.
unique(nig$language)
##  [1] "Igbo"           "Other"          "Hausa"          "English"       
##  [5] "Pidgin English" "Yoruba"         "Tiv"            "Ebira"         
##  [9] "Isoko"          "Idoma"          "Don't know"     "Urhobo"        
## [13] "Refused"        "Fulani"         "Ijaw"           "Kanuri"        
## [17] "Edo"            "Agatu"          "Esan"           "Igala"         
## [21] "Ogoni"          "Ikwere"         "Nupe"           "Anang"         
## [25] "Ibibio"         "Efik"           "Karekare"       "Berom"
  • Quantes llengües diferents tenim a l’enquesta? Podem afegir length() al codi anterior perquè ens compti el total de valors únics nig$language.
length(unique(nig$language))
## [1] 28
  • Quants ciutadans tenen soldats a prop del municipi on viuen? Amb table() podem veure les freqüències dels valors de nig$soldiers_nearby.
table(nig$soldiers_nearby)
## 
## Can't determine              No             Yes 
##               8            1439             152
  • Confia la ciutadania nigeriana en les seves forces armades?: Si combinem table() i barplot() podem graficar la distribució per freqüències de nig$armed_forces_trust.
barplot(table(nig$armed_forces_trust))

Així doncs, normalment utilitzarem només un argument a les funcions per extreure informació de les dades de forma ràpida.

8.2.3 Funcions amb varis arguments

Com hem dit abans, però, la majoria de funcions tenen més d’un argument. Això ens complica les coses com a usuaris d’R, perquè si ja és difícil de recordar tantes funcions com hi ha, encara ho és més si tenen varis arguments associats. Per sort, algunes d’elles tenen arguments fàcils de recordar, perquè cada argument té exactament la mateixa funcionalitat. És el cas, per exemple, de tres funcions que ja hem vist anteriorment:

  • c(): serveix per concatenar diferents valors, de manera que cada valor és un argument.
  • tibble(): permet crear un marc de dades a partir de d’ubicar vectors d’igual longitud a cada argument.
  • table(): si posem un segon argument en forma de vector de la mateixa longitud del primer argument, ens creuarà les dades en forma de taula de freqüències.
table(nig$situation_country, nig$electricity_nearby)
##                       
##                         No Yes
##   Don't know             4   4
##   Fairly Bad           103 257
##   Fairly Good          143 206
##   Neither good nor bad  47  88
##   Refused                0   1
##   Very Bad             145 409
##   Very good             78 114

Les funcions més complicades són aquelles que tenen varis arguments i que cada un dels seus arguments té una funcionalitat diferent. Aquestes són més difícils de recordar. Anem a veure un exemple amb sample(), una funció que permet obtenir una mostra aleatòria d’un conjunt de dades. Fixem-nos que té varis arguments:

args(sample)
## function (x, size, replace = FALSE, prob = NULL) 
## NULL

Anem a veure’ls un per un.

L’argument x és un vector. Si l’introduïm, sample() ens retorna els valors del vector desordenats de manera aleatòria.

sample(1:10)

El segon argument, size, ens retorna un vector de longitud diferent (més petita) al vector que hem posat a x. Suposem que fem el sorteig de dues paneres i hem repartit 100 butlletes. Podem decidir els guanyadors fent ús d’R, demanant-li que tregui dos nombres a l’atzar.

sample(1:100, 2)
## [1] 51 54

A vegades, podem voler que es repeteixin els valors. És a dir, que un cop tret un valor a l’atzar, R el torni a posar a dins el bombo perquè pugui tornar a sortir a la següent tirada. Això passa, per exemple, quan llancem una moneda a l’aire, que es poden repetir les cares o les creus. Això s’aconsegueix introduint el tercer argument replace.

moneda <- c("Cara", "Creu")
sample(moneda, 5, replace = TRUE)
## [1] "Cara" "Creu" "Cara" "Cara" "Cara"

Fins i tot podem trucar la moneda amb un quart argument, on li indiquem la probabilitat que surti cada valor. En aquesta moneda, serà menys probable que surti cara.

moneda <- c("Cara", "Creu")
sample(moneda, 10, replace = TRUE, prob = c(0.2, 0.8))
##  [1] "Creu" "Creu" "Creu" "Creu" "Creu" "Creu" "Creu" "Creu" "Creu" "Creu"

I hem de memoritzar tots els arguments de cada una de les funcions d’R? És clar que no! En primer lloc, hem de saber que normalment treballem amb un grup reduït d’entre 30 i 50 funcions, de les quals amb la majoria d’elles utilitzem només un argument. I en segon lloc, per sort tenim les eines d’ajuda, que ens seran molt útils per refrescar-nos la memòria quan fem servir funcions que requereixen varis arguments.

8.3 Ajuda amb les funcions

Els usuaris d’R acostumen a tenir mala memòria per recordar les funcions i els seus arguments. És per això que per treballar amb R és absolutament imprescindible utilitzar les eines d’ajuda que ofereix la comunitat d’usuaris del programa. No hi ha cap usuari d’R, per molt avançats que siguin els seus coneixements, que no utilitzi l’ajuda en el seu dia a dia.

Com hem vist anteriorment, una possibilitat que tenim és la funció args(), que ens recorda els arguments d’una funció. Però més enllà d’una llista d’arguments, hem de saber que totes les funcions d’R tenen un quadre d’ajuda que convé saber utilitzar. També és fonamental dominar altres recursos de què disposa R a la xarxa.

8.3.1 Quadre d’ajuda

El quadre d’ajuda s’obre de la següent manera:

?funcio

I normalment consta dels següents apartats:

  • Description: Una petita explicació de la seva funcionalitat.
  • Usage: Com s’utilitza la funció en codi.
  • Arguments: Fa una descripció de com s’han d’utilitzar els arguments de la funció.
  • Altres apartats: Rarament els farem servir.
  • Examples: Molt important. Normalment inclou exemples reproduïbles que permetran fer-nos una idea

Vegem-ne tres exemples:

Si mirem l’ajuda de ?mean veiem que només cal un sol argument (x), però també accepta altres arguments.

  • x acostuma a ser un vector numèric o lògic.
  • trim és per defecte 0 i elimina les observacions dels extrems a l’hora de fer la mitjana del vector numèric que indiquem a x.
  • na.rm (per defecte FALSE) elimina els valors perduts (NA).
mean(elections$turnout)
## [1] 63.42

Si posem trim = 0.2, eliminarà els valors que es trobin al 20 % superior i 20 % inferior de la distribució. Per tant, eliminarà 82.24 i 47.33 abans de fer la mitjana.

mean(elections$turnout, trim = 0.2)
## [1] 62.51

Si mirem l’ajuda de ?str_replace, del paquet stringr veiem que té tres arguments i els tres són necessaris per executar la funció.

  • stringr és l’string (vector de caràcter) on practicarem els canvis.
  • pattern és el patró que haurà de buscar a l’string en qüestió.
  • replacement és el vector de caràcter, normalment d’un sol valor, amb què substituirà el patró que hem indicat.
str_replace(elections$country, "Germany", "France")
[1] "Colombia"    "Japan"       "France"      "Chile"       "New Zealand"

A l’apartat d’Examples tenim exemples molt útils per veure les possibilitats que té la funció.

El quadre d’ajuda també serveix per explorar marcs de dades que estan vinculats a paquets. Si consultem l’ajuda de ?who, del paquet tidyr, veurem que ens mostra dades del Global Tuberculosis Report. A Format habitualment trobarem el llibre de codis del marc de dades.

Exercici 8.2 (Menú d’ajuda) Investiga les funcions funcions ?seq(), ?rep() i ?sort() mitjançant el quadre d’ajuda. Intenta construir un codi per a cada funció per tal que retorni a la consola el resultat que s’indica a continuació:

  • seq(): 7 12 17 22 27 32
  • rep(x = q): 3 3 9 9 5 5 6 6 3 3 9 9
  • sort(x = q): 9 6 5 3

En les dues últimes funcions hauràs d’indicar, com a argument x, l’objecte q i intentar esbrinar la resta d’arguments:

q <- c(3, 9, 5, 6)
seq(from=7,to=32,by=5)
## [1]  7 12 17 22 27 32
rep(x=q,length.out=12,each=2)
##  [1] 3 3 9 9 5 5 6 6 3 3 9 9
sort(x=q,decreasing=TRUE)
## [1] 9 6 5 3

8.3.2 La xarxa

Després del quadre d’ajuda, la xarxa és la segona gran amistat que té l’analista de dades d’R.

  • Cheat sheets: La seva traducció literal seria ‘xuletes’. En un espai molt reduït, expliquen com utilitzar les funcions d’un paquet determinat. La majoria es poden trobar a la web de Posit.
  • Stackoverflow: Stackoverflow és un fòrum d’usuaris de programes estadístics com R. Funciona de la manera següent: ens donem d’alta, obrim una consulta i, al cap de pocs minuts, algun usuari ens haurà retornat una resposta.
  • Google: Òbviament un recurs senzill és copiar l’error de la consola a Google o fer una pregunta al buscador de Google (preferiblement en anglès). Alguna de les primeres entrades segurament ens conduirà a una pàgina web on algú abans ha tingut el mateix problema i on algú altre li haurà indicat la solució.

Exercici 8.3 (Funcions) Practica aquí el que has après en aquesta secció. Recorda que no cal utilitzar R per realitzar els exercicis.