4 Сложные структуры данных в R
4.1 Матрица
Если вдруг вас пугает это слово, то совершенно зря. Матрица (matrix) — это всего лишь “двумерный” вектор: вектор, у которого есть не только длина, но и ширина. Создать матрицу можно с помощью функции matrix()
из вектора, указав при этом количество строк и столбцов.
<- matrix(1:20, nrow=5,ncol=4)
A A
## [,1] [,2] [,3] [,4]
## [1,] 1 6 11 16
## [2,] 2 7 12 17
## [3,] 3 8 13 18
## [4,] 4 9 14 19
## [5,] 5 10 15 20
Заметьте, значения вектора заполняются следующим образом: сначала заполняется первый столбик сверху вниз, потом второй сверху вниз и так до конца, т.е. заполнение значений матрицы идет в первую очередь по вертикали. Это довольно стандартный способ создания матриц, характерный не только для R.
Если мы знаем сколько значений в матрице и сколько мы хотим строк, то количество столбцов указывать необязательно:
<- matrix(1:20, nrow=5)
A A
## [,1] [,2] [,3] [,4]
## [1,] 1 6 11 16
## [2,] 2 7 12 17
## [3,] 3 8 13 18
## [4,] 4 9 14 19
## [5,] 5 10 15 20
Все остальное так же как и с векторами: внутри находится данные только одного типа. Поскольку матрица — это уже двумерный массив, то у него имеется два индекса. Эти два индекса разделяются запятыми.
2,3] A[
## [1] 12
2:4, 1:3] A[
## [,1] [,2] [,3]
## [1,] 2 7 12
## [2,] 3 8 13
## [3,] 4 9 14
Первый индекс — выбор строк, второй индекс — выбор колонок. Если же мы оставляем пустое поле вместо числа, то мы выбираем все строки/колонки в зависимости от того, оставили мы поле пустым до или после запятой:
1:3] A[,
## [,1] [,2] [,3]
## [1,] 1 6 11
## [2,] 2 7 12
## [3,] 3 8 13
## [4,] 4 9 14
## [5,] 5 10 15
2:4, ] A[
## [,1] [,2] [,3] [,4]
## [1,] 2 7 12 17
## [2,] 3 8 13 18
## [3,] 4 9 14 19
A[, ]
## [,1] [,2] [,3] [,4]
## [1,] 1 6 11 16
## [2,] 2 7 12 17
## [3,] 3 8 13 18
## [4,] 4 9 14 19
## [5,] 5 10 15 20
Так же как и в случае с обычными векторами, часть матрицы можно переписать:
2:4, 2:4] <- 100
A[ A
## [,1] [,2] [,3] [,4]
## [1,] 1 6 11 16
## [2,] 2 100 100 100
## [3,] 3 100 100 100
## [4,] 4 100 100 100
## [5,] 5 10 15 20
В принципе, это все, что нам нужно знать о матрицах. Матрицы используются в R довольно редко, особенно по сравнению, например, с MATLAB. Но вот индексировать матрицы хорошо бы уметь: это понадобится в работе с датафреймами.
То, что матрица — это просто двумерный вектор, не является метафорой: в R матрица — это по сути своей вектор с дополнительными атрибутами
dim
и (опционально)dimnames
. Атрибуты — это свойства объектов, своего рода “метаданные.” Для всех объектов есть обязательные атрибуты типа и длины и могут быть любые необязательные атрибуты. Можно задавать свои атрибуты или удалять уже присвоенные: удаление атрибутаdim
у матрицы превратит ее в обычный вектор. Про атрибуты подробнее можно почитать здесь или на стр. 99-101 книги “R in a Nutshell” (Adler 2010).
4.2 Массив
Два измерения — это не предел! Структура с одним типом данных внутри, но с тремя измерениями или больше, называется массивом (array). Создание массива очень похоже на создание матрицы: задаем вектор, из которого будет собран массив, и размерность массива.
<- array(1:12, c(3, 2, 2))
array_3d array_3d
## , , 1
##
## [,1] [,2]
## [1,] 1 4
## [2,] 2 5
## [3,] 3 6
##
## , , 2
##
## [,1] [,2]
## [1,] 7 10
## [2,] 8 11
## [3,] 9 12
4.3 Список
Теперь представим себе вектор без ограничения на одинаковые данные внутри. И получим список (list)!
<- list(42, "Пам пам", TRUE)
simple_list simple_list
## [[1]]
## [1] 42
##
## [[2]]
## [1] "Пам пам"
##
## [[3]]
## [1] TRUE
А это значит, что там могут содержаться самые разные данные, в том числе и другие списки и векторы!
<- list(c("Wow", "this", "list", "is", "so", "big"), "16", simple_list)
complex_list complex_list
## [[1]]
## [1] "Wow" "this" "list" "is" "so" "big"
##
## [[2]]
## [1] "16"
##
## [[3]]
## [[3]][[1]]
## [1] 42
##
## [[3]][[2]]
## [1] "Пам пам"
##
## [[3]][[3]]
## [1] TRUE
Если у нас сложный список, то есть очень классная функция, чтобы посмотреть, как он устроен, под названием str()
:
str(complex_list)
## List of 3
## $ : chr [1:6] "Wow" "this" "list" "is" ...
## $ : chr "16"
## $ :List of 3
## ..$ : num 42
## ..$ : chr "Пам пам"
## ..$ : logi TRUE
Представьте, что список - это такое дерево с ветвистой структурой. А на конце этих ветвей - листья-векторы.
Как и в случае с векторами мы можем давать имена элементам списка:
<- list(age = 24, PhDstudent = T, language = "Russian")
named_list named_list
## $age
## [1] 24
##
## $PhDstudent
## [1] TRUE
##
## $language
## [1] "Russian"
К списку можно обращаться как с помощью индексов, так и по именам. Начнем с последнего:
$age named_list
## [1] 24
А вот с индексами сложнее, и в этом очень легко запутаться. Давайте попробуем сделать так, как мы делали это раньше:
1] named_list[
## $age
## [1] 24
Мы, по сути, получили элемент списка — просто как часть списка, т.е. как список длиной один:
class(named_list)
## [1] "list"
class(named_list[1])
## [1] "list"
А вот чтобы добраться до самого элемента списка (и сделать с ним что-то хорошее), нам нужна не одна, а две квадратных скобочки:
1]] named_list[[
## [1] 24
class(named_list[[1]])
## [1] "numeric"
Indexing lists in #rstats. Inspired by the Residence Inn pic.twitter.com/YQ6axb2w7t
— Hadley Wickham ((hadleywickham?)) September 14, 2015
Как и в случае с вектором, к элементу списка можно обращаться по имени.
'age']] named_list[[
## [1] 24
Хотя последнее — практически то же самое, что и использование знака $.
Списки довольно часто используются в R, но реже, чем в Python. Со многими объектами в R, такими как результаты статистических тестов, удобно работать именно как со списками — к ним все вышеописанное применимо. Кроме того, некоторые данные мы изначально получаем в виде древообразной структуры — хочешь не хочешь, а придется работать с этим как со списком. Но обычно после этого стоит как можно скорее превратить список в датафрейм.
4.4 Датафрейм
Итак, мы перешли к самому главному. Самому-самому. Датафреймы (data.frames). Более того, сейчас станет понятно, зачем нам нужно было разбираться со всеми предыдущими темами.
Без векторов мы не смогли бы разобраться с матрицами и списками. А без последних мы не сможем понять, что такое датафрейм.
<- c("Ivan", "Eugeny", "Lena", "Misha", "Sasha")
name <- c(26, 34, 23, 27, 26)
age <- c(F, F, T, T, T)
student <- data.frame(name, age, student)
df df
str(df)
## 'data.frame': 5 obs. of 3 variables:
## $ name : chr "Ivan" "Eugeny" "Lena" "Misha" ...
## $ age : num 26 34 23 27 26
## $ student: logi FALSE FALSE TRUE TRUE TRUE
Вообще, очень похоже на список, не правда ли? Так и есть, датафрейм — это что-то вроде проименованного списка, каждый элемент которого является atomic вектором фиксированной длины. Скорее всего, список Вы представляли “горизонтально.” Если это так, то теперь “переверните” его у себя в голове на 90 градусов. Так, чтоб названия векторов оказались сверху, а колонки стали столбцами. Поскольку длина всех этих векторов равна (обязательное условие!), то данные представляют собой табличку, похожую на матрицу. Но в отличие от матрицы, разные столбцы могут имет разные типы данных. В нашем случае первая колонка — character
, вторая колонка — numeric
, третья колонка — logical
. Тем не менее, обращаться с датафреймом можно и как с проименованным списком, и как с матрицей:
$age[2:3] df
## [1] 34 23
Здесь мы сначала вытащили колонку age
с помощью оператора $
. Результатом этой операции является числовой вектор, из которого мы вытащили кусок, выбрав индексы 2
и 3
.
Используя оператор $
и присваивание можно создавать новые колонки датафрейма:
$lovesR <- T #правило recycling - узнали?
df df
Ну а можно просто обращаться с помощью двух индексов через запятую, как мы это делали с матрицей:
3:5, 2:3] df[
Как и с матрицами, первый индекс означает строчки, а второй — столбцы.
А еще можно использовать названия колонок внутри квадратных скобок:
1:2,"age"] df[
## [1] 26 34
И здесь перед нами открываются невообразимые возможности! Узнаем, любят ли R те, кто моложе среднего возраста в группе:
$age < mean(df$age), 4] df[df
## [1] TRUE TRUE TRUE TRUE
Эту же задачу можно выполнить другими способами:
$lovesR[df$age < mean(df$age)] df
## [1] TRUE TRUE TRUE TRUE
$age < mean(df$age), 'lovesR'] df[df
## [1] TRUE TRUE TRUE TRUE
В большинстве случаев подходят сразу несколько способов — тем не менее, стоит овладеть ими всеми.
Датафреймы удобно просматривать в RStudio. Для это нужно написать команду View(df)
или же просто нажать на названии нужной переменной из списка вверху справа (там где Environment). Тогда увидите табличку, очень похожую на Excel и тому подобные программы для работы с таблицами. Там же есть и всякие возможности для фильтрации, сортировки и поиска…9
Но, конечно, интереснее все эти вещи делать руками, т.е. с помощью написания кода.
Все, что вы нажмете в этом окошке, никак не повлияет на исходную переменную. Так что можете смело использовать эти функции для исследования содержимого датафрейма.↩︎