0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【D3.js】IPA・JUAS・総務省の公開データで日本11業種のAI導入格差を可視化してみた——5指標複合スコアとバブルチャート実装手順

0
Posted at

ai-job-impact_eyecatch-1024x659.jpg

この記事でできること

  • IPA・JUAS・総務省の公開統計データを使った業種別AI導入格差の定量化手順を理解できる
  • 複数の異なる指標を重み付き複合スコアに統合する設計パターンを実装できる
  • D3.js v7でバブルチャート(4次元: X軸・Y軸・バブルサイズ・バブル色)を実装できる
  • WordPress(SWELLテーマ)への埋め込みで詰まる点を把握できる

実物のツールは業界別AI活用度ランキング(AI Japan Index)で確認できる。

環境・前提

  • D3.js: v7.9.0
  • JavaScript: ES2020+(Vanilla JS、フレームワーク不使用)
  • 動作確認ブラウザ: Chrome 122+、Firefox 123+、Safari 17+、Edge 122+
  • ホスティング: WordPress 6.4 + SWELLテーマ 2.6.x(インラインHTML埋め込み)
  • データソース: IPA「DX動向2025」(2025年6月公表)、JUAS「企業IT動向調査2025」(2025年4月公表)、総務省「通信利用動向調査(令和6年)」(2025年5月公表)

完成形

金融・保険業のAI導入ギャップスコア(100点満点)が最も高く、サービス業・建設業が下位に位置する11業種のバブルチャート。X軸に生成AI導入率、Y軸にDX取組率、バブルサイズに従業者数、バブル色にAI導入ギャップスコアをマッピングした4次元可視化だ。業種クリックでレーダーチャートによる5指標詳細を表示できる。

動作確認: https://ai-japan-index.com/ai-gap-watcher/


手順

Step 1: データの収集と整形

IPA・JUAS・総務省の各報告書(PDF)から業種別の数値を手動で抽出する。単位や調査方法が異なるため、0〜100の共通スケールに正規化する必要がある。

「このコードの意味」: 各指標のレンジが異なるため(例: DX取組率は60〜97%、人材充足度は5〜30%等)、そのまま加算するとレンジの広い指標が結果を支配してしまう。正規化で全指標を0〜100に揃えてから重み付け加算する。

// 11業種の生データ(IPAとJUASの報告書から手動入力)
const RAW_DATA = [
  {
    id: "finance-insurance",
    name: "金融・保険",
    employeeCount: 1700000,
    raw: {
      dxAdoptionRate: 97.2,      // IPA DX動向2024(%)
      genAiAdoptionRate: 54.0,   // JUAS 2025(%)
      aiInvestmentIntent: 78.5,  // 総務省 通信利用動向調査(%)
      dxTalentSufficiency: 22.3, // IPA DX動向2025(%)
      effectRealizationRate: 68.4 // JUAS 2025(%)
    }
  },
  // ... 他10業種
];

// 全業種の値からmin/maxを取得して0〜100に正規化
function normalizeAllIndustries(data) {
  const indicators = [
    "dxAdoptionRate",
    "genAiAdoptionRate",
    "aiInvestmentIntent",
    "dxTalentSufficiency",
    "effectRealizationRate"
  ];

  const minMax = {};
  indicators.forEach(key => {
    const values = data.map(d => d.raw[key]);
    minMax[key] = { min: Math.min(...values), max: Math.max(...values) };
  });

  return data.map(d => ({
    ...d,
    scores: Object.fromEntries(
      indicators.map(key => {
        const { min, max } = minMax[key];
        const normalized = (max === min) ? 50 : (d.raw[key] - min) / (max - min) * 100;
        return [key, Math.round(normalized * 10) / 10];
      })
    )
  }));
}

つまずきポイント: PDFからの数値抽出は手動作業が必要で、各報告書で「業種の分類名」が微妙に異なる(IPA: 「製造(加工組立型)」/ JUAS: 「組立・加工型製造業」)。統一した内部IDを決めてからマッピングテーブルを作るとトラブルを避けられる。


Step 2: 複合スコアの計算

「このコードの意味」: 5つの指標を1つのスコアにまとめることで「総合的なAI活用水準」を比較できるようにする。重みは「何が最もAI活用の現状を直接示すか」の判断で決定した(生成AI導入率30%が最大)。

function calcCompositeScore(scores) {
  return Math.round((
    scores.genAiAdoptionRate    * 0.30 +
    scores.dxAdoptionRate       * 0.25 +
    scores.aiInvestmentIntent   * 0.15 +
    scores.dxTalentSufficiency  * 0.15 +
    scores.effectRealizationRate * 0.15
  ) * 10) / 10;
}

// 全業種にcompositeを追加
const processedData = normalizeAllIndustries(RAW_DATA).map(d => ({
  ...d,
  scores: {
    ...d.scores,
    composite: calcCompositeScore(d.scores)
  }
}));

Step 3: D3.jsバブルチャートの実装

「このコードの意味」: D3.jsでバブルチャートを作る。4つの次元(X・Y・サイズ・色)それぞれにスケールを定義し、データをSVG要素にバインドする。バブルが端で切れないようにposition paddingを入れる点が重要。

function renderBubbleChart(data) {
  const container = document.getElementById("bubble-chart");
  const width = container.clientWidth;
  const height = Math.min(width * 0.75, 400);
  const margin = { top: 30, right: 20, bottom: 50, left: 50 };
  const innerW = width - margin.left - margin.right;
  const innerH = height - margin.top - margin.bottom;
  const mobile = width < 500;
  const maxR = mobile ? 18 : 32;

  const svg = d3.select(container).append("svg")
    .attr("width", width)
    .attr("height", height);
  const g = svg.append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`);

  // スケール定義(position padding: 半径分の余白を両端に確保)
  const xScale = d3.scaleLinear().domain([0, 100]).range([maxR, innerW - maxR]);
  const yScale = d3.scaleLinear().domain([0, 100]).range([innerH - maxR, maxR]);
  const rScale = d3.scaleSqrt()
    .domain([0, d3.max(data, d => d.employeeCount)])
    .range([mobile ? 7 : 10, maxR]);
  const colorScale = d3.scaleLinear()
    .domain([30, 55, 80])
    .range(["#e74c3c", "#f39c12", "#2ecc71"]);

  // バブル描画
  g.selectAll("circle.bubble")
    .data(data)
    .join("circle")
    .attr("class", "bubble")
    .attr("cx", d => xScale(d.scores.genAiAdoptionRate))
    .attr("cy", d => yScale(d.scores.dxAdoptionRate))
    .attr("r", d => rScale(d.employeeCount))
    .attr("fill", d => colorScale(d.scores.composite))
    .attr("fill-opacity", 0.8)
    .attr("stroke", "#e0e0e0")
    .attr("stroke-width", 1)
    .attr("cursor", "pointer")
    .on("touchstart", function(event, d) {
      event.preventDefault();
      event.stopPropagation();  // 省略するとパネルが即閉じする
      showMobilePanel(d);
    })
    .on("click", function(event, d) {
      if (!mobile) showDetailPanel(d);
    });

  // 軸: スマホはX軸を3本に間引く
  const xTicks = mobile ? [0, 50, 100] : [0, 20, 40, 60, 80, 100];
  g.append("g")
    .attr("transform", `translate(0, ${innerH})`)
    .call(d3.axisBottom(xScale).tickValues(xTicks));

  // Y軸ラベル: スマホでは非表示
  if (!mobile) {
    g.append("g").call(d3.axisLeft(yScale));
  } else {
    g.append("g").call(d3.axisLeft(yScale).tickFormat(""));
  }
}

NG例 → OK例:

// NG: バブルが端で切れる
const xScale = d3.scaleLinear().domain([0, 100]).range([0, innerW]);

// OK: position paddingで半径分の余白を確保
const xScale = d3.scaleLinear().domain([0, 100]).range([maxR, innerW - maxR]);

Step 4: WordPress埋め込み対応

「このコードの意味」: WordPressのコンテンツフィルターが&&をHTMLエンティティ変換する問題と、投稿内CDN <script src> が無視される問題の2つを回避する必要がある。

// NG: WordPressが && を &#038;&#038; に変換してJS構文エラー
if (width < 500 && data) { ... }

// OK: 三項演算子で回避
const valid = (width < 500) ? (data ? true : false) : false;

// NG: SWELLは投稿コンテンツ内のCDN script srcを無視する
// <script src="https://cdn.jsdelivr.net/npm/d3@7/..."></script>

// OK: 動的ロード(D3ブートストラップ)
(function() {
  if (typeof d3 !== "undefined") { initChart(); return; }
  const s = document.createElement("script");
  s.src = "https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js";
  s.onload = initChart;
  document.head.appendChild(s);
})();

Step N: 動作確認

  1. 11業種それぞれのバブルが正しい位置に表示されているか確認(特にスコアが低い業種のバブルが画面端でクリップされていないか)
  2. バブルをクリック/タップで詳細パネルが表示されるか確認
  3. スマホサイズ(767px以下)でY軸ラベルが非表示になっているか確認
  4. && を使っていないかスクリプト全体をgrepで確認(WordPress環境でのデバッグより事前チェックが効率的)

つまずきポイントまとめ

  • IPA・JUASで業種分類名が違う: 同じ業種でも報告書ごとに名称が異なる。内部IDを先に決めてマッピングテーブルを作成してから数値を入力すること
  • 正規化の順序が重要: 「全業種のmin/maxで正規化」→「複合スコア計算」の順番でないと、追加業種があった時にスコアが変わってしまう。全データがそろってから正規化すること
  • バブルが端でクリップされる: range([0, innerW]) だとバブルの中心が端になった時に半分見切れる。range([maxR, innerW - maxR]) でpadding必須
  • && 演算子がWordPressで壊れる: 投稿後にJSエラーが出て全チャートが動かなくなる典型的なWordPress罠。三項演算子かネストifに置き換える
  • PDF数値の手動入力ミス: IPAのPDFはグラフベースで数値が読みにくい場合がある。複数回確認するか、公表のデータ集(.xlsxや.csv)が別途配布されている場合はそちらを使う。IPA DX動向2025はデータ集PDFが別途公開されている
  • バブルタップで「パネルが開いて即閉じる」: touchstart ハンドラに e.stopPropagation() が抜けているとdocumentのタッチハンドラが「パネル外タップ」と誤判定する。e.preventDefault() と e.stopPropagation() を必ずセットで書く

まとめ

今回学んだこと:

  • 複数指標の統合には「正規化→重み付け加算→複合スコア」の手順が基本
  • D3.jsのバブルチャートはposition padding(range([maxR, width - maxR]))が視認性の要
  • WordPress/SWELLでは&&禁止とCDN動的ロードが必須
  • touchstartには必ずe.stopPropagation()をセットで書く
  • 公開統計PDFの業種分類は報告書ごとに違うため、内部IDとマッピングテーブルを最初に設計する

関連ツール一覧: AI Japan Index — 日本のAI関連データを無料で可視化

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?