Excel に R のプロット


この記事は R Advent Calendar 2012 への寄稿です。

特にタイトル以上の説明はないのですが, Excel で R っぽいグラフが描けたらいいよね,って思う人はいるのかなと思います。ということでやってみました。どんなものができたかというと,下の図のような感じです。画像をクリックすると実際の Excel ファイル (.xlsx) がダウンロードできます。下の図のものを含めて全部で 3 種類あります。

excel

R 組み込みのサンプルデータである iris のがく片の幅 (Sepal.Width) と長さ (Sepal.Length) を散布図にしたものです。事前に周りにこれを見せたところ反応がイマイチだったのですが,どうやら R の出力画像を Excel に貼り付けたと思われたみたいです。違います。このグラフの点,線,テキスト,これらすべてがオートシェイプでできています。グラフの手動調整ができるよ! やったねたえちゃん!

グラフのタイトルだけ下の方にずれてしまっていますが[A],それ以外は概ね綺麗に描けているのではないでしょうか。しかし図の線や点やテキストはすべて Excel のオブジェクトなので,複雑な図になってくるとオブジェクト数が増えて重くなってしまい[B],とても使えたものではありません。 Excel だし仕方ないね[C]

ところで今年は本当に沢山の種類の Advent Calendar がありますね。私は R Advent Calendar 以外に F# Advent Calendar にも参加しています。意外にもその中で R に関する話題がありました。この記事では Excel-DNAR.NET を使って Excel から R を呼び出すということをしています。 F# Advent Calendar で R と Excel の連携が話題にされているのに R Advent Calendar で Excel を話題にしないのは良くないかなと思って今回のネタにいたりました。 F# Advent Calendar では実用性が求められましたが,その反動でこんなことになってしまいました。

以下にどうやってこれを作ったかを説明します。

R.NET には,まだリリースには含まれていない Graphics という名前のブランチがあります。このブランチで実装しているのは, R の低レベル描画 API の薄いラッパーです。 IGraphicsDevice というインターフェイスを実装してそれを REngine に与えてやることで, R 環境のグラフィックデバイスが, IGraphicsDevice を実装したクラスのインスタンスになるという仕組みです[D]。なぜリリース版に含まれていないかというと,終了処理にリソースを正常に解放できないという問題があるからです。

Excel で線や図形はオートシェイプで描くことができます。つまり, IGraphicsDevice を実装するクラスの描画命令でオートシェイプを描画すれば目的が達成できます。このために, NetOffice というライブラリーを利用します。 NetOffice は .NET Framework 言語から Excel を操作するためのライブラリーで, Visual Studio Tools for Office (VSTO) を使いやすくしたものです[E]

IGraphicsDevice の実装を一部抜粋すると以下のような感じです。

public class ExcelCanvas : IGraphicsDevice
{
   private static readonly double Width = 512.0;
   private static readonly double Height = 512.0;

   private readonly Excel.Workbook workbook;
   private Excel.Worksheet worksheet;

   public ExcelCanvas(Excel.Workbook workbook)
   {
      this.workbook = workbook;
   }

   public string Name { get { return "excel"; } }
   public int Version { get { return 9; } }  // 2.14 以降。詳しくはヘッダーファイルを参照。 

   // 新しいプロットのために新しいワークシートを用意する。
   public void OnNewPageRequested(GraphicsContext context, DeviceDescription description)
   {
      this.worksheet = (Excel.Worksheet)this.workbook.Add();
      var rect = new Rectangle(0.0, 0.0, Width, Height);  // 実際の描画領域。
      description.Bounds = rectangle;
      description.ClipBounds = rectangle;
   }

   // GraphicsContext には線のスタイルやフォントなどの情報などが入っているが,今回は無視した。
   // グラフが青っぽかったのはそのため (Excel のデフォルトカラー)。
   public void DrawLine(Point source, Point destination, GraphicsContext context, DeviceDescription description)
   {
      var src = ConvertPoint(source);
      var dest = ConvertPoint(destination);
      this.worksheet.Shapes.AddLine(src.X, src.Y, dest.X, dest.Y);
   }

   // R の Y 軸は数学の座標軸と同じで左下が (0, 0) になる。
   // Excel は左上が (0, 0) なので上下を逆転すると R が要求する点に対応する Excel 上の点が求まる。
   private static PointF ConvertPoint(Point point)
   {
      return new PointF((float)pointX, (float)(Height - point.Y));
   }
}

コード中のコメントにある色設定の他に,このコードでは省略したテキストの描画周りをもう少し丁寧に書けば, R の出力により近づけることができます。やらないけどね。

使うときは次のような感じです。

using (var excel = new Excel.Application() { DisplayAlerts = false })
{
   var workbook = excel.Workbooks.Add();
   var canvas = new ExcelCanvas(workbook);
   using (var engine = REngine.CreateInstance("RDotNet"))
   {
      engine.Initialize();
      engine.Install(canvas);  // R の描画エンジンを Excel にする。
      engine.Evaluate(@"plot(Sepal.Width ~ Sepal.Length, data=datasets::iris)");
   }
   workbook.SaveAs("radventjp.xlsx");
   excel.Quit();
}

先ほど述べたように,このままでは R の終了時 (using 節を抜けた時) にエラーが起こってしまいます。その場しのぎ的ではありますが, R の終了処理 (Rf_endEmbeddedR) をコード 0 呼ばなければ[F],今回のネタくらいの処理なら大きな問題がないので,今回はそれに甘えた処理をしました。良い子はマネしないでね

GetFunction<Rf_endEmbeddedR>("Rf_endEmbeddedR")(1);

まとめ

R の描画 API を利用して Excel 上に R のプロットを出力してみました。今回用いたライブラリー以外でも,複数の技術を組み合わせるためのライブラリーというのはたくさんあります。自分の好きな環境から他の環境を利用するというのは,意外と簡単に実現できるものなのです[G]

脚注

  1. 大体 80 行目あたりにあります。 Sheet1 のグラフはもともとタイトルがありません。 []
  2. 単に真面目に実装していない (例えば四角形の描画に四角形オブジェクトでなく 4 つの直線オブジェクトを用いている) ことが原因かもしれませんが。 []
  3. 真面目にやるなら,オートシェイプを使うのではなく,メタファイルのような画像ファイルに出力してそれを貼れば良いでしょう。今回のような複雑な実装する必要もありません。 []
  4. 本来の目的は, .NET Framework の言語のみでウィンドウや画像ファイルにグラフの描画を達成することです。当然のことですが,出力先が Excel である必要はありません。 []
  5. NetOffice 自体は VSTO に依存しません。したがって無償版の Visual Studio でも利用できます。 []
  6. 正常終了の場合は 0 を呼びます。そうでない場合は使用されたデバイスの解放が R プロセス側で行われません。デバイスとして .NET Framework で作成したクラスだけを用いるのなら特に問題はないように思います。 []
  7. Azure でウェブアプリケーションというネタも少し考えたのですが, R でウェブアプリケーションを作るのは今は Shiny が流行な感じなので,その内機会があったらということで。 []