Chapter 3 Oggetti in R
3.1 Creazione di un dado virtuale
Ora che sappiamo come usare la console in RStudio, proviamo a svolgere un piccolo esercizio: creiamo un dado virtuale.
Se ben ricordate, nella sezione precedente abbiamo introdotto l’operatore :
. Questo operatore ritorna come risultato un vettore. Un vettore è un insieme unidimensionale di elementi (interi, stringhe, ecc…).
Per creare un dado virtuale eseguiamo il comando
## [1] 1 2 3 4 5 6
ma ciò non è sufficiente poichè il vettore creato è stato salvato, solo temporaneamente, nella RAM e non è più possibile recuperarlo.
Per rendere il ‘dado’ un oggetto di R, successivamente richiamabile, dobbiamo salvarlo nell’ambiente globale (Global Environment in RStudio) assegnando il risultato dell’operazione 1:6
a una variabile, tramite l’operatore di assegnazione <-
.
Decidiamo di chiamare la nostra variabile ‘dado’ con il nome ‘dado’:
## [1] 1 2 3 4 5 6
In R l’operatore di assegnazione <-
è uguale all’operatore =
ma, quest ultimo può essere usato solo nel codice top-level. Si consiglia pertanto l’uso di <-
. Esistono anche l’operatore di assegnazione inversa (da destra verso sinistra) ->
e gli operatori di assegnazione globale ->>
e <<-
che possono tornare utili quando si vuole modificare il valore di una variabile che risiede nell’ambiente di livello superiore (globale) a quello in cui stiamo lavorando (locale).
Per approfondimenti: https://stat.ethz.ch/R-manual/R-devel/library/base/html/assignOps.html
Se l’oggetto dado
è stato creato con successo lo potremo trovare nel pannello ‘Global Environemnt’ di RStudio come indicato nella figura seguente:
- pannello Global Environment: contiene tutti gli oggetti creati nell’attuale sessione di R. Quando la sessione viene terminata, l’ambiente viene ‘svuotato’.
[1] Regole per la scelta dei nomi delle variabili
In R, le variabili possono avere qualsiasi nome che non inizi per un numero e non contenga i seguenti caratteri: ^
, !
, $
, @
, +
, -
, *
o /
.
Attenzione: R è case sensitive, ciò significa che la variabile dado
è diversa da Dado
.
3.2 Operazioni element-wise
Ora che la variabile dado
è stata creata possiamo eseguire delle operazioni su di essa.
Ecco alcuni esempi:
## [1] 0 1 2 3 4 5
## [1] 0.5 1.0 1.5 2.0 2.5 3.0
## [1] 1 4 9 16 25 36
Come si può notare dai risultati, quando si manipola un set di dati come può essere un vettore, R non segue le regole dell’algebra lineare, bensì, esegue le operazioni elemento per elemento (element-wise).
Cosa succede se si effettuano operazioni tra due vettori di lunghezza diversa?
Provate a eseguire i seguenti comandi:
1:7 * 1:6
1:12 * 1:6
1:6 * 1:12
## Warning in 1:7 * 1:6: longer object length is not a
## multiple of shorter object length
## [1] 1 4 9 16 25 36 7
## [1] 1 4 9 16 25 36 7 16 27 40 55 72
## [1] 1 4 9 16 25 36 7 16 27 40 55 72
R, prima di eseguire un’operazione tra set di dati, confronta le dimensioni dei set ed esegue quello che si chiama broadcasting della funzione tra gli oggetti. Se le dimensioni dei set sono compatibili (ad esempio una è un multiplo dell’altra), R adatta uno dei duoi elementi alla dimensione dell’altro ed esegue l’operazione che ritiene più logica (ad esempio ripete il vettore più corto fino a raggiungere la lunghezza del vettore più lungo e moltiplica i vettori ridimensionati).
3.3 Tipi
In questo capitolo, useremo R per assemblare un mazzo di 52 carte.
Inizieremo costruendo un semplice oggetto di
figura seme valore
re picche 13
regina picche 12
jack picche 11
dadoci picche 10
nove picche 9
otto picche 8
...
E’ sempre necessario inserire i dati a mano in R?
Ovviamente no!
Nella maggiorparte dei casi, i dati vengono importati tramite delle semplici procedure, vedi [Importazione]. Questo esercizio ci servirà per capire come R salva i dati in memoria e come possiamo assemblare e disassemblare il nostro dataset.
- Vettori atomici
L’oggetto più semplice in R è chiamato vettore atomico ( atomic vector ). I vettori atomici non si incontrano spesso nella pratica, ma molti oggetti di R sono costruiti a partire da essi.
Un vettore atomico è semplicemente un vettore di dati. Ad esempio l’oggetto dado
che abbiamo creato precedentemente è proprio un vettore atomico.
Si può creare un vettore atomico raggruppando dei valori attraverso la funzione c()
:
## [1] 1 2 3 4 5 6
## [1] TRUE
is.vector()
La funzione is.vector()
testa se un oggetto è un vettore atomico. Ritorna TRUE
se l/’oggetto è un vettore atomico e FALSE
altrimenti.
La funzione c()
può essere usata anche per concatenare vettori:
## [1] 1 2 3 4 5 6 1 2 3 4 5 6
Si possono inoltre creare vettore atomici con solo un elemento:
## [1] 5
## [1] TRUE
## [1] 1
length()
La funzione length()
ritorna la lunghezza di un vettore atomico.
Un vettore atomico può solo contenere elementi che possiedono lo stesso tipo di dato.
In R esistono 6 tipi di vettore atomici: double, integer, character, logical, complex, and raw (tralasceremo questi ultimi due).
Per creare il nostro mazzo di carte, avremo bisogno di usare diversi tipi di vettori atomici per salvare informazioni diverse (figura e seme: testo, valori: numeri).
Esistono delle convenzioni per comunicare ad R il tipo di vettore atomico che stiamo creando. Ad esempio, se vogliamo creare un vettore di interi basterà mettere una L
maiuscola dopo ogni numero, per creare invece un vettore di stringhe il testo andrà racchiuso nei doppi apici ""
:
Perché R deve assegnare un tipo a un vettore atomico?
Ogni funzione implementata in R si può comportare diversamente (metodo) a seconda del tipo di dato in input. Ad esempio non si possono compiere certe operazioni matematiche tra vettori di stringhe.
## [1] 6
## Error in sum(stringhe): invalid 'type' (character) of argument
- double
Un vettore double contiene valori numerici. Tali valori possono essere positivi o negativi, grandi o piccoli, avere o meno dei decimali. Se non specificato diversamente, R tratta tutti i valori numerici come double. Ad esempio, il dado che abbiamo creato precedentemente era un vettore double:
## [1] TRUE
Per conoscere il tipo di un oggetto salvato nell’ambiente globale si può usare la funzione typeof()
.
Determinare il tipo dei seguenti oggetti:
dado
stringhe
interi
Il termine “double” si riferisce al numero di byte necessari per salvare il numero nella memoria. Alcune funzioni in R fanno riferimento ai double come numerics. Anche in questo corso useremo i termini “double” e “numerics” in modo intercambiabile.
- integer
I vettori integer contengono valori numerici interi, vale a dire numeri che si possono scirvere senza valori decimali. Solitamente, questo tipo di vettore viene usato per immagazzinare indici od oggetti specifici che non potrebbero essere considerati come double. Generalmente, i vettori numerici vengono salvati come oggetto double.
Per creare un vettore di interi, come già anticipato, basta postporre al numero intero la lettera L
maiuscola.
## [1] -1 2 4
## [1] TRUE
La differenza tra tipo double e tipo integer sta nel numero di byte utilizzati per salvare il numero in memoria. R salva gli oggetti double in uno spazio di 16 bytes, questo significa che i numeri che hanno decimali significativi anche dopo il sedicesimo decimale vengono arrotondati. La costante \(\pi\) è un esempio.
A volte, questo genere di arrotondamento crea delle incongruenze nei calcoli matematici. Gli errori di questo tipo vengono chiamati floating point error o numerical error. Eccone un esempio:
## [1] 4.441e-16
Il risultato corretto della precedente operazione sarebbe 0 ma R non è in grado di salvare l’oggetto sqrt(2)
con sufficienti cifre decimali. Perciò il calcolo non è eseguito precisamente.
- character
Un vettore di character contiene testi brevi. Si può creare un vettore di character, come già anticipato, racchiudendo il testo tra doppi apici.
## [1] "Hello" "World"
## [1] TRUE
Gli elementi singoli di un vettore character sono chiamati stringhe.
Si noti che una stringa può contenere caratteri alfanumerici o simboli.
Determinare se i seguenti oggetti sono stringhe o numeri:
1
"1"
"uno"
E/’ necessario scrivere le stringhe all’interno dei doppi apici, altrimenti, R andrebbe a cercare una variabile con nome uguale alla stringa digitata e molto probabilmente ritornerebbe un errore in quanto la variabile non esisterebbe.
- logical
I vettori logical contengono elementi TRUE
o FALSE
. I vettori logici sono molto utili per fare confronti:
## [1] FALSE
R assume che T
e F
siano le abbreviazioni di TRUE
e FALSE
, a meno che non vengano ridefinite da qualche altra parte (e.g. T <- 500
).
## [1] TRUE FALSE TRUE
## [1] TRUE
Creare un vettore atomico che rappresenti le figure che posso avere in una mano di texas hold’em.
Che tipo di vettore bisogna usare per salvare la mia mano?
Un vettore di caratteri è il vettore più appropriato per salvare le figure delle carte. Possiamo crearlo attraverso la funzione c
:
## [1] "asso" "re" "regina" "jack" "dieci"
Ben fatto!
Ora creiamo una struttura dati più sofisticata: una tabella a due dimensioni con le figure e i semi delle carte. Si può costruire un oggetto più sofisticato a partire da un vettore atomico, assegnandogli una classe ( class ) e degli attributi ( attribute ).
3.4 Attributi
Un attributo ( attribute ) è un segmento di informazione che si può annettere a un vettore atomico (o a qualsiasi altro oggetto di R). L’attributo non modificherà i valori nell’oggetto e non apparirà quando l’oggetto verrà mostrato nell’output della console. Un attributo è un metadata, o più comunemente un’etichetta, ovvero un luogo adatto a contenere informazioni associate all’oggetto. Alcune funzioni possono usare gli attributi di un oggetto per eseguire specifiche azioni.
Si possono vedere gli attributi di un oggetto usando la funzione attributes()
. Tale funzione ritornerà NULL
se l’oggetto non possiede attributi. Un vettore atomico come dado
non ha nessun attributo a meno che non glielo si assegni:
## NULL
NULL
R usa NULL
per rappresentare l’insieme vuoto, un oggetto vuoto. Si può creare un oggetto NULL
assegnandogli il valore NULL
, scritto in maiuscolo.
## NULL
Per assegnare un attributo personalizzato a un oggetto si può usare la funzione attr(oggetto, 'nome_attributo')
.
Ad esempio assegnamo all’oggetto ‘dado’ il suo numero di facce:
## [1] 6
3.4.1 Nomi e dimensioni
Gli attributi più comuni che si possono assegnare a un vettore atomico sono i nomi ( names ) e le dimensioni ( dim ).
Ad esempio, possiamo assegnare dei nomi al nostro dado
## [1] "uno" "due" "tre" "quattro" "cinque"
## [6] "sei"
## $n_facce
## [1] 6
##
## $names
## [1] "uno" "due" "tre" "quattro" "cinque"
## [6] "sei"
R mostrerà i nomi del vettore atomico quando lo si richiamerà nella console:
## uno due tre quattro cinque sei
## 1 2 3 4 5 6
## attr(,"n_facce")
## [1] 6
L’attributo dim trasforma il vettore atomico in un array n-dimensionale. Per usare questa funzionalità dobbiamo assegnare all’attributo dim dell’oggetto che vogliamo trasformare (input) un vettore atomico di lunghezza n contenente i valori numerici che identificano le dimensioni dell’oggetto trasformato (output). R riorganizzerà gli elementi per colonna. Vediamo in pratica come funziona. Ristrutturiamo il nostro dado
in un array di n=2 dimensioni attraverso un vettore di dimensioni c(2,3)
(i.e. in una matrice 2 per 3):
dado.arr2d <- dado #non perdiamo l'oggetto originale
dim(dado.arr2d) <- c(2, 3) #equivalentemente avremmo potuto usare array(dado, dim=c(2,3))
dado.arr2d
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
## attr(,"n_facce")
## [1] 6
Abbiamo ottenuto così una matrice!
Un altro modo per creare una matrice a partire da un vettore è quello di usare la funzione matrix(oggetto, nrow=r, ncol=c)
dove oggetto
è un vettore atomico e r
e c
sono valori interi. L’equivalente della precedente operazione è:
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
## [,1] [,2] [,3]
## [1,] TRUE TRUE TRUE
## [2,] TRUE TRUE TRUE
La funzione matrix()
riempie la matrice colonna per colonna. Per eseguire l’operazione inversa (riga per riga) bisogna impostare il parametro byrow = TRUE
:
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
parametri di una funzione
La funzione matrix()
fa parte del pacchetto base di R. Come tutte le funzioni approvate dal CRAN, anche matrix()
ha una sua documentazione in cui si possono trovare tutti i parametri modificabili. La documentazione di una matrice è accessibile attraverso il comando ?
seguito dal nome della funzione di cui si stanno cercando informazioni. Faremo un po’ di pratica nella navigazione della documentazione delle funzioni successivamente [funcdoc].
Creare la seguente matrice usando uno dei metodi precedentemente elencati
## [,1] [,2]
## [1,] "asso" "picche"
## [2,] "re" "picche"
## [3,] "regina" "picche"
## [4,] "jack" "picche"
## [5,] "dieci" "picche"
figura <- c("asso", "re", "regina", "jack", "dieci")
seme <- c("picche", "picche", "picche", "picche", "picche" )
figseme.mat<-array(c(figura, seme), dim=c(5,2))
figseme.mat
## [,1] [,2]
## [1,] "asso" "picche"
## [2,] "re" "picche"
## [3,] "regina" "picche"
## [4,] "jack" "picche"
## [5,] "dieci" "picche"
3.4.2 Fattori [#factors]
I fattori ( factor ) sono la modalità attraverso il quale R salva in memoria i dati di tipo categorico, come l’etnia o il colore degli occhi. I fattori, a differenza delle variabili character, hanno la particolarità di essere salvate con un certo ordine. Ad esempio, per il genere, F < M. Questo ordinamento rende più agevole la gestione di variabili come il livello di trattamento nelle analisi biostatistiche.
Per convertire un vettore character in un vectore factor si utilizza la funzione, appunto, factor()
. R ricodificherà la variabile in un vettore di interi, uno per ogni modalità del fattore. Tali modalità vengono chiamati livelli ( levels ) e non sono altro che attributi del vettore appena creato. I livelli contengono le etichette testuali di ogni modalità.
## Error in typeof(gender): oggetto "gender" non trovato
## Error in eval(expr, envir, enclos): oggetto "gender" non trovato
Le classi ( class ) sono degli attributi che non affronteremo in questo corso. In breve, servono a dare una categoria all’oggetto a cui vengono assegnate. Ad esempio, il vettore genere
appartiene alla classe factor
.
Si può riconvertire un vettore factor in un vettore character attraverso la funzione as.character()
.
## Error in eval(expr, envir, enclos): oggetto "gender" non trovato
Ora che abbiamo capito quali sono le possibilità che i vettori atomici di R ci forniscono possiamo creare una carta da gioco più complicata.
Molti giochi di carte assegnano un valore numerico a ogni carta. Per esempio, nel blackjack, ogni figura vale 10 punti, ogni carta non vestita vale dai 2 ai 10 punti, e ogni asso vale 1 o 11 punti, a seconda del punteggio finale.
Crea una carta virtuale che combini “asso”, “cuori” e il valore 1 in un vettore. Che tipo di vettore atomico risulterà?
Non è possibile combinare tali informazioni in un vettore atomico poichè, come abbiamo già detto, esso può contenere elementi di un solo tipo. “asso” è una stringa mentre 1 è un valore numerico.
## [1] "asso" "cuori" "1"
R adotterà la tecnica di coercion e renderà 1 un “1”.
3.5 Coercion
Se si prova a inserire in un vettore elementi di tipo diverso, R effettua quella che si chiama coercion degli elementi non conformi, seguendo una lista di priorità. In cima alla lista c’è il tipo ‘carattere’, quindi, se c’è almeno un elemento di tipo carattere in un vettore, gli altri elementi vengono convertiti in carattere a loro volta.
R usa le stesse regole di coercion anche se il tipo di variabile usata in input in una funzione non è compatibile con la funzione stessa. Ad esempio, se si prova a sommare un vettore di elementi logici, il vettore verrà prima convertito in interi 0 e 1 e successivamente viene eseguita la somma degli elementi convertiti.
## [1] 2
diventerà:
## [1] 2
3.6 Conversione
Per evitare di incorrere in conversioni errate, si può preventivamente cambiare il tipo di un oggetto attraverso la funzione as.type()
dove ‘type’ corrisponde al tipo desiderato.
Esempi:
## [1] "1"
## [1] TRUE
## [1] 0
Molti dataset contengono diversi tipi di informazione. L’impossibilità di inserire informazioni di tipo diverso in un vettore, in una matrice o in un array n-dimensionale sembra una limitazione importante. Quindi perchè dobbiamo usarli?
In molti casi usare un solo tipo di dati è un grande vantaggio. Vettori, matrici e array rendono molto semplici e veloci le operazioni matematiche tra insiemi di grandi dimensioni.
In altri casi, invece, può essere uno svantaggio. In queste situazioni ricorreremo alle liste.
3.7 Liste
Le liste ( list ) sono come dei vettori atomici perchè raggruppano i dati in un insieme. Tuttavia, le liste non raggruppano insieme oggetti singoli ma oggetti di R come vettori atomici o altre liste. Per sempio, si può creare una lista che contenga un vettore numerico di lunghezza 31 come il primo elemento, un vettore di caratteri di lunghezza 2 come secondo elemento e così via.
Per creare una lista dobbiamo usare la funzione list()`.
list
creates a list the same way c
creates a vector. Separate each element in the list with a comma:
list1 <- list(100:130, "R", list(TRUE, FALSE))
list1
[1] [[1]]
[1] [1] 100 101 102 103 104 105 106 107 108 109 110 111 112
[1] [14] 113 114 115 116 117 118 119 120 121 122 123 124 125
[1] [27] 126 127 128 129 130
[1]
[1] [[2]]
[1] [1] "R"
[1]
[1] [[3]]
[1] [[3]][[1]]
[1] [1] TRUE
[1]
[1] [[3]][[2]]
[1] [1] FALSE
I left the [1]
notation in the output so you can see how it changes for lists. The double-bracketed indexes tell you which element of the list is being displayed. The single-bracket indexes tell you which subelement of an element is being displayed. For example, 100
is the first subelement of the first element in the list. "R"
is the first sub-element of the second element. This two-system notation arises because each element of a list can be any R object, including a new vector (or list) with its own indexes.
Lists are a basic type of object in R, on par with vettori atomici. Like vettori atomici, they are used as building blocks to create many more spohisticated types of R objects.
As you can imagine, the structure of lists can become quite complicated, but this flexibility makes lists a useful all-purpose storage tool in R: you can group together anything with a list.
However, not every list needs to be complicated. You can store a playing card in a very simple list.
card <- list("ace", "hearts", 1)
card
[1] [[1]]
[1] [1] "ace"
[1]
[1] [[2]]
[1] [1] "hearts"
[1]
[1] [[3]]
[1] [1] 1
You can also use a list to store a whole deck of playing cards. Since you can save a single playing card as a list, you can save a deck of playing cards as a list of 52 sublists (one for each card). But let’s not botherâthere’s a much cleaner way to do the same thing. You can use a special class of list, known as a data frame.–>
[1] Data Frames
Data frames are the two-dimensional version of a list. They are far and away the most useful storage structure for data analysis, and they provide an ideal way to store an entire deck of cards. You can think of a data frame as R’s equivalent to the Excel spreadsheet because it stores data in a similar format.
Data frames group vectors together into a two-dimensional table. Each vector becomes a column in the table. As a result, each column of a data frame can contain a different type of data; but within a column, every cell must be the same type of data, as in Figure 3.1.
Creating a data frame by hand takes a lot of typing, but you can do it (if you like) with the data.frame
function. Give data.frame
any number of vectors, each separated with a comma. Each vector should be set equal to a name that describes the vector. data.frame
will turn each vector into a column of the new data frame:
df <- data.frame(face = c("ace", "two", "six"),
suit = c("clubs", "clubs", "clubs"), value = c(1, 2, 3))
df
[1] face suit value
[1] ace clubs 1
[1] two clubs 2
[1] six clubs 3
You’ll need to make sure that each vector is the same length (or can be made so with R’s recycling rules; see Figure ??, as data frames cannot combine columns of different lengths.
In the previous code, I named the arguments in data.frame
face
, suit
, and value
, but you can name the arguments whatever you like. data.frame
will use your argument names to label the columns of the data frame.
Names
You can also give names to a list or vector when you create one of these objects. Use the same syntax as with data.frame
:
list(face = "ace", suit = "hearts", value = 1)
c(face = "ace", suit = "hearts", value = "one")
names
attribute.
If you look at the type of a data frame, you will see that it is a list. In fact, each data frame is a list with class data.frame
. You can see what types of objects are grouped together by a list (or data frame) with the str
function:
typeof(df)
[1] "list"
class(df)
[1] "data.frame"
str(df)
[1] 'data.frame': 3 obs. of 3 variables:
[1] $ face : Factor w/ 3 levels "ace","six","two": 1 3 2
[1] $ suit : Factor w/ 1 level "clubs": 1 1 1
[1] $ value: num 1 2 3
Notice that R saved your character strings as factors. I told you that R likes factors! It is not a very big deal here, but you can prevent this behavior by adding the argument stringsAsFactors = FALSE
to data.frame
:
3.8 Funzioni e documentazione
Ovviamente, in R, è possibile trovare una grandissima quantità di funzioni matematiche, probabilistiche e statistiche. Non sarà perciò possibile affrontare
Per calcolare il prodotto tra due vettori in modo algebrico, bisogna utilizzare l’operatore %*%
.
Alcuni esempi di operatori algebrici implementati in R sono il determinante det
, la trasposizione t()
ma ce ne sono molti altri.
Provare a eseguire alcune delle operazioni algebriche tra vettori che sono state citate precedentemente.