ExcelのデータをPython、C#で読み込むまとめ

Webアプリ用のデータをExcelを使って作っている関係で、Python、C#でExcelファイルを読み込むためのライブラリーを比較してみました。（.NET Core 2.1、Python3.7 が公開されたので記載内容を修正 2018年7月3日）

Excelファイルを読み込むための方法として、Microsoft.Office.Interop.Excelを使ったCOM参照による方法を紹介している記事も多いですが、それを使うのは処理が遅いし、問題も多いし、そもそも Excel が必要なので Linux サーバーだと動作しません。以下に紹介する OSS のライブラリーはかなり改善されていて、少し注意すれば問題なくかつ高速に処理することができます。

## パフォーマンス比較
Excelのファイルを読み込みTSV形式で出力するコンソールアプリケーションを作成し、それの起動から終了までの時間を測定しました。Excelのファイルは、589行☓235列の表でxlsx形式で829KBのものを使いました。

テストに使ったソースコード及びデータは、[GitHub](https://github.com/timej/ExcelLibraryTest)の方に公開してあります。まだ、整理はできていませんが興味があればみてください。

### テスト結果

Linux（Ubuntu 18.04）

| ライブラリ | バージョン | xlsx 読込処理時間 | xls 読込処理時間 
|:----------|:-----------|-------------:|-------------:|
|Pandas (xlrd)|0.23.1 (1.1.0)|898|572|
|openpyxl|2.5.4|1314|-|
|ExcelDataReader|3.4.0|704|341|
|DotNetCore.NPOI|1.0.2|1016|384
|EPPlus|4.5.2.1|1954|-|
|ClosedXML|0.93.0|2336|-|

処理時間は、`ExcelDataReader`が最も速くなりました。一方、Pandasを使うと、ExcelDataReader を使うより読み込み時間は1.3〜1.6倍ぐらいかかりますが、プログラムは1行で書けるし、読み込んだ後のデータの加工のための機能が多くNumpyを使って高速な処理ができるので便利です。自分はPandasを使うことに決めました。

## 各ライブラリーの特徴

以下では、各ライブラリーの特徴と Excel の xlsx ファイルを読み込んで、tsv 形式で書き出すコードを載せています。

## Python
### Pandas (xlrd)
[xlrd](http://xlrd.readthedocs.io/en/latest/) は、単独で Excelファイルを読み込むことができますが、pandas.read_excel を使って [Pandas](https://pandas.pydata.org/) のデータフレームに変換した方が処理が楽です。以下のように簡単な記述で tsv 形式に変換できます。pandas.read_excel を使用する場合 xlrd の import 文を書く必要はありませんが、ライブラリーをインストールしておく必要はあります。

```example.py
import pandas as pd

df = pd.read_excel(in_path, header=None)
df.to_csv(out_path, header=False, sep='\t', index=False)
```
データフレームには多くの[API](https://pandas.pydata.org/pandas-docs/stable/api.html#dataframe)があり、csv形式以外に、json, HTMLテーブル,SQLデータベース等に簡単に出力できます。 

データフレームは、`df.iat[0, 0]`のようにしてCellの番号でCellの値を取得したり設定したりすることができるので、VBAやC#でよくするようにfor文で処理をすることもできますが、行列での演算が整備されているので、そちらの方を使うことが多くなります。

上の式では、任意の表を扱えるように header=None にしていますが、データベース形式のようにヘッダーがきちんとある場合は、例えば3行目にある場合は`header=2`と指定するとヘッダーの名前で列を指定できるので便利です。

また、上の式のようにシート名を指定していない場合は、一番最初のシートが読み込まれます。シートを指定したい場合は、`sheet_name='シート名'`又は2番目のシートであれば`sheet_name=1`のように番号で指定することができます。詳しくは、[pandas.read_excelのマニュアル](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_excel.html)をみてください。

### [openpyxl](https://openpyxl.readthedocs.io/en/stable/)
openpyxlは、Excel 2010 の xlsx/xlsm/xltx/xltmfor ファイルを読み書きできる Python のライブラリーです。PythonにはExcelファイルを操作するためのライブラリーがいくつかありますが、その中で最もよく使われるライブラリーの一つです。

```example.py
from openpyxl import load_workbook
import csv

def iter_row(row):
    yield [cell.value for cell in row]

wb = load_workbook(in_path, read_only=True)
sheet = wb.worksheets[0]
with open(out_path, 'w') as f:
    writer = csv.writer(f, delimiter='\t', quoting=csv.QUOTE_MINIMAL)
    for row in sheet.rows:
        writer.writerows(iter_row(row))
```
Excelのセルには、上の場合であれば`sheet['A1']`又は`sheet.cell(row=1, column=1)`のようにしてアクセスできます。


## C# 
### [ExcelDataReader](https://github.com/ExcelDataReader/ExcelDataReader)
読み込み専用のソフトで、高速に処理できるのが特徴で、海外では以前から人気のあるライブラリーです。かっては日本語の処理に問題があったのですが、現時点では解消されたので、日本語でも安心して使えます。Excel 2.0 から Excel 2007以降のOpen XML形式まで広く対応しています。

```example.cs
using (var stream = File.Open(inFile, FileMode.Open, FileAccess.Read))          
using (var excelReader = ExcelReaderFactory.CreateReader(stream))
{
    var result = excelReader.AsDataSet();
    var sb = new StringBuilder();
  
    foreach (DataRow datarow in result.Tables[0].Rows)
    {
        sb.Append(datarow.ItemArray.Aggregate((s, x) => s + "\t" + x.ToString()));
        sb.Append("\n");
    }
    File.WriteAllText(outFile, sb.ToString());
}
```

### [EPPlus](https://github.com/JanKallman/EPPlus)
Office Open XML 形式（xlsx）の読み込み及び書き込みに対応しています。チャートや VBA を操作できる API もあり、Excel に関して言えば NPOI よりも機能は豊富です。.NET Core で使う場合は、Ver4.5からの対応なので現在は RC のものを使う必要があります。また、.NETStandard 2.0 の対応なので、.NET Core では 2.0 からの対応になります。

EPPlusでは、Cellsの番号は1から始まります。

```example.cs
FileInfo newFile = new FileInfo(infile);
ExcelPackage pck = new ExcelPackage(newFile);
//Ver4.5では、Worksheetsは 0 から始まるように変更
ExcelWorksheet sheet = pck.Workbook.Worksheets[1];

var sb = new StringBuilder();
int rows = sheet.Dimension.Rows;

for (int r = 1; r <= rows; r++)
{
    for (int c = 1; c <= sheet.Dimension.Columns; c++)
        sb.Append(sheet.Cells[r, c].Text + "\t");
 
    sb.Remove(sb.Length - 1, 1);
    sb.Append("\n");         
}
File.WriteAllText(outFile, sb.ToString());
```

### [NPOI](https://github.com/tonyqus/npoi)
Java の Apacche POI を .NET に移植したものです。2007 の xlsx 及び 2003 の xls ファイルの双方に対応しており、読み込みも書き出しもできます。Excel だけでなく docx にも対応しています。Tony Qu's NPOIは.NET Core には対応していませんが、それから派生した [DotNetCore.NPOI](https://github.com/dotnetcore/NPOI)が、.NET Standard 2.0 に対応しているので、.NET Core 2.0 でも動作します。DotNetCore.NPOI は ZKWeb.System.Drawing を使っているので Linux で動作させる場合には、libgdiplus が必要になります。

NPOIは、Cellsの番号は0からになります。

```example.cs
FileStream stream = File.OpenRead(inFile);
var book = new XSSFWorkbook(stream);
stream.Close();
ISheet sheet = book.GetSheetAt(0);
int lastRowNum = sheet.LastRowNum;

var sb = new StringBuilder();
for (int r = 0; r < sheet.LastRowNum; r++)
{
    var datarow = sheet.GetRow(r);
    {
        foreach (ICell cell in datarow.Cells)
        { 
            if(cell.CellType == CellType.Numeric)
                sb.Append(cell.NumericCellValue + "\t");
            else
                sb.Append(cell.StringCellValue + "\t");
        }       
        sb.Remove(sb.Length - 1, 1);
        sb.Append("\n");
    }
}
File.WriteAllText(outFile, sb.ToString());
```

### [ClosedXML](https://github.com/ClosedXML/ClosedXML)
Microsoft 純正の Open XML SDK をそのままで使うのはあまりにも面倒なので、それを使いやすくしたライブラリーです。ベースとしてOpen XML SDK を使うので、対応する Excel のファイル形式は、Excel2007以降のOpen XML形式のものになります。Open XML SDK がベースなので機能は豊富ですが、処理は少し重いです。

sheet.Cells()を使って処理をすると処理がものすごく遅くなるので、sheet.Rows()を使ってforeachで行ごとに処理をしています。

```example.cs
var workbook = new ClosedXML.Excel.XLWorkbook(inFile);
ClosedXML.Excel.IXLWorksheet sheet = workbook.Worksheets.Worksheet(1);
var sb = new StringBuilder();
var rows = sheet.LastRowUsed().RangeAddress.FirstAddress.RowNumber;
var columns = sheet.LastColumnUsed().RangeAddress.FirstAddress.ColumnNumber;

foreach(var row in sheet.Rows())
{
    foreach (var cell in row.Cells())
        sb.Append(cell.GetString() + "\t");
    sb.Remove(sb.Length - 1, 1);
    sb.Append("\n");
}
File.WriteAllText(outFile, sb.ToString());
```

### 参考 Windows（Windows10）でのテスト結果

Windows（Windows10）でのテスト結果は以下のとおりでした。Windows と Linux は、同一のPCでデュアルブートで立ち上げたものを使っています。処理時間の単位はミリ秒です。

| ライブラリ | バージョン | xlsx 読込処理時間 | xls 読込処理時間 
|:----------|:-----------|-------------:|-------------:|
|Pandas (xlrd)|0.23.1 (1.1.0)|1148|755|
|openpyxl|2.5.4|1848|-|
|ExcelDataReader(.NET Framework)|3.4.0|581|344|
|ExcelDataReader(.NET Core)|3.4.0|610|353|
|NPOI(.NET Framework)|2.3.0|1004|372|
|DotNetCore.NPOI(.NET Core)|1.0.2|1062|388|
|EPPlus(.NET Framework)|4.5.2.1|1310|-|
|EPPlus(.NET Core)|4.5.2.1|1246|-|
|ClosedXML(.NET Framework)|0.93.0|2202|-|
|ClosedXML(.NET Core)|0.93.0|2069|-|

.NET Core 2.1 になって、Linux と Windows の処理速度にやっと差がなくなりました。また、.NET Core と .NET Framework との処理時間もほぼ同じなりました。やっと、Linux で C# が使えるようになったといいでしょう。しかしながら、.NET Framework 4.5が公開されたのが2012年8月なので6年かかったということになります。競争相手のJavaにはKotlinという新しい言語が出てきたし、その間にJavaScriptは大きく進歩してしまいました。遅すぎたというのが現実だと思います。

Pythonの方は、Windowsで使うと3割ぐらいも遅くなるという結果になりました。PythonをWindowsで使うと遅くなるのは以前からでしたが、Python3.7が公開されたばかりなのでバイナリーの方がまだ十分に対応していないためというのもあると思います。