ChartPress は、 CSV / TSV を 投げ込む だけ で 列 ごと の 型 ・ 要約統計 ・ ヒストグラム ・ 相関 ヒートマップ を 一気 に 出す ダッシュボード。 パーサ も 統計 も 描画 も 素 の JavaScript で 書いて あり、 データ は ブラウザ の 外 に 出ません。
#🎯 「Excel を 開く 前 の 5 分」 を 埋める
営業会議 で 渡さ れる CSV、 役所 の オープンデータ、 アンケート の 集計結果。 これら を 受け取った とき に 最初 に やる の は 分析 では なく、 「何 列 あって、 どれ が 数値 で、 欠損 が どれ くらい か」 を 掴む こと です。
表計算 ソフト で やる と 列 を 一つ ずつ 見る 羽目 に なる し、 Python を 出す ほど でも ない。 その 隙間 を 埋める の が 狙い でした。 だから 操作 は 「投げる」 の 一手 だけ に して、 設定 画面 を 作ら ない と 決め ました。
社外 の データ を 扱う 以上、 アップ ロード 先 が ある 時点 で 使え ない 場面 が あり ます。 端末 内 で 完結 する の は 演出 では なく 前提 条件 でした。
#🏗️ CSV パーサ を 自前 で 書く
ライブラリ を 入れ ず に 書き ました。 CSV は 一見 「カンマ で split」 です が、 それ だ と 引用符 の 中 の カンマ で 壊れ ます。
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 割 ルール に しました。
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 / 最小 / 最大 / 標準偏差 / 合計 を 出し ます。 四分位 は ソート 済み 配列 から の 線形補間。
ヒストグラム は 最小 と 最大 の 間 を 等分 する 単純 な 方式 で、 端 の 値 が こぼれ ない よう に 最終 ビン へ 寄せて います。
let idx = Math.floor((v - min) / step)
if (idx >= binCount) idx = binCount - 1max そのもの が idx === binCount に なる 境界 で、 これ を 入れ 忘れる と 最大値 だけ が どの ビン に も 入ら なく なり ます。
相関 は 数値列 の 総当たり で Pearson を 計算 して ヒートマップ に。 列 が 増える と 組み合わせ は 二乗 で 増え ます が、 100 行 数十列 の 即席 分析 が 想定 な ので、 総当たり の まま で 十分 でした。
#😓 苦労した ところ
相関 を 出す と 因果 に 見えて しまう。 ヒートマップ は 分かり やすい ぶん、 赤い マス を 見た 人 が 「A が B の 原因」 と 読み がち です。 数字 と 一緒 に 「相関 で あって 因果 では ない」 を 画面 に 置き、 色 も 過度 に 煽ら ない 段階 に 抑え ました。
サンプル データ を 何 に する か。 空 の 画面 に 「CSV を 入れて ください」 と だけ 出て いる と、 何 が 起きる の か 分から ず に 離脱 され ます。 店舗別売上 ・ 東京 の 気温 ・ クラス の 成績 ・ マラソン の 心拍 ・ 23 区 の 家賃 ・ ツール 利用 調査 の 6 種 を 同梱 し、 1 クリック で 全部 の 機能 が 動く 状態 から 始まる ように しました。 型推定 が 4 種類 とも 出る よう、 数値 ・ 日付 ・ カテゴリ ・ テキスト が 混ざる 構成 に して あり ます。
大きい ファイル。 数万 行 を 投げる と 型推定 の 全走査 で 一瞬 固まり ます。 現状 は 想定 規模 を 明記 する ところ で 止めて いて、 分割 処理 は 入れて いません。
#🔭 今後 の 拡張
- 大きい ファイル の 逐次 パース (Web Worker + ストリーム)
- 散布図 と 箱ひげ図 (相関 の 中身 を 見る に は ヒートマップ だけ で は 足り ない)
- 列 の 型 を 手動 で 上書き する 指定
- 結果 の PNG / PDF 書き出し
#💡 この サービス から 言える こと
「自動 で 判定 する」 機能 は、 閾値 を どこ に 置く か が ほぼ すべて でした。 8 割 と いう 数字 は 理論 から 出た もの では なく、 実際 の CSV を 何本 か 通して 決め た もの です。 こう いう 数字 は 後 から 触れる よう コード の 一箇所 に 置いて おく の が 正解 でした。
もう 一つ は、 入力 を 求める ツール は 空 の 状態 が 一番 難しい と いう こと。 サンプル を 同梱 する か どう か で、 同じ 機能 でも 伝わり 方 が まったく 変わり ます。
[ ./next_action ]
読んだら、 ChartPress を実際に動かす。
この開発ログは ChartPress をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。