Треки
/
R
R
/
Салабус
/
Матриці та масиви
Ма

Матриці та масиви у R

{one: "1 вправа", few: "%{count} вправи", many: "%{count} вправ", other: "%{count} вправи"}

Про концепцію Матриці та масиви

Коли ми працюємо з колекціями однорідних атомарних даних (наприклад, усі числові або всі символьні), у R доступні такі структури:

  • vector для одновимірних даних, доступ до яких отримують за одним індексом.
  • matrix для двовимірних прямокутних (не «рваних») даних.
  • array для загального N-вимірного випадку. matrix - це просто array з N=2.
Note

Для програмістів Python: цей розділ може здатися знайомим.

Будь-яка схожість із NumPy не випадкова (але R трохи старша і має все це в базовій мові).

Матриці

Матриця, по суті, - це просто вектор плюс кількість рядків і стовпців, що задають її форму.

Для їх створення є функція matrix():

# 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

Зауважмо, що дані зберігаються в порядку за стовпцями (тобто спершу вниз, потім упоперек). Це так само, як у Fortran, Matlab і Julia, але інакше, ніж у C/C++ та NumPy.

Розмірність матриці можна знайти за допомогою dim(), яка повертає і кількість рядків, і кількість стовпців, а nrow() чи ncol() дають лише одну з них. Натомість length() поверне просто довжину базового вектора: загальну кількість елементів (6 у прикладі вище).

Деякі інші функції теж створюють матриці на виході, наприклад 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" 

Індексування матриць

Воно працює так само, як індексування векторів, хіба що потрібно вказати рядок (рядки) і стовпець (стовпці) саме в такому порядку. Якщо залишити якийсь вимір порожнім, отримаємо весь рядок або весь стовпець.

Скористаймося тією самою матрицею, що й вище:

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

Зауважмо, що підмножина з одним рядком і/або одним стовпцем повернеться як вектор, а не як матриця.

class(m)
#> [1] "matrix" "array" 
class(m[2,])
#> [1] "integer"

Таке dimension reduction потрібне не завжди, особливо в конвеєрі функцій, який приймає на вході матриці.

Якщо вказати drop=FALSE, результатом матричного входу гарантовано буде матриця. Зауважмо, що матриця лише з одним рядком чи одним стовпцем не того самого типу, що й вектор.

m[2, , drop = FALSE]
#>      [,1] [,2] [,3]
#> [1,]    2    4    6

Функція as.matrix() перетворює вектор на матрицю з одним стовпцем.

as.matrix(1:3)
#>      [,1]
#> [1,]    1
#> [2,]    2
#> [3,]    3

Як і з векторами, частини матриці можна змінювати, просто присвоюючи їм нові значення.

m[, 2] <- c(10, 20)
m
#>      [,1] [,2] [,3]
#> [1,]    1   10    5
#> [2,]    2   20    6

Додавання та вилучення рядків і стовпців

Для векторів функції c() було достатньо, щоб вставляти й додавати дані. Матриці мають складнішу структуру, тому потрібно уважніше вказувати, що саме потрібно.

Функції rbind() і cbind() приєднують до матриці новий рядок (рядки) або новий стовпець (стовпці).

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

Для вилучення рядка чи стовпця використовують відʼємне індексування, як і для векторів.

m
#>      [,1] [,2] [,3]
#> [1,]    1    3    5
#> [2,]    2    4    6

m[, -2]
     #> [,1] [,2]
#> [1,]    1    5
#> [2,]    2    6

Фільтрування та функції

Усі векторні функції працюють і з матрицями, застосовуючись до всіх елементів. Однак вони можуть втратити структуру матриці й повернути вектор.

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

Для матриць також можуть стати в пригоді функції colSums() і rowSums().

У цьому випадку зменшення розмірності навмисне, тому повернені значення є векторами.

# m as defined above
rowSums(m)
#> [1]  9 12
colSums(m)
#> [1]  3  7 11

Так само є функції rowMeans() і colMeans() для обчислення середніх значень.

Загальні функції для роботи з матрицями

В інших концепціях ми бачили, що бібліотеки Tidyverse можуть спростити операції з векторами та списками (purrr) або датафреймами (dplyr).

Однак матриці та масиви здебільшого поза межами того, з чим працює Tidyverse (принаймні поки що).

Функція apply() входить до Base R і досі є найкращим інструментом для тоншого контролю над програмуванням матриць.

Параметри, у порядку передачі:

  1. Вхідна матриця.
  2. Вимір, з яким працюємо (1 для рядків, 2 для стовпців).
  3. Функція, яку застосовуємо.
  4. Необовʼязково: будь-які додаткові параметри для цієї функції.

Функція rowMeans() є в Base R, але її можна також відтворити за допомогою apply() і 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

Лінійна алгебра

Note

Цей розділ передбачає рівень математичних знань поза межами Exercism.

Пропустіть його, якщо він здається незрозумілим.

Однак R - це мова науки про дані, і багато її користувачів мають досвід у прикладній математиці та статистиці. Цей стислий огляд може зацікавити деяких учнів.

Сучасна R «очевидно» є мовою науки про дані: вона створена для роботи переважно з табличними даними і популярна серед таких користувачів, як статистики, епідеміологи та соціологи. Головним конкурентом у цій царині є науковий стек Python, зокрема такі пакети, як Pandas чи Polars.

Однак у попередні десятиліття R розглядали і як потенційну інженерну мову, що прямо конкурувала з Matlab.

Можливо, несподівано, але Base R досі підтримує повний набір операцій лінійної алгебри над векторами та матрицями. Незрозуміло, наскільки вони тепер присутні в коді кінцевих користувачів, але такі прийоми залишаються дуже важливими всередині для реалізації статистичних функцій.

Лінійну алгебру в R досі викладають у деяких університетських курсах, як-от цей курс економетрії. Хороший путівник із самої математики (не з програмування) є на 3Blue1Brown.

Advanced

Найповніше лінійну алгебру в межах Exercism розглянуто в силабусі Julia (ті самі автори, що й у силабусі R). Ми вважаємо, що Julia (або Python там, де доступний модуль numpy.linalg) тепер була б доречнішою мовою для серйозної роботи з лінійною алгеброю, ніж R.

Діагоналі

Головна діагональ квадратної (N x N)) матриці A проходить від верхнього лівого кута (A[1,1]) до нижнього правого (A[N,N]).

Функція diag() поводиться по-різному залежно від переданого їй аргументу.

Для вектора v виклик diag(v) створює діагональну матрицю.

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

Для квадратної матриці A виклик diag(A) повертає елементи головної діагоналі як вектор.

A
#>      [,1] [,2] [,3]
#> [1,]   10    5    2
#> [2,]    9    3    7
#> [3,]    8   10    4
diag(A)
#> [1] 10  3  4

Для одного цілого числа k виклик diag(k) створює одиничну матрицю k × k: можливо, не те, чого можна було б очікувати.

Навіть серйозні підручники з R схильні додавати в цьому місці коментарі на кшталт «ну хто б подумав».

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

Транспонування та обернена матриця

Функція t() транспонує будь-яку матрицю, міняючи місцями рядки й стовпці.

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

Для матриці з комплексними значеннями результат не є комплексно спряженим. Щоб отримати спряжене транспонування (приєднану матрицю, adjoint) матриці A, використовують Conj(t(A)).

Для багатовимірних масивів aperm() переставляє виміри.

Для оберненої матриці до квадратної матриці A можна скористатися solve(A). Дехто з нас вважає цей синтаксис... несподіваним.

Множення матриці на її обернену дає одиничну матрицю (з точністю до дуже малих похибок округлення).

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

Множення

Звичайні арифметичні оператори, як-от +, -, *, ^, діють на матриці поелементно. Якщо розмірності не збігаються, діють звичайні правила повторного використання елементів.

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

Пояснення різних інших видів множення векторів і матриць виходить за межі цієї статті, але дуже стисло:

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці