Как мне составить список фреймов данных?


186

Как составить список фреймов данных и как получить доступ к каждому из этих фреймов данных из списка?

Например, как я могу поместить эти фреймы данных в список?

d1 <- data.frame(y1 = c(1, 2, 3),
                 y2 = c(4, 5, 6))
d2 <- data.frame(y1 = c(3, 2, 1),
                 y2 = c(6, 5, 4))

13
Это в паре ответов, но здесь тоже стоит иметь заметный комментарий: используйте =не <-внутри data.frame(). При использовании <-вы создаете y1и y2в вашей глобальной среде, и ваш фрейм данных не то, что вы хотите, чтобы это было.
Грегор Томас

37
Посмотрите на этот беспорядок кода без пробелов и <-внутри data.frame (). Каким я был новичком.
Бен

5
Уже нет. Я только что отредактировал ваш вопрос, чтобы исправить форматирование кода. Не стесняйтесь вернуться, если вы чувствуете ностальгию.
Клаус Уилк

Ответы:


133

Это не связано с вашим вопросом, но вы хотите использовать, =а не <-в вызове функции. Если вы используете <-, вы в конечном итоге создадите переменные y1и y2в любой среде, в которой вы работаете:

d1 <- data.frame(y1 <- c(1, 2, 3), y2 <- c(4, 5, 6))
y1
# [1] 1 2 3
y2
# [1] 4 5 6

Это не будет иметь желаемого эффекта от создания имен столбцов во фрейме данных:

d1
#   y1....c.1..2..3. y2....c.4..5..6.
# 1                1                4
# 2                2                5
# 3                3                6

=Оператор, с другой стороны, будет связывать свои векторы с аргументами data.frame.

Что касается вашего вопроса, составить список фреймов данных очень просто:

d1 <- data.frame(y1 = c(1, 2, 3), y2 = c(4, 5, 6))
d2 <- data.frame(y1 = c(3, 2, 1), y2 = c(6, 5, 4))
my.list <- list(d1, d2)

Вы получаете доступ к фреймам данных так же, как и к любому другому элементу списка:

my.list[[1]]
#   y1 y2
# 1  1  4
# 2  2  5
# 3  3  6

344

Другие ответы показать вам , как сделать список data.frames , когда вы уже имеете кучу data.frames, например, d1, d2, .... Имея последовательно именованные кадры данных является проблемой, и поместив их в список является Хорошее решение, но лучше всего избегать того, чтобы куча data.frames отсутствовала в списке .

Другие ответы дают много подробностей о том, как назначить фреймы данных элементам списка, получить к ним доступ и т. Д. Мы также рассмотрим это немного здесь, но главное - сказать , не ждите, пока у вас не появится куча data.framesдобавить их в список. Начните со списка.

В оставшейся части этого ответа будут рассмотрены некоторые распространенные случаи, когда у вас может возникнуть желание создать последовательные переменные, и будет показано, как переходить прямо к спискам. Если вы новичок в списках в R, вы можете также прочитать В чем разница между доступом к элементам списка [[и [доступом к ним? ,


Списки с самого начала

Никогда не создавай d1 d2 d3... dnво-первых. Создайте список dс nэлементами.

Чтение нескольких файлов в список фреймов данных

Это делается довольно легко при чтении в файлах. Может быть, у вас есть файлы data1.csv, data2.csv, ...в каталоге. Ваша цель - список вызываемых data.frames mydata. Первое, что вам нужно, это вектор со всеми именами файлов. Вы можете построить это с пастой (например, my_files = paste0("data", 1:5, ".csv")), но это, вероятно , проще в использовании , list.filesчтобы захватить все соответствующие файлы: my_files <- list.files(pattern = "\\.csv$"). Вы можете использовать регулярные выражения для сопоставления файлов, узнайте больше о регулярных выражениях в других вопросах, если вам нужна помощь там. Таким образом, вы можете получить все файлы CSV, даже если они не следуют хорошей схеме именования. Или вы можете использовать более необычный шаблон регулярных выражений, если вам нужно выбрать определенные CSV-файлы из нескольких.

На этом этапе большинство начинающих R будут использовать forцикл, и в этом нет ничего плохого, он работает просто отлично.

my_data <- list()
for (i in seq_along(my_files)) {
    my_data[[i]] <- read.csv(file = my_files[i])
}

Более R-подобный способ сделать это с lapplyпомощью ярлыка для выше

my_data <- lapply(my_files, read.csv)

Конечно, замените другую функцию импорта данных на read.csvсоответствующую. readr::read_csvили data.table::freadбудет быстрее, или вам может потребоваться другая функция для файла другого типа.

В любом случае, удобно именовать элементы списка в соответствии с файлами

names(my_data) <- gsub("\\.csv$", "", my_files)
# or, if you prefer the consistent syntax of stringr
names(my_data) <- stringr::str_replace(my_files, pattern = ".csv", replacement = "")

Разделение фрейма данных на список фреймов данных

Это очень просто, базовая функция split()делает это за вас. Вы можете разделить на столбец (или столбцы) данных, или на что угодно

mt_list = split(mtcars, f = mtcars$cyl)
# This gives a list of three data frames, one for each value of cyl

Это также хороший способ разбить фрейм данных на части для перекрестной проверки. Может быть, вы хотите разделить mtcarsна части обучения, тестирования и проверки.

groups = sample(c("train", "test", "validate"),
                size = nrow(mtcars), replace = TRUE)
mt_split = split(mtcars, f = groups)
# and mt_split has appropriate names already!

Имитация списка фреймов данных

Может быть, вы имитируете данные, что-то вроде этого:

my_sim_data = data.frame(x = rnorm(50), y = rnorm(50))

Но кто делает только одну симуляцию? Вы хотите сделать это 100 раз, 1000 раз, больше! Но вам не нужно 10 000 фреймов данных в вашем рабочем пространстве. Используйте replicateи поместите их в список:

sim_list = replicate(n = 10,
                     expr = {data.frame(x = rnorm(50), y = rnorm(50))},
                     simplify = F)

Особенно в этом случае вам следует также подумать, действительно ли вам нужны отдельные фреймы данных, или же сработал бы один фрейм данных со столбцом «группа»? Использование data.tableили dplyrэто довольно легко сделать "по группам" с фреймом данных.

Я не поместил свои данные в список :( Я сделаю это в следующий раз, но что мне теперь делать?

Если это нечетный ассортимент (что необычно), вы можете просто назначить их:

mylist <- list()
mylist[[1]] <- mtcars
mylist[[2]] <- data.frame(a = rnorm(50), b = runif(50))
...

Если у вас есть кадры данных с именем в виде рисунка, например, df1, df2, df3и вы хотите их в списке, вы можете getих , если вы можете написать регулярное выражение для имен. Что-то вроде

df_list = mget(ls(pattern = "df[0-9]"))
# this would match any object with "df" followed by a digit in its name
# you can test what objects will be got by just running the
ls(pattern = "df[0-9]")
# part and adjusting the pattern until it gets the right objects.

Обычно mgetиспользуется для получения нескольких объектов и возврата их в именованный список. Его аналог getиспользуется для получения одного объекта и его возврата (не в списке).

Объединение списка фреймов данных в один фрейм данных

Обычной задачей является объединение списка фреймов данных в один большой фрейм данных. Если вы хотите разместить их друг над другом, вы должны использовать rbindих пару, но для списка фреймов данных есть три хороших варианта:

# base option - slower but not extra dependencies
big_data = do.call(what = rbind, args = df_list)

# data table and dplyr have nice functions for this that
#  - are much faster
#  - add id columns to identify the source
#  - fill in missing values if some data frames have more columns than others
# see their help pages for details
big_data = data.table::rbindlist(df_list)
big_data = dplyr::bind_rows(df_list)

(Аналогично, используя cbindили dplyr::bind_colsдля столбцов.)

Чтобы объединить (объединить) список фреймов данных, вы можете увидеть эти ответы . Часто, идея заключается в том , чтобы использовать Reduceс merge(или какой - либо другой функцией соединения) , чтобы получить их вместе.

Зачем помещать данные в список?

Помещенный аналогичные данные в списках , потому что вы хотите делать подобные вещи для каждого кадра данных, а также функции , такие как lapply, sapply do.call, пакет , и старые функции позволяют легко сделать это. Примеры людей, легко делающих вещи со списками, повсюду.purrrplyr l*ply

Даже если вы используете простой цикл for, гораздо проще зацикливать элементы списка, чем создавать имена переменных с помощью pasteи обращаться к объектам с помощью get. Проще отлаживать тоже.

Подумайте о масштабируемости . Если вам действительно нужно только три переменные, это хорошо для использования d1, d2, d3. Но тогда, если окажется, что вам действительно нужно 6, это намного больше печатать. И в следующий раз, когда вам понадобится 10 или 20, вы обнаружите, что копируете и вставляете строки кода, возможно, используете find / replace для перехода d14на d15, и вы думаете, что программирование не должно быть таким . Если вы используете список, разница между 3 случаями, 30 случаями и 300 случаями составляет не более одной строки кода - без изменений, если количество случаев автоматически определяется, например, по количеству .csvфайлов в вашем каталог.

Вы можете назвать элементы списка, если вы хотите использовать что-то кроме числовых индексов для доступа к вашим фреймам данных (и вы можете использовать оба, это не выбор XOR).

В целом, использование списков приведет к написанию более чистого и удобного для чтения кода, что приведет к уменьшению количества ошибок и путанице.


2
Какую книгу вы рекомендуете для работы со списками?
Заброшенный

15
Я рекомендую прочитать вопросы и ответы по переполнению стека, которые отмечены как rи list.
Грегор Томас

2
@ Грегор Я хотел бы добавить, что мы можем избежать именования элементов списка в соответствии с файлами, просто назначая my_data <- NULLвместо my_data <- list () '! :)
Даниил

6
Это возможно, но my_data <- list()дает понять, что вы создаете список, и это хорошо! Чистый код это хорошая вещь. Я не вижу никакого преимущества в использовании my_data <- NULLвместо этого.
Грегор Томас

3
Я согласен с тем, что вы сказали, но, как я уже сказал, делая это, вы можете избежать стадии именования файлов. names(my_data) <- gsub("\\.csv$", "", my_files) ;) <br> Но я действительно уважаю ваши советы, поскольку я многому у них учусь, как новичку, и я действительно ценю это :)
Даниэль

21

Вы также можете получить доступ к определенным столбцам и значениям в каждом элементе списка с помощью [и [[. Вот пара примеров. Во-первых, мы можем получить доступ только к первому столбцу каждого фрейма данных в списке lapply(ldf, "[", 1), где 1обозначает номер столбца.

ldf <- list(d1 = d1, d2 = d2)  ## create a named list of your data frames
lapply(ldf, "[", 1)
# $d1
#   y1
# 1  1
# 2  2
# 3  3
#
# $d2
#   y1
# 1  3
# 2  2
# 3  1

Точно так же мы можем получить доступ к первому значению во втором столбце с помощью

lapply(ldf, "[", 1, 2)
# $d1
# [1] 4
# 
# $d2
# [1] 6

Тогда мы также можем получить доступ к значениям столбца напрямую, как вектор, с [[

lapply(ldf, "[[", 1)
# $d1
# [1] 1 2 3
#
# $d2
# [1] 3 2 1

13

Если у вас есть большое количество последовательно именованных фреймов данных, вы можете создать список нужного подмножества фреймов данных, например:

d1 <- data.frame(y1=c(1,2,3), y2=c(4,5,6))
d2 <- data.frame(y1=c(3,2,1), y2=c(6,5,4))
d3 <- data.frame(y1=c(6,5,4), y2=c(3,2,1))
d4 <- data.frame(y1=c(9,9,9), y2=c(8,8,8))

my.list <- list(d1, d2, d3, d4)
my.list

my.list2 <- lapply(paste('d', seq(2,4,1), sep=''), get)
my.list2

где my.list2возвращает список, содержащий 2-й, 3-й и 4-й кадры данных.

[[1]]
  y1 y2
1  3  6
2  2  5
3  1  4

[[2]]
  y1 y2
1  6  3
2  5  2
3  4  1

[[3]]
  y1 y2
1  9  8
2  9  8
3  9  8

Однако обратите внимание, что кадры данных в приведенном выше списке больше не называются. Если вы хотите создать список, содержащий подмножество фреймов данных и хотите сохранить их имена, вы можете попробовать это:

list.function <-  function() { 

     d1 <- data.frame(y1=c(1,2,3), y2=c(4,5,6))
     d2 <- data.frame(y1=c(3,2,1), y2=c(6,5,4))
     d3 <- data.frame(y1=c(6,5,4), y2=c(3,2,1))
     d4 <- data.frame(y1=c(9,9,9), y2=c(8,8,8))

     sapply(paste('d', seq(2,4,1), sep=''), get, environment(), simplify = FALSE) 
} 

my.list3 <- list.function()
my.list3

который возвращает:

> my.list3
$d2
  y1 y2
1  3  6
2  2  5
3  1  4

$d3
  y1 y2
1  6  3
2  5  2
3  4  1

$d4
  y1 y2
1  9  8
2  9  8
3  9  8

> str(my.list3)
List of 3
 $ d2:'data.frame':     3 obs. of  2 variables:
  ..$ y1: num [1:3] 3 2 1
  ..$ y2: num [1:3] 6 5 4
 $ d3:'data.frame':     3 obs. of  2 variables:
  ..$ y1: num [1:3] 6 5 4
  ..$ y2: num [1:3] 3 2 1
 $ d4:'data.frame':     3 obs. of  2 variables:
  ..$ y1: num [1:3] 9 9 9
  ..$ y2: num [1:3] 8 8 8

> my.list3[[1]]
  y1 y2
1  3  6
2  2  5
3  1  4

> my.list3$d4
  y1 y2
1  9  8
2  9  8
3  9  8

2
Вместо этого lapply(foo, get)просто используйтеmget(foo)
Грегор Томас

9

Принимая как данность, у вас есть «большое» число data.frames с похожими именами (здесь d #, где # - некоторое положительное целое число), следующее - небольшое улучшение метода @ mark-miller. Это более кратко и возвращает именованный список data.frames, где каждое имя в списке является именем соответствующего исходного data.frame.

Ключ используется mgetвместе с ls. Если фреймы данных d1 и d2, представленные в вопросе, были единственными объектами с именами d # в среде, то

my.list <- mget(ls(pattern="^d[0-9]+"))

который бы вернулся

my.list
$d1
  y1 y2
1  1  4
2  2  5
3  3  6

$d2
  y1 y2
1  3  6
2  2  5
3  1  4

В этом методе используется аргумент pattern ls, который позволяет нам использовать регулярные выражения для более точного анализа имен объектов в среде. Альтернативой регулярному выражению "^d[0-9]+$"является "^d\\d+$".

Как указывает @gregor , в целом лучше настроить процесс построения данных, чтобы в начале они помещались в именованные списки.

данные

d1 <- data.frame(y1 = c(1,2,3),y2 = c(4,5,6))
d2 <- data.frame(y1 = c(3,2,1),y2 = c(6,5,4))

3

Это может быть немного поздно, но возвращаясь к вашему примеру, я подумал, что я бы немного расширил ответ.

 D1 <- data.frame(Y1=c(1,2,3), Y2=c(4,5,6))
 D2 <- data.frame(Y1=c(3,2,1), Y2=c(6,5,4))
 D3 <- data.frame(Y1=c(6,5,4), Y2=c(3,2,1))
 D4 <- data.frame(Y1=c(9,9,9), Y2=c(8,8,8))

Тогда вы легко составите свой список:

mylist <- list(D1,D2,D3,D4)

Теперь у вас есть список, но вместо доступа к списку старый способ, такой как

mylist[[1]] # to access 'd1'

Вы можете использовать эту функцию, чтобы получить и назначить данные по вашему выбору.

GETDF_FROMLIST <- function(DF_LIST, ITEM_LOC){
   DF_SELECTED <- DF_LIST[[ITEM_LOC]]
   return(DF_SELECTED)
}

Теперь получите тот, который вы хотите.

D1 <- GETDF_FROMLIST(mylist, 1)
D2 <- GETDF_FROMLIST(mylist, 2)
D3 <- GETDF_FROMLIST(mylist, 3)
D4 <- GETDF_FROMLIST(mylist, 4)

Надеюсь, что дополнительный бит поможет.

Ура!


2
Да, я знаю, но по какой-то причине, когда я скопировал и вставил, все пошло кепкам. :( В любом случае код в нижнем регистре работает.
ML_for_now

4
Мне интересно , почему вы предпочли бы , GETDF_FROMLIST(mylist, 1)чтобы mylist[[1]]? Если вы предпочитаете синтаксис функции, вы можете обойтись "[["(mylist, 1)без определения пользовательской функции.
Грегор Томас

4
Вы также можете упростить определение своей функции, весь текст функции может быть просто return(DF_LIST[[ITEM_LOC]]), нет необходимости назначать промежуточную переменную.
Грегор Томас

1

Очень просто ! Вот мое предложение:

Если вы хотите выбрать кадры данных в своем рабочем пространстве, попробуйте это:

Filter(function(x) is.data.frame(get(x)) , ls())

или

ls()[sapply(ls(), function(x) is.data.frame(get(x)))]

все это даст тот же результат.

Вы можете изменить, is.data.frameчтобы проверить другие типы переменных, таких какis.function


1

Я считаю себя полным новичком, но я думаю, что у меня есть чрезвычайно простой ответ на один из первоначальных подвопросов, который здесь не был сформулирован: доступ к фреймам данных или их частям.

Начнем с создания списка с фреймами данных, как было указано выше:

d1 <- data.frame(y1 = c(1, 2, 3), y2 = c(4, 5, 6))

d2 <- data.frame(y1 = c(3, 2, 1), y2 = c(6, 5, 4))

my.list <- list(d1, d2)

Затем, если вы хотите получить доступ к определенному значению в одном из фреймов данных, вы можете сделать это, используя двойные скобки последовательно. Первый набор возвращает вас во фрейм данных, а второй набор возвращает вас к определенным координатам:

my.list[[1]][[3,2]]

[1] 6
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.