R предоставляет два разных метода для доступа к элементам списка или data.frame: []
и [[]]
.
В чем разница между этими двумя, в каких ситуациях я должен использовать один над другим?
R предоставляет два разных метода для доступа к элементам списка или data.frame: []
и [[]]
.
В чем разница между этими двумя, в каких ситуациях я должен использовать один над другим?
Ответы:
Определение языка R удобно для ответа на следующие вопросы:
R имеет три основных оператора индексации, синтаксис которых показан в следующих примерах
x[i] x[i, j] x[[i]] x[[i, j]] x$a x$"a"
Для векторов и матриц
[[
формы используются редко, хотя они имеют некоторые небольшие семантические отличия от[
формы (например, он отбрасывает любые атрибуты names или dimnames, и это частичное соответствие используется для индексов символов). При индексации многомерных структур с одним индексомx[[i]]
илиx[i]
будет возвращатьсяi
последовательный элементx
.Для списков обычно используется
[[
для выбора любого отдельного элемента, тогда как[
возвращает список выбранных элементов.
[[
Форма позволяет только один элемент , который будет выбран с помощью целочисленных или символьных индексов, в то время как[
позволяет индексировать векторами. Обратите внимание, что для списка индекс может быть вектором, и каждый элемент вектора применяется по очереди к списку, выбранному компоненту, выбранному компоненту этого компонента и так далее. Результат по-прежнему один элемент.
[
всегда возвращаемого списка означает, что вы получаете один и тот же класс вывода x[v]
независимо от длины v
. Например, один может понадобиться lapply
более чем подмножество списка: lapply(x[v], fun)
. Если [
бы исключить список для векторов длины один, это вернуло бы ошибку всякий раз, когда v
имеет длину один.
Существенными различиями между этими двумя методами являются класс объектов, которые они возвращают при использовании для извлечения, и могут ли они принимать диапазон значений или только одно значение во время присваивания.
Рассмотрим случай извлечения данных в следующем списке:
foo <- list( str='R', vec=c(1,2,3), bool=TRUE )
Скажем, мы хотели бы извлечь значение, сохраненное в bool, из foo и использовать его внутри if()
оператора. Это проиллюстрирует различия между возвращаемыми значениями []
и [[]]
когда они используются для извлечения данных. В []
метод возвращает список объектов класса (или data.frame , если Foo был data.frame) в то время как [[]]
метод возвращает объекты , чей класс определяется типом их значений.
Итак, использование []
метода приводит к следующему:
if( foo[ 'bool' ] ){ print("Hi!") }
Error in if (foo["bool"]) { : argument is not interpretable as logical
class( foo[ 'bool' ] )
[1] "list"
Это связано с тем, что []
метод возвратил список, а список не является допустимым объектом для передачи непосредственно в if()
оператор. В этом случае нам нужно использовать, [[]]
потому что он вернет «голый» объект, хранящийся в «bool», который будет иметь соответствующий класс:
if( foo[[ 'bool' ]] ){ print("Hi!") }
[1] "Hi!"
class( foo[[ 'bool' ]] )
[1] "logical"
Второе отличие состоит в том, что []
оператор может использоваться для доступа к диапазону интервалов в списке или столбцах в кадре данных, в то время как [[]]
оператор ограничен доступом к одному интервалу или столбцу. Рассмотрим случай присвоения значения с использованием второго списка bar()
:
bar <- list( mat=matrix(0,nrow=2,ncol=2), rand=rnorm(1) )
Скажем, мы хотим перезаписать последние два слота foo данными, содержащимися в строке. Если мы попытаемся использовать [[]]
оператор, вот что происходит:
foo[[ 2:3 ]] <- bar
Error in foo[[2:3]] <- bar :
more elements supplied than there are to replace
Это потому, что [[]]
ограничен доступ к одному элементу. Нам нужно использовать []
:
foo[ 2:3 ] <- bar
print( foo )
$str
[1] "R"
$vec
[,1] [,2]
[1,] 0 0
[2,] 0 0
$bool
[1] -0.6291121
Обратите внимание, что пока назначение было успешным, слоты в foo сохранили свои первоначальные имена.
Двойные скобки обращаются к элементу списка , в то время как одна скобка возвращает вам список с одним элементом.
lst <- list('one','two','three')
a <- lst[1]
class(a)
## returns "list"
a <- lst[[1]]
class(a)
## returns "character"
От Хэдли Уикхем:
Моя (на вид дрянная) модификация для отображения с использованием tidyverse / purrr:
[]
извлекает список, [[]]
извлекает элементы из списка
alist <- list(c("a", "b", "c"), c(1,2,3,4), c(8e6, 5.2e9, -9.3e7))
str(alist[[1]])
chr [1:3] "a" "b" "c"
str(alist[1])
List of 1
$ : chr [1:3] "a" "b" "c"
str(alist[[1]][1])
chr "a"
Просто добавив сюда, что [[
также оборудовано для рекурсивной индексации .
На это намекнул @JijoMatthew, но не исследовал.
Как отмечено в ?"[["
, синтаксис, подобный x[[y]]
, где length(y) > 1
, интерпретируется как:
x[[ y[1] ]][[ y[2] ]][[ y[3] ]] ... [[ y[length(y)] ]]
Обратите внимание, что это не меняет того, что должно быть вашим основным понятием разницы между [
и [[
- а именно, что первое используется для поднабора , а второе используется для извлечения отдельных элементов списка.
Например,
x <- list(list(list(1), 2), list(list(list(3), 4), 5), 6)
x
# [[1]]
# [[1]][[1]]
# [[1]][[1]][[1]]
# [1] 1
#
# [[1]][[2]]
# [1] 2
#
# [[2]]
# [[2]][[1]]
# [[2]][[1]][[1]]
# [[2]][[1]][[1]][[1]]
# [1] 3
#
# [[2]][[1]][[2]]
# [1] 4
#
# [[2]][[2]]
# [1] 5
#
# [[3]]
# [1] 6
Чтобы получить значение 3, мы можем сделать:
x[[c(2, 1, 1, 1)]]
# [1] 3
Возвращаясь к ответу @ JijoMatthew выше, вспомните r
:
r <- list(1:10, foo=1, far=2)
В частности, это объясняет ошибки, которые мы склонны получать при неправильном использовании [[
, а именно:
r[[1:3]]
Ошибка в
r[[1:3]]
: не удалось выполнить рекурсивное индексирование на уровне 2
Поскольку этот код фактически пытался оценить r[[1]][[2]][[3]]
, а вложенность r
останавливается на первом уровне, попытка извлечь с помощью рекурсивной индексации не удалась на уровне [[2]]
, то есть на уровне 2.
Ошибка в
r[[c("foo", "far")]]
: индекс за пределами
Здесь R искал r[["foo"]][["far"]]
, чего не существует, поэтому мы получаем ошибку индекса за пределами границ.
Вероятно, было бы немного более полезным / последовательным, если бы обе эти ошибки дали одно и то же сообщение.
Оба они являются способами поднабора. Одиночная скобка вернет подмножество списка, которое само по себе будет списком. То есть: может содержать или не содержать более одного элемента. С другой стороны, двойная скобка вернет только один элемент из списка.
Одиночная скобка даст нам список. Мы также можем использовать одну скобку, если мы хотим вернуть несколько элементов из списка. рассмотрим следующий список:
>r<-list(c(1:10),foo=1,far=2);
Теперь обратите внимание, как возвращается список, когда я пытаюсь его отобразить. Я набираю r и нажимаю ввод
>r
#the result is:-
[[1]]
[1] 1 2 3 4 5 6 7 8 9 10
$foo
[1] 1
$far
[1] 2
Теперь мы увидим магию одиночной скобки: -
>r[c(1,2,3)]
#the above command will return a list with all three elements of the actual list r as below
[[1]]
[1] 1 2 3 4 5 6 7 8 9 10
$foo
[1] 1
$far
[1] 2
что точно так же, как когда мы пытались отобразить значение r на экране, что означает, что использование одиночной скобки вернул список, где по индексу 1 у нас есть вектор из 10 элементов, тогда у нас есть еще два элемента с именами foo и далеко. Мы также можем выбрать один индекс или имя элемента в качестве ввода для одной скобки. например:
> r[1]
[[1]]
[1] 1 2 3 4 5 6 7 8 9 10
В этом примере мы дали один индекс «1» и взамен получили список с одним элементом (который представляет собой массив из 10 чисел)
> r[2]
$foo
[1] 1
В приведенном выше примере мы дали один индекс «2» и взамен получили список с одним элементом
> r["foo"];
$foo
[1] 1
В этом примере мы передали имя одного элемента, а взамен был возвращен список с одним элементом.
Вы также можете передать вектор имен элементов, таких как: -
> x<-c("foo","far")
> r[x];
$foo
[1] 1
$far
[1] 2
В этом примере мы передали вектор с двумя именами элементов "foo" и "far"
Взамен мы получили список с двумя элементами.
Короче говоря, одна скобка всегда вернет вам другой список с количеством элементов, равным количеству элементов или количеству индексов, которые вы передаете в одну скобку.
Напротив, двойная скобка всегда будет возвращать только один элемент. Прежде чем переходить на двойные скобки, следует помнить.
NOTE:THE MAJOR DIFFERENCE BETWEEN THE TWO IS THAT SINGLE BRACKET RETURNS YOU A LIST WITH AS MANY ELEMENTS AS YOU WISH WHILE A DOUBLE BRACKET WILL NEVER RETURN A LIST. RATHER A DOUBLE BRACKET WILL RETURN ONLY A SINGLE ELEMENT FROM THE LIST.
Я приведу несколько примеров. Пожалуйста, запишите слова жирным шрифтом и вернитесь к ним после того, как закончите с примерами ниже:
Двойная скобка вернет вам фактическое значение по индексу. ( НЕ вернет список)
> r[[1]]
[1] 1 2 3 4 5 6 7 8 9 10
>r[["foo"]]
[1] 1
для двойных скобок, если мы попытаемся просмотреть более одного элемента, передав вектор, это приведет к ошибке только потому, что она создана не для удовлетворения этой потребности, а просто для возврата одного элемента.
Рассмотрим следующее
> r[[c(1:3)]]
Error in r[[c(1:3)]] : recursive indexing failed at level 2
> r[[c(1,2,3)]]
Error in r[[c(1, 2, 3)]] : recursive indexing failed at level 2
> r[[c("foo","far")]]
Error in r[[c("foo", "far")]] : subscript out of bounds
[]
класс списка возвращается, даже если он представляет собой одну цифру, очень не интуитивно понятен. Им следовало создать другой синтаксис, подобный ([])
списку, и [[]]
для доступа к фактическому элементу все в порядке. Я предпочитаю воспринимать [[]]
как необработанную ценность, как и в других языках.
Чтобы помочь новичкам перемещаться по ручному туману, было бы полезно рассматривать [[ ... ]]
нотацию как сворачивающуюся функцию - другими словами, это когда вы просто хотите «получить данные» из именованного вектора, списка или фрейма данных. Это хорошо сделать, если вы хотите использовать данные этих объектов для расчетов. Эти простые примеры проиллюстрируют.
(x <- c(x=1, y=2)); x[1]; x[[1]]
(x <- list(x=1, y=2, z=3)); x[1]; x[[1]]
(x <- data.frame(x=1, y=2, z=3)); x[1]; x[[1]]
Итак, из третьего примера:
> 2 * x[1]
x
1 2
> 2 * x[[1]]
[1] 2
iris[[1]]
возвращает вектор, тогда как iris[1]
возвращает data.frame
Будучи терминологическим, [[
оператор извлекает элемент из списка, тогда как [
оператор берет подмножество списка.
Для еще одного конкретного случая использования используйте двойные скобки, когда вы хотите выбрать фрейм данных, созданный split()
функцией. Если вы не знаете, split()
группируйте список / фрейм данных в подмножества на основе ключевого поля. Это полезно, если вы хотите работать с несколькими группами, строить их и т. Д.
> class(data)
[1] "data.frame"
> dsplit<-split(data, data$id)
> class(dsplit)
[1] "list"
> class(dsplit['ID-1'])
[1] "list"
> class(dsplit[['ID-1']])
[1] "data.frame"
Пожалуйста, обратитесь к подробному объяснению ниже.
Я использовал встроенный фрейм данных в R, называемый mtcars.
> mtcars
mpg cyl disp hp drat wt ...
Mazda RX4 21.0 6 160 110 3.90 2.62 ...
Mazda RX4 Wag 21.0 6 160 110 3.90 2.88 ...
Datsun 710 22.8 4 108 93 3.85 2.32 ...
............
Верхняя строка таблицы называется заголовком, который содержит имена столбцов. Каждая горизонтальная линия после этого обозначает строку данных, которая начинается с имени строки, а затем сопровождается фактическими данными. Каждый элемент данных строки называется ячейкой.
Чтобы получить данные в ячейке, мы должны ввести ее координаты строки и столбца в одной квадратной скобке оператора «[]». Две координаты разделены запятой. Другими словами, координаты начинаются с позиции строки, затем следуют запятая и заканчиваются позицией столбца. Порядок важен.
Например: 1 - это значение ячейки из первого ряда, второго столбца mtcars.
> mtcars[1, 2]
[1] 6
Например, 2: - Кроме того, мы можем использовать имена строк и столбцов вместо числовых координат.
> mtcars["Mazda RX4", "cyl"]
[1] 6
Мы ссылаемся на столбец фрейма данных оператором [[]] двойной квадратной скобки.
Например, 1: - Чтобы получить вектор девятого столбца встроенного набора данных mtcars, мы пишем mtcars [[9]].
mtcars [[9]] [1] 1 1 1 0 0 0 0 0 0 0 0 ...
Например, 2: - Мы можем получить тот же вектор столбца по его имени.
mtcars [["am"]] [1] 1 1 1 0 0 0 0 0 0 0 0 ...
К тому же:
Вот небольшой пример, посвященный следующему пункту:
x[i, j] vs x[[i, j]]
df1 <- data.frame(a = 1:3)
df1$b <- list(4:5, 6:7, 8:9)
df1[[1,2]]
df1[1,2]
str(df1[[1,2]])
str(df1[1,2])