R

Rのwrite.csvで列名指定を有効にする

Rでcsvを出力する関数であるwrite.csvで、たまに以下のような警告が出て困っていたので対策して見ます。

エラー
write.csv(df, pipe("pbcopy"), row.names=F, col.names=F)

警告メッセージ: 
 write.csv(df, pipe("pbcopy"), row.names = F, col.names = F) : 
   'col.names' への変更の試みは無視されました 

前提

write.csvは名前の通り、データをcsv形式で出力する関数です。
ファイルを指定して書き出したり、クリップボードに出力することもできるのでわりと重宝します。

write.csv
# 適当データ
df <- data.frame("ID" = c("A1","A2","A3"),
                 "Value" = c("abc", 123, T))
# ファイル出力
write.csv(df, "output.csv")

# クリップボードにコピー
## Windowsの場合
write.csv(df, "clipboard")
## Macの場合
write.csv(df, pipe("pbcopy"))

出力形式を色々設定できるwrite.tableのラッパーです。
親であるwrite.tableには行名・列名の出力を制御するrow.names=(TRUE/FALSE/ベクトル)col.names=(TRUE/FALSE/NA/ベクトル)がありますが、write.csvに指定してみると上記のようなエラーが出ます。

仕様です

マニュアルを読むとちゃんと書いていました。
write.csvwrite.csv2は出力したcsvを他のソフト(Excelなど)に貼り付けた時にcsvの行や列が崩れるのを防止するために制約をきつくしてるよ、"col.names"や"sep"は制限してるよ、とのこと。

確かに試しに制限を受けないwrite.tableのほうにwrite.table(...,row.names=T, col.names=T)のような指定をすると

"ID" "Value"
"1" "A1" "abc"
"2" "A2" "123"
"3" "A3" "TRUE"

こんな感じにいかにも列がずれそうな出力になります。

ああ、なるほど仕様でしたか。

とは言うものの、write.csvからクリップボード経由でExcelに貼れるのを非常に重宝している身としては、列名なしでコピーとかをしたいわけです。
そんな訳で改造して見ます。

col.namesが有効になるよう改造してみる

Rでは関数のオーバーライドが簡単にできてしまうので、おもむろにwrite.csvを上書きします。

write.csv <- function (...) {
    Call <- match.call(expand.dots = TRUE)
    # チェック対象から外す
    for (argname in c("append", "sep", "dec", "qmethod")) if (!is.null(Call[[argname]])) 
        warning(gettextf("attempt to set '%s' ignored", argname), 
            domain = NA)
    isFALSE <- function(x) identical(F, x)
    rn <- eval.parent(Call$row.names)
    cn <- eval.parent(Call$col.names)
    Call$append <- NULL
    # 列がずれるパターンは回避
    Call$col.names <- 
      if (!is.logical(rn) && !isFALSE(cn))
        NA
      else if (isTRUE(rn) && !isFALSE(cn))
        NA
      else if (isFALSE(rn) && is.na(cn))
        F
      else 
        cn
    Call$sep <- ","
    Call$dec <- "."
    Call$qmethod <- "double"
    Call[[1L]] <- as.name("write.table")
    eval.parent(Call)
}

列のズレが起こる以下のパターン

  • row.names=c("行名", "を", "指定"), col.names=T
  • row.names=c("行名", "を", "指定"), col.names=c("列名", "指定")
  • row.names=T, col.names=T
  • row.names=T, col.names=c("列名", "指定")

と、そもそも指定ができない

  • row.names=F, col.names=NA

のパターンの場合は回避するようにしています。
これで列名なしでコピペもできますね。

出力
write.csv(df, pipe("pbcopy"), row.names=T, col.names=T)
"","ID","Value"
"1","A1","abc"
"2","A2","123"
"3","A3","TRUE"

write.csv(df, pipe("pbcopy"), row.names=T, col.names=F)
"1","A1","abc"
"2","A2","123"
"3","A3","TRUE"

write.csv(df, pipe("pbcopy"), row.names=F, col.names=F)
"A1","abc"
"A2","123"
"A3","TRUE"

果たしてニーズあるのかと言われると、、、微妙。

なお、write.csvを元に戻す場合は、utilsパッケージに元のがあるので上書きすれば大丈夫です。

元に戻す
write.csv <- utils::write.csv