Leer todos los archivos en una carpeta y aplicar una función a cada marco de datos

90

Estoy haciendo un análisis relativamente simple que he puesto en una función, en todos los archivos de una carpeta en particular. Me preguntaba si alguien tenía algún consejo que me ayude a automatizar el proceso en varias carpetas diferentes.

  1. En primer lugar, me preguntaba si había alguna forma de leer todos los archivos de una carpeta en particular directamente en R. Creo que el siguiente comando enumerará todos los archivos:

files <- (Sys.glob("*.csv"))

... que encontré en Usando R para enumerar todos los archivos con una extensión específica

Y luego el siguiente código lee todos esos archivos en R.

listOfFiles <- lapply(files, function(x) read.table(x, header = FALSE)) 

... de Manipular varios archivos en R

Pero los archivos parecen leerse como una lista continua y no como archivos individuales ... ¿cómo puedo cambiar el script para abrir todos los archivos csv en una carpeta en particular como marcos de datos individuales?

  1. En segundo lugar, suponiendo que pueda leer todos los archivos por separado, ¿cómo puedo completar una función en todos estos marcos de datos de una sola vez? Por ejemplo, he creado cuatro pequeños marcos de datos para poder ilustrar lo que quiero:

    Df.1 <- data.frame(A = c(5,4,7,6,8,4),B = (c(1,5,2,4,9,1)))
    Df.2 <- data.frame(A = c(1:6),B = (c(2,3,4,5,1,1)))
    Df.3 <- data.frame(A = c(4,6,8,0,1,11),B = (c(7,6,5,9,1,15)))
    Df.4 <- data.frame(A = c(4,2,6,8,1,0),B = (c(3,1,9,11,2,16)))
    

También he creado una función de ejemplo:

Summary<-function(dfile){
SumA<-sum(dfile$A)
MinA<-min(dfile$A)
MeanA<-mean(dfile$A)
MedianA<-median(dfile$A)
MaxA<-max(dfile$A)

sumB<-sum(dfile$B)
MinB<-min(dfile$B)
MeanB<-mean(dfile$B)
MedianB<-median(dfile$B)
MaxB<-max(dfile$B)

Sum<-c(sumA,sumB)
Min<-c(MinA,MinB)
Mean<-c(MeanA,MeanB)
Median<-c(MedianA,MedianB)
Max<-c(MaxA,MaxB)
rm(sumA,sumB,MinA,MinB,MeanA,MeanB,MedianA,MedianB,MaxA,MaxB)

Label<-c("A","B")
dfile_summary<-data.frame(Label,Sum,Min,Mean,Median,Max)
return(dfile_summary)}

Normalmente, usaría el siguiente comando para aplicar la función a cada marco de datos individual.

Df1.summary <-Summary (archivo df)

¿Hay alguna manera en lugar de aplicar la función a todos los marcos de datos y usar los títulos de los marcos de datos en las tablas de resumen (es decir, Df1.summary)?

Muchas gracias,

Katie

KT_1
fuente

Respuestas:

104

Por el contrario, creo que trabajar con listfacilita la automatización de este tipo de cosas.

Aquí hay una solución (almacené sus cuatro marcos de datos en la carpeta temp/).

filenames <- list.files("temp", pattern="*.csv", full.names=TRUE)
ldf <- lapply(filenames, read.csv)
res <- lapply(ldf, summary)
names(res) <- substr(filenames, 6, 30)

Es importante almacenar la ruta completa de sus archivos (como hice con full.names), de lo contrario, debe pegar el directorio de trabajo, por ejemplo

filenames <- list.files("temp", pattern="*.csv")
paste("temp", filenames, sep="/")

también funcionará. Tenga en cuenta que solía substrextraer nombres de archivos mientras descartaba la ruta completa.

Puede acceder a sus tablas de resumen de la siguiente manera:

> res$`df4.csv`
       A              B        
 Min.   :0.00   Min.   : 1.00  
 1st Qu.:1.25   1st Qu.: 2.25  
 Median :3.00   Median : 6.00  
 Mean   :3.50   Mean   : 7.00  
 3rd Qu.:5.50   3rd Qu.:10.50  
 Max.   :8.00   Max.   :16.00  

Si realmente desea obtener tablas de resumen individuales, puede extraerlas posteriormente. P.ej,

for (i in 1:length(res))
  assign(paste(paste("df", i, sep=""), "summary", sep="."), res[[i]])
chl
fuente
3
+1 Me gustaría plyr::llply(o ldply) en lugar de lapplyconservar los nombres en todo momento, y definir mi propia función de resumen, por ejemploplyr::each(min, max, mean, sd, median)
baptiste
+1 @chl: gracias por el truco de nombres completos en la función list.files ... ¡¡¡lo olvidé en mi respuesta !!!
Dickoa
@baptiste (+1) Gracias por la plyrsugerencia.
chl
Gracias @chl. ¿Cómo utilizo el código anterior con una función que he escrito? La función de ejemplo que usé arriba ("Resumen") con suma, media, mediana, etc. se usó como un ejemplo que creé rápidamente; la función real que estoy usando para mi análisis real es mucho más compleja. ¿Alguna idea de cómo incorporo una función más compleja en el código anterior para dar las mismas tablas de resumen individuales? -
KT_1
@Katie Supongo que puede reemplazar summarycon cualquier función suya, siempre que tome un data.frame como argumento (y / o parámetros opcionales que sean constantes en los DF de diferencia). Por ejemplo, lapply(ldf, function(x) apply(x, 2, function(x) c(mean(x), sd(x))))devolvería la media y la DE calculadas en sentido contrario.
chl
16

generalmente no uso el bucle for en R, pero aquí está mi solución usando bucles for y dos paquetes: plyr y dostats

plyr está en cran y puede descargar dostats en https://github.com/halpo/dostats (puede estar usando install_github del paquete Hadley devtools )

Suponiendo que tengo sus dos primeros data.frame (Df.1 y Df.2) en archivos csv, puede hacer algo como esto.

require(plyr)
require(dostats)

files <- list.files(pattern = ".csv")


for (i in seq_along(files)) {

    assign(paste("Df", i, sep = "."), read.csv(files[i]))

    assign(paste(paste("Df", i, sep = ""), "summary", sep = "."), 
           ldply(get(paste("Df", i, sep = ".")), dostats, sum, min, mean, median, max))

}

Aquí está la salida

R> Df1.summary
  .id sum min   mean median max
1   A  34   4 5.6667    5.5   8
2   B  22   1 3.6667    3.0   9
R> Df2.summary
  .id sum min   mean median max
1   A  21   1 3.5000    3.5   6
2   B  16   1 2.6667    2.5   5
Dickoa
fuente
(+1) ¡Parece que respondimos al mismo tiempo y tu plyrsolución es bastante buena!
chl
1
Gracias @dickoa por tus respuestas. La función que inventé ("Resumen") estaba mal descrita. Solo lo estaba usando con fines ilustrativos: mi función real es mucho más complicada, así que me preguntaba cómo se podría cambiar el código anterior (y probablemente mi función) para que se aplique a todos los diferentes marcos de datos (y no solo utilice las funciones integradas en R).
KT_1
2

Aquí hay una tidyverseopción que puede no ser la más elegante, pero ofrece cierta flexibilidad en términos de lo que se incluye en el resumen:

library(tidyverse)
dir_path <- '~/path/to/data/directory/'
file_pattern <- 'Df\\.[0-9]\\.csv' # regex pattern to match the file name format

read_dir <- function(dir_path, file_name){
  read_csv(paste0(dir_path, file_name)) %>% 
    mutate(file_name = file_name) %>%                # add the file name as a column              
    gather(variable, value, A:B) %>%                 # convert the data from wide to long
    group_by(file_name, variable) %>% 
    summarize(sum = sum(value, na.rm = TRUE),
              min = min(value, na.rm = TRUE),
              mean = mean(value, na.rm = TRUE),
              median = median(value, na.rm = TRUE),
              max = max(value, na.rm = TRUE))
  }

df_summary <- 
  list.files(dir_path, pattern = file_pattern) %>% 
  map_df(~ read_dir(dir_path, .))

df_summary
# A tibble: 8 x 7
# Groups:   file_name [?]
  file_name variable   sum   min  mean median   max
  <chr>     <chr>    <int> <dbl> <dbl>  <dbl> <dbl>
1 Df.1.csv  A           34     4  5.67    5.5     8
2 Df.1.csv  B           22     1  3.67    3       9
3 Df.2.csv  A           21     1  3.5     3.5     6
4 Df.2.csv  B           16     1  2.67    2.5     5
5 Df.3.csv  A           30     0  5       5      11
6 Df.3.csv  B           43     1  7.17    6.5    15
7 Df.4.csv  A           21     0  3.5     3       8
8 Df.4.csv  B           42     1  7       6      16
sbha
fuente
Gran solución ya que es muy flexible. Porque mi formato de datos read_csv()no funcionaba correctamente, así que lo reemplacé con data.table::fread().
Thorsten