Як правильно використовувати списки в R?


320

Коротка інформація: Багато (більшість?) Сучасних мов програмування, що широко використовуються, мають принаймні декілька спільних ADT (абстрактних типів даних), зокрема,

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

  • список (упорядкована колекція значень) та

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

У мові програмування R перші два реалізовані як characterі vector, відповідно.

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

По-перше, мені здалося, що listтип даних R - це пряма реалізація карти ADT ( dictionaryв Python, NSMutableDictionaryв Objective C, hashPerl і 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 lists також не схожі на інші рекламні картки типу (серед мов, яких я навчився в будь-якому випадку). Я здогадуюсь, що це наслідок початкової специфікації для S, тобто наміру створити DSL даних / статистики [мова, що залежить від домену] з нуля.

три суттєві відмінності між listтипами R s та картографуванням в інших мовах при широкому використанні (наприклад, Python, Perl, JavaScript):

по-перше , lists в R - це впорядкована колекція, подібно до векторів, навіть незважаючи на те, що значення клавішуються (тобто, ключі можуть бути будь-якими хешируемыми значеннями, а не лише послідовними цілими числами). Майже завжди тип даних відображення в інших мовах є не упорядкованим .

по-друге , lists може бути повернуто з функцій, навіть якщо ви ніколи не переходили в a, 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структурі даних чи тому, як вона поводиться, щось не так . Я лише хочу виправити своє розуміння того, як вони працюють, щоб я міг правильно використовувати їх у своєму коді.

Ось такі речі, які я хотів би краще зрозуміти:

  • Які правила визначають, коли виклик функції поверне a 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'. У цьому випадку виклик для obj["a"]поверне лише перший елемент відповідного списку. Ви можете отримати поведінку, схожу (можливо ідентичну) хешу, лише з одним елементом на x <- new.env(); x[["a"]] <- 1; x[["a"]] <- 2; x[["a"]]
вказані

1
Я перечитав цю публікацію з відповідями тричі протягом останніх 6 місяців і щоразу знаходив більше просвітлення. Чудове запитання та кілька чудових відповідей. Дякую.
Багатий доктор фізичних наук Лісаковський

Відповіді:


150

Просто для вирішення останньої частини вашого питання, оскільки це дійсно вказує на різницю між a 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 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
Це допомагає, дякую. (До речі, ваш приклад повторного "складного списку", як ви, можливо, вже знаєте, - це стандартний спосіб копіювання оператора "переключення" в 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: "Список тієї ж довжини, що і x". Але слід врахувати, що може бути функція повернення різних значень, що залежать від аргументів (наприклад, 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

До речі, 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пакеті . Ви можете прочитати про це у цій публікації щоденника із Групи відкритих даних . Ось простий приклад:

> 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
Мені відомо про хеш-пакет - він згадується в моєму первісному запитанні як підходящий проксі для традиційного типу хешу.
дог

Також зауважте, що використання хеш :: хешу викликає сумнівну корисність щодо хешованих середовищ, rpubs.com/rpierce/hashBenchmarks .
russellpierce

9

Ти кажеш:

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

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)

4

Це дуже давнє запитання, але я думаю, що нова відповідь може внести певну цінність, оскільки, на мою думку, ніхто не торкнувся деяких проблем в ОП.

Незважаючи на те, що припускають відповіді, listоб’єкти в R не є хеш-картами. Якщо ви хочете зробити паралель з python, listвони більше схожі на python lists (або tupleнасправді s).

Краще описати, як більшість R-об'єктів зберігаються всередині (тип C об'єкта R SEXP). Вони складаються в основному з трьох частин:

  • заголовок, який оголошує тип об'єкта R, довжину та деякі інші метадані;
  • частина даних, яка являє собою стандартний масив, виділений C (суміжний блок пам'яті);
  • атрибути, які є названим пов'язаним списком покажчиків на інші R об'єкти (або NULLякщо в об'єкта немає атрибутів).

Наприклад, з внутрішньої точки зору, між a listі numericвектором є невелика різниця . Значення, які вони зберігають, просто різні. Давайте розбимо два об'єкти на описаній нами парадигмі:

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

Для x:

  • У заголовку буде сказано, що тип numeric( REALSXPна стороні С), довжина - 10 та інші речі.
  • Частина даних буде масивом, що містить 10 doubleзначень.
  • Атрибути є NULL, оскільки об’єкта немає.

Для y:

  • У заголовку буде сказано, що тип list( VECSXPна стороні С), довжина - 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на стороні С), довжина - 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")

    ідеально діє в Р. При спробі буде y[["same"]]отримано перше значення. Ви повинні знати, чому саме в цей момент.

На закінчення, можливість надати довільним атрибутам об’єкт надає вам зовнішнього вигляду чогось іншого. Але R lists - це ні в якому разі не хеш-карти.


2

Щодо векторів та концепції хеш-масиву з інших мов:

  1. Вектори - це атоми Р. Напр., 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))ні, без гарантії того, що тип буде подвійним.


Щодо вашого другого питання:

Списки можна повернути з функцій, навіть якщо ви ніколи не переходили до Списку під час виклику функції

Функції повертають різні типи даних, ніж вони постійно вводяться. plotповертає графік, навіть якщо він не приймає графік як вхідний. Argповертає, numericнавіть якщо він прийняв a complex. І т.д.

(А щодо strsplit: вихідний код тут .)


2

Хоча це досить старе питання, я мушу сказати, що це стосується саме тих знань, яких мені не вистачало під час моїх перших кроків у R - тобто як висловити дані в моїй руці як об’єкт в R або як вибрати з існуючих об'єктів. Початківцю R непросто думати "в коробці R" з самого початку.

Тож я сам почав використовувати милиці внизу, що мені дуже допомогло з’ясувати, який об’єкт використовувати для отримання даних, і в основному уявити використання реального світу.

Хоча я не даю точних відповідей на запитання, короткий текст, поданий нижче, може допомогти читачеві, який тільки розпочав роботу з R і задає подібні запитання.

  • Атомний вектор ... Я назвав цю "послідовність" для себе, не напрям, просто послідовність одного типу. [підмножини
  • Вектор ... послідовність з одним напрямком від 2D, [підмножини.
  • Матриця ... купа векторів однакової довжини, що утворюють рядки чи стовпці, [підмножини за рядками та стовпцями або за послідовністю.
  • Масиви ... багатошарові матриці, що утворюють 3D
  • Рамка даних ... 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 як "записи" в інших мовах, що передують ООС:

  • вони не роблять жодних припущень щодо загальнозміцнюючого типу (точніше, тип усіх можливих записів будь-яких назв аритості та полів є).
  • їхні поля можуть бути анонімними (тоді ви отримуєте доступ до них у строгому порядку визначення).

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


1

чому ці два різні оператори [ ]і [[ ]]повертають один і той же результат?

x = list(1, 2, 3, 4)
  1. [ ]забезпечує операцію підстановки. Загалом підмножина будь-якого об'єкта матиме той самий тип, що і вихідний об'єкт. Тому x[1] надає список. Аналогічно x[1:2]є підмножиною вихідного списку, тому є списком. Вих.

    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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.