Nella scorsa lezione abbiamo visto un po’ della logica della cosiddetta programmazione a oggetti (object-oriented programming). Ricordiamo che gli oggetti possono essere considerati dei contenitori di informazioni, delle scatole all’interno delle quali possiamo mettere diversi tipi di informazioni. Possono contenere valori di diverso tipo (numeri, caratteri alfanumerici) ma anche gruppi di valori strutturati in modi diversi (come un dataset), e addirittura sequenze di istruzioni per fare operazioni con altri oggetti (come funzioni e algoritmi).
Lavorare con R in larga parte consiste nel fare interagire diversi oggetti tra loro. È quindi importante capirne la logica e vedere alcuni esempi di cosa si può fare con gli oggetti.
L’ultima volta abbiamo visto oggetti che contengono singoli valori numerici
e che possono essere fatti interagire in modi diversi
## [1] 5
Ovviamente gli oggetti possono contenere anche il risultato di operazioni tra altri oggetti
## [1] 6
Da qui in poi la storia si fa più complessa, ma la logica rimane la stessa.
Gli oggetti possono contenere vettori. In informatica, i vettori sono serie di valori organizzati su una dimensione:
## [1] 1 2 10 30 10000
Gli oggetti possono contenere anche matrici. Queste sono serie di valori organizzate su due dimensioni:
## [,1] [,2]
## [1,] 1 10
## [2,] 2 30
Una cosa importante: i vettori e le matrici non possono contenere valori di diverso tipo (come numeri e caratteri) allo stesso tempo. Se cercate di assegnare un valore di tipo diverso a un oggetto, i valori verranno convertiti per essere tutti dello stesso tipo. Dato che i caratteri alfanumerici sono più generici dei numeri (i caratteri sono semplici etichette, non hanno un valore quantitativo), quando aggiungete un elemento alfanumerico a un vettore o una matrice, R convertirà anche gli altri valori in caratteri:
## [1] "1" "2" "10" "30" "10000" "f"
Si può vedere che ora tutti i valori sono caratteri dalle virgolette.
Per selezionare un elemento da dentro un oggetto, occorre usare le parentesi quadre e specificare la posizione (o valore di indice) dell’elemento desiderato. Per esempio, per vedere il secondo elemento nel vettore v
:
## [1] "2"
Seguendo la stessa logica si possono selezionare più elementi diversi:
## [1] "1" "10" "10000"
Per oggetti multidimensionali, come le matrici, occorre specificare la posizione dell’elemento in tutte le dimensioni. Nel caso delle matrici, che hanno 2 dimensioni, il primo valore di indice si riferisce alla posizione di riga, e il secondo alla posizione di colonna:
## [1] 10
Si possono anche selezionare intere righe e colonne
## [1] 1 10
## [1] 1 2
Le liste sono “vettori di oggetti” che possono contenere di tutto (altri vettori, matrici, singoli valori, ecc.). Il grande vantaggio delle liste sta nel fatto che possono contenere valori di diverso tipo (non solamente numerici o caratteri). Sono un formato molto conveniente per archiviare dati complessi (organizzati in più di 2 dimensioni). Tuttavia sono oggetti dalla struttura più “astratta” e difficile da capire intuitivamente di quelli che abbiamo visto fino a questo momento
lst <- list(
a = c(1, 3, 5), # Un vettore numerico
b = matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2), # Una matrice numerica
c = "c" # Un valore alfanumerico
)
lst
## $a
## [1] 1 3 5
##
## $b
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
##
## $c
## [1] "c"
Come con vettori e matrici, gli elementi di una lista possono essere richiamati utilizzando il numero di indice
## [1] 1 3 5
o chiamandoli con il loro nome (quando ne hanno uno)
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
In questo caso si utilizza il simbolo del dollaro $
.
Un tipo di oggetti molto importanti è il “data frame”. I data frame sono qualcosa a metà tra le matrici e le liste: sono bi-dimensionali, come le matrici, ma i valori in colonne diverse possono contenere valori di diverso tipo (numerici, caratteri), come nelle liste. Quando lavoriamo con un data frame assumiamo che le righe sono osservazioni e le colonne variabili. Come nelle liste, le colonne di un data frame hanno un nome: questi sono i nomi delle variabili nel dataset.
## var1 var2
## 1 1 a
## 2 2 b
## 3 3 c
## 4 10 d
Si possono vedere i nomi delle variabili contenute in un data frame usando la funzione names()
.
## [1] "var1" "var2"
Le variabili in un data frame possono essere selezionate utilizzando le parentesi quadre e il numero di indice, come con una matrice:
## [1] 1 2 3 10
Oppure si possono utilizzare i nomi delle variabili (più utile), come nelle liste, utilizzando il simbolo del dollaro $
.
## [1] 1 2 3 10
Le parentesi quadre sono più utili quando occorre selezionare osservazioni
## var1 var2
## 1 1 a
## var1 var2
## 3 3 c
## 4 10 d
R può gestire dati e valori di diverso tipo, come numeri e caratteri. Possiamo chiedere direttamente a R quale tipo di valori contiene una variabile utilizzando la funzione typeof()
, o possiamo chiedere in modo più specifico utilizzando le funzioni di classe is.
# Un vettore di NUMERI INTERI
int <- 1:10
# Un vettore numerico con decimali
num <- seq(0, 1, by = 0.2)
# Un vettore di carattere
let <- letters[1:10]
# Chiedere il tipo di valore
typeof(int)
## [1] "integer"
## [1] "double"
## [1] "character"
## [1] TRUE
## [1] TRUE
Convertire i valori da un tipo all’altro è molto semplice, utilizzando le funzioni di classe as.
. Alcuni esempi:
## [1] "0" "0.2" "0.4" "0.6" "0.8" "1"
## [1] "character"
## [1] 0.0 0.2 0.4 0.6 0.8 1.0
# La conversione da carattere a numero funziona solo quando i caratteri rappresentano numeri (ed è già tanto)
as.numeric(let)
## Warning: NAs introduced by coercion
## [1] NA NA NA NA NA NA NA NA NA NA
Un altro tipo di valore che sta più o meno a metà tra numeri e caratteri sono le cosiddette variabili “factor”, ovvero valori categorici. R utilizza molto questo formato, al punto che a volte pensiamo che alcuni valori sono numerici ma in realtà i numeri sono semplici etichette.
## [1] 0 0.2 0.4 0.6 0.8 1
## Levels: 0 0.2 0.4 0.6 0.8 1
## Warning in mean.default(as.factor(num)): argument is not numeric or logical:
## returning NA
## [1] NA
Una variabile factor contiene informazioni riguardo a tutti i possibili valori unici che la variabile può avere. Questi sono chiamati “livelli”:
## [1] "0" "0.2" "0.4" "0.6" "0.8" "1"
I livelli sono come le “etichette” in SPSS o Stata: i valori di una variabile factor sono numeri (anche se non hanno valore quantitativo ma, appunto, sono solo etichette), e i livelli rappresentano il contenuto sostantivo di tali valori. Questa proprietà delle variabili factor ci permette di convertire un carattere in numero utilizzando il factor come passo intermedio:
## letters factors numbers
## 1 a a 1
## 2 b b 2
## 3 c c 3
## 4 d d 4
## 5 e e 5
## 6 f f 6
## 7 g g 7
## 8 h h 8
## 9 i i 9
## 10 j j 10
Le funzioni sono una parte molto importante di R. Qualsiasi cosa che R fa, è fatta tramite una funzione. In generale, le funzioni sono sequenze di istruzioni che, dato uno o più argomenti di input, restituiscono un output (che può essere messo in un oggetto). La struttura tipica di una funzione in R è function(argument 1, argument 2, ...)
. Naturalmente, anche le funzioni stesse possono essere immagazzinate in oggetti.
## [1] 6
Una caratteristica importante delle funzioni in R è che possono essere vettorizzate: nonostante la funzione richieda un solo argomento, può essere applicata a una qualsiasi collezione di numeric, come un vettore una matrice. In tal caso R ripeterà la stessa operazione per ogni elemento del vettore (o della matrice) e restituirà un vettore (o matrice) dove ogni elemento è passato attraverso la funzione.
## [1] 2 4 6 100
La maggior parte delle funzioni in R è più complessa della nostra funzione plusone()
, e gli output possono essere di tipo diverso. La logica comunque è sempre la stessa: (1) la funzione richiede un input, (2) elabora l’input e, (3) restituisce un output.
c()
: la funzione che combina diversi valori in un vettore. L’abbiamo usata prima, e in generale è una delle funzioni che si useranno più spesso## [1] 1 5 6 8 100
seq()
: crea un vettore con una sequenza di numeri## [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0
## [1] 1.000000 1.111111 1.222222 1.333333 1.444444 1.555556 1.666667 1.777778
## [9] 1.888889 2.000000
Un modo rapido per ottenere una sequenza di numeri interi con incremento 1 è usare i due punti :
## [1] 1 2 3 4 5 6 7 8 9 10
rep()
: crea un vettore dove un valore o un insieme di valori vengono ripetuti n volte## [1] 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3
## [1] 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3
## [1] "a" "b" "c" "a" "b" "c" "a" "b" "c" "a" "b" "c" "a" "b" "c"
sample()
: estrae uno o più valori casuali da un vettore## [1] 50 26
Notare che i numeri estratti possono essere rimessi nell’insieme (e quindi possibilmente venire estratti di nuovo) aggiungendo l’opzione replace = TRUE
:
## [1] 3 3 4 2 1 2
round()
: arrotonda numeri con decimali selezionando il numero di cifre dopo la virgola## [1] 3.141593
## [1] 3.14
rbind()
e cbind()
: creano una matrice combinando diversi vettori, per riga e per colonna rispetivamente## [,1] [,2] [,3] [,4] [,5]
## [1,] 0 2 4 6 8
## [2,] 1 3 5 7 9
## [,1] [,2]
## [1,] 0 1
## [2,] 2 3
## [3,] 4 5
## [4,] 6 7
## [5,] 8 9
sort()
: ordina i valori all’interno di un vettore## [1] 0 3 9 28 57 74 100 2359
## [1] 2359 100 74 57 28 9 3 0
order()
: funzione più generica di sort()
, restituisce l’ordine con il quale i numeri di indice (o posizioni) degli elemenbti nel vettore devono essere selezionati per essere messi in ordine## [1] 7 2 1 5 4 6 3 8
# Per ottenere lo stesso risultato di "sort()", occorre chiamare "order()" all'interno delle parentesi quadre
unord[order(unord)]
## [1] 0 3 9 28 57 74 100 2359
## [1] 2359 100 74 57 28 9 3 0
unique()
: restituisce i valori unici in un vettore (ovvero, elimina i valori duplicati)## [1] 1 2 4 3 6 7
length()
: restituisce la lunghezza di un vettore, ovvero quanti elementi contiene## [1] 4
nrow()
, ncol()
e dim()
: queste funzioni riportano il numero di righe e colonne di una matrice## [1] 4
## [1] 3
## [1] 4 3
## [1] 4
## [1] 3
rm()
: elimina un oggetto dalla memoriaunlist()
: converte una lista in un vettore (portando tutti i valori allo stesso tipo)## $a
## [1] 1 3 5
##
## $b
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
##
## $c
## [1] "c"
## a1 a2 a3 b1 b2 b3 b4 c
## "1" "3" "5" "1" "2" "3" "4" "c"
a
, b
and c
, ognuno contenente un vettore di 3 numeri estratti casualmente dalla sequenza 1:500Naturalmente R ha parecchie funzioni che permettono di fare operazioni statistiche nei valori all’interno di vettori e matrici
## [1] 0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0 2.2 2.4 2.6 2.8
## [16] 3.0 3.2 3.4 3.6 3.8 4.0 4.2 4.4 4.6 4.8 5.0 5.2 5.4 5.6 5.8
## [31] 6.0 6.2 6.4 6.6 6.8 7.0 7.2 7.4 7.6 7.8 8.0 8.2 8.4 8.6 8.8
## [46] 9.0 9.2 9.4 9.6 9.8 10.0
## [1] 255
## [1] 5
## [1] 5
## [1] 8.84
## [1] 2.973214
## [1] 0
## [1] 10
## [1] 0 10
## 5% 50% 95%
## 0.5 5.0 9.5
Nota: tutte queste funzioni non amano i valori mancanti (i cosiddetti missing values). In R i valori mancanti sono rappresentati come NA
. Basta un NA
in un vettore o in una matrice e R si rifiuterà di calcolare il risultato:
## [1] NA 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0 2.2 2.4 2.6 2.8
## [16] 3.0 3.2 3.4 3.6 3.8 4.0 4.2 4.4 4.6 4.8 5.0 5.2 5.4 5.6 5.8
## [31] 6.0 6.2 6.4 6.6 6.8 7.0 7.2 7.4 7.6 7.8 8.0 8.2 8.4 8.6 8.8
## [46] 9.0 9.2 9.4 9.6 9.8 10.0
## [1] NA
Come soluzione, tutte le funzioni che abbiamo visto includono l’opzione na.rm = TRUE
, che dice a R di calcolare il parametro richiesto solo sulle osservazioni effettivamente presenti nel vettore o nella matrice (quindi escludendo i valori mancanti).
## [1] 5.1
## [1] 8.5
summary()
è una funzione generica utilizzata per ispezionare il contenuto di un oggetto. Può essere utilizzata con diversi tipi di oggetti, con risultati differenti.## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0 2.5 5.0 5.0 7.5 10.0
## var1 var2
## Min. : 1.00 Length:4
## 1st Qu.: 1.75 Class :character
## Median : 2.50 Mode :character
## Mean : 4.00
## 3rd Qu.: 4.75
## Max. :10.00
## Length Class Mode
## a 3 -none- numeric
## b 1 -none- character
table()
: produce una tabella di frequenze (“tabella di contingenza”, “cross-table”)##
## 0 1 2 3
## 5 5 5 5
##
## 0 1
## 0 3 2
## 1 3 2
## 2 2 3
## 3 2 3
prop.table()
: prende una tabella di frequenze come input (ovvero l’output della funzione table()
, che può essere messo in un oggetto) e restituisce una tabella con le frequenze relative invece di quelle assolute. Può calcolare le frequenze relative per riga, per colonna, o per riga e colonna.##
## 0 1
## 0 0.6 0.4
## 1 0.6 0.4
## 2 0.4 0.6
## 3 0.4 0.6
##
## 0 1
## 0 0.3 0.2
## 1 0.3 0.2
## 2 0.2 0.3
## 3 0.2 0.3
##
## 0 1
## 0 0.15 0.10
## 1 0.15 0.10
## 2 0.10 0.15
## 3 0.10 0.15
Mettiamo il caso che abbiate bisogno di utilizzare una funzione, come per esempio mean()
, ma non sapete come usarla. In questo caso potete utilizzare la funzione help()
, semplificata con il singolo punto di domanda ?
, che aprirà una pagina con una descrizione più o meno dettagliata di quello che la funzione fa e quali argomenti necessita.
La maggior parte delle operazioni in R vengono eseguite utilizzando funzioni. Tuttavia, programmare (con R, così come con qualsiasi altro software) implica seguire un flusso logico che risponda ai dati (o a qualsiasi input dato al software). Le strutture di controllo sono un insieme di istruzioni che possono essere usate per istruire R su come comportarsi in alcuni punti del vostro codice. Potete vederle come procedure fisse che permettono al programma di fare operazioni più complesse di quelle di base che abbiamo visto finora (e, in un certo senso, di comportarsi indipendentemente dal vostro costante input di istruzioni).
I seguenti comandi sono utili strumenti di programmazione che userete per lo più in alcune operazioni di data management. Vi renderanno la vita più facile e vi faranno risparmiare tempo (e digitazione) quando i compiti sono complessi o ripetitivi.
Un aspetto molto importante della programmazione riguarda il dire al programma di eseguire alcune operazioni date certe condizioni. Le funzioni statistiche che abbiamo appena visto sono fatte per essere applicate a tutti i dati contenuti nell’input (sia esso un vettore, una matrice, una variabile in un data frame, e così via). Tuttavia, spesso abbiamo bisogno di fare cose diverse per diversi gruppi di osservazioni. Per esempio, con un campione di persone intervistate in un sondaggio, potremmo voler creare una variabile che da valore 1 a tutte le femmine e 0 a tutti i maschi. In tal caso dobbiamo istruire R a comportarsi diversamente con le femmine e con i maschi.
Inoltre, un giorno potremmo ritrovarci a scrivere programmi più complessi, dove ci serve che R si comporti in modo diverso a seconda del risultato di una operazione precedente. Anche in questo caso ci sono delle condizioni da cui dipende quello che chiediamo a R di fare.
if
e else
permettono di valutare una data condizione e di agire diversamente a seconda della risposta. Possono essere usati insieme, ma a volte serve solo il primo e non il secondo. La forma più elementare di dichiarazione condizionale sarebbe:In questo caso, se la condizione è vera R eseguirà una data operazione, altrimenti passerà alle operazioni successive. Immaginate di andare a fare la spesa e di incontrare un amico lungo la strada. Nella maggior parte dei casi, avrete una linea di condotta che implica salutare o anche fermarsi a chiacchierare nel caso in cui incontriate qualcuno che conoscete per strada. Tuttavia, avete già un compito predefinito (andare a fare la spesa), quindi se non incontrate nessuno per strada sapete già cosa fare. In questo caso, una semplice dichiarazione if
sarà sufficiente.
Un ulteriore passo è specificare un’operazione da eseguire nel caso in cui la condizione sia falsa. In questo caso la struttura della sintassi assomiglia di più a:
Un esempio banale di struttura if/else
è il seguente programma che valuta se un numero b
è più grande di un altro numero a
, e produce come output un oggetto y
contenente un valore diverso a seconda del risultato di tale valutazione
a <- 0
b <- sample(-5:5, 1) # Estrae un numero casuale nella sequenza da -5 a +5
if(b > a) {
y <- 1
} else {
y <- 0
}
y
## [1] 1
Notare che questo può anche essere scritto nel seguente modo:
## [1] 1
Una limitazione del comando if
in R è che non può essere vettorizzato. Questo significa che se gli elementi da valutare sono più di uno, R valuterà solo il primo (e farà apparire anche un messaggio di warning).
## Warning in if (b > a) {: the condition has length > 1 and only the first element
## will be used
## [1] 0
Questo complica le cose. Tuttavia, tenete a mente che gli enunciati if
e else
non sono pensati per ricodificare dati, ma per stare all’interno di algoritmi più complessi.
&
e |
Spesso gli enunciati if
e else
si applicano a condizioni complesse, o in generale a più di una sola condizione. Due operatori fondamentali per scrivere condizioni complesse sono gli operatori AND e OR (che potreste avere usato per ricerche su Google, dato che funzionano anche in quel caso).
a <- 0
b <- sample(c(-1, 1), 1)
c <- sample(c(0:10), 1)
# AND statement: &
if(b > a & c > 3) {
y <- 0
} else {
y <- 1
}
y
## [1] 1
## [1] 0
ifelse()
ifelse()
è una funzione “contenitore” (wrapper) per gli enunciati if
e else
. Essendo una funzione, ha l’indubbio vantaggio di essere vettorizzata. Questo implica che, a differenza delle strutture vistre poco fa, ifelse()
può essere applicata a vettori e più in generale a sequenze di dati. La struttura è sempre la stessa: SE la condizione è vera ALLORA esegui “espressione 1”, ALTRIMENTI esegui “espressione 2”. Questo significa che la funzione vuole sapere sempre cosa fare in ogni caso. È una funzione molto utilizzata per ricodificare le variabili in un dataset.Per esempio, data una variabile var1
, possiamo utilizzare ifelse()
per creare una seconda variabile chiamata var2
che ha valore \(1\) per tutti i casi in cui var
è maggiore di \(100\), e \(0\) negli altri casi:
## var1 var2
## 1 1 0
## 2 100 0
## 3 3 0
## 4 200 1
## 5 5 0
Il data frame seguente contiene informazioni riguardanti il genere e l’età di 10 studenti:
students <- data.frame(
sex = c("F", "M", "M", "F", "F", "F", "M", "F", "M", "F"),
age = c(20, 23, 19, 18, 21, 21, 20, 22, 19, 18)
)
Create una variabile chiamata male_teen
che ha valore 1
per tutti i maschi che hanno meno di 20 anni e 0
per tutti gli altri studenti.
I loop sono strutture che si usano per dire a R di ripetere una certa operazione per un determinato numero di volte, o finché non succede qualcosa (ad esempio, finché non si raggiunge la fine di un vettore, finché non si raggiunge un certo valore, ecc.) In generale, i loop si possono usare ogni volta che occorre fare qualcosa di ripetitivo e non si vuole digitare il codice ogni volta (per esempio, se avete bisogno di eseguire una regressione su molte variabili dipendenti). Al giorno d’oggi la programmazione efficiente è andata oltre l’uso dei loop, ma è utile vedere come funzionano i loop per “entrare” nella logica della programmazione. Inoltre, i loop sono meno “da scatola nera” di modi alternativi e più efficienti per ripetere le stesse operazioni, e questo è un utile esercizio per iniziare. Qui parliamo di 3 tipi di loop.
repeat
repeat
, come dice il nome, si limita a ripetere la stessa espressione. Questo è il tipo di loop più semplice e generico che si possa trovare. Richiede tuttavia di utilizzare il comando break
per essere interrotto:i <- 0 # Questo oggetto fa da "indice", è necessario in ogni loop
repeat {
if (i <= 25) {
print(i)
i <- i + 5
} else break
}
## [1] 0
## [1] 5
## [1] 10
## [1] 15
## [1] 20
## [1] 25
Notare che senza il comando break
il loop sarebbe andato avanti all’infinito.
while
while
ripetono la stessa operazione fino a quando una determinata condizione è vera. Sono un passo verso la minore generalità e maggiore semplicità rispetto a repeat
. Per esempio il loop qui sotto concatena una serie di numeri a un vettore chiamato a
. Prima di entrare nel loop creiamo un oggetto che utilizziamo come indice e chiamiamo i
, a cui diamo valore \(1\). Dopodichè diciamo al loop di continuare ad aggiungere valori al vettore a
fino a che i
\(\leq 10\), e poi fermarsi. Ricordiamoci di aggiornare il valore di i
all’interno del loop, altrimenti la condizione non si avvererà mai e il loop andrà avanti all’infinito.## [1] 0 1 2 3 4 5 6 7 8 9 10
Altro esempio di loop di tipo while
: la “sequenza di Fibonacci”. In questa sequenza ogni numero è la somma dei due numeri precedenti, date le prime due cifre \(0\) e \(1\). Potete cercarla su Wikipedia. NEll’esercizio sotto calcoliamo i primi 20 numeri della sequenza:
fib.seq <- c(0, 1)
i <- 1
while(i <= 18) { # Abbiamo già i primi 2 numeri, quindi il loop deve girare 18 volte
fib.seq <- c(fib.seq, fib.seq[i] + fib.seq[i + 1])
i <- i + 1
}
fib.seq
## [1] 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
## [16] 610 987 1597 2584 4181
for
for
sono i più comuni. Rappresentano un ulteriore passo verso una minore generalità, ma questo li rende anche più comodi da utilizzare. I loop di tipo for
essenzialmente passano da ogni elemento all’interno di un vettore. In questo caso non dobbiamo definire i
come variabile, perchè viene create implicitamente dal loop e viene aggiornata in automatico al suo internorandom.vector <- c(2, 5, 13, 0.5, 8, 100)
for(i in random.vector) {
print(i^2) # Notare che per riportare un valore dall'interno di un loop occorre utilizzare "print()"
}
## [1] 4
## [1] 25
## [1] 169
## [1] 0.25
## [1] 64
## [1] 10000
for
per creare la sequenza di Fibonacci come abbiamo fatto sopra con while
.I loop sono divertenti, tuttavia come ho detto prima, vengono usati sempre meno spesso Perché? Perché la maggior parte delle operazioni che si fanno in R usando i loop possono essere fatte più velocemente e usando meno codice vettorizzando un’operazione. In sostanza “vettorializzare” significa applicare la stessa funzione a tutti i valori, gruppi di valori, oggetti e quant’altro, che sono in un vettore/data frame/lista.
R vettorizza automaticamente molte funzioni. Per esempio, possiamo applicare automaticamente la nostra funzione plusone()
a tutti gli elementi di un vettore o di una matrice:
## [1] 2 4 6 100
## [,1] [,2]
## [1,] 5 6
## [2,] 4 3
## [3,] 11 1
Tuttavia, ci sono alcune funzioni che ci aiutano a vettorizzare operazioni complesse in modo esplicito.
apply()
: come suggerisce il nome, “applica” la stessa funzione a tutte le righe o a tutte le colonne di una matrice o di un data frame. Per esempio, possiamo cercare il numero minore in ogni riga e in ogni colonna della matrice mt
.## [1] 4 2 0
## [1] 3 0
Naturalmente apply()
può essere utilizzato sui data frame:
## var1 var2
## 1 1 70
## 2 100 4
## 3 3 1
## 4 200 370
## 5 5 -7
## [1] 35.5 52.0 2.0 285.0 -1.0
## var1 var2
## 61.8 87.6
Nel caso di operazioni più complesse, la funzione può essere scritta direttamente all’interno di apply()
:
# Differenza tra il valore massimo e quello minimo, per riga
apply(dat, 1, function(x) max(x) - min(x))
## [1] 69 96 2 170 12
lapply()
fa la stessa cosa per ogni elemento di una lista (questa è una delle funzioni di vettorizzazione più utilizzate in R):## $a
## [1] 2
##
## $b
## [1] 6
##
## $c
## [1] 1
## $a
## [1] 1.5 11.6
##
## $b
## [1] 2 7 31 965 1 NA
##
## $c
## [1] 10
sapply()
uguale a lapply()
, ma restituisce un vettore invece di una lista come output:## a b c
## 2 6 1
Tuttavia, a seconda della struttura dei dati o dell’operazione richiesta, sapply()
potrebbe non produrre un vettore ma una lista (diventando di fatto equivalente a lapply()
). Per esempio, se vogliamo applicare la funzione plusone()
a tutti gli elementi di any_list
, il risultato manterrà la struttura multidimensionale di any_list
per evitare di farci perdere informazione riguardante a quali elementi sono assieme nello stesso gruppo:
## $a
## [1] 1.5 11.6
##
## $b
## [1] 2 7 31 965 1 NA
##
## $c
## [1] 10
In questo caso, se proprio vogliamo un vettore, possiamo usare la funzione unlist()
.
## a1 a2 b1 b2 b3 b4 b5 b6 c
## 1.5 11.6 2.0 7.0 31.0 965.0 1.0 NA 10.0