Tema 6 Tipos de datos: matrices

En este tema seguimos analizando los principales tipos de datos de R, estudiando las matrices. Las matrices son un tipo de dato que implementa el concepto matemático del mismo nombre. En R, una matriz es un vector que tiene asociado dos dimensiones. Todos los elementos de una matriz deben ser del mismo tipo.

Las matrices se implementan como ilustra la Figura 6.1. La memoria del ordenador es lineal. Por lo tanto, los elementos de una matriz se ubican en posiciones contiguas de memoria columna a columna (en otros lenguajes de programación es fila a fila). Como todos los elementos de una matriz tienen que ser del mismo tipo, ocupan el mismo tamaño en la memoria y, por lo tanto, guardando la dirección de inicio de la matriz es fácil y eficiente acceder a cualquiera de sus elementos dado su número de fila y columna. Por ejemplo, la figura ilustra el pequeño cálculo aritmético realizado para obtener la dirección de memoria donde se almacena el elemento de la matriz ubicado en la fila 2, columna 1. Como R almacena los elementos de las matrices por columnas, siempre que tenga que recorrer todos sus elementos lo hará por columnas en lugar de por filas, porque es más eficiente. En concreto, la creación de matrices que se ve a continuación se realiza por defecto por columnas.

Implementación de una matriz.
Implementación de una matriz.

Podemos crear una matriz con la función matrix:

m <- matrix(c(2, 3, 6, 5), nrow = 2, ncol = 2)
m
##      [,1] [,2]
## [1,]    2    6
## [2,]    3    5

El primer parámetro es un vector e indica los elementos de la matriz. nrow sirve para especificar el número de filas y ncol el número de columnas. Si a partir de uno de los valores de nrow o ncol se puede deducir el otro, no es preciso especificar ambos:

matrix(c(2, 3, 6, 5), nrow = 2)
##      [,1] [,2]
## [1,]    2    6
## [2,]    3    5
matrix(c(2, 3, 6, 5), ncol = 2)
##      [,1] [,2]
## [1,]    2    6
## [2,]    3    5

Por defecto los elementos del vector se almacenan por columnas, pero podemos usar el parámetro byrow para que se guarden por filas:

matrix(1:20, nrow = 2)
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,]    1    3    5    7    9   11   13   15   17    19
## [2,]    2    4    6    8   10   12   14   16   18    20
matrix(1:20, nrow = 2, byrow = TRUE)
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,]    1    2    3    4    5    6    7    8    9    10
## [2,]   11   12   13   14   15   16   17   18   19    20

Si el vector que especifica los elementos tiene un tamaño inferior al de la matriz, los elementos del vector se reciclan. Esto es útil para crear una matriz en la que todos sus elementos tienen el mismo valor:

matrix(0, nrow = 3, ncol = 3)
##      [,1] [,2] [,3]
## [1,]    0    0    0
## [2,]    0    0    0
## [3,]    0    0    0

Podemos cambiar el atributo dim de un vector para convertirlo en una matriz:

(v <- c(2, 5, 1.1, 10)) # v es un vector
## [1]  2.0  5.0  1.1 10.0
dim(v) <- c(2, 2)      # cambia v de vector a matriz
v
##      [,1] [,2]
## [1,]    2  1.1
## [2,]    5 10.0

Ejercicio. Crea una matriz 8x8 rellena con un patrón de tablero de ajedrez (utiliza 0 y 1).

col1 <- rep(0:1, 4)
col2 <- rep(1:0, 4)
matrix(c(col1, col2), nrow = 8, ncol = 8)
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
## [1,]    0    1    0    1    0    1    0    1
## [2,]    1    0    1    0    1    0    1    0
## [3,]    0    1    0    1    0    1    0    1
## [4,]    1    0    1    0    1    0    1    0
## [5,]    0    1    0    1    0    1    0    1
## [6,]    1    0    1    0    1    0    1    0
## [7,]    0    1    0    1    0    1    0    1
## [8,]    1    0    1    0    1    0    1    0

6.1 Consultar el tamaño de una matriz

La función length indica cuántos elementos tiene una matriz, la función dim cuántos elementos hay en cada dimensión, nrow el número de filas y ncol el número de columnas.

(m <- matrix(5, 2, 4))
##      [,1] [,2] [,3] [,4]
## [1,]    5    5    5    5
## [2,]    5    5    5    5
length(m)
## [1] 8
dim(m)     # dim devuelve un vector de tamaño 2
## [1] 2 4
nrow(m)
## [1] 2
ncol(m)
## [1] 4

6.2 Filas y columnas con nombres

Para algunas matrices puede resultar útil que las distintas filas y/o columnas tengan asociado un nombre. Los nombres se pueden especificar al crear la matriz con el parámetro dimnames de la función matrix. También de esta forma:

notas <- matrix(runif(9, 1, 10), nrow = 3)
rownames(notas) <- c("Juan", "María", "Jaime")
colnames(notas) <- paste("Examen", 1:3)
notas
##       Examen 1 Examen 2 Examen 3
## Juan  7.808117 8.353483 5.887392
## María 7.717274 1.646936 9.760126
## Jaime 2.253255 5.516627 1.508032

Los nombres se especifican mediante un vector de cadenas de caracteres. Es posible consultar los nombres de una dimensión o incluso anularlos:

rownames(notas)
## [1] "Juan"  "María" "Jaime"
colnames(notas)
## [1] "Examen 1" "Examen 2" "Examen 3"
colnames(notas) <- NULL # Anula los nombres de las columnas
notas
##           [,1]     [,2]     [,3]
## Juan  7.808117 8.353483 5.887392
## María 7.717274 1.646936 9.760126
## Jaime 2.253255 5.516627 1.508032

6.3 Aritmética de matrices

Al igual que ocurre con los vectores, los operadores aritméticos (+, -, /, ^, …) son aplicables a matrices numéricas y trabajan elemento a elemento, reciclando si es necesario:

(m1 <- matrix(10, nrow = 2, ncol = 2))
##      [,1] [,2]
## [1,]   10   10
## [2,]   10   10
(m2 <- matrix(1:4, nrow = 2))
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
m1 - m2^2 + 3
##      [,1] [,2]
## [1,]   12    4
## [2,]    9   -3
(m3 <- matrix(rnorm(10), nrow = 2))
##             [,1]     [,2]       [,3]       [,4]       [,5]
## [1,] -0.04650393 0.071325 -1.1570084 0.08306695 -0.1263636
## [2,]  0.16674280 1.628937 -0.4157481 1.14650415 -0.4490542
abs(m3) # aplica la función absoluto a todos los elementos de m3
##            [,1]     [,2]      [,3]       [,4]      [,5]
## [1,] 0.04650393 0.071325 1.1570084 0.08306695 0.1263636
## [2,] 0.16674280 1.628937 0.4157481 1.14650415 0.4490542

El producto clásico de matrices se realiza con el operador %*%:

(m1 <- matrix(1, nrow = 2, ncol = 2))
##      [,1] [,2]
## [1,]    1    1
## [2,]    1    1
(m2 <- matrix(1:4, nrow = 2))
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
m1 * m2   # multiplicación elemento a elemento
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
m1 %*% m2 # multiplicación clásica
##      [,1] [,2]
## [1,]    3    7
## [2,]    3    7

6.4 Funciones aplicables a matrices

Existen muchas funciones que aplican una operación al conjunto de los elementos de una matriz. Son tantas que no es posible enumerarlas todas, pero ten presente que si quieres hacer un procesamiento típico con matrices es muy probable que esté implementado en alguna función de R.

Por ejemplo, mean calcula la media de los elementos de una matriz. También se puede calcular la media de las distintas columnas o filas con colMeans y rowMeans respectivamente.

m <- matrix(sample(4, size = 9, replace = TRUE), nrow = 3)
m
##      [,1] [,2] [,3]
## [1,]    2    3    4
## [2,]    1    3    1
## [3,]    2    4    4
mean(m)     # media de todos los elementos
## [1] 2.666667
rowMeans(m) # media de las filas
## [1] 3.000000 1.666667 3.333333
colMeans(m) # media de las columnas
## [1] 1.666667 3.333333 3.000000

sum, colSums y rowSums son análogas, pero suman los elementos de una matriz. Hay funciones, como median, que pueden aplicarse al conjunto formado por todos los elementos de una matriz, pero no existen versiones para aplicar el procesamiento por filas o columnas. En caso de que tengamos la necesidad de aplicar una función a las filas o columnas de una matriz se puede usar la función apply:

(m <- matrix(sample(4, size = 9, replace = TRUE), nrow = 3))
##      [,1] [,2] [,3]
## [1,]    3    1    4
## [2,]    1    4    4
## [3,]    3    1    2
rowSums(m)
## [1] 8 9 6
apply(m, 1, sum) # mismos efectos que rowSums
## [1] 8 9 6
colSums(m)
## [1]  7  6 10
apply(m, 2, sum) # mismos efectos que colSums
## [1]  7  6 10

La sintaxis de apply es apply(matriz, dimensión, función) y el efecto es aplicar la función indicada en el tercer parámetro a la matriz especificada en el primer parámetro en una dimensión. Si dimensión es 1 se aplica a las filas y si es 2 a las columnas. En caso de que la invocación a la función requiera argumentos adicionales, estos pueden ser indicados después del argumento que especifica la función:

(m <- matrix(sample(c(1:5, NA), size = 12, replace = TRUE), nrow = 3))
##      [,1] [,2] [,3] [,4]
## [1,]    4   NA    2    3
## [2,]   NA    3    3    5
## [3,]    4    4    5    4
apply(m, 2, median) # mediana por columnas
## [1] NA NA  3  4
apply(m, 2, median, na.rm = TRUE)
## [1] 4.0 3.5 3.0 4.0

En la segunda llamada a apply el parámetro na.rm se le pasa a la función median.

Ejemplo. Vamos a realizar una simulación para obtener una aproximación de la función masa de probabilidad de la variable aleatoria máximo del lanzamiento de 2 dados. Esta función es fácil de calcular sin realizar una simulación. La probabilidad de que la variable valga 1 es \(\frac{1}{36}\), la probabilidad de que valga 2 es \(\frac{3}{36}\), la probabilidad de que valga 3 es \(\frac{5}{6}\), etcétera. Para realizar la simulación vamos a lanzar 2 dados un millón de veces, vamos a calcular el máximo de cada tirada de dos dados y luego calcularemos la proporción de esos máximos. Usaremos una matriz para guardar las tiradas, la matriz tiene 2 filas y un millón de columnas y cada columna representa el lanzamiento de dos dados.

m  <- matrix(sample(6, size = 2e6, replace = TRUE), nrow = 2)
m[, 1:5]                # ver 5 primeras tiradas
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    6    1    1    1    6
## [2,]    3    6    6    2    6
va <- apply(m, 2, max)  # máximo por columnas: 1e6 var. aleatorias
prop.table(table(va))   # estimación
## va
##        1        2        3        4        5        6 
## 0.027668 0.083708 0.138061 0.195239 0.249521 0.305803
c(1, 3, 5, 7, 9, 11)/36 # probabilidades reales
## [1] 0.02777778 0.08333333 0.13888889 0.19444444 0.25000000 0.30555556

Ejercicio. Haz que todas las columnas de una matriz m aparezcan ordenadas en orden creciente. Sugerencia: usa apply y sort.

set.seed(1)
(m <- matrix(sample(100, size = 12), nrow = 3))
##      [,1] [,2] [,3] [,4]
## [1,]   68   34   14   51
## [2,]   39   87   82   85
## [3,]    1   43   59   21
apply(m, 2, sort)
##      [,1] [,2] [,3] [,4]
## [1,]    1   34   14   21
## [2,]   39   43   59   51
## [3,]   68   87   82   85

Cuando se aplica apply por filas y el resultado de la función aplicada es un vector, la matriz de salida se rellena por columnas:

set.seed(1); (m <- matrix(sample(100, size = 12), nrow = 3))
##      [,1] [,2] [,3] [,4]
## [1,]   68   34   14   51
## [2,]   39   87   82   85
## [3,]    1   43   59   21
apply(m, 1, sort)
##      [,1] [,2] [,3]
## [1,]   14   39    1
## [2,]   34   82   21
## [3,]   51   85   43
## [4,]   68   87   59

Observa que la primera fila ordenada es la primera columna de la matriz que genera apply, la segunda fila ordenada la segunda columna y así sucesivamente. Si queremos la salida por filas, se puede calcular la traspuesta:

set.seed(1); (m <- matrix(sample(100, size = 12), nrow = 3))
##      [,1] [,2] [,3] [,4]
## [1,]   68   34   14   51
## [2,]   39   87   82   85
## [3,]    1   43   59   21
t(apply(m, 1, sort))
##      [,1] [,2] [,3] [,4]
## [1,]   14   34   51   68
## [2,]   39   82   85   87
## [3,]    1   21   43   59

La función apply también puede usarse con funciones definidas por el usuario. Aunque la definición de funciones se verá más adelante, vamos a estudiar un ejemplo. Supongamos que queremos restarle a cada elemento de una columna el mínimo de esa columna.

set.seed(5)
(m <- matrix(sample(10, size = 6), nrow = 2))
##      [,1] [,2] [,3]
## [1,]    2    7    1
## [2,]    9    3    6
apply(m, 2, function(col) col - min(col))
##      [,1] [,2] [,3]
## [1,]    0    4    0
## [2,]    7    0    5

La función definida en la invocación a apply recibe como parámetro un vector (con una columna de la matriz) y realiza el cálculo col - min(col), es decir, le resta a cada elemento del vector el mínimo del vector.

Vamos a seguir describiendo algunas funciones de R que trabajan con matrices. var calcula la matriz de covarianzas:

m <- matrix(rnorm(1e4, mean = 0, sd = 2), nrow = 2500)
var(m)
##             [,1]       [,2]        [,3]       [,4]
## [1,]  4.00528998 0.01746979 -0.06435747 0.04593452
## [2,]  0.01746979 4.00425351  0.01896810 0.06651531
## [3,] -0.06435747 0.01896810  3.95432971 0.02343102
## [4,]  0.04593452 0.06651531  0.02343102 3.96734850

var considera cada columna de la matriz como una muestra. Observa la salida: en la diagonal aparece la varianza de cada muestra y en la posición \((x, y)\) la covarianza entre las muestras de las columnas \(x\) e \(y\). En el ejemplo las varianzas tienen un valor cercano a 4 (son muestras de una \(\mathcal{N}(0, 2)\)) y las covarianzas son casi 0 (pues las muestras son independientes entre sí).

La función t calcula la traspuesta:

(m <- matrix(1:15, ncol = 5, byrow = TRUE))
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]    6    7    8    9   10
## [3,]   11   12   13   14   15
t(m)
##      [,1] [,2] [,3]
## [1,]    1    6   11
## [2,]    2    7   12
## [3,]    3    8   13
## [4,]    4    9   14
## [5,]    5   10   15

La función diag extrae la diagonal de una matriz:

(m <- matrix(1:9, ncol = 3, byrow = TRUE))
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
## [3,]    7    8    9
diag(m)
## [1] 1 5 9
diag(m) <- 0
m
##      [,1] [,2] [,3]
## [1,]    0    2    3
## [2,]    4    0    6
## [3,]    7    8    0

La función diag aplicada a un vector crea una matriz con dicho vector como diagonal:

diag(3:1)
##      [,1] [,2] [,3]
## [1,]    3    0    0
## [2,]    0    2    0
## [3,]    0    0    1

Las funciones det y determinant calculan el determinante de una matriz. La función solve resuelve un sistema de ecuaciones lineales. Por ejemplo, supongamos que queremos saber dónde intersectan las rectas \(2x + y -3\) y \(5x + 4y -7\):

m <- matrix(c(2, 5, 1, 4), nrow = 2)
b <- c(3, 7)
solve(m, b)
## [1]  1.6666667 -0.3333333

Vamos a comprobar gráficamente que el cálculo es correcto:

plot(solve(m, b)[1], solve(m, b)[2], pch = 19)
abline(3, -2, col = "blue")
abline(7/4, -5/4, col = "red")

6.5 Matrices lógicas

Al igual que ocurre con los vectores, se puede aplicar operadores relacionales para crear matrices lógicas.

(m <- matrix(runif(8), nrow = 2))
##           [,1]      [,2]      [,3]      [,4]
## [1,] 0.9582220 0.6041567 0.9787058 0.5667330
## [2,] 0.3893406 0.7511718 0.3811577 0.3753636
m >= 0.75
##       [,1]  [,2]  [,3]  [,4]
## [1,]  TRUE FALSE  TRUE FALSE
## [2,] FALSE  TRUE FALSE FALSE

Podemos contar y calcular la proporción de los elementos de una matriz que verifican una condición:

sum(m >= 0.75)  # ¿Cuántos elementos de m son >= 0.75?
## [1] 3
mean(m >= 0.75) # ¿Y la proporción?
## [1] 0.375

También podemos aplicar los operadores lógicos (&, | y !) a matrices lógicas, así como las funciones any y all, de la misma forma que se estudió con los vectores.

# Se simula lanzar 4 veces dos dados
(m <- matrix(sample(6, size = 8, replace = TRUE), nrow = 2))
##      [,1] [,2] [,3] [,4]
## [1,]    1    3    6    4
## [2,]    5    2    3    3
sum(m == 5 | m == 3) # ¿Cuántas veces sale el 3 o el 5?
## [1] 4
any(m == 6)          # ¿Ha salido el 6 al menos una vez?
## [1] TRUE
all(m != 1)          # ¿No ha salido el 1?
## [1] FALSE

Ejemplo. Una bolsa contiene 3 bolas rojas y 5 negras. Si extraemos 3 bolas de la bolsa al azar, ¿cuál es la probabilidad de que 2 de ellas sean negras y la otra roja? Esta probabilidad es:

\[ \frac{\binom{5}{2}\binom{3}{1}}{\binom{8}{3}} \]

que se puede calcular en R así:

choose(5, 2) * choose(3, 1) / choose(8, 3)
## [1] 0.5357143

Vamos a realizar una simulación para estimar esta probabilidad: repetiremos el experimento aleatorio de extraer 3 bolas al azar un millón de veces y calcularemos en qué proporción de los experimentos había 2 bolas negras y una roja. Para ello vamos a utilizar la función replicate, que permite ejecutar una expresión varias veces:

bolsa <- rep(c("r", "n"), times = c(3, 5)) # simula la bolsa
# simula un millón de extracciones
s <- replicate(1e6, sample(bolsa, size = 3)) 

Como la expresión usada en replicate (sample(bolsa, size = 3)) genera un vector de tamaño 3, el resultado de cada ejecucion se almacena en una columna distinta de una matriz de salida, cuyo tamaño es \(3x10^6\). Es decir, cada columna de la matriz de salida es un experimento. Ahora calculamos la proporción de experimentos en los que ha ocurrido el evento:

s <- colSums(s == "r")
mean(s == 1)                               # probabilidad estimada
## [1] 0.536005
choose(5, 2) * choose(3, 1) / choose(8, 3) # probabilidad real
## [1] 0.5357143

La primera línea convierte a s, la matriz de salida, en un vector. Cada elemento del vector indica cuántas bolas rojas hay en ese experimento. La segunda línea calcula la proporción de experimentos en los que ha salido exactamente una bola roja y, por tanto, dos negras. O sea, la segunda línea calcula la probabilidad estimada.

La función replicate nos ofrece una alternativa para realizar simulaciones. Por ejemplo, supongamos que queremos simular el lanzamiento de dos dados 5 veces

matrix(sample(6, size = 10, replace = TRUE), nrow = 2)
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    5    3    1    3    4
## [2,]    6    3    2    5    1
# alternativa usando replicate
replicate(5, sample(6, size = 2, replace = TRUE))
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    3    1    5    1
## [2,]    4    3    6    5    4

Cuando se quiere repetir un experimento sin reemplazo, entonces la solución es usar replicate, como, por ejemplo, en el ejemplo previo de extraer al azar 3 bolas de una urna con 8 bolas varias veces.

6.6 Indexación de matrices

La indexación de matrices es similar a la de los vectores, pero es preciso indicar los índices de las dos dimensiones separados por una coma. Si alguna dimensión se deja vacía se seleccionan todos los elementos de dicha dimensión. Vamos a estudiar los 6 tipos de indexación que admiten las matrices. Los cuatro primeros son análogos a los usados en los vectores. Hay que tener en cuenta que con las matrices hay que especificar índices para las filas y para las columnas, con lo que se puede mezclar dos tipos de indexación, por ejemplo, indexar las filas con un vector lógico y las columnas con un vector numérico. En casi todos los ejemplos de esta sección las matrices son indexadas para consultar sus valores, pero también es posible indexar una matriz para modificarla.

6.6.1 Mediante un vector de enteros positivos

Se seleccionan los índices indicados en el vector:

(m <- matrix(1:16, nrow = 4))
##      [,1] [,2] [,3] [,4]
## [1,]    1    5    9   13
## [2,]    2    6   10   14
## [3,]    3    7   11   15
## [4,]    4    8   12   16
m[c(1, 3), 3:4]  # filas 1 y 3, columnas 3 y 4
##      [,1] [,2]
## [1,]    9   13
## [2,]   11   15
m[2, 2]          # fila 2, columna 2
## [1] 6
m[3:4, ]         # filas 3 y 4 (todas las columnas)
##      [,1] [,2] [,3] [,4]
## [1,]    3    7   11   15
## [2,]    4    8   12   16

6.6.2 Mediante un vector de enteros negativos

El vector indica los índices no seleccionados, en lugar de los seleccionados:

(m <- matrix(1:16, nrow = 4))
##      [,1] [,2] [,3] [,4]
## [1,]    1    5    9   13
## [2,]    2    6   10   14
## [3,]    3    7   11   15
## [4,]    4    8   12   16
m[, -1]  # todas las columnas, salvo la primera
##      [,1] [,2] [,3]
## [1,]    5    9   13
## [2,]    6   10   14
## [3,]    7   11   15
## [4,]    8   12   16
m[1:2, -c(1, ncol(m))] # filas 1 y 2. Todas las columnas salvo la primera y la última
##      [,1] [,2]
## [1,]    5    9
## [2,]    6   10

6.6.3 Mediante valores lógicos

Es muy útil, pues permite filtrar aquellas filas o columnas que verifiquen una condición. Por ejemplo:

(m <- matrix(1:16, nrow = 4))
##      [,1] [,2] [,3] [,4]
## [1,]    1    5    9   13
## [2,]    2    6   10   14
## [3,]    3    7   11   15
## [4,]    4    8   12   16
m[m[, 1] %% 2 == 0, ]
##      [,1] [,2] [,3] [,4]
## [1,]    2    6   10   14
## [2,]    4    8   12   16

Selecciona las filas en las que la columna 1 tiene un valor par. Veamos otro ejemplo con una condición un poco más compleja: vamos a seleccionar aquellas columnas de una matriz que no tienen valores NA.

(m <- matrix(sample(c(NA, 1:3), size = 12, replace = TRUE), nrow = 2))
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,]    2    1    2    1   NA    2
## [2,]    2    2    2    1    3   NA
condicion <- apply(is.na(m), 2, any)
m[, !condicion]
##      [,1] [,2] [,3] [,4]
## [1,]    2    1    2    1
## [2,]    2    2    2    1

También se puede usar una matriz lógica para indexar. Por ejemplo:

(m <- matrix(sample(c(1:3, NA), size = 12, replace = TRUE), nrow = 3))
##      [,1] [,2] [,3] [,4]
## [1,]    3   NA    1    1
## [2,]    3    1   NA   NA
## [3,]    2    2    2   NA
is.na(m) # produce una matriz lógica
##       [,1]  [,2]  [,3]  [,4]
## [1,] FALSE  TRUE FALSE FALSE
## [2,] FALSE FALSE  TRUE  TRUE
## [3,] FALSE FALSE FALSE  TRUE
m[is.na(m)] <- 0 # indexa con una matriz lógica
m
##      [,1] [,2] [,3] [,4]
## [1,]    3    0    1    1
## [2,]    3    1    0    0
## [3,]    2    2    2    0

6.6.4 Mediante un vector de cadenas de caracteres

Para usar esta posibilidad las filas y/o columnas deben tener nombres asociados:

notas <- matrix(runif(9, 1, 10), nrow = 3)
rownames(notas) <- c("Juan", "María", "Jaime")
colnames(notas) <- paste("Examen", 1:3)
notas
##       Examen 1 Examen 2 Examen 3
## Juan  7.821840 2.014123 4.571020
## María 9.860010 4.396650 5.968692
## Jaime 4.693878 2.406851 7.745703
notas[, "Examen 3"] # sólo el examen 3
##     Juan    María    Jaime 
## 4.571020 5.968692 7.745703
notas[c("Juan", "Jaime"), ]
##       Examen 1 Examen 2 Examen 3
## Juan  7.821840 2.014123 4.571020
## Jaime 4.693878 2.406851 7.745703

Observa que en el primer ejemplo, al seleccionarse una única columna, R devuelve un vector en lugar de una matriz. Más adelante comentaremos cómo podemos hacer que R devuelva una matriz con una única columna.

6.6.5 Indexación lineal

Aunque no se use habitualmente, una matriz puede ser indexada con un único vector de índices. El resultado de esta indexación es un vector con los elementos de la matriz que ocupan los índices lineales indicados. Los índices lineales hacen referencia al vector asociado a la matriz, éste se puede obtener con la función c.

(m <- matrix(sample(10, 6), nrow = 2))
##      [,1] [,2] [,3]
## [1,]    9    7    5
## [2,]    3    1   10
c(m)  # vector asociado a m
## [1]  9  3  7  1  5 10

Como vemos, el vector asociado se construye concatenando las columnas de la matriz, y es a este vector al que se le aplica la indexación lineal:

m[c(1, 3, length(m))] # elementos 1, 3 y último de la versión lineal de m
## [1]  9  7 10

6.6.6 Mediante matriz de índices

Por último, una matriz se puede indexar con una matriz de dos columnas cuyas filas indican los distintos índices a indexar.

(m <- matrix(sample(100, 10), nrow = 2))
##      [,1] [,2] [,3] [,4] [,5]
## [1,]   59    6   85   11   55
## [2,]   47   26   87   71   84
i <- matrix(c(1, 1,
        1, 5,
        2, 3), ncol = 2, byrow = TRUE)
i # matriz con los índices a los que se quiere acceder
##      [,1] [,2]
## [1,]    1    1
## [2,]    1    5
## [3,]    2    3
m[i]
## [1] 59 55 87

6.7 Indexado que genera una única fila o columna

Si al indexar una matriz el resultado tiene una única fila o columna, R, por defecto, devuelve un vector con la fila o columna seleccionada, en lugar de devolver una matriz. Este comportamiento por defecto no es quizás lo más indicado. En el siguiente código se ejemplifica cómo cambiar el comportamiento por defecto, para poder obtener una matriz:

(m <- matrix(1:10, nrow = 2))
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    3    5    7    9
## [2,]    2    4    6    8   10
m[1, ] # fila 1, se obtiene un vector
## [1] 1 3 5 7 9
m[, 2] # columna 2, se obtiene un vector
## [1] 3 4
m[1, , drop = FALSE] # se obtiene una matriz
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    3    5    7    9
m[, 2, drop = FALSE] # se obtiene una matriz
##      [,1]
## [1,]    3
## [2,]    4

6.8 Concatenación de matrices

Se puede concatenar verticalmente matrices con el mismo número de columnas con rbind. Para concatenar horizontalmente matrices con el mismo número de filas se usa la función cbind:

(m1 <- matrix(c(1, 1, 2, 2), nrow = 2))
##      [,1] [,2]
## [1,]    1    2
## [2,]    1    2
(m2 <- m1 ^ 2)
##      [,1] [,2]
## [1,]    1    4
## [2,]    1    4
cbind(m1, m2)     # cbind concatena las columnas
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    1    4
## [2,]    1    2    1    4
rbind(m1, m1 * 3) # rbind concatena las filas
##      [,1] [,2]
## [1,]    1    2
## [2,]    1    2
## [3,]    3    6
## [4,]    3    6

Las funciones cbind y rbind admiten el reciclado:

(m <- matrix(1:6, nrow = 3))
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6
cbind(m , 0) # añade una columna de ceros
##      [,1] [,2] [,3]
## [1,]    1    4    0
## [2,]    2    5    0
## [3,]    3    6    0

También se puede usar rbind y cbind para crear una matriz concatenando vectores:

cbind(1:3, 4:6)
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6

6.9 Arrays

Los vectores y matrices son un caso particular de arrays. Los vectores son arrays de una dimensión y las matrices de dos dimensiones. Dado que los arrays de 3 o más dimensiones se utilizan con poca frecuencia no los vamos a cubrir en estos apuntes. Sólo vamos a ver un ejemplo. Supongamos que queremos almacenar las 3 notas de n alumnos en 2 semestres. Esto se puede almacenar de una forma ordenada en un array tridimensional nx3x2:

n <- 5
notas <- array(runif(n*3*2, max = 10), dim = c(n, 3, 2))
notas
## , , 1
## 
##          [,1]     [,2]     [,3]
## [1,] 2.025852 3.481609 2.501262
## [2,] 6.475442 1.413403 4.852748
## [3,] 5.689355 4.902850 1.078265
## [4,] 2.932403 7.832303 8.690019
## [5,] 7.226437 3.107789 6.202055
## 
## , , 2
## 
##            [,1]       [,2]     [,3]
## [1,] 3.33405509 3.33707212 0.991434
## [2,] 3.99848249 1.67320594 5.687474
## [3,] 9.37977419 0.67608627 4.656393
## [4,] 4.29522662 0.06766415 4.145739
## [5,] 0.06464175 1.37047550 2.071032
notas[, , 1]  # notas del primer semestre
##          [,1]     [,2]     [,3]
## [1,] 2.025852 3.481609 2.501262
## [2,] 6.475442 1.413403 4.852748
## [3,] 5.689355 4.902850 1.078265
## [4,] 2.932403 7.832303 8.690019
## [5,] 7.226437 3.107789 6.202055
notas[1, , 2] # notas del alumno 1 en el segundo semestre
## [1] 3.334055 3.337072 0.991434

6.10 Ejercicios

  1. Crea una matriz 5x5 cuya primera fila sean unos, la segunda doses y así sucesivamente.

  2. Crea la matriz matrix(sample(100, size = 12), nrow = 3) y haz que todas las columnas aparezcan ordenadas.

  3. Crea una matriz con matrix(rnorm(15), nrow = 5). Selecciona las filas de la matriz cuya suma es positiva.

  4. Haz un pequeño programa que solicite un entero y cree una matriz de tres columnas con su tabla de multiplicar. Por ejemplo, si se hubiera introducido el 3 se crearía la matriz (se muestran sólo las tres primeras filas):

    3 1 3
    3 2 6
    3 3 9

  5. Dada una matriz, calcula el índice de la fila cuya suma de elementos es mayor. Sugerencia: usa apply (o rowSums) y la función which.max, que devuelve el índice del primer máximo de un vector numérico.

  6. Una matriz cuadrada es simétrica si es igual a su traspuesta. Escribe una expresión que dada una matriz m genere un valor lógico indicando si m es simétrica. Nota: puedes usar la función all.

  7. Dada la matriz obtenida con matrix(rnorm(10), nrow = 2), genera una expresión para calcular la cantidad de valores mayores que 0 en la matriz y otra expresión para determinar si hay más valores positivos que negativos en la matriz.

  8. Cada fila de una matriz representa el resultado de un alumno en 3 exámenes:

    set.seed(2)
    (m <- matrix(sample(1:10, size = 12, replace = TRUE), nrow = 4))
    ##      [,1] [,2] [,3]
    ## [1,]    5    1    1
    ## [2,]    6    1    3
    ## [3,]    6    9    6
    ## [4,]    8    2    2

    Calcula la nota final de los alumnos si la ponderación de los exámenes es 0.2, 0.3 y 0.5 respectivamente.

  9. Dada la matriz:

    set.seed(5)
    m <- matrix(rnorm(15), nrow = 5)

    Selecciona las filas de la matriz cuya suma es positiva.

  10. Dada una matriz, añádele una columna con la suma de los elementos de cada fila de la matriz original.

  11. Dada una matriz, obtén otra matriz formada por una permutación de sus filas.

6.11 Soluciones a los ejercicios de matrices

# Matriz con 1 en primera filas, 2 en segunda, ...
matrix(1:5, nrow = 5, ncol = 5) # usando reciclado
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    1    1    1    1
## [2,]    2    2    2    2    2
## [3,]    3    3    3    3    3
## [4,]    4    4    4    4    4
## [5,]    5    5    5    5    5
# Ordenar las columnas de una matriz
(m <- matrix(sample(100, size = 12), nrow = 3))
##      [,1] [,2] [,3] [,4]
## [1,]   90   56   79   46
## [2,]   51   92   23   18
## [3,]   54   13   98   80
apply(m, 2, sort)
##      [,1] [,2] [,3] [,4]
## [1,]   51   13   23   18
## [2,]   54   56   79   46
## [3,]   90   92   98   80
# Filtrar filas cuya suma es positiva
(m <- matrix(rnorm(15), nrow = 5))
##            [,1]       [,2]       [,3]
## [1,]  0.1251427 -0.3024286  1.2373051
## [2,] -1.4869821 -0.6511225 -1.0054358
## [3,]  0.3392875 -1.0966331  0.9421038
## [4,]  0.1668925 -0.9394679  0.5749584
## [5,]  1.9752061  2.3021163  0.9759743
m[rowSums(m) > 0, ]
##           [,1]       [,2]      [,3]
## [1,] 0.1251427 -0.3024286 1.2373051
## [2,] 0.3392875 -1.0966331 0.9421038
## [3,] 1.9752061  2.3021163 0.9759743
# Tabla de multiplicar
n <- as.integer(readline("Número: "))
matrix(c(rep(n, 10), 1:10, 1:10 * n), nrow = 10)
cbind(n, 1:10, 1:10 * n) # otra posibilidad
# Índice de la fila cuya suma es mayor
(m <- matrix(sample(100, size = 12), nrow = 3))
##      [,1] [,2] [,3] [,4]
## [1,]   76   97   20    4
## [2,]   60   15   11    3
## [3,]   90   81    9   28
which.max(rowSums(m))
## [1] 3
# Comprobar si una matriz es simétrica
(m <- matrix(c(1, 2, 4, 2, 1, 6, 4, 6, 7), nrow = 3))
##      [,1] [,2] [,3]
## [1,]    1    2    4
## [2,]    2    1    6
## [3,]    4    6    7
ncol(m) == nrow(m) && all(m == t(m))
## [1] TRUE
m[1, 3] <- 0
ncol(m) == nrow(m) && all(m == t(m))
## [1] FALSE
# Contar valores que verifican una condición
(m <- matrix(rnorm(10), nrow = 2))
##            [,1]        [,2]      [,3]       [,4]       [,5]
## [1,] -0.4602446 -0.06921116 0.1877261 -0.5918348 -0.9249531
## [2,] -0.7243285  1.46324856 1.0220229 -0.1122007  0.7533048
sum(m > 0) # cantidad de valores mayores que 0
## [1] 4
sum(m > 0) > sum(m < 0) # ¿más valores positivos que negativos?
## [1] FALSE
# Calcular la nota final ponderada
set.seed(2)
(m <- matrix(sample(1:10, size = 12, replace = TRUE), nrow = 4))
##      [,1] [,2] [,3]
## [1,]    5    1    1
## [2,]    6    1    3
## [3,]    6    9    6
## [4,]    8    2    2
m[, 1] * 0.2 + m [, 2] * 0.3 + m[, 3] * 0.5
## [1] 1.8 3.0 6.9 3.2
c(0.2, 0.3, 0.5) %*% t(m) # otra posibilidad, usando producto de matrices
##      [,1] [,2] [,3] [,4]
## [1,]  1.8    3  6.9  3.2
# Seleccionar las filas de una matriz cuya suma es positiva
set.seed(5)
(m <- matrix(rnorm(15), nrow = 5))
##             [,1]       [,2]       [,3]
## [1,] -0.84085548 -0.6029080  1.2276303
## [2,]  1.38435934 -0.4721664 -0.8017795
## [3,] -1.25549186 -0.6353713 -1.0803926
## [4,]  0.07014277 -0.2857736 -0.1575344
## [5,]  1.71144087  0.1381082 -1.0717600
m[rowSums(m) > 0, ]
##          [,1]       [,2]       [,3]
## [1,] 1.384359 -0.4721664 -0.8017795
## [2,] 1.711441  0.1381082 -1.0717600
# Añadir una columna con la suma de las filas
(m <- matrix(sample(0:1, size = 9, replace = TRUE), nrow = 3))
##      [,1] [,2] [,3]
## [1,]    1    1    1
## [2,]    0    1    0
## [3,]    0    1    0
m <- cbind(m, rowSums(m))
m
##      [,1] [,2] [,3] [,4]
## [1,]    1    1    1    3
## [2,]    0    1    0    1
## [3,]    0    1    0    1
# La función addmargins sirve para este propósito
(m <- matrix(sample(0:1, size = 9, replace = TRUE), nrow = 3))
##      [,1] [,2] [,3]
## [1,]    0    1    1
## [2,]    0    1    0
## [3,]    1    1    1
addmargins(m)
##           Sum
##     0 1 1   2
##     0 1 0   1
##     1 1 1   3
## Sum 1 3 2   6
# Permutación de las filas de una matriz
(m <- matrix(1:10, ncol = 2, byrow = TRUE))
##      [,1] [,2]
## [1,]    1    2
## [2,]    3    4
## [3,]    5    6
## [4,]    7    8
## [5,]    9   10
permutacion <- sample(nrow(m))
m[permutacion, ]
##      [,1] [,2]
## [1,]    3    4
## [2,]    7    8
## [3,]    1    2
## [4,]    5    6
## [5,]    9   10