Как правильно использовать списки в R?
Краткая предыстория: Многие (большинство?) Современные языки программирования в широком распространении имеют по крайней мере несколько общих ADT [абстрактных типов данных], в частности,
строка (последовательность, состоящая из символов)
список (упорядоченный набор значений) и
основанный на карте тип (неупорядоченный массив, который отображает ключи на значения)
В языке программирования R первые два реализованы как character
а также vector
соответственно.
Когда я начал изучать R, две вещи были очевидны почти с самого начала: list
является наиболее важным типом данных в R (потому что это родительский класс для R data.frame
), а во-вторых, я просто не мог понять, как они работают, по крайней мере, недостаточно хорошо, чтобы правильно использовать их в моем коде.
Во-первых, мне казалось, что R list
Тип данных был простой реализацией карты ADT (dictionary
в Python, NSMutableDictionary
в цели C, hash
в Perl и Ruby, object literal
в Javascript и т. д.).
Например, вы создаете их так же, как словарь Python, передавая пары ключ-значение в конструктор (который в Python dict
не list
):
x = list("ev1"=10, "ev2"=15, "rv"="Group 1")
И вы получаете доступ к элементам списка R так же, как и к словарю Python, например, x['ev1']
, Аналогично, вы можете получить только "ключи" или только "значения":
names(x) # fetch just the 'keys' of an R list
# [1] "ev1" "ev2" "rv"
unlist(x) # fetch just the 'values' of an R list
# ev1 ev2 rv
# "10" "15" "Group 1"
x = list("a"=6, "b"=9, "c"=3)
sum(unlist(x))
# [1] 18
но R list
Они также не похожи на другие ADT типа карты (из всех языков, которые я изучал в любом случае). Я предполагаю, что это является следствием начальной спецификации для S, то есть намерения разработать DSL для данных / статистики с нуля с нуля.
три значимых различия между R list
s и типы отображения в других языках в широком распространении (например, Python, Perl, JavaScript):
во-первых, list
s в R - упорядоченная коллекция, как и векторы, даже если значения являются ключевыми (т. е. ключи могут быть любыми хешируемыми значениями, а не только последовательными целыми числами). Почти всегда тип данных отображения в других языках неупорядочен.
во-вторых, list
s могут быть возвращены из функций, даже если вы никогда не переходили в list
когда вы вызвали функцию, и даже если функция, которая возвратила list
не содержит (явный) list
Конструктор (Конечно, вы можете справиться с этим на практике, обернув возвращаемый результат в вызов unlist
):
x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character'
class(x) # returns 'list', not a vector of length 2
# [1] list
Третья особенность R's list
s: не похоже, что они могут быть членами другого ADT, и если вы попытаетесь это сделать, то основной контейнер будет приведен к list
, Например,
x = c(0.5, 0.8, 0.23, list(0.5, 0.2, 0.9), recursive=TRUE)
class(x)
# [1] list
мое намерение здесь не в том, чтобы критиковать язык или как он задокументирован; аналогично, я не предполагаю, что с list
структура данных или как она себя ведет. Все, что мне нужно, это исправить, это мое понимание того, как они работают, чтобы я мог правильно использовать их в своем коде.
Вот некоторые вещи, которые я хотел бы лучше понять:
Какие правила определяют, когда вызов функции вернет
list
(например,strsplit
выражение, изложенное выше)?Если я не назначу имена явно
list
(например,list(10,20,30,40)
) являются ли имена по умолчанию просто последовательными целыми числами, начинающимися с 1? (Я предполагаю, но я далеко не уверен, что ответ - да, иначе мы не смогли бы принудить этот типlist
к вектору с вызовомunlist
.)Почему эти два разных оператора,
[]
, а также[[]]
вернуть тот же результат?x = list(1, 2, 3, 4)
оба выражения возвращают "1":
x[1]
x[[1]]
почему эти два выражения не возвращают одинаковый результат?
x = list(1, 2, 3, 4)
x2 = list(1:4)
Пожалуйста, не указывайте мне на документацию R (?list
, R-intro
) - Я внимательно прочитал, и это не помогает мне ответить на тип вопросов, которые я изложил чуть выше.
(наконец, я недавно узнал о и начал использовать пакет R (доступный в CRAN) под названием hash
который реализует обычное поведение типа карты через класс S4; Я, конечно, могу рекомендовать этот пакет.)
13 ответов
Просто для решения последней части вашего вопроса, так как это действительно указывает на разницу между list
а также vector
в R:
Почему эти два выражения не возвращают одинаковый результат?
х = список (1, 2, 3, 4); x2 = список (1:4)
Список может содержать любой другой класс в качестве каждого элемента. Таким образом, у вас может быть список, в котором первый элемент представляет собой символьный вектор, второй - фрейм данных и т. Д. В этом случае вы создали два разных списка. x
имеет четыре вектора, каждый длиной 1. x2
имеет 1 вектор длины 4:
> length(x[[1]])
[1] 1
> length(x2[[1]])
[1] 4
Так что это совершенно разные списки.
Списки R очень похожи на структуру данных хэш-карты, в которой каждое значение индекса может быть связано с любым объектом. Вот простой пример списка, который содержит 3 разных класса (включая функцию):
> complicated.list <- list("a"=1:4, "b"=1:3, "c"=matrix(1:4, nrow=2), "d"=search)
> lapply(complicated.list, class)
$a
[1] "integer"
$b
[1] "integer"
$c
[1] "matrix"
$d
[1] "function"
Учитывая, что последним элементом является функция поиска, я могу назвать ее так:
> complicated.list[["d"]]()
[1] ".GlobalEnv" ...
В качестве заключительного комментария к этому: следует отметить, что data.frame
действительно список (из data.frame
документация):
Фрейм данных - это список переменных с одинаковым количеством строк с уникальными именами строк, заданный классом "data.frame"
Вот почему столбцы в data.frame
могут иметь разные типы данных, в то время как столбцы в матрице не могут. В качестве примера здесь я пытаюсь создать матрицу с числами и символами:
> a <- 1:4
> class(a)
[1] "integer"
> b <- c("a","b","c","d")
> d <- cbind(a, b)
> d
a b
[1,] "1" "a"
[2,] "2" "b"
[3,] "3" "c"
[4,] "4" "d"
> class(d[,1])
[1] "character"
Обратите внимание, что я не могу изменить тип данных в первом столбце на числовой, потому что во втором столбце есть символы:
> d[,1] <- as.numeric(d[,1])
> class(d[,1])
[1] "character"
Что касается ваших вопросов, позвольте мне рассмотреть их по порядку и привести несколько примеров:
1) Список возвращается, если и когда оператор возврата добавляет его. Рассматривать
R> retList <- function() return(list(1,2,3,4)); class(retList())
[1] "list"
R> notList <- function() return(c(1,2,3,4)); class(notList())
[1] "numeric"
R>
2) Имена просто не заданы:
R> retList <- function() return(list(1,2,3,4)); names(retList())
NULL
R>
3) Они не возвращают одно и то же. Ваш пример дает
R> x <- list(1,2,3,4)
R> x[1]
[[1]]
[1] 1
R> x[[1]]
[1] 1
где x[1]
возвращает первый элемент x
- который так же, как x
, Каждый скаляр - это вектор длины один. С другой стороны x[[1]]
возвращает первый элемент списка.
4) Наконец, они отличаются друг от друга тем, что они создают, соответственно, список, содержащий четыре скаляра, и список с одним элементом (который является вектором из четырех элементов).
Just to take a subset of your questions:
This article on indexing addresses the question of the difference between []
а также [[]]
,
In short [[]] selects a single item from a list and []
returns a list of the selected items. В вашем примере x = list(1, 2, 3, 4)'
item 1 is a single integer but x[[1]]
returns a single 1 and x[1]
returns a list with only one value.
> x = list(1, 2, 3, 4)
> x[1]
[[1]]
[1] 1
> x[[1]]
[1] 1
Одна из причин, по которой списки работают так, как они работают (упорядочены), заключается в том, чтобы удовлетворить потребность в упорядоченном контейнере, который может содержать любой тип в любом узле, чего нет у векторов. Списки повторно используются для различных целей в R, включая формирование основы data.frame
, который представляет собой список векторов произвольного типа (но одинаковой длины).
Почему эти два выражения не возвращают одинаковый результат?
x = list(1, 2, 3, 4); x2 = list(1:4)
Чтобы добавить к ответу @Shane, если вы хотите получить тот же результат, попробуйте:
x3 = as.list(1:4)
Который заставляет вектор 1:4
в список.
Просто чтобы добавить еще один момент к этому:
R имеет структуру данных, эквивалентную диктату Python в hash
пакет Вы можете прочитать об этом в этом блоге от Open Data Group. Вот простой пример:
> library(hash)
> h <- hash( keys=c('foo','bar','baz'), values=1:3 )
> h[c('foo','bar')]
<hash> containing 2 key-value pairs.
bar : 2
foo : 1
С точки зрения удобства использования, hash
класс очень похож на список. Но производительность лучше для больших наборов данных.
Ты говоришь:
С другой стороны, списки могут быть возвращены из функций, даже если вы никогда не передавали в List, когда вызывали функцию, и даже если функция не содержит конструктора List, например,
x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character'
class(x)
# => 'list'
И я полагаю, вы предполагаете, что это проблема (?). Я здесь, чтобы рассказать вам, почему это не проблема:-). Ваш пример немного прост в том, что когда вы делаете разбиение строки, у вас есть список с элементами длиной 1 элемент, так что вы знаете, что x[[1]]
такой же как unlist(x)[1]
, Но что, если результат strsplit
возвращал результаты различной длины в каждом бине. Простой возврат вектора (по сравнению со списком) не поможет вообще.
Например:
stuff <- c("You, me, and dupree", "You me, and dupree",
"He ran away, but not very far, and not very fast")
x <- strsplit(stuff, ",")
xx <- unlist(strsplit(stuff, ","))
В первом случае (x
: который возвращает список), вы можете сказать, какой была вторая часть третьей строки, например: x[[3]][2]
, Как вы могли бы сделать то же самое, используя xx
теперь, когда результаты были "распутаны" (unlist
-ed)?
Это очень старый вопрос, но я думаю, что новый ответ может добавить некоторую ценность, поскольку, на мой взгляд, никто напрямую не затронул некоторые из проблем в OP.
Несмотря на принятые ответы, list
объекты в R не являются хэш-картами. Если вы хотите провести параллель с python,list
скорее, как вы думаете, питон list
s (или tuple
s на самом деле).
Лучше описать, как большинство объектов R хранится внутри (тип C объекта R SEXP
). В основном они состоят из трех частей:
- заголовок, в котором объявляется R-тип объекта, длина и некоторые другие метаданные;
- часть данных, которая представляет собой стандартный массив C с выделенной кучей (непрерывный блок памяти);
- атрибуты, которые представляют собой именованный связанный список указателей на другие объекты R (или
NULL
если у объекта нет атрибутов).
С внутренней точки зрения разница между list
и numeric
вектор, например. Просто они хранят разные ценности. Давайте разберем два объекта на парадигму, которую мы описали ранее:
x <- runif(10)
y <- list(runif(10), runif(3))
За x
:
- В заголовке будет указано, что тип
numeric
(REALSXP
на стороне С), длина 10 и прочее. - Часть данных будет массивом, содержащим 10
double
ценности. - Атрибуты
NULL
, поскольку у объекта их нет.
За y
:
- В заголовке будет указано, что тип
list
(VECSXP
на стороне C), длина 2 и прочее. - Часть данных будет представлять собой массив, содержащий 2 указателя на два типа SEXP, указывающих на значение, полученное с помощью
runif(10)
а такжеrunif(3)
соответственно. - Атрибуты
NULL
, что касаетсяx
.
Так что единственная разница между numeric
вектор и list
это то numeric
часть данных состоит из double
значения, а для list
часть данных - это массив указателей на другие объекты R.
Что происходит с именами? Ну, имена - это лишь некоторые из атрибутов, которые вы можете назначить объекту. Посмотрим на объект ниже:
z <- list(a=1:3, b=LETTERS)
- В заголовке будет указано, что тип
list
(VECSXP
на стороне C), длина 2 и прочее. - Часть данных будет представлять собой массив, содержащий 2 указателя на два типа SEXP, указывающих на значение, полученное с помощью
1:3
а такжеLETTERS
соответственно. - Атрибуты теперь присутствуют и являются
names
компонент, который являетсяcharacter
Объект R со значениемc("a","b")
.
На уровне R вы можете получить атрибуты объекта с помощью attributes
функция.
Типичное значение ключа-значения для хэш-карты в R - всего лишь иллюзия. Когда ты сказал:
z[["a"]]
вот что происходит:
- в
[[
вызывается функция подмножества; - аргумент функции (
"a"
) имеет типcharacter
, поэтому метод получает указание искать такое значение вnames
атрибут (если есть) объектаz
; - если
names
атрибута нет,NULL
возвращается; - если присутствует, то
"a"
значение ищется в нем. Если"a"
это не имя объекта,NULL
возвращается; - если присутствует, позиция определяется (1 в примере). Таким образом, возвращается первый элемент списка, т.е. эквивалент
z[[1]]
.
Поиск "ключ-значение" довольно косвенный и всегда позиционный. Также полезно иметь в виду:
- в хэш-картах единственное ограничение, которое должен иметь ключ, - это то, что он должен быть хешируемым.
names
в R должны быть строки (character
векторы); в хэш-картах не может быть двух одинаковых ключей. В R вы можете назначить
names
объекту с повторяющимися значениями. Например:names(y) <- c("same", "same")
совершенно верно в R. Когда вы пытаетесь
y[["same"]]
извлекается первое значение. Вы должны знать, почему именно сейчас.
В заключение скажу, что способность давать произвольные атрибуты объекту дает вам видимость чего-то отличного от внешней точки зрения. Но Rlist
s ни в коем случае не являются хэш-картами.
x = list(1, 2, 3, 4)
x2 = list(1:4)
all.equal(x,x2)
не то же самое, потому что 1:4 совпадает с c(1,2,3,4). Если вы хотите, чтобы они были одинаковыми, то:
x = list(c(1,2,3,4))
x2 = list(1:4)
all.equal(x,x2)
Хотя это довольно старый вопрос, я должен сказать, что он касается именно тех знаний, которые мне не хватало во время моих первых шагов в R - то есть, как выразить данные в моей руке как объект в R или как выбрать из существующих объектов. Новичку не просто мыслить "в коробке" с самого начала.
Таким образом, я сам начал использовать костыли ниже, которые мне очень помогли выяснить, какой объект использовать для каких данных, и в основном представить себе реальное использование.
Хотя я не даю точных ответов на этот вопрос, краткий текст ниже может помочь читателю, который только начал с R и задает простые вопросы.
- Атомный вектор... Я назвал эту "последовательность" для себя, никакого направления, просто последовательность тех же типов.
[
подмножества. - Вектор... последовательность с одним направлением от 2D,
[
подмножества. - Матрица... набор векторов одинаковой длины, образующих строки или столбцы,
[
подмножества по строкам и столбцам или по последовательности. - Массивы... слоистые матрицы, образующие 3D
- Dataframe... 2D-таблица, как в Excel, где я могу сортировать, добавлять или удалять строки или столбцы или создавать arit. операций с ними, только через некоторое время я действительно осознал, что датафрейм является умной реализацией
list
где я могу использовать подмножество[
по строкам и столбцам, но даже используя[[
, - Список... чтобы помочь себе, я думал о списке с
tree structure
где[i]
выбирает и возвращает целые ветви и[[i]]
возвращает товар из ветки. И потому что это такtree like structure
Вы можете даже использоватьindex sequence
обратиться к каждому листу на очень сложномlist
используя его[[index_vector]]
, Списки могут быть простыми или очень сложными и могут объединять различные типы объектов в один.
Таким образом, для lists
вы можете получить больше способов, как выбрать leaf
в зависимости от ситуации, как в следующем примере.
l <- list("aaa",5,list(1:3),LETTERS[1:4],matrix(1:9,3,3))
l[[c(5,4)]] # selects 4 from matrix using [[index_vector]] in list
l[[5]][4] # selects 4 from matrix using sequential index in matrix
l[[5]][1,2] # selects 4 from matrix using row and column in matrix
Такое мышление мне очень помогло.
Относительно векторов и концепции хэш / массива из других языков:
Векторы - это атомы R. Например,
rpois(1e4,5)
(5 случайных чисел),numeric(55)
(нулевой вектор длины 55 над двойниками) иcharacter(12)
(12 пустых строк), все "основные".Списки или векторы могут иметь
names
,> n = numeric(10) > n [1] 0 0 0 0 0 0 0 0 0 0 > names(n) NULL > names(n) = LETTERS[1:10] > n A B C D E F G H I J 0 0 0 0 0 0 0 0 0 0
Векторы требуют, чтобы все были одинакового типа данных. Смотри:
> i = integer(5) > v = c(n,i) > v A B C D E F G H I J 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 > class(v) [1] "numeric" > i = complex(5) > v = c(n,i) > class(v) [1] "complex" > v A B C D E F G H I J 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i
Списки могут содержать различные типы данных, как видно из других ответов и самого вопроса ОП.
Я видел языки (ruby, javascript), в которых "массивы" могут содержать переменные типы данных, но, например, в C++ "массивы" должны иметь одинаковый тип данных. Я считаю, что это скорость / эффективность: если у вас есть numeric(1e6)
вы знаете его размер и расположение каждого элемента априори; если вещь может содержать "Flying Purple People Eaters"
в каком-то неизвестном срезе, тогда вы должны фактически разобрать материал, чтобы узнать основные факты о нем.
Некоторые стандартные операции R также имеют больше смысла, когда тип гарантирован. Например cumsum(1:9)
имеет смысл, тогда как cumsum(list(1,2,3,4,5,'a',6,7,8,9))
не, без гарантируемого типа double.
Что касается вашего второго вопроса:
Списки могут быть возвращены из функций, даже если вы никогда не передавали список, когда вызывали функцию
Функции возвращают разные типы данных, чем они вводятся постоянно. plot
возвращает график, даже если он не принимает график в качестве входных данных. Arg
возвращает numeric
хотя он принял complex
, И т.п.
(А что касается strsplit
: исходный код здесь.)
Если это помогает, я склонен воспринимать "списки" в R как "записи" на других языках до OO:
- они не делают никаких предположений о всеобъемлющем типе (точнее, о типе всех возможных записей любых имен арности и полей).
- их поля могут быть анонимными (тогда вы получите доступ к ним в строгом порядке определения).
Название "запись" противоречило бы стандартному значению "записи" (иначе говоря, строки) на языке базы данных, и, возможно, именно поэтому их имя предлагалось: в виде списков (полей).
Почему эти два разных оператора, [ ]
, а также [[ ]]
вернуть тот же результат?
x = list(1, 2, 3, 4)
[ ]
обеспечивает операцию поднабора. В общем случае подмножество любого объекта будет иметь тот же тип, что и исходный объект. Следовательно,x[1]
предоставляет список. так жеx[1:2]
это подмножество исходного списка, поэтому это список. Ex.x[1:2] [[1]] [1] 1 [[2]] [1] 2
[[ ]]
для извлечения элемента из списка.x[[1]]
допустимо и извлеките первый элемент из списка.x[[1:2]]
не действует как[[ ]]
не предоставляет суб-настройки, такие как[ ]
,x[[2]] [1] 2 > x[[2:3]] Error in x[[2:3]] : subscript out of bounds
вы можете попробовать что-то вроде,
set.seed(123)
l <- replicate(20, runif(sample(1:10,1)), simplify = FALSE)
out <- vector("list", length(l))
for (i in seq_along(l)) {
out[[i]] <- length(unique(l[[i]])) #length(l[[i]])
}
unlist(out)
unlist(lapply(l,length))
unlist(lapply(l, class))
unlist(lapply(l, mean))
unlist(lapply(l, max))