Ao lidar com coleções de dados atômicos homogêneos (por exemplo, todos numéricos ou todos do tipo caractere), as seguintes estruturas estão disponíveis no R:
vector para dados unidimensionais, acessados com um único índice.matrix para dados bidimensionais, retangulares (e 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 seçã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 isso na linguagem base).
Uma matriz é, essencialmente, apenas um vetor, mais a contagem de linhas e colunas que define o formato.
Existe uma função matrix() para criá-las:
# 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
Repare que os dados são armazenados em ordem por colunas (ou seja, de cima para baixo e só depois para a coluna seguinte). Isso é igual ao Fortran, ao Matlab e ao Julia, mas é diferente de C/C++ e do NumPy.
Descubra as dimensões de uma matriz com dim(), que dá linhas e colunas ao mesmo tempo, ou com nrow() e ncol() para obter uma única dimensão.
Usar length() apenas retorna o comprimento do vetor subjacente: o número total de elementos (6 no exemplo acima).
Algumas outras funções produzem uma matriz como saída, 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"
Isso funciona exatamente como a indexação de vetores, com a diferença de que é preciso especificar a(s) linha(s) e a(s) coluna(s), nessa ordem. Deixe uma dimensão vazia para obter a linha ou a coluna inteira.
Usando a mesma matriz do exemplo anterior:
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
Repare que um subconjunto com uma única linha e/ou coluna é retornado como vetor, e não como matriz.
class(m)
#> [1] "matrix" "array"
class(m[2,])
#> [1] "integer"
Esse tipo de dimension reduction nem sempre é o que você quer, principalmente em um pipeline de funções que espera receber matrizes.
Especificar drop=FALSE garante que uma matriz de entrada continue sendo uma matriz na saída.
Repare 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 em uma matriz de uma única coluna.
as.matrix(1:3)
#> [,1]
#> [1,] 1
#> [2,] 2
#> [3,] 3
Assim como acontece com 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
No caso de vetores, a função c() bastava para inserir e acrescentar dados.
As matrizes têm mais estrutura, então exigem mais cuidado ao especificar o que você quer.
As funções rbind() e cbind() anexam 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
Para remover uma linha ou uma coluna, usa-se indexação negativa, como no caso dos 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 vetor também funcionam em matrizes, aplicadas a todos os elementos. No entanto, elas podem perder a estrutura de matriz e retornar 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
No caso de matrizes, as funções colSums() e rowSums() também podem ser úteis.
Aqui a redução de dimensão é intencional, então os valores retornados são vetores.
# m as defined above
rowSums(m)
#> [1] 9 12
colSums(m)
#> [1] 3 7 11
Do mesmo modo, existem as funções rowMeans() e colMeans() para calcular médias.
Em outros conceitos, vimos que as bibliotecas do Tidyverse podem simplificar operações com vetores e listas (purrr) ou com dataframes (dplyr).
No entanto, matrizes e arrays estão, em boa parte, fora do escopo das entradas do Tidyverse (pelo menos por enquanto).
A função apply() faz parte do R base e continua sendo a melhor ferramenta para ter um controle mais fino sobre a programação com matrizes.
Os parâmetros, na ordem, são:
A função rowMeans() existe no R base, mas também poderíamos simulá-la 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 seção pressupõe um conhecimento de matemática que está fora do escopo do Exercism.
Pule esta parte se ela não fizer sentido para você.
No entanto, o R é uma linguagem de ciência de dados, e muitos usuários têm formação em matemática aplicada e estatística. Este resumo breve pode interessar a alguns estudantes.
O R moderno é, "obviamente", uma linguagem de ciência de dados: feita para trabalhar principalmente com dados tabulares e popular entre usuários como estatísticos, epidemiologistas e cientistas sociais. O principal concorrente nessa área é o ecossistema científico do Python, com pacotes como Pandas ou Polars.
No entanto, em décadas passadas o R também era visto como uma possível linguagem de engenharia, competindo diretamente com o Matlab.
Talvez para surpresa de muitos, o R base ainda dá suporte a toda uma gama de operações de álgebra linear com vetores e matrizes. Não está claro o quanto elas aparecem hoje no código de quem usa a linguagem, mas essas técnicas continuam muito importantes internamente para implementar funções estatísticas.
Álgebra linear em R ainda é ensinada em alguns cursos universitários, como esta aula de econometria. Um bom guia da matemática (não da programação) está no 3Blue1Brown.
A discussão mais completa sobre álgebra linear dentro do Exercism está no syllabus de Julia (dos mesmos autores do syllabus de R).
Acreditamos que Julia (ou Python, em contextos em que o módulo numpy.linalg está disponível) seria hoje uma linguagem mais apropriada do que o R para trabalhar com álgebra linear a sério.
A diagonal principal de uma matriz quadrada (N x N)) A vai do canto superior esquerdo (A[1,1]) até o canto inferior direito (A[N,N]).
A função diag() se comporta de maneira diferente, dependendo do argumento que recebe.
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) retorna 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 você esperaria.
Até livros sérios de R costumam acrescentar comentários como "vá entender" neste ponto.
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.
Use Conj(t(A)) para obter a transposta conjugada (adjunta) da matriz A.
Para arrays de dimensões maiores, aperm() permuta as dimensões.
Para a inversa de uma matriz quadrada A, podemos usar solve(A).
Para alguns de nós, essa sintaxe é ... inesperada.
Multiplicar uma matriz pela sua inversa dá a matriz identidade (a menos de 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 usuais, como +, -, * e ^, operam sobre matrizes elemento a elemento.
Se houver incompatibilidade de dimensões, valem as regras usuais 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 outros tipos de multiplicação de vetores e matrizes está além do escopo desta página, mas, em resumo bem 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.