Quando trabalhas com coleções de dados atómicos homogéneos (por exemplo, todos numéricos ou todos de caráter), estão disponíveis em R as seguintes estruturas:
vector para dados unidimensionais, acedidos com um único índice.matrix para dados bidimensionais retangulares (não irregulares).array para o caso geral de N dimensões. Uma matrix é apenas um array com N=2.Para quem programa em Python: Esta secção pode parecer familiar.
Qualquer semelhança com o NumPy não é coincidência (mas o R é um pouco mais antigo e já tem tudo isto na linguagem base).
Uma matriz é essencialmente apenas um vetor, mais o número de linhas e de colunas para definir a forma.
Existe uma função matrix() para as criar:
# 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
Repara que os dados são armazenados em ordem por colunas (ou seja, primeiro para baixo, depois para o lado). Isto é o mesmo que em Fortran, Matlab e Julia, mas é diferente de C/C++ e NumPy.
Descobre as dimensões de uma matriz com dim() para obter linhas e colunas, ou com nrow() ou ncol() para obter uma única dimensão.
Usar length() devolve apenas o comprimento do vetor subjacente: o número total de elementos (6 no exemplo acima).
Algumas outras funções produzem resultados em forma de matriz, por exemplo 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"
Isto funciona exatamente da mesma forma que a indexação de vetores, exceto pela necessidade de especificar a(s) linha(s) e a(s) coluna(s), por essa ordem. Deixa uma dimensão vazia para obteres a linha ou a coluna inteira.
Usando a mesma matriz que acima:
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
Repara que um subconjunto com uma única linha e/ou coluna é devolvido como um vetor, não como uma matriz.
class(m)
#> [1] "matrix" "array"
class(m[2,])
#> [1] "integer"
Este tipo de dimension reduction nem sempre é desejado, especialmente numa cadeia de funções que espera receber matrizes.
Especificar drop=FALSE garante que uma matriz à entrada devolve uma matriz.
Repara que uma matriz com apenas uma linha ou uma coluna não é do mesmo tipo que um vetor.
m[2, , drop = FALSE]
#> [,1] [,2] [,3]
#> [1,] 2 4 6
A função as.matrix() converte um vetor numa matriz de uma só coluna.
as.matrix(1:3)
#> [,1]
#> [1,] 1
#> [2,] 2
#> [3,] 3
Tal como com os vetores, partes de uma matriz podem ser modificadas simplesmente atribuindo novos valores.
m[, 2] <- c(10, 20)
m
#> [,1] [,2] [,3]
#> [1,] 1 10 5
#> [2,] 2 20 6
Com vetores, a função c() era suficiente para inserir e acrescentar dados.
As matrizes têm mais estrutura, por isso é preciso mais cuidado a especificar o que é necessário.
As funções rbind() e cbind() ligam novas linhas ou colunas à 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
Eliminar uma linha ou coluna usa indexação negativa, tal como nos vetores.
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m[, -2]
#> [,1] [,2]
#> [1,] 1 5
#> [2,] 2 6
Todas as funções de vetores também funcionam em matrizes, aplicadas a todos os elementos. No entanto, podem perder a estrutura de matriz e devolver um vetor.
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
Para matrizes, as funções colSums() e rowSums() também podem ser úteis.
Neste caso, a redução de dimensões é intencional, por isso os valores devolvidos são vetores.
# m as defined above
rowSums(m)
#> [1] 9 12
colSums(m)
#> [1] 3 7 11
De forma semelhante, existem as funções rowMeans() e colMeans() para calcular médias.
Noutros conceitos, vimos que as bibliotecas do Tidyverse podem simplificar operações sobre vetores e listas (purrr) ou dataframes (dplyr).
No entanto, matrizes e arrays estão, na sua maioria, fora do âmbito das entradas do Tidyverse (pelo menos para já).
A função apply() faz parte do R base e continua a ser a melhor ferramenta para um controlo mais fino sobre a programação com matrizes.
Os parâmetros, por ordem, são:
A função rowMeans() existe no R base, mas também a podíamos simular com apply() e 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 secção pressupõe um nível de conhecimentos matemáticos fora do âmbito do Exercism.
Salta-a se não fizer sentido para ti.
No entanto, o R é uma linguagem de ciência de dados, e muitos utilizadores têm formação em matemática aplicada e estatística. Este resumo conciso pode ser do interesse de alguns estudantes.
O R moderno é "obviamente" uma linguagem de ciência de dados: concebido para trabalhar principalmente com dados tabulares e popular entre utilizadores como estatísticos, epidemiologistas e cientistas sociais. O principal concorrente nesta área é o ecossistema científico do Python, que inclui pacotes como o Pandas ou o Polars.
No entanto, nas décadas anteriores, o R também era visto como uma potencial linguagem de engenharia, competindo diretamente com o Matlab.
Talvez surpreendentemente, o R base ainda suporta toda uma gama de operações de álgebra linear sobre vetores e matrizes. Não é claro até que ponto estas ainda aparecem no código do utilizador final, mas estas técnicas continuam a ser muito importantes internamente para implementar funções estatísticas.
A álgebra linear em R ainda é ensinada em alguns cursos universitários, como esta aula de econometria. Um bom guia para a matemática (não para a programação) está em 3Blue1Brown.
A discussão mais completa sobre álgebra linear no Exercism está no programa de estudos de Julia (os mesmos autores do programa de estudos de R).
Acreditamos que a Julia (ou o Python, em contextos em que o módulo numpy.linalg está disponível) seria agora uma linguagem mais adequada para trabalho sério de álgebra linear do que o R.
A diagonal principal de uma matriz quadrada (N x N)) A vai do canto superior esquerdo (A[1,1]) ao canto inferior direito (A[N,N]).
A função diag() comporta-se de forma diferente consoante o argumento que lhe é passado.
Com um vetor v, diag(v) cria uma matriz diagonal.
diag(1:3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 2 0
#> [3,] 0 0 3
Com uma matriz quadrada A, diag(A) devolve os elementos da diagonal principal como um vetor.
A
#> [,1] [,2] [,3]
#> [1,] 10 5 2
#> [2,] 9 3 7
#> [3,] 8 10 4
diag(A)
#> [1] 10 3 4
Com um único número inteiro k, diag(k) cria uma matriz identidade k × k: talvez não seja o que adivinharias.
Até os manuais de R mais sérios tendem a acrescentar comentários como "Vai-se lá saber" nesta altura.
diag(3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 1 0
#> [3,] 0 0 1
Para trocar linhas e colunas, a função t() transpõe qualquer 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 uma matriz de valores complexos, o resultado não é o conjugado complexo.
Usa Conj(t(A)) para obter a transposta conjugada (adjunta) da matriz A.
Para arrays de dimensão superior, aperm() permuta as dimensões.
Para a inversa de uma matriz quadrada A, podemos usar solve(A).
Alguns de nós acham esta sintaxe ... inesperada.
Multiplicar uma matriz pela sua inversa dá a matriz identidade (sujeito a erros de arredondamento muito pequenos).
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
Os operadores aritméticos habituais, como +, -, *, ^, operam sobre matrizes elemento a elemento.
Se houver uma incompatibilidade de dimensões, aplicam-se as regras habituais de reciclagem.
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 os vários outros tipos de multiplicação de vetores e matrizes está fora do nosso âmbito, mas, em resumo muito breve:
%*% é a forma de operador infixo de matmult(), que dá o produto matricial.%o% é a forma de operador infixo de outer(), que dá o produto externo.crossprod() dá o produto vetorial.