JavaScript Tips | 配列ユーティリティ:欠損補完

JavaScript JavaScript
スポンサーリンク

テーマの整理:「欠損補完」とは何か

ここでの「欠損補完」は、配列の中にある「抜けている値・足りないレコード」を、一定のルールで埋めるユーティリティのことです。 業務だと、次のような場面でよく出てきます。

  • 日別データで「ある日だけレコードがない」ので、0 や前日値で埋めたい。
  • 数値配列の中に null や undefined が混ざっていて、計算前に補完したい。
  • 時系列データで「間の時間が抜けている」ので、連続した系列にしたい。

「欠損があるのは現実」で、「それをどう埋めるか」が欠損補完の仕事です。

基本コンセプト:「抜けているところを、ルールに従って埋める」

欠損の種類をざっくりイメージする

配列の欠損には、ざっくり次のようなパターンがあります。

  • 値があるべき場所に null や undefined が入っている。
  • 本来あるべきキー(例: 特定の日付)のレコード自体が存在しない。
  • 数値が入るべきところに空文字や不正値が入っていて、実質的に欠損になっている。

欠損補完ユーティリティは、 「どこを欠損とみなすか」「どういう値で埋めるか」を決めて、配列を“扱いやすい状態”にするためのものです。

ユーティリティ 1:数値配列の欠損を「固定値」で補完する

シナリオ

次のような配列があるとします。

const raw = [10, null, 20, undefined, 30];
JavaScript

ここから、

  • null や undefined を「0」で埋めたい。

という欠損補完をしたいとします。

実装:fillMissingWithValue

function fillMissingWithValue(array, fillValue) {
  if (!Array.isArray(array)) {
    return [];
  }

  return array.map((v) => (v == null ? fillValue : v));
}
JavaScript

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

まず「配列かどうか」をチェックし、配列でなければ空配列を返します。 v == null は、nullundefined の両方を「欠損」とみなす書き方です。 欠損なら fillValue を入れ、それ以外なら元の値をそのまま返します。

これで、「欠損を特定の値で埋めた配列」が手に入ります。

例題 1:null と undefined を 0 で補完する

const raw = [10, null, 20, undefined, 30];

const filled = fillMissingWithValue(raw, 0);

console.log(filled);
JavaScript

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

[10, 0, 20, 0, 30]
JavaScript

この「欠損補完済みの配列」を使えば、 合計や平均などの計算をするときに、毎回 null チェックをしなくて済みます。

ユーティリティ 2:数値配列の欠損を「前の値」で補完する(前方補完)

シナリオ

次のような時系列データがあるとします。

const raw = [100, null, null, 130, null, 150];
JavaScript

ここから、

  • 欠損を「直前の値」で埋めたい(前方補完)。

という欠損補完をしたいとします。

実装:fillMissingForward

function fillMissingForward(array) {
  if (!Array.isArray(array)) {
    return [];
  }

  let lastValue = null;

  return array.map((v) => {
    if (v == null) {
      return lastValue;
    } else {
      lastValue = v;
      return v;
    }
  });
}
JavaScript

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

lastValue に「直前の有効な値」を覚えておきます。 値が欠損(null / undefined)のときは、lastValue を返します。 値が有効なときは、その値を返しつつ、lastValue を更新します。

これで、「最初の有効値以降の欠損」が、直前の値で埋められます。

例題 2:前方補完で時系列の欠損を埋める

const raw = [100, null, null, 130, null, 150];

const filled = fillMissingForward(raw);

console.log(filled);
JavaScript

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

[100, 100, 100, 130, 130, 150]
JavaScript

ここでのポイントは、

  • 2 番目と 3 番目の null が「100」で埋まっている。
  • 5 番目の null が「130」で埋まっている。

というところです。

時系列データでは、「直前の値で補完する」ことがよくあります。 このユーティリティを持っておくと、そのパターンを簡単に書けます。

ユーティリティ 3:日付系列の「抜けている日」を補完する

シナリオ

次のような日別売上データがあるとします。

const sales = [
  { date: "2024-07-01", amount: 100 },
  { date: "2024-07-03", amount: 200 },
];
JavaScript

ここで、「2024-07-02 のレコードがない」という欠損があります。 これを、

  • 連続した日付系列にしたい。
  • 欠損日の amount は 0 で埋めたい。

という欠損補完をしたいとします。

日付を 1 日進めるヘルパー

function addDays(dateStr, days) {
  const d = new Date(dateStr);
  d.setDate(d.getDate() + days);
  const yyyy = d.getFullYear();
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return `${yyyy}-${mm}-${dd}`;
}
JavaScript

実装:fillMissingDates

function fillMissingDates(records, startDate, endDate) {
  const map = new Map();

  records.forEach((r) => {
    map.set(r.date, r.amount);
  });

  const result = [];
  let current = startDate;

  while (current <= endDate) {
    const amount = map.has(current) ? map.get(current) : 0;
    result.push({ date: current, amount });
    current = addDays(current, 1);
  }

  return result;
}
JavaScript

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

まず recordsMap に入れて、「日付 → amount」の対応を作ります。 startDate から endDate まで 1 日ずつ進めながら、 その日付が Map にあれば元の amount を、なければ 0 を使います。 こうして、「連続した日付系列+欠損日の 0 補完」の配列を作ります。

例題 3:抜けている日付を 0 で補完する

const sales = [
  { date: "2024-07-01", amount: 100 },
  { date: "2024-07-03", amount: 200 },
];

const filled = fillMissingDates(sales, "2024-07-01", "2024-07-03");

console.log(filled);
JavaScript

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

[
  { date: "2024-07-01", amount: 100 },
  { date: "2024-07-02", amount: 0 },
  { date: "2024-07-03", amount: 200 },
]
JavaScript

ここでのポイントは、

  • 「日付の欠損」を「レコード自体を追加する」ことで補完している。
  • 欠損日の amount を 0 にするルールが、ユーティリティに閉じ込められている。

というところです。

業務では、「抜けている日・時間・ID を補完して連続系列にする」ことがよくあるので、 このパターンはかなり実用的です。

欠損補完ユーティリティで意識してほしい重要ポイント

「何を欠損とみなすか」「何で埋めるか」をはっきり決める

欠損補完は、ルールを決めないと危ない処理です。

  • null と undefined だけを欠損とみなすのか。
  • 空文字や 0 も欠損とみなすのか。
  • 補完値は 0 なのか、前の値なのか、平均なのか。

ここを曖昧にしたまま補完すると、 「本当は意味のある 0 まで消してしまう」などの事故につながります。

初心者のうちは、

  • 「欠損=null / undefined」とまずは決める。
  • 補完値は「0」か「前の値」のどちらかから始める。

くらいで十分です。 慣れてきたら、業務仕様に合わせてルールを増やしていくイメージを持つとよいです。

「補完したことが分かるようにしておく」ことも大事

場合によっては、「どこが元データで、どこが補完された値か」を区別したくなることがあります。 そのときは、レコードにフラグを持たせるのも一つの手です。

例えば、fillMissingDates を少し変えて、

result.push({
  date: current,
  amount,
  filled: !map.has(current), // 補完された行なら true
});
JavaScript

のようにしておくと、

  • グラフでは補完値を薄い色で表示する。
  • レポートでは「補完値が含まれている」ことを注記する。

といった工夫がしやすくなります。

手を動かして「欠損補完」の感覚をつかむ

次のコードを実行して、

  • 固定値補完
  • 前方補完
  • 日付欠損補完

の 3 パターンを体感してみてください。

function fillMissingWithValue(array, fillValue) {
  if (!Array.isArray(array)) {
    return [];
  }
  return array.map((v) => (v == null ? fillValue : v));
}

function fillMissingForward(array) {
  if (!Array.isArray(array)) {
    return [];
  }
  let lastValue = null;
  return array.map((v) => {
    if (v == null) {
      return lastValue;
    } else {
      lastValue = v;
      return v;
    }
  });
}

function addDays(dateStr, days) {
  const d = new Date(dateStr);
  d.setDate(d.getDate() + days);
  const yyyy = d.getFullYear();
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return `${yyyy}-${mm}-${dd}`;
}

function fillMissingDates(records, startDate, endDate) {
  const map = new Map();
  records.forEach((r) => {
    map.set(r.date, r.amount);
  });

  const result = [];
  let current = startDate;

  while (current <= endDate) {
    const amount = map.has(current) ? map.get(current) : 0;
    result.push({ date: current, amount });
    current = addDays(current, 1);
  }

  return result;
}

function demo() {
  console.log("=== fillMissingWithValue ===");
  console.log(fillMissingWithValue([10, null, 20, undefined, 30], 0));

  console.log("=== fillMissingForward ===");
  console.log(fillMissingForward([100, null, null, 130, null, 150]));

  console.log("=== fillMissingDates ===");
  console.log(
    fillMissingDates(
      [
        { date: "2024-07-01", amount: 100 },
        { date: "2024-07-03", amount: 200 },
      ],
      "2024-07-01",
      "2024-07-03"
    )
  );
}

demo();
JavaScript

まとめ:欠損補完ユーティリティで「穴だらけの現実データを、扱える形にする」

欠損補完は、 「現実のデータに必ず存在する“穴”を、ルールに従って埋めて、配列を扱いやすくするためのユーティリティ」です。

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

export function fillMissingWithValue(array, fillValue) { ... }
export function fillMissingForward(array) { ... }
export function fillMissingDates(records, startDate, endDate) { ... }
JavaScript

そして、

  • 集計前
  • グラフ描画前
  • レポート生成前

など、「欠損があると困る処理」の前で必ずこの欠損補完を通す。

こう決めておくと、 配列データが「穴だらけの現実」から「扱いやすい形」に変わり、 業務ロジックがぐっと安定して動くようになります。

欠損は避けられないけれど、 どう埋めるかは、あなたが決められる。 その決め方をコードに落としたものが、欠損補完ユーティリティです。

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