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.
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"
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:
## $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.
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.
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.
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 * 100data.frame a una matrix.barplot, girando la matriz si hace falta.