Как правильно использовать списки в 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 скорее, как вы думаете, питон lists (или tuples на самом деле).

Лучше описать, как большинство объектов 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"]]извлекается первое значение. Вы должны знать, почему именно сейчас.

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

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

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

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

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

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

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

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

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

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
    

вы можете попробовать что-то вроде,

      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))
Другие вопросы по тегам