RでExcelのデータを読む方法

今のところXLConnectパッケージが制約や問題点が少ないようである。 openxlsxパッケージについて追記した。xlsxに限ればこれがお薦めである(ただし注意点あり)。

Hadley Wickhamのreadxlパッケージについて書き足した。爆速かつ完璧。

xlsxパッケージ

xlsxパッケージはxls/xlsxファイルを読み書きするためのものである。Javaで書かれたApache POIを使っている。インストールはMacでも簡単に行えた(昨今のセキュリティ事情からJavaをアンインストールした人は使えない)。

ワークシートをデータフレームとして読み込む関数は read.xlsx()read.xlsx2() である。どちらもファイル名とシート番号(1から始まる)を引数とする:

data = read.xlsx("test.xls", 1) # または read.xlsx2("test.xls", 1)

いろいろなオプションが指定できるので,ヘルプを参照されたい。Windowsの日本語環境では encoding="UTF-8" のオプションが必要のようである。

ヘルプによれば,read.xlsx() より read.xlsx2() のほうがJava依存度が高く,行方向に大きいデータでは1桁高速とのことである。

デフォルト(header=TRUE)では1行目が列の名前になる。read.xlsx() は,それ以降の最初の空でない要素から各列のデータ型を判断する。read.xlsx2() はすべて文字列として読む。いずれも,colClasses で各列の型を指定することができる。

いずれもデフォルトでは文字列を因子(factor)に変換する。特に read.xlsx2() はすべて文字列として読むので,数値も因子になる。これを避けるには

data = read.xlsx2("test.xls", 1, stringsAsFactors=FALSE)

のようなオプションを使うか,あらかじめ

options(stringsAsFactors=FALSE)

と打ち込んでおくのがよい。

下の「いろいろな方法の比較」を見れば,いろいろおもしろいことがわかる。日時については,read.xlsx() は日時として扱っているのに read.xlsx2() では内部表現の数値の文字列になってしまっている。また,セルA4には 2013-02-01 23:45:01 と入力したのに read.xlsx() で 2013-02-01 23:45:00 になってしまっている。12:34:56 のような時刻だけの場合は 1899-12-30 という日付が付く。これらの理由はExcelの日時の内部形式にある。

Excelの日時の内部表現は,1900年1月1日を第1日(第0日ではない)とした数値である。つまり,数値0はこの前日の1900年1月0日(つまり1899年12月31日)に対応する。もっとも,これは1900年を閏年とした場合(間違いだが古いLotus 1-2-3との互換性のためExcelではこうしている)の値で,1900年を閏年としない正しい計算では数値0は1899年12月30日に対応する。また,昔のMac版Excelでは数値0は1904年1月1日に対応したが,現在はWindows版と同じ方式がデフォルトで,環境設定の「計算方法」で起点を1904年に変更できる。

この内部表現は浮動小数点数で,小数部は時刻を表す。数値0.5は,一般的な正午12:00を表す場合と,Excelの1900年1月0日(正しくは1899年12月30日)の正午を表す場合がある。

Excelはタイムゾーンも夏時間も扱えない。

read.xlsx2() ではこの内部表現の数値をそのまま文字列として返す。ヘルプには,これを日時に直すには as.POSIXct((x-25569)*86400, tz="GMT", origin="1970-01-01") または日付なら as.Date(x-25569, origin="1970-01-01") とすればよいと書かれている。read.xlsx() ではそのような変換をしている。しかしこれはまずい。実際,下の「いろいろな方法の比較」の例でセルA4に入れた 2013-02-01 23:45:01 は1秒ずれて 2013-02-01 23:45:00 になっている。理由は,この日時の (x-25569)*86400 は 1359762300.999… となり,as.POSIXct() は小数部分を切り捨てるためである。秒単位の表示をするためには as.POSIXct(round((x-25569)*86400), origin="1970-01-01") とすべきである(Excelに入れた日時が現地時間であるとすれば tz="GMT" もまずい)。

gdataパッケージ

gdataパッケージはGregory Warnes(geneticsパッケージの作者)によるデータハンドリングのためのツール集である。この中に,Perlを使ってxls/xlsxファイルを読む read.xls() などの関数がある。MacやLinuxでは簡単に使えるが,Windowsではここを見てPerlのインストールから始めなければならない。Windowsの日本語環境では read.xls()fileEncoding="UTF-8" のオプションが必要のようである。

ここで使われているPerlスクリプトは,Macなら例えば ~/Library/R/2.15/library/gdata/perl/ のようなディレクトリに収められている。この中の xls2csv.pl がxls/xlsxをcsvに変換する。使い方は

xls2csv.pl [-s] Excelファイル名 [出力ファイル名] [ワークシート番号]

である。オプション -s で空白行を無視しない。出力ファイル名やシート番号を略せば ベース名.csv または ベース名_シート名.csv になる。

このスクリプトでは,Excelのシートに表示される通りの値に丸められてしまうので,例えば6.022e23を入れても6.02e23になってしまうという問題がある(下の「いろいろな方法の比較」参照)。

XLConnectパッケージ

XLConnectApache POIベースで,Javaに依存する。xls/xlsxともに読み書きできる。Macのバイナリは用意されていないので,以下のようにしてソースからインストールする(ご指摘感謝!):

install.packages("XLConnect", type="source") # Mac

R 3.0.0ではMacでも普通にバイナリインストールできた。

MacのGUIでも「CRAN(ソース)」を選択すればインストールできるとのことである。githubからもインストールできる。

たいへん大きなパッケージであるが,シートごとに読むのであれば次のようにファイル名とシート番号(またはシート名)を指定すればデータフレームの形で取得できる:

data = readWorksheetFromFile("test.xls", 1) # xlsxも同様

結果はxls/xlsxともに完璧である。なお,他のパッケージと違って,日時の変換はExcelに合わせて1900年を閏年とするバグを温存している。したがって,数値0には1899-12-31が対応する。

より一般的には,例えば次のようにする。デフォルトでは1行目に列名が並んでいると仮定するが,そうでない場合は header=FALSE オプションが必要。

wb = loadWorkbook("test.xlsx")
sh = getSheets(wb) # シート名の配列を得る
for (s in sh) {
  data = readWorksheet(wb, s, header=FALSE)
  # ...
}

ほかにも多数のオプションがある。ヘルプ参照。

いろいろな方法の比較

例として,MacのExcel 2011で次のように入力し,xlsおよびxlsx形式で保存する。

Excelの入力例

ここでは手で打ち込んだが,次のCSVファイルを読み込んでも同じことである:

日時,時刻,文字列,数値
2013-02-10 12:34:56,12:34:56,"Hello, world!",299792458
2013-02-11 00:00:00,,,3.14159265358979
2013-02-01 23:45:01,23:45:01,もじれつ,
,00:00:00,,6.022e23

セルD5には 6.022e23 と入力したが,「数値」と判断され,セル内では 6.02E+23 に見える(上の fx の右には 6.022E+23 と表示されているので内部では正しく扱われている)。セルの書式設定で「標準」に直せばセル内も 6.022E+23 になる。

また,A列は「日付」→「2001-3-14 13:30」の形式,B列には「時刻」→「13:30:55」の形式を設定し,セルA2には 2013-2-10 12:34:56 と入力したが,セル内では 2013-2-10 12:34 までしか表示されない(上の fx の右には :56 まで表示される)。セルA4も 2013-2-1 23:45:01 と入力したが分までしか表示されない。

これをExcelからCSVまたはHTMLで保存したらどうなるか。結果は,日付は分までしか出力されず,6.022e23と入れたセルも6.02E+23になってしまう。Excelを入力ツールとして使う際に注意しなければならない点である。

ちなみにxlsxファイルの中身はXMLで,CSVで表すと次のような文字列が収められている:

日時,時刻,文字列,数値
41315.524259259262,0.52425925925925931,"Hello, world!",299792458
41316,,,3.14159265358979
41306.989594907405,0.98959490740740741,もじれつ,
,0,,6.0220000000000003E+23

Rのxlsxパッケージの read.xlsx() でxls/xlsxファイルを処理した例を示す。上に書いたように,「日時」の列の最後の 23:45:00 は間違いである。

> read.xlsx("test.xls", 1)
                 日時                時刻        文字列         数値
1 2013-02-10 12:34:56 1899-12-30 12:34:56 Hello, world! 2.997925e+08
2 2013-02-11 00:00:00                <NA>          <NA> 3.141593e+00
3 2013-02-01 23:45:00 1899-12-30 23:45:01      もじれつ           NA
4                <NA> 1899-12-30 00:00:00          <NA> 6.022000e+23
> read.xlsx("test.xlsx", 1)
                 日時                時刻        文字列         数値
1 2013-02-10 12:34:56 1899-12-30 12:34:56 Hello, world! 2.997925e+08
2 2013-02-11 00:00:00                <NA>          <NA> 3.141593e+00
3 2013-02-01 23:45:00 1899-12-30 23:45:01      もじれつ           NA
4                <NA> 1899-12-30 00:00:00          <NA> 6.022000e+23

同じファイルに対して,read.xlsx2() では次のようになる。NAは空文字列になっている。

> read.xlsx2("test.xls", 1, stringsAsFactors=FALSE)
                日時               時刻        文字列             数値
1  41315.52425925926 0.5242592592592593 Hello, world!     2.99792458E8
2            41316.0                                  3.14159265358979
3 41306.989594907405 0.9895949074074074      もじれつ                 
4                                   0.0                       6.022E23
> read.xlsx2("test.xlsx", 1, stringsAsFactors=FALSE)
                日時               時刻        文字列             数値
1  41315.52425925926 0.5242592592592593 Hello, world!     2.99792458E8
2            41316.0                                  3.14159265358979
3 41306.989594907405 0.9895949074074074      もじれつ                 
4                                   0.0                       6.022E23

次に,Rのgdataパッケージの read.xls() でxls/xlsxファイルを処理した例を示す。上にも書いたように,「数値」の列の最後の 6.02e+23 は間違いである。

> read.xls("test.xls", 1)
Wide character in print at /Users/okumura/Library/R/2.15/library/gdata/perl/xls2csv.pl line 270.
Wide character in print at /Users/okumura/Library/R/2.15/library/gdata/perl/xls2csv.pl line 270.
             日時     時刻        文字列         数値
1 2013/2/10 12:34 12:34:56 Hello, world! 2.997925e+08
2  2013/2/11 0:00                        3.141593e+00
3  2013/2/1 23:45 23:45:01      もじれつ           NA
4                  0:00:00               6.020000e+23
> read.xls("test.xlsx", 1)
       日時ニチジ 時刻ジコク 文字列モジレツ   数値スウチ
1 2013/2/10 12:34   12:34:56  Hello, world! 2.997925e+08
2  2013/2/11 0:00                           3.141593e+00
3  2013/2/1 23:45   23:45:01       もじれつ           NA
4                    0:00:00                6.022000e+23

最後に,RのXLConnectパッケージの readWorksheetFromFile() でxls/xlsxファイルを処理した例を示す。他のパッケージで1899-12-30となるところが1899-12-31となっていることに注意する。

> readWorksheetFromFile("test.xls", 1)
                 日時                時刻        文字列         数値
1 2013-02-10 12:34:56 1899-12-31 12:34:56 Hello, world! 2.997925e+08
2 2013-02-11 00:00:00                <NA>          <NA> 3.141593e+00
3 2013-02-01 23:45:01 1899-12-31 23:45:01      もじれつ           NA
4                <NA> 1899-12-31 00:00:00          <NA> 6.022000e+23
> readWorksheetFromFile("test.xlsx", 1)
                 日時                時刻        文字列         数値
1 2013-02-10 12:34:56 1899-12-31 12:34:56 Hello, world! 2.997925e+08
2 2013-02-11 00:00:00                <NA>          <NA> 3.141593e+00
3 2013-02-01 23:45:01 1899-12-31 23:45:01      もじれつ           NA
4                <NA> 1899-12-31 00:00:00          <NA> 6.022000e+23

比較のため,RではなくRubyであるが,Rubyによるデータ処理のspreadsheetライブラリの例で*.xlsを処理すると次のようになる(それぞれ puts row.join(',') および puts CSV.generate_line(row) の出力):

日時,時刻,文字列,数値
41315.5242592593,0.524259259259259,Hello, world!,299792458
41316.0,,,3.14159265358979
41306.9895949074,0.989594907407407,もじれつ
,0.0,,6.022e+23
日時,時刻,文字列,数値
2013-02-10T12:34:56+00:00,1899-12-30T12:34:56+00:00,"Hello, world!",299792458
2013-02-11T00:00:00+00:00,,,3.14159265358979
2013-02-01T23:45:01+00:00,1899-12-30T23:45:01+00:00,もじれつ
,1899-12-30T00:00:00+00:00,,6.022e+23

また,*.xlsxについて,同ページのrooライブラリの例では次のようになる(それぞれ puts row.join(',') および sheet.to_csv の出力):

日時,時刻,文字列,数値
2013-02-10T12:34:56+00:00,45296.00000000001,Hello, world!,299792458.0
2013-02-11,,,3.14159265358979
2013-02-01T23:45:00+00:00,85501.0,もじれつ,
,0.0,,6.022e+23
"日時","時刻","文字列","数値"
2013-02-10T12:34:56+00:00,12:34:56,"Hello, world!",299792458
2013-02-11,,,3.14159265358979
2013-02-01T23:45:00+00:00,23:45:01,"もじれつ",
,00:00:00,,602200000000000027262976

さらに比較のため,Pythonで書かれた dilshod/xlsx2csv を使ってみる(sudo pip3 install xlsx2csv しておく)。時刻が分単位に切り捨てられてしまう:

日時,時刻,文字列,数値
2013/02/10 12:34,12:34,"Hello, world!",299792458
2013/02/11 00:00,,,3.14159265358979
2013/02/ 1 23:45,23:45,もじれつ,
,00:00,,602200000000000027262976

追記:openxlsxパッケージ

openxlsx はJavaに依存せずxlsxファイルの読み書きができるパッケージである(Javaに依存せずにxlsxを読む - 東京で尻を洗う参照)。

以下のように簡単かつ高速に処理できる。ただし,日時の内部形式を文字列に直す convertToDateTime() は空セル(NA)があるとエラーになる。また,時刻の内部形式を文字列に直す関数がないので convertToDateTime() を流用することになる(不要な 1899-12-30 が付いてしまう)。

> install.packages("openxlsx")
> library(openxlsx)
> data = read.xlsx("test.xlsx")
> data
      日時      時刻        文字列         数値
1 41315.52 0.5242593 Hello, world! 2.997925e+08
2 41316.00        NA          <NA> 3.141593e+00
3 41306.99 0.9895949      もじれつ           NA
4       NA 0.0000000          <NA> 6.022000e+23
> convertToDate(data$日時)
[1] "2013-02-10" "2013-02-11" "2013-02-01" NA          
> convertToDateTime(data$日時)
 以下にエラー as.POSIXlt.character(x, tz, ...) : 
character string is not in a standard unambiguous format
> convertToDateTime(data$日時[1:3])
[1] "2013-02-10 12:34:56 JST" "2013-02-11 00:00:00 JST"
[3] "2013-02-01 23:45:00 JST"
> convertToDateTime(data$時刻[c(1,3:4)])
[1] "1899-12-30 12:34:56 JCST" "1899-12-30 23:45:01 JCST"
[3] "1899-12-30 00:00:00 JCST"

追記:Windowsではinvalid multibyte errorとなり使えないという情報をいただいた。

[2015-12-06] 追記:Rで解析:セル体裁!大きなデータも大丈夫!エクセル操作の「openxlsx」パッケージ

readxl

Hadley Wickhamの新しい readxl(開発版は hadley/readxl)を試してみた。爆速という評判。最初はちょっとおかしかったが,バグ報告したおかげで直してもらえた。結果は完璧:

> install.packages("readxl")
> # install.packages("devtools")              # 開発版の場合
> # devtools::install_github("hadley/readxl") # 開発版の場合
> library(readxl)
> data1 = read_excel("test.xls")
> data1
                 日時                時刻        文字列         数値
1 2013-02-10 12:34:56 1899-12-30 12:34:56 Hello, world! 2.997925e+08
2 2013-02-11 00:00:00                <NA>          <NA> 3.141593e+00
3 2013-02-01 23:45:00 1899-12-30 23:45:01      もじれつ           NA
4                <NA> 1899-12-30 00:00:00          <NA> 6.022000e+23
> data2 = read_excel("test.xlsx")
> data2
                 日時                時刻        文字列         数値
1 2013-02-10 12:34:56 1899-12-30 12:34:56 Hello, world! 2.997925e+08
2 2013-02-11 00:00:00                <NA>          <NA> 3.141593e+00
3 2013-02-01 23:45:00 1899-12-30 23:45:01      もじれつ           NA
4                <NA> 1899-12-30 00:00:00          <NA> 6.022000e+23

大量のExcelファイルを集計する

カレントディレクトリに大量のExcelファイルがあって,それを集計したいとする。ファイルは次のような形式で1行だけ(あるいは複数行)のデータが入っているとする。

品名値段
りんご298

これらを全部縦につないで一つの大きなデータフレームにしたい。ただし1行目は烈名であるので一つあれば十分である。

上で解説した readxl パッケージの read_excel() 関数を使えば次のようにできる:

names = dir(pattern="*.xlsx")
alldata = do.call(rbind, lapply(names, read_excel))

参考