C++ matplotlib-cppでtrend chartを書く方法
データの可視化は情報を整理したり、わかりやすく結果を示すときによく使います。グラフの種類は、トレンドチャートやバーチャート、モザイクチャートやヒートマップ、データテーブルなど様々あり、適切な表現で示すことで伝えたいことを100%伝えることができます。今回はC++のmatplotlib-cppを使ったGraphのつくり方をまとめました。
matplotlib-cppとは
ドイツ ハンブルクのソフトウェア企業TenzirのBenno Eversが2014年にGitに公開したC++ Plotライブラリ。Python Matlabとmatplotlibを参考に作られており、使い方はmatplotlibに似ている。
Tenzir Home Page
matplotlib-cpp Github
matplotlib-cpp documentation
環境設定
- OS : Windows10(2021-)
- Editor : VS code(version 1.58.0)
- C++ compiler : MinGW w64bit(gcc version 8.1.0)
- Python : Python39(v3.9.6)
- make : v3.81
初期設定
pipでpython moduleをinstallする。
各環境のProxyサーバーを指定する。
pip --proxy=http://**********.com:80 install --trusted-host file.pythonhosted.org numpy
pip --proxy=http://**********.com:80 install --trusted-host file.pythonhosted.org matplotlib
MinGW w64bitをinstallする。
choco install mingw
もしくは
mingw download page
詳細は以下の記事参考。
https://qiita.com/ryo-sato/items/00c17469978e47d91a09
buildするのにmakeを使用するため、以下のサイトからmakeのinstallerをダウンロードする。
"Complete package, except sources"の横の"Setup"を押す。
まずはデータをloadする
getlineで行毎に読み込み、for文で以下のように列毎に分け、データをpair vectorに代入する。
今回のデータセットの1列目に日付情報、2列目以降に数字・数値問わない縦軸となるデータがあることを前提にデータを読み込んでいます。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <cmath>
#include <ctime>
#include <math.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstdio>
#include <time.h>
#include <numeric>
#include <unistd.h>
#include <cctype>
#include <direct.h>
#include "Python.h"
#include "datetime.h"
#include "matplotlibcpp.h"
namespace plt = matplotlibcpp;
while (getline(ifs_csv_file, str_buf)) {
std::istringstream i_stream(str_buf);
std::string i_stream_tmp = i_stream.str();
long long row_n = std::count(i_stream_tmp.cbegin(), i_stream_tmp.cend(), ',') ;
while (getline(i_stream, str_line_buf, '\n')) {
for (long long i = 0; i <= row_n; i++) {
tokun = str_line_buf.substr(0, str_line_buf.find(delimiter));
vvec.push_back(tokun);
str_line_buf.erase(0, str_line_buf.find(delimiter) + delimiter.length());
}
//After second Row reading
if (nn != 0 & !(vvec[1].empty())) {
time_t t = string_to_time_t(vvec[1]);
char longdate[30];
strftime(longdate, 20, "%Y%m%d%H%M%S", localtime(&t));
tvec.push_back(t);
for (long long k = 2; k <= n; k++) {
if (!(vvec[k].empty()) && isdigit(vvec[k][0])){
double ytmp = stod(vvec[k]);
TY[k-1].push_back(std::make_pair(t,ytmp));
ty.push_back(std::make_pair(t,ytmp));
}
}
//Initial Row reading
}else if (nn == 0){
for (long long k = 0; k <= n; k++) {
label_vec.push_back(vvec[k]);
}
}
nn =+ 1;
vvec.clear();
}
}
時間の軸ラベルを生成する
matplotlibの横軸のラベルベクトルを作成する。
今回は横軸を日付にしたいので、実際の横軸の秒値を一日毎に数えて対応する日付を文字で入れている。
//横軸時間のベクトルの重複を消し、日付若い順にソートする。
sort(tvec.begin(), tvec.end());
//x-axis Min Value
time_t t_min1 = tvec[0];
char ltmp_min[20];
strftime(ltmp_min, 20, "%Y/%m/%d 00:00", localtime(&t_min1));
std::string Ltmp_min = ltmp_min;
time_t Lttmp_min = string_to_time_t(Ltmp_min);
time_t t_min = Lttmp_min;
//x-axis Max Value
size_t tlen;
tlen = tvec.size();
time_t t_max = tvec[tlen-1];
vector<pair<string,long long>> Ltx = time_t_to_date(t_min,t_max);
//横軸のラベルに使う値と日付文字ベクトルを作成。
for (auto& i: Ltx) {
Label_strdate_row.push_back(i.first);
Label_seconddate_row.push_back(i.second);
}
最後にmatplotlib形式でGraphを書く。
std::vector<double> ycounts(Lx_row.size()), y_sum(Lx_row.size()), y_ave(Lx_row.size());
for (long long j = 0; j < lvec.size(); j++) {
ycounts.resize(Lx_row.size());
y_sum.resize(Lx_row.size());
y_ave.resize(Lx_row.size());
sort(TY[j].begin(), TY[j].end());
for (auto& i: TY[j]) {
time_hour = (long long)i.first;
t.push_back(i.first);
y.push_back(i.second);
y_sum[(i.first-Lt_row[0])/86414] = y_sum[(i.first-Lt_row[0])/86414] + i.second;
ycounts[(i.first-Lt_row[0])/86414] = ycounts[(i.first-Lt_row[0])/86414] + 1;
}
for (long long k = 0; k < Lx_row.size(); k++) {
y_ave[k] = y_sum[k]/ycounts[k];
}
//matplotlib pltでグラフ作成
plt::figure_size(600, 300);
plt::plot(Lt, y_ave, "r-o");
plt::grid("true");
plt::ylabel("");
plt::xlabel("");
std::map<string,string> m;
m["rotation"] = "vertical";
m["fontsize"] = "small";
m["fontstyle"] = "normal";
m["fontstretch"] = "normal";
plt::xticks(Lt_row,Lx_row,m);
plt::axis("tight");//on,off,equal,scaled,tight,auto,image,square
plt::title(lvec[k]);
_mkdir("./image");
std::string out_image_name = "./image/image_" + to_string(k);
plt::tight_layout();
plt::save(out_image_name);
plt::close();
t.clear();
y.clear();
y_sum.clear();
y_ave.clear();
ycounts.clear();
k = k + 1;
}
makeでコンパイル
main.cppと同じフォルダにMakefileを以下のように作成し、makeする。
CC = g++
CFLAGS = -IC:/matplotlib-cpp \
-IC:/Python39/include \
-IC:/Python39/Lib/site-packages/numpy/core/include \
-IC:/Python39/Lib/site-packages/numpy/core/include/numpy
LDFLAGS = -LC:/Python39/libs
LIBS = -lpython39
OBJS = main.cpp
PROGRAM = output
all: $(PROGRAM)
$(PROGRAM): $(OBJS)
$(CC) -o $(PROGRAM) $(OBJS) $(CFLAGS) $(LDFLAGS) $(LIBS)
するとoutput.exeができる。
できたプログラムでトレンドチャートを作るとこんなのが書ける。
乱数で作った以下のテストデータでトレンドチャートを書いてみよう。
datetime | Para1 | Para2 |
---|---|---|
2021/5/1 0:00 | 0.01 | 0.85 |
2021/5/1 1:00 | 0.54 | 0.70 |
2021/5/1 2:00 | 0.09 | 0.00 |
2021/5/1 3:00 | 0.74 | 0.06 |
2021/5/1 4:00 | 0.50 | 0.03 |
2021/5/1 5:00 | 0.95 | 0.49 |
2021/5/1 6:00 | 0.62 | 0.45 |
2021/5/1 7:00 | 0.55 | 0.98 |
2021/5/1 8:00 | 0.42 | 0.34 |
2021/5/1 8:59 | 0.12 | 0.97 |
出力はこんな感じ。よく見るmatplotlibと同じformatのグラフができる。
まとめ
いかがでしたか。今回はC プロットライブラリ matplotlib-cppでのグラフ作成を紹介しました。
もともとpython matplotlibを使っていた人ならそれなりに理解しやすいのではないかと思います。
実際グラフを書くときは細かい調整をしたりするので、Pythonで作ったほうがいいですね。。。
大量にグラフを作る必要があるときは、C言語なので処理が速く、まぁ一応メリットはあるかなという感じです。
以上、ありがとうございました。