▍AI Dev Lab
ChartPress

ChartPress ができるまで — CSV を投げるだけの統計ダッシュボード

CSV ダッシュボードの開発ログ。 引用符と BOM に対応した自前の CSV パーサ、 8 割ルールで列の型を決める推定、 要約統計・ヒストグラム・Pearson 相関ヒートマップの実装を記録しています。

ChartPress は、 CSV / TSV を 投げ込む だけ で 列 ごと の 型 ・ 要約統計 ・ ヒストグラム ・ 相関 ヒートマップ を 一気 に 出す ダッシュボード。 パーサ も 統計 も 描画 も 素 の JavaScript で 書いて あり、 データ は ブラウザ の 外 に 出ません。

#🎯 「Excel を 開く 前 の 5 分」 を 埋める

営業会議 で 渡さ れる CSV、 役所 の オープンデータ、 アンケート の 集計結果。 これら を 受け取った とき に 最初 に やる の は 分析 では なく、 「何 列 あって、 どれ が 数値 で、 欠損 が どれ くらい か」 を 掴む こと です。

表計算 ソフト で やる と 列 を 一つ ずつ 見る 羽目 に なる し、 Python を 出す ほど でも ない。 その 隙間 を 埋める の が 狙い でした。 だから 操作 は 「投げる」 の 一手 だけ に して、 設定 画面 を 作ら ない と 決め ました。

社外 の データ を 扱う 以上、 アップ ロード 先 が ある 時点 で 使え ない 場面 が あり ます。 端末 内 で 完結 する の は 演出 では なく 前提 条件 でした。

#🏗️ CSV パーサ を 自前 で 書く

ライブラリ を 入れ ず に 書き ました。 CSV は 一見 「カンマ で split」 です が、 それ だ と 引用符 の 中 の カンマ で 壊れ ます。

ts
if (inQuotes) {
  if (ch === '"') {
    if (src[i + 1] === '"') { cell += '"'; i += 2; continue }  // "" は エスケープ

    inQuotes = false; i++; continue
  }
  cell += ch; i++; continue
}

状態 は inQuotes 一つ だけ。 RFC 4180 の うち 実務 で 効く ところ (引用符 ・ "" エスケープ ・ CRLF ・ BOM 除去 ・ タブ 区切り) に 絞る と、 この 大きさ で 収まり ます。 BOM 付き の Excel 出力 が そのまま 通る か どうか は、 日本 の CSV では 効き ます。

#🔑 列 の 型 を 8 割 で 決める

一番 悩んだ の が 型推定 です。 「1 個 でも 数値 で ない 値 が あれば テキスト」 に すると、 現実 の CSV は ほぼ 全部 テキスト に なり ます。 欠損 や 「N/A」 や 全角 が 必ず 混ざる から です。

そこで 8 割 ルール に しました。

ts
if (numCount / ratio >= 0.8) return "numeric"
if (dateCount / ratio >= 0.8) return "date"
const unique = new Set(nonEmpty).size
if (unique <= 20 && unique / ratio <= 0.5) return "categorical"
return "text"

数値 判定 は カンマ 区切り (1,234)、 末尾 の %、 指数表記 を 落とし込んで から Number() に 通し ます。 日付 は 2024/03/01 と 2024年3月1日 を ISO に 直して から Date へ。

カテゴリ 判定 の 条件 が 二段 に なって いる の が ポイント で、 「種類 が 20 以下」 かつ 「行数 の 半分 以下 の 種類 数」。 前者 だけ だ と 20 行 の データ で 全部 が カテゴリ 扱い に なり、 後者 だけ だ と 1 万 行 の 自由記述 が カテゴリ に なり ます。

#📊 統計 と 相関

数値列 に は 件数 / 欠損 / 平均 / 中央値 / Q1 / Q3 / 最小 / 最大 / 標準偏差 / 合計 を 出し ます。 四分位 は ソート 済み 配列 から の 線形補間。

ヒストグラム は 最小 と 最大 の 間 を 等分 する 単純 な 方式 で、 端 の 値 が こぼれ ない よう に 最終 ビン へ 寄せて います。

ts
let idx = Math.floor((v - min) / step)
if (idx >= binCount) idx = binCount - 1

max そのもの が idx === binCount に なる 境界 で、 これ を 入れ 忘れる と 最大値 だけ が どの ビン に も 入ら なく なり ます。

相関 は 数値列 の 総当たり で Pearson を 計算 して ヒートマップ に。 列 が 増える と 組み合わせ は 二乗 で 増え ます が、 100 行 数十列 の 即席 分析 が 想定 な ので、 総当たり の まま で 十分 でした。

#😓 苦労した ところ

相関 を 出す と 因果 に 見えて しまう。 ヒートマップ は 分かり やすい ぶん、 赤い マス を 見た 人 が 「A が B の 原因」 と 読み がち です。 数字 と 一緒 に 「相関 で あって 因果 では ない」 を 画面 に 置き、 色 も 過度 に 煽ら ない 段階 に 抑え ました。

サンプル データ を 何 に する か。 空 の 画面 に 「CSV を 入れて ください」 と だけ 出て いる と、 何 が 起きる の か 分から ず に 離脱 され ます。 店舗別売上 ・ 東京 の 気温 ・ クラス の 成績 ・ マラソン の 心拍 ・ 23 区 の 家賃 ・ ツール 利用 調査 の 6 種 を 同梱 し、 1 クリック で 全部 の 機能 が 動く 状態 から 始まる ように しました。 型推定 が 4 種類 とも 出る よう、 数値 ・ 日付 ・ カテゴリ ・ テキスト が 混ざる 構成 に して あり ます。

大きい ファイル。 数万 行 を 投げる と 型推定 の 全走査 で 一瞬 固まり ます。 現状 は 想定 規模 を 明記 する ところ で 止めて いて、 分割 処理 は 入れて いません。

#🔭 今後 の 拡張

  • 大きい ファイル の 逐次 パース (Web Worker + ストリーム)
  • 散布図 と 箱ひげ図 (相関 の 中身 を 見る に は ヒートマップ だけ で は 足り ない)
  • 列 の 型 を 手動 で 上書き する 指定
  • 結果 の PNG / PDF 書き出し

#💡 この サービス から 言える こと

「自動 で 判定 する」 機能 は、 閾値 を どこ に 置く か が ほぼ すべて でした。 8 割 と いう 数字 は 理論 から 出た もの では なく、 実際 の CSV を 何本 か 通して 決め た もの です。 こう いう 数字 は 後 から 触れる よう コード の 一箇所 に 置いて おく の が 正解 でした。

もう 一つ は、 入力 を 求める ツール は 空 の 状態 が 一番 難しい と いう こと。 サンプル を 同梱 する か どう か で、 同じ 機能 でも 伝わり 方 が まったく 変わり ます。

[ ./next_action ]

読んだら、 ChartPress を実際に動かす。

この開発ログは ChartPress をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ShadowTwin

立体パズルの別解を作る:同じ影と最少の積み木

ShadowTwinの開発記録。3方向の図から別々の最少配置を2つ探す仕組み、5問の独立全探索、スマホの横並び比較、保存・再編集と入力検査を解説します。実照明の計算や能力診断ではありません。

read log →
SlitCard

スリットアニメーションを作る:4コマの合成と透明原稿の設計

SlitCardの開発記録。4コマの帯を交互に合成し、すき間と黒帯で別のコマを見せる仕組みを解説。同縮尺の台紙・透明シートSVG、50mm確認線、手動プレビュー・初期非表示、再編集と実出力検査を整理します。

read log →
WindowLight

日当たりシミュレーションを作る:窓の光を床へ投影する幾何モデル

WindowLightの開発記録。太陽位置近似から窓の4隅を床へ投影し、凸多角形を部屋で切り取る設計を解説。国立天文台の16時刻との限定照合、大気差・低高度・実際の採光との違い、SVGと設定の再編集を整理します。

read log →
GridLights

電源構成の比較を作る:割合と実量を100個の電球で読み解く

電源構成比較GridLightsの開発記録。12か国の年次発電データを最大剰余法で100個の電球に変換し、割合とTWhを比較する設計を解説。欠損を0補完しない7分類、予想と実値、出典付きSVGと実データ検証を整理します。

read log →
SoundLoom

音のお絵かきを作る:実WAVから絵を測り直す設計

音のお絵かきの開発記録。描画を正弦波のPCMとWAVへ変換し、保存音声の周波数をGoertzel法で測って比較する仕組みを解説。音量・測定窓・量子化の限界と、編集・離脱時の古い結果を抑止する検証を整理します。

read log →
MazeLoom

宝探し迷路を作る:寄り道まで含めた最短ルートの探索

宝探し迷路の開発記録。通路を木として生成し、現在地と3つの宝の取得状態を組み合わせて最短ルートを探す仕組みを解説。問題コードの版固定、画面と解答付きPDFの一致、手数と印刷の検証を整理します。

read log →