Eine schnelle und prägnante tidyverse
Lö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 = FALSE
Argument 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_types
Argument 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 filename
jedem 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
Größerer Anwendungsfall
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)
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
.)