Cuando trabajas con colecciones de datos atómicos homogéneos (por ejemplo, todos numéricos o todos de tipo carácter), en R tienes disponibles las siguientes estructuras:
vector para datos de una dimensión, a los que se accede con un solo índice.matrix para datos bidimensionales y rectangulares (no irregulares).array para el caso general de N dimensiones. Una matrix no es más que un array con N=2.Para quienes programan en Python: esta sección puede resultarte familiar.
Cualquier parecido con NumPy no es coincidencia (aunque R es un poco más antiguo y tiene todo esto en el lenguaje base).
Una matriz no es más que un vector, más un número de filas y columnas que define su forma.
Existe una función matrix() para crearlas:
# data already available
matrix(1:6, nrow=2, ncol=3)
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
# empty matrix to be filled in later
matrix(nrow=2, ncol=3)
#> [,1] [,2] [,3]
#> [1,] NA NA NA
#> [2,] NA NA NA
Fíjate que los datos se almacenan en orden por columnas (es decir, hacia abajo y luego hacia los lados). Esto es igual que en Fortran, Matlab y Julia, pero distinto de C/C++ y NumPy.
Puedes obtener las dimensiones de una matriz con dim() para filas y columnas a la vez, o con nrow() o ncol() para una sola dimensión.
Si usas length(), solo obtendrás la longitud del vector subyacente: el número total de elementos (6 en el ejemplo de arriba).
Otras funciones también producen una matriz como resultado, por ejemplo str_split_fixed().
library(stringr)
str_split_fixed(c("split some words", "and some more"), " ", 3)
#> [,1] [,2] [,3]
#> [1,] "split" "some" "words"
#> [2,] "and" "some" "more"
Funciona igual que la indexación de vectores, salvo que hay que especificar la fila (o filas) y la columna (o columnas), en ese orden. Deja una dimensión vacía para obtener toda la fila o toda la columna.
Usando la misma matriz de antes:
m <- matrix(1:6, nrow=2, ncol=3)
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m[2, 3] # row 2, col 3
#> [1] 6
m[1:2, 3] # rows 1:2, col 3
#> [1] 5 6
m[2, ] # all of row 2
#> [1] 2 4 6
Fíjate que un subconjunto con una sola fila o una sola columna se devuelve como vector, no como matriz.
class(m)
#> [1] "matrix" "array"
class(m[2,])
#> [1] "integer"
Este tipo de dimension reduction no siempre es lo que quieres, sobre todo en una cadena de funciones que espera recibir matrices como entrada.
Especificar drop=FALSE garantiza que una entrada de tipo matriz devuelva una matriz.
Fíjate que una matriz con una sola fila o una sola columna no es del mismo tipo que un vector.
m[2, , drop = FALSE]
#> [,1] [,2] [,3]
#> [1,] 2 4 6
La función as.matrix() convierte un vector en una matriz de una sola columna.
as.matrix(1:3)
#> [,1]
#> [1,] 1
#> [2,] 2
#> [3,] 3
Igual que con los vectores, puedes modificar partes de una matriz simplemente asignando valores nuevos.
m[, 2] <- c(10, 20)
m
#> [,1] [,2] [,3]
#> [1,] 1 10 5
#> [2,] 2 20 6
Con los vectores, la función c() bastaba para insertar y agregar datos.
Las matrices tienen más estructura, así que hay que ser más cuidadoso al especificar lo que se necesita.
Las funciones rbind() y cbind() agregan nuevas filas o columnas a la matriz.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m2 <- matrix(7:9, nrow = 1, ncol = 3)
rbind(m, m2)
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
#> [3,] 7 8 9
Para eliminar una fila o una columna se usa la indexación negativa, igual que con los vectores.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m[, -2]
#> [,1] [,2]
#> [1,] 1 5
#> [2,] 2 6
Todas las funciones de vectores también funcionan en matrices, aplicándose a todos los elementos. Sin embargo, pueden perder la estructura de matriz y devolver un vector.
sqrt(m)
#> [,1] [,2] [,3]
#> [1,] 1.000000 1.732051 2.236068
#> [2,] 1.414214 2.000000 2.449490
sum(m)
#> [1] 21
m[m > 3]
#> [1] 4 5 6
En el caso de las matrices, las funciones colSums() y rowSums() también pueden ser útiles.
En este caso, la reducción de dimensiones es intencional, por lo que los valores devueltos son vectores.
# m as defined above
rowSums(m)
#> [1] 9 12
colSums(m)
#> [1] 3 7 11
De manera similar, existen las funciones rowMeans() y colMeans() para calcular promedios.
En otros conceptos hemos visto que las librerías de Tidyverse pueden simplificar las operaciones con vectores y listas (purrr) o con dataframes (dplyr).
Sin embargo, las matrices y los arrays quedan en su mayoría fuera del alcance de las entradas de Tidyverse (al menos por ahora).
La función apply() forma parte de Base R y sigue siendo la mejor herramienta para tener un control más fino al programar con matrices.
Los parámetros, en orden, son:
La función rowMeans() existe en Base R, pero también podríamos simularla con apply() y mean().
randm <- sample(1:10, 16, replace = TRUE) |> matrix(4, 4)
randm
#> [,1] [,2] [,3] [,4]
#> [1,] 4 9 8 3
#> [2,] 6 8 5 5
#> [3,] 4 2 1 2
#> [4,] 4 2 5 4
apply(randm, 1, mean)
#> [1] 6.00 6.00 2.25 3.75
rowMeans(randm)
#> [1] 6.00 6.00 2.25 3.75
Esta sección da por sentado un nivel de conocimientos matemáticos que queda fuera del alcance de Exercism.
Puedes saltártela si no le encuentras sentido.
Sin embargo, R es un lenguaje de ciencia de datos, y muchas personas que lo usan tienen formación en matemáticas aplicadas y estadística. Este resumen tan breve puede interesarle a algunos estudiantes.
El R moderno es «obviamente» un lenguaje de ciencia de datos: está diseñado para trabajar principalmente con datos tabulares y es popular entre usuarios como estadísticos, epidemiólogos y científicos sociales. El principal competidor en este campo es el ecosistema científico de Python, que incluye paquetes como Pandas o Polars.
Sin embargo, en décadas anteriores R también se veía como un posible lenguaje de ingeniería, que competía directamente con Matlab.
Quizá te sorprenda, pero Base R todavía admite una gama completa de operaciones de álgebra lineal con vectores y matrices. No está claro cuánto se usan ahora en el código de quien lo escribe, pero estas técnicas siguen siendo muy importantes a nivel interno para implementar funciones estadísticas.
El álgebra lineal en R todavía se enseña en algunos cursos universitarios, como esta clase de econometría. Tienes una buena guía de las matemáticas (no de la programación) en 3Blue1Brown.
El análisis más completo de álgebra lineal dentro de Exercism está en el temario de Julia (de los mismos autores que el temario de R).
Creemos que hoy en día Julia (o Python, en contextos donde el módulo numpy.linalg esté disponible) sería un lenguaje más apropiado que R para trabajar en serio con álgebra lineal.
La diagonal principal de una matriz cuadrada (N x N)) A va desde la esquina superior izquierda (A[1,1]) hasta la inferior derecha (A[N,N]).
La función diag() se comporta de manera distinta según el argumento que recibe.
Con un vector v, diag(v) crea una matriz diagonal.
diag(1:3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 2 0
#> [3,] 0 0 3
Con una matriz cuadrada A, diag(A) devuelve los elementos de la diagonal principal como un vector.
A
#> [,1] [,2] [,3]
#> [1,] 10 5 2
#> [2,] 9 3 7
#> [3,] 8 10 4
diag(A)
#> [1] 10 3 4
Con un solo número entero k, diag(k) crea una matriz identidad de k × k: quizá no sea lo que habrías adivinado.
Hasta los libros de texto serios sobre R suelen agregar comentarios como "vaya uno a saber" en este punto.
diag(3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 1 0
#> [3,] 0 0 1
Para intercambiar filas y columnas, la función t() transpone cualquier matriz.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
t(m)
#> [,1] [,2]
#> [1,] 1 2
#> [2,] 3 4
#> [3,] 5 6
Para una matriz con valores complejos, el resultado no es el conjugado complejo.
Usa Conj(t(A)) para obtener la transpuesta conjugada (adjunta) de la matriz A.
Para arrays de dimensiones superiores, aperm() permuta las dimensiones.
Para obtener la inversa de una matriz cuadrada A podemos usar solve(A).
A algunas personas esta sintaxis nos parece... inesperada.
Multiplicar una matriz por su inversa da la matriz identidad (salvo por errores de redondeo muy pequeños).
A <- matrix(sample(1:10, 9, replace = TRUE), 3, 3)
A
#> [,1] [,2] [,3]
#> [1,] 10 5 2
#> [2,] 9 3 7
#> [3,] 8 10 4
solve(A)
#> [,1] [,2] [,3]
#> [1,] 0.16666667 0.00000000 -0.08333333
#> [2,] -0.05747126 -0.06896552 0.14942529
#> [3,] -0.18965517 0.17241379 0.04310345
A %*% solve(A) |> round(digits = 10)
[,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 1 0
#> [3,] 0 0 1
Los operadores aritméticos habituales, como +, -, * y ^, operan sobre las matrices elemento por elemento.
Si las dimensiones no coinciden, se aplican las reglas habituales del reciclaje.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m * m
#> [,1] [,2] [,3]
#> [1,] 1 9 25
#> [2,] 4 16 36
# same calculation, different syntax
m ^ 2
#> [,1] [,2] [,3]
#> [1,] 1 9 25
#> [2,] 4 16 36
Explicar los demás tipos de multiplicación de vectores y matrices queda fuera de nuestro alcance, pero este es un resumen muy breve:
%*% es la forma de operador infijo de matmult(), que da el producto de matrices.%o% es la forma de operador infijo de outer(), que da el producto externo.crossprod() da el producto cruz.