Abbiamo visto che R dispone di vari tipi di dato: vettori numerici, vettori booleani, vettori di stringhe, matrici, array (anch'essi divisi in numerici, booleani e stringhe), liste, data frames, funzioni, etc... In realtà, in R vi sono due punti di vista per quanto riguarda i tipi degli oggetti: uno a basso livello ed uno ad alto livello. L'elenco appena fatto corrisponde ad una visione ad alto livello dei tipi di dato. In realtà, questa ricchezza è ottenuta a partire da un sistema di tipi a basso di livello molto ristretto, con l'uso degli attributi.
Dal punto di vista a più basso livello, in R esistono fondamentalmente i seguenti tipi di dato:
numeric
: vettori numerici;logical
: vettori booleani;character
: vettori di stringhe;list
: liste;function
: funzioniPer sapere il tipo di dato di un oggetto si può usare la funzione mode
. Ad esempio:
> ? mode > x=c(1,2,3,4) > mode(x) [1] "numeric" > mode(log) [1] "function" > m=matrix(1:20,nrow=10) > mode(m) [1] "numeric" > l=list(a=c(1,2),b=c(1,3)) > mode(l) [1] "list" > df=data.frame(a=c(1,2),b=c(1,3)) > mode(df) [1] "list"
Notare come il modo di una matrice di numeri è numeric
, esattamente come un vettore numerico. Inoltre, il modo per un data frame è esattamente lo stesso del modo per una lista.
Tutto questo avviene perché, internamente, data frame e liste (e anche vettori e matrici numeriche) sono memorizzati e trattati esattamente con le stesse strutture dati. Quello che differenzia due tipi diversi che hanno lo stesso modo sono i loro attributi.
Ad ogni oggetto in R sono associati uno o più attributi. Per manipolare gli attributi abbiamo le seguenti funzioni:
attributes(obj)
: restituisce una lista con gli attributi di obj;attributes(obj) = value
: modifica la lista degli attributi di obj;attr(x,which)
: restituisce l'attributo which di x;attr(x,which) = value
: modifica l'attributo which di x.Normalmente un vettore (di qualunque modo) non ha attributi. Continuando l'esempio precedente:
> attributes(x) NULL
La cosa cambia per matrici e array:
> attributes(m) $dim [1] 10 2 > a=array(1:20, dim=c(2,5,2)) > attributes(a) $dim [1] 2 5 2
Entrambi questi tipi hanno un attributo dim
, un vettore numerico con la dimensione dell'array (da cui si capisce che m è un verrore 10x2 e a un array 2x5x2). La presenza di questo attributo è l'unica cosa che distingue i vettori dagli array o dalle matrici, tant'è che è possibile passare da un tipo all'altro semplicemente eliminando o aggiungendo un tale attributo.
> x [1] 1 2 3 4 > attr(x,"dim") = c(2,2) > x [,1] [,2] [1,] 1 3 [2,] 2 4 > attr(x,"dim") = NULL > x [1] 1 2 3 4
In generale, esistono alcuni attributi che hanno un significato speciale per R, e altri che invece non hanno alcun significato particolare e possiamo usare per i nostri scopi. Oltre a dim
, un altro attributo che ha un significato per R è names
, un vettore destinato a contenere i nomi delle posizioni.
> attr(x,"names")=c("A","B","C","D") > x A B C D 1 2 3 4 > attr(x,"giuseppe")=12 > x A B C D 1 2 3 4 attr(,"giuseppe") [1] 12
Gli attributi più utilizzati, come dim
e names
, hanno di solito delle funzioni associate che consentono di accedervi direttamente. Così, attr(x,"dim")
è esattamente equivalente a dim(x)
e attr(x,"names")
è esattamente equivalente a names(x)
(quest'ultimo, tra l'altro, l'avevamo già incontrato nella lezione sui vettori).
Così come mode
restituisce il tipo a basso livello di un oggetto, class
restituisce il tipo ad alto livello. Ad esempio:
> class(x) [1] "numeric" > class(m) [1] "matrix" > class(log) [1] "function" > class(l) [1] "list" > class(df) [1] "data.frame"
Notare che adesso matrici e data.frame hanno un valore di classe diverso da vettori e liste. Che cosa determina la classe di un oggetto? Se un oggetto ha uno specifico attributo class
, la classe sarà il valore di quell'attributo. Infatti:
> attributes(l) $names [1] "a" "b" > attributes(df) $names [1] "a" "b" $row.names [1] 1 2 $class [1] "data.frame"
Se invece non è presente l'attributo class
, il valore restituito dalla funzione class
corrisponde in generale con il modo, tranne nel caso il modo sia uno tra numeric, logical e character ed è presente l'attributo dim
. In quest'ultimo caso la classe sarà matrix
se dim
ha lunghezza 2, array
altrimenti.
Il valore di classe di un oggetto determina come le varie funzioni di R si comportano su quell'oggetto. Ad esempio, la funzione print
è utilizzata per visualizzare un oggetto, ed è quella chiamata direttamente da R alla fine della valutazione di una espressione. Liste e data frame vengono visualizzati da print in maniera completamente differente.
> l $a [1] 1 2 $b [1] 1 3 > df a b 1 1 1 2 2 3 >
La funzione unclass
restituisce l'oggetto passato come input dopo aver rimosso l'attributo class
. Questo è usato talvolta per vedere la struttura di lista di un oggetto di una classe complessa. Ad esempio:
> unclass(df) $a [1] 1 2 $b [1] 1 3 attr(,"row.names") [1] 1 2
Vedremo nelle prossime lezioni dei casi in cui può tornare utile.
Alcune funzioni di R sono funzioni "generiche", nel senso che si applicano in maniera different a oggeti di classi differenti. Ad esempio, la funzione print
che visualizza un oggetto sullo schermo, è una di esse. Sebbene un data frame sia solo un tipo particolare di lista, la visualizzazione di un data frame avviene in forma tabellare, una lista no.
Queste funzioni generiche sono normalmente definite utilizzando la funzione ausiliare UseMethod
:
> print function (x, ...) UseMethod("print") <environment: namespace:base>
La chiamata UseMethod("print")
cerca se esiste una funzione dal nome print.class
dove class è la classe di x
. Se la trova la esegue, passando x come parametro. Altrimenti esegue la funzione print.default
.
> print(df) a b 1 1 1 2 2 3 > print.data.frame(df) a b 1 1 1 2 2 3 > print.default(df) $a [1] 1 2 $b [1] 1 3 attr(,"class") [1] "data.frame"
Notare l'uso di print.default
per utilizzare la routine di stampa di default, invece di quella specifica per i data frame.
Per conoscere tutte le classi per cui esiste una implementazione specifica di una funzione generica si può usare la funzione methods
:
> methods(print) [1] print.acf* [2] print.anova [3] print.aov* [4] print.aovlist* omissis [42] print.data.frame [43] print.Date [44] print.default omissis
Alcune funzioni specifiche sono nascoste, ovvero non possono essere chiamate esplicitamente ma solo passando dalla funzione generica. In tal caso sono asteriscate nell'output di methods.
Altri esempi di funzioni generiche sono plot
e summary
.