Posted at

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

More than 1 year has passed since last update.

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