class: title-slide, center, middle #.title[Introduzione a R] #.subtitle[Programmazione in R] <img src="data:image/png;base64,#img/arca_logo.svg" width="10%" style="display: block; margin: auto;" /> ###.location[ARCA - @DPSS] ###.author[Filippo Gambarota] --- # Programmazione in R Quello che vedremo in questa sezione sono i principali **costrutti della programmazione** e la loro applicazione in R. Ci sono alcuni punti da considerare: - Sono concetti trasversali estremamente utili - Sono alla base di qualcunque **funzionalità già implementata in R** - Vi permettono di fare qualunque cosa con il linguaggio --- # Programmazione in R - Disclaimer Ci sono delle cose che per tempo e complessità non possiamo affrontare e che sono R specifiche. Per questi aspetti avanzati del linguaggio, il libro [**Advanced R**](https://adv-r.hadley.nz/) è la cosa migliore <img src="data:image/png;base64,#img/adv_R.png" width="20%" style="display: block; margin: auto;" /> .footnote[ https://adv-r.hadley.nz/ ] --- class: section, center, middle # Costrutti della programmazione in R --- # Costrutti della programmazione in R - #### Funzioni - #### Programmazione condizionale - #### Programmazione iterativa --- # Funzioni Analogalmente alle *funzioni matematiche* la funzione in programmazione consiste nell' **astrarre** una serie di operazioni (nel nostro caso una porzione di codice) definendo una serie di operazioni che forniti degli *input* forniscono degli *output* eseguendo una serie di *operazioni* <img src="data:image/png;base64,#img/function.svg" width="80%" style="display: block; margin: auto;" /> --- # Funzioni Prendiamo un operazione ripetitiva che spesso si fa in analisi dati, **standardizzare** (trasformare in punti *z*) una variabile ovvero sottrarre da un *vettore* di osservazioni `\(x\)` la sua media `\(\mu_x\)` e poi dividere per la deviazione standard `\(\sigma_x\)`: $$ x_{z} = \frac{x - \mu_x}{\sigma_x} $$ Seppur semplice, questa operazione può essere resa molto automatica scrivendo una funzione. --- # Funzioni Se vogliamo *astrarre* questa operazione in modo da renderla più generale e utile dobbiamo definire: - **argomenti funzione**: quelle che in matematica sono le *variabili* - **corpo funzione**: le **operazioni** che la funzione deve eseguire usando gli argomenti - **output funzione**: cosa la funzione deve **restituire** come risultato --- # Funzioni - Argomenti Gli **argomenti** sono quelle parti variabili della funzione che vengono definiti e poi sono necessari ad eseguire la funzione stessa. Se vogliamo *astrarre* la retta che abbiamo visto prima dobbiamo definire alcune parti come **variabili**. Nel caso della nostra funzione l'unico argomento è il vettore `\(x\)` in input. Possiamo analogalmente a `mean` e `sd` impostare un argomento che indichi se eliminare gli `NA`: ```r z_score <- function(x, na.rm = FALSE){ # argomenti # body # output } ``` --- # Funzioni - Body Il **corpo** della funzione sono le operazioni da eseguire utilizzando gli argomenti in input. Nel nostro caso dobbiamo sottrarre la *media* da `\(x\)` e dividere per la *deviazione standard* ```r z_score <- function(x, na.rm = FALSE){ # argomenti (x - mean(x, na.rm = na.rm)) / sd(x, na.rm = na.rm) # output } ``` --- # Funzioni - Output L'output è il **risultato che la funzione ci restituisce** dopo aver eseguito tutte le operazioni. Nel nostro caso vogliamo che la funzione restituisca il vettore `\(x\)` ma trasformato in punti z: ```r z_score <- function(x, na.rm = FALSE){ # argomenti (x - mean(x, na.rm = na.rm)) / sd(x, na.rm = na.rm) } ``` Per essere più consistenti possiamo usare il comando `return` che esplicitamente dice alla funzione cosa restituire: ```r z_score <- function(x, na.rm = FALSE){ # argomenti xcen <- (x - mean(x, na.rm = na.rm)) / sd(x, na.rm = na.rm) # assegno ad una nuova variabile nell'ambiente funzione return(xcen) } ``` --- # Funzioni - Risultato finale Ora possiamo salvare la nostra funzione come un normale oggetto ed utilizzarla come se fosse una funzione giù implementata in R: ```r z_score <- function(x, na.rm = FALSE){ # argomenti xcen <- (x - mean(x, na.rm = na.rm)) / sd(x, na.rm = na.rm) # assegno ad una nuova variabile nell'ambiente funzione return(xcen) } vec <- rnorm(100, 50, 10) # media 50 e deviazione standard 10 mean(vec) ``` ``` ## [1] 51.46285 ``` ```r sd(vec) ``` ``` ## [1] 10.27785 ``` ```r vec0 <- z_score(vec) mean(vec0) ``` ``` ## [1] -2.270319e-17 ``` ```r sd(vec0) ``` ``` ## [1] 1 ``` --- class: section, center, middle # Programmazione condizionale --- # Programmazione condizionale In programmazione solitamente è necessario non solo eseguire una serie di operazione **MA** eseguire delle operazione in funzione di alcune **condizioni** Facciamo un esempio pratico, la funzione `summary()` in R fornisce un risultato diverso in base al tipo di input. Come è possibile tutto questo? Tramite l'utilizzo di **condizioni**: ```r x <- 1:10 # vettore numerico y <- factor(rep(c("a", "b", "c"), each = 10)) # vettore di stringhe summary(x) ``` ``` ## Min. 1st Qu. Median Mean 3rd Qu. Max. ## 1.00 3.25 5.50 5.50 7.75 10.00 ``` ```r summary(y) ``` ``` ## a b c ## 10 10 10 ``` --- # Programmazione condizionale Anche se non sappiamo quali operazioni svolga la funzione `summary()` possiamo immaginare una cosa simile ```r summary <- function(argomento){ # se l'argomento è un vettore numerico # esegui --> operazioni a,b,c # se l'argomento è un vettore stringa # esegui --> operazioni d,e,f # ... } ``` Quindi non solo una funzione esegue lo stesso codice ogni volta che è chiamata ma può eseguire un codice specifico (o un parte) in base al contesto (condizioni) --- # Programmazione condizionale Il concetto di `se <condizione> allora fai <operazione>` si traduce in programmazione tramite quelli che si chiamano `if statement`: <img src="data:image/png;base64,#img/if_chart.png" width="40%" style="display: block; margin: auto;" /> --- # Programmazione condizionale Per lavorare con gli `if statements` dobbiamo avere chiaro: - il concetto di *operatori logici* ovvero `TRUE` e `FALSE` - il concetto di *operazioni logiche* `TRUE and TRUE = TRUE` --- # Programmazione condizionale Quando una sola condizione non basta... <img src="data:image/png;base64,#img/ifelse_chart.png" width="40%" style="display: block; margin: auto;" /> --- # Programmazione condizionale Per poter capire quale struttura condizionale utilizzare è importante capire bene il problema che dobbiamo risolvere. Ritornando all'esempio della funzione `summary()`, immaginiamo di avere 2 tipi di dati in R; stringhe e numeri. In questo caso è sufficiente avere un `if statement` che controlla se l'elemento è una stringa/numero e per tutto il resto applicare l'opposto. --- # Programmazione condizionale - Tip Esiste una famiglia di funzioni con prefisso `is.*` che fornisce `TRUE` quando la tipologia di oggetto corrisponde a quella richiesta e `FALSE` in caso contrario. ```r x <- 1:10 is.numeric(x) ``` ``` ## [1] TRUE ``` ```r is.factor(x) ``` ``` ## [1] FALSE ``` ```r is.character(x) ``` ``` ## [1] FALSE ``` Possiamo usare queste funzioni per creare un flusso condizionale nella nostra funzione `summary()` --- # Programmazione condizionale Scriviamo una funzione che restituisca la `media` quando il vettore è numerico e la tabella di frequenza (con la funzione `table()`) ```r my_summary <- function(x){ # testiamo la condizione if(is.numeric(x)){ return(mean(x)) }else{ return(table(x)) } } x <- 1:10 my_summary(x) ``` ``` ## [1] 5.5 ``` ```r x <- rep(c("a","b","c"), c(10, 2, 8)) my_summary(x) ``` ``` ## x ## a b c ## 10 2 8 ``` --- # `ifelse()` Un limite di usare gli `if statements` riguarda il fatto che funzionano solo su un singolo valore (i.e. non sono **vettorizzati**): ```r x <- 1:10 if(x < 5){ print("x è minore di 5") }else{ print("x è maggiore di 5") } ``` ``` ## Error in if (x < 5) {: the condition has length > 1 ``` La versione vettorizzata è la funzione `ifelse(test, yes, no)`: ```r ifelse(x < 5, "x è minore di 5", "x è maggiore di 5") ``` ``` ## [1] "x è minore di 5" "x è minore di 5" "x è minore di 5" ## [4] "x è minore di 5" "x è maggiore di 5" "x è maggiore di 5" ## [7] "x è maggiore di 5" "x è maggiore di 5" "x è maggiore di 5" ## [10] "x è maggiore di 5" ``` --- # `ifelse()` Come anche per gli `if statements` normali, posso creare degli `ifelse() nested` quando ho bisogno di testare più alternative. Immaginiamo di avere una colonna/vettore `age` e voler creare un altro vettore dove l'età è divisa in 3 fascie, bambino, adulto, anziano: ```r age <- round(runif(50, 3, 80)) age_ifelse <- ifelse(age < 18, yes = "bambino", no = ifelse( age >= 18 & age < 60, "adulto", "anziano" )) ``` --- # `dplyr::case_when()` Quando le condizioni da testare sono numerose (indicativamente > 3) può essere tedioso scrivere molti `ifelse()` multipli. Possiamo allora usare la funzione `dplyr::case_when()` del pacchetto `dplyr` che è una generalizzazione di `ifelse()`: ```r age_case_when <- case_when(age < 18 ~ "bambino", age >= 18 & age < 60 ~ "adulto", TRUE ~ "anziano") # con TRUE si identifica "tutto il resto" in modo da non lasciare valori scoperti (ATTENZIONE) ``` I due risultati sono identici: ```r all.equal(age_case_when, age_ifelse) ``` ``` ## [1] TRUE ``` --- # Esempio con `dplyr::case_when()` Ricodificare i valori di una variabile come ad esempio "girare" gli item di un questionario è un operazione facilmente eseguibile in con `dplyr::case_when()`: ```r item <- sample(1:5, 20, replace = TRUE) # simuliamo delle risposte ad un item item ``` ``` ## [1] 4 4 5 3 5 1 2 1 1 4 2 1 1 4 2 2 2 1 5 2 ``` ```r # ricodifichiamo con 1 = 5, 2 = 4, 3 = 3, 4 = 2, 5 = 1 item_rec <- case_when( item == 1 ~ 5, item == 2 ~ 4, item == 3 ~ 3, item == 4 ~ 2, item == 5 ~ 1 ) item_rec ``` ``` ## [1] 2 2 1 3 1 5 4 5 5 2 4 5 5 2 4 4 4 5 1 4 ``` Se usate spesso dei questionari potete scrivervi la vostra funzione che fa lo scoring in automatico 😏 --- class: section, center, middle # Programmazione iterativa --- # Programmazione iterativa Il concetto di *iterazione* è alla base di qualsiasi operazione nei linguaggi di programmazione. In R molte delle operazioni sono **vettorizzate**. Questo rende il linguaggio più efficiente e pulito MA nasconde il concetto di **iterazione**. Ad esempio la funzione `sum()` permette di sommare un vettore di numeri. Ma cosa si nasconde sotto? ```r sum(1:100) ``` ``` ## [1] 5050 ``` ```r # come è possibile? ``` --- # Programmazione iterativa Esempio: se io vi chiedo di usare la funzione `print()` per scrivere `"hello world"` nella console 5 volte, come fate? ```r msg <- "Hello World" print(msg) # 1 ``` ``` ## [1] "Hello World" ``` ```r print(msg) # 2 ``` ``` ## [1] "Hello World" ``` ```r print(msg) # 3 ``` ``` ## [1] "Hello World" ``` ```r print(msg) # 4 ``` ``` ## [1] "Hello World" ``` ```r print(msg) # 5 ``` ``` ## [1] "Hello World" ``` --- # Programmazione iterativa Quello che ci manca è un modo di ripetere una certa operazione, senza effettivamente ripetere il codice manualmente. Ci sono vari costrutti che ci permettono di ripetere operazioni: - Cicli `for` - Cicli `while` - `*apply` family - altri --- # Il ciclo `for` <img src="data:image/png;base64,#img/for_loop.png" width="40%" style="display: block; margin: auto;" /> --- # For Il ciclo `for` è una struttura che permette di ripetere un numero *finito* e *pre-determinato* di volte una certa porzione di codice: La scrittura di un ciclo `for` è: ```r for(i in 1:n){ # quante operazioni voglio } ``` Se voglio stampare una cosa 5 volte, posso tranquillamente usare un ciclo `for`: ```r for(i in 1:5){ print(paste("Ciclo for giro", i)) } ``` ``` ## [1] "Ciclo for giro 1" ## [1] "Ciclo for giro 2" ## [1] "Ciclo for giro 3" ## [1] "Ciclo for giro 4" ## [1] "Ciclo for giro 5" ``` --- # Scomponiamo il ciclo `for` Ci sono diversi elementi: - `for(){}`: è l'implementazione in R (in modo simile all'`if statement`) - `i`: questo viene chiamato *iteratore* o *indice*. E' un indice generico che può assumere qualsiasi valore e nome. Per convenzione viene chiamato `i`, `j` etc. Questo tiene conto del numero di iterazioni che il nostro ciclo deve fare - `in <valori>`: questo indica i valori che assumerà l'*iteratore* all'interno del ciclo - `{ # operazioni }`: sono le operazioni che i ciclo deve eseguire --- # Ma l'iteratore? La potenza del ciclo `for` sta nel fatto che l'iteratore `i` assume i valori del vettore specificato dopo `in`, uno alla volta: ```r for(i in 1:10){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ## [1] 6 ## [1] 7 ## [1] 8 ## [1] 9 ## [1] 10 ``` --- # For con iteratore vs senza Questa è una distinzione importante quanto sottile, notate la differenza tra questi due cicli: .pull-left[ ```r vec <- 1:5 for(i in 1:length(vec)){ print(vec[i]) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 for(i in vec){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # While Il ciclo `while` è una versione più generale del ciclo for. Per funzionare utilizza una *condizione logica* e non un iteratore e un range di valori come nel `for`. Il ciclo continuerà fino a che la `condizione` è vera: .pull-left[ ```r while(condizione){ # operazioni } ``` ] .pull-right[ <img src="data:image/png;base64,#img/while_loop.png" width="70%" style="display: block; margin: auto;" /> ] --- # While - (Fun 🤭) Provate a scrivere questo ciclo `while` e vedere cosa succede e capire perchè accade. .pull-left[ ```r x <- 10 while (x < 15) { print(x) } ``` ] -- .pull-right[ <iframe src="https://giphy.com/embed/LPHXLKEOZw6T6" width="400" height="400" frameBorder="0"></iframe ] --- # While Questo esercizio è utile per capire che il `while` è un ciclo non pre-determinato e quindi necessita sempre di un modo per essere interrotto, facendo diventare la condizione falsa. ```r x <- 5 while (x < 15) { print(x) x <- x + 1 } ``` ``` ## [1] 5 ## [1] 6 ## [1] 7 ## [1] 8 ## [1] 9 ## [1] 10 ## [1] 11 ## [1] 12 ## [1] 13 ## [1] 14 ``` --- # Applicazioni dei cicli Gli esempi finora sono semplici ma poco utili. Quando il queste strutture iterative sono veramente utili? Molte delle funzioni che utilizziamo come ad esempio `sum()`, `mean()`, etc. hanno al loro interno una sturttura iterativa Immaginiamo di non avere la funzione `sum()` e di volerla ricreare, come facciamo? Idee? --- # Somma come iterazione Scomponiamo concettualmente la somma, sommiamo i numeri da 1 a 10: - prendo il primo e lo sommo al secondo (`somma = 1 + 2`) - prendo la `somma` e la sommo al 3 elemento `somma = somma + 3` - ... In pratica abbiamo: - il nostro vettore da sommare - un oggetto `somma` che accumula progressivamente le somme precedenti --- # Somma come iterazione ```r somma <- 0 # inizializziamo la somma a 0 x <- 1:10 for(i in seq_along(x)){ somma <- somma + x[i] } ``` --- # Somma come iterazione Mettiamo tutto dentro una funzione ```r my_sum <- function(x){ somma <- 0 # inizializziamo la somma a 0 for(i in seq_along(x)){ somma <- somma + x[i] } return(somma) } x <- rnorm(100) my_sum(x) ``` ``` ## [1] -11.42991 ``` ```r sum(x) ``` ``` ## [1] -11.42991 ``` --- # Iterazione e funzioni Per quanto sia un esercizio utile e divertente ricreare le funzioni base di R capendo la struttura iterativa (🤭) questo nella pratica non è quasi mai necessario. Però è assolutamente fondamentale capire il **concetto** di iterazione perchè praticamente ogni operazione consiste nell'iterare tra: - colonne/righe di un dataframe - elementi di un vettore - lettere in una parole - ... --- class: section, center, middle # Ma in R c'è qualcosa di meglio... --- # Ma in R c'è qualcosa di meglio... In R, l'utilizzo **esplicito** dei cicli `for` non è molto diffuso, per 2 motivi: - R è un linguaggio fortemente **funzionale** - R è un linguaggio spesso **vettorizzato** - I cicli `for` sono molto verbosi e non sempre leggibili - I cicli `for` in R, se non scritti bene, possono essere *estremamente lenti* --- class: section, center, middle # `*apply` family --- # `*apply` family Immaginate di avere una `lista` di vettori, e di voler applicare la stessa funzione/i ad ogni elemento della lista. Come fare? ^[1] - applico manualmente la funzione selezionando gli elementi - ciclo `for` che itera sugli elementi della lista e applica la funzione/i - ... ```r my_list <- list( vec1 <- rnorm(100), vec2 <- runif(100), vec3 <- rnorm(100), vec4 <- rnorm(100) ) ``` .footnote[ Hadley Wickam - The joy of functional programming - [link](https://www.youtube.com/watch?v=bzUmK0Y07ck&t=1453s) ] --- # `*apply` family Applichiamo `media`, `mediana` e `deviazione standard`: .pull-left[ ```r means <- vector(mode = "numeric", length = length(my_list)) medians <- vector(mode = "numeric", length = length(my_list)) stds <- vector(mode = "numeric", length = length(my_list)) for(i in 1:length(my_list)){ means[i] <- mean(my_list[[i]]) medians[i] <- median(my_list[[i]]) stds[i] <- sd(my_list[[i]]) } ``` ] .pull-right[ ```r means ``` ``` ## [1] 0.09909788 0.49791648 0.14584730 -0.07782825 ``` ```r medians ``` ``` ## [1] 0.05212908 0.49666299 0.10087418 -0.05087895 ``` ```r stds ``` ``` ## [1] 0.9982326 0.3168344 0.9608252 0.9635796 ``` ] --- # `*apply` family Funziona tutto! ma: - il `for` è molto laborioso da scrivere gli indici sia per la lista che per il vettore che stiamo popolando - dobbiamo *pre-allocare delle variabili* (per il motivo della velocità che dicevo) - 8 righe di codice (per questo esempio semplice) --- # `*apply` family In R è presente una famiglia di funzioni `*apply` come `lapply`, `sapply`, etc. che permettono di ottenere lo stesso risultato in modo più conciso, rapido e semplice: ```r means <- sapply(my_list, mean) medians <- sapply(my_list, median) stds <- sapply(my_list, sd) means ``` ``` ## [1] 0.09909788 0.49791648 0.14584730 -0.07782825 ``` ```r medians ``` ``` ## [1] 0.05212908 0.49666299 0.10087418 -0.05087895 ``` ```r stds ``` ``` ## [1] 0.9982326 0.3168344 0.9608252 0.9635796 ``` --- # `*apply` family - Bonus Prima di introdurre l'`*apply` family un piccolo bonus. Sfruttando il fatto che in R **tutto è un oggetto** possiamo scrivere in modo ancora più conciso: ```r my_funs <- list(median = median, mean = mean, sd = sd) lapply(my_list, function(vec) sapply(my_funs, function(fun) fun(vec))) ``` ``` ## [[1]] ## median mean sd ## 0.05212908 0.09909788 0.99823258 ## ## [[2]] ## median mean sd ## 0.4966630 0.4979165 0.3168344 ## ## [[3]] ## median mean sd ## 0.1008742 0.1458473 0.9608252 ## ## [[4]] ## median mean sd ## -0.05087895 -0.07782825 0.96357957 ``` Amazing! ora cerchiamo di dare un senso a queste righe di codice! --- # `*apply` family ```r *apply(<lista>, <funzione>) ``` - cosa può essere la `lista`? - lista - dataframe - vettore - cosa può essere la `funzione`? - funzione *base* o importata *pacchetto* - funzione *custom* - funzione *anonima* --- # `*apply` family - intuizione Prima di analizzare l'`*apply` family, credo sia utile un ulteriore parallelismo con il ciclo `for` che abbiamo visto. `*apply` non è altro che un ciclo `for`, leggermente semplificato: .pull-left[ ```r vec <- 1:5 for(i in vec){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 res <- sapply(vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # `*apply` family - spoiler funzione anonima Quindi come il ciclo `for` scritto come `i in vec` assegna al valore `i` un elemento per volta dell'oggetto `vec`, internamente le funzioni `*apply` prendono il primo elemento dell'oggetto in input (`lista`) e applicano direttamente la funzione che abbiamo scelto. C'è un modo per rendere esplicito questo, anche nelle funzioni `*apply`: .pull-left[ ```r vec <- 1:5 res <- sapply(vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 res <- sapply(vec, function(i) print(i)) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # `*apply` e funzioni custom ```r center_var <- function(x){ x - mean(x) } my_list <- list( vec1 = runif(10), vec2 = runif(10), vec3 = runif(10) ) lapply(my_list, center_var) ``` ``` ## $vec1 ## [1] 0.24206684 0.26703972 0.17604523 0.13145191 -0.36487592 -0.27774841 ## [7] 0.07876956 -0.13964211 0.20021399 -0.31332082 ## ## $vec2 ## [1] 0.05059684 -0.03419106 -0.25062540 0.11397349 0.35098170 -0.11105802 ## [7] -0.15261647 0.12666392 -0.21936830 0.12564329 ## ## $vec3 ## [1] 0.056942973 -0.050518841 -0.227978478 -0.135502809 0.007841452 0.007972049 ## [7] -0.063457073 0.084684488 0.268858624 0.051157615 ``` --- # `*apply` e funzioni anonime Una funzione anonima è una funzione non salvata in un oggetto ma scritta per essere **eseguita direttamente**, all'interno di altre funzioni che lo permettono: ```r lapply(my_list, function(x) x - mean(x)) ``` ``` ## $vec1 ## [1] 0.24206684 0.26703972 0.17604523 0.13145191 -0.36487592 -0.27774841 ## [7] 0.07876956 -0.13964211 0.20021399 -0.31332082 ## ## $vec2 ## [1] 0.05059684 -0.03419106 -0.25062540 0.11397349 0.35098170 -0.11105802 ## [7] -0.15261647 0.12666392 -0.21936830 0.12564329 ## ## $vec3 ## [1] 0.056942973 -0.050518841 -0.227978478 -0.135502809 0.007841452 0.007972049 ## [7] -0.063457073 0.084684488 0.268858624 0.051157615 ``` Come per i cicli `for` (ricordo che `*apply` e `for` sono identici), `x` è solo un placeholder (analogo di `i`) e può essere qualsiasi lettera o nome --- # Tutte le tipologie di `*apply` Vediamo tutti i tipi di `*apply` che ci sono. Alcuni sono più *utili* altri più *robusti* e altri ancora poco utilizzati: - `lapply()`: la funzione di base - `sapply()`: `simplified-apply` - `tapply()`: poco utilizzata, utile con i *fattori* - `apply()`: utile per i *dataframe/matrici* - `mapply()`: versione multivariata, utilizza *più liste contemporaneamente* - `vapply()`: utilizzata dentro le funzioni e pacchetti --- # `lapply` `lapply` sta per list-apply e restituisce sempre una lista, applicando la funzione ad ogni elemento della lista in input: ```r res <- lapply(my_list, mean) res ``` ``` ## $vec1 ## [1] 0.6452496 ## ## $vec2 ## [1] 0.512933 ## ## $vec3 ## [1] 0.5440575 ``` ```r class(res) ``` ``` ## [1] "list" ``` --- # `sapply` `sapply` sta per simplified-apply e (cerca) di restituire una versione più semplice di una lista, applicando la funzione ad ogni elemento della lista in input: ```r res <- sapply(my_list, mean) res ``` ``` ## vec1 vec2 vec3 ## 0.6452496 0.5129330 0.5440575 ``` ```r class(res) ``` ``` ## [1] "numeric" ``` --- # `apply` `apply` funziona in modo specifico per dataframe o matrici, applicando una funzione alle righe o alle colonne: - `apply(dataframe, index, fun)` .pull-left[ ```r # index 1 = riga, 2 = colonna my_dataframe <- data.frame(my_list) head(my_dataframe) ``` ``` ## vec1 vec2 vec3 ## 1 0.8873165 0.5635299 0.6010005 ## 2 0.9122894 0.4787420 0.4935387 ## 3 0.8212949 0.2623076 0.3160790 ## 4 0.7767016 0.6269065 0.4085547 ## 5 0.2803737 0.8639147 0.5518990 ## 6 0.3675012 0.4018750 0.5520296 ``` ] .pull-right[ ```r apply(my_dataframe, 1, mean) ``` ``` ## [1] 0.6839489 0.6281900 0.4665605 0.6040543 0.5653958 0.4404686 0.5216454 ## [8] 0.5913155 0.6506482 0.5219068 ``` ```r apply(my_dataframe, 2, mean) ``` ``` ## vec1 vec2 vec3 ## 0.6452496 0.5129330 0.5440575 ``` ```r apply(my_dataframe, 2, center_var) ``` ``` ## vec1 vec2 vec3 ## [1,] 0.24206684 0.05059684 0.056942973 ## [2,] 0.26703972 -0.03419106 -0.050518841 ## [3,] 0.17604523 -0.25062540 -0.227978478 ## [4,] 0.13145191 0.11397349 -0.135502809 ## [5,] -0.36487592 0.35098170 0.007841452 ## [6,] -0.27774841 -0.11105802 0.007972049 ## [7,] 0.07876956 -0.15261647 -0.063457073 ## [8,] -0.13964211 0.12666392 0.084684488 ## [9,] 0.20021399 -0.21936830 0.268858624 ## [10,] -0.31332082 0.12564329 0.051157615 ``` ] --- # `tapply` `tapply` permette di applicare una funzione ad un *vettore*, dividendo questo vettore in base ad una variabile categoriale: - `tapply(dataframe, index, fun)`: dove `index` è un vettore di stringa o un fattore ```r vec <- rnorm(75) index <- rep(c("a", "b", "c"), each = 25) tapply(vec, index, mean) ``` ``` ## a b c ## 0.07679020 -0.25698128 -0.01260037 ``` --- # `vapply` `vapply` è una versione più *solida* delle precedenti dal punto di vista di programmazione. In pratica permette (e richiede) di specificare in anticipo la tipologia di dato che ci aspettiamo come risultato `vapply(X = , FUN = , FUN.VALUE = ,... )` ```r vapply(my_list, FUN = mean, FUN.VALUE = numeric(length = 1)) ``` ``` ## vec1 vec2 vec3 ## 0.6452496 0.5129330 0.5440575 ``` - `my_list, FUN = mean`: è esattamente uguale a `sapply/lapply` - `FUN.VALUE = numeric(length = 1)`: indica che ogni risultato è un singolo valore numerico --- # `mapply` Questa è quella più complicata ma anche molto utile. Praticamente permette di gestire più liste contemporaneamente per scenari più complessi. Ad esempio vogliamo usare la funzione `rnorm()` e generare vettori con diverse **medie** e **deviazioni stardard** in combinazione. ```r medie <- list(10, 20, 30, 40) stds <- list(1,2,3,4) mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` ``` ## [[1]] ## [1] 10.708347 9.907274 8.908202 9.358959 10.254062 7.722587 10.531340 ## [8] 10.871665 7.573373 10.097219 ## ## [[2]] ## [1] 19.68206 22.35745 22.85313 19.22269 21.59939 17.85078 20.63746 19.23401 ## [9] 17.25255 22.73279 ## ## [[3]] ## [1] 36.11318 28.15993 26.84050 30.92185 25.44886 31.81081 30.07586 25.38069 ## [9] 30.17653 30.43922 ## ## [[4]] ## [1] 45.78883 42.18123 42.41545 44.19182 38.44961 41.42697 35.43716 36.96837 ## [9] 37.76565 42.86545 ``` **IMPORTANTE**, tutte le liste incluse devono avere la stessa dimensione! --- # `mapply` ```r mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` - `function(...)`: è una funzione anonima come abbiamo visto prima che può avere *n* elementi - `rnorm(n = 10, mean = x, sd = y)`: è l'effettiva funzione anonima dove abbiamo i placeholders `x` and `y` - `medie, stds`: sono **in ordine** le liste corrispondenti ai placeholders indicati, quindi `x = medie` e `y = stds`. - `SIMPLIFY = FALSE`: semplicemente dice di restituire una lista e non cercare (come `sapply`) di semplificare il risultato --- # `mapply` come `for` Lo stesso risultato (in modo più verboso e credo meno intuitivo) si ottiene con un `for` usando più volte l'iteratore `i`: ```r medie <- list(10, 20, 30, 40) stds <- list(1,2,3,4) res <- vector(mode = "list", length = length(medie)) for(i in 1:length(medie)){ res[[i]] <- rnorm(10, mean = medie[[i]], sd = stds[[i]]) } res ``` ``` ## [[1]] ## [1] 7.711664 9.825397 10.129326 8.895863 11.515388 11.221279 10.511833 ## [8] 9.280230 8.966889 10.976996 ## ## [[2]] ## [1] 19.40006 16.87381 25.60202 22.16801 21.44283 20.94422 20.09084 22.38744 ## [9] 17.35970 24.02909 ## ## [[3]] ## [1] 33.85865 28.61219 28.24609 30.52583 25.68278 30.14761 28.58358 33.18449 ## [9] 31.90814 29.48460 ## ## [[4]] ## [1] 44.31427 41.54230 47.99708 38.38299 40.76353 42.47403 35.02408 39.00322 ## [9] 42.26178 40.31021 ``` --- class: section, center, middle # `*apply` alcune precisazioni --- # `*apply` vettore vs lista Abbiamo sempre usato esplicitamente `liste` fino ad ora, ma le funzioni `*apply` sono direttamente applicabili anche a **vettori** - se usiamo un vettore di *n* elementi, allora itereremo da `1:n` - se usiamo una lista di *n* elementi, allora iteriamo da `1:n` dove il singolo elemento può essere qualsiasi cosa ```r my_vec <- 1:5 my_list <- list(a = 1:2, b = 3:4, c = 5:6) res <- sapply(my_vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ```r res <- sapply(my_list, print) ``` ``` ## [1] 1 2 ## [1] 3 4 ## [1] 5 6 ``` --- # `*apply` come un `for` Nulla ci vieta (ma perdiamo l'aspetto intuitivo e conciso) di usare le funzioni `*apply` esattamente come un ciclo `for`, usando un **iteratore**: ```r medie <- c(10, 20, 30, 40) stds <- c(1,2,3,4) res <- lapply(1:length(medie), function(i){ rnorm(n = 10, mean = medie[i], sd = stds[i]) }) ``` Trovo tuttavia più chiara l'alternativa usando `mapply`: ```r mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` --- class: section, center, middle # Extra: `purrr::map*` --- # Extra: `purrr::map*` .pull-left[ <img src="data:image/png;base64,#img/purrr.svg" width="60%" style="display: block; margin: auto;" /> ] .pull-right[ Senza addentrarci troppo in questo modo, c'è una famiglia di funzioni che una volta imparato `*apply` vi consiglio di usare perchè più consistenti e intuitive, la `map*` family. ] --- # Extra: `purrr::map*` Per usare `purrr::map*` è sufficiente installare il pacchetto `purrr` con `install.packages("purrr")` ed iniziare ad usare le nuove funzioni. La sintassi è esattamente la stessa di `*apply` (qualche modifica ma potete usare la stessa) ma invece che usare una funzione per tutto, abbiamo molte funzioni per ogni casistica: - `map(lista, funzione)` è l'analogo di `lapply()` e fornisce sempre una lista - `map_dbl(lista, funzione)` applica la funzione ad ogni elemento e **si aspetta che** il risultato sia un vettore di *double* - `map_lgl(lista, funzione)` applica la funzione ad ogni elemento e **si aspetta che** il risultato sia un vettore *logico* - `map2/pmap_*` sono rispettivamente applicare la funzione a 2/n liste (analogo di `mapply()`) --- class: section, center, middle # Extra: `replicate()` and `repeat()` --- # Extra: `replicate()` and `repeat()` Ci sono altre due funzioni in R che permettono di *iterare*. Sono meno utilizzate perchè si ottengono gli stessi risultati usando un semplice `for` o `*apply`. - `replicate()` permette di ripetere un operazione *n* volte, senza però utilizzare un `iteratore` o un `placeholder`. - `repeat()` anche repeat permette di ripetere ma fino a che non si verifica un certa condizione (**logica**). Ha una struttura simile al ciclo `while` --- class: section, center, middle # Extra: Formula syntax --- # Formula syntax In R molte operazioni vengono eseguite usando la **formula syntax** `something ~ something else` ad esempio: - modelli statistici: `lm(y ~ x, data = data)`, `t.test(y ~ factor, data = data)` - plot: `boxplot(y ~ x, data = data)` - ... In cosa consiste? --- # Formula syntax Senza andare nei dettagli tecnici, R usa una cosa che si chiama *lazy evaluation*. In altri termini "salva" delle operazioni per essere eseguite in un secondo momento. Tutti sappiamo che se scriviamo un nome (senza virgolette) e questo non è associato ad un oggetto otteniamo un errore. Tuttavia alcune funzioni come `library()` non forniscono errore. Perchè? ```r stats # errore ``` ``` ## Error in eval(expr, envir, enclos): object 'stats' not found ``` ```r library(stats) # no errore ``` --- # Formula syntax La ragione è che R è in grado di salvare un'espressione per usarla poi in uno specifico contesto (ad esempio dentro una funzione). La `formula syntax` è un esempio. Usando la tilde `~` possiamo creare delle `formule` che R può utilizzare in specifici contesti: ```r head(y) ``` ``` ## [1] a a a a a a ## Levels: a b c ``` ```r head(x) ``` ``` ## [1] -1.39078224 -1.90745513 0.18332576 1.99568617 -0.57335261 -0.05257702 ``` ```r y ~ x ``` ``` ## y ~ x ## <environment: 0x5d40c9f21ad8> ``` ```r my_formula <- y ~ x class(my_formula) ``` ``` ## [1] "formula" ``` --- # Formula syntax e `aggregate()` Un esempio utile è la funzione `aggregate()` molto interessante per applicare funzioni a dataframe. Immaginate di avere il dataset `iris` e calcolare la media per ogni livello del fattore `Species`: .pull-left[ ```r tapply(iris$Sepal.Length, iris$Species) ``` ``` ## [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ## [39] 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 ## [77] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ## [115] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ``` ```r aggregate(Sepal.Length ~ Species, FUN = mean, data = iris) ``` ``` ## Species Sepal.Length ## 1 setosa 5.006 ## 2 versicolor 5.936 ## 3 virginica 6.588 ``` ] .pull-right[ ```r # Anche creando un oggetto, ma solo come formula my_formula <- Sepal.Length ~ Species my_char <- "Sepal.Length ~ Species" aggregate(my_char, FUN = mean, data = iris) ``` ``` ## Error in aggregate.data.frame(as.data.frame(x), ...): argument "by" is missing, with no default ``` ```r # Viene lo stesso usando $ e senza specificare data = aggregate(iris$Sepal.Length, iris$Species, FUN = mean) ``` ``` ## Error in aggregate.data.frame(as.data.frame(x), ...): 'by' must be a list ``` ] --- # Formula syntax e `aggregate()` Ma anche operazioni più complesse: ```r my_iris <- iris my_iris$fac <- rep(c("a", "b", "c"), 50) aggregate(Sepal.Length ~ Species + fac, mean, data = my_iris) ``` ``` ## Species fac Sepal.Length ## 1 setosa a 5.052941 ## 2 versicolor a 5.770588 ## 3 virginica a 6.756250 ## 4 setosa b 5.011765 ## 5 versicolor b 6.018750 ## 6 virginica b 6.447059 ## 7 setosa c 4.950000 ## 8 versicolor c 6.023529 ## 9 virginica c 6.570588 ``` --- # Replicate .pull-left[ `replicate(n, expr)` - `n` è il numero di ripetizioni - `expr` è la porzione di codice da ripetere ```r # Campioniamo 1000 volte da una normale e facciamo la media AKA distribuzione campionaria della media nrep <- 1000 nsample <- 30 media <- 100 ds <- 30 means <- replicate(n = nrep, expr = { mean(rnorm(nsample, media, ds)) }) ``` ] .pull-right[ <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-66-1.png" width="2100" style="display: block; margin: auto;" /> ] --- # `repeat()` ```r repeat { # cose da ripetere if(...){ # condizione da valutare break # ferma il loop } } ``` ```r i <- 1 repeat { print(i) i = i + 1 if(i > 3){ break } } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` --- # `repeat()` vs `while` <!-- TODO revise repeat vs loop --> .pull-left[ ```r i <- 1 repeat { print(i) i = i + 1 if(i > 3){ break } } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` ] .pull-right[ ```r i <- 1 while(i < 4){ print(i) i <- i + 1 } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` ] - `repeat` valuta la condizione una volta finita l'iterazione, mentre `while` all'inizio. Se la condizione non è `TRUE` all'inizio, il `while` non parte mentre `repeat` si. --- class: section, center, middle # Dataframe come Liste --- # Dataframe come Liste Essendo il dataframe tecnicamente una lista, è possibile eseguire delle operazioni iterative. Ad esempio: ```r sapply(mtcars, mean) ``` ``` ## mpg cyl disp hp drat wt qsec ## 20.090625 6.187500 230.721875 146.687500 3.596563 3.217250 17.848750 ## vs am gear carb ## 0.437500 0.406250 3.687500 2.812500 ``` Applica a tutti gli elementi della lista i.e. colonne la funzione mean --- # Dataframe come Liste Possiamo però anche dividere un dataframe in liste di dataframes in base alle righe. Ad esempio possiamo voler fittare un modello statistico su ogni soggetto separatamente. Prendiamo questo dataframe di esempio con 2 condizioni, 30 trial in ogni condizione e 10 soggetti: ```r dat <- expand.grid( id = 1:10, cond = c("a", "b"), ntrial = 1:30 ) dat$y <- rnorm(nrow(dat)) head(dat) ``` ``` ## id cond ntrial y ## 1 1 a 1 0.68686181 ## 2 2 a 1 -0.15258459 ## 3 3 a 1 -0.08487191 ## 4 4 a 1 -0.35472196 ## 5 5 a 1 1.45533219 ## 6 6 a 1 1.05735577 ``` --- # Dataframe come Liste L'idea è quindi di calcolare un `t.test()` tra le condizioni separatamente per ogni soggetto. Possiamo splittare il dataframe per soggetto ottenendo una lista con 10 dataframes e poi applicare la funzione `t.test()` ad ogni elemento. ```r # definisco la funzione con tutti gli argomenti ttest <- function(data){ t.test(y ~ cond, data = data, paired = TRUE) } dat_list <- split(dat, dat$id) # splittiamo per id length(dat_list) ``` ``` ## [1] 10 ``` ```r t_list <- lapply(dat_list, ttest) t_list[[1]] ``` ``` ## ## Paired t-test ## ## data: y by cond ## t = 1.3256, df = 29, p-value = 0.1953 ## alternative hypothesis: true mean difference is not equal to 0 ## 95 percent confidence interval: ## -0.1643213 0.7696416 ## sample estimates: ## mean difference ## 0.3026602 ``` --- # Dataframe come Liste (extra) Questo approccio è la base per lavorare in modo molto compatto anche per fare cose complesse con più dataframe insieme. Basta avere chiaro il concetto di `funzione` e di `iterazione`. Il capitolo [Many models](https://r4ds.had.co.nz/many-models.html) di R4DS illustra molto chiaramente questa idea introducendo il concetto di nested dataframe. .pull-left[ <img src="data:image/png;base64,#img/r4ds.png" width="40%" style="display: block; margin: auto;" /> ] .pull-right[ ```r nestdat <- tibble::tibble( id = 1:10, data = dat_list ) nestdat ``` ``` ## [90m# A tibble: 10 × 2[39m ## id data ## [3m[90m<int>[39m[23m [3m[90m<named list>[39m[23m ## [90m 1[39m 1 [90m<df [60 × 4]>[39m ## [90m 2[39m 2 [90m<df [60 × 4]>[39m ## [90m 3[39m 3 [90m<df [60 × 4]>[39m ## [90m 4[39m 4 [90m<df [60 × 4]>[39m ## [90m 5[39m 5 [90m<df [60 × 4]>[39m ## [90m 6[39m 6 [90m<df [60 × 4]>[39m ## [90m 7[39m 7 [90m<df [60 × 4]>[39m ## [90m 8[39m 8 [90m<df [60 × 4]>[39m ## [90m 9[39m 9 [90m<df [60 × 4]>[39m ## [90m10[39m 10 [90m<df [60 × 4]>[39m ``` ]