web-dev-qa-db-fra.com

Comment importer plusieurs fichiers .csv à la fois?

Supposons que nous ayons un dossier contenant plusieurs fichiers data.csv, chacun contenant le même nombre de variables, mais chacun à des moments différents . Y a-t-il un moyen dans R d'importer tous ces éléments simultanément plutôt que de les importer tous individuellement?

Mon problème est que j'ai environ 2000 fichiers de données à importer et que je dois les importer individuellement en utilisant simplement le code: 

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

n'est pas très efficace.

148
Jojo Ono

Quelque chose comme ce qui suit devrait donner à chaque bloc de données un élément distinct dans une liste unique:

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

Cela suppose que vous avez ces CSV dans un seul répertoire - votre répertoire de travail actuel - et que tous ont l'extension minuscule .csv.

Si vous souhaitez ensuite combiner ces trames de données en un seul, définissez les solutions figurant dans d'autres réponses, à l'aide d'éléments tels que do.call(rbind,...), dplyr::bind_rows() ou data.table::rbindlist().

Si vous voulez vraiment chaque trame de données dans un objet séparé, même si cela est souvent déconseillé, vous pouvez procéder comme suit avec assign:

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

Ou, sans assign, et pour montrer (1) comment nettoyer le nom du fichier et (2) montrer comment utiliser list2env, vous pouvez essayer les solutions suivantes:

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

Mais encore une fois, il vaut souvent mieux les laisser dans une seule liste.

198

Voici une autre option pour convertir les fichiers .csv en un seul fichier data.frame . À l’aide des fonctions de base R. C'est un ordre de grandeur plus lent que les options ci-dessous.

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

Edit: - Quelques options supplémentaires avec data.table et readr

Une version fread(), qui est une fonction du package data.table. Cela devrait être l'option la plus rapide. 

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

Utilisation de readr , qui est un nouveau paquetage hadley pour lire les fichiers csv. Un peu plus lent que fread mais avec des fonctionnalités différentes. 

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

Une solution tidyverse rapide et succincte: (Plus de deux fois plus rapide que base Rread.csv)

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

et data.table ' fread() peut même réduire de nouveau les temps de chargement. (pour 1/4 le Base R fois)

library(data.table)

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

L'argument stringsAsFactors = FALSE permet de conserver le facteur Dataframe libre.

Si la transtypage est en cours, vous pouvez forcer toutes les colonnes à être des caractères avec l'argument col_types.

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

Si vous souhaitez puiser dans les sous-répertoires pour construire votre liste de fichiers à lier, assurez-vous d’inclure le nom du chemin, ainsi que d’enregistrer les fichiers avec leurs noms complets dans votre liste. Cela permettra au travail de liaison de continuer en dehors du répertoire en cours. (En pensant que les noms de chemin d'accès complets fonctionnent comme des passeports pour permettre le retour au-delà des «frontières» du répertoire.)

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

Comme Hadley décrit ici (à peu près à mi-chemin): 

map_df(x, f) est effectivement identique à do.call("rbind", lapply(x, f))....

Bonus Feature - ajout de noms de fichiers aux enregistrements par demande de fonctionnalité Niks dans les commentaires ci-dessous:
* Ajoutez l'original filename à chaque enregistrement. 

Code expliqué: créez une fonction pour ajouter le nom de fichier à chaque enregistrement lors de la lecture initiale des tables. Puis utilisez cette fonction à la place de la simple fonction read_csv().

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

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

(Les approches de conversion de type et de traitement de sous-répertoire peuvent également être gérées dans la fonction read_plus() de la même manière que celle illustrée dans les deuxième et troisième variantes suggérées ci-dessus.)

### 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(., stringsAsFactors = FALSE))
}

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

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

Cas d'utilisation de Middling

 Boxplot Comparison of Elapsed Time my typical use case

Cas d'utilisation plus large

 Boxplot Comparison of Elapsed Time for Extra Large Load

Variété de cas d'utilisation

Lignes: nombre de fichiers (1000, 100, 10)
Colonnes: taille finale de la structure de données (5 Mo, 50 Mo, 500 Mo)
(cliquez sur l'image pour voir la taille originale)}.  Boxplot Comparison of Directory Size Variations

Les résultats de base R sont meilleurs pour les cas d'utilisation les plus petits où la surcharge liée à la mise en œuvre des bibliothèques C de purrr et de dplyr l'emporte sur les gains de performance constatés lors de la réalisation de tâches de traitement à plus grande échelle.

si vous souhaitez exécuter vos propres tests, vous trouverez peut-être utile ce script bash.

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 créera 100 copies de votre fichier numéroté séquentiellement (après les 8 premiers caractères du nom de fichier et un trait de soulignement).

Attributions et Appréciations

Avec un merci spécial à: 

  • Tyler Rinker et Akrun pour la démonstration du micro-critère.
  • Jake Kaupp pour m'avoir présenté map_df()here .
  • David McLaughlin pour ses commentaires utiles sur l'amélioration des visualisations et la discussion/confirmation des inversions de performance observées dans le petit fichier, les résultats d'analyse de la petite trame de données.
71
leerssej

En plus d'utiliser lapply ou une autre construction en boucle en R, vous pouvez fusionner vos fichiers CSV en un fichier.

Sous Unix, si les fichiers n’ont pas d’en-tête, c’est aussi simple que:

cat *.csv > all.csv

ou s'il y a des en-têtes et que vous pouvez trouver une chaîne qui correspond aux en-têtes et uniquement aux en-têtes (c'est-à-dire que les lignes d'en-tête commencent toutes par "Age"), vous feriez:

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

Je pense que sous Windows, vous pouvez le faire avec COPY et SEARCH (ou FIND ou quelque chose du genre) à partir de la boîte de commande DOS, mais pourquoi ne pas installer cygwin et bénéficier de la puissance du shell de commande Unix?

22
Spacedman

C’est le code que j’ai développé pour lire tous les fichiers csv en R. Il va créer un cadre de données pour chaque fichier csv individuellement et le titre qui définit le nom original du fichier (en supprimant les espaces et le .csv). J'espère que vous le trouverez utile!

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

En utilisant plyr::ldply, il y a une augmentation de vitesse d'environ 50% en activant l'option .parallel lors de la lecture de 400 fichiers CSV d'environ 30 à 40 Mo chacun. L'exemple inclut une barre de progression du texte.

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

À mon avis, la plupart des autres réponses sont obsolètes avec rio::import_list, qui est une ligne succincte:

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

Tous les arguments supplémentaires sont passés à rio::import. rio peut traiter presque tous les formats de fichier que R peut lire, et il utilise le fread de data.table dans la mesure du possible.

3
user3603486

S'appuyant sur le commentaire de dnlbrk, attribuer peut être considérablement plus rapide que list2env pour les gros fichiers.

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)

En définissant l'argument full.names sur true, vous obtenez le chemin complet de chaque fichier sous la forme d'une chaîne de caractères distincte dans votre liste de fichiers. Par exemple, List_of_file_paths [1] ressemblera à "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)
}

Vous pouvez utiliser le fread ou la base R read.csv du paquet data.table au lieu de read_csv. L'étape nom_fichier vous permet de nettoyer le nom afin que chaque trame de données ne reste pas avec le chemin d'accès complet au fichier tel qu'il est nommé. Vous pouvez étendre votre boucle pour faire d’autres tâches dans la table de données avant de la transférer dans l’environnement global, par exemple:

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

J'aime l'approche qui utilise les fonctions list.files(), lapply() et list2env() (ou fs::dir_ls(), purrr::map() et list2env()). Cela semble simple et flexible.

Vous pouvez également essayer le petit paquet { tor } (to-R): par défaut, il importe les fichiers du répertoire de travail dans une liste (variantes list_*()) ou dans l'environnement global (variantes load_*()). .

Par exemple, ici, je lis tous les fichiers .csv de mon répertoire de travail dans une liste avec 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

Et maintenant, je charge ces fichiers dans mon environnement global avec 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

Si vous avez besoin de lire des fichiers spécifiques, vous pouvez associer leur chemin de fichier à regexp, ignore.case et invert.


Pour encore plus de flexibilité, utilisez list_any() . Il vous permet de fournir la fonction de lecteur via l'argument .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

Passer des arguments supplémentaires via ... ou à l'intérieur de la fonction 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
0
Mauro Lepore