web-dev-qa-db-fra.com

Comment puis-je lire uniquement les lignes qui remplissent une condition d'un fichier CSV vers R?

J'essaie de lire un fichier csv volumineux dans R. Même s'il est volumineux, je souhaite uniquement travailler avec certaines des lignes remplissant une condition particulière (par exemple, Variable2> = 3). C'est un ensemble de données beaucoup plus petit. J'aimerais lire ces lignes directement dans un cadre de données plutôt que de charger l'ensemble de données complet dans un cadre de données, puis de les sélectionner en fonction de la condition. La raison principale étant que le jeu de données ne s'insère pas facilement dans la mémoire d'un ordinateur de bureau ou d'un ordinateur portable. Je recherche une solution utilisant uniquement R et ne nécessitant ni python ni d’autres langages. Merci.

18
Hernan

Vous pouvez utiliser la fonction read.csv.sql dans le package sqldf et filtrer à l'aide de SQL select. Depuis la page d'aide de read.csv.sql:

library(sqldf)
write.csv(iris, "iris.csv", quote = FALSE, row.names = FALSE)
iris2 <- read.csv.sql("iris.csv", 
    sql = "select * from file where `Sepal.Length` > 5", eol = "\n")
28
Karsten W.

De loin le plus simple (dans mon livre) est d’utiliser le pré-traitement.

R> DF <- data.frame(n=1:26, l=LETTERS)
R> write.csv(DF, file="/tmp/data.csv", row.names=FALSE)
R> read.csv(pipe("awk 'BEGIN {FS=\",\"} {if ($1 > 20) print $0}' /tmp/data.csv"),
+           header=FALSE)
  V1 V2
1 21  U
2 22  V
3 23  W
4 24  X
5 25  Y
6 26  Z
R> 

Ici, nous utilisons awk. Nous disons à awk d'utiliser une virgule comme séparateur de champ, puis le conditon 'si le premier champ est supérieur à 20' pour décider si nous imprimons (la ligne entière via $0).

La sortie de cette commande peut être lue par R via pipe().

Cela va être plus rapide et plus efficace en termes de mémoire que de tout lire en R.

20
Dirk Eddelbuettel

Vous pouvez lire le fichier par morceaux, traiter chaque morceau, puis assembler uniquement les sous-ensembles. 

Voici un exemple minimal en supposant que le fichier comporte 1001 lignes (y compris l'en-tête) et que 100 seulement puissent tenir dans la mémoire. Les données comportent 3 colonnes et nous prévoyons qu’au plus 150 lignes remplissent la condition (ceci est nécessaire pour préallouer de l’espace pour les données finales:

# initialize empty data.frame (150 x 3)
max.rows <- 150
final.df <- data.frame(Variable1=rep(NA, max.rows=150), 
                       Variable2=NA,  
                       Variable3=NA)

# read the first chunk outside the loop
temp <- read.csv('big_file.csv', nrows=100, stringsAsFactors=FALSE)
temp <- temp[temp$Variable2 >= 3, ]  ## subset to useful columns
final.df[1:nrow(temp), ] <- temp     ## add to the data
last.row = nrow(temp)                ## keep track of row index, incl. header

for (i in 1:9){    ## nine chunks remaining to be read
  temp <- read.csv('big_file.csv', skip=i*100+1, nrow=100, header=FALSE,
                   stringsAsFactors=FALSE)
  temp <- temp[temp$Variable2 >= 3, ]
  final.df[(last.row+1):(last.row+nrow(temp)), ] <- temp
  last.row <- last.row + nrow(temp)    ## increment the current count
}

final.df <- final.df[1:last.row, ]   ## only keep filled rows
rm(temp)    ## remove last chunk to free memory

Edit: Ajout de l'option stringsAsFactors=FALSE sur la suggestion de @ lucacerone dans les commentaires.

8
ilir

J'étais à la recherche de readr::read_csv_chunked lorsque j'ai vu cette question et que je pensais procéder à des analyses comparatives. Pour cet exemple, read_csv_chunked réussit bien et il était avantageux d’augmenter la taille du bloc. sqldf n'était que légèrement plus rapide que awk.

library(tidyverse)
library(sqldf)
library(microbenchmark)

# Generate an example dataset with two numeric columns and 5 million rows
data_frame(
  norm = rnorm(5e6, mean = 5000, sd = 1000),
  unif = runif(5e6, min = 0, max = 10000)
) %>%
write_csv('medium.csv')

microbenchmark(
  readr  = read_csv_chunked('medium.csv', callback = DataFrameCallback$new(function(x, pos) subset(x, unif > 9000)), col_types = 'dd', progress = F),
  readr2 = read_csv_chunked('medium.csv', callback = DataFrameCallback$new(function(x, pos) subset(x, unif > 9000)), col_types = 'dd', progress = F, chunk_size = 1000000),
  sqldf  = read.csv.sql('medium.csv', sql = 'select * from file where unif > 9000', eol = '\n'),
  awk    = read.csv(pipe("awk 'BEGIN {FS=\",\"} {if ($2 > 9000) print $0}' medium.csv")),
  awk2   = read_csv(pipe("awk 'BEGIN {FS=\",\"} {if ($2 > 9000) print $0}' medium.csv"), col_types = 'dd', progress = F),
  check  = function(values) all(sapply(values[-1], function(x) all.equal(values[[1]], x))),
  times  = 10L
)

# Unit: seconds
#   expr       min        lq      mean    median        uq       max neval
#  readr      5.58      5.79      6.16      5.98      6.68      7.12    10
# readr2      2.94      2.98      3.07      3.03      3.06      3.43    10
#  sqldf     13.59     13.74     14.20     13.91     14.64     15.49    10
#    awk     16.83     16.86     17.07     16.92     17.29     17.77    10
#   awk2     16.86     16.91     16.99     16.92     16.97     17.57    10
7
Eric

Vous pouvez ouvrir le fichier en mode lecture à l'aide de la fonction file (par exemple, file("mydata.csv", open = "r")).

Vous pouvez lire le fichier ligne par ligne à l’aide de la fonction readLines avec l’option n = 1, l = readLines(fc, n = 1).

Ensuite, vous devez analyser votre chaîne en utilisant une fonction telle que strsplit, des expressions régulières, ou vous pouvez essayer le paquetage stringr (disponible sur CRAN).

Si la ligne remplit les conditions pour importer les données, vous l'importez.

Pour résumer je ferais quelque chose comme ceci:

df = data.frame(var1=character(), var2=int(), stringsAsFactors = FALSE)
fc = file("myfile.csv", open = "r")

i = 0
while(length( (l <- readLines(fc, n = 1) ) > 0 )){ # note the parenthesis surrounding l <- readLines..

   ##parse l here: and check whether you need to import the data.

   if (need_to_add_data){
     i=i+1
     df[i,] = #list of data to import
  }

}
1
lucacerone