R minimo
1 Basi del linguaggio
I blocchi di codice di seguito sono live: è possibile modificarli, eseguirli e osservare il risultato!
1.1 Variabili
L’assegnazione di variabili avviene con il simbolo <-, il quale funziona anche in direzione opposta (->):
1.2 Vettori
I vettori si creano con la funzione c() (come combine):
Le sequenze regolari con passo 1 si creano con l’operatore ::
Si noti che il punto per R è un carattere qualsiasi, quindi può fare parte di nomi di variabili.
Le sequenze con passo diverso da 1 si ottengono con seq():
Se si vuole specificare il numero di elementi invece che il passo:
1.3 Matrici
Una matrice si crea con la funzione matrix(). Di default i valori vengono passati per colonne:
Si noti che le opzioni delle funzioni possono essere passate per posizione o per nome. Se si usa il nome, possono essere passate in qualsiasi posizione.
1.4 Indicizzazione
Matrici e vettori in R hanno base 1, cioè il primo elemento ha indice 1 (anziché 0 come in C/C++ e Python).
Per indicizzare un vettore si usano le parentesi quadrate:
Per una matrice si passano due argomenti tra le parentesi quadrate, omettendo uno o entrambi se si vile estrarre un’intera colonna o intera riga. Il primo argomento è quello di riga:
È possibile estrarre sottoinsiemi di elementi passando alle parentesi quadrate vettori di indici:
Infine, è possibile usare vettori di valori logici (TRUE o FALSE, abbreviabili in T e F). In questo caso, se il vettore di indici è più breve del vettore/matrice originale, si assume ripetuto:
1.5 Funzioni vettorializzate
In R ogni variabile è intrinsecamente un vettore e tutte (o quasi) le funzioni e operatori base sono vettorializzate, cioè operano elemento per elemento. Ciò spiega perché l’espressione v.up[v.up > 2] funziona: v.up > 2 restituisce un vettore di valori logici lungo come v.up, valutando la diseguaglianza per ogni elemento di v.up.
Ma anche:
Ciò fa sì che sia raramente necessario utilizzare dei loop.
1.6 Cicli e loop
Quando proprio è necessario:
anche se bastava (ed è molto più efficiente):
2 Funzioni
R è un linguaggio funzionale, cioè le funzioni sono first class objects, ossia tipi come gli altri e possono essere utilizzate anonime o assegnate a variabili:
Si noti che è possibile assegnare un default ad alcuni argomenti.
Alcune funzioni (vedremo più avanti) accettano funzioni come argomenti. In tal caso è frequente usare l’abbreviazione (con \ al posto di function):
3 Strutture dati
Le principali strutture dati sono le liste e le tabelle:
- liste: sono contenitori eterogenei, che possono cioè raggruppare valori di qualsiasi tipo. Possono essere anonime, nel qual caso gli elementi sono accessibili per posizione, oppure nominate, nel qual caso ogni elemento ha un nome univoco;
- tabelle: sono matrici in cui le colonne hanno nomi. A differenza delle matrici, che sono sempre omogenee, le tabelle possono avere colonne di tipi diversi, ma ogni colonna deve essere internamente omogenea.
3.1 Liste
Le liste si creano con la funzione list():
Si noti che NA significa not available e rappresenta un elemento mancante.
Per accedere agli elementi di una lista si usano sempre le parentesi quadrate:
Si noti però che usando [] si ottiene di nuovo una lista (una sotto-lista). Per ottenere l’elemento contenuto in una data posizione è necessario usare la doppia parentesi [[]]:
3.2 Le tabelle
Le tabelle sono la struttura più usata in R e si creano con la funzione data.frame(). Noi utilizzeremo però la funzione tibble(): una versione più evoluta che fa parte della famiglia di librerie tidyverse:
Una tabella può essere indicizzata per riga e per colonna come una matrice, ma è possibile anche attingere alle colonne come vettori usando il nome:
E, ovviamente, per ottenere i numeri la cui radice è dispari:
4 La libreria tidyverse
Si tratta di una libreria relativamente nuova che ha modificato radicalmente il modo di programmare in R, rendendolo più moderno ed efficiente. È in realtà una meta-libreria cioè un collezione di librerie, caricando la quale (comando library()) si caricano tutte le sotto librerie.
Si noti che le librerie devono anzitutto essere installate: o con il comando install.packages("tidyverse"), oppure da RStudio, pannello Packages, pulsante Install.
Una volta installata, una libreria può essere utilizzata o caricandola con library(nome_della_lib_senza_virgolette), oppure scrivendo il nome della libreria e doppio due punti prima della funzione, ad es. dplyr::filter().
4.1 La gestione dei dati (data mangling)
La libreria tidyverse mette a disposizione tutto ciò che serve per maneggiare tabelle di dati in maniera molto completa, sintetica e efficiente.
Per rendere il codice più leggibile, anziché nidificare le chiamate ad una serie di funzioni si preferisce utilizzare l’operatore pipe, %>%, che passa il risultato di una funzione come primo argomento della successiva:
che equivale a scrivere:
ma è più leggibile, soprattutto se si va a capo dopo ogni pipe, in modo che ad ogni riga corrisponda un passo dell’algoritmo.
Le funzioni di data mangling più comuni sono:
mutate(), per modificare o aggiungere una o più colonnefilter(), per filtrare la tabella selezionando solo alcune righeselect(), per selezionare solo alcune colonnearrange(), per riordinare le righe
Ad esempio:
Si noti che tutte queste funzioni sono non distruttive, cioè non alterano la tabella di partenza ma ne restituiscono una nuova. Se il risultato è utile per successive operazioni, quindi, è necessario salvarlo, ad esempio con:
Le funzioni group_by() e summarise() servono per operare su gruppi di righe; ad esempio, per calcolare il massimo di B e la media di C per le righe in cui A è pari o dispari:
4.2 I grafici
tidyverse ha messo a disposizione la libreria ggplot2, che consente di creare grafici per layer. Ogni layer rappresenta una geometria, ed è una funzione che comincia generalmente con geom_; i vari layer si sommano con un +. Il grafico comincia con ggplot(), che serve a caricare la tabella di dati e predisporre lo spazio del grafico stesso.
La funzione aes()infine rappresenta l’estetica, cioè stabilisce quali variabili (cioè colonne della tabella) vanno sui vari assi del grafico. Qui per assi si intendono sia gli assi cartesiani che gli assi generalizzati, cioè colori, dimensione, tipo linea, ecc. utilizzati per rappresentare le varie serie.
Nel caso di più assi si passa l’estetica più generale a ggplot(), quelle particolari alle successive geometrie. In questo caso, solo per geom_point() si sono specificate le estetiche per coloer e dimensione del punto, entrambe collegate alla colonna C.
Si noti che nelle estetiche è possibile anche applicare trasformazioni:
Serie differenti possono essere identificate per colore (o per tipo linea, o per larghezza linea, ecc.):
In questi casi però è preferibile usare l’approccio tidy : la tabella originale deve contenere una osservazione per riga, una variabile per colonna. La tabella t non è tidy, perché una stessa riga ha più di una osservazione (per A, B e C).
Per rendere la tabella tidy:
dove pivot_longer(-A, ...) significa “riorganizza tutte le colonne meno la colonna A”, dopodiché i nomi delle colonne vanno a finire nella colonna series, i valori nella colonna value.
Una tabella tidy è più semplice da mettere in grafico (e più efficiente da manipolare):
Come si vede, si ottiene gratuitamente la legenda. Più serie possono essere separate contemporaneamente per colore, tipo di linea, larghezza di linea, trasparenza, ecc.
5 Map/reduce
In molti linguaggi moderni si sono diffusi gli algoritmi map e reduce come alternative efficienti (perché implementate a basso livello) dei loop espliciti. La prima realizza una mappa, cioè esegue la stessa operazione su tutti gli elementi di una collezione (tipicamente un vettore). La seconda riduce la dimensione di una collezione, ad esempio da un vettore a uno scalare, tipicamente accumulando gli elementi di una collezione.
In R, le operazioni analoghe sono fornite dal pacchetto purrr, parte di tidyverse.
5.1 Map
le funzioni di purrr che cominciano con map_* operano su una lista o un vettore, ritornando una collezione di uguale dimensione e del tipo specificato. Ad esempio, map_dbl() restituisce una lista di double, map_chr() una lista di stringhe, ecc. Il primo argomento (spesso passato via pipe) è la collezione, il secondo è una funzione, eventualmente creata sul posto:
Per brevità si può scrivere anche:
e addirittura, sostituendo \(x) con ~ e x con .:
Tutti questi esempi in realtà possono essere evitati scrivendo semplicemente LETERS[1:10], ma le funzioni map_*() risultano indispensabili quando non esiste una equivalente funzione vettorializzata.
5.2 Reduce
L’algoritmo reduce applica una mappa alla collezione e accumula ogni elemento in un accumulatore. In R si ottiene mediante la funzione purrr::reduce():
Più in breve:
In questo caso, il valore iniziale dell’accumulatore è il primo elemento della collezione e il loop inizia a partire dal secondo. Quando invece l’accumulatore deve essere di un tipo differente dagli elementi, è necessario inizializzarlo con il parametro .init. È il caso, ad esempio, se voglio accumulare gli elementi in una lista:
in cui l’accumulatore è inizialmente una lista vuota (list()) alla quale aggiungo di volta in volta 2 elevato a ogni elemento del vettore iniziale (1:4), con il nome pari alla i-esima lettera minuscola (con offset 1, letters[elem + 1]). A differenza degli esempi precedenti, questo non è semplificabile con una funzione vettorializzata.
6 File In/Out
Ci sono numerose funzioni per importare ed esportare dati su file, in differenti formati. Il metodo più semplice e comune è usando il formato CSV e le funzioni read_csv() e write_csv() (fornite da tidyverse).
Per salvare una tabella su un file:
Se si usa il formato italiano/francese/spagnolo (celle separate da ; e decimali separati da ,), sostituire write_csv() con write_csv2().
Per leggere un file:
Analogamente, usare read_csv2() per il formato delle lingue romanze.