Допустим, создаем список, содержащий вектор, строку “my_data”, матрицу
list(1:5, "my_data", matrix(0,2,2))
## [[1]]
## [1] 1 2 3 4 5
##
## [[2]]
## [1] "my_data"
##
## [[3]]
## [,1] [,2]
## [1,] 0 0
## [2,] 0 0
Необязательно давать имена всем элементам списка. Невалидные имена должны быть заключены в кавычки
list(a = 1, b = 1:3, "1to5" = 1:5, 42)
## $a
## [1] 1
##
## $b
## [1] 1 2 3
##
## $`1to5`
## [1] 1 2 3 4 5
##
## [[4]]
## [1] 42
Т.е. список может содержать список, тот еще список и так далее рекурсивно.
Рекурсию можно использовать, чтобы создавать древовидные структуры. Строго говоря, рекурсивный список - тот, который содержит сам себя в качестве подсписка
list(a = list(1,2,3), b = list(list(4), 5, 6))
## $a
## $a[[1]]
## [1] 1
##
## $a[[2]]
## [1] 2
##
## $a[[3]]
## [1] 3
##
##
## $b
## $b[[1]]
## $b[[1]][[1]]
## [1] 4
##
##
## $b[[2]]
## [1] 5
##
## $b[[3]]
## [1] 6
Допустим, есть 2 списка
l1 <- list(name = "john", salary = 1000)
l2 <- list(has_car = TRUE, car = "lamborghini")
l1
## $name
## [1] "john"
##
## $salary
## [1] 1000
l2
## $has_car
## [1] TRUE
##
## $car
## [1] "lamborghini"
Объединить списки можно с помощью той же функции combine, с помощью которой объединяли вектора
c(l1, l2)
## $name
## [1] "john"
##
## $salary
## [1] 1000
##
## $has_car
## [1] TRUE
##
## $car
## [1] "lamborghini"
Вектора и списки очень похожи друг на друга. Потому конверсия между ними заслуживает отдельного рассмотрения
* В какой-то степени вектор может рассматриваться, как подмножества всех возможных списков
Проверить это можно следующим образом. Заведем вектор от 1 до 7. С помощью функции list мы получим список, первым и последним элементом которого служит вектор
v <- 1:7
list(v)
## [[1]]
## [1] 1 2 3 4 5 6 7
Т.е. любой вектор легко свести к списку.
Но обратно конверсия не всегда работает. Но если мы уверены, что сведение имеет смысл, то есть функция unlist:
l <- list(1:3, 4:5, last = 6)
l
## [[1]]
## [1] 1 2 3
##
## [[2]]
## [1] 4 5
##
## $last
## [1] 6
unlist(l)
## last
## 1 2 3 4 5 6
Из списка получили вектор, при том, что сохранилось именование последнего элемента вектора.
Важно помнить, что функция unlist автоматически подразумевает приведение типов, т.е. если мы заведем список, содержащий элементы разного типа, то при конверсии в вектор сработает автоматическое привдение.
В примере ниже все элементы приводятсяк строчному типу
unlist(c(l, "spy"))
## last
## "1" "2" "3" "4" "5" "6" "spy"
Три способа:
* как для векторов - через индексы, []
* доступ к конкретному элементу, [[]]
* доступ по имени с частичным дополнением, $
l
## [[1]]
## [1] 1 2 3
##
## [[2]]
## [1] 4 5
##
## $last
## [1] 6
l[3:2];
## $last
## [1] 6
##
## [[2]]
## [1] 4 5
l[-(1:2)]
## $last
## [1] 6
l[c(F,T,F)]
## [[1]]
## [1] 4 5
Имя должно быть заключено в кавычки
l["last"]
## $last
## [1] 6
Важно! Если в результате обращения возрващается только 1 элемент - это все равно список
Указываем единичный номер или полное имя
l[[1]]
## [1] 1 2 3
l[["last"]]
## [1] 6
При доступе по имени получаем не список длины = 1, а вектор из одного элемента
Получаем доступ к элементу last (при этом кавычки не нужны). Получаем непосредственно сам элемент, а не подсписок
l$last
## [1] 6
Частичное дополнение - когда мы обращаемся по минимально уникальному идентификатору к элементу. В данном случае у нас имя одно, поэтому возможны варианты
l$l
## [1] 6
l$las
## [1] 6
Одинарные скобки:
** действуют векторные правила индексирования
** возвращаемое значение - подсписок
Двойные скобки
** (скалярный) номер элемента или его полное имя
** возвращаемое значение - элемент списка
Знак доллара
** частичное имя элемента
** возвращаемое значение - элемент списка
Последний элемент опустили
l <- list(1:3, 4:5, last = 6)
l
## [[1]]
## [1] 1 2 3
##
## [[2]]
## [1] 4 5
##
## $last
## [1] 6
l[[3]] <- NULL; l
## [[1]]
## [1] 1 2 3
##
## [[2]]
## [1] 4 5
l
## [[1]]
## [1] 1 2 3
##
## [[2]]
## [1] 4 5
Важно (<-)! - список перезаписан с новым элементом.
Т.е. например, если нужно присвоить NULL элементу списка, то используется конструкция:
l[[1]] <- list(NULL)
Но при этом первый элемент станет списком из одного элемента. Поэтому можно так:
l[1] <- list(NULL)
l[1:3] <- list(NULL)
l$name <- list(NULL)
# Вместо NULL можно использовать NA
Необязательно создавать элементы подряд, правда, в этом случае пропущенные элементы будут создаваться автоматическими со значением NA
l[[4]] <- 99; l
## [[1]]
## NULL
##
## [[2]]
## NULL
##
## [[3]]
## NULL
##
## $name
## [1] 99
l <- list(vec = 1:7, fun = sqrt)
# Функция работает как обычно при обращении к ней
l$fun(4)
## [1] 2
names(l) # Смотрим имена списка
## [1] "vec" "fun"
Хотим создать новый элемент. Проверяем, существует ли он уже в списке
is.null(l$string)
## [1] TRUE
Можем создать элемент
l$string <- "Citius, altius, fortius"
l
## $vec
## [1] 1 2 3 4 5 6 7
##
## $fun
## function (x) .Primitive("sqrt")
##
## $string
## [1] "Citius, altius, fortius"
Чтобы применить функцию к строкам или столбцам матрицы используется функция - apply. На самом деле apply - целое семейство функций. Познакомимся еще с 2мя функциями из этого семейства. ## * lapply - применяет функция к каждому элементу списка
l <- list(a = c("12","34"), b = LETTERS[5:10], c = 1:5)
l
## $a
## [1] "12" "34"
##
## $b
## [1] "E" "F" "G" "H" "I" "J"
##
## $c
## [1] 1 2 3 4 5
Например, мы хотим узнать длину каждого элемента. Функции length недостаточно
length(l)
## [1] 3
Функция в таком виде дает количество элементов в списке
lapply(l, length)
## $a
## [1] 2
##
## $b
## [1] 6
##
## $c
## [1] 5
Теперь видим длину каждого подсписка #N.B! lapply(l, length) - устаревшая конструкция Для того, чтобы узнать длины элементов списка, используется функция lengths, которую добавили в R 3.2.0. Так что конструкция sapply(l, length) можно объявить устаревшей. lengths также на порядок более производительнее
Например, хочу склеить все элементы в подсписках.
#Первый аргумент lapply - список
#Второй аргумент lapply - применяемая функция
#Все последующие аргументы будут считываться, как аргументы функции
lapply(l, paste, collapse = "|")
## $a
## [1] "12|34"
##
## $b
## [1] "E|F|G|H|I|J"
##
## $c
## [1] "1|2|3|4|5"
Не очень понятно, что это и зачем
lapply(l, function(s) paste(s, collapse = "|"))
## $a
## [1] "12|34"
##
## $b
## [1] "E|F|G|H|I|J"
##
## $c
## [1] "1|2|3|4|5"
Результат такой же - зачем, не понятно
sapply(l, paste, collapse = "|")
## a b c
## "12|34" "E|F|G|H|I|J" "1|2|3|4|5"
Т.е. если хотим дополнительно упростить результат функции lapply, то воспользуемся функцией sapply и получим вектор
Уже встречались с частичным дополнением по знаку $ при обращении к элементам списка или подсписку по более короткому уникальному имени
l <- list(some_name = 1, incredibly_long_name = 2)
l$incr + 1
## [1] 3
Дополнение по аргументам функций работает таким же образом. Если есть функция с очень длинным аргументом, то необязательно прописывать название аргумента полностью - достаточно указать уникальный префикс
f <- function(x, ridiculously_long_argument) x + ridiculously_long_argument
f(3, ridic = 5)
## [1] 8
?matrix ?dim, ?rownames, ?colnames ?rbind, ?cbind, ?apply ?rowSums, ?rowMeans, ?colSums, ?colMeans ?list, ?unlist ?“[”, (?“[[”), ?“$” ?lapply, ?sapply Partial matching, ?“…” (ellipsis)
Небольшая добавка: в R есть три похожих ключевых слова, NA, NaN и NULL. Они различаются по смысловой нагрузке.
NA – это пропущенное значение (“not available”). Например, респондент не ответил на все вопросы предложенной анкеты, или данные с метеостанции за определённый период потерялись из-за сбоя оборудования. NA в этом случае обозначает, что эти данные существуют и имеют смысл, но их не удалось узнать.
NaN – “not-a-number” – результат недопустимой арифметической операции, например 0/0 или Inf - Inf.
NULL – отсутствие объекта, “пустота”. Применяется в тех случаях, когда объект действительно не существует, не может иметь осмысленного значения. Для проверки значений есть три функции, is.na, is.nan и is.null, соответственно
Допустим, есть список, элементы которого вектора различной длины. Поэтому матрицу использовать не можем, приходится оперировать списком.
Нам нужно найти позицию элемента максимальной длины и вернуть сам элемент. Что для этоо нужно сделать?
get_longest <- function(l) {
#Создаем вектор из длин
len <- lengths(l)
#Узнаем индекс вектора наибольшей длины
ind <- which.max(len)
#Результат функции и номер элемента, и сам элемент
list(number = ind, element = l[ind])
}
Чтобы протестировать эту задачу, нужно как-то проверить функцию. Напишем функцию, которая будет генерировать списки, подходящие под условия задачи.
Напишем функцию, которая возвращала бы список, у которого определенное количество элементов, но элементы могут быть разной длины и содержат случайные значения
#Создадим функцию с 3 аругументами:
# * количество элементов списка
# * максимальная длина
# * значение датчика генератора случайных чисел (для воспроизведения результатов лекции Степки)
# Длина каждого элемента определяется случайным образом
gen_list <- function(n_elements, max_len, seed = 111) {
# устанавливаем значение seed по умолчанию
set.seed(seed)
# Генерируем вектор функцией sample
len <- sample(1:max_len, n_elements)
# Составляем список с помощью lapply
lapply(1:n_elements, function(i) rnorm(len[i]))
}
Кстати 3ю строку можно написать проще, например:
* lapply(len, function(i) rnorm(i))
или
* lapply(len, rnorm)
Вообще запись:
#### lapply(1:n_elements, function(i) rnorm(len[i])) Это стандартная запись анонимной функции. Синтаксис для определения функции такой (help(function)):
function( arglist ) expr
где expr - это одно выражение, если нужно объединить несколько выражений в одно, то это делается с помощью фигурных скобок: “{}”.
####N.B! Замечание к функции Если n_elements > max_len, то sample(1:max_len, n_elements) вернёт ошибку.
Чтобы этого не происходило, в функцию sample, третьим аргументом нужно вписать replace = T. Зачем это нужно? Функция sample генерирует случайные числа на заданном промежутке (1й аргумент) и в заданном количестве (2й аргумент). Ее работу можно себе представить как черный мешочек, из которого она каждый раз достает какое-то значение. Но! Однажды взяв конкретное значение, повторно она его уже не вытянет, потому что в черный мешочек обратно “не возвращает”. Чтобы sample мог не просто генерировать случайные числа, но и создавать одинаковые в ходе такого случайного набора и нужен ‘replace = T’.
Для воспроизводимости результатов. set.seed задает отправную точку (порождающий элемент) для генератора псевдослучайных чисел.
https://ru.wikipedia.org/wiki/%D0%93%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80_%D0%BF%D1%81%D0%B5%D0%B2%D0%B4%D0%BE%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D1%85_%D1%87%D0%B8%D1%81%D0%B5%D0%BB#%D0%93%D0%9F%D0%A1%D0%A7_%D1%81_%D0%B8%D1%81%D1%82%D0%BE%D1%87%D0%BD%D0%B8%D0%BA%D0%BE%D0%BC_%D1%8D%D0%BD%D1%82%D1%80%D0%BE%D0%BF%D0%B8%D0%B8_%D0%B8%D0%BB%D0%B8_%D0%93%D0%A1%D0%A7 Данная отправная точка используется внутри функции get_list сразу двумя функциями: sample() и rnorm(). Без аргумента seed результат этих функций (а соотвественно и результат функции get_list) будет неопределенным. Таким образом, Вы не сможете обеспечить один и тот же вход в функцию get_longest, полученный с помощью функции get_list, без предварительного сохранения результата
Сгенерируем 1й список (4 элемента, длина не больше 10)
l1 <- gen_list(4, 10)
l1
## [[1]]
## [1] -2.3023457 -0.1708760 0.1402782 -1.4974267
##
## [[2]]
## [1] -1.0101884 -0.9484756 -0.4939622
##
## [[3]]
## [1] -0.17367413 -0.40659878 1.84563626 0.39405411 0.79752850 -1.56666536
## [7] -0.08585101
##
## [[4]]
## [1] -0.3591395
Теперь проверим, что функция get_longest работает корректно
get_longest(l1)
## $number
## [1] 3
##
## $element
## $element[[1]]
## [1] -0.17367413 -0.40659878 1.84563626 0.39405411 0.79752850 -1.56666536
## [7] -0.08585101
Можно результат преобразовать в список, например
gl1 <- get_longest(l1)
Теперь можно обратиться к элементам списка по имени
gl1$number
## [1] 3
И так можно проверить функцию несколько раз с разными значениями заданных аргументов, в том числе разным значением счетчика генератора случайных числе. Например:
l2 <- gen_list(4, 10, 777); l2
## [[1]]
## [1] 0.5108363 -0.3988121 1.6386861 0.6212740 0.2027043 1.1089378 -0.2062248
## [8] -0.3789650
##
## [[2]]
## [1] -0.3042617
##
## [[3]]
## [1] 0.05416232 -1.88093062
##
## [[4]]
## [1] -0.03375647 2.31149476 0.97234017 0.96460804
Проверяем функцию
get_longest(l2)
## $number
## [1] 1
##
## $element
## $element[[1]]
## [1] 0.5108363 -0.3988121 1.6386861 0.6212740 0.2027043 1.1089378 -0.2062248
## [8] -0.3789650
Все работает корректно
Если вектор достаточно длинный, то визуально сложно оценить, какие в нём содержатся элементы и сколько раз они повторяются. В этом случае полезно будет посмотреть на таблицу частот элементов.
Пусть x – целочисленный вектор. Напишите функцию, которая вернёт матрицу из двух строк. В первой строке перечислите все различные элементы вектора, упорядоченные по возрастанию. Во второй строке укажите частоты (количество повторов) этих элементов.
x <- c(5, 2, 7, 7, 7, 2, 0, 0)
z <- unique(sort(x))
z
## [1] 0 2 5 7
y <- table(x)
y
## x
## 0 2 5 7
## 2 2 1 3
as.vector(y)
## [1] 2 2 1 3
m <- matrix(rbind(z,y), nrow = 2)
m
## [,1] [,2] [,3] [,4]
## [1,] 0 2 5 7
## [2,] 2 2 1 3
Теперь сделаю из этого функцию
count_elements <- function(x) {
z <- unique(sort(x))
y <- table(x)
as.vector(y)
m <- matrix(rbind(z,y), nrow = 2)
m
}
Проверю
x <- c(5, 2, 7, 7, 7, 2, 0, 0)
count_elements(x)
## [,1] [,2] [,3] [,4]
## [1,] 0 2 5 7
## [2,] 2 2 1 3
Готово. Есть другие более изящные решения из форму Степик.
count_elements <- function(x) {
t <- table(x)
rbind(as.numeric(names(t)), t)
}
или
count_elements <- function(x) {
l <- rle(sort(x))
rbind(l$values,l$lengths)
}