概要
今回は C++ で作成した数値データをエクセルでグラフ化します。
大数の法則
題材として大数の法則を取り上げます。
サイコロを何回か振って出目の回数をそれぞれ集計して、理論値 16.666… %
に近づく様子をグラフ化してみます。1
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <comdef.h>
#include <iostream>
#include <iomanip>
#include <random>
//Excelを操作するためのタイプライブラリを読みこむ (オフィスのバージョンに依存しない)
//Microsoft Office Object Library
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" no_auto_exclude auto_rename dual_interfaces
//Microsoft Visual Basic for Applications Extensibillity
#import "libid:0002E157-0000-0000-C000-000000000046" dual_interfaces
//Mcrosoft Excel Object Library
#import "libid:00020813-0000-0000-C000-000000000046" no_auto_exclude auto_search auto_rename dual_interfaces
using namespace Excel; //名前空間の定義
struct StartOle {
StartOle() { CoInitialize(NULL); } //COMを初期化
~StartOle() { CoUninitialize(); } //COMを閉じる
} _inst_StartOle;
void dump_com_error(_com_error &e)
{
std::cerr << "Com error!\n";
std::cerr << "\tCode = " << std::setw(8) << std::hex << e.Error() << '\n';
std::cerr << "\tCode meaning = " << e.ErrorMessage() << '\n';
_bstr_t bstrSource(e.Source());
_bstr_t bstrDescription(e.Description());
LPCSTR source = (LPCSTR)bstrSource;
std::cerr << "\tSource = " << (source ? source : "(NULL)") << "\n";
LPCSTR descript = (LPCSTR)bstrDescription;
std::cerr << "\tDescription = " << (descript ? descript : "(NULL)") << "\n";
}
namespace {
const int SMP = 30000000; //サンプル数
const int SEG = 100; //分割数
const int DICE = 6; //出目の数
}
//乱数の平均を生成する
void set_data(_WorksheetPtr &sheet)
{
std::random_device rand_dev; //非決定的な乱数生成器を生成
std::mt19937 mt(rand_dev()); //メルセンヌ・ツイスタの32ビット版、引数は初期シード値
std::uniform_int_distribution<> rand_dice(0, DICE-1);
//[0, 5] 範囲の一様乱数
int sum[DICE] = { 0 }; //1~DICEの出目
double ave[DICE][SEG]; //1~DICEの平均値
for (int x = 0; x < SEG; ++x) { //分割数(SEG)分繰り返し
for (int i = 0; i < (SMP / SEG); ++i) { //サンプル数÷分割数ずつ繰り返し
int r = rand_dice(mt); //サイコロの振る
++sum[r]; //出目を加算する
}
for (int i = 0; i < DICE; ++i)
ave[i][x] = (double)sum[i] / ((SMP / SEG) * (x + 1));
//平均を求める
}
for (int x = 0; x < SEG; ++x) {
RangePtr cell1 = sheet->Cells->Item[x+1][1];
cell1->Value2 = (SMP / SEG) * (x + 1); //サンプル数
for (int i = 0; i < DICE; ++i) {
RangePtr cell2 = sheet->Cells->Item[x+1][i + 2]; //セルを指定
cell2->Value2 = ave[i][x]; //平均値
}
}
}
int main(void)
{
const int WaitSec = 5; //休止秒数
char buff[256];
_ApplicationPtr xl;
//---------------------------------------------------------
//Excelの起動
HRESULT hr = xl.CreateInstance(L"Excel.Application");
if (xl) { //エクセル インスタンスの生成を確認
xl->Visible[0] = VARIANT_TRUE; //表示する
xl->UserControl = VARIANT_TRUE; //ユーザーの操作を可能にする
try { //例外を捕捉
WorkbooksPtr workbooks = xl->Workbooks;
//WorkBookを追加
_WorkbookPtr workbook = workbooks->Add(xlWorksheet);
//ワークシートを追加
_WorksheetPtr worksheet = workbook->ActiveSheet;
//アクティブ・シート
//データ作成
set_data(worksheet);
RangePtr range = worksheet->Range["B1:B100"][vtMissing];
//グラフ化する範囲を指定
//グラフ作成
_ChartPtr chart = workbook->Charts->Add();
//グラフを追加
chart->SetSourceData(range, xlColumns);
//データソースを指定
chart->HasTitle[0] = VARIANT_TRUE; //タイトルあり
bstr_t title{ "大数の法則 (サイコロ 出目の確率)" };
chart->ChartTitle->Caption = title; //タイトル指定
chart->ChartType = xlXYScatterSmoothNoMarkers;
//散布グラフ(平滑線マーカーなし)
chart = chart->Location(xlLocationAsObject, bstr_t("Sheet1"));
//オブジェクトとして埋め込む
chart->HasLegend[0] = VARIANT_TRUE; //凡例あり
chart->Legend->Position = xlLegendPositionBottom;
//凡例位置を下に
char xValues[256] = "=Sheet1!R1C1:R100C1";
//x軸
SeriesPtr series1 = chart->SeriesCollection(1);
//系列1
series1->XValues = variant_t(xValues);
//Xの値
series1->Name = bstr_t("1の目"); //凡例1
series1.Release();
AxisPtr axis1 = chart->Axes(xlCategory, xlPrimary);
//X軸
axis1->MaximumScale = SMP; //最大値
axis1.Release();
AxisPtr axis2 = chart->Axes(xlValue, xlPrimary);
//Y軸
axis2->HasMajorGridlines = VARIANT_TRUE;
//目盛線あり
axis2.Release();
SeriesCollectionPtr collection = chart->SeriesCollection();
//データ系列のコレクション
//系列追加
for (int i = 1; i < DICE; ++i) { //出目のループ
chart->Refresh(); //グラフ更新
SeriesPtr series = collection->NewSeries();
//系列を追加
char yValues[256];
_snprintf_s(yValues, _TRUNCATE, "=Sheet1!R1C%d:R100C%d", i+2, i+2);
series->Values = variant_t(yValues);
//データを指定
series->XValues = variant_t(xValues);
//Xの値
char legend[256];
_snprintf_s(legend, _TRUNCATE, "%dの目", i + 1);
series->Name = bstr_t(legend); //系列名指定
series.Release();
}
//グラフの位置とサイズの調整
ShapePtr shape = worksheet->Shapes->Item(1);
shape->Left = 240.; //左端
shape->Top = 10.; //上端
shape->Width = 640.; //横幅
shape->Height = 480.; //高さ
chart->ChartArea->Copy(); //グラフをコピー
//COMオブジェクトを解放する
collection.Release();
shape.Release();
chart.Release();
range.Release();
worksheet.Release();
//WaitSec秒休止
::Sleep(WaitSec * 1000);
//ウィンドウを最小化
xl->WindowState[0] = xlMinimized;
std::cout << "確認のために一時停止:";
std::cin.getline(buff, sizeof buff);
workbook->Saved[0] = VARIANT_TRUE; //警告メッセージが出ないようにする
workbook->Close(); //ワークシートを閉じる
workbook.Release(); //COMオブジェクトを解放する
workbooks.Release(); //COMオブジェクトを解放する
} catch (_com_error &e) { //COMの例外処理
dump_com_error(e); //エラーログを出力
}
//Excelを終了する
xl->Quit();
xl.Release(); //COMオブジェクトを解放する
} else {
std::cerr << "エクセルを起動できません\n";
}
std::cout << "テストプログラムを終了:";
std::cin.getline(buff, sizeof buff);
return 0;
}
エクセルの起動など以前のプログラムと同様です。
set_data()
でSMP
回サイコロを振って、区分ごとの各目の平均を求めます。求めた平均回数をエクセルのセルに出力します。
//グラフ作成
_ChartPtr chart = workbook->Charts->Add();
chart->SetSourceData(range, xlColumns);
グラフを追加し、データソースを指定します。
最初に一つだけ系列 (1の目) を指定して、後で他の系列 (2~6の目) を追加します。
chart->HasTitle[0] = VARIANT_TRUE;
bstr_t title{ "大数の法則 (サイコロ 出目の確率)" };
chart->ChartTitle->Caption = title;
chart->ChartType = xlXYScatterSmoothNoMarkers;
タイトルやグラフの種類を指定します。
//系列追加
for (int i = 1; i < DICE; ++i) {
chart->Refresh();
SeriesPtr series = collection->NewSeries();
char yValues[256];
_snprintf_s(yValues, _TRUNCATE, "=Sheet1!R1C%d:R100C%d", i+2, i+2);
series->Values = variant_t(yValues);
series->XValues = variant_t(xValues);
char legend[256];
_snprintf_s(legend, _TRUNCATE, "%dの目", i + 1);
series->Name = bstr_t(legend);
series.Release();
}
2~6の目
の系列を追加します。
chart->ChartArea->Copy();
作成したグラフをクリップボードにコピーします。
実行結果
試行回数が多くなると理論値0.1666…
に近づくことがわかります。
開発環境および実行環境
- Windows10
- Visual Studio 2019 Community
- Excel 2013
-
平均の計算方法に間違いがあるようでしたら指摘して下さい。 ↩