2.3 Estructura de datos
Además de los tipos de datos básicos que hemos cubierto en este tema, R permite trabajar también con estructuras más complejas, que veremos a lo largo de este apartado.
2.3.1 Vectores
Es la estructura más simple proporcionada por R y consiste en una secuencia de elementos de un cierto tipo (numérico, complejo, lógico, etc.).
En la práctica existen distintos mecanismos que podemos utilizar a la hora de crear un vector, entre los más utilizados destacamos:
- La función c que simplemente recibe como entrada la secuencia de elementos que queremos especificar en el vector.
<-c(1,3,5,7)
v
v # [1] 1 3 5 7
<-c("a", "b", "d")
v
v # [1] "a" "b" "d"
También se puede utilizar esta función para concatenar vectores:
c(c(1,2),c(1,5),-7)
# [1] 1 2 1 5 -7
- El operador «:». Si ejecutamos en R una expresión de la forma x:y (con x e y numéricos) veremos que se crea una secuencia desde x hasta y, donde cada uno de los elementos de la secuencia está separado por una unidad.
<-2:7
v
v # [1] 2 3 4 5 6 7
3.2:7.8
# [1] 3.2 4.2 5.2 6.2 7.2
- La función seq permite crear una secuencia numérica desde un valor inicial a uno final, indicando también el paso que separa dos elementos consecutivos.
<-seq(2.0, 3.2, 0.1)
v
v # [1] 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.0 3.1 3.2
- La función rep permite crear secuencias con elementos repetidos, indicando primero el elemento o elementos a repetir y después el número de repeticiones.
<-rep(c(1,2),4)
v
v # [1] 1 2 1 2 1 2 1 2
- La función append se puede utilizar para añadir elementos a un vector.
<-c()
v<-append(v, 1:3)
v<-append(v, 7)
v
v # [1] 1 2 3 7
Es posible utilizar los operadores con vectores:
<-c(T, F, F, T, T)
v
v # [1] TRUE FALSE FALSE TRUE TRUE
&F
v# [1] FALSE FALSE FALSE FALSE FALSE
<-seq(3,7,0.5)
v<-(v<5)
m
m # [1] TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
2*v+1
# [1] 7 8 9 10 11 12 13 14 15
Nótese que también es posible realizar operaciones con dos o más vectores (componente a componente):
<-seq(-1,-5)
v1<-seq(2,6)
v2+v2
v1# [1] 1 1 1 1 1
En este último caso, tenemos que tener cuidado si los vectores con los que queremos operar no tienen la misma longitud (podemos obtener esta longitud con la función length). En ese caso los elementos del vector más pequeño se reutilizan hasta completar el tamaño necesario:
<-c(1,2)
v1<-rep(-1,6)
v2length(v1)
# [1] 2
length(v2)
# [1] 6
*v1
v2# [1] -1 -2 -1 -2 -1 -2
Una vez tenemos creado un vector, también nos puede interesar seleccionar una parte del mismo para trabajar solo con ella. Para hacer esto, utilizaremos los corchetes [] y pasaremos en el interior de esos corchetes un vector que permitirá seleccionar los elementos. Tenemos varias alternativas para hacer esta selección:
Utilizar un vector de números (enteros) positivos indicando las posiciones de los elementos que queremos seleccionar.
Utilizar un vector de números (enteros) negativos indicando las posiciones de los elementos que queremos excluir.
Utilizar un vector de componentes de tipo lógico, que deberán tener el valor TRUE en las posiciones que queremos seleccionar y FALSE en las que no.
Podemos ver estos tres mecanismos en acción en el siguiente ejemplo:
<-seq(-5,5)
v1:7]
v[# [1] -5 -4 -3 -2 -1 0 1
c(-1,-4)]
v[# [1] -4 -3 -1 0 1 2 3 4 5
<2]
v[v# [1] -5 -4 -3 -2 -1 0 1
Es posible utilizar los mecanismos de selección arriba indicados para modificar una parte de un vector, como por ejemplo:
<0]<-0
v[v
v # [1] 0 0 0 0 0 0 1 2 3 4 5
También es posible seleccionar un elemento en una posición concreta (la primera posición es la 1) por medio del doble corchete:
<-seq(-5,5)
v2]]
v[[# [1] -4
2.3.2 Matrices y arrays
Extienden el concepto de vector a dos dimensiones (matrices) o más (arrays). Son, por tanto, estructuras en las que los valores de las celdas pertenecen a un tipo concreto (numérico, complejo, lógico…). Para crear estas estructuras de datos, tenemos dos funciones específicas:
- La función matrix se utiliza para crear matrices. Sus tres argumentos principales son: (1) un vector conteniendo los datos de las celdas de la matriz, (2) el número de filas de la matriz y (3) el número de columnas de la matriz.
<-seq(1,12)
datos<-matrix(datos, 3, 4)
m
m # [,1] [,2] [,3] [,4]
# [1,] 1 4 7 10
# [2,] 2 5 8 11
# [3,] 3 6 9 12
- La función array es similar a la anterior. Recibe también como primer parámetro los datos para construir el array pero, en lugar de especificar directamente el número de filas y columnas, el segundo parámetro es un vector que indica las dimensiones.
<-seq(1,12)
datos<-array(datos, c(3,2,2))
m
m # , , 1
# [,1] [,2]
# [1,] 1 4
# [2,] 2 5
# [3,] 3 6
# , , 2
# [,1] [,2]
# [1,] 7 10
# [2,] 8 11
# [3,] 9 12
Al igual que ocurría con los vectores, también podemos seleccionar una parte de una matriz o array para operar con ella. De hecho, el mecanismo a utilizar es el mismo, basado en los corchetes y en vectores conteniendo índices o valores lógicos. La diferencia es que ahora tenemos varias dimensiones, así que dentro de los corchetes puede haber varios vectores. Por ejemplo, operando sobre el array m anterior:
1:2,,2]
m[# [,1] [,2]
# [1,] 7 10
# [2,] 8 11
Se puede ver que dentro de los corchetes hay tres elementos, uno por dimensión del array. Obsérvese también que el segundo elemento está vacío, lo cual quiere decir que se cogen todos los elementos de esa dimensión.
Las matrices y arrays son compatibles también con el uso de los operadores que vimos en secciones anteriores (siempre que el tipo de las celdas permita el uso de esos operadores, por ejemplo, no será posible sumar matrices cuyas celdas sean cadenas de caracteres):
<-matrix(seq(1,12), 3, 4)
m1<-matrix(rep(c(1,0,-1), 4), 3, 4)
m2+m2
m1# [,1] [,2] [,3] [,4]
# [1,] 2 5 8 11
# [2,] 2 5 8 11
# [3,] 2 5 8 11
Como se puede ver, al igual que ocurría con los vectores, estos operadores actúan elemento a elemento. Por tanto, si queremos calcular el producto matricial necesitaremos un operador específico: %*%
%*% t(m2)
m1 # [,1] [,2] [,3]
# [1,] 22 0 -22
# [2,] 26 0 -26
# [3,] 30 0 -30
Se puede ver en este ejemplo que se ha utilizado la función t para calcular la traspuesta de la matriz. Otras funciones que se suelen emplear habitualmente con matrices y/o arrays son:
La función dim permite obtener las dimensiones de la matriz o array.
La función solve se utiliza para invertir una matriz (o resolver sistemas de ecuaciones asociados a esa matriz).
La función diag sirve para obtener la diagonal principal de una matriz o para crear matrices diagonales (como la matriz identidad).
La función det permite calcular el determinante de una matriz cuadrada.
La función apply permite aplicar una determinada función a los elementos de una matriz o array.
Las funciones lower.tri y upper.tri nos servirán de ayuda a la hora de obtener una matriz triangular inferior o superior a partir de una dada.
Podemos ver a continuación ejemplos de uso de estas funciones:
<-matrix(-4:4,3,3) + diag(3)
A
A # [,1] [,2] [,3]
# [1,] -3 -1 2
# [2,] -3 1 3
# [3,] -2 1 5
diag(A)
# [1] -3 1 5
dim(A)
# [1] 3 3
det(A)
# [1] -17
<-c(1,-1,1)
bsolve(A,b)
# [1] 0.5882353 -1.3529412 0.7058824
apply(A, 1, sum) # Aplicamos la función sum para sumar las filas
# [1] -2 1 4
apply(A, 2, sum) # Aplicamos la función sum para sumar las columnas
# [1] -8 1 10
solve(A) %*% A
# [,1] [,2] [,3]
# [1,] 1.000000e+00 5.551115e-17 -2.220446e-16
# [2,] 2.775558e-16 1.000000e+00 0.000000e+00
# [3,] 0.000000e+00 5.551115e-17 1.000000e+00
lower.tri(A)
# [,1] [,2] [,3]
# [1,] FALSE FALSE FALSE
# [2,] TRUE FALSE FALSE
# [3,] TRUE TRUE FALSE
lower.tri(A)]<-0
A[
A # [,1] [,2] [,3]
# [1,] -3 -1 2
# [2,] 0 1 3
# [3,] 0 0 5
Puedes encontrar más información sobre estas funciones en la ayuda de R
2.3.3 Listas
Al igual que los vectores, representan secuencias de elementos, pero a diferencia de estos, en las listas de R se pueden combinar elementos de distintos tipos. Para construirlas, utilizaremos la función list que recibirá como entrada los elementos de la lista:
<-list(3.2, "Hola", TRUE, 2+4.2i)
l
l 1]]
[[# [1] 3.2
2]]
[[# [1] "Hola"
3]]
[[# [1] TRUE
4]]
[[# [1] 2+4.2i
Los elementos de una lista están numerados y, por tanto, es posible acceder a una posición concreta o seleccionar una parte de la lista utilizando los mismos mecanismos que vimos con vectores [] y corchete doble. También es posible asignar nombres a los componentes y acceder en base a esos nombres:
<-list(nota=7.2, nombre="N. Fernandez", aprobado=TRUE)
alumno$nota
alumno# [1] 7.2
2]]
alumno[[# [1] "N. Fernandez"
A la hora de acceder a los campos por nombre, tenemos que tener en cuenta que podemos abreviar el nombre, usando un prefijo más corto, siempre que el resultado sea no ambiguo (es decir, que no haya dos o más nombres con el mismo prefijo):
<-list(nota=7.2, nombre="N. Fernandez", aprobado=TRUE)
alumno$no
alumnoNULL
$nom
alumno# [1] "N. Fernandez"
$ap
alumno# [1] TRUE
Nótese que en el caso de acceder al nombre «no» el resultado es NULL (valor nulo), puesto que se trata de un prefijo ambiguo (válido para referirse tanto a «nota» como a «nombre»). Normalmente las listas se utilizan para representar estructuras de datos, no utilizaremos los operadores directamente sobre listas (por ejemplo, sumando dos listas), aunque sí podemos operar con sus elementos:
<-list(nota=7.2, nombre="N. Fernandez", aprobado=TRUE)
alumno1<-list(nota=8.3, nombre="M. Salgueiro", aprobado=TRUE)
alumno2+alumno2
alumno1+ alumno2 : argumento no-numérico para operador binario
Error en alumno1 $nota + alumno2$nota)/2
(alumno1# [1] 7.75
También podemos utilizar la función lapply para aplicar una función a los elementos de una lista:
<-list(a=seq(1,4), b=rep(1,4)) # Una lista con dos vectores
llapply(l, sum)
# Para cada elemento de la lista (vector)
# sumamos sus componentes
$a
# [1] 10
$b
# [1] 4
2.3.4 Factores (factors)
Un factor es un vector que se utiliza para especificar los niveles de los que consta una clasificación discreta (variable categórica). Su uso es similar al de un tipo enumerado en otros lenguajes de programación.
Para crear un factor, utilizaremos la función factor, que recibirá como entrada un vector con los distintos valores de los niveles:
<-factor(levels = c("L", "M", "X", "J", "V")) dias
Utilizando la función levels, podemos obtener un listado de los distintos niveles que tiene asignado un factor:
levels(dias)
# [1] "L" "M" "X" "J" "V"
También podemos crear un factor a partir de datos numéricos empleando la función cut. Esta función se utiliza a la hora de convertir una variable continua en una categórica, mediante la división de la variable continua en intervalos, cuyos límites se indican con el parámetro breaks:
<-c(1.7,6.4,5.3,9.8,3.5,7.8)
notascut(notas, breaks=c(0,5,7.5,9,10))
# [1] (0,5] (5,7.5] (5,7.5] (9,10] (0,5] (7.5,9]
: (0,5] (5,7.5] (7.5,9] (9,10] Levels
Para cada intervalo es posible especificar una etiqueta por medio del parámetro labels:
cut(notas, breaks=c(0,5,7.5,9,10),
labels=c("Sus","Apr","Not","Sob"))
# [1] Sus Apr Apr Sob Sus Not
: Sus Apr Not Sob Levels
Los factores se suelen utilizar en combinación con la función tapply, que usualmente toma como entradas un vector con datos, un factor que permite establecer el grupo al que pertenece cada uno de los datos, y una función. Esta función se aplica a los elementos del vector de datos según el grupo al que pertenecen. Veamos un ejemplo, en el que queremos calcular la media (función mean) de unas muestras que representan alturas de individuos pertenecientes a dos grupos distintos:
<-c(1.75, 1.68, 1.72, 2.03, 1.73, 1.69, 1.83, 1.92)
alturas<-factor(rep(c("Grupo1", "Grupo2"), 4))
grupos
grupos # [1] Grupo1 Grupo2 Grupo1 Grupo2 Grupo1 Grupo2 Grupo1 Grupo2
: Grupo1 Grupo2
Levelstapply(alturas, grupos, mean)
Grupo1 Grupo2 1.7575 1.8300
Nótese que hemos llamado a la función factor utilizando directamente un vector de datos, en lugar de especificar los niveles con el parámetro levels. En este caso, el sistema extrae automáticamente los niveles quedándose con los valores distintos del vector de entrada (en este caso, «Grupo1» y «Grupo2»).
2.3.5 Tablas (data frames)
Al igual que las listas permitían extender el concepto de vector y trabajar con distintos tipos de elementos, las tablas (data frames en nomenclatura de R) permiten extender el concepto de matriz y trabajar con estructuras tabulares que tienen elementos de distintos tipos de datos.
Se pueden construir data frames a partir de vectores, factores, matrices, listas u otros data frames. Por ejemplo, supongamos que tenemos un vector con datos de alturas y un factor con los grupos a los que pertenecen cada uno de los datos (como vimos en el ejemplo del apartado anterior). Es sencillo construir un data frame a partir de estos elementos, utilizando una llamada a la función data.frame.
<-c(1.75, 1.68, 1.72, 2.03, 1.73, 1.69, 1.83, 1.92)
alturas<-factor(rep(c("Grupo1", "Grupo2"), 4))
grupos<-data.frame(Alturas=alturas, Grupos=grupos)
tabla
tabla
Alturas Grupos 1 1.75 Grupo1
2 1.68 Grupo2
3 1.72 Grupo1
4 2.03 Grupo2
5 1.73 Grupo1
6 1.69 Grupo2
7 1.83 Grupo1
8 1.92 Grupo2
Como se puede ver se ha creado una estructura tabular de dos columnas. A cada una de estas columnas se le ha asignado el nombre que indicamos en la llamada para crear la tabla («Alturas» y «Grupos»). También se le ha asignado automáticamente un nombre a cada una de las filas (en este caso con valores numéricos por defecto).
Podemos cambiar las etiquetas de la siguiente forma:
colnames(tabla)<-c("Alt","Grp")
rownames(tabla)<-c("U1","U2","U3","U4","U5","U6","U7","U8")
tabla
Alt Grp 1.75 Grupo1
U1 1.68 Grupo2
U2 1.72 Grupo1
U3 2.03 Grupo2
U4 1.73 Grupo1
U5 1.69 Grupo2
U6 1.83 Grupo1
U7 1.92 Grupo2 U8
Podemos utilizar esos nombres para acceder a los datos de la tabla…
$Alt
tabla# [1] 1.75 1.68 1.72 2.03 1.73 1.69 1.83 1.92
"U5",]
tabla[# Alt Grp
# U5 1.73 Grupo1
… o para añadir nuevas entradas…
$Nueva<-seq(1,8) # Nueva columna
tabla"U9",]<-list(Alt=1.59, Grp="Grupo2", Nueva=9) # Nueva fila
tabla[
tabla # Alt Grp Nueva
# U1 1.75 Grupo1 1
# U2 1.68 Grupo2 2
# U3 1.72 Grupo1 3
# U4 2.03 Grupo2 4
# U5 1.73 Grupo1 5
# U6 1.69 Grupo2 6
# U7 1.83 Grupo1 7
# U8 1.92 Grupo2 8
# U9 1.59 Grupo2 9
También es posible acceder a los datos de una tabla utilizando índices, al igual que ocurría en las matrices:
1,]
tabla[# Alt Grp
# U1 1.75 Grupo1
1]
tabla[,# [1] 1.75 1.68 1.72 2.03 1.73 1.69 1.83 1.92
3,1]
tabla[# [1] 1.72
R proporciona múltiples funciones que podemos utilizar para manipular data frames. Entre las más empleadas se encuentran
La función dim para obtener las dimensiones de la tabla.
Las funciones nrow y ncol permiten obtener, respectivamente, el número de filas y columnas del data frame.
La función names permite obtener los nombres de las distintas columnas del data frame, mientras que dimnames que permite obtener los nombres de filas y columnas.
La función str permite visualizar información sobre la estructura de la tabla.
Podemos ver a continuación ejemplos de uso de estas funciones:
names(tabla)
# [1] "Alt" "Grp" "Nueva"
dimnames(tabla)
1]]
[[# [1] "U1" "U2" "U3" "U4" "U5" "U6" "U7" "U8" "U9"
2]]
[[# [1] "Alt" "Grp" "Nueva"
dim(tabla)
# [1] 9 3
nrow(tabla)
# [1] 9
str(tabla)
# 'data.frame': 9 obs. of 3 variables:
# $ Alt : num 1.75 1.68 1.72 2.03 1.73 1.69 1.83 1.92 1.59
# $ Grp : Factor w/ 2 levels "Grupo1","Grupo2": 1 2 1 2 1 2 1 2 2
# $ Nueva: num 1 2 3 4 5 6 7 8 9
Al igual que ocurría con las listas, no utilizaremos los operadores directamente sobre data frames (por ejemplo, sumando dos tablas), aunque sí podemos operar con sus elementos (por ejemplo, sumar columnas numéricas de data frames).