JavaScript Tips | 配列ユーティリティ:統計情報生成

JavaScript JavaScript
スポンサーリンク

テーマの整理:「統計情報生成」とは何か

ここでの「統計情報生成」は、配列に入っている数値データから、もう一歩踏み込んだ“統計的な指標”を計算するユーティリティのことです。 サマリー生成が「合計・平均・最大・最小」だとしたら、統計情報生成はそこからさらに、

分散・標準偏差(どれくらいバラついているか) 中央値(極端な値に引っ張られない代表値) パーセンタイル(上位何%のラインか)

などを計算して、「データの性格」をより深く理解するための道具です。

業務では、例えば「売上の安定度」「工数のバラつき」「レスポンス時間の偏り」などを知りたいときに使います。

基本コンセプト:「平均だけでは見えない“バラつき”や“偏り”を数値化する」

なぜ統計情報が必要なのか

平均だけを見ると、こんな落とし穴があります。

売上が「100, 100, 100, 100, 100」でも平均は 100。 売上が「0, 0, 0, 0, 500」でも平均は 100。

でも、前者は「安定している」、後者は「ほぼゼロで、たまにドカン」ですよね。 この違いを表現してくれるのが、分散・標準偏差・中央値などの統計情報です。

統計情報生成ユーティリティは、 「配列の中身が、安定しているのか、バラついているのか、偏っているのか」を数値で教えてくれる」役割を持ちます。

ユーティリティ 1:基本統計情報(平均・分散・標準偏差・中央値)

実装:basicStats

まずは、数値配列に対して「基本的な統計情報」をまとめて計算するユーティリティです。

function basicStats(values) {
  if (!Array.isArray(values) || values.length === 0) {
    return {
      count: 0,
      mean: 0,
      variance: 0,
      stddev: 0,
      median: null,
    };
  }

  const nums = values.filter(
    (v) => typeof v === "number" && !Number.isNaN(v)
  );

  const count = nums.length;
  if (count === 0) {
    return {
      count: 0,
      mean: 0,
      variance: 0,
      stddev: 0,
      median: null,
    };
  }

  const sum = nums.reduce((acc, v) => acc + v, 0);
  const mean = sum / count;

  const sqDiffSum = nums.reduce((acc, v) => {
    const diff = v - mean;
    return acc + diff * diff;
  }, 0);

  const variance = sqDiffSum / count;
  const stddev = Math.sqrt(variance);

  const sorted = [...nums].sort((a, b) => a - b);
  let median;
  if (count % 2 === 1) {
    median = sorted[(count - 1) / 2];
  } else {
    const mid1 = sorted[count / 2 - 1];
    const mid2 = sorted[count / 2];
    median = (mid1 + mid2) / 2;
  }

  return {
    count,
    mean,
    variance,
    stddev,
    median,
  };
}
JavaScript

重要ポイントをかみ砕いて説明

まず、配列かどうか・空かどうかをチェックし、空なら「全部 0・中央値は null」を返します。 values から「数値だけ」を取り出して nums にします(文字列や NaN は無視)。

mean(平均)は、sum / count で計算します。 variance(分散)は、「各値と平均の差」を二乗して足し合わせ、それを count で割っています。 stddev(標準偏差)は、分散の平方根です。

median(中央値)は、ソートした配列から「真ん中の値」を取ります。 要素数が奇数なら真ん中の 1 つ、偶数なら真ん中 2 つの平均です。

これで、「配列の基本的な統計情報」が一発で手に入ります。

例題 1:安定している配列とバラついている配列を比べる

const stable = [100, 100, 100, 100, 100];
const unstable = [0, 0, 0, 0, 500];

console.log("stable:", basicStats(stable));
console.log("unstable:", basicStats(unstable));
JavaScript

イメージとしては、こうなります。

stable の結果は、 mean ≒ 100 stddev ≒ 0(ほぼバラつきなし)

unstable の結果は、 mean ≒ 100 stddev がかなり大きい(バラつきが大きい)

ここでのポイントは、「平均は同じでも、標準偏差が全然違う」というところです。 これによって、「このデータは安定しているのか、たまにドカンなのか」が分かります。

ユーティリティ 2:オブジェクト配列から「特定フィールドの統計情報」を作る

シナリオ

売上配列から「amount の統計情報」を作りたい。 レスポンス時間配列から「duration の統計情報」を作りたい。

実装:fieldStats

function fieldStats(records, field) {
  if (!Array.isArray(records) || records.length === 0) {
    return basicStats([]);
  }

  const values = records
    .map((r) => r[field])
    .filter((v) => typeof v === "number" && !Number.isNaN(v));

  return basicStats(values);
}
JavaScript

重要ポイントをかみ砕いて説明

records が配列でない・空配列なら basicStats([]) を返します。 指定フィールド field の値だけを取り出し、数値でないものや NaN をフィルタしています。 最後は basicStats に渡して、統計情報を作っています。

これで、「オブジェクト配列の特定フィールドに対する統計情報」が簡単に作れます。

例題 2:売上配列から amount の統計情報を作る

const sales = [
  { id: 1, store: "A", amount: 100 },
  { id: 2, store: "A", amount: 200 },
  { id: 3, store: "B", amount: 150 },
];

const amountStats = fieldStats(sales, "amount");

console.log(amountStats);
JavaScript

出力イメージはこうなります。

{
  count: 3,
  mean: 150,
  variance: 1666.666...,
  stddev: 40.82...,
  median: 150
}
JavaScript

ここでのポイントは、

平均 150 中央値 150 標準偏差はそこそこ(100〜200 の範囲でほどほどにバラついている)

というところです。

ユーティリティ 3:パーセンタイル(上位何%のラインか)を計算する

シナリオ

レスポンス時間の配列から「90 パーセンタイル(P90)」を知りたい。 「この値以下なら、全体の 90%が収まっている」というラインを知りたい。

実装:percentile

function percentile(values, p) {
  if (!Array.isArray(values) || values.length === 0) {
    return null;
  }

  const nums = values
    .filter((v) => typeof v === "number" && !Number.isNaN(v))
    .sort((a, b) => a - b);

  const count = nums.length;
  if (count === 0) {
    return null;
  }

  const clampedP = Math.min(Math.max(p, 0), 100);
  const rank = (clampedP / 100) * (count - 1);
  const lowerIndex = Math.floor(rank);
  const upperIndex = Math.ceil(rank);

  if (lowerIndex === upperIndex) {
    return nums[lowerIndex];
  }

  const weight = rank - lowerIndex;
  return nums[lowerIndex] * (1 - weight) + nums[upperIndex] * weight;
}
JavaScript

重要ポイントをかみ砕いて説明

values から数値だけを取り出し、昇順にソートします。 p は 0〜100 のパーセンテージで、範囲外なら 0〜100 に丸めます。 rank は「何番目の位置か」を小数で表したものです。 lowerIndex と upperIndex の間を線形補間して、パーセンタイル値を求めています。

これで、「P50(中央値)」「P90」「P95」などを柔軟に計算できます。

例題 3:レスポンス時間の P90 を計算する

const responseTimes = [100, 120, 130, 200, 300, 400, 500];

const p90 = percentile(responseTimes, 90);
const p50 = percentile(responseTimes, 50);

console.log("P50:", p50);
console.log("P90:", p90);
JavaScript

イメージとしては、

P50 は「真ん中あたりの値」 P90 は「全体の 90%がこの値以下に収まるライン」

になります。

ここでのポイントは、「平均ではなく、パーセンタイルで“ほとんどのユーザーがどのくらいの値を見ているか”を知る」ことです。 レスポンス時間や処理時間の評価では、平均よりも P90・P95 がよく使われます。

ユーティリティ 4:統計情報生成を「業務サマリー」に組み込む

シナリオ

売上ダッシュボードで、「合計・平均」に加えて「標準偏差・中央値」も出したい。 レスポンス監視画面で、「平均・P90・P95」を出したい。

統計情報をまとめたオブジェクトを返す

例えば、売上配列に対してこんなユーティリティを作れます。

function salesStatsSummary(sales) {
  const amountStats = fieldStats(sales, "amount");
  const amounts = sales
    .map((s) => s.amount)
    .filter((v) => typeof v === "number" && !Number.isNaN(v));

  const p90 = percentile(amounts, 90);
  const p50 = percentile(amounts, 50);

  return {
    amount: amountStats,
    p50,
    p90,
  };
}
JavaScript

使い方はこうです。

const sales = [
  { id: 1, store: "A", amount: 100 },
  { id: 2, store: "A", amount: 200 },
  { id: 3, store: "B", amount: 150 },
];

console.log(salesStatsSummary(sales));
JavaScript

ここでのポイントは、

「統計情報生成ユーティリティ」を組み合わせて、 業務でそのまま使える「統計サマリーオブジェクト」を作っていることです。

統計情報生成ユーティリティで意識してほしい重要ポイント

「平均だけでは足りない」という感覚を持つ

統計情報生成の一番大事なポイントは、 「平均だけではデータの性格は分からない」という感覚を持つことです。

標準偏差で「バラつき」を見る。 中央値で「極端な値に引っ張られない代表値」を見る。 パーセンタイルで「ほとんどのケースがどこに収まっているか」を見る。

初心者のうちは、

まずは basicStats の mean・stddev・median 次に percentile の P90・P95

くらいから触ってみると、 「データの見え方」が一気に変わります。

「統計情報は“判断の材料”になる」

統計情報生成ユーティリティは、 単なる数字遊びではなく、「業務の判断の材料」を作るためのものです。

売上が安定しているか。 工数が偏っていないか。 レスポンスが一部のユーザーだけ極端に遅くなっていないか。

こういう問いに答えるために、 統計情報を配列から自動で生成しておくと、 「感覚」ではなく「数字」で話ができるようになります。

手を動かして「統計情報生成」の感覚をつかむ

次のコードを実行して、

基本統計情報 フィールド統計情報 パーセンタイル

を一気に体感してみてください。

function basicStats(values) {
  if (!Array.isArray(values) || values.length === 0) {
    return { count: 0, mean: 0, variance: 0, stddev: 0, median: null };
  }

  const nums = values.filter(
    (v) => typeof v === "number" && !Number.isNaN(v)
  );
  const count = nums.length;
  if (count === 0) {
    return { count: 0, mean: 0, variance: 0, stddev: 0, median: null };
  }

  const sum = nums.reduce((acc, v) => acc + v, 0);
  const mean = sum / count;

  const sqDiffSum = nums.reduce((acc, v) => {
    const diff = v - mean;
    return acc + diff * diff;
  }, 0);

  const variance = sqDiffSum / count;
  const stddev = Math.sqrt(variance);

  const sorted = [...nums].sort((a, b) => a - b);
  let median;
  if (count % 2 === 1) {
    median = sorted[(count - 1) / 2];
  } else {
    const mid1 = sorted[count / 2 - 1];
    const mid2 = sorted[count / 2];
    median = (mid1 + mid2) / 2;
  }

  return { count, mean, variance, stddev, median };
}

function fieldStats(records, field) {
  if (!Array.isArray(records) || records.length === 0) {
    return basicStats([]);
  }
  const values = records
    .map((r) => r[field])
    .filter((v) => typeof v === "number" && !Number.isNaN(v));
  return basicStats(values);
}

function percentile(values, p) {
  if (!Array.isArray(values) || values.length === 0) {
    return null;
  }

  const nums = values
    .filter((v) => typeof v === "number" && !Number.isNaN(v))
    .sort((a, b) => a - b);

  const count = nums.length;
  if (count === 0) {
    return null;
  }

  const clampedP = Math.min(Math.max(p, 0), 100);
  const rank = (clampedP / 100) * (count - 1);
  const lowerIndex = Math.floor(rank);
  const upperIndex = Math.ceil(rank);

  if (lowerIndex === upperIndex) {
    return nums[lowerIndex];
  }

  const weight = rank - lowerIndex;
  return nums[lowerIndex] * (1 - weight) + nums[upperIndex] * weight;
}

function demo() {
  const sales = [
    { id: 1, store: "A", amount: 100 },
    { id: 2, store: "A", amount: 200 },
    { id: 3, store: "B", amount: 150 },
  ];

  console.log("=== basicStats (amounts) ===");
  console.log(basicStats(sales.map((s) => s.amount)));

  console.log("=== fieldStats (amount) ===");
  console.log(fieldStats(sales, "amount"));

  const responseTimes = [100, 120, 130, 200, 300, 400, 500];
  console.log("=== percentile (P50, P90) ===");
  console.log("P50:", percentile(responseTimes, 50));
  console.log("P90:", percentile(responseTimes, 90));
}

demo();
JavaScript

まとめ:統計情報生成ユーティリティで「配列の性格を、数字で語れるようにする」

統計情報生成は、 「配列の性格(安定・バラつき・偏り)を、平均だけでなく統計指標で語れるようにするためのユーティリティ」です。

プロジェクトに例えば次のような形で置いておくイメージです。

export function basicStats(values) { ... }
export function fieldStats(records, field) { ... }
export function percentile(values, p) { ... }
JavaScript

そして、

ダッシュボード、 監視画面、 レポート、

など、「データの質を評価したい場面」で必ずこの統計情報生成を通す。

こう決めておくと、 「なんとなく」ではなく「数字で」データを語れるようになり、 業務の判断が一段クリアになります。

平均だけじゃ、もったいない。 統計情報で、配列の“性格”まで見に行きましょう。

タイトルとURLをコピーしました