Как правильно использовать списки в R? - PullRequest
302 голосов
/ 12 января 2010

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

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

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

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

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

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

Во-первых, мне показалось, что тип данных R's list является простой реализацией карты 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']. Аналогично, вы можете получить только 'keys' или только 'values' по:

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):

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

second , 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

A третья особенность 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 к вектору с вызовом 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; I могу, конечно, рекомендовать этот пакет.)

Ответы [ 11 ]

138 голосов
/ 12 января 2010

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

В качестве заключительного комментария к этому: следует отметить, что 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"
61 голосов
/ 12 января 2010

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

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

34 голосов
/ 12 января 2010

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

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

Короче говоря [[]] выбирает один элемент из списка, а [] возвращает список выбранных элементов. В вашем примере x = list(1, 2, 3, 4)' item 1 - это одно целое число, но x[[1]] возвращает одно значение 1, а x[1] возвращает список только с одним значением.

> x = list(1, 2, 3, 4)
> x[1]
[[1]]
[1] 1

> x[[1]]
[1] 1
12 голосов
/ 12 января 2010

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

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

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

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

x3 = as.list(1:4)

Что приводит вектор 1:4 в список.

11 голосов
/ 17 февраля 2010

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

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

9 голосов
/ 13 января 2010

Вы говорите:

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

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

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

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
    
1 голос
/ 03 марта 2015

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

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

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

...