Il ciclo for di base
Un ciclo for in R esegue un blocco di codice una volta per ogni elemento di un vettore o di una lista. La variabile del ciclo assume a turno ogni valore:
Tre elementi, tre passaggi. Non c'è un contatore da impostare né una condizione da mantenere: for (fruit in fruits) si legge "per ogni frutto in fruits", ed è esattamente ciò che fa. La stessa forma scorre gli elementi di una lista, una sequenza di numeri (for (i in 1:10)) o i nomi di qualsiasi cosa tramite names().
Per stampare dentro un ciclo servono print() o cat()
In console, scrivere x stampa il suo valore. Dentro il corpo di un ciclo no: la stampa automatica avviene solo al livello più alto, e un'espressione nuda dentro un ciclo viene valutata e buttata via:
Quindi quando un ciclo "non fa niente", questa è la prima cosa da controllare. Usa print() per dare un'occhiata veloce a qualsiasi valore, oppure cat() quando componi tu una riga di output (ricorda il "\n": cat() non lo aggiunge).
Scorrere gli indici con seq_along()
A volte ti serve la posizione oltre al valore: per scrivere in un altro vettore allo stesso indice, o per stampare un elenco numerato. Scorri gli indici con seq_along():
Nel codice più vecchio vedrai for (i in 1:length(prices)), e nasconde un vero bug. Quando il vettore è vuoto, length() vale 0, e 1:0 non significa "nessuna iterazione": i due punti contano all'indietro:
Un ciclo su 1:length(empty) viene eseguito due volte, indicizzando elementi che non esistono (empty[1] è NA, e gli assegnamenti creano voci fantasma). seq_along() restituisce una sequenza vuota per un vettore vuoto, quindi il corpo del ciclo semplicemente non viene mai eseguito. Fai di seq_along() un'abitudine; la sua sorella seq_len(n) fa lo stesso lavoro quando hai un conteggio invece di un vettore.
Raccogliere i risultati: prealloca, non far crescere
Il primo istinto naturale per accumulare risultati è questo:
squares <- c()
for (i in 1:10000) {
squares <- c(squares, i^2) # copies the ENTIRE vector every pass
}
Funziona, ma ogni c(squares, ...) alloca un vettore nuovo di zecca e ci copia dentro tutti gli elementi vecchi. Alla fine hai copiato circa 50 milioni di numeri per produrne 10.000. Questo anti-pattern del vettore che cresce è il motivo principale per cui si dice che i cicli di R sono lenti.
La soluzione: crea prima il risultato alla dimensione completa, poi assegna per indice:
Per risultati che non sono numeri vale la stessa idea con character(n), logical(n) o vector("list", n) per una lista di cose arbitrarie. I cicli con preallocazione sono perfettamente veloci.
next e break
Due parole chiave guidano un ciclo dall'interno del suo corpo: next abbandona il passaggio corrente e salta all'elemento successivo; break esce del tutto dal ciclo.
Questo stampa 2 4 6 8: i valori dispari vengono saltati da next, e quando arriva 10, break termina il ciclo prima di stampare. Usa next per escludere subito certi casi (mantiene il corpo principale senza rientri in più), e break quando un ciclo ha trovato ciò che cercava e non ha motivo di continuare.
Cicli annidati
Il corpo di un ciclo può contenere un altro ciclo. Il ciclo interno viene eseguito fino alla fine per ogni singolo passaggio di quello esterno; la dimostrazione classica è una tavola pitagorica:
Per ogni riga i, il ciclo interno percorre tutte le colonne j, e l'a capo della riga viene stampato quando il ciclo interno finisce. I cicli annidati vanno bene su due livelli; da tre in su, di solito il corpo chiede di diventare una funzione, o l'intero calcolo chiede di diventare un outer() o un'operazione su matrici.
L'annidamento copre anche lo scorrimento di una lista di vettori: ciclo esterno sulla lista, ciclo interno (o meglio, una chiamata vettoriale) su ogni elemento:
Nota che alla fine non c'è nessun ciclo interno: mean() gestisce l'intero vettore interno in una chiamata. Questa osservazione vale in generale, e ci porta alla parte onesta.
Quando non usare un ciclo
R è un linguaggio vettoriale: le sue operazioni di base lavorano già su interi vettori in una volta. Un ciclo che trasforma ogni elemento uno alla volta è spesso un modo più lungo di scrivere una riga sola:
Preferisci la forma vettoriale ogni volta che esiste: è più breve, più difficile da sbagliare e più veloce. Per applicare una funzione a ogni elemento di una lista, la famiglia apply (sapply, lapply, vapply) svolge lo stesso ruolo.
Ma non trasformare questo in un dogma del tipo "i cicli sono il male". Un ciclo è lo strumento giusto quando le iterazioni dipendono da quelle precedenti (stato che si accumula, simulazioni), quando produci effetti collaterali come scrivere file, o semplicemente quando il ciclo è la versione che tu e chi legge capite al primo sguardo. Un ciclo chiaro batte una riga ingegnosa che nessuno riesce a decifrare.
Cosa ti porti a casa
for (x in v) { ... }esegue il corpo una volta per elemento, senza contatori da gestire.- Dentro un ciclo, stampa esplicitamente con
print()ocat(); le espressioni nude vengono scartate. - Scorri le posizioni con
seq_along(v), mai con1:length(v): quest'ultimo viene eseguito due volte su un vettore vuoto. - Prealloca i risultati con
numeric(n)/vector("list", n)e assegna per indice; far crescere conc()copia tutto a ogni passaggio. nextsalta un passaggio,breakesce; preferisci le operazioni vettoriali quando esistono, ma non temere un ciclo leggibile.
Prossimo passo: i cicli che vengono eseguiti finché succede qualcosa invece che una volta per elemento, cioè while e repeat.
Domande frequenti
Come si scrive un ciclo for in R?
for (x in v) { ... }: la variabile del ciclo x assume a turno ogni elemento del vettore (o della lista) v, e il corpo viene eseguito una volta per elemento. Per scorrere le posizioni invece dei valori, usa for (i in seq_along(v)) e indicizza con v[i].
Perché usare seq_along invece di 1:length(v) in R?
Quando v è vuoto, length(v) vale 0, quindi 1:length(v) diventa 1:0, cioè il vettore di due elementi c(1, 0), e il corpo del ciclo viene eseguito due volte su elementi che non esistono. seq_along(v) restituisce una sequenza vuota per un vettore vuoto, quindi il ciclo viene eseguito correttamente zero volte.
Perché il mio ciclo for in R non stampa niente?
La stampa automatica di R avviene solo per le espressioni scritte al livello più alto della console. Dentro il corpo di un ciclo, un'espressione nuda come x viene valutata e scartata. Avvolgila in print(x), oppure usa cat(...) quando vuoi formattare tu l'output.
I cicli for sono lenti in R?
Il ciclo in sé va bene: ciò che è lento è far crescere un vettore al suo interno con result <- c(result, ...), che copia l'intero vettore a ogni passaggio. Prealloca con numeric(n) o vector("list", n) e assegna per indice, e un ciclo ha prestazioni ottime. Detto questo, quando un'operazione vettoriale o una chiamata della famiglia apply esprime la stessa idea, di solito è sia più veloce sia più chiara.