時系列予測データのフロントエンド可視化へのアプローチ
機械学習モデルによる時系列予測の結果を、ユーザーにわかりやすい形で提示することは、データ分析パイプラインの重要な最終ステップです。Granite TimeSeries FlowState R1モデルが出力するJSON形式の予測データを、EChartsを活用してインタラクティブなグラフとして描画する具体的な手法を解説します。
予測データの構造理解
モデルのAPIレスポンスは一般的に以下のような構造を持っています:
{
"status": "success",
"prediction": {
"timestamps": ["2024-05-01", "2024-05-02", "2024-05-03", "2024-05-04", "2024-05-05"],
"mean": [120.5, 125.3, 118.7, 130.2, 128.9],
"lower_bound": [110.2, 115.8, 109.5, 120.1, 119.3],
"upper_bound": [130.8, 134.8, 127.9, 140.3, 138.5]
},
"model_info": {
"name": "granite-timeseries-flowstate-r1",
"window_size": 30
}
}
meanは予測の期待値、lower_boundとupper_boundは信頼区間の境界値を表します。この不確実性を視覚的に示すことが、信頼できる予測結果の提示には不可欠です。
ECharts環境のセットアップ
HTMLドキュメントにEChartsを組み込みます。CDN経由でライブラリを読み込み、チャート描画用のコンテナを用意します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>時系列予測ダッシュボード</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
#viz-container {
width: 100%;
height: 550px;
margin: 20px auto;
}
</style>
</head>
<body>
<div id="viz-container"></div>
<script src="app.js"></script>
</body>
</html>
データ取得レイヤーの実装
Fetch APIを使用してバックエンドから予測データを取得します。エラーハンドリングを含めた堅牢な実装を行います。
// app.js
const API_ENDPOINT = '/api/v1/forecast';
async function loadForecastData() {
try {
const response = await fetch(API_ENDPOINT);
if (!response.ok) {
throw new Error(`サーバーエラー: ${response.status}`);
}
const jsonData = await response.json();
if (jsonData.status !== 'success' || !jsonData.prediction) {
throw new Error('データ形式が不正です');
}
return jsonData;
} catch (err) {
console.error('データ読み込み失敗:', err);
displayErrorMessage('予測データの取得に失敗しました。ページを再読み込みしてください。');
return null;
}
}
function displayErrorMessage(message) {
const container = document.getElementById('viz-container');
container.innerHTML = `<div class="error-banner" style="color:#c0392b;padding:20px;text-align:center;">${message}</div>`;
}
データ変換とECharts設定の構築
取得したデータをEChartsが解釈可能な形式に変換します。信頼区間の帯状表示には、スタック領域を活用します。
function buildChartConfiguration(apiResponse) {
const prediction = apiResponse.prediction;
// 予測値ラインの設定
const forecastLine = {
name: '予測値',
type: 'line',
data: prediction.mean,
smooth: true,
symbol: 'circle',
symbolSize: 8,
lineStyle: { width: 3, color: '#2E86AB' },
itemStyle: { color: '#2E86AB' },
z: 10 // 最前面に表示
};
// 信頼区間下限(非表示ライン)
const confidenceLower = {
name: '信頼区間下限',
type: 'line',
data: prediction.lower_bound,
lineStyle: { opacity: 0 },
showSymbol: false,
stack: 'confidence-zone',
itemStyle: { color: 'transparent' }
};
// 信頼区間上限(帯表示用)
const confidenceUpper = {
name: '信頼区間上限',
type: 'line',
data: prediction.upper_bound.map((val, idx) => {
return val - prediction.lower_bound[idx];
}),
lineStyle: { opacity: 0 },
showSymbol: false,
stack: 'confidence-zone',
areaStyle: { color: 'rgba(46, 134, 171, 0.25)' },
itemStyle: { color: 'transparent' }
};
return {
timeAxis: prediction.timestamps,
seriesData: [confidenceLower, confidenceUpper, forecastLine]
};
}
チャートの初期化と描画
設定を適用してチャートを描画し、レスポンシブ対応を追加します。
async function renderVisualization() {
const rawData = await loadForecastData();
if (!rawData) return;
const processedData = buildChartConfiguration(rawData);
const chartElement = document.getElementById('viz-container');
const chartInstance = echarts.init(chartElement);
const chartOption = {
title: {
text: '時系列予測結果',
subtext: `Model: ${rawData.model_info.name}`,
left: 'center',
textStyle: { fontSize: 18 }
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255,255,255,0.95)',
borderColor: '#ddd',
borderWidth: 1,
formatter: function(params) {
let tooltip = `<strong>${params[0].axisValue}</strong><br/>`;
params.forEach(item => {
if (item.seriesName === '予測値') {
tooltip += `${item.marker} 予測値: <b>${item.value}</b><br/>`;
}
});
return tooltip;
}
},
legend: {
data: ['予測値', '信頼区間'],
top: 50,
selected: { '信頼区間下限': false, '信頼区間上限': false }
},
grid: {
left: '5%',
right: '5%',
bottom: '15%',
top: '20%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: processedData.timeAxis,
axisLabel: { rotate: 30 }
},
yAxis: {
type: 'value',
name: '値',
splitLine: { lineStyle: { type: 'dashed' } }
},
dataZoom: [
{ type: 'inside', start: 0, end: 100 },
{ type: 'slider', start: 0, end: 100, bottom: 5 }
],
series: processedData.seriesData,
animation: true,
animationDuration: 800,
animationEasing: 'elasticOut'
};
chartInstance.setOption(chartOption);
// ウィンドウリサイズ対応
window.addEventListener('resize', () => chartInstance.resize());
}
document.addEventListener('DOMContentLoaded', renderVisualization);
機能拡張のポイント
実用性を高めるため、以下の要素を追加で検討できます。
閾値マーキング
const forecastLine = {
// ... 既存設定
markLine: {
data: [
{ yAxis: 125, name: '警告閾値', lineStyle: { color: '#E74C3C', type: 'dashed' } }
],
label: { formatter: '{b}: {c}' }
}
};
複合データ表示
実績値と予測値を同一グラフで比較表示する場合、時系列データを結合してseriesを追加します。
const actualLine = {
name: '実績値',
type: 'line',
data: historicalActuals,
lineStyle: { color: '#27AE60', type: 'dashed' },
itemStyle: { color: '#27AE60' }
};
EChartsの豊富な設定オプションを活用することで、ビジネス要件に応じた柔軟な可視化が可能になります。dataZoomによる期間選択、toolboxによる画像エクスポート機能など、ユーザー体験を向上させる機能を段階的に追加できます。