Краткая история. Многие (большинство?) современных языков программирования в широком использовании имеют, по крайней мере, несколько ADT [абстрактных типов данных], в частности,
строка (последовательность, состоящая из символов)
список (упорядоченный набор значений) и
тип на основе карты (неупорядоченный массив, который сопоставляет ключи значениям)
На языке программирования R первые два реализованы как character
и vector
соответственно.
Когда я начал изучать R, две вещи были очевидны почти с самого начала: list
- самый важный тип данных в R (потому что это родительский класс для R data.frame
), а во-вторых, я просто не мог Не понимаю, как они работают, по крайней мере, недостаточно хорошо, чтобы правильно использовать их в моем коде.
Во-первых, мне казалось, что тип данных R list
представляет собой прямую реализацию карты ADT (dictionary
в Python, NSMutableDictionary
в Objective C, hash
в Perl и Ruby, object literal
в Javascript и т.д.).
Например, вы создаете их так же, как и словарь Python, передавая пары ключ-значение конструктору (который в Python dict
not 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
и типами отображения на других языках в широком использовании (например, Python, Perl, JavaScript):
во-первых, list
в R - упорядоченный набор, точно так же, как и векторы, даже если значения вводятся ключом (т.е. ключи могут быть любым хешируемым значением, а не только целыми целыми числами). Почти всегда тип данных сопоставления на других языках неупорядочен.
second, list
может быть возвращен из функций, даже если вы никогда не передавали в list
при вызове функции, и даже если функция, которая вернула list
, не содержит (явного) list
constructor (Конечно, вы можете справиться с этим на практике, обернув возвращаемый результат при вызове 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 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
к вектору w/к вызову 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, я могу, конечно, рекомендовать этот пакет.)
Просто чтобы ответить на последнюю часть вашего вопроса, так как это действительно указывает на разницу между list
и vector
в R:
Почему эти два выражения не возвращают один и тот же результат?
x = список (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" ...
В качестве заключительного комментария к этому: следует отметить, что a 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"
switch
которую можно использовать для этой цели (см. help(switch)
).
Что касается ваших вопросов, позвольте мне привести их в порядок и привести несколько примеров:
1) Список возвращается, если и когда оператор return добавляет его. Рассмотрим
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). Наконец, они отличаются друг от друга тем, что они создают, соответственно, список, содержащий четыре скаляра, и список с одним элементом (который представляет собой вектор из четырех элементов).
Value
. Как в ?strsplit
: «Список такой же длины, что и x». Но вы должны учитывать, что может быть функция, возвращающая разные значения в зависимости от аргументов (например, sapply может вернуть список или вектор).
Просто возьмите подмножество своих вопросов:
В этой статье об индексации рассматривается вопрос о разнице между []
и [[]]
.
Короче [[]] выбирает один элемент из списка, а []
возвращает список выбранных элементов. В вашем примере x = list(1, 2, 3, 4)'
элемент 1 представляет собой одно целое число, но x[[1]]
возвращает один 1 и x[1]
возвращает список только с одним значением.
> x = list(1, 2, 3, 4)
> x[1]
[[1]]
[1] 1
> x[[1]]
[1] 1
A = array( 11:16, c(2,3) ); A[5]
равно 15, в плоском массиве ?!
Одна из причин приводит к тому, что работа, которую они выполняют (заказываются), предназначена для удовлетворения потребности в упорядоченном контейнере, который может содержать любой тип в любом node, который не выполняется. Списки повторно используются для различных целей в R, включая формирование базы data.frame
, которая представляет собой список векторов произвольного типа (но с той же длиной).
Почему эти два выражения не возвращают один и тот же результат?
x = list(1, 2, 3, 4); x2 = list(1:4)
Чтобы добавить к ответу @Shane, если вы хотите получить тот же результат, попробуйте:
x3 = as.list(1:4)
Который принуждает вектор 1:4
к списку.
Просто добавьте еще один момент:
R имеет структуру данных, эквивалентную Python dict в пакете hash
. Вы можете прочитать об этом в этом сообщении в блоге из группы открытых данных. Вот простой пример:
> 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, например.
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
: который возвращает список) вы можете указать, что было 2-й "частью" третьей строки, например: x[[3]][2]
. Как вы могли бы сделать то же самое с помощью xx
теперь, когда результаты были "распущены" (unlist
-ed)?
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 начинать думать "в коробке R" с самого начала.
Итак, я сам начал использовать костыли, которые помогли мне много узнать, какой объект использовать для каких данных, и в основном представить себе реальное использование.
Хотя я не даю точных ответов на вопрос, короткий текст ниже может помочь читателю, который только что начал с R и задает близкие вопросы.
[
подмножества.[
подмножеств.[
по строкам и столбцам или по последовательности.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
Этот способ мышления очень помог мне.
Почему эти два разных оператора [ ]
и [[ ]]
возвращают тот же результат?
x = list(1, 2, 3, 4)
[ ]
выполняет вспомогательную настройку. В общем подмножестве любого объекта
будет иметь тот же тип, что и исходный объект. Поэтому x[1]
предоставляет список. Аналогично x[1:2]
является подмножеством исходного списка,
поэтому это список. Пример.
x[1:2]
[[1]] [1] 1
[[2]] [1] 2
[[ ]]
предназначен для извлечения элемента из списка. x[[1]]
и извлечь первый элемент из списка. x[[1:2]]
недействителен как [[ ]]
не предоставляет настройку sub, например [ ]
.
x[[2]] [1] 2
> x[[2:3]] Error in x[[2:3]] : subscript out of bounds
Если это помогает, я склонен воспринимать "списки" в R как "записи" на других языках до OO:
Название "запись" столкнулось бы со стандартным значением "записей" (aka rows) в языке базы данных, и, возможно, именно поэтому их имя предложило себя как списки (полей).
Относительно векторов и концепции хэш/массив от других языков:
Векторы представляют собой атомы 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
Списки могут содержать различные типы данных, как видно из других ответов и самого вопроса OP.
Я видел языки (ruby, javascript), в которых "массивы" могут содержать переменные типы данных, но, например, в С++ "массивы" должен быть одинаковый тип данных. Я считаю, что это скорость/эффективность: если у вас есть numeric(1e6)
, вы знаете его размер и расположение каждого элемента априори; если предмет может содержать "Flying Purple People Eaters"
в некотором неизвестном фрагменте, тогда вы должны фактически разбирать материал, чтобы знать основные факты об этом.
Некоторые стандартные операции R также имеют больше смысла, когда тип гарантирован. Например, cumsum(1:9)
имеет смысл, тогда как cumsum(list(1,2,3,4,5,'a',6,7,8,9))
нет, без гарантированного двойного типа.
Что касается вашего второго вопроса:
Списки могут быть возвращены из функций, даже если вы никогда не передавали в List, когда вы вызывали функцию
Функции возвращают разные типы данных, чем они все время вводятся. plot
возвращает график, даже если он не принимает график в качестве входа. Arg
возвращает a numeric
, даже если он принял complex
. Etc.
(И что касается strsplit
: исходный код здесь.)
x = list(1, 2, 3, 4)
оба они НЕ возвращают одинаковый результат:x[1]
иx[[1]]
. Первый возвращает список, а второй возвращает числовой вектор. Прокручивая ниже, мне кажется, что Дирк был единственным респондентом, который правильно ответил на этот вопрос.list
в R не похож на хэш. У меня есть еще один, который я считаю достойным внимания.list
в R может иметь два члена с одинаковым ссылочным именем. Учтите, чтоobj <- c(list(a=1),list(a=2))
является допустимым и возвращает список с двумя именованными значениями 'a'. В этом случае вызовobj["a"]
вернет только первый соответствующий элемент списка. Вы можете получить поведение, похожее (возможно, идентичное) на хеш с только одним элементом наx <- new.env(); x[["a"]] <- 1; x[["a"]] <- 2; x[["a"]]
имена, используя среды в R. Например,x <- new.env(); x[["a"]] <- 1; x[["a"]] <- 2; x[["a"]]