Capitolo 14 Programmazione Iterativa
L’essenza della maggior parte delle operazioni nei vari linguaggi di programmazione è quella del concetto di iterazione. Iterazione significa ripetere una porzione di codice un certo numero di volte o fino anche una condizione viene soddisfatta.
Molte delle funzioni che abbiamo usato finora come la funzione sum()
o la funzione mean()
si basano su operazioni iterative. In R, purtroppo o per fortuna, userete abbastanza raramente delle iterazioni tramite loop anche se nella maggior parte delle funzioni sono presenti. Infatti molte delle funzioni implementate in R sono disponibili solo con pacchetti esterni oppure devono essere scritte manualmente implementando strutture iterative.
14.1 Loop
14.1.1 For
Il primo tipo di struttura iterativa viene denominata ciclo for
. L’idea è quella di ripetere una serie di istruzioni un numero predefinito di volte.La Figura 14.1 rappresenta l’ idea di un ciclo for
. In modo simile alle strutture condizionali del capitolo precedente, quando scriviamo un ciclo, entriamo in una parte di codice temporaneamente, eseguiamo le operazioni richieste e poi continuiamo con il resto del codice. Quello che nell’immagine è chiamato i
è un modo convenzionale di indicare il conteggio delle operazioni. Se vogliamo ripetere un’operazione 1000 volte, i
parte da 1 e arriva fino a 1000.
Struttura For Loop
In R la scrittura del ciclo for
è la seguente:
i
è un nome generico per indicare la variabile conteggio che abbiamo introdotto prima. Può essere qualsiasi carattere, ma solitamente per un ciclo generico si utilizzano singole lettere comei
oj
proabilmente per una similarità con la notazione matematica che spesso utilizza queste lettere per indicare una serie di elementiin
è l’operatore per indicare chei
varia rispetto ai valori specificati di seguitoc(...)
è il range di valori che assumerài
in per ogni iterazione
Possiamo riformulare il codice in:
Ripeti le operazioni incluse tra {} un numero di volte uguale alla lunghezza di
c(...)
e in questo ciclo,i
assumerà, uno alla volta i valori contenuti inc(...)
.
Informalmente ci sono due tipi di ciclo, quello che utilizza un counter generico da assegnare a i
e un’altro che utilizza direttamente dei valori di interesse.
Esempio
- Loop con direttamente i valori di interesse
# numerico
# caratteri
for (name in c("Alessio", "Beatrice", "Carlo")){
print(paste0("Ciao ", name))
}
## [1] "Ciao Alessio"
## [1] "Ciao Beatrice"
## [1] "Ciao Carlo"
Loop che utilizza un counter generico per indicizzare gli elementi:
my_vector <- c(93, 27, 46, 99)
# i in 1:length(my_vector)
for (i in seq_along(my_vector)){
print(my_vector[i])
}
## [1] 93
## [1] 27
## [1] 46
## [1] 99
Questa distinzione è molto utile e spesso fonte di errori. Se si utilizza direttamente il vettore e il nostro counter assume i valori del vettore, “perdiamo” un indice di posizione. Nell’esempio del ciclo con i nomi infatti, se volessimo sapere e stampare quale posizione occupa Alessio dobbiamo modificare l’approccio, puntando ad utilizzando anche counter generico. Possiamo crearlo fuori dal ciclo e aggiornarlo manualmente:
i <- 1
for (name in c("Alessio", "Beatrice", "Carlo")){
print(paste0(name, " è il numero ", i))
i <- i + 1
}
## [1] "Alessio è il numero 1"
## [1] "Beatrice è il numero 2"
## [1] "Carlo è il numero 3"
In generale, il modo migliore è sempre quello di utilizzare un ciclo utilizza indici e non i valori effettivi, in modo da poter accedere comunque entrambe le informazioni.
Esempio: la funzione somma
Come introdotto all’inizio di questo capitolo, molte delle funzioni disponibili in R derivano da strutture iterative. Se pensiamo alla funzione sum()
sappiamo che possiamo calcolare la somma di un vettore semplicemente con sum(x)
. Per capire appieno i cicli, è interessante pensare e implementare le funzioni comuni.
Se dovessimo sommare n
numeri a mano la struttura sarebbe questa:
- prendo il primo numero \(x_1\) e lo sommo con il secondo \(x_2\)
- ottengo un nuovo numero
x_{1+2}
- prendo il terzo numero \(x_3\) e lo sommo a
x_{1+2}
- ottengo \(x_{1+2+3}\)
- ripeto questa operazione fino all’ultimo elemento di \(x_n\)
Come vedete, questa è una struttura iterativa, che conta da 1 alla lunghezza di \(x\) ed ogni iterazione somma il successivo con la somma dei precedenti. In R:
my_values <- c(2,4,6,8)
# Calcolare somma valori
my_sum <- 0 # inizializzo valore
for (i in seq_along(my_values)){
my_sum <- my_sum + my_values[i]
}
my_sum
## [1] 20
La struttura è la stessa del nostro ragionamento in precedenza. Creo una variabile “partenza” che assume valore 0 ed ogni iterazione sommo indicizzando il rispettivo elemento.
Esempio: creo un vettore
Essendo che utilizziamo un indice che assume un range di valori, possiamo non solo accedere ad un vettore utilizzando il nostro indice ma anche creare o sostituire un vettore progressivamente.
# Calcola la somma di colonna
my_matrix <- matrix(1:24, nrow = 4, ncol = 6)
# Metodo non efficiente (aggiungo valori)
sum_cols <- c()
for( i in seq_len(ncol(my_matrix))){
sum_col <- sum(my_matrix[, i]) # calcolo i esima colonna
sum_cols <- c(sum_cols, sum_col) # aggiungo il risultato
}
sum_cols
## [1] 10 26 42 58 74 90
# Metodo efficiente (aggiorno valori)
sum_cols <- vector(mode = "double", length = ncol(my_matrix))
for( i in seq_along(sum_cols)){
sum_col <- sum(my_matrix[, i]) # calcolo i esima colonna
sum_cols[i] <- sum_col # aggiorno il risultato
}
sum_cols
## [1] 10 26 42 58 74 90
14.1.2 While
Il ciclo while
può essere considerato come una generalizzazione del ciclo for
. In altri termini il ciclo for
è un tipo particolare di ciclo while
.
Struttura While Loop
La scrittura è più concisa del ciclo for
perchè non definiamo nessun counter o placeholder e nemmeno un vettore di valori. L’unica cosa che muove un ciclo while
è una condizione logica (quindi con valori booleani TRUE
e FALSE
). Anche qui, parafrasando:
Ripeti le operazioni incluse tra {} fino a che la condizione
<test>
è VERA.
In altri termini, ad ogni iterazione la condizione <test>
viene valutata. Se questa è vera, viene eseguita l’operazione altrimenti il ciclo si ferma.
Esempio
Se vogliamo fare un conto alla rovescia
count <- 5
while(count >= 0){
print(count)
count <- count - 1 # aggiorno variabile
}
## [1] 5
## [1] 4
## [1] 3
## [1] 2
## [1] 1
## [1] 0
Quando si scrive un ciclo while
è importante assicurarsi di due cose:
- Che la condizione sia
TRUE
inizialmente, altrimenti il ciclo non comincierà nemmeno - Che ad un certo punto la condizione diventi
FALSE
(perchè abbiamo ottenuto il risultato o perchè è passato troppo tempo o iterazioni)
Se la seconda condizione non è rispettata, otteniamo quelli che si chiamano endless loop
come ad esempio:
14.1.2.1 While e For
Abbiamo introdotto in precedenza che il for
è un tipo particolare di while
. Concettualmente infatti possiamo pensare ad un for
come un while
dove il nostro counter i
incrementa fino alla lunghezza del vettore su cui iterare. In altri termini possiamo scrivere un for
anche in questo modo:
14.1.3 Next e Brake
All’interno di una struttura iterativa, possiamo eseguire qualsiasi tipo di operazione, ed anche includere strutture condizionali. Alcune volte può essere utile saltare una particolare iterazione oppure interrompere il ciclo iterativo. In R tali operazioni posso essere eseguite rispettivamene coni comandi next
e break
.
next
- passa all’iterazione successivabreak
- interrompe l’esecuzione del ciclo
14.2 Nested loop
Una volta compresa la struttura iterativa, è facile espanderne le potenzialità inserendo un ciclo all’interno di un altro. Possiamo avere quanti cicli nested necessari, chiaramente aumenta non solo la complessità ma anche il tempo di esecuzione. Per capire al meglio cosa succede all’interno di un ciclo nested è utile visualizzare gli indici:
for(i in 1:3){ # livello 1
for(j in 1:3){ # livello 2
for(l in 1:3){ # livello 3
print(paste(i, j, l))
}
}
}
## [1] "1 1 1"
## [1] "1 1 2"
## [1] "1 1 3"
## [1] "1 2 1"
## [1] "1 2 2"
## [1] "1 2 3"
## [1] "1 3 1"
## [1] "1 3 2"
## [1] "1 3 3"
## [1] "2 1 1"
## [1] "2 1 2"
## [1] "2 1 3"
## [1] "2 2 1"
## [1] "2 2 2"
## [1] "2 2 3"
## [1] "2 3 1"
## [1] "2 3 2"
## [1] "2 3 3"
## [1] "3 1 1"
## [1] "3 1 2"
## [1] "3 1 3"
## [1] "3 2 1"
## [1] "3 2 2"
## [1] "3 2 3"
## [1] "3 3 1"
## [1] "3 3 2"
## [1] "3 3 3"
Guardando gli indici, è chiaro che il ciclo più interno viene ultimato per primo fino ad arrivare a quello più esterno. La logica è la seguente:
- La prima iterazione entriamo nel ciclo più esterno
i = 1
, poi in quello internoj = 1
e in quello più internol = 1
. - nella seconda iterazione siamo bloccati nel ciclo interno e quindi sia
i
chej
saranno 1 mentrel
sarà uguale a 2. - quando il ciclo
l
sarà finito,i
sarà sempre 1 mentrej
passerà a 2 e così via
Un aspetto importante è l’utilizzo di indici diversi, infatti i valori i
, j
e l
assumono valori diversi ad ogni iterazione e se usassimo lo stesso indice, non otterremo il risultato voluto.
Esercizi
- Scrivi una funzione che calcoli la media di un vettore numerico usando un for loop.
- Scrivi una funzione che dato un vettore numerico restituisca il valore massimo e minimo usando un for loop (attenti al valore di inizializzazione).
- Scrivi una funzione che per ogni iterazione generi \(n\) osservazioni da una normale (funzione
rnorm()
) con media \(mu\) e deviazione standard \(sigma\), salva la media di ogni campione. I parametri della funzione saranno \(n\), \(mu\), \(sigma\) e \(iter\) (numero di iterazioni).
14.3 Apply Family
Ci sono una famiglia di funzioni in R estremamente potenti e versatili chiamate *apply
. L’asterisco suggerisce una serie di varianti presenti in R che nonostante la struttura e funzione comune hanno degli obiettivi diversi:
apply
: dato un dataframe (o matrice) esegue la stessa funzione su ogni riga o colonnatapply
: dato un vettore di valori esegue la stessa funzione su ogni gruppo che è satato definitolapply
: esegue la stessa funzione per ogni elemento di una lista. Restituisce ancora una listasapply
: esegue la stessa funzione per ogni elemento di una lista. Restituisce se possibile un oggetto seplificato (un vettore, una matrice o un array)vapply
: analogo asapply
ma richiede di definire il tipo di dati restituitimapply
: è la versione multivariata. Permette di applicare una funzione a più liste di elementi
Prima di illustrare le varie funzioni è utile capire la struttura generale. In generale queste funzioni accettano un oggetto lista quindi un insieme di elementi e una funzione. L’idea infatti è quella di avere una funzione che accetta altre funzioni come argomenti e applichi la funzione-argomento ad ogni elemento in input.
Queste funzioni, sopratutto in R, sono spesso preferite rispetto ad utilizzare cicli for
per velocità, compattezza e versatilità.
Hadley Wickam[^talk-map] riporta un bellissimo esempio per capire la diffenza tra loop
e *apply
. Immaginiamo di avere una serie di vettori e voler applicare alcune funzioni ad ogni vettore, possiamo impostare un semplice loop in questo modo:
list_vec <- list(
vec1 <- rnorm(100),
vec2 <- rnorm(100),
vec3 <- rnorm(100),
vec4 <- rnorm(100),
vec5 <- rnorm(100)
)
means <- vector(mode = "numeric", length = length(list_vec))
medians <- vector(mode = "numeric", length = length(list_vec))
st_devs <- vector(mode = "numeric", length = length(list_vec))
for(i in seq_along(list_vec)){
means[i] <- mean(list_vec[[i]])
medians[i] <- median(list_vec[[i]])
st_devs[i] <- sd(list_vec[[i]])
}
Nonostante sia perfettamente corretto, questa scrittura ha diversi problemi:
- E’ molto ridondante. Tra calcolare media, mediana e deviazione standard l’unica cosa che cambia è la funzione applicata mentre dobbiamo per ognuno preallocare una variabile, impostare l’indicizzazione in base all’iterazione per selezionare l’elemento della lista e memorizzare il risultato. Per migliorare questa scrittura possiamo mettere in una funzione tutta questa struttura (preallocazione, indicizzazione e memorizzazione) e utilizzare questa funzione con argomenti la lista di input e la funzione da applicare. Utilizzando la funzione
sapply
:
means <- lapply(list_vec, mean)
means
## [[1]]
## [1] -0.1744844
##
## [[2]]
## [1] -0.001581971
##
## [[3]]
## [1] 0.003746107
##
## [[4]]
## [1] -0.1040844
##
## [[5]]
## [1] 0.2978849
medians <- lapply(list_vec, median)
medians
## [[1]]
## [1] -0.1336871
##
## [[2]]
## [1] 0.08244486
##
## [[3]]
## [1] 0.002455108
##
## [[4]]
## [1] -0.00886783
##
## [[5]]
## [1] 0.2095848
st_devs <- lapply(list_vec, sd)
st_devs
## [[1]]
## [1] 1.028626
##
## [[2]]
## [1] 1.056527
##
## [[3]]
## [1] 1.027976
##
## [[4]]
## [1] 0.9351077
##
## [[5]]
## [1] 1.04055
Come vedete il codice diventa estremamente compatto, pulito e facile da leggere.
14.3.1 Quali funzioni applicare?
Prima di descrivere nel dettaglio ogni funzione *apply
è importante capire quali tipi di funzioni possiamo usare all’interno di questa famiglia. In generale, qualsiasi funzione può essere applicata ma per comodità possiamo distinguerle in:
- funzioni già presenti in R
- funzioni personalizzate (create e salvate nell’ambiente principale)
- funzioni anonime
Nell’esempio precedente, abbiamo utilizzato la funzione mean
semplicemente scrivendo lapply(lista, mean)
. Questo è possibile perchè mean
necessita di un solo argomento. Se tuttavia volessimo applicare funzioni più complesse o aggiungere argomenti possiamo usare la scrittura più generale:
means <- lapply(list_vec, function(x) mean(x))
means
## [[1]]
## [1] -0.1744844
##
## [[2]]
## [1] -0.001581971
##
## [[3]]
## [1] 0.003746107
##
## [[4]]
## [1] -0.1040844
##
## [[5]]
## [1] 0.2978849
L’unica differenza è che abbiamo definito una funzione anonima con la scrittura function(x) ...
. Questa scrittura si interpreta come “ogni elemento di list_vec
diventa x
, quindi applica la funzione mean()
per ogni elemento di list_vec
”. La funzione anonima permette di scrivere delle funzioni non salvate o presenti in R e applicare direttamente ad una serie di elementi. Possiamo anche usare funzioni più complesse come centrare ogni elemento di list_vec
:
centered_list <- lapply(list_vec, function(x) x - mean(x))
centered_list
## [[1]]
## [1] 0.052024420 0.726941033 0.523133907 0.534116649 1.072538094
## [6] -1.748085116 0.436228769 1.090050776 0.188256343 1.904447568
## [11] -0.907720449 -0.098340775 0.356479804 1.683026192 1.778954510
## [16] -1.666991209 1.797794611 0.305873422 1.655606875 1.687802692
## [21] -0.767958869 -0.011200606 -0.926640234 1.382599658 -1.450454139
## [26] 0.279862739 -1.280958942 -0.179531732 0.080784364 1.275153032
## [31] -1.789340704 -1.273459981 1.193927825 -1.246932674 -0.430047712
## [36] -1.408989494 -1.111447945 -1.280200478 0.087413286 0.679220848
## [41] 0.290873113 1.934698137 -0.170632058 2.294484562 0.140106914
## [46] -0.617669647 1.649999618 -0.551072804 0.486863447 0.866448514
## [51] -0.325806393 -2.081384947 0.218225732 -0.194333682 -0.785738000
## [56] 0.278250713 0.601773540 0.004002865 -1.374655893 -1.331115539
## [61] 0.190527940 -0.010879909 0.566417661 -0.582226517 0.405902013
## [66] -0.809129005 0.739565224 1.791236297 -0.077479735 -0.881394205
## [71] -0.173747353 0.131494431 -1.223069551 1.664700735 -0.864902716
## [76] -0.062460642 -0.824657046 -1.218058185 1.156489655 0.535425340
## [81] -0.163024811 -0.468903190 -1.992400872 0.807773400 0.029570256
## [86] -1.065542698 0.708443728 -1.413780392 -0.816480128 0.657745237
## [91] 0.985102777 -0.119180161 0.121026086 0.909668900 0.189469398
## [96] 0.052482555 -0.472289289 -0.693373923 -0.334215899 -1.903099951
##
## [[2]]
## [1] -0.25875450 0.45192195 -0.14129965 -0.48513950 -1.19419119 0.04852296
## [7] -0.12494971 -2.69913307 -0.56923136 0.59317969 0.48855867 -0.12521792
## [13] -1.25761789 0.20287395 -1.91532248 1.67432109 0.47237159 1.41569682
## [19] 0.08588055 -1.80072157 0.75532561 -0.31036112 -1.73097833 -2.13698056
## [25] 2.36738038 0.48633857 1.09481961 0.30449038 1.01688141 2.45517490
## [31] -0.24403885 0.54310131 0.19845858 -2.06958035 0.51416551 -0.40417991
## [37] 0.35777998 -0.32998890 0.08217311 -0.25995052 -0.87586827 0.74289078
## [43] -2.68138122 -0.94789651 0.44784397 -1.28724749 -0.15622349 0.34939757
## [49] -0.05804050 1.47831075 -0.65258270 -0.25523286 -1.25245596 0.77262514
## [55] -0.90976997 -0.69172340 -0.61608640 0.76370516 -1.08557639 -0.39821755
## [61] 0.82937843 0.35700165 0.16072076 0.95697716 -0.33806032 -0.72579950
## [67] -1.69622426 1.95572036 2.66831987 2.06496139 0.82036535 -0.07806814
## [73] -0.48786746 0.84930072 -0.95746280 0.93026931 0.38254698 1.49618576
## [79] -0.46612240 0.26273926 -0.99102667 -1.06179903 0.27586663 0.94692452
## [85] 0.72777105 -0.25293070 1.48676214 0.23186813 0.27958462 0.14862272
## [91] -1.19470741 0.09171824 1.22085175 -0.55991059 0.33846373 -1.53521819
## [97] -0.23854774 0.51644745 -0.23727325 0.58340054
##
## [[3]]
## [1] 0.2664491638 -1.3469963272 -0.8526350420 -0.4113540385 -0.6698965617
## [6] -0.1069834877 -0.9277972315 0.4348533514 -0.0158461161 -0.6496568470
## [11] 1.3404006827 0.3308553677 -0.0008524137 1.0932070221 1.1595245506
## [16] 0.0970402348 -0.3954985959 -0.4148154462 -0.9173273893 -0.5441391984
## [21] 0.1176161927 1.7239915983 -0.0479376631 0.5410962859 1.9136056395
## [26] -0.2403162233 1.5678772409 0.4771039676 0.0388510442 0.4360775197
## [31] -1.8843109374 -1.7278160519 1.8794222142 -0.0350334621 0.9952978517
## [36] 0.2965731726 -0.0089397169 -0.2440504016 -1.7747293817 -0.1599453739
## [41] 1.9116204426 1.1063735070 -1.9894549474 0.8106197600 1.0945052782
## [46] -1.4444401450 1.2114915468 -0.7866311249 -2.0708321586 0.3306083127
## [51] 0.2571921978 -0.4380882424 -1.8767536475 -0.8067906332 0.3285759849
## [56] 0.0083665339 0.8463602573 -1.5346139219 -0.0353043768 1.4238938000
## [61] -0.9307764997 1.0018625824 -0.0897035610 0.9342787542 -0.5220418242
## [66] -0.9382625616 1.2278393538 -0.2433379966 0.2694431409 1.4270790033
## [71] -1.1447635626 0.8922605510 0.0790918016 0.3463267640 -1.2620371186
## [76] 0.8359400483 -1.2621666167 0.1493675938 -0.4874471285 2.2071080499
## [81] 0.0770783378 0.7123603570 1.0507096270 0.2724235769 -0.4240092719
## [86] 2.5187562528 -0.0017295860 -1.5659889251 -1.9915300574 -1.4957999457
## [91] 0.2514953082 -0.8182432673 0.8301238234 -1.1562445178 -0.1441958015
## [96] 1.1723315781 0.1831991812 -0.6985203200 -0.4631891217 -0.4787516183
##
## [[4]]
## [1] 1.31014158 1.09819466 1.64324210 0.39593137 0.61463274 -0.43683054
## [7] -0.56982943 0.78423560 0.29079922 -1.28144712 -0.95320981 0.47832305
## [13] -0.89843251 0.06142016 0.09834140 -0.93750994 -0.22114701 -0.50447853
## [19] 1.62135004 0.40054994 0.50377989 0.85258426 0.01601205 -0.98150469
## [25] 0.01879710 0.56958632 0.10405552 0.72039850 -0.54796577 0.55132606
## [31] -0.07849753 -0.70235630 -1.70546632 -0.67537030 -1.88424294 1.10131999
## [37] -0.93803709 0.30884776 0.38639004 0.26406936 -1.48220109 0.64941069
## [43] -1.26245228 -1.01196361 0.26655677 1.61573387 -0.24499703 -1.86615753
## [49] -0.74331502 -1.66625739 1.19375590 -1.29834857 1.03931548 0.09209179
## [55] 0.36417167 1.25497932 1.27163836 -1.08423536 0.32463046 1.23838613
## [61] 0.61505477 -1.39306979 -1.01347083 -0.65377057 -0.67234873 -0.15951329
## [67] -1.09475421 -1.24828431 0.44936851 -0.81031734 0.48577901 -0.44783374
## [73] 0.13878656 -0.42861642 1.05753379 0.80005894 -0.56816795 1.42707901
## [79] -0.24334500 -0.42674961 0.83810754 0.25860309 0.76271285 -0.83189895
## [85] -1.83769866 0.13777046 -0.34121388 1.28152635 0.02692627 0.65523055
## [91] -0.34789338 -1.27102060 0.10899483 1.00604579 -0.82845156 0.66493483
## [97] 0.41550756 1.49611518 2.54392757 -0.08039012
##
## [[5]]
## [1] -0.97244116 -0.05975831 0.24720098 -0.74673643 0.67336181 -1.84504878
## [7] -0.10088763 0.54085362 0.19802171 -0.21846188 -1.35504038 -1.00568442
## [13] -1.66143029 0.32770743 -0.87494585 1.42240563 1.33871389 -0.49724504
## [19] 0.23183020 -1.28236562 0.94682403 0.15097940 -0.08996773 -1.87922549
## [25] 0.56434072 1.01141751 0.59054641 0.90708246 1.81054234 1.52125463
## [31] 1.59879629 0.65703478 -1.36100263 1.52636999 -0.67568978 1.18846185
## [37] -0.71371197 -0.62705645 -0.34696123 1.81946720 -0.14410534 0.77391802
## [43] -1.40734286 -0.22652747 -0.08663243 0.24917797 0.10482833 0.40064474
## [49] -0.11465530 1.67669566 0.89489601 -1.18726409 -0.69802085 -0.34089280
## [55] -0.31607492 -1.19132141 -1.34752712 0.06417591 0.57868314 1.93393514
## [61] 1.29099212 0.28454888 -1.05159708 -0.12011029 -1.39846790 -0.34956404
## [67] 1.15216589 0.47442520 0.42866114 1.24934339 -0.17264100 -0.18756486
## [73] 1.52868692 -0.50630793 -0.52108509 -2.27123722 0.10655986 -1.07339635
## [79] -0.53370249 1.60949570 -0.08346146 -1.11549234 -1.60046934 -0.70496768
## [85] 2.72371679 -0.15590380 -1.05128938 0.86727635 0.28486870 -1.34945579
## [91] -0.81570377 -1.37540652 0.69447464 -0.31187353 -2.04954616 0.56538309
## [97] 0.55236386 1.42950364 1.15527183 -0.17463612
In questo caso, è chiaro che x
è un placeholder che assume il valore di ogni elemento della lista list_vec
.
L’uso di funzioni anonime è estremamente utile e chiaro una volta compresa la notazione. Tuttavia per funzioni più complesse è più conveniente scrivere salvare la funzione in un oggetto e poi applicarla come per mean
. Usando sempre l’esempio di centrare la variabile possiamo:
Possiamo comunque applicare funzioni complesse come anonime semplicemente utilizzando le parentesi graffe proprio come se dovessimo dichiarare una funzione:
center_vec <- function(x){
return(x - mean(x))
}
centered_list <- lapply(list_vec, function(x){
res <- x - mean(x)
return(res)
})
Un ultimo aspetto riguarda un parallismo tra x
nei nostri esempi e i
nei cicli for
che abbiamo visto in precedenza. Proprio come i
, x
è una semplice convenzione e si può utilizzare qualsiasi nome per definire l’argomento generico. Inoltre, è utile pensare a x
proprio con lo stesso ruolo di i
, infatti se pensiamo alla funzione in precedenza, x
ad ogni iterazione prende il valore di un elemento di list_vec
proprio come utilizzare il ciclo for
non con gli indici ma con i valori del vettore su cui stiamo iterando. Qualche volta infatti può essere utile applicare un principio di indicizzazione anche con l’ *apply
family:
means <- lapply(seq_along(list_vec), function(i) mean(list_vec[[i]]))
means
## [[1]]
## [1] -0.1744844
##
## [[2]]
## [1] -0.001581971
##
## [[3]]
## [1] 0.003746107
##
## [[4]]
## [1] -0.1040844
##
## [[5]]
## [1] 0.2978849
In questo caso l’argomento non è più la lista ma un vettore di numeri da 1 alla lunghezza della lista (proprio come nel ciclo for
). La funzione anonima poi prende come argomento i
(che ricordiamo può essere qualsiasi nome) e utilizza i
per indicizzare e applicare una funzione. In questo caso non è estremamente utile, ma con questa scrittura abbiamo riprodotto esattamente la logica del ciclo for
in un modo estremamente compatto.
14.3.2 apply
La funzione apply
viene utilizzata su matrici, dataframe per applicare una funzione ad ogni dimensione (riga o colonna). La struttura della funzione è questa:
Dove:
X
è il dataframe o la matriceMARGIN
è la dimensione su cui applicare la funzione:1
= riga e2
= colonnaFUN
è la funzione da applicare
14.3.3 tapply
tapply
è utile quando vogliamo applicare una funzione ad un elemento che viene diviso in base ad un’altra variabile. La scrittura è la seguente:
Dove:
X
è la variabile principaleINDEX
è la variabile in base a cui suddividereX
FUN
è la funzione da applicare
Esempi
my_data <- data.frame(
y = sample(c(2,4,6,8,10), size = 32, replace = TRUE),
gender = factor(rep(c("F", "M"), each = 16)),
class = factor(rep(c("3", "5"), times = 16))
)
head(my_data, n = 4)
## y gender class
## 1 8 F 3
## 2 10 F 5
## 3 2 F 3
## 4 4 F 5
# Media y per classe
tapply(my_data$y, INDEX = my_data$class, FUN = mean)
## 3 5
## 5.875 6.625
# Media y per classe e genere
tapply(my_data$y, INDEX = list(my_data$class, my_data$gender), FUN = mean)
## F M
## 3 6.50 5.25
## 5 5.75 7.50
14.3.4 lapply
E’ forse la funzione più utilizzata e generica. Viene applicata ad ogni tipo di dato che sia una lista di elementi o un vettore. La particolarità è quella di restituire sempre una lista come risultato, indipendentemente dal tipo di input. La scrittura è la seguente:
Dove:
X
è il vettore o listaFUN
è la funzione da applicare
Esempi
my_list <- list(
sample_norm = rnorm(10, mean = 0, sd = 1),
sample_unif = runif(15, min = 0, max = 1),
sample_pois = rpois(20, lambda = 5)
)
str(my_list)
## List of 3
## $ sample_norm: num [1:10] 0.343 1.371 0.26 0.482 0.309 ...
## $ sample_unif: num [1:15] 0.031 0.011 0.882 0.113 0.323 ...
## $ sample_pois: int [1:20] 2 3 7 9 5 3 3 5 2 4 ...
# Media
lapply(my_list, FUN = mean)
## $sample_norm
## [1] 0.5029392
##
## $sample_unif
## [1] 0.3935366
##
## $sample_pois
## [1] 4.25
14.3.5 sapply
sapply
ha la stessa funzionalità di lapply
ma ha anche la capacità di restituire una versione semplificata (se possibile) dell’output.
Esempi
# Media
sapply(my_list, FUN = mean)
## sample_norm sample_unif sample_pois
## 0.5029392 0.3935366 4.2500000
Per capire la differenza, applichiamo sia lapply
che sapply
con gli esempi precedenti:
sapply(list_vec, mean)
## [1] -0.174484405 -0.001581971 0.003746107 -0.104084425 0.297884925
lapply(list_vec, mean)
## [[1]]
## [1] -0.1744844
##
## [[2]]
## [1] -0.001581971
##
## [[3]]
## [1] 0.003746107
##
## [[4]]
## [1] -0.1040844
##
## [[5]]
## [1] 0.2978849
sapply(list_vec, mean, simplify = FALSE)
## [[1]]
## [1] -0.1744844
##
## [[2]]
## [1] -0.001581971
##
## [[3]]
## [1] 0.003746107
##
## [[4]]
## [1] -0.1040844
##
## [[5]]
## [1] 0.2978849
Come vedete, il risultato di queste operazioni corrisponde ad un valore per ogni elemento della lista list_vec
. lapply
restituisce una lista con i risultati mentre sapply
resituisce un vettore. Nel caso di risultati singoli come in questa situazione, l’utilizzo di sapply
è conveniente mentre mantenere la struttura a lista può essere meglio in altre condizioni. Possiamo comunque evitare che sapply
semplifichi l’output usando l’argomento simplify = FALSE
.
14.3.6 vapply
Esempi
vapply
è una ancora una volta simile sia a lapply
che a sapply
. Tuttavia richiede che il tipo di output sia specificato in anticipo. Per questo motivo è ritenuta una versione più solida delle precedenti perchè permette più controllo su quello che accade.
# Media
vapply(my_list, FUN = mean, FUN.VALUE = numeric(length = 1L))
## sample_norm sample_unif sample_pois
## 0.5029392 0.3935366 4.2500000
In questo caso come in precendeza definiamo la lista su cui applicare la funzione. Tuttavia l’argomento FUN.VALUE = numeric(length = 1L)
specifica che ogni risultato dovrà essere un valore di tipo numeric
di lunghezza 1. Infatti applicando la media otteniamo un singolo valore per iterazione e questo valore è necessariamente numerico.
sapply()
non restituisce sempre la stessa tipologia di oggetto mentre vapply()
richiede sia specificato il tipo di l’output di ogni iterazione.
x1 <- list(
sample_unif = c(-1, runif(15, min = 0, max = 1)),
sample_norm = rnorm(5, mean = 0, sd = 1),
sample_pois = rpois(20, lambda = 5)
)
x2 <- list(
sample_gamma = c(-1, rgamma(10, shape = 1)),
sample_unif = c(-2, runif(15, min = 0, max = 1)),
sample_pois = c(-3, rpois(20, lambda = 5))
)
negative_values <- function(x) x[x < 0]
sapply(x1, negative_values)
## $sample_unif
## [1] -1
##
## $sample_norm
## [1] -0.95907200 -0.08395547
##
## $sample_pois
## integer(0)
sapply(x2, negative_values)
## sample_gamma sample_unif sample_pois
## -1 -2 -3
vapply(x1, negative_values, FUN.VALUE = numeric(1))
## Error in vapply(x1, negative_values, FUN.VALUE = numeric(1)): values must be length 1,
## but FUN(X[[2]]) result is length 2
vapply(x2, negative_values, FUN.VALUE = numeric(1))
## sample_gamma sample_unif sample_pois
## -1 -2 -3
14.3.7 Lista di funzioni a lista di oggetti
L’*apply
family permette anche di estendere la formula “applica una funzione ad una lista di argomenti” applicando diverse funzioni inm modo estremamente compatto. Le funzioni infatti sono oggetti come altri in R e possono essere contenute in liste:
list_funs <- list(
"mean" = mean,
"median" = median,
"sd" = sd
)
lapply(list_funs, function(f) sapply(list_vec, function(x) f(x)))
## $mean
## [1] -0.174484405 -0.001581971 0.003746107 -0.104084425 0.297884925
##
## $median
## [1] -0.133687067 0.082444857 0.002455108 -0.008867830 0.209584846
##
## $sd
## [1] 1.0286259 1.0565268 1.0279756 0.9351077 1.0405497
Quello che abbiamo fatto è creare una lista di funzioni e poi scrivere due lapply
e sapply
in modo nested. Proprio come quando scriviamo due loop
nested, la stessa funzione viene applicata a tutti gli elementi, per poi passare alla funzione successiva. Il risultato infatti è una lista dove ogni elemento contiene i risultati applicando ogni funzione. Questo tipo di scrittura è più rara da trovare, tuttavia è utile per capire la logica e la potenza di questo approccio.
:::
14.3.8 mapply
mapply
è la versione più complessa di quelle considerate perchè estende a n il numero di liste che vogliamo utilizzare. La scrittura è la seguente:
Dove:
FUN
è la funzione da applicare...
sono le liste di elementi su cui applicare la funzione. E’ importante che tutti gli elementi siano della stessa lunghezza
Proviamo a generare dei vettori da una distribuzione normale, usando la funzione rnorm()
con diversi valori di numerosità, media e deviazione standard.
ns <- c(10, 3, 5)
means <- c(10, 20, 30)
sds <- c(2, 5, 7)
mapply(function(x, y, z) rnorm(x, y, z), # funzione
ns, means, sds) # argomenti
## [[1]]
## [1] 6.953099 6.854824 9.080728 11.687548 7.215676 7.706871 11.480102
## [8] 7.142393 8.784290 13.292173
##
## [[2]]
## [1] 21.99553 14.81288 18.84860
##
## [[3]]
## [1] 33.26673 22.67479 26.38153 26.86117 25.80392
La scrittura è sicuramente meno chiara rispetto agli esempi precedenti ma l’idea è la seguente:
- la funzione anonima non ha solo un argomento ma n argomenti
- gli argomenti sono specificati in ordine, quindi nel nostro esempio
x = ns, y = means e z = sds
- ogni iterazione, la funzione
rnorm
ottiene come argomenti una diversa numerosità, media e deviazione standard
14.4 Replicate
replicate
è una funzione leggermente diversa ma estremamente utile. Permette di ripetere una serie di operazioni un numero prefissato di volte.
Dove:
n
è il numero di ripetizioniexpr
è il codice da ripetere
Esempi
- Semplice
sample_info <- replicate(n = 1000,{
my_sample <- rnorm(n = 20, mean = 0, sd = 1)
my_mean <- mean(my_sample)
return(my_mean)
})
str(sample_info)
## num [1:1000] 0.395 -0.057 0.252 0.225 0.286 ...
- Complesso
sample_info <- replicate(n = 1000,{
my_sample <- rnorm(n = 20, mean = 0, sd = 1)
my_mean <- mean(my_sample)
my_sd <- sd(my_sample)
return(data.frame(mean = my_mean,
sd = my_sd))
}, simplify = FALSE)
sample_info <- do.call("rbind", sample_info)
str(sample_info)
## 'data.frame': 1000 obs. of 2 variables:
## $ mean: num 0.0966 -0.2776 0.1665 0.166 -0.1766 ...
## $ sd : num 1.093 1.059 1.105 1.109 0.954 ...
head(sample_info)
## mean sd
## 1 0.09660155 1.0926324
## 2 -0.27757342 1.0591981
## 3 0.16649003 1.1050091
## 4 0.16595363 1.1089836
## 5 -0.17656075 0.9537385
## 6 -0.18156384 0.9634895
E’ importante sottolineare che la ripetizione è alla base di qualsiasi struttura iterativa che abbiamo visto finora. Infatti lo stesso risultato (al netto di leggibilità, velocità e versalità) lo possiamo ottenere indistintamente con un ciclo for
, lapply
o replicate
. Riprendendo l’esempio precedente:
# Replicate
sample_info <- replicate(n = 1000,{
my_sample <- rnorm(n = 20, mean = 0, sd = 1)
my_mean <- mean(my_sample)
return(my_mean)
})
str(sample_info)
## num [1:1000] -0.109 -0.153 0.132 0.102 -0.202 ...
# *apply
sample_info <- sapply(1:1000, function(x){
my_sample <- rnorm(n = 20, mean = 0, sd = 1)
my_mean <- mean(my_sample)
})
str(sample_info)
## num [1:1000] -0.0768 -0.1913 -0.0709 0.0338 0.0372 ...
# for
sample_info <- vector(mode = "numeric", length = 1000)
for(i in 1:1000){
my_sample <- rnorm(n = 20, mean = 0, sd = 1)
sample_info[i] <- mean(my_sample)
}
str(sample_info)
## num [1:1000] -0.314 0.1203 0.1121 -0.1172 -0.0241 ...