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