Estructuras de datos

Vectores

En lenguaje R, un vector corresponde a una conjunto ordenado de elementos que reúnen la condición de pertenecer al mismo tipo de dato atómico. Si comprobamos de nuevo el contenido de las variables que se han empleado hasta ahora como ejemplos, se aprecia una indicación previa al valor asignado entre corchetes, tal que así [1], indicando que el primer elemento del vector corresponde al primer elemento del renglón que se muestra. Esto es porque R, por defecto, crea vectores de longitud 1 para nuestra variable.

La forma de crear vectores de mayor longitud o introducir nuevos elementos al vector es mediante la función c(), la cual concatena varios elementos del mismo tipo. Si los elementos del vector pertenecen a distintos tipos de datos, la función realiza automáticamente la coerción de elementos siguiendo la jerarquía character > complex > numeric > integer > logical. Esto puede ser modificado por la función as., como ya se ha mencionado anteriormente.

Ejemplo

1
2
3
vector1 <- c(FALSE, 1, 1)
vector2 <- c(vector1, "a", 1, FALSE)
vector3 <- c(vector2, vector1)

Otras funciones como vector() y seq() son capaces de crear vectores. La primera genera un vector cuyos elementos son 0, FALSE o nada dependiendo del tipo de dato que se indique en el paréntesis, al igual que el número de componentes. En cuanto a seq(), genera una secuencia de tipo numérico, al igual que el operador :. Sin embargo, esta función permite realizar indicaciones mediante los argumentos que dispone.

Ejemplos

1
2
3
4
5
vector4 <- vector("character", 3)
vector5 <- vector("numeric", 3)
vector6 <- vector("logical", 3)
vector7 <- -2.5:2.5
vector8 <- seq(from = -2.5, to = 2.5, by = 2.5)

Los elementos del vector pueden recibir un nombre que permite acceder a ellos a partir de él. Esto se consigue de diversas formas:

1
2
3
4
names(vector6) <- c("dato1", "dato2", "dato3")
vector8 <- c(dato1=-2.5, dato2=0, dato3=2.5)
vector6["dato2"]
vector6[2]

Como se puede observar, la forma de acceder a los elementos de un vector es mediante el operador []. En su interior se indica el nombre o la posición del elemento en el vector. Esto crea un subconjunto del vector, el cual puede someterse a modificaciones indipendientemente del resto de elementos, ya que cuando se opera con un vector sin señalar un elemento, se transforma completamente.

Ejemplos

1
2
3
vector8[4:7] <- log(vector8[3])
vector9 <- vector8 * c(2,1)
vector10 <- vector8[-(4:7)] / 5

Info

  • En el primer ejemplo, se opera con un elemento y el resultado se asigna a una secuencia de elementos que, inicialmente, no conformaban vector8. Sin embargo, se puede extender la longitud de éste y, por tanto, aumentar sus componentes mediante esta forma. Las posiciones que no se indican en situaciones similares a las de este ejemplo y se encuentran entre los nuevos elementos y aquellos que componían el vector previamente, se introduce un valor no disponible NA.

  • Los vectores pueden someterse a operaciones matemáticamente permitidas para estas estructuras, por lo que se puede realizar cálculos donde participan diversos vectores, aunque su longitud no sea la misma, como en el tercer ejemplo, donde se repiten los componentes del vector de menor longitud para operar, tal que así c(2,1,2,1,2,1,2).

  • Se ignoran las posiciones de un vector cuando se emplea el signo negativo delante de éstas, como el último ejemplo.

Cree un script llamado tabla_multiplicar.R que devuelva un vector con el resultado de la tabla de multiplicar de un número.

Respuesta
1
2
3
4
print("¿De qué número deseas conocer su tabla de multiplicar?")
numero <- scan(n=1)
tabla <- c(0:10) * numero 
cat("Resultados de la tabla de multiplicar del", numero, ":", tabla, "\n")

Una matriz es un conjunto de números que se disponen bidimensionalmente en filas y columnas. Desde el punto de vista del lenguaje R, una matriz es un vector numérico con un atributo adicional: dim. Dicho atributo es un vector de dos elementos que indican, por este orden, el número de renglones y columnas que componen esta estructura de datos. Predeterminadamente, los elementos son localizados por columnas, es decir, completando todas las filas de la primera columna y, posteriormente, las sucesivas columnas, hasta complementar cada coordenada. Se requiere que el número de coordenadas sea idéntico a la longitud del vector numérico a partir del cual se genera la matriz. Por tanto, una de las formas de conseguir una matriz es creando dicho atributo del vector mediante dim().

1
dim(vector7) <- c(3,2)

Además de este modo, se puede obtener mediante funciones como matrix(), que permite indicar si la matriz se completa por columnas o filas, y rbind() o cbind, que propiamente seleccionan el método de completar la matriz, por filas o columnas, respectivamente. En estas funciones no se requiere la longitud exacta, sino que se complementan repitiendo los elementos continuando con el mismo orden o, en caso de una longitud menor, finalizando la matriz con el elemento que corresponde por orden.

Ejemplos

1
2
3
4
5
matriz1 <- matrix(vector7, nrow=3, ncol=2, , byrow=TRUE)
matriz2 <- rbind(c(-2.5,0,2.5), c(-1,0,1))
matriz3 <- cbind(c(-2.5,0,2.5), c(-1,0,1))
matriz4 <- cbind(c(-2.5,0,2.5), c(-1,0))
matriz5 <- cbind(c(-2.5,0,2.5,5), c(-1,0,1))

¿Qué destacarías si comparas mediante el operador == las matrices vector7 y matriz1 o matriz2 y matriz3?

Respuesta
  • Las matrices vector7 y matriz1 presentan las mismas dimensiones aunque se hayan creado de distinta forma. Esto es porque dim() indica las dimensiones tomando al primer elemento del vector como número de filas y al segundo como columnas, de la misma forma que los argumentos nrow y ncol de matrix(). Sin embargo, se aprecia que matriz1 se ha completado distintamente debido a que se emplea el argumento byrow como TRUE. Esto se conseguiría igualmente usando el argumento bycol como FALSE. Usando las formas opuestas o ignorando estos argumentos, matriz1 y vector7 serían idénticos.

  • Los elementos de matriz2 y matriz3 son los mismos números, sin embargo, se disponen de forma distinta, ya que se emplean diferentes funciones para combinar los vectores y obtener la matriz, pues rbind() sitúa el primer vector en la primera fila, colocando sus elementos en columnas diferentes y, así, con los demás vectores que componen la matriz. Al contrario ocurre con cbind(), que sitúa los vectores en columnas, por lo que cada uno de sus elementos ocupa una fila diferente.

Las columnas y las filas de las matrices, al igual que los vectores y las listas, pueden recibir un nombre que las identifique y funcione como índice. Esto se realiza mediante las funciones rownames() y colnames(). Para obtener un sobconjunto de la matriz se introduce el nombre o las coordenadas en el operador []. En este tipo de estructuras se toma el total de una fila o columna cuando se ignora introducir su coordenada.

Ejemplos

1
2
3
4
rownames(matriz2) <- c("fila1", "fila2")
colnames(matriz2) <- c("columna1", "columna2", "columna3")
matriz2[2,"columna1"] <- matriz2[2,"columna1"]*2
matriz6 <- matriz2 %*% matriz6

Info

  • Las matrices pueden someterse a las operaciones matemáticas que les caracterizan, como la multiplicación matricial mediante el operador %*% o multiplicación elemento por elemento *.

Cree un script llamado matriz.R que devuelva una matriz de dimensiones y valores deseados.

Respuesta
1
2
3
4
5
6
7
print("¿Cuántas filas y columnas desea que tenga la matriz? Indíquelo de forma respectiva: ")
dim_matriz <- scan(n=2)
n_elementos <- dim_matriz[1] * dim_matriz[2]
cat("Indique", n_elementos, "valores uno por uno como desees que se rellenen las filas de la matriz: ")
elementos <- scan(n=n_elementos)
matriz <- matrix(elementos, nrow=dim_matriz[1], ncol=dim_matriz[2], byrow=TRUE)
print(matriz)

Factores

Este tipo de dato se consigue a partir de un vector mediante la función factor(). Esto genera una estructura de datos que contiene los mismos elementos que el vector y, además, información categórica de dichos elementos, de forma que sean identificados con etiquetas numéricas, correspondientes a un índice, atendiendo al orden de aparición de la categoría en la longitud del vector. Dichas categorías se pueden ordenar como guste si se indica con un vector, que puede ser referido con el argumento levels, aunque no es necesario.

Ejemplo

1
factor1 <- factor(vector3, c(1,"a",FALSE,0))

Info

Se puede visualizar los elementos del factor y sus categorías simultáneamente con funciones como str() o unclass(), que también permiten observar componentes de otros tipos de datos; o bien sólamente las categorías con levels(). Aplicando la función table() al factor, se crea una tabla que indica la cantidad de elementos pertenecientes a cada categoría en el factor.

1
2
3
4
str(factor1)
unclass(factor1)
levels(factor1)
tabla1 <- table(factor1)

Para realizar subconjuntos de los elementos del factor se puede consultar tanto su posición en el factor como el índice de la categoría. Estos subconjuntos no se pueden someter a operaciones numéricas.

Ejemplos

1
2
subconjunto1 <- factor1[3]
subconjunto2 <- levels(factor1)[3]

Cree un script llamado tabla_factor.R que devuelva una tabla indicando la cantidad de veces que se repite cada valor que introduzcas como entrada.

Reespuesta
1
2
3
4
print("Introduzca el valor que desee hasta que no quiera incluir más números: ")
nums <- scan()
tabla_nums<- table(factor(nums))
print(tabla_nums)

Listas

Una lista es una clase de datos que puede contener elementos de distinto tipo sin necesidad de coerción. Se obtiene mediante la función list() y, al igual que en los vectores, se pueden nombrar sus componentes tanto directamente en el momento de creación de la lista como a partir de la función names() junto a c() para dar un nombre a los componentes, habiendo creado previamente la lista. También sería posible sustituyendo c() por list(). Para acceder a los elementos de la lista se pueden emplear los operadores [], [[]] y $. Estos dos últimos se utilizan con las nombres, aunque [[]] también puede emplearse con la localización, como [], y la función que tienen es extraer elementos de un objeto, con su clase original.

Ejemplo

1
2
3
4
lista1 <- list(equipo="verde", componentes=2, nombres=c("Ana", "Juan"), ganador=TRUE) 
names(lista1) <- list("color", "número", "usuario", "perdedor")  
subconjunto3 <- lista1$color
subconjunto4 <- lista1[1]

Cree un script llamado nota_media.R que devuelva una lista con el nombre del usuario y la nota media actual del Grado.

Respuesta
1
2
3
4
5
6
7
nombre_usuario <- readline(prompt="Dime tu nombre: ")
print("¿Cuántas asignaturas has finalizado?")
num_asignaturas <- scan(n=1)
print("¿Qué notas finales obtuviste en ellas?")
notas <- scan(n=num_asignaturas)
lista <- list(nombre=nombre_usuario, nota_media=mean(notas))
print(lista)

Data frames

Un data frame es una estructura de datos bidimensional que parece una tabla, aunque realmente actúa como una lista, cuyos componentes pueden ser de diferentes tipos, como vectores, listas o matrices. Esta estructura requiere que coincida el número de datos por fila y el tipo de dato de cada columna. Los data frames se pueden crear mediante la función data.frame(), que ordena por columnas los datos que indicamos como componentes, y si la longitud de las filas resultase inferior o superior entre ellas, no se generaría el data frame, ya que no cumpliría las restricciones.

Ejemplo

1
data_frame1 <- data.frame(vector4, vector5, vector6)

Los data frames pueden aumentar sus componentes mediante las funciones rbind() y cbind, siempre que se cumplan los requerimientos. Además, las columnas y las filas pueden ser nombradas mediante colnames() y rownames(), respectivamente. También se puede acceder a los elementos de la estructura y modificarlos, para ello se emplean los operadores [], [[]] y $.

Ejemplos

1
2
3
4
5
data_frame1 <- cbind(data_frame1, c(1,2,3))
colnames(data_frame1) <- c("caracter" , "numerico", "logico", "entero")
rownames(data_frame1) <- c("n1", "n2", "n3")
data_frame1$numerico <- c(1.5, 2.5, 3.5)
data_frame1[["n2", "logico"]] <- TRUE

¿Qué diferencia hay entre lo que devuelve y lo que se podría esperar en los siguientes casos?

1
2
data_frame1[1:3, "cadena"] <- c("si", "si", "no")
data_frame2 <- rbind(data_frame1, c("no", 4.5, TRUE, 4))

Respuesta

En el primer caso, en lugar de los nuevos caracteres indicados que podríamos esperar situados en la primera columna, correspondiente a cadena, se encuentran datos no disponibles NA. Esto se debe a que los vectores de caracteres se transforman en factores para que sea posible su tratamiento mediante operaciones estadísticas. Por tanto, al introducir una nueva cadena, no la reconoce, ya que previamente requiere su categorización con la función levels(), lo que resultaría del siguiente modo:

1
2
levels(data_frame1$cadena) <- c(levels(data_frame1$cadena), "si", "no")
data_frame1[1:3, "cadena"] <- c("si", "si", "no")

En cuanto a data_frame2, a simple vista parece devolver lo esperado. No obstante, si se comprueba el contenido de cada columna, observamos que los datos han cambiado a tipo carácter. Esto ocurre porque se introduce un vector con un elemento de tipo carácter, que lo convierte los demás datos en dicho tipo por coerción, y como ya se ha mencionado, las columas deben de contener la misma clase de dato, por lo que también se produce coerción en sus componentes. Esto repercutiría en las operaciones a las que podría someterse. El método de evadir este error sería introduciendo los elementos como lista y no como vector, de forma que mantenga el tipo de dato en sus componentes. Para ello, se sustituye la función c() por list(), tal que así:

1
data_frame3 <- rbind(data_frame1, list("no", 4.5, TRUE, 4))

Cree un script llamado data_frame.R que devuelva un data frame partiendo de matriz.R.

Respuesta
1
2
3
source("matriz.R")
df <- data.frame(matriz)
print(df)

Funciones de interés

En el apartado de Conceptos básicos se comentaron algunas funciones iniciales de utilidad. Otras que podrían resultar interesantes son las siguientes:

Nombre Función
sample() Ofrece un número de muestras aleatorias indicadas
rnorm() Crea un vector numérico cuya longitud y media son indicados
unique() Elimina elementos repetidos de un vector
sort() Ordena los elementos del vector
tabulate() Cuenta las veces que se repite un elemento
match() Devuelve un vector con las posiciones de las similitudes entre vectores
max() Indica el elemento cuyo valor es el más grande
min() Indica el elemento cuyo valor es el más pequeño
which.max() Indica la posición del elemento cuyo valor es el más grande
which.min() Indica la posición del elemento cuyo valor es el más pequeño
t() Realiza la matriz traspuesta
diag() Extrae los elementos de la primera diagonal de la matriz
-diag() Extrae los elementos de la diagonal opuesta de la matriz
det() Determinante de la matriz

Cree un script llamado moda.R que realice la moda de un vector numérico.

Respuesta
1
2
3
4
5
6
7
print("Indique los valores numéricos del vector: ")
V <- scan()
uV <- sort(unique(V))              # Elimina duplicaciones y ordena
Ht <- tabulate(match(V, uV))       # Histograma dado    
pos_max <- which.max(Ht)           # Posicion del max
moda <- uV[pos_max]                # Toma el valor más repetido 
cat("La moda del vector es", moda, "\n")

Enlaces de interés