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

320

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

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

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

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

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

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

Во-первых, мне показалось, что listтип данных R является простой реализацией карты ADT ( dictionaryв Python, NSMutableDictionaryв Objective C, hashв Perl и Ruby, object literalв Javascript и т. Д.).

Например, вы создаете их так же, как словарь Python, передавая пары ключ-значение в конструктор (чего в Python dictнет list):

x = list("ev1"=10, "ev2"=15, "rv"="Group 1")

И вы получаете доступ к элементам R List точно так же, как и к словарю 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 lists и типами отображения в других языках в широком распространении (например, Python, Perl, JavaScript):

listВо- первых , s в R - это упорядоченная коллекция, как и векторы, даже если значения имеют ключи (т. е. ключи могут быть любыми хешируемыми значениями, а не только последовательными целыми числами). Почти всегда тип данных отображения в других языках неупорядочен .

во-вторых , 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

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

Дуг
источник
3
При этом x = list(1, 2, 3, 4)оба они НЕ возвращают один и тот же результат:, x[1]и x[[1]]. Первый возвращает список, а второй возвращает числовой вектор. Прокручивая ниже, мне кажется, что Дирк был единственным респондентом, который правильно ответил на этот вопрос.
IRTFM
2
Я не заметил, чтобы кто-то расширил ваш список способов, которые listв R не похожи на хэш. У меня есть еще один, который я считаю достойным внимания. listв R может быть два члена с одинаковыми ссылочными именами. Считайте, что obj <- c(list(a=1),list(a=2))это допустимо и возвращает список с двумя именованными значениями 'a'. В этом случае вызов for obj["a"]вернет только первый соответствующий элемент списка. Вы можете получить поведение, подобное (может быть, идентичное) хешу только с одним элементом на x <- new.env(); x[["a"]] <- 1; x[["a"]] <- 2; x[["a"]]
указанные
1
Я перечитал эту публикацию с ответами три раза за последние 6 месяцев и находил больше просветления каждый раз. Отличный вопрос и несколько отличных ответов. Спасибо.
Рич Лысаковски PhD

Ответы:

150

Просто для решения последней части вашего вопроса, так как это действительно указывает на разницу между a listи vectorR:

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

х = список (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"
Шейн
источник
4
Это помогает, спасибо. (Между прочим, ваш пример «сложного списка», как вы, возможно, уже знаете, является стандартным способом репликации оператора «switch» в C ++, Java и т. Д. В языках, в которых его нет; возможно, это хороший способ сделать это в R, когда мне нужно). +1
даг
8
Правильно, хотя switchв R есть полезная функция, которую можно использовать для этой цели (см. help(switch)).
Шейн
63

Что касается ваших вопросов, позвольте мне рассмотреть их по порядку и привести несколько примеров:

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 ) Наконец, они различаются тем, что они создают, соответственно, список, содержащий четыре скаляра, и список с одним элементом (который является вектором из четырех элементов).

Дирк Эддельбюттель
источник
1
Очень полезно, спасибо. (Повторяйте пункт № 1 в своем ответе - я согласен, но я имел в виду встроенные функции, такие как «strsplit», а не созданные пользователем функции). В любом случае +1 от меня.
Даг
2
@doug Об элементе № 1 Я думаю, что единственный способ - это проверить справку по конкретной функции, раздел Value. Как в ?strsplit: «Список такой же длины, как х». Но вы должны учитывать, что может быть функция, возвращающая разные значения в зависимости от аргументов (например, sapply может возвращать список или вектор).
Марек
34

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

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

Вкратце [[]] выбирает один элемент из списка и []возвращает список выбранных элементов. В вашем примере 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
JD Long
источник
Кстати, A = array( 11:16, c(2,3) ); A[5]это 15, в плоском массиве ?!
Денис
13

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

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

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

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

x3 = as.list(1:4)

Который приводит вектор 1:4в список.

Алекс Браун
источник
11

Просто чтобы добавить еще один момент к этому:

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

Шейн
источник
1
Я знаю о пакете хэша - он упоминается в моем первоначальном вопросе как подходящий прокси для традиционного типа хэша.
Дуг
Также обратите внимание, что использование hash :: hash вызывает сомнение в отношении хэшированных сред, rpubs.com/rpierce/hashBenchmarks .
Russellpierce
9

Ты говоришь:

С другой стороны, списки могут быть возвращены из функций, даже если вы никогда не передавали в 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: , который возвращает список), вы можете сказать , что вторая «часть» 3 - й строка была, например: x[[3]][2]. Как вы могли бы сделать то же самое, используя xxтеперь, когда результаты были «распутаны» ( unlist-ed)?

Стив Лианоглу
источник
5
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)
JeremyS
источник
4

Это очень старый вопрос, но я думаю, что новый ответ может добавить некоторую ценность, так как, по моему мнению, никто не обратил внимание на некоторые проблемы в ОП.

Несмотря на то, что подтверждают принятые ответы, listобъекты в R не являются хэш-картами. Если вы хотите провести параллель с python, listвы, скорее всего, похожи на python lists (или tuples).

Лучше описать, как большинство объектов R хранятся внутри (тип C объекта R SEXP). Они сделаны в основном из трех частей:

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

С внутренней точки зрения, есть небольшая разница, например, между a listи numericвектором. Значения, которые они хранят, просто разные. Давайте разберем два объекта в парадигме, которую мы описали ранее:

x <- runif(10)
y <- list(runif(10), runif(3))

Для x:

  • Заголовок скажет, что тип numeric( REALSXPна стороне C), длина 10 и другие вещи.
  • Часть данных будет массивом, содержащим 10 doubleзначений.
  • Атрибуты есть NULL, так как у объекта их нет.

Для y:

  • Заголовок скажет, что тип list( VECSXPна стороне C), длина 2 и другие вещи.
  • Часть данных будет массивом, содержащим 2 указателя на два типа SEXP, указывающие на значение, полученное runif(10)и, runif(3)соответственно.
  • Атрибуты такие NULL, как для x.

Таким образом, единственное различие между numericвектором и a 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"]]первое значение. Вы должны знать, почему на этом этапе.

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

Никола
источник
2

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

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

Я видел языки (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: исходный код здесь .)

isomorphismes
источник
2

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

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

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

  • Атомный вектор ... Я назвал эту «последовательность» для себя, никакого направления, просто последовательность тех же типов. [подмножества.
  • Вектор ... последовательность с одним направлением от 2D, [подмножеств.
  • Матрица ... набор векторов одинаковой длины, образующих строки или столбцы, [подмножества строк и столбцов или последовательности.
  • Массивы ... слоистые матрицы, образующие 3D
  • Dataframe ... 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

Такое мышление мне очень помогло.

Петр Матоусу
источник
1

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

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

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

Франсиско Дж. Вальверде Альбасете
источник
1

почему эти два разных оператора [ ], и [[ ]], возвращают один и тот же результат?

x = list(1, 2, 3, 4)
  1. [ ]обеспечивает операцию поднабора. В целом подмножество любого объекта будет иметь тот же тип, что и исходный объект. Таким образом, x[1] предоставляет список. Точно так x[1:2]же это подмножество исходного списка, поэтому это список. Ex.

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

     x[[2]] [1] 2 
    
    > x[[2:3]] Error in x[[2:3]] : subscript out of bounds
Harig
источник