Wie importiere ich mehrere CSV-Dateien gleichzeitig?


219

Angenommen, wir haben einen Ordner mit mehreren data.csv-Dateien, die jeweils die gleiche Anzahl von Variablen enthalten, jedoch jeweils zu unterschiedlichen Zeiten. Gibt es in R eine Möglichkeit, sie alle gleichzeitig zu importieren, anstatt sie alle einzeln importieren zu müssen?

Mein Problem ist, dass ich ungefähr 2000 Datendateien importieren muss und sie einzeln importieren muss, indem ich nur den Code verwende:

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

ist nicht sehr effizient.

Antworten:


259

So etwas wie das Folgende sollte dazu führen, dass jeder Datenrahmen als separates Element in einer einzelnen Liste angezeigt wird:

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

Dies setzt voraus, dass Sie diese CSVs in einem einzigen Verzeichnis haben - Ihrem aktuellen Arbeitsverzeichnis - und dass alle die Erweiterung in Kleinbuchstaben haben .csv.

Wenn Sie dann die Datenrahmen in einem einzigen Datenrahmen kombinieren möchten, finden Sie die Lösungen in anderen Antworten mit Dingen wie do.call(rbind,...), dplyr::bind_rows()oder data.table::rbindlist().

Wenn Sie wirklich jeden Datenrahmen in einem separaten Objekt haben möchten, obwohl dies oft nicht ratsam ist, können Sie Folgendes tun assign:

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

Oder ohne assignund um zu demonstrieren, (1) wie der Dateiname bereinigt werden kann und (2) wie er verwendet wird list2env, können Sie Folgendes versuchen:

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

Aber auch hier ist es oft besser, sie in einer einzigen Liste zu belassen.


Vielen Dank! das funktioniert sehr gut ... wie würde ich vorgehen, um jede Datei zu benennen, die ich gerade importiert habe, damit ich sie einfach aufrufen kann?
Jojo Ono

Wenn Sie uns die ersten Zeilen einiger Ihrer Dateien zeigen können, haben wir möglicherweise einige Vorschläge - bearbeiten Sie Ihre Frage dafür!
Spacedman

2
Der obige Code funktioniert perfekt zum Importieren als einzelne Objekte, aber wenn ich versuche, eine Spalte aus dem Datensatz aufzurufen, erkennt er sie nicht, da es sich nur um ein einzelnes Objekt handelt, nicht um einen Datenrahmen, dh meine Version des obigen Codes lautet: setwd ( 'C: / Users / new / Desktop / Dives / 0904_003') temp <-list.files (pattern = "*. Csv") ddives <- lapply (temp, read.csv) Jetzt heißt jede Datei ddives [n ] aber wie würde ich vorgehen, um eine Schleife zu schreiben, um alle Datenrahmen und nicht einzelne Objekte zu erstellen? Ich kann dies individuell mit dem Operator data.frame erreichen, bin mir aber nicht sicher, wie ich dies wiederholen soll. @mrdwab
Jojo Ono

@ JosephOnoufriou, siehe mein Update. Im Allgemeinen fällt es mir jedoch leichter, mit Listen zu arbeiten, wenn ich ähnliche Berechnungen für alle Datenrahmen durchführen möchte.
A5C1D2H2I1M1N2O1R2T1

2
Wenn Sie versuchen, eine Funktion zu schreiben, um die aktualisierte Version dieser Antwort mit assign... auszuführen. Wenn die zugewiesenen Werte in der globalen Umgebung gespeichert werden sollen, stellen Sie sicher, dass Sie diese festlegen inherits=T.
dnlbrky

127

Eine schnelle und prägnante tidyverseLösung: (mehr als doppelt so schnell wie die von Base R read.csv )

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

und data.table 's fread()können diese Ladezeiten sogar noch einmal halbieren . (für 1/4 der Basis-R- Zeiten)

library(data.table)

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

Das stringsAsFactors = FALSEArgument hält den Datenrahmenfaktor frei (und ist, wie Marbel betont, die Standardeinstellung für fread)

Wenn die Typumwandlung frech ist, können Sie alle Spalten als Zeichen mit dem col_typesArgument erzwingen .

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

Wenn Sie in Unterverzeichnisse eintauchen möchten, um eine Liste der Dateien zu erstellen, die eventuell gebunden werden sollen, müssen Sie den Pfadnamen angeben und die Dateien mit ihren vollständigen Namen in Ihrer Liste registrieren. Dadurch kann die Bindungsarbeit außerhalb des aktuellen Verzeichnisses fortgesetzt werden. (Stellen Sie sich die vollständigen Pfadnamen als Pässe vor, um eine Bewegung über die Verzeichnisgrenzen hinweg zu ermöglichen.)

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

Wie Hadley hier beschreibt (ungefähr auf halber Höhe):

map_df(x, f)ist effektiv das gleiche wie do.call("rbind", lapply(x, f))....

Bonus-Funktion - Hinzufügen von Dateinamen zu den Datensätzen gemäß Niks-Funktionsanforderung in den folgenden Kommentaren:
* Fügen Sie filenamejedem Datensatz das Original hinzu .

Code erklärt: Erstellen Sie eine Funktion, um den Dateinamen beim ersten Lesen der Tabellen an jeden Datensatz anzuhängen. Verwenden Sie dann diese Funktion anstelle der einfachen read_csv()Funktion.

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

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

(Die Ansätze zur Typumwandlung und Handhabung von Unterverzeichnissen können auch innerhalb der read_plus()Funktion auf die gleiche Weise behandelt werden, wie in der oben vorgeschlagenen zweiten und dritten Variante dargestellt.)

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

Mittlerer Anwendungsfall

Boxplot-Vergleich der verstrichenen Zeit mein typischer Anwendungsfall

Größerer Anwendungsfall

Boxplot-Vergleich der verstrichenen Zeit für extra große Lasten

Vielzahl von Anwendungsfällen

Zeilen:
Anzahl der Dateien (1000, 100, 10) Spalten: endgültige Datenrahmengröße (5 MB, 50 MB, 500 MB)
(Klicken Sie auf das Bild, um die Originalgröße anzuzeigen) Boxplot-Vergleich von Variationen der Verzeichnisgröße

Die Basis-R-Ergebnisse sind besser für die kleinsten Anwendungsfälle, in denen der Aufwand, die C-Bibliotheken von purrr und dplyr zum Tragen zu bringen, die Leistungsgewinne überwiegt, die bei der Ausführung von Verarbeitungsaufgaben in größerem Maßstab beobachtet werden.

Wenn Sie Ihre eigenen Tests ausführen möchten, ist dieses Bash-Skript möglicherweise hilfreich.

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 erstellt 100 Kopien Ihrer Datei fortlaufend nummeriert (nach den ersten 8 Zeichen des Dateinamens und einem Unterstrich).

Zuschreibungen und Wertschätzungen

Mit besonderem Dank an:

  • Tyler Rinker und Akrun für die Demonstration von Mikrobenchmark.
  • Jake Kaupp, der mich map_df() hier vorgestellt hat .
  • David McLaughlin für hilfreiches Feedback zur Verbesserung der Visualisierungen und zur Diskussion / Bestätigung der Leistungsinversionen, die in den Ergebnissen der Analyse kleiner Dateien und kleiner Datenrahmen beobachtet wurden.
  • Marbel für den Hinweis auf das Standardverhalten für fread(). (Ich muss lernen data.table.)

1
Ihre Lösung funktioniert für mich. In diesem möchte ich diesen Dateinamen speichern, um sie zu unterscheiden. Ist es möglich?
Niks

1
@Niks - Auf jeden Fall! Schreiben und tauschen Sie einfach eine kleine Funktion aus, die nicht nur die Dateien liest, sondern sofort einen Dateinamen an jeden gelesenen Datensatz anfügt. Wie so readAddFilename <- function(flnm) { read_csv(flnm) %>% mutate(filename = flnm) }Dann lassen Sie map_dfdas einfach in das statt in das einfache Nur-Lesen fallen read_csv(), das jetzt da ist. Ich kann den obigen Eintrag aktualisieren, um die Funktion zu zeigen und wie sie in das Rohr passt, wenn Sie noch Fragen haben oder dies für hilfreich halten.
Leerssej

Das Problem in der Praxis ist, dass read_csves viel langsamer ist als fread. Ich würde einen Benchmark hinzufügen, wenn Sie sagen wollen, dass etwas schneller ist. Eine Idee ist, 30 1-GB-Dateien zu erstellen und diese zu lesen. Dies ist ein Fall, in dem die Leistung wichtig ist.
Marbel

@marbel: Danke für den Vorschlag! Auf 530 MB und kleinere Verzeichnissen (mit bis zu 100 Dateien) Ich finde eine 25% ige Verbesserung in der Leistung zwischen data.table ‚s fread()und dplyr ‘ s read_csv(): 14,2 vs 19,9 Sekunden. TBH, ich hatte nur Base R mit dplyr verglichen und da read_csv()es ungefähr 2-4x schneller als das ist read.csv(), schien Benchmarking nicht notwendig zu sein. Es war jedoch interessant, fread()einen Wirbel zu machen und eine Pause einzulegen, um vollständigere Benchmark-Ergebnisse zu sehen. Danke noch einmal!
Leerssej

1
Ein weiterer großartiger Punkt. Ich denke, als ich schrieb, dass ich etwas zu vorsichtig war, um data.table-Aktivitäten vor Mutation der vorhandenen Daten zu schützen (was sich auf die Leistung für den nächsten und alle nachfolgenden Läufe über die Daten auswirkt). Das macht in diesem Fall natürlich keinen Sinn. Danke dir. :-D Ich freue mich darauf, die Zahlen bald wieder ohne die Funktionen und mit größeren Datensätzen mit einer größeren Maschine auszuführen.
Leerssej

104

Hier sind einige Optionen zum Konvertieren der CSV-Dateien in einen data.frame mithilfe von R base und einige der verfügbaren Pakete zum Lesen von Dateien in R.

Dies ist langsamer als die folgenden Optionen.

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

Bearbeiten: - Noch ein paar zusätzliche Auswahlmöglichkeiten mit data.tableundreadr

Eine fread()Version, die eine Funktion des data.tablePakets ist. Dies ist bei weitem die schnellste Option in R .

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

Verwenden von readr , einem weiteren Paket zum Lesen von CSV-Dateien. Es ist langsamer als fread, schneller als Basis R, hat aber unterschiedliche Funktionen.

library(readr)
library(dplyr)
tbl = lapply(files, read_csv) %>% bind_rows()

2
Wie funktioniert dies im Vergleich zu Reduzieren (rbind, lapply (...))? Ich lerne gerade R, aber meine Vermutung ist weniger performant
Aaron

4
Ich habe eine data.tableVersion hinzugefügt , die die Leistung verbessern soll.
Marbel

Ist es möglich, nur bestimmte Dateien zu lesen? ex Dateien, deren Name 'Wetter' enthält?
Verfallen


1
+1 scheint die Erstellung eines einzelnen Datenrahmens - der SQL UNION aller CSV-Dateien - am einfachsten zu handhaben. Da OP nicht angegeben hat, ob 1 Datenrahmen oder viele Datenrahmen gewünscht werden, habe ich angenommen, dass 1 Datenrahmen am besten ist. Daher bin ich überrascht, dass die akzeptierte Antwort keine der "UNION" ausführt. Ich mag diese Antwort, die mit dieser Erklärung vondo.call
The Red Pea

24

Neben der Verwendung lapplyoder eine andere Schleifenkonstrukt in R könnten Sie Ihre CSV - Dateien in einer einzigen Datei zusammenführen.

Wenn die Dateien unter Unix keine Header hatten, ist dies so einfach wie:

cat *.csv > all.csv

oder wenn es Überschriften gibt und Sie eine Zeichenfolge finden, die mit Überschriften und nur mit Überschriften übereinstimmt (dh, alle Überschriften beginnen alle mit "Alter"), würden Sie Folgendes tun:

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

Ich denke, in Windows könnten Sie dies mit COPYund SEARCH(oder FINDetwas anderem) über das DOS-Befehlsfeld tun , aber warum nicht cygwindie Unix-Befehlsshell installieren und nutzen?


oder sogar mit dem Git Bash gehen, der mit der GitInstallation zusammenfällt?
Leerssej

Nach meiner Erfahrung ist dies nicht die schnellste Lösung, wenn Ihre Dateien ziemlich groß werden.
Amir

20

Dies ist der Code, den ich entwickelt habe, um alle CSV-Dateien in R zu lesen. Er erstellt einen Datenrahmen für jede CSV-Datei einzeln und betitelt den ursprünglichen Namen der Datei (Entfernen von Leerzeichen und CSV). Ich hoffe, Sie finden ihn nützlich!

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

8

Die drei wichtigsten Antworten von @ A5C1D2H2I1M1N2O1R2T1, @leerssej und @marbel sind alle im Wesentlichen gleich: Wenden Sie fread auf jede Datei an und binden Sie dann die resultierenden data.tables rbind / rbindlist. Normalerweise benutze ich das rbindlist(lapply(list.files("*.csv"),fread))Formular.

Dies ist besser als andere R-interne Alternativen und für eine kleine Anzahl großer CSVs in Ordnung, aber nicht die beste für eine große Anzahl kleiner CSVs, wenn es auf Geschwindigkeit ankommt. In diesem Fall kann die erste Verwendung viel schneller sein cat, wie @Spacedman in der Antwort auf Rang 4 vorschlägt. Ich werde einige Details dazu in R hinzufügen:

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

Was ist jedoch, wenn jede CSV einen Header hat?

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

Und was ist, wenn Sie so viele Dateien haben, dass der *.csvShell-Glob ausfällt?

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

Und was ist, wenn alle Dateien einen Header haben UND zu viele Dateien vorhanden sind?

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)

Und was ist, wenn die resultierende verkettete CSV zu groß für den Systemspeicher ist?

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

Mit Überschriften?

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)

Was ist, wenn Sie nicht alle CSV-Dateien in einem Verzeichnis haben möchten, sondern nur einen bestimmten Satz von Dateien? (Außerdem haben alle Header.) (Dies ist mein Anwendungsfall.)

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

und das ist ungefähr die gleiche Geschwindigkeit wie bei einfachen Fread Xargs Cat :)

Hinweis: Lassen Sie für die Datentabelle vor Version 1.11.6 (19. September 2018) das cmd=von weg fread(cmd=.

Nachtrag: Die Verwendung von mclapply der parallelen Bibliothek anstelle von serial lapply, z. B., rbindlist(lapply(list.files("*.csv"),fread))ist auch viel schneller als rbindlist lapply fread.

Zeit zum Lesen von 121401 CSVs in eine einzelne Datentabelle. Jede CSV hat 3 Spalten, eine Kopfzeile und durchschnittlich 4,510 Zeilen. Maschine ist eine GCP-VM mit 96 Kernen:

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

Zusammenfassend lässt sich sagen, dass fread xargs cat etwa 50-mal schneller ist als die schnellste Lösung in den Top-3-Antworten, wenn Sie an Geschwindigkeit interessiert sind und viele Dateien und viele Kerne haben.


6

Meiner Ansicht nach sind die meisten anderen Antworten veraltet durch rio::import_list, was ein prägnanter Einzeiler ist:

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

Alle zusätzlichen Argumente werden an übergeben rio::import. riokann umgehen kann mit fast jedem Dateiformat R gelesen, und es verwendet data.table‚s freadmöglich , wo, so dass es schnell sein.


5

Bei Verwendung von plyr::ldplywird die Geschwindigkeit um ca. 50% erhöht, indem die .parallelOption aktiviert wird, während 400 CSV-Dateien mit jeweils ca. 30-40 MB gelesen werden. Das Beispiel enthält eine Textfortschrittsleiste.

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)

Gute Antwort! Wie übergeben Sie zusätzliche Argumente an freadoder user-defined functions? Vielen Dank!
Tung

1
@Tung Beim Betrachten ?ldplywerden ...andere Argumente angezeigt, die an weitergegeben wurden .fun. Verwenden Sie entweder fread, skip = 100oder function(x) fread(x, skip = 100)würde funktionieren
Manotheshark

Die Verwendung function(x) fread(x, skip = 100)hat bei mir nicht funktioniert, aber die Bereitstellung zusätzlicher Argumente nach dem Namen der nackten Funktion hat den Trick getan. Danke noch einmal!
Tung

3

Aufbauend auf dem Kommentar von dnlbrk kann die Zuweisung für große Dateien erheblich schneller sein als list2env.

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)

Wenn Sie das Argument full.names auf true setzen, erhalten Sie den vollständigen Pfad zu jeder Datei als separate Zeichenfolge in Ihrer Dateiliste. Beispiel: List_of_file_paths [1] lautet beispielsweise "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)
}

Sie können anstelle von read_csv den Fread oder die Basis R read.csv des data.table-Pakets verwenden. Mit dem Schritt Dateiname können Sie den Namen aufräumen, damit nicht jeder Datenrahmen mit dem vollständigen Pfad zur Datei als Name verbleibt. Sie können Ihre Schleife erweitern, um weitere Änderungen an der Datentabelle vorzunehmen, bevor Sie sie in die globale Umgebung übertragen. Beispiel:

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

3

Dies ist mein spezielles Beispiel, um mehrere Dateien zu lesen und zu einem Datenrahmen zu kombinieren:

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

1
Sie können rbindlist()vondata.table
jogo

3

Die folgenden Codes sollten Ihnen die schnellste Geschwindigkeit für Big Data bieten, solange Sie viele Kerne auf Ihrem Computer haben:

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)

Aktualisiert am 16.04.2020: Da ich ein neues Paket für die parallele Berechnung finde, wird eine alternative Lösung mit den folgenden Codes bereitgestellt.

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

1

Ich mag den Ansatz list.files(), lapply()und list2env()(oder fs::dir_ls(), purrr::map()und list2env()). Das scheint einfach und flexibel.

Alternativ können Sie das kleine Paket { tor } ( to-R ) ausprobieren : Standardmäßig werden Dateien aus dem Arbeitsverzeichnis in eine Liste ( list_*()Varianten) oder in die globale Umgebung ( load_*()Varianten) importiert .

Zum Beispiel lese ich hier alle CSV-Dateien aus meinem Arbeitsverzeichnis in eine Liste mit 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

Und jetzt lade ich diese Dateien in meine globale Umgebung mit 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

Sollten Sie müssen bestimmte Dateien lesen, können Sie ihre Datei-Pfad mit übereinstimmen regexp, ignore.caseund invert.


Für noch mehr Flexibilität verwenden list_any(). Sie können die Reader-Funktion über das Argument bereitstellen .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

Übergeben Sie zusätzliche Argumente über ... oder innerhalb der Lambda-Funktion.

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

1

Es wurde angefordert, dass ich diese Funktionalität zum Paket stackoverflow R hinzufüge. Da es sich um ein tinyverse Paket handelt (und nicht von Paketen von Drittanbietern abhängen kann), habe ich mir Folgendes ausgedacht:

#' 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, ...))
}

Durch die Parametrisierung der Lese- und Reduzierungsfunktion können Benutzer data.table oder dplyr verwenden, wenn sie dies wünschen, oder einfach die Basis-R-Funktionen verwenden, die für kleinere Datensätze in Ordnung sind.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.