Как правильно использовать списки в R?

краткая справка: Многие (большинство?) современные языки программирования в широком использовании имеют по крайней мере несколько общих ADTs [абстрактных типов данных], в частности,

  • строка (последовательность, состоящая из символов)

  • список (упорядоченная коллекция значений) и

  • тип на основе карты (неупорядоченный массив, который сопоставляет ключи значения)

в языке программирования R первые два реализованы как character и vector, соответственно.

когда я начал изучать R, две вещи были очевидны почти с самого начала:list является наиболее важным типом данных в R (потому что это родительский класс для R data.frame), а во-вторых, я просто не мог понять, как они работают, по крайней мере, недостаточно хорошо, чтобы правильно использовать их в моем коде.

во-первых, казалось, что мне, что Р 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']. Аналогично, вы можете получить только '' или просто 'values' by:

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 lists также в отличие от другие карты типа ADTs (из числа языков, которые я узнал в любом случае). Я предполагаю, что это является следствием начальной спецификации для S, т. е. намерения разработать DSL данных/статистики [язык домена] с нуля.

три значимые различия Р listS и типы отображения на других широко используемых языках (e.g,. Python, Perl, JavaScript):

первый, lists в R являются приказал сбор, как и векторы, хотя значения по ключу (т. е. ключи могут быть hashable не просто последовательные целые числа). Почти всегда тип данных сопоставления на других языках -ненумерованный.

второй, lists может быть возвращен из функций, даже если вы никогда не проходили в 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

A третий особенность Р lists: не похоже, что они могут быть членами другого 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; я, безусловно, могу рекомендовать этот пакет.)

11 ответов


просто для решения последней части вашего вопроса, так как это действительно указывает на разницу между 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 могут иметь разные типы данных, в то время как столбцы в матрице не может. В качестве примера, здесь я пытаюсь создать матрицу с цифры и символы:

> 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) список возвращается, если и когда оператор 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. Каждый скаляр-это вектор длины один. С другой стороны!--6--> возвращает первый элемент списка.

4) наконец, они отличаются друг от друга, они создают, соответственно, список, содержащий четыре скаляра и список с одним элементом (который является вектором из четырех элементов).


просто взять подмножество ваших вопросов:

в этой статье при индексации решается вопрос о разнице между [] и [[]].

короче [[]] выбирает один элемент из списка и [] возвращает список выбранных элементов. В вашем примере, 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

одна из причин, по которой списки работают так ,как они (упорядочены), - это необходимость упорядоченного контейнера, который может содержать любой тип на любом узле, чего не делают векторы. Списки повторно используются для различных целей в R, включая формирование базы a data.frame, который представляет собой список векторов произвольного типа (но одинаковой длины).

Почему эти два выражения не возвращают один и тот же результат?

x = list(1, 2, 3, 4); x2 = list(1:4)

чтобы добавить к ответу @Shane, если вы хотите получить тот же результат, попробуйте:

x3 = as.list(1:4)

который coerces вектор 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 класс очень похож на список. Но производительность лучше для больших наборов данных.


вы говорите:

для другого, списки могут быть возвращены от функций, даже если вы никогда передано в список, когда вы позвонили функция, и даже если функция не содержит конструктор списка, например,

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-ю часть 3-й строки, например: 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)

относительно векторов и концепции хэша / массива из других языков:

  1. векторы являются атомами R. например,rpois(1e4,5) (5 случайных чисел), numeric(55) (длина-55 нулевой вектор над двойниками), и character(12) (12 пустых строк), все "основные".

  2. либо списки, либо векторы могут иметь 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
    
  3. векторы требуют, чтобы все было одного и того же типа данных. Часы это:

    > 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
    
  4. списки могут содержать различные типы данных, как видно из других ответов и самого вопроса OP.

я видел языки (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)) не делает, без гарантированного, что тип будет двойным.


Что касается вашего второго вопроса:

списки могут быть возвращены из функций, даже если вы никогда не проходили в списке, когда вы вызывается функция

функции возвращают разные типы данных, чем они все время. plot возвращает участок, даже если он не принимает участок в качестве входных данных. Arg возвращает numeric хотя он принял complex. Так далее.

strsplit исходный код здесь.)


Если это помогает, я склонен понимать "списки" в R как "записи" на других языках pre-OO:

  • они не делают никаких предположений о всеобъемлющем типе (или, скорее, тип всех возможных записей любых имен arity и полей доступен).
  • их поля могут быть анонимными (тогда вы получаете к ним доступ по строгому порядку определения).

имя " запись "будет конфликтовать со стандартным значением" записи " (он же строки) на языке базы данных, и может быть, поэтому их название предложило себя: как списки (полей).


хотя это довольно старый вопрос, я должен сказать, что он касается именно знания, которого мне не хватало во время моих первых шагов в R - т. е. как выразить данные в моей руке как объект в R или как выбрать из существующих объектов. Новичку R нелегко думать "в коробке R" с самого начала.

поэтому я сам начал использовать костыли ниже, которые помогли мне много узнать, какой объект использовать для каких данных, и в основном представить реальный мир использование.

хотя я не даю точных ответов на вопрос, короткий текст ниже может помочь читателю, который только что начал с R и задает вопросы simmilar.

  • атомный вектор ... Я назвал это "последовательностью" для себя, без направления, просто последовательностью тех же типов. [ подмножеств.
  • вектор ... последовательность с одним направлением из 2D,[ подмножеств.
  • Матрица ... связка векторов одинаковой длины, образующих строки или столбцы, [ подмножества по строкам и столбцам, или по последовательности.
  • массивы ... слоистые матрицы, образующие 3D
  • таблицы данных ... 2D-таблица, как в excel, где я могу сортировать, добавлять или удалять строки или столбцы или делать arit. операции с ними, только через некоторое время я действительно признал, что dataframe является умной реализацией 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)
  1. [ ] обеспечивает sub деятельность установки. В общем sub set любого объекта будет иметь тот же тип, что и исходный объект. Следовательно,x[1] предоставляет список. Аналогично x[1:2] является подмножеством исходного списка, поэтому список. Бывший.

    x[1:2]
    
    [[1]] [1] 1
    
    [[2]] [1] 2
    
  2. [[ ]] для извлечения элемента из списка. x[[1]] is действительный и извлечь первый элемент из списка. x[[1:2]] не является [[ ]] не предоставляет настройки sub, такие как [ ].

     x[[2]] [1] 2 
    
    > x[[2:3]] Error in x[[2:3]] : subscript out of bounds