2014-09-09 16 views
5

Podczas korzystania z write.csv można znacznie zmniejszyć rozmiary plików (około 25% dla dużych zbiorów danych), usuwając cudzysłowy za pomocą quote=FALSE. Może to jednak spowodować nieprawidłowe działanie, jeśli w twoich danych znajdują się przecinki. Na przykład:Jak warunkowo usunąć cytaty w pliku write.csv?

x <- data.frame(a=1:2,b=c("hello,","world")) 
dim(x) 
[1] 2 2 
f <- tempfile() 
write.csv(x,f,row.names=FALSE,quote=FALSE) 
dim(read.csv(f)) 
[1] 2 2 
read.csv(f) 
     a b 
1 hello NA 
2 world NA 

Obserwować niewspółosiowość nazwy kolumny oraz utratę danych i dodawanie fałszywych danych.

Czy można ogólnie usunąć cytaty, ale zachować je dla pól z przecinkami w danych?

+0

[Docs] (http://stat.ethz.ch/R-manual/R-devel/library/utils/html/write.table.html) wydają się mówić, że nie jest to możliwe, chyba że wiesz z góry, które kolumny cytować ("Jeśli [. quote' is] to wektor liczbowy, jego elementy są traktowane jako wskaźniki kolumn do cytowania. "). Inne języki oferują opcję "cytuj tylko wtedy, gdy jest to konieczne", której R wydaje się nie mieć. Być może mógłbyś postprocesować swoje pliki w takim języku (jak Python)? –

+0

lub coś w stylu 'który (sapply (mydata, funkcja (x) (is.factor (x) || is.character (x)) && any (grepl (", "x)))', aby dowiedzieć się, które kolumny potrzebują być chronionym? –

+0

@TimPietzcker Podczas przeglądania, wygląda na to, że nie ma wbudowanego sposobu cytowania w razie potrzeby, ale użyłem twoich sugestii, aby zacytować tylko kolumny, których to dotyczy. "Jeśli któryś z was chce napisać jako odpowiedź, Przyjmij to. – James

Odpowiedz

4

Rozwiązaniem, z którym skorzystałem, było połączenie komentarzy @TimPietzcker i @BenBolker.

quote może być numerem wektorowym, aby określić, które kolumny będą cytowane. Chociaż wolałbym tylko cytować w razie potrzeby, to pozwoliło na prawie całkowitą redukcję rozmiaru pliku w moim przypadku (również przy użyciu na="").

commas <- which(sapply(x, function(y) any(grepl(",",y)))) 
write.csv(x,f,row.names=FALSE,quote=commas) 
read.csv(f) 
    a  b 
1 1 hello, 
2 2 world 
1

Jeśli wartość zawiera przecinek, zawiń go w cudzysłów. Następnie write.csv z quote = FALSE.

library(stringr) 
options(useFancyQuotes = FALSE) 

d <- data.frame(
    x = c("no comma", "has,comma") 
) 

d$x <- with(d, ifelse(str_detect(x, ","), dQuote(x), as.character(x))) 

filename <- "test.csv" 
write.csv(d, file = filename, quote = FALSE, row.names= FALSE) 
noquote(readLines(filename)) 
## [1] x   no comma "has,comma" 
read.csv(filename) 
##   x 
## 1 no comma 
## 2 has,comma 

(można zastąpić grepl dla str_detect i paste dla dQuote jeśli wolisz).


Z drugiej strony, nie wierzę, że dla większości zestawów danych można uzyskać w dowolnym miejscu w pobliżu 25% oszczędności rozmiaru pliku. Jeśli Twoim celem są małe pliki, lepiej skompresuj plik (zobacz zip i tar w pakiecie utils) lub zapisz go w pliku binarnym (zobacz save i pakiet rhdf5) lub w bazie danych.

+0

Dzięki, to robi to, o czym myślałem, ale dość skrzypce dla dużej liczby kolców. Wziąłem radę w komentarzach i po prostu przytoczyłem całą kolumnę, której dotyczyło, określając "cytat". Zmniejszenie rozmiaru pliku było prawie takie samo. 25% było trochę mylące - było w połączeniu z 'na =" "', ale wciąż było co najmniej 20% z własnych danych. – James

+0

Czy zastanawiałeś się nad zrobieniem 'write.csv (..., quote = FALSE, sep =" | ")' lub podobnym? Jeśli znajdziesz znak separacji, który nie pojawia się w tekście, problem znika. –

+0

Teraz myślę o tym, to jest powód, dla którego istnieją pliki rozdzielane tabulatorami. –

1

W przypadku innych szukasz podobnego rozwiązania, po prostu napisał kompleksową wymianę write.csv (write.csv.minimal.quote), że tylko cytuje gdy jest to absolutnie konieczne:

quote.if.required <- function(x, qmethod=c("double", "escape"), sep=",", eol="\n") { 
    qmethod <- match.arg(qmethod) 
    x <- as.character(x) 
    mask.quote.sub <- grepl('"', x, fixed=TRUE) 
    mask.quote.sep <- 
    grepl(sep, x, fixed=TRUE) | 
    grepl(eol, x, fixed=TRUE) 
    qstring <- switch(qmethod, escape="\\\\\"", double="\"\"") 
    x[mask.quote.sub] <- 
    paste0('"', gsub('"', qstring, x[mask.quote.sub]), '"') 
    x[mask.quote.sep & !mask.quote.sub] <- 
    paste0('"', x[mask.quote.sep & !mask.quote.sub], '"') 
    x 
} 

write.csv.minimal.quote <- function(x, file="", ..., qmethod=c("double", "escape"), row.names=FALSE, sep=",", eol="\n", quote) { 
    qmethod <- match.arg(qmethod) 
    if (!is.data.frame(x)) { 
    cn <- colnames(x) 
    x <- as.data.frame(x) 
    colnames(x) <- cn 
    } else { 
    cn <- colnames(x) 
    } 
    cn <- quote.if.required(cn, 
          qmethod=qmethod, 
          sep=sep, 
          eol=eol) 
    x <- as.data.frame(lapply(x, quote.if.required, 
          qmethod=qmethod, 
          sep=sep, 
          eol=eol)) 
    if (is.logical(row.names) && row.names) { 
    row.names <- quote.if.required(base::row.names(x), 
            qmethod=qmethod, 
            sep=sep, 
            eol=eol) 
    } else if (is.character(row.names)) { 
    row.names <- quote.if.required(row.names, 
            qmethod=qmethod, 
            sep=sep, 
            eol=eol) 
    } 

    write.table(x, file=file, append=FALSE, sep=",", dec=".", eol="\n", col.names=cn, row.names=row.names, quote=FALSE) 
} 

#tmp <- data.frame('"abc'=1:3, "def,hij"=c("1,2", "3", '4"5'), klm=6:8) 
#names(tmp) <- c('"abc', "def,hij", "klm") 
#write.csv.minimal.quote(tmp, file="test.csv") 
+0

To jest właściwa droga. Ostatnio mamy problem, że csv z R jest odrzucany przez importerów, którzy dostosowali się do sposobu, w jaki robi to MS Excel. Ponieważ MS Excel zmienił sposób, w jaki robią cudzysłowy, był to stały PITA. Zamierzam sprawdzić, czy mogę trochę to skrócić i porozmawiać z niektórymi osobami z R. Wierzę, że R's write.table potrzebuje argumentu z wyceną "msexcel" lub podobnego do wytworzenia zachowania MS. – pauljohn32

+0

Po prostu napisałem własną funkcję R, aby zrobić to samo. Mógłby użyć testów warunków skrajnych. – pauljohn32

2

To moja realizacja idei, że @Bill Denney zasugerował. Myślę, że jest ładniejszy częściowo dlatego, że jest krótsza i bardziej zrozumiałe dla mnie, ale głównie dlatego, że napisał ją :)

##' Write CSV files with quotes same as MS Excel 2013 or newer 
##' 
##' R inserts quotes where MS EExcel CSV export no longer inserts quotation marks on character 
##' variables, except when the cells include commas or quotation marks. 
##' This function generates CSV files that are, so far as we know 
##' in exactly the same style as MS Excel CSV export files. 
##' 
##' This works by manually inserting quotation marks where necessary and 
##' turning FALSE R's own method to insert quotation marks. 
##' @param x a data frame 
##' @param file character string for file name 
##' @param row.names Default FALSE for row.names 
##' @return the return from write.table. 
##' @author Paul Johnson 
##' @examples 
##' set.seed(234) 
##' x1 <- data.frame(x1 = c("a", "b,c", "b", "The \"Washington, DC\""), 
##'  x2 = rnorm(4), stringsAsFactors = FALSE) 
##' x1 
##' dn <- tempdir() 
##' fn <- tempfile(pattern = "testcsv", fileext = ".csv") 
##' writeCSV(x1, file = fn) 
##' readLines(fn) 
##' x2 <- read.table(fn, sep = ",", header = TRUE, stringsAsFactors = FALSE) 
##' all.equal(x1,x2) 
writeCSV <- function(x, file, row.names = FALSE){ 
    xischar <- colnames(x)[sapply(x, is.character)] 
    for(jj in xischar){ 
     x[ , jj] <- gsub('"', '""', x[ , jj], fixed = TRUE) 
     needsquotes <- grep('[\",]', x[ ,jj]) 
     x[needsquotes, jj] <- paste0("\"", x[needsquotes, jj], "\"") 
    } 
    write.table(x, file = file, sep = ",", quote = FALSE, 
       row.names = row.names) 
} 

Wyjście z przykładu:

> set.seed(234) 
> x1 <- data.frame(x1 = c("a", "b,c", "b", "The \"Washington, DC\""), 
+  x2 = rnorm(4), stringsAsFactors = FALSE) 
> x1 
        x1   x2 
1     a 0.6607697 
2     b,c -2.0529830 
3     b -1.4992061 
4 The "Washington, DC" 1.4712331 
> dn <- tempdir() 
> fn <- tempfile(pattern = "testcsv", fileext = ".csv") 
> writeCSV(x1, file = fn) 
> readLines(fn) 
[1] "x1,x2"           
[2] "a,0.660769736644892"       
[3] "\"b,c\",-2.052983003941"      
[4] "b,-1.49920605110092"       
[5] "\"The \"\"Washington, DC\"\"\",1.4712331168047" 
> x2 <- read.table(fn, sep = ",", header = TRUE, stringsAsFactors = FALSE) 
> all.equal(x1,x2) 
[1] TRUE 
> 
+0

W skrócie, twoja funkcja wygląda dobrze. Moje myśli to: 1) A co z typami innymi niż postacie i liczby? Wiele typów (czynniki, data itp.) Może również zawierać przecinki lub cytaty. 2) Nie jest generalizowany na inne separatory, metody cytowania, znaczniki końca linii itp. (To prawdopodobnie nie ma znaczenia dla twojego przypadku użycia, ale muszę je mieć, ponieważ czasami generuję pliki wartości oddzielone tabulatorami, które również przynoszą korzyści od minimalnego cytowania.) 3) A co z internacjonalizacją (w tym zastępcze miejsca dziesiętne jako przecinki)? –

+0

Zamiast pętli for użyj lapply, aby utworzyć zmodyfikowane ciągi. Indeksowanie ramki danych za pomocą '[' może spowalniać tępo. –