Как правильно использовать списки в 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 ]

1 голос
/ 07 ноября 2014

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

  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), вы знаете его размер и расположение каждого элемента a priori ; если вещь может содержать "Flying Purple People Eaters" в каком-то неизвестном срезе, то вам нужно проанализировать материал, чтобы узнать основные факты о нем.

Некоторые стандартные операции R также имеют больше смысла, когда тип гарантирован. Например, cumsum(1:9) имеет смысл, тогда как cumsum(list(1,2,3,4,5,'a',6,7,8,9)) - нет, без гарантируемого типа double.


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

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

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

(А что касается strsplit: исходный код здесь .)

...