Cuando se trabaja con colecciones de datos atómicos homogéneos (por ejemplo, todos numéricos o todos de tipo carácter), en R se dispone de las siguientes estructuras:
vector para datos unidimensionales, a los que se accede con un único índice.matrix para datos bidimensionales, 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: puede que esta sección te resulte familiar.
Que se parezca a NumPy no es casualidad (aunque R es un poco anterior y tiene todo esto en el lenguaje base).
Una matrix es esencialmente un vector, más un número de filas y de columnas que definen 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
Ten en cuenta que los datos se almacenan en orden por columnas (es decir, primero hacia abajo y luego a lo ancho). Esto es igual que en Fortran, Matlab y Julia, pero distinto de C/C++ y NumPy.
Para hallar las dimensiones de una matrix, usa dim() y obtendrás tanto las filas como las columnas, o bien nrow() / ncol() para obtener 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 anterior).
Otras funciones producen como resultado una matrix, 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 de todas las mismas formas 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 matrix que 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
Ten en cuenta que un subconjunto con una sola fila o una sola columna se devolverá como un vector, no como una matrix.
class(m)
#> [1] "matrix" "array"
class(m[2,])
#> [1] "integer"
Este tipo de dimension reduction no siempre es lo que se busca, sobre todo en una cadena de funciones que espera recibir matrix como entrada.
Especificar drop=FALSE garantizará que una matrix de entrada devuelva como resultado una matrix.
Ten en cuenta que una matrix 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 matrix 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 matrix 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 añadir datos.
Las matrix tienen más estructura, así que hay que especificar con más cuidado lo que se necesita.
Las funciones rbind() y cbind() unen nuevas filas o columnas a la matrix.
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 con las matrix, aplicándose a todos los elementos. Sin embargo, pueden perder la estructura de matrix 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
Con las matrix, las funciones colSums() y rowSums() también pueden ser útiles.
En este caso la reducción de dimensiones es intencionada, así que los valores devueltos son vectores.
# m as defined above
rowSums(m)
#> [1] 9 12
colSums(m)
#> [1] 3 7 11
Del mismo modo, existen las funciones rowMeans() y colMeans() para calcular medias.
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 matrix y los arrays quedan en su mayor parte fuera del alcance de las entradas de Tidyverse (al menos por ahora).
La función apply() forma parte de R base y sigue siendo la mejor herramienta para controlar con más finura la programación con matrix.
Los parámetros, en orden, son:
La función rowMeans() existe en R base, 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 presupone un nivel de conocimientos matemáticos que queda fuera del alcance de Exercism.
Sáltala si no le ves sentido.
Sin embargo, R es un lenguaje de ciencia de datos, y muchas de las personas que lo usan tienen formación en matemáticas aplicadas y estadística. Este resumen tan escueto puede interesar a algunos estudiantes.
El R moderno es «obviamente» un lenguaje de ciencia de datos: diseñado para trabajar principalmente con datos tabulares y popular entre usuarios como estadísticos, epidemiólogos y científicos sociales. Su 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, en competencia directa con Matlab.
Puede que sorprenda, pero R base sigue admitiendo toda una gama de operaciones de álgebra lineal con vectores y matrix. No está claro hasta qué punto se usan hoy en el código de los usuarios finales, pero estas técnicas siguen siendo muy importantes internamente 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. Una buena guía de las matemáticas (no de la programación) la tienes en 3Blue1Brown.
La exposición más completa del álgebra lineal dentro de Exercism está en el temario de Julia (los mismos autores que el temario de R).
Creemos que Julia (o Python, en contextos en los que se dispone del módulo numpy.linalg) sería hoy un lenguaje más apropiado que R para trabajar en serio con álgebra lineal.
La diagonal principal de una matrix cuadrada (N x N)) A va de la esquina superior izquierda (A[1,1]) a la inferior derecha (A[N,N]).
La función diag() se comporta de forma distinta según el argumento que se le pase.
Con un vector v, diag(v) crea una matrix diagonal.
diag(1:3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 2 0
#> [3,] 0 0 3
Con una matrix cuadrada A, diag(A) devuelve en un vector los elementos de la diagonal principal.
A
#> [,1] [,2] [,3]
#> [1,] 10 5 2
#> [2,] 9 3 7
#> [3,] 8 10 4
diag(A)
#> [1] 10 3 4
Con un único número entero k, diag(k) crea una matrix identidad k × k: puede que no sea lo que habrías adivinado.
Hasta los manuales serios de R tienden a añadir comentarios como «vete tú 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 matrix.
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 matrix con valores complejos, el resultado no es el conjugado complejo.
Usa Conj(t(A)) para obtener la transpuesta conjugada (adjunta) de la matrix A.
Para los arrays de más dimensiones, aperm() permuta las dimensiones.
Para la inversa de una matrix cuadrada A podemos usar solve(A).
A algunas personas nos resulta una sintaxis... sorprendente.
Multiplicar una matrix por su inversa da la matrix identidad (salvo 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 +, -, *, ^ operan sobre las matrix elemento a elemento.
Si hay un desajuste de dimensiones, 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 matrix queda fuera del alcance de esta página, pero, en un resumen muy breve:
%*% es la forma como operador infijo de matmult(), que da el producto matricial.%o% es la forma como operador infijo de outer(), que da el producto exterior.crossprod() da el producto cruz.