Jak zaimportować wiele plików .csv jednocześnie?

219

Załóżmy, że mamy folder zawierający wiele plików data.csv, z których każdy zawiera tę samą liczbę zmiennych, ale każdy z innego czasu. Czy istnieje sposób, aby w R zaimportować je wszystkie jednocześnie zamiast importować je wszystkie indywidualnie?

Mój problem polega na tym, że mam około 2000 plików danych do zaimportowania i muszę je zaimportować indywidualnie za pomocą kodu:

read.delim(file="filename", header=TRUE, sep="\t")

nie jest bardzo wydajny.

Jojo Ono
źródło

Odpowiedzi:

259

Coś takiego jak poniżej powinno skutkować każdą ramką danych jako oddzielnym elementem na pojedynczej liście:

temp = list.files(pattern="*.csv")
myfiles = lapply(temp, read.delim)

Zakłada się, że te pliki CSV znajdują się w jednym katalogu - bieżącym katalogu roboczym - i że wszystkie mają małe litery .csv.

Jeśli następnie chcemy połączyć te ramki danych w jednej ramce danych, zobacz rozwiązania w innych odpowiedzi za pomocą takich rzeczy do.call(rbind,...), dplyr::bind_rows()albo data.table::rbindlist().

Jeśli naprawdę chcesz każdej ramki danych w oddzielnym obiekcie, nawet jeśli jest to często niewskazane, możesz wykonać następujące czynności assign:

temp = list.files(pattern="*.csv")
for (i in 1:length(temp)) assign(temp[i], read.csv(temp[i]))

Lub bez assigni, aby zademonstrować (1), jak można wyczyścić nazwę pliku i (2) pokazać, jak używać list2env, możesz wypróbować następujące czynności:

temp = list.files(pattern="*.csv")
list2env(
  lapply(setNames(temp, make.names(gsub("*.csv$", "", temp))), 
         read.csv), envir = .GlobalEnv)

Ale znowu często lepiej pozostawić je na jednej liście.

A5C1D2H2I1M1N2O1R2T1
źródło
Dzięki! działa to bardzo dobrze ... jak mam nazwać każdy właśnie zaimportowany plik, aby móc go łatwo wywołać?
Jojo Ono,
jeśli możesz nam pokazać kilka pierwszych wierszy niektórych twoich plików, możemy mieć jakieś sugestie - edytuj w tym celu swoje pytanie!
Spacedman,
2
Powyższy kod działa idealnie do importowania ich jako pojedynczych obiektów, ale kiedy próbuję wywołać kolumnę z zestawu danych, nie rozpoznaje go, ponieważ jest to tylko pojedynczy obiekt, a nie ramka danych, tj. Moja wersja powyższego kodu to: setwd ( 'C: / Users / new / Desktop / Dives / 0904_003') temp <-list.files (pattern = "*. Csv") ddives <- lapply (temp, read.csv) Teraz każdy plik nazywa się ddives [n ] ale jak mam napisać pętlę, aby wszystkie ramki danych były pojedynczymi obiektami? Mogę to osiągnąć indywidualnie za pomocą operatora data.frame, ale nie jestem pewien, jak to zapętlić. @mrdwab
Jojo Ono,
@JosephOnoufriou, zobacz moją aktualizację. Ale ogólnie uważam, że praca z listami jest łatwiejsza, jeśli zamierzam wykonywać podobne obliczenia dla wszystkich ramek danych.
A5C1D2H2I1M1N2O1R2T1 11.11. O
2
Dla każdego, kto próbuje napisać funkcję wykonującą zaktualizowaną wersję tej odpowiedzi, używając assign... Jeśli chcesz, aby przypisane wartości znajdowały się w środowisku globalnym, upewnij się, że ustawiłeś inherits=T.
dnlbrky
127

tidyverseSzybkie i zwięzłe rozwiązanie: (ponad dwa razy szybsze niż Base R read.csv )

tbl <-
    list.files(pattern = "*.csv") %>% 
    map_df(~read_csv(.))

i data.table „s fread()można nawet wyciąć te czasy ładowania o połowę ponownie. (dla 1/4 podstawy czasu R )

library(data.table)

tbl_fread <- 
    list.files(pattern = "*.csv") %>% 
    map_df(~fread(.))

stringsAsFactors = FALSEArgumentem utrzymuje wolny współczynnik dataframe (i jak zaznacza MARBEL jest domyślne ustawienie, fread)

Jeśli rzutowanie jest bezczelne, możesz wymusić, aby wszystkie kolumny były znakami z col_typesargumentem.

tbl <-
    list.files(pattern = "*.csv") %>% 
    map_df(~read_csv(., col_types = cols(.default = "c")))

Jeśli chcesz zanurzyć się w podkatalogach, aby skonstruować listę plików do ostatecznego powiązania, pamiętaj, aby podać nazwę ścieżki, a także zarejestrować pliki z ich pełnymi nazwami na liście. Umożliwi to kontynuowanie pracy wiązania poza bieżącym katalogiem. (Myślenie o pełnych nazwach ścieżek jako paszportach umożliwiających powrót z powrotem do „granic” katalogu).

tbl <-
    list.files(path = "./subdirectory/",
               pattern = "*.csv", 
               full.names = T) %>% 
    map_df(~read_csv(., col_types = cols(.default = "c"))) 

Jak Hadley opisuje tutaj (mniej więcej w połowie):

map_df(x, f)jest faktycznie taki sam jak do.call("rbind", lapply(x, f))....

Funkcja bonusowa - dodawanie nazw plików do rekordów zgodnie z żądaniem funkcji Niksa w komentarzach poniżej:
* Dodaj oryginał filenamedo każdego rekordu.

Kod wyjaśniony: utwórz funkcję dodawania nazwy pliku do każdego rekordu podczas początkowego odczytu tabel. Następnie użyj tej funkcji zamiast prostej read_csv()funkcji.

read_plus <- function(flnm) {
    read_csv(flnm) %>% 
        mutate(filename = flnm)
}

tbl_with_sources <-
    list.files(pattern = "*.csv", 
               full.names = T) %>% 
    map_df(~read_plus(.))

(Metody rzutowania i obsługi podkatalogów można również obsługiwać wewnątrz read_plus()funkcji w taki sam sposób, jak pokazano w drugim i trzecim wariancie sugerowanym powyżej.)

### Benchmark Code & Results 
library(tidyverse)
library(data.table)
library(microbenchmark)

### Base R Approaches
#### Instead of a dataframe, this approach creates a list of lists
#### removed from analysis as this alone doubled analysis time reqd
# lapply_read.delim <- function(path, pattern = "*.csv") {
#     temp = list.files(path, pattern, full.names = TRUE)
#     myfiles = lapply(temp, read.delim)
# }

#### `read.csv()`
do.call_rbind_read.csv <- function(path, pattern = "*.csv") {
    files = list.files(path, pattern, full.names = TRUE)
    do.call(rbind, lapply(files, function(x) read.csv(x, stringsAsFactors = FALSE)))
}

map_df_read.csv <- function(path, pattern = "*.csv") {
    list.files(path, pattern, full.names = TRUE) %>% 
    map_df(~read.csv(., stringsAsFactors = FALSE))
}


### *dplyr()*
#### `read_csv()`
lapply_read_csv_bind_rows <- function(path, pattern = "*.csv") {
    files = list.files(path, pattern, full.names = TRUE)
    lapply(files, read_csv) %>% bind_rows()
}

map_df_read_csv <- function(path, pattern = "*.csv") {
    list.files(path, pattern, full.names = TRUE) %>% 
    map_df(~read_csv(., col_types = cols(.default = "c")))
}

### *data.table* / *purrr* hybrid
map_df_fread <- function(path, pattern = "*.csv") {
    list.files(path, pattern, full.names = TRUE) %>% 
    map_df(~fread(.))
}

### *data.table*
rbindlist_fread <- function(path, pattern = "*.csv") {
    files = list.files(path, pattern, full.names = TRUE)
    rbindlist(lapply(files, function(x) fread(x)))
}

do.call_rbind_fread <- function(path, pattern = "*.csv") {
    files = list.files(path, pattern, full.names = TRUE)
    do.call(rbind, lapply(files, function(x) fread(x, stringsAsFactors = FALSE)))
}


read_results <- function(dir_size){
    microbenchmark(
        # lapply_read.delim = lapply_read.delim(dir_size), # too slow to include in benchmarks
        do.call_rbind_read.csv = do.call_rbind_read.csv(dir_size),
        map_df_read.csv = map_df_read.csv(dir_size),
        lapply_read_csv_bind_rows = lapply_read_csv_bind_rows(dir_size),
        map_df_read_csv = map_df_read_csv(dir_size),
        rbindlist_fread = rbindlist_fread(dir_size),
        do.call_rbind_fread = do.call_rbind_fread(dir_size),
        map_df_fread = map_df_fread(dir_size),
        times = 10L) 
}

read_results_lrg_mid_mid <- read_results('./testFolder/500MB_12.5MB_40files')
print(read_results_lrg_mid_mid, digits = 3)

read_results_sml_mic_mny <- read_results('./testFolder/5MB_5KB_1000files/')
read_results_sml_tny_mod <- read_results('./testFolder/5MB_50KB_100files/')
read_results_sml_sml_few <- read_results('./testFolder/5MB_500KB_10files/')

read_results_med_sml_mny <- read_results('./testFolder/50MB_5OKB_1000files')
read_results_med_sml_mod <- read_results('./testFolder/50MB_5OOKB_100files')
read_results_med_med_few <- read_results('./testFolder/50MB_5MB_10files')

read_results_lrg_sml_mny <- read_results('./testFolder/500MB_500KB_1000files')
read_results_lrg_med_mod <- read_results('./testFolder/500MB_5MB_100files')
read_results_lrg_lrg_few <- read_results('./testFolder/500MB_50MB_10files')

read_results_xlg_lrg_mod <- read_results('./testFolder/5000MB_50MB_100files')


print(read_results_sml_mic_mny, digits = 3)
print(read_results_sml_tny_mod, digits = 3)
print(read_results_sml_sml_few, digits = 3)

print(read_results_med_sml_mny, digits = 3)
print(read_results_med_sml_mod, digits = 3)
print(read_results_med_med_few, digits = 3)

print(read_results_lrg_sml_mny, digits = 3)
print(read_results_lrg_med_mod, digits = 3)
print(read_results_lrg_lrg_few, digits = 3)

print(read_results_xlg_lrg_mod, digits = 3)

# display boxplot of my typical use case results & basic machine max load
par(oma = c(0,0,0,0)) # remove overall margins if present
par(mfcol = c(1,1)) # remove grid if present
par(mar = c(12,5,1,1) + 0.1) # to display just a single boxplot with its complete labels
boxplot(read_results_lrg_mid_mid, las = 2, xlab = "", ylab = "Duration (seconds)", main = "40 files @ 12.5MB (500MB)")
boxplot(read_results_xlg_lrg_mod, las = 2, xlab = "", ylab = "Duration (seconds)", main = "100 files @ 50MB (5GB)")

# generate 3x3 grid boxplots
par(oma = c(12,1,1,1)) # margins for the whole 3 x 3 grid plot
par(mfcol = c(3,3)) # create grid (filling down each column)
par(mar = c(1,4,2,1)) # margins for the individual plots in 3 x 3 grid
boxplot(read_results_sml_mic_mny, las = 2, xlab = "", ylab = "Duration (seconds)", main = "1000 files @ 5KB (5MB)", xaxt = 'n')
boxplot(read_results_sml_tny_mod, las = 2, xlab = "", ylab = "Duration (milliseconds)", main = "100 files @ 50KB (5MB)", xaxt = 'n')
boxplot(read_results_sml_sml_few, las = 2, xlab = "", ylab = "Duration (milliseconds)", main = "10 files @ 500KB (5MB)",)

boxplot(read_results_med_sml_mny, las = 2, xlab = "", ylab = "Duration (microseconds)        ", main = "1000 files @ 50KB (50MB)", xaxt = 'n')
boxplot(read_results_med_sml_mod, las = 2, xlab = "", ylab = "Duration (microseconds)", main = "100 files @ 500KB (50MB)", xaxt = 'n')
boxplot(read_results_med_med_few, las = 2, xlab = "", ylab = "Duration (seconds)", main = "10 files @ 5MB (50MB)")

boxplot(read_results_lrg_sml_mny, las = 2, xlab = "", ylab = "Duration (seconds)", main = "1000 files @ 500KB (500MB)", xaxt = 'n')
boxplot(read_results_lrg_med_mod, las = 2, xlab = "", ylab = "Duration (seconds)", main = "100 files @ 5MB (500MB)", xaxt = 'n')
boxplot(read_results_lrg_lrg_few, las = 2, xlab = "", ylab = "Duration (seconds)", main = "10 files @ 50MB (500MB)")

Przypadek użycia pośredniego

Porównanie wykresów upływającego czasu mój typowy przypadek użycia

Większy przypadek użycia

Porównanie wykresu upływu czasu dla bardzo dużego obciążenia

Różne przypadki użycia

Wiersze: liczba plików (1000, 100, 10)
Kolumny: ostateczny rozmiar ramki danych (5 MB, 50 MB, 500 MB)
(kliknij obraz, aby zobaczyć oryginalny rozmiar) Porównanie wykresów wariantów wielkości katalogów

Podstawowe wyniki R są lepsze dla najmniejszych przypadków użycia, w których narzut związany z doprowadzeniem bibliotek C purrr i dplyr do zniesienia przewyższa wzrost wydajności, który obserwuje się podczas wykonywania zadań przetwarzania na większą skalę.

jeśli chcesz uruchomić własne testy, ten skrypt bash może ci się przydać.

for ((i=1; i<=$2; i++)); do 
  cp "$1" "${1:0:8}_${i}.csv";
done

bash what_you_name_this_script.sh "fileName_you_want_copied" 100 utworzy 100 kopii pliku kolejno numerowanych (po pierwszych 8 znakach nazwy pliku i podkreśleniu).

Atrybucje i podziękowania

Specjalne podziękowania dla:

  • Tyler Rinker i Akrun za zademonstrowanie mikrodruku .
  • Jake Kaupp za przedstawienie mnie map_df() tutaj .
  • David McLaughlin za pomocne opinie na temat ulepszania wizualizacji oraz omawiania / potwierdzania odwrócenia wydajności zaobserwowanych w małym pliku, w wynikach analizy małych ramek danych.
  • marbel za wskazanie domyślnego zachowania dla fread(). (Muszę się uczyć data.table.)
leerssej
źródło
1
twoje rozwiązanie działa dla mnie. W tym chcę zapisać nazwę pliku, aby je rozróżnić. Czy to możliwe?
Niks
1
@Niks - Oczywiście! Po prostu napisz i zamień w małej funkcji, która nie tylko odczytuje pliki, ale natychmiast dołącza nazwę pliku do każdego odczytanego rekordu. W ten sposób readAddFilename <- function(flnm) { read_csv(flnm) %>% mutate(filename = flnm) }po prostu upuść to w miejsce map_dfzamiast zwykłego tylko do odczytu, read_csv()które jest teraz. Mogę zaktualizować powyższy wpis, aby pokazać funkcję i sposób dopasowania do potoku, jeśli nadal masz pytania lub uważasz, że to będzie pomocne.
leerssej,
Problem w praktyce polega na tym, że read_csvjest on znacznie wolniejszy niż fread. Dodałbym punkt odniesienia, jeśli chcesz powiedzieć, że coś jest szybsze. Jednym z pomysłów jest utworzenie 30 plików 1 GB i ich odczytanie, w takim przypadku liczy się wydajność.
marbel
@marbel: Dziękuję za sugestię! Na 530 MB i mniejsze katalogów (z maksymalnie 100 plików) Jestem znalezienie poprawę o 25% w wydajności pomiędzy data.table „s fread()i dplyr ” s read_csv(): 14,2 vs 19,9 sek. TBH, porównywałem tylko bazę R do dplyr i ponieważ read_csv()jest to około 2-4x szybsze niż read.csv(), testowanie porównawcze nie wydawało się konieczne. Interesujące było jednak fread()zawirowanie i zatrzymanie się, aby sprawdzić bardziej kompletne wyniki testów porównawczych. Dzięki jeszcze raz!
leerssej
1
Kolejny świetny punkt. Myślę, że kiedy napisałem, byłem zbyt ostrożny w ochronie działań data.table przed zmutowaniem danych w miejscu (co wpływa na wydajność następnego i wszystkich kolejnych przebiegów danych). To oczywiście nie ma sensu w tym przypadku. Dziękuję Ci. :-D Nie mogę się doczekać ponownego uruchomienia liczb wkrótce bez funkcji i przy większych zestawach danych na większej maszynie.
leerssej,
104

Oto niektóre opcje konwersji plików .csv w jedną ramkę danych. Przy użyciu bazy R i niektórych dostępnych pakietów do odczytu plików w języku R.

Jest to wolniejsze niż poniższe opcje.

# Get the files names
files = list.files(pattern="*.csv")
# First apply read.csv, then rbind
myfiles = do.call(rbind, lapply(files, function(x) read.csv(x, stringsAsFactors = FALSE)))

Edycja: - Kilka dodatkowych opcji za pomocą data.tableireadr

fread()Wersja, która jest funkcją data.tablepakietu. Jest to zdecydowanie najszybszy opcji w R .

library(data.table)
DT = do.call(rbind, lapply(files, fread))
# The same using `rbindlist`
DT = rbindlist(lapply(files, fread))

Korzystanie z readr , który jest kolejnym pakietem do odczytu plików csv. Jest wolniejszy niż fread, szybszy niż podstawa R, ale ma różne funkcje.

library(readr)
library(dplyr)
tbl = lapply(files, read_csv) %>% bind_rows()
marmur
źródło
2
jak to działa w porównaniu do Reduce (rbind, lapply (...))? Tylko uczę się R, ale moim zdaniem jest mniej wydajny
aaron
4
Dodałem data.tablewersję, która powinna poprawić wydajność.
marbel
Czy można odczytać tylko określone pliki? ex Pliki zawierające w nazwie słowo „pogoda”?
Opuszczony
1
znalazłem to tutaj: stackoverflow.com/questions/10353540/… dzięki.
Opuszczony
1
+1 wydaje się, że tworzenie pojedynczej ramki danych - SQL UNION wszystkich plików CSV - jest najłatwiejsze do pracy. Ponieważ OP nie określił, czy chcą 1 ramki danych, czy wielu ramek danych, założyłem, że 1 ramka danych jest najlepsza, więc jestem zaskoczony, że zaakceptowana odpowiedź nie robi żadnej z „UNION”. Podoba mi się ta odpowiedź, która jest zgodna z tym wyjaśnieniemdo.call
The Red Pea
24

Oprócz użycia lapplylub innej konstrukcji pętlowej w R możesz połączyć swoje pliki CSV w jeden plik.

W Uniksie, jeśli pliki nie mają nagłówków, jest to tak proste jak:

cat *.csv > all.csv

lub jeśli są nagłówki i możesz znaleźć ciąg pasujący do nagłówków i tylko nagłówki (tj. załóżmy, że wszystkie nagłówki zaczynają się od „Wiek”), zrobiłbyś:

cat *.csv | grep -v ^Age > all.csv

Myślę, że w systemie Windows można to zrobić za pomocą COPYi SEARCH(lub FINDcoś w tym stylu) z okna poleceń DOS, ale dlaczego nie zainstalować cygwini uzyskać mocy powłoki poleceń Uniksa?

Spacedman
źródło
a może nawet użyć Git Bash, który wpada wraz z Gitinstalacją?
leerssej
Z mojego doświadczenia nie jest to najszybsze rozwiązanie, jeśli twoje pliki zaczynają być dość duże.
Amir
20

To jest kod, który opracowałem, aby odczytać wszystkie pliki csv do R. Stworzy ramkę danych dla każdego pliku csv indywidualnie i nada tytułowi ramkę danych oryginalnej nazwy pliku (usuwając spacje i .csv) Mam nadzieję, że okaże się przydatny!

path <- "C:/Users/cfees/My Box Files/Fitness/"
files <- list.files(path=path, pattern="*.csv")
for(file in files)
{
perpos <- which(strsplit(file, "")[[1]]==".")
assign(
gsub(" ","",substr(file, 1, perpos-1)), 
read.csv(paste(path,file,sep="")))
}
Chris Fees TotalThriver.com
źródło
8

Trzy najlepsze odpowiedzi: @ A5C1D2H2I1M1N2O1R2T1, @leerssej i @marbel i wszystkie są zasadniczo takie same: zastosuj fread do każdego pliku, a następnie rbind / rbindlist powstałe tabele danych. Zwykle używam rbindlist(lapply(list.files("*.csv"),fread))formularza.

Jest to lepsze niż inne R-wewnętrzne alternatywy i w porządku dla niewielkiej liczby dużych plików csv, ale nie jest najlepsze dla dużej liczby małych plików csv, gdy liczy się prędkość. W takim przypadku pierwsze użycie może być znacznie szybsze cat, jak sugeruje @Spacedman w odpowiedzi na 4. miejscu. Dodam kilka szczegółów, jak to zrobić z poziomu R:

x = fread(cmd='cat *.csv', header=F)

Co jednak, jeśli każdy plik csv ma ​​nagłówek?

x = fread(cmd="awk 'NR==1||FNR!=1' *.csv", header=T)

A co, jeśli masz tyle plików, że *.csvglob powłoki nie działa?

x = fread(cmd='find . -name "*.csv" | xargs cat', header=F)

A jeśli wszystkie pliki mają nagłówek ORAZ jest ich zbyt wiele?

header = fread(cmd='find . -name "*.csv" | head -n1 | xargs head -n1', header=T)
x = fread(cmd='find . -name "*.csv" | xargs tail -q -n+2', header=F)
names(x) = names(header)

A co, jeśli wynikowy połączony plik csv jest zbyt duży dla pamięci systemowej?

system('find . -name "*.csv" | xargs cat > combined.csv')
x = fread('combined.csv', header=F)

Z nagłówkami?

system('find . -name "*.csv" | head -n1 | xargs head -n1 > combined.csv')
system('find . -name "*.csv" | xargs tail -q -n+2 >> combined.csv')
x = fread('combined.csv', header=T)

Wreszcie, co jeśli nie chcesz wszystkich plików .csv w katalogu, a raczej określonego zestawu plików? (Ponadto wszystkie mają nagłówki.) (To jest mój przypadek użycia.)

fread(text=paste0(system("xargs cat|awk 'NR==1||$1!=\"<column one name>\"'",input=paths,intern=T),collapse="\n"),header=T,sep="\t")

i jest to mniej więcej taka sama prędkość jak zwykły fread xargs cat :)

Uwaga: w przypadku data.table przed wersją 1.1.6 (19 września 2018 r.) Pomiń opcję cmd=od fread(cmd=.

Dodatek: użycie mclapply biblioteki równoległej zamiast seryjnego lapply, np., rbindlist(lapply(list.files("*.csv"),fread))Jest również znacznie szybsze niż rbindlist lapply fread.

Czas odczytać 121401 plików csv w pojedynczej tabeli danych. Każdy plik csv ma ​​3 kolumny, jeden wiersz nagłówka i średnio 4.510 wierszy. Maszyna jest maszyną wirtualną GCP z 96 rdzeniami:

rbindlist lapply fread   234.172s 247.513s 256.349s
rbindlist mclapply fread  15.223s   9.558s   9.292s
fread xargs cat            4.761s   4.259s   5.095s

Podsumowując, jeśli interesuje Cię szybkość i masz wiele plików i wiele rdzeni, fread xargs cat jest około 50 razy szybszy niż najszybsze rozwiązanie w 3 najlepszych odpowiedziach.

webb
źródło
6

Moim zdaniem, większość pozostałych odpowiedzi jest nieaktualna rio::import_list, co jest zwięzłym, jednostronnym tekstem:

library(rio)
my_data <- import_list(dir("path_to_directory", pattern = ".csv", rbind = TRUE))

Wszelkie dodatkowe argumenty są przekazywane do rio::import. rioradzi sobie z prawie dowolnego pliku formatu R może czytać i używa data.table„s freadgdzie jest to możliwe, więc powinno być zbyt szybko.


źródło
5

Użycie plyr::ldplytej .parallelopcji powoduje zwiększenie prędkości o około 50% poprzez włączenie opcji podczas odczytu 400 plików csv około 30-40 MB każdy. Przykład zawiera pasek postępu tekstu.

library(plyr)
library(data.table)
library(doSNOW)

csv.list <- list.files(path="t:/data", pattern=".csv$", full.names=TRUE)

cl <- makeCluster(4)
registerDoSNOW(cl)

pb <- txtProgressBar(max=length(csv.list), style=3)
pbu <- function(i) setTxtProgressBar(pb, i)
dt <- setDT(ldply(csv.list, fread, .parallel=TRUE, .paropts=list(.options.snow=list(progress=pbu))))

stopCluster(cl)
manotheshark
źródło
Niezła odpowiedź! Jak przekazujesz dodatkowe argumenty do freadlub user-defined functions? Dzięki!
Tung
1
@Tung Patrząc na ?ldplypokazuje ...inne argumenty przekazywane do .fun. Korzystanie albo fread, skip = 100czy function(x) fread(x, skip = 100)będzie działać
manotheshark
Użycie function(x) fread(x, skip = 100)nie działało dla mnie, ale załatwienie dodatkowych argumentów po nazwie funkcji nie działało. Dzięki jeszcze raz!
Tung
3

Opierając się na komentarzu dnlbrk, przypisywanie może być znacznie szybsze niż list2env dla dużych plików.

library(readr)
library(stringr)

List_of_file_paths <- list.files(path ="C:/Users/Anon/Documents/Folder_with_csv_files/", pattern = ".csv", all.files = TRUE, full.names = TRUE)

Po ustawieniu argumentu full.names na true, otrzymasz pełną ścieżkę do każdego pliku jako osobny ciąg znaków na liście plików, np. List_of_file_paths [1] będzie wyglądał jak „C: / Users / Anon / Documents / Folder_with_csv_files / file1.csv ”

for(f in 1:length(List_of_filepaths)) {
  file_name <- str_sub(string = List_of_filepaths[f], start = 46, end = -5)
  file_df <- read_csv(List_of_filepaths[f])  
  assign( x = file_name, value = file_df, envir = .GlobalEnv)
}

Możesz użyć fread lub dataR pakietu read.csv pakietu data.table zamiast read_csv. Krok nazwa_pliku pozwala uporządkować nazwę, tak aby każda ramka danych nie pozostawała z pełną ścieżką do pliku, jak sama nazwa. Możesz rozszerzyć pętlę, aby zrobić dalsze rzeczy w tabeli danych przed przeniesieniem jej do środowiska globalnego, na przykład:

for(f in 1:length(List_of_filepaths)) {
  file_name <- str_sub(string = List_of_filepaths[f], start = 46, end = -5)
  file_df <- read_csv(List_of_filepaths[f])  
  file_df <- file_df[,1:3] #if you only need the first three columns
  assign( x = file_name, value = file_df, envir = .GlobalEnv)
}
użytkownik6741397
źródło
3

Oto mój konkretny przykład, aby odczytać wiele plików i połączyć je w 1 ramkę danych:

path<- file.path("C:/folder/subfolder")
files <- list.files(path=path, pattern="/*.csv",full.names = T)
library(data.table)
data = do.call(rbind, lapply(files, function(x) read.csv(x, stringsAsFactors = FALSE)))
vuminh91
źródło
1
Możesz używać rbindlist()oddata.table
jogo
3

Poniższe kody powinny zapewnić najszybszą prędkość dużych zbiorów danych, o ile masz wiele rdzeni na komputerze:

if (!require("pacman")) install.packages("pacman")
pacman::p_load(doParallel, data.table, stringr)

# get the file name
dir() %>% str_subset("\\.csv$") -> fn

# use parallel setting
(cl <- detectCores() %>%
  makeCluster()) %>%
  registerDoParallel()

# read and bind all files together
system.time({
  big_df <- foreach(
    i = fn,
    .packages = "data.table"
  ) %dopar%
    {
      fread(i, colClasses = "character")
    } %>%
    rbindlist(fill = TRUE)
})

# end of parallel work
stopImplicitCluster(cl)

Zaktualizowano w 2020/04/16: Gdy znajduję nowy pakiet dostępny do obliczeń równoległych, dostępne jest alternatywne rozwiązanie przy użyciu następujących kodów.

if (!require("pacman")) install.packages("pacman")
pacman::p_load(future.apply, data.table, stringr)

# get the file name
dir() %>% str_subset("\\.csv$") -> fn

plan(multiprocess)

future_lapply(fn,fread,colClasses = "character") %>% 
  rbindlist(fill = TRUE) -> res

# res is the merged data.table
Nadzieja
źródło
1

I jak podejście z użyciem list.files(), lapply()i list2env()(a fs::dir_ls(), purrr::map()a list2env()). To wydaje się proste i elastyczne.

Alternatywnie możesz wypróbować mały pakiet { tor } ( do-R ): Domyślnie importuje pliki z katalogu roboczego do listy ( list_*()warianty) lub do środowiska globalnego ( load_*()warianty).

Na przykład tutaj czytam wszystkie pliki .csv z mojego katalogu roboczego na listę, używając tor::list_csv():

library(tor)

dir()
#>  [1] "_pkgdown.yml"     "cran-comments.md" "csv1.csv"        
#>  [4] "csv2.csv"         "datasets"         "DESCRIPTION"     
#>  [7] "docs"             "inst"             "LICENSE.md"      
#> [10] "man"              "NAMESPACE"        "NEWS.md"         
#> [13] "R"                "README.md"        "README.Rmd"      
#> [16] "tests"            "tmp.R"            "tor.Rproj"

list_csv()
#> $csv1
#>   x
#> 1 1
#> 2 2
#> 
#> $csv2
#>   y
#> 1 a
#> 2 b

A teraz ładuję te pliki do mojego globalnego środowiska za pomocą tor::load_csv():

# The working directory contains .csv files
dir()
#>  [1] "_pkgdown.yml"     "cran-comments.md" "CRAN-RELEASE"    
#>  [4] "csv1.csv"         "csv2.csv"         "datasets"        
#>  [7] "DESCRIPTION"      "docs"             "inst"            
#> [10] "LICENSE.md"       "man"              "NAMESPACE"       
#> [13] "NEWS.md"          "R"                "README.md"       
#> [16] "README.Rmd"       "tests"            "tmp.R"           
#> [19] "tor.Rproj"

load_csv()

# Each file is now available as a dataframe in the global environment
csv1
#>   x
#> 1 1
#> 2 2
csv2
#>   y
#> 1 a
#> 2 b

Jeśli potrzebujesz odczytać określone pliki, możesz dopasować ich ścieżkę do pliku za pomocą regexp, ignore.casei invert.


Dla jeszcze większej elastyczności użytkowania list_any(). Pozwala podać funkcję czytnika za pomocą argumentu .f.

(path_csv <- tor_example("csv"))
#> [1] "C:/Users/LeporeM/Documents/R/R-3.5.2/library/tor/extdata/csv"
dir(path_csv)
#> [1] "file1.csv" "file2.csv"

list_any(path_csv, read.csv)
#> $file1
#>   x
#> 1 1
#> 2 2
#> 
#> $file2
#>   y
#> 1 a
#> 2 b

Przekaż dodatkowe argumenty przez ... lub wewnątrz funkcji lambda.

path_csv %>% 
  list_any(readr::read_csv, skip = 1)
#> Parsed with column specification:
#> cols(
#>   `1` = col_double()
#> )
#> Parsed with column specification:
#> cols(
#>   a = col_character()
#> )
#> $file1
#> # A tibble: 1 x 1
#>     `1`
#>   <dbl>
#> 1     2
#> 
#> $file2
#> # A tibble: 1 x 1
#>   a    
#>   <chr>
#> 1 b

path_csv %>% 
  list_any(~read.csv(., stringsAsFactors = FALSE)) %>% 
  map(as_tibble)
#> $file1
#> # A tibble: 2 x 1
#>       x
#>   <int>
#> 1     1
#> 2     2
#> 
#> $file2
#> # A tibble: 2 x 1
#>   y    
#>   <chr>
#> 1 a    
#> 2 b
Mauro Lepore
źródło
1

Zażądano dodania tej funkcji do pakietu Stackoverflow R. Biorąc pod uwagę, że jest to pakiet maleverseverse (i nie może zależeć od pakietów stron trzecich), oto co wymyśliłem:

#' Bulk import data files 
#' 
#' Read in each file at a path and then unnest them. Defaults to csv format.
#' 
#' @param path        a character vector of full path names
#' @param pattern     an optional \link[=regex]{regular expression}. Only file names which match the regular expression will be returned.
#' @param reader      a function that can read data from a file name.
#' @param ...         optional arguments to pass to the reader function (eg \code{stringsAsFactors}).
#' @param reducer     a function to unnest the individual data files. Use I to retain the nested structure. 
#' @param recursive     logical. Should the listing recurse into directories?
#'  
#' @author Neal Fultz
#' @references \url{/programming/11433432/how-to-import-multiple-csv-files-at-once}
#' 
#' @importFrom utils read.csv
#' @export
read.directory <- function(path='.', pattern=NULL, reader=read.csv, ..., 
                           reducer=function(dfs) do.call(rbind.data.frame, dfs), recursive=FALSE) {
  files <- list.files(path, pattern, full.names = TRUE, recursive = recursive)

  reducer(lapply(files, reader, ...))
}

Poprzez parametryzację funkcji czytnika i reduktora ludzie mogą korzystać z data.table lub dplyr, jeśli tak chcą, lub po prostu użyć podstawowych funkcji R, które są odpowiednie dla mniejszych zestawów danych.

Neal Fultz
źródło