如何在write.csv中有条件地删除引号?

时间:2014-09-09 11:03:46

标签: r csv

使用write.csv时,可以通过使用quote=FALSE删除引号来显着减小文件大小(大数据集大约25%)。但是,如果您的数据中存在逗号,则可能会导致read.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

观察列名称错位,丢失数据和添加虚假数据。

是否可以删除引号,但是为数据中包含逗号的字段维护它们?

4 个答案:

答案 0 :(得分:6)

我使用的解决方案是@TimPietzcker和@BenBolker的评论的组合。

quote可以是一个数字向量,用于指定引用哪些列。虽然我希望只在需要时引用,但这几乎可以减少文件大小的缩减(也使用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 :(得分:3)

这是我对@Bill Denney建议的想法的实现。我觉得它更好一部分因为它更简洁,更容易理解,但主要是因为我写了它:)

##' 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)
}

示例输出:

>  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
> 

答案 2 :(得分:2)

如果其他人正在寻找类似的解决方案,我只是写了write.csvwrite.csv.minimal.quote)的全面替代品,只在绝对需要时才引用:

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

答案 3 :(得分:1)

如果值包含逗号,则用引号括起来。然后write.csvquote = 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

(如果您愿意,可以将grepl替换为str_detect,将paste替换为dQuote。)


另一方面,我不相信对于大多数数据集,您可以节省大约25%的文件大小。如果您的目标是小文件,那么您最好压缩文件(请参阅zip包中的tarutils),或将其存储在二进制文件中(请参阅{{ 1}}和save包),或者可能在数据库中。