Introducción a R - Clases de objetos


Distintas clases de tablas en R

Vamos a aprovechar a conocer un poco más de cómo trabajar en R con distinto tipo de datos. Sigamos con el archivo del ejercicio anterior.

Si no lo tienen, descarguen el archivo: ensembl_info.tab

ensTab = read.table("ensembl_info.tab")
ensTab = ensTab[order(ensTab[,"genome_length"]),]

Las variables en R se llaman objetos. Cada objeto tiene cierta clase y conocer su clase nos permite saber qué cosas podemos hacer con él. Veamos la clase del objeto donde tenemos los datos de los genomas:

class(ensTab)
## [1] "data.frame"

Un objeto de clase data.frame es una tabla (con renglones y columnas), pero donde R nos permite tener distintos tipos de datos en cada columna. Por ejemplo, en una columna podemos tener texto y en otra columna números. Es por ello que hay ciertas operaciones que NO podemos hacer con un data.frame. Por ejemplo, calcular el promedio:

mean(ensTab)
## Warning in mean.default(ensTab): argument is not numeric or logical:
## returning NA
## [1] NA

Sin embargo, muchas veces sabemos que dentro de un data.frame tenemos solamente datos de un mismo tipo. Por ejemplo, preparemos una tabla que tiene solamente la longitud de los tres tipos de genes:

genesTab = ensTab[,c("coding_genes","ncrna_genes","pseudo_genes")]
head(genesTab)
##              coding_genes ncrna_genes pseudo_genes
## Anole_lizard        18595        7168          157
## C_elegans           20362         922         1658
## Cat                 19493        1855          542
## Chicken             18346        6490           43
## Chimpanzee          18759        8681          572
## Cow                 19994        3825          797
class(genesTab)
## [1] "data.frame"

En este caso, podría ser muy razonable querer calcular el promedio. Para ello debemos convertir el objeto a una clase de tabla donde R sabe que todos los datos son de un mismo tipo. Dicha clase es una matrix (matriz). R cuenta con una variedad de funciones para convertir objetos entre distintas clases, en este caso:

genesMat = as.matrix(genesTab)
head(genesMat)
##              coding_genes ncrna_genes pseudo_genes
## Anole_lizard        18595        7168          157
## C_elegans           20362         922         1658
## Cat                 19493        1855          542
## Chicken             18346        6490           43
## Chimpanzee          18759        8681          572
## Cow                 19994        3825          797
class(genesMat)
## [1] "matrix"

Aunque los datos parecen no haber cambiado, internamente R nos va a permitir ahora hacer cualquier cálculo que requiera de objetos con contenido exclusivamente numérico:

mean(genesMat)
## [1] 8816.373
sd(genesMat)
## [1] 8566.046

No solamente son operaciones matemáticas las que pueden estar restringidas con un data.frame. La función de barplot es otra que simplemente no funciona. Verán que intentar una instrucción como la que sigue marca un error:

barplot(genesTab)
## Error in barplot.default(genesTab): 'height' must be a vector or a matrix

Sin embargo, con el objeto convertido a matriz podemos trabajar sin problema:

barplot(genesMat, beside=TRUE)

Aquí vemos que el barplot por default nos agrupa los datos por columna (es decir, nos genera 3 grupos de barras porque hay 3 columnas). Qué podemos hacer si lo que queremos es un grupo por especie (por renglón)?

Lo más simple es girar o transponer la matriz, y eso lo logramos con la función t.

barplot(t(genesMat), horiz=TRUE, las=1)

Nota: el parámetro las viene de label-style y permite configurar la orientación de las leyendas de los ejes (para que sean paralelos o perpendiculares a los ejes, o siempre horizontales o verticales). Acepta valores entre 0 y 3. La ayuda para este tipo de parámetros se encuentra en ?par.


Vectores con datos numéricos o texto

Algunas otras conversiones típicas son para convertir entre números y texto. Un breve ejemplo:

x = 1:15
x
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
class(x)
## [1] "integer"
y = as.character(x)
y
##  [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10" "11" "12" "13" "14"
## [15] "15"
class(y)
## [1] "character"
sort(y)
##  [1] "1"  "10" "11" "12" "13" "14" "15" "2"  "3"  "4"  "5"  "6"  "7"  "8" 
## [15] "9"
z = as.integer(y)
z
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
class(z)
## [1] "integer"


Listas

Hasta ahora hemos visto dentro de R objetos que tienen bastantes restricciones. Los vectores solo pueden contener un mismo tipo de datos, las matrices también. Los data.frame tienen más flexibilidad, pero solo sirven cuando tenemos datos con un número preciso de renglones y columnas. Hay una clase de objeto en R que nos da muchísima más flexibilidad y se le llama list (lista). Se parece a un vector en el sentido que tiene datos guardados en un orden lineal (es decir, hay algo guardado en la posición 1, otra cosa en la posición 2, etc). Sin embargo, en cada posición de una lista podemos guardar cualquier cosa: un número, un texto, inclusive una tabla o una lista nueva!

Veamos un ejemplo simple:

genomeList = list("Human genome", 3.09e9, c(22097,15502,16146), c("44.7%", "7.32%", "1.96%"))
genomeList
## [[1]]
## [1] "Human genome"
## 
## [[2]]
## [1] 3.09e+09
## 
## [[3]]
## [1] 22097 15502 16146
## 
## [[4]]
## [1] "44.7%" "7.32%" "1.96%"

Un elemento que nos ayuda a distinguir el contenido de una lista es que los sub-elementos son extraidos con dobles corchetes: [[ ]]. Es por ello que para obtener el segundo elemento de la lista, necesitamos hacerlo así:

genomeList[[2]]
## [1] 3.09e+09

También podemos continuar las extracciones, para situaciones donde queremos extraer algo que está adentro de una cierta posición en la lista. Por ejemplo:

genomeList[[3]]
## [1] 22097 15502 16146
genomeList[[3]][2]
## [1] 15502

Y por supuesto, los elementos de la lista también pueden tener etiquetas o nombres:

names(genomeList) = c("species", "genome_length", "gene_numbers", "gene_length_percentages")
genomeList
## $species
## [1] "Human genome"
## 
## $genome_length
## [1] 3.09e+09
## 
## $gene_numbers
## [1] 22097 15502 16146
## 
## $gene_length_percentages
## [1] "44.7%" "7.32%" "1.96%"


Ejercicio:

  • Pueden ponerle los nombres de “coding_genes”, “ncrna_genes” y “pseudo_genes” a los elementos 3 y 4 de la lista?
## $species
## [1] "Human genome"
## 
## $genome_length
## [1] 3.09e+09
## 
## $gene_numbers
## coding_genes  ncrna_genes pseudo_genes 
##        22097        15502        16146 
## 
## $gene_length_percentages
## coding_genes  ncrna_genes pseudo_genes 
##      "44.7%"      "7.32%"      "1.96%"


Ya con los nombres, R nos está sugiriendo una manera extra de poder accesar a los elementos de algunos objetos. Pueden adivinar cuál es? En el caso de una lista, hay tres maneras de accesar uno de los elementos:

genomeList[[2]]
## [1] 3.09e+09
genomeList[["genome_length"]]
## [1] 3.09e+09
genomeList$genome_length
## [1] 3.09e+09

De hecho, otros objetos como los data.frame nos dejan usar un símbolo de $ para acceder a una de las columnas. Sin embargo, esto no funciona para una matrix.

genesTab$coding_genes
##  [1] 18595 20362 19493 18346 18759 19994 19856 16550 15634 20033 13918
## [12] 18523 20962 20449 22097 19728 22176 19343 21605 21698 22258 20787
## [23] 18788 18442 25658
genesMat$coding_genes
## Error in genesMat$coding_genes: $ operator is invalid for atomic vectors

Ante la duda, no duden en experimentar. Y recuerden que todas las maneras válidas son igual de correctas, así que usen la sintaxis que recuerden con mayor facilidad.


Avanzado

Así como existe la función apply para aplicar una función sobre los renglones o columnas de una tabla, existen versiones para otras clases de objetos como las listas. Un par de ejempos:

lapply(genomeList, length)
## $species
## [1] 1
## 
## $genome_length
## [1] 1
## 
## $gene_numbers
## [1] 3
## 
## $gene_length_percentages
## [1] 3
sapply(genomeList, class)
##                 species           genome_length            gene_numbers 
##             "character"               "numeric"               "numeric" 
## gene_length_percentages 
##             "character"

La versión lapply (list apply) toma una lista y regresa su resultado en forma de lista. La versión sapply es igual pero intenta simplificar el resultado cuando sea posible, en general regresando una tabla o un vector en lugar de una lista.


Ejercicio final

En la práctica anterior usaron un ciclo for para hacer una gráfica pie para cada genoma. No sería más conveniente tener toda esta información en una sóla gráfica barplot? Tendría la ventaja de ocupar menos espacio, además de que sería más fácil comparar las diferencias al tener los datos de los distintos genomas lado a lado.

  1. Usando el data.frame original ensTab, hagan las operaciones aritméticas necesarias y guarden los resultados en columnas nuevas. Por ejemplo:
ensTab$coding_genes_percentage = ensTab$coding_genes * ensTab$coding_gene_avg_length / ensTab$genome_length * 100
  1. Extraigan solamente las columnas que necesitan para la figura que van a generar, guardando en un nuevo objeto.
  2. Conviertan el objeto data.frame a una matrix.
  3. Generen un barplot, girando la matriz si hace falta.
  4. Avanzado: generen dos versiones de su barplot, una con el espacio ocupado en Megabases, otra usando porcentajes del tamaño de cada genoma.