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 R che rappresenti delle carte da gioco. Successivamente, cercheremo di lavorare con oggetti sempre più complessi fino ad arrivare a una “tabella” di dati. In breve, costruiremo l’equivalente di un foglio di Excel. Quando avremo finito, il nostro mazzo di carte somiglierà a questo:

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:

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"

## [1] TRUE
## [1] TRUE
## [1] TRUE

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):

##      [,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"
##      [,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:

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.

Exercise 3.1 (Use a List to Make a Card) Use a list to store a single playing card, like the ace of hearts, which has a point value of one. The list should save the face of the card, the suit, and the point value in separate elements.
Solution. You can create your card like this. In the following example, the first element of the list is a character vector (of length 1). The second element is also a character vector, and the third element is a numeric vector:

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.

Data frames store data as a sequence of columns. Each column can be a different data type. Every column in a data frame must be the same length.

Figure 3.1: Data frames store data as a sequence of columns. Each column can be a different data type. Every column in a data frame must be the same length.

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:

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")

The names will be stored in the object’s 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:

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.