web-dev-qa-db-fra.com

Accélérer les performances de write.table 

J'ai un data.frame et je veux l'écrire. Les dimensions de mon data.frame sont 256 lignes sur 65536 colonnes. Quelles sont les alternatives plus rapides à write.csv

38
lolibility

Si toutes vos colonnes appartiennent à la même classe, convertissez-les en matrice avant d'écrire, ce qui accélère presque 6 fois. En outre, vous pouvez envisager d'utiliser write.matrix() à partir du package MASS, bien que cela n'ait pas été plus rapide pour cet exemple. Peut-être que je n'ai pas mis en place quelque chose correctement:

#Fake data
m <- matrix(runif(256*65536), nrow = 256)
#AS a data.frame
system.time(write.csv(as.data.frame(m), "dataframe.csv"))
#----------
#   user  system elapsed 
# 319.53   13.65  333.76 

#As a matrix
system.time(write.csv(m, "matrix.csv"))
#----------
#   user  system elapsed 
#  52.43    0.88   53.59 

#Using write.matrix()
require(MASS)
system.time(write.matrix(m, "writematrix.csv"))
#----------
#   user  system elapsed 
# 113.58   59.12  172.75 

MODIFIER

Pour répondre à la préoccupation évoquée ci-dessous concernant le fait que les résultats ci-dessus ne sont pas équitables pour data.frame, voici quelques résultats supplémentaires et un calendrier indiquant que le message général reste "convertissez votre objet de données en matrice si possible. Si non Vous pouvez également vous demander pourquoi vous devez écrire un fichier de 200 Mo + au format CSV si la synchronisation est de la plus haute importance ":

#This is a data.frame
m2 <- as.data.frame(matrix(runif(256*65536), nrow = 256))
#This is still 6x slower
system.time(write.csv(m2, "dataframe.csv"))
#   user  system elapsed 
# 317.85   13.95  332.44
#This even includes the overhead in converting to as.matrix in the timing 
system.time(write.csv(as.matrix(m2), "asmatrix.csv"))
#   user  system elapsed 
#  53.67    0.92   54.67 

Donc, rien ne change vraiment. Pour confirmer que cela est raisonnable, considérons les coûts de temps relatifs de as.data.frame():

m3 <- as.matrix(m2)
system.time(as.data.frame(m3))
#   user  system elapsed 
#   0.77    0.00    0.77 

Donc, pas vraiment une grosse affaire ou biaiser des informations autant que le commentaire ci-dessous pourrait croire. Si vous n'êtes toujours pas convaincu que l'utilisation de write.csv() sur des données volumineuses est une mauvaise idée en termes de performances, consultez le manuel situé sous la Note:

write.table can be slow for data frames with large numbers (hundreds or more) of
columns: this is inevitable as each column could be of a different class and so must be
handled separately. If they are all of the same class, consider using a matrix instead.

Enfin, envisagez de passer à un objet RData natif si vous perdez encore le sommeil en économisant plus rapidement.

system.time(save(m2, file = "thisisfast.RData"))
#   user  system elapsed 
#  21.67    0.12   21.81
24
Chase

data.table::fwrite() a été fourni par Otto Seiskari et est disponible dans les versions 1.9.8+. Matt a apporté des améliorations supplémentaires (y compris la parallélisation) et a écrit un article à ce sujet. Veuillez signaler tout problème sur le tracker .

Tout d’abord, voici une comparaison des mêmes dimensions que celles utilisées par @chase ci-dessus (c’est-à-dire un très grand nombre de colonnes: 65 000 colonnes (!) X 256 lignes), ainsi que fwrite et write_feather, de manière à assurer une certaine cohérence. à travers des machines. Notez l'énorme différence que compress=FALSE fait en base R.

# -----------------------------------------------------------------------------
# function  | object type |  output type | compress= | Runtime | File size |
# -----------------------------------------------------------------------------
# save      |      matrix |    binary    |   FALSE   |    0.3s |    134MB  |
# save      |  data.frame |    binary    |   FALSE   |    0.4s |    135MB  |
# feather   |  data.frame |    binary    |   FALSE   |    0.4s |    139MB  |
# fwrite    |  data.table |    csv       |   FALSE   |    1.0s |    302MB  |
# save      |      matrix |    binary    |   TRUE    |   17.9s |     89MB  |
# save      |  data.frame |    binary    |   TRUE    |   18.1s |     89MB  |
# write.csv |      matrix |    csv       |   FALSE   |   21.7s |    302MB  |
# write.csv |  data.frame |    csv       |   FALSE   |  121.3s |    302MB  |

Notez que fwrite() est exécuté en parallèle. La synchronisation indiquée ici concerne un Macbook Pro 13 'avec 2 cœurs et 1 thread/core (+2 threads virtuels via l'hyperthreading), un disque SSD de 512 Go, un cache L2 de 256 Ko/cœur et un cache L4 de 4 Mo. Selon les spécifications de votre système, YMMV.

Je répète également les points de repère sur des données relativement plus probables (et plus volumineuses):

library(data.table)
NN <- 5e6 # at this number of rows, the .csv output is ~800Mb on my machine
set.seed(51423)
DT <- data.table(
  str1 = sample(sprintf("%010d",1:NN)), #ID field 1
  str2 = sample(sprintf("%09d",1:NN)),  #ID field 2
  # varying length string field--think names/addresses, etc.
  str3 = replicate(NN,paste0(sample(LETTERS,sample(10:30,1),T), collapse="")),
  # factor-like string field with 50 "levels"
  str4 = sprintf("%05d",sample(sample(1e5,50),NN,T)),
  # factor-like string field with 17 levels, varying length
  str5 = sample(replicate(17,paste0(sample(LETTERS, sample(15:25,1),T),
      collapse="")),NN,T),
  # lognormally distributed numeric
  num1 = round(exp(rnorm(NN,mean=6.5,sd=1.5)),2),
  # 3 binary strings
  str6 = sample(c("Y","N"),NN,T),
  str7 = sample(c("M","F"),NN,T),
  str8 = sample(c("B","W"),NN,T),
  # right-skewed (integer type)
  int1 = as.integer(ceiling(rexp(NN))),
  num2 = round(exp(rnorm(NN,mean=6,sd=1.5)),2),
  # lognormal numeric that can be positive or negative
  num3 = (-1)^sample(2,NN,T)*round(exp(rnorm(NN,mean=6,sd=1.5)),2))

# -------------------------------------------------------------------------------
# function  |   object   | out |        other args         | Runtime  | File size |
# -------------------------------------------------------------------------------
# fwrite    | data.table | csv |      quote = FALSE        |   1.7s   |  523.2MB  |
# fwrite    | data.frame | csv |      quote = FALSE        |   1.7s   |  523.2MB  |
# feather   | data.frame | bin |     no compression        |   3.3s   |  635.3MB  |
# save      | data.frame | bin |     compress = FALSE      |  12.0s   |  795.3MB  |
# write.csv | data.frame | csv |    row.names = FALSE      |  28.7s   |  493.7MB  |
# save      | data.frame | bin |     compress = TRUE       |  48.1s   |  190.3MB  |
# -------------------------------------------------------------------------------

Donc, fwrite est environ 2x plus rapide que feather dans ce test. Ceci a été exécuté sur la même machine que celle indiquée ci-dessus avec fwrite s'exécutant en parallèle sur 2 cœurs.

feather semble également un format binaire assez rapide, mais pas encore de compression.


Voici une tentative pour montrer comment fwrite se compare par rapport à l'échelle:

NB: le benchmark a été mis à jour en exécutant la fonction save() de la base R avec compress = FALSE (car la plume n’est pas compressée).

 mb

Donc, fwrite est le plus rapide de tous sur ces données (fonctionnant sur 2 cœurs) et crée un .csv qui peut facilement être visualisé, inspecté et passé à grep, sed etc.

Code de reproduction:

require(data.table)
require(microbenchmark)
require(feather)
ns <- as.integer(10^seq(2, 6, length.out = 25))
DTn <- function(nn)
    data.table(
          str1 = sample(sprintf("%010d",1:nn)),
          str2 = sample(sprintf("%09d",1:nn)),
          str3 = replicate(nn,paste0(sample(LETTERS,sample(10:30,1),T), collapse="")),
          str4 = sprintf("%05d",sample(sample(1e5,50),nn,T)),
          str5 = sample(replicate(17,paste0(sample(LETTERS, sample(15:25,1),T), collapse="")),nn,T),
          num1 = round(exp(rnorm(nn,mean=6.5,sd=1.5)),2),
          str6 = sample(c("Y","N"),nn,T),
          str7 = sample(c("M","F"),nn,T),
          str8 = sample(c("B","W"),nn,T),
          int1 = as.integer(ceiling(rexp(nn))),
          num2 = round(exp(rnorm(nn,mean=6,sd=1.5)),2),
          num3 = (-1)^sample(2,nn,T)*round(exp(rnorm(nn,mean=6,sd=1.5)),2))

count <- data.table(n = ns,
                    c = c(rep(1000, 12),
                          rep(100, 6),
                          rep(10, 7)))

mbs <- lapply(ns, function(nn){
  print(nn)
  set.seed(51423)
  DT <- DTn(nn)
  microbenchmark(times = count[n==nn,c],
               write.csv=write.csv(DT, "writecsv.csv", quote=FALSE, row.names=FALSE),
               save=save(DT, file = "save.RData", compress=FALSE),
               fwrite=fwrite(DT, "fwrite_turbo.csv", quote=FALSE, sep=","),
               feather=write_feather(DT, "feather.feather"))})

png("microbenchmark.png", height=600, width=600)
par(las=2, oma = c(1, 0, 0, 0))
matplot(ns, t(sapply(mbs, function(x) {
  y <- summary(x)[,"median"]
  y/y[3]})),
  main = "Relative Speed of fwrite (turbo) vs. rest",
  xlab = "", ylab = "Time Relative to fwrite (turbo)",
  type = "l", lty = 1, lwd = 2, 
  col = c("red", "blue", "black", "Magenta"), xaxt = "n", 
  ylim=c(0,25), xlim=c(0, max(ns)))
axis(1, at = ns, labels = prettyNum(ns, ","))
mtext("# Rows", side = 1, las = 1, line = 5)
legend("right", lty = 1, lwd = 3, 
       legend = c("write.csv", "save", "feather"),
       col = c("red", "blue", "Magenta"))
dev.off()
59
MichaelChirico

Une autre option consiste à utiliser le format de fichier feather .

df <- as.data.frame(matrix(runif(256*65536), nrow = 256))

system.time(feather::write_feather(df, "df.feather"))
#>   user  system elapsed 
#>  0.237   0.355   0.617 

Feather est un format de fichier binaire conçu pour être très efficace en lecture et en écriture. Il est conçu pour fonctionner avec plusieurs langues: il existe actuellement des clients R et Python, et un client Julia est en préparation.

À titre de comparaison, voici combien de temps saveRDS prend:

system.time(saveRDS(df, "df.rds"))
#>   user  system elapsed 
#> 17.363   0.307  17.856

Il s’agit d’une comparaison quelque peu injuste car la valeur par défaut de saveRDS consiste à compresser les données. Dans ce cas, les données sont incompressibles car elles sont complètement aléatoires. Si vous désactivez la compression, saveRDS accélère considérablement:

system.time(saveRDS(df, "df.rds", compress = FALSE))
#>   user  system elapsed 
#>  0.181   0.247   0.473     

Et en effet, il est maintenant légèrement plus rapide que la plume. Alors pourquoi utiliser la plume? En règle générale, il est plus rapide que readRDS() et vous écrivez les données relativement peu de fois par rapport au nombre de fois où vous les avez lues.

system.time(readRDS("df.rds"))
#>   user  system elapsed 
#>  0.198   0.090   0.287 

system.time(feather::read_feather("df.feather"))
#>   user  system elapsed 
#>  0.125   0.060   0.185 
12
hadley

Le fst package

Une option plus récente permettant une lecture et une écriture très rapides des fichiers de données est le paquetage fst }. fst génère des fichiers au format binaire. 

Utilisez write.fst(dat, "file.fst", compress=0), où compress peut aller de 0 (pas de compression) à 100 (compression maximale). Les données peuvent être relues dans R avec dat = read.fst("file.fst"). Selon le minutage indiqué sur le site Web du paquet , il est plus rapide que feather, data.table et base R readRDS et writeRDS

Le site de développement de paquet avertit que le format de données fst évolue toujours et que fst ne doit donc pas encore être utilisé pour le stockage de données à long terme.

3
eipi10

je pense que vous devriez utiliser fwrite ()

c'est beaucoup plus rapide et m'a beaucoup aidé:

fwrite(x, file = "", append = FALSE, quote = "auto",
  sep = ",", sep2 = c("","|",""),
  eol = if (.Platform$OS.type=="windows") "\r\n" else "\n",
  na = "", dec = ".", row.names = FALSE, col.names = TRUE,
  qmethod = c("double","escape"),
  logical01 = getOption("datatable.logical01", FALSE),  # due to change to TRUE; see NEWS
  logicalAsInt = logical01,  # deprecated
  dateTimeAs = c("ISO","squash","Epoch","write.csv"),
  buffMB = 8L, nThread = getDTthreads(),
  showProgress = interactive(),
  verbose = getOption("datatable.verbose", FALSE))

https://jangorecki.gitlab.io/data.table/library/data.table/html/fwrite.html

0
Ako Heidari

Vous pouvez également essayer read_rds du paquet 'readr' (comparez à data.table :: fread) et write_rds (comparez à data.table :: fwrite).

Voici un exemple simple dans mon jeu de données (1133 lignes et 429499 colonnes):

écrire un jeu de données

fwrite(rankp2,file="rankp2_429499.txt",col.names=T,row.names=F,quote = F,sep="\t")write_rds(rankp2,"rankp2_429499.rds")

lire le jeu de données (1133 lignes et 429499 colonnes)

system.time(fread("rankp2_429499.txt",sep="\t",header=T,fill = TRUE))  user system elapsed 42.391 0.526 42.949

system.time(read_rds("rankp2_429499.rds")) user system elapsed 2.157 0.388 2.547

J'espère que ça aide.

0
Yi Li