Le strutture di controllo permettono di dirigere il flusso di esecuzione di un programma: eseguire istruzioni solo se una condizione è soddisfatta, ripetere blocchi di codice, o interrompere l’esecuzione anticipatamente. R offre le stesse strutture presenti nella maggior parte dei linguaggi di programmazione:
| Struttura | Uso principale |
|---|---|
if / else |
esecuzione condizionale |
ifelse() |
versione vettorializzata di if/else |
while |
ripete finché una condizione è vera |
repeat |
ripete indefinitamente fino a break |
for |
itera su una sequenza di valori |
break |
interrompe il ciclo corrente |
next |
salta all’iterazione successiva |
if /
else💡
ifesegue un blocco di codice solo se la condizione èTRUE. La sintassi minimale èif (condizione) codice.
#> [1] "x è minore di 3"
Con il blocco else si specifica cosa fare quando la
condizione è FALSE:
x <- 2
if (x < 3) {
print(x)
print("x è minore di 3")
} else {
print(x)
print("x non è minore di 3")
}#> [1] 2
#> [1] "x è minore di 3"
# Su una riga sola (per istruzioni brevi)
x <- 4
if (x < 3) print("x è minore di 3") else print("x non è minore di 3")#> [1] "x non è minore di 3"
if/else come espressioneif/else restituisce in output l’ultima istruzione
eseguita, che può essere salvata direttamente in un oggetto:
#> [1] "x non minore di 3"
# Se manca il ramo else e la condizione è FALSE, restituisce NULL
b <- if (x < 3) "x minore di 3"
b # → NULL#> NULL
# Equivalenze
x <- 2
if (x < 3) a <- "x è minore di 3" else a <- "x non è minore di 3"
a <- if (x < 3) "x è minore di 3" else "x non è minore di 3"
a#> [1] "x è minore di 3"
⚠️
ifsi aspetta una condizione scalare (un singoloTRUEoFALSE). Se si passa un vettore, R usa solo il primo elemento e lancia un warning. Per condizioni su vettori usareifelse().
ifelse()
— Versione Vettorializzataifelse() è la versione vettorializzata
di if/else: si applica elemento per elemento su un vettore
e restituisce un vettore della stessa lunghezza.
#> [1] "x è minore di 3"
#> [1] "piccolo" "grande" "piccolo" "grande" "grande"
💡
ifelse()è particolarmente utile per creare nuove variabili in un data frame, come già visto conmutate():
Esercizio 1
Datox <- c(3, 7, 1, 9, 4, 2), usaifelse()per creare un vettore che contenga"pari"o"dispari"per ogni elemento.
💡 L’operatore modulo%%restituisce il resto della divisione intera.
#> [1] "dispari" "dispari" "dispari" "dispari" "pari" "pari"
while e
repeatwhilewhile esegue un blocco di codice finché la
condizione è vera. La condizione viene verificata
prima di ogni iterazione.
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
⚠️ Se la condizione non diventa mai
FALSE, il ciclo è infinito. Assicurarsi sempre che la variabile nella condizione venga modificata all’interno del blocco.
repeatrepeat esegue un blocco di codice
indefinitamente, finché non incontra un’istruzione
break. La condizione di uscita va quindi inserita
esplicitamente all’interno del blocco.
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
💡
repeatè equivalente awhile (TRUE)con unbreakinterno. La scelta tra i due è stilistica:whileè più leggibile quando la condizione di uscita è nota a priori;repeatè utile quando la condizione si valuta solo alla fine del blocco.
forIl ciclo for itera su tutti gli elementi di un vettore,
eseguendo il blocco di codice ad ogni iterazione. La variabile indice
assume a turno il valore di ciascun elemento.
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
Il vettore su cui si itera può essere di qualsiasi tipo:
#> [1] "a"
#> [1] "b"
#> [1] "c"
#> [1] "d"
#> [1] "e"
for AnnidatiSi possono annidare più cicli for. L’indice del ciclo
esterno rimane fisso mentre quello interno completa tutte le sue
iterazioni:
#> [1] "1 a"
#> [1] "1 b"
#> [1] "1 c"
#> [1] "1 d"
#> [1] "1 e"
#> [1] "2 a"
#> [1] "2 b"
#> [1] "2 c"
#> [1] "2 d"
#> [1] "2 e"
#> [1] "3 a"
#> [1] "3 b"
#> [1] "3 c"
#> [1] "3 d"
#> [1] "3 e"
#> [1] "4 a"
#> [1] "4 b"
#> [1] "4 c"
#> [1] "4 d"
#> [1] "4 e"
#> [1] "5 a"
#> [1] "5 b"
#> [1] "5 c"
#> [1] "5 d"
#> [1] "5 e"
break e
nextbreak
— Interrompere il Ciclobreak interrompe immediatamente il ciclo corrente
(funziona con for, while e
repeat):
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
next —
Saltare un’Iterazionenext interrompe l’iterazione corrente e
passa alla successiva, senza uscire dal ciclo:
# Stampa tutti i valori da 1 a 6, saltando il 3
for (i in 1:10) {
if (i == 3) next
print(i)
if (i > 5) break
}#> [1] 1
#> [1] 2
#> [1] 4
#> [1] 5
#> [1] 6
💡 Lo stesso risultato si può ottenere con
repeat,whileofor— la scelta dipende da quale struttura rende il codice più leggibile nel contesto specifico:
#> [1] 1
#> [1] 2
#> [1] 4
#> [1] 5
#> [1] 6
Esercizio 2
Scrivi un cicloforche stampi i numeri da 1 a 20, ma salti i multipli di 3 e si fermi quando incontra il primo multiplo di 7 maggiore di 10.
for (i in 1:20) {
if (i %% 3 == 0) next # salta i multipli di 3
if (i %% 7 == 0 & i > 10) break # ferma al primo multiplo di 7 > 10
print(i)
}#> [1] 1
#> [1] 2
#> [1] 4
#> [1] 5
#> [1] 7
#> [1] 8
#> [1] 10
#> [1] 11
#> [1] 13
applyI cicli for sono flessibili ma poco concisi. R offre la
famiglia apply come alternativa più
compatta e spesso più efficiente, particolarmente adatta per applicare
una funzione a tutti gli elementi di una struttura dati.
| Funzione | Input | Output |
|---|---|---|
apply() |
matrice/array | vettore, matrice o lista |
lapply() |
lista o vettore | lista |
sapply() |
lista o vettore | vettore/matrice (se possibile) |
mapply() |
più vettori | vettore/lista |
tapply() |
vettore + fattore | array (statistiche per gruppo) |
apply()
— Su Matrici e Arrayapply(X, MARGIN, FUN) applica una funzione alle righe
(MARGIN = 1) o alle colonne (MARGIN = 2) di
una matrice.
#> [1] 21 22 23 24 25 26 27 28 29 30
#> [1] 3.02765 3.02765 3.02765 3.02765 3.02765
Con valori mancanti è necessario na.rm = TRUE:
#> [1] NA 15.5 25.5 35.5 45.5
#> [1] 6.0 15.5 25.5 35.5 45.5
apply() accetta anche funzioni anonime
definite al volo:
# Equivalente a mean(na.rm=TRUE) scritto come funzione anonima
apply(z, 2, function(x) sum(x) / length(x))#> [1] NA 15.5 25.5 35.5 45.5
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] NA 0 0 0 0
#> [2,] NA 1 1 1 1
#> [3,] NA 2 2 2 2
#> [4,] NA 3 3 3 3
#> [5,] NA 4 4 4 4
#> [6,] NA 5 5 5 5
#> [7,] NA 6 6 6 6
#> [8,] NA 7 7 7 7
#> [9,] NA 8 8 8 8
#> [10,] NA 9 9 9 9
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] NA -1.4863011 -1.4863011 -1.4863011 -1.4863011
#> [2,] NA -1.1560120 -1.1560120 -1.1560120 -1.1560120
#> [3,] NA -0.8257228 -0.8257228 -0.8257228 -0.8257228
#> [4,] NA -0.4954337 -0.4954337 -0.4954337 -0.4954337
#> [5,] NA -0.1651446 -0.1651446 -0.1651446 -0.1651446
#> [6,] NA 0.1651446 0.1651446 0.1651446 0.1651446
#> [7,] NA 0.4954337 0.4954337 0.4954337 0.4954337
#> [8,] NA 0.8257228 0.8257228 0.8257228 0.8257228
#> [9,] NA 1.1560120 1.1560120 1.1560120 1.1560120
#> [10,] NA 1.4863011 1.4863011 1.4863011 1.4863011
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] NA -1.4863011 -1.4863011 -1.4863011 -1.4863011
#> [2,] -1.4605935 -1.1560120 -1.1560120 -1.1560120 -1.1560120
#> [3,] -1.0954451 -0.8257228 -0.8257228 -0.8257228 -0.8257228
#> [4,] -0.7302967 -0.4954337 -0.4954337 -0.4954337 -0.4954337
#> [5,] -0.3651484 -0.1651446 -0.1651446 -0.1651446 -0.1651446
#> [6,] 0.0000000 0.1651446 0.1651446 0.1651446 0.1651446
#> [7,] 0.3651484 0.4954337 0.4954337 0.4954337 0.4954337
#> [8,] 0.7302967 0.8257228 0.8257228 0.8257228 0.8257228
#> [9,] 1.0954451 1.1560120 1.1560120 1.1560120 1.1560120
#> [10,] 1.4605935 1.4863011 1.4863011 1.4863011 1.4863011
for# Ciclo for
v2 <- c()
for (i in 1:ncol(z)) v2[i] <- mean(z[, i], na.rm = TRUE)
# apply
v1 <- apply(z, 2, mean, na.rm = TRUE)
v1#> [1] 6.0 15.5 25.5 35.5 45.5
#> [1] 6.0 15.5 25.5 35.5 45.5
💡 Per matrici di grandi dimensioni
apply()è generalmente più veloce del ciclofor. Si può misurare consystem.time():
lapply()
— Su Listelapply(X, FUN) applica FUN a ogni elemento
della lista X e restituisce sempre una
lista con lo stesso numero di elementi.
x <- as.list(1:5)
v1 <- lapply(x, log) # lista
v2 <- list()
for (i in seq_along(x)) v2[[i]] <- log(x[[i]])
v1#> [[1]]
#> [1] 0
#>
#> [[2]]
#> [1] 0.6931472
#>
#> [[3]]
#> [1] 1.098612
#>
#> [[4]]
#> [1] 1.386294
#>
#> [[5]]
#> [1] 1.609438
#> [[1]]
#> [1] 0
#>
#> [[2]]
#> [1] 0.6931472
#>
#> [[3]]
#> [1] 1.098612
#>
#> [[4]]
#> [1] 1.386294
#>
#> [[5]]
#> [1] 1.609438
sapply()
— Versione Semplificata di lapply()sapply() è analoga a lapply() ma
semplifica il risultato: restituisce un vettore o una
matrice quando possibile, altrimenti una lista.
#> [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379
#> [[1]]
#> [1] 1 2 3
#>
#> [[2]]
#> [1] 1 2 3 4
#>
#> [[3]]
#> [1] 1 2 3 4 5
#>
#> [[4]]
#> [1] 1 2 3 4 5 6
#>
#> [[5]]
#> [1] 1 2 3 4 5 6 7
#>
#> [[6]]
#> [1] 1 2 3 4 5 6 7 8
#>
#> [[7]]
#> [1] 1 2 3 4 5 6 7 8 9
mapply()
— Su Più Argomentimapply() è la versione multivariata di
sapply(): applica una funzione passando in parallelo più
vettori come argomenti.
#> [[1]]
#> [1] 1 1 1 1
#>
#> [[2]]
#> [1] 2 2 2
#>
#> [[3]]
#> [1] 3 3
#>
#> [[4]]
#> [1] 4
Esercizio 3
Usandoapply(), calcola per ogni colonna della matricez(definita sopra): minimo, massimo e range (max - min). Confronta il risultato con un cicloforequivalente.
#> [1] 1 11 21 31 41
#> [1] 10 20 30 40 50
#> [1] 9 9 9 9 9
# for equivalente
risultati <- matrix(NA, nrow = 3, ncol = ncol(z),
dimnames = list(c("min", "max", "range"), NULL))
for (i in 1:ncol(z)) {
risultati["min", i] <- min(z[, i])
risultati["max", i] <- max(z[, i])
risultati["range", i] <- max(z[, i]) - min(z[, i])
}
risultati#> [,1] [,2] [,3] [,4] [,5]
#> min 1 11 21 31 41
#> max 10 20 30 40 50
#> range 9 9 9 9 9
In R è possibile creare funzioni personalizzate e organizzarle in pacchetti. Le funzioni vengono salvate come oggetti nell’environment e possono essere usate esattamente come le funzioni predefinite di R.
La sintassi è:
#> [1] 27
# Equivalentemente — senza salvare in un oggetto intermedio
cube <- function(x) {
return(x^3)
}
cube(2)#> [1] 8
💡 Se
return()è omesso, la funzione restituisce automaticamente l’ultima espressione valutata:
power <- function(x, exp) {
x^exp # ultima istruzione → viene restituita automaticamente
}
power(2, 2)#> [1] 4
#> [1] 4
Si possono specificare valori di default per gli argomenti: se l’utente non li fornisce, viene usato il default.
power <- function(x, exp = 1) { # exp = 1 è il default
y <- x^exp
return(y)
}
power(2) # usa exp = 1 → restituisce 2#> [1] 2
#> [1] 8
Una funzione può restituire un solo oggetto. Per restituire più valori si raccolgono in un vettore o in una lista:
power <- function(x, exp = 1) {
y <- x^exp
return(c(result = y, input = x, exp = exp))
}
power_list <- function(x, exp = 1) {
y <- x^exp
return(list(result = y, input = x, exp = exp))
}
power(2)#> result input exp
#> 2 2 1
#> result input exp
#> 4 2 2
#> result input exp
#> 4 2 2
#> result
#> 4
#> [1] 4
#> [1] 5.5
#> [1] 5.5
Esercizio 4
Scrivi una funzionemyvar()che calcoli la varianza di un vettore numerico e confronta il risultato convar().
Ricorda: \(\text{Var}(x) = \frac{1}{n-1} \sum_{i=1}^{n}(x_i - \bar{x})^2\)
myvar <- function(x) {
n <- length(x)
xbar <- sum(x) / n
sum((x - xbar)^2) / (n - 1)
}
x <- c(2, 4, 4, 4, 5, 5, 7, 9)
myvar(x)#> [1] 4.571429
#> [1] 4.571429
Esercizio 5
Scrivi una funzionemysummary()che prenda in input un data frame e restituisca un data frame con, per ogni variabile numerica: minimo, massimo, media, i tre quartili, numero diNAe numero di valori unici.
Verifica il funzionamento sul datasetInsurancedel pacchettoMASS.
mysummary <- function(df) {
df2 <- df[, sapply(df, is.numeric)]
varMean <- apply(df2, 2, mean, na.rm = TRUE)
varQuant <- apply(df2, 2, quantile, na.rm = TRUE)
varNA <- apply(df2, 2, function(x) sum(is.na(x)))
varUnique <- apply(df2, 2, function(x) length(unique(x)))
varMin <- apply(df2, 2, min, na.rm = TRUE)
varMax <- apply(df2, 2, max, na.rm = TRUE)
as.data.frame(rbind(
min = varMin,
Q1 = varQuant[2, ],
mean = varMean,
median = varQuant[3, ],
Q3 = varQuant[4, ],
max = varMax,
n_NA = varNA,
n_unici = varUnique
))
}
library(MASS)
mysummary(Insurance)get() —
Iterare su Oggetti per NomeLa funzione get() prende in input una stringa e
restituisce l’oggetto con quel nome nell’environment. Permette di
costruire cicli for che iterano su diversi oggetti (ad
esempio funzioni):
square <- function(x) x^2
cube <- function(x) x^3
doublesquare <- function(x) x^4
for (name in c("square", "cube", "doublesquare", "mean")) {
f <- get(name)
print(f(2))
}#> [1] 4
#> [1] 8
#> [1] 16
#> [1] 2
if si aspetta una condizione scalare:
per condizioni su vettori usare ifelse().while verifica la condizione prima di
ogni iterazione; repeat esegue sempre almeno una
volta.for, break esce dal ciclo;
next salta all’iterazione successiva.apply è preferibile ai cicli
for quando si applica la stessa funzione a tutti gli
elementi di una struttura: il codice è più compatto e spesso più
veloce.return() è omesso.# Stampa a schermo
print("stringa")
# IF / ELSE
if (cond) codice # se cond = T esegui codice
if (cond){ # se cond = T esegui codice1
codice1
} else { # altrimenti esegui codice2
codice2
}
if (cond1){ # se cond1 = T esegui codice1
codice1
} else if(cond2){ #se cond2 = T esegui codice2
codice2
} else { # altrimenti esegui codice 3
codice2
}
ifelse(cond, val_vero, val_falso) # vettorializzato
# WHILE / REPEAT
while (cond) { codice } #ripeti codice finchè cond = T
repeat { # ciclo infinito esplicito
codice
if (cond) break # se cond = T esci da ciclo
}
# FOR
for (i in vettore) { codice } #cicla su vettore
break # esce dal ciclo
next # salta all'iterazione successiva
# APPLY: applica FUN
apply(X, 1, FUN) # su matrice, per riga -> vettore
apply(X, 2, FUN) # su matrice, per colonna -> vettore
lapply(lista, FUN) # su ista -> lista
sapply(lista, FUN) # su lista -> vettore/matrice
mapply(FUN, v1, v2) # più argomenti in parallelo
tapply(x, gruppo, FUN) # per gruppo (come aggregate)
# FUNZIONI
nome <- function(arg1, arg2 = default) {
corpo #codice
return(risultato) #ritorna oggetto
}