Jak utworzyć listę ramek danych?

186

Jak utworzyć listę ramek danych i jak uzyskać dostęp do każdej z tych ramek danych z listy?

Na przykład, jak mogę umieścić te ramki danych na liście?

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))
Ben
źródło
13
Jest to w kilku odpowiedziach, ale warto tu również mieć widoczny komentarz: =nie używaj w <-środku data.frame(). Używając <-tworzenia y1i y2w swoim globalnym środowisku, twoja ramka danych nie jest taka, jak chcesz.
Gregor Thomas
37
Spójrz na ten bałagan kodu bez spacji <-is wewnątrz data.frame (). Co za nowość.
Ben
5
Nigdy więcej. Właśnie edytowałem twoje pytanie, aby naprawić formatowanie kodu. Zapraszam do powrotu, jeśli czujesz nostalgię.
Claus Wilke,

Odpowiedzi:

133

Nie ma to związku z twoim pytaniem, ale chcesz go używać, =a nie <-w wywołaniu funkcji. Jeśli go użyjesz <-, skończysz na tworzeniu zmiennych y1i y2niezależnie od środowiska, w którym pracujesz:

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

To nie będzie miało pozornie pożądanego efektu tworzenia nazw kolumn w ramce danych:

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

Z =drugiej strony operator powiąże Twoje wektory z argumentami data.frame.

Jeśli chodzi o twoje pytanie, utworzenie listy ramek danych jest łatwe:

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)

Dostęp do ramek danych uzyskuje się tak samo, jak do dowolnego innego elementu listy:

my.list[[1]]
#   y1 y2
# 1  1  4
# 2  2  5
# 3  3  6
Peyton
źródło
344

Pozostałe odpowiedzi pokazać wam jak zrobić listę data.frames kiedy już kilka data.frames, np d1, d2.... Mając nazwanych kolejno ramek danych jest problemem, a umieszczenie ich w wykazie jest to dobra poprawka, ale najlepszą praktyką jest unikanie posiadania wiązki danych. ramek nie ma na liście .

Inne odpowiedzi podają wiele szczegółów na temat przypisywania ramek danych elementom listy, uzyskiwania do nich dostępu itp. Omówimy to trochę tutaj, ale głównym punktem jest to, że nie czekaj, aż będziesz miał sporo data.framesaby dodać je do listy. Zacznij od listy.

Pozostała część tej odpowiedzi obejmie niektóre typowe przypadki, w których możesz ulec pokusie, aby utworzyć sekwencyjne zmienne i pokazać, jak przejść bezpośrednio do list. Jeśli dopiero zaczynasz korzystać z list w R, możesz także przeczytać Jaka jest różnica między dostępem do elementów listy [[a [dostępem do nich? .


Listy od początku

Nigdy nie twórz d1 d2 d3... ... dnw pierwszej kolejności. Utwórz listę dz nelementami.

Odczytywanie wielu plików do listy ramek danych

Odbywa się to dość łatwo podczas wczytywania plików. Może masz pliki data1.csv, data2.csv, ...w katalogu. Twoim celem jest lista nazwanych data.frames mydata. Pierwszą rzeczą, której potrzebujesz, jest wektor ze wszystkimi nazwami plików. Można skonstruować ten pastą (np my_files = paste0("data", 1:5, ".csv")), ale to chyba łatwiejsze w użyciu list.files, aby pobrać wszystkie odpowiednie pliki: my_files <- list.files(pattern = "\\.csv$"). Możesz użyć wyrażeń regularnych, aby dopasować pliki, przeczytaj więcej o wyrażeniach regularnych w innych pytaniach, jeśli potrzebujesz pomocy. W ten sposób możesz pobrać wszystkie pliki CSV, nawet jeśli nie są zgodne z dobrym schematem nazewnictwa. Lub możesz użyć bardziej wyszukanego wzorca wyrażenia regularnego, jeśli chcesz wybrać niektóre pliki CSV z kilku z nich.

W tym momencie większość początkujących R używa forpętli i nie ma w tym nic złego, działa dobrze.

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

Bardziej podobny do R sposób to zrobić lapply, co jest skrótem do powyższego

my_data <- lapply(my_files, read.csv)

Oczywiście zastąp odpowiednio inną funkcję importu danych read.csv. readr::read_csvlub data.table::freadbędzie szybszy, lub możesz potrzebować innej funkcji dla innego typu pliku.

Tak czy inaczej, warto nazwać elementy listy, aby pasowały do ​​plików

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 = "")

Dzielenie ramki danych na listę ramek danych

Jest to bardzo łatwe, funkcja podstawowa split()robi to za Ciebie. Możesz dzielić według kolumny (lub kolumn) danych lub według dowolnej innej opcji

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

Jest to również dobry sposób na rozbicie ramki danych na części w celu weryfikacji krzyżowej. Może chcesz podzielić się mtcarsna części szkoleniowe, testowe i weryfikacyjne.

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

Symulowanie listy ramek danych

Może symulujesz dane, coś takiego:

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

Ale kto wykonuje tylko jedną symulację? Chcesz to zrobić 100 razy, 1000 razy, więcej! Ale nie chcesz 10 000 ramek danych w swoim obszarze roboczym. Użyj replicatei umieść je na liście:

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

W tym przypadku szczególnie powinieneś rozważyć, czy naprawdę potrzebujesz osobnych ramek danych, czy też pojedyncza ramka danych z kolumną „grupową” równie dobrze by działała? Używanie data.tablelub dplyrdość łatwo jest robić rzeczy „grupowo” w ramce danych.

Nie umieściłem moich danych na liście :( Będę następnym razem, ale co mogę teraz zrobić?

Jeśli są dziwnym asortymentem (co jest niezwykłe), możesz po prostu przypisać im:

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

Jeśli masz ramek danych nazwanych we wzór, na przykład df1, df2, df3, i chcesz je na liście, można getje, jeśli możesz napisać wyrażenie regularne pasujące nazwy. Coś jak

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.

Zasadniczo mgetsłuży do pobierania wielu obiektów i zwracania ich na nazwaną listę. Jego odpowiednik getsłuży do pobrania pojedynczego obiektu i zwrócenia go (nie na liście).

Łączenie listy ramek danych w jedną ramkę danych

Częstym zadaniem jest łączenie listy ramek danych w jedną dużą ramkę danych. Jeśli chcesz je ułożyć jeden na drugim, użyj ich rbinddla pary, ale dla listy ramek danych są trzy dobre opcje:

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

(Podobnie przy użyciu cbindlub dplyr::bind_colsdla kolumn.)

Aby scalić (dołączyć) listę ramek danych, możesz zobaczyć te odpowiedzi . Często chodzi o to, aby korzystać Reducez merge(lub innej funkcji łączącej), aby je ze sobą.

Po co umieszczać dane na liście?

Umieścić podobne dane w listach, bo chcesz robić podobne rzeczy do każdej ramki danych, a funkcje takie jak lapply, sapply do.call, opakowanie , a stare funkcje sprawiają, że łatwo to zrobić. Przykłady osób, które łatwo robią rzeczy z listami, są na SO.purrrplyr l*ply

Nawet jeśli używasz mało używanej pętli for, o wiele łatwiej jest zapętlać elementy listy niż konstruować nazwy zmiennych pastei uzyskiwać do nich dostęp get. Łatwiej też debugować.

Pomyśl o skalowalności . Jeśli naprawdę potrzebujesz tylko trzy zmienne, w porządku do użytku d1, d2, d3. Ale jeśli okaże się, że naprawdę potrzebujesz 6, to dużo więcej pisania. Następnym razem, gdy będziesz potrzebować 10 lub 20, przekonasz się, że kopiujesz i wklejasz linie kodu, być może używasz funkcji znajdź / zamień, aby zmienić d14na d15, i myślisz, że nie tak powinno być programowanie . Jeśli używasz listy, różnica między 3 przypadkami, 30 przypadkami i 300 przypadkami wynosi co najwyżej jeden wiersz kodu --- bez zmian, jeśli liczba spraw jest automatycznie wykrywana np. Przez liczbę .csvplików w twoim informator.

Możesz nazwać elementy listy, na wypadek gdybyś chciał użyć czegoś innego niż indeksy numeryczne, aby uzyskać dostęp do swoich ramek danych (i możesz użyć obu, nie jest to wybór XOR).

Ogólnie rzecz biorąc, używanie list doprowadzi Cię do napisania czystszego, łatwiejszego do odczytania kodu, co spowoduje mniej błędów i mniej zamieszania.

Gregor Thomas
źródło
2
Którą książkę polecasz na okładki do pracy z listami?
Opuszczony
15
Zalecam przeczytanie pytań i odpowiedzi na temat przepełnienia stosu, które są oznaczone zarówno jako, jak ri list.
Gregor Thomas
2
@Gregor Chciałbym dodać, że możemy uniknąć nazwania elementów listy tak, aby pasowały do ​​plików, po prostu przez przypisanie my_data <- NULLzamiast „my_data <- list () '! :)
Daniel
6
Jest to możliwe, ale my_data <- list()wyjaśnia, że ​​tworzysz listę, co jest dobre! Wyczyść kod to dobra rzecz. Nie widzę żadnej korzyści z używania my_data <- NULLzamiast tego.
Gregor Thomas
3
Zgadzam się, co do tego, co powiedziałeś, ale tak jak powiedziałem, dzięki czemu możesz uciec od etapu nazywania plików. names(my_data) <- gsub("\\.csv$", "", my_files) ;) <br> Ale szanuję twoje rady, ponieważ dużo się od nich uczę jako początkujący i naprawdę to doceniam :)
Daniel,
21

Możesz również uzyskać dostęp do określonych kolumn i wartości w każdym elemencie listy za pomocą [i [[. Oto kilka przykładów. Po pierwsze, możemy uzyskać dostęp tylko do pierwszej kolumny każdej ramki danych na liście za pomocą lapply(ldf, "[", 1), gdzie 1oznacza numer kolumny.

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

Podobnie możemy uzyskać dostęp do pierwszej wartości w drugiej kolumnie za pomocą

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

Następnie możemy również uzyskać dostęp do wartości kolumn bezpośrednio, jako wektor, za pomocą [[

lapply(ldf, "[[", 1)
# $d1
# [1] 1 2 3
#
# $d2
# [1] 3 2 1
Rich Scriven
źródło
13

Jeśli masz dużą liczbę ramek danych o kolejnych nazwach, możesz utworzyć listę żądanego podzbioru ramek danych w następujący sposób:

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

gdzie my.list2zwraca listę zawierającą 2., 3. i 4. ramkę danych.

[[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

Należy jednak pamiętać, że ramki danych z powyższej listy nie są już nazywane. Jeśli chcesz utworzyć listę zawierającą podzbiór ramek danych i zachować ich nazwy, możesz spróbować:

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

który zwraca:

> 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
Mark Miller
źródło
2
Zamiast tego lapply(foo, get)użyjmget(foo)
Gregor Thomas
9

Biorąc pod uwagę, że masz „dużą” liczbę ramek danych o podobnych nazwach (tutaj d #, gdzie # to pewna dodatnia liczba całkowita), poniżej jest niewielka poprawa metody @ mark-millera. Jest bardziej zwięzły i zwraca nazwaną listę data.frame, gdzie każda nazwa na liście jest nazwą odpowiedniego oryginalnego data.frame.

Klucz używa mgetrazem z ls. Jeśli ramki danych d1 i d2 podane w pytaniu były jedynymi obiektami o nazwach d # w środowisku, to

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

które wróci

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

Ta metoda wykorzystuje argument wzorca w ls, który pozwala nam używać wyrażeń regularnych do dokładniejszej analizy nazw obiektów w środowisku. Alternatywą dla wyrażenia regularnego "^d[0-9]+$"jest "^d\\d+$".

Jak wskazuje @gregor , ogólnie lepiej jest skonfigurować proces konstruowania danych, aby data.frame były umieszczane na nazwanych listach na początku.

dane

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))
Lmo
źródło
3

To może być trochę późno, ale wracając do twojego przykładu, pomyślałem, że odrobinę przedłużę odpowiedź.

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

Następnie łatwo tworzysz listę:

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

Teraz masz listę, ale zamiast uzyskiwać do niej dostęp w stary sposób, taki jak

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

możesz użyć tej funkcji, aby uzyskać i przypisać wybraną ramkę danych.

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

Teraz zdobądź ten, który chcesz.

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

Mam nadzieję, że ten dodatkowy kawałek pomaga.

Twoje zdrowie!

ML_for_now
źródło
2
Tak, wiem, ale z jakiegoś powodu po skopiowaniu i wklejeniu wszystko poszło na dobre. :( W każdym razie kod
pisany
4
Jestem ciekaw dlaczego wolisz GETDF_FROMLIST(mylist, 1)się mylist[[1]]? Jeśli wolisz składnię funkcji, możesz to zrobić nawet "[["(mylist, 1)bez definiowania funkcji niestandardowej.
Gregor Thomas
4
Możesz także uprościć definicję funkcji, cała treść funkcji może być po prostu return(DF_LIST[[ITEM_LOC]]), bez potrzeby przypisywania zmiennej pośredniej.
Gregor Thomas
1

Bardzo prosty ! Oto moja sugestia:

Jeśli chcesz wybrać ramki danych w swoim obszarze roboczym, spróbuj tego:

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

lub

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

wszystko to da ten sam rezultat.

Możesz zmienić, is.data.frameaby sprawdzić inne typy zmiennych, takie jakis.function

Soufiane Chami
źródło
1

Uważam się za kompletnego nowicjusza, ale myślę, że mam niezwykle prostą odpowiedź na jedno z pierwotnych pytań, które nie zostały tutaj wymienione: dostęp do ramek danych lub ich części.

Zacznijmy od utworzenia listy z ramkami danych, jak wspomniano powyżej:

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)

Następnie, jeśli chcesz uzyskać dostęp do określonej wartości w jednej z ramek danych, możesz to zrobić, używając kolejno podwójnych nawiasów. Pierwszy zestaw przenosi Cię do ramki danych, a drugi zestaw prowadzi do określonych współrzędnych:

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

[1] 6
Loek van der Kallen
źródło