SQL NULL以外のデータ分析を始める前に知っておくべきこと

SQL NULL以外のデータ分析を始める前に知っておくべきこと:不完全なデータとの賢い付き合い方

データ分析は、現代ビジネスにおける意思決定や問題解決に不可欠な要素となっています。しかし、分析を始めるにあたって、理想的な完璧なデータが手元にあることは稀です。データベースに格納されたデータは、入力漏れ、システムエラー、収集方法の限界など、様々な要因により不完全な状態であることがよくあります。その不完全さを象徴する最も基本的で重要な概念の一つが、SQLにおける「NULL」です。

SQLのNULLは、単なる「空っぽ」を意味するだけでなく、データ分析において無視できない、多くの落とし穴の原因となり得ます。NULLを正しく理解し、適切に扱うことは、正確で信頼性の高い分析結果を得るための出発点と言えます。この記事では、SQLにおけるNULLの基本的な仕組みから、それがデータ分析にどのような影響を与えるのか、そして分析を始める前にどのようにNULLを特定し、対処すべきなのかを、約5000語にわたって詳細に解説します。

1. はじめに:なぜNULLを知ることがデータ分析の第一歩なのか

データ分析の目的は、データから有用な知見を引き出し、より良い意思決定につなげることです。そのためには、まず手元にあるデータがどのような状態であるかを正確に把握する必要があります。特に、データソースとして広く利用されるリレーショナルデータベースでは、SQLが主要な操作言語となります。SQLを使ってデータを抽出、加工、集計する過程で、NULLという存在は常に付きまといます。

多くのデータ分析プロジェクトにおいて、初期段階でデータの品質を確認する「データプロファイリング」が行われます。このプロファイリングにおいて、各列にどれくらいのNULLが含まれているかを把握することは、非常に重要なステップです。なぜなら、NULLの存在は、その後の集計、フィルタリング、結合、統計分析、そして機械学習モデルの構築といったあらゆる分析プロセスに影響を及ぼす可能性があるからです。

NULLを無視したり、誤った方法で処理したりすると、分析結果が大きく歪んでしまい、誤った結論を導き出すリスクが高まります。例えば、平均値を計算する際にNULLの扱いを間違えると、本来の値よりも高くなったり低くなったりする可能性があります。また、特定の条件でデータを絞り込む際にNULLを含む行が意図せず除外されてしまうこともあります。

この記事を通じて、あなたは以下の点を理解し、データ分析のスキルを向上させることができます。

  • SQLにおけるNULLの正確な定義と、他の「空」の状態との違い。
  • SQLの基本操作(比較、集計、結合など)におけるNULLの挙動。
  • NULLがデータ分析の各フェーズで引き起こす具体的な問題点。
  • 分析対象データに含まれるNULLを特定し、その性質を理解する方法。
  • NULLを分析に適した形に変換・処理するための具体的な対処法(削除、補完、変換など)。
  • これらの対処法をSQLで実現するための関数とテクニック。

データ分析の旅を始める前に、この基本的ながらも奥深いNULLという概念と、しっかり向き合っておきましょう。

2. SQLにおけるNULLの基本的な理解

まずは、SQLにおけるNULLが具体的にどのようなものなのかを正確に理解することから始めます。NULLは、他のいかなるデータ型とも異なる特別な値です。

2.1. NULLとは何か?:値がない、不明、適用不能

SQLにおけるNULLは、特定のカラムに「値が存在しない」状態を表すマーカーです。これは、データがまだ入力されていない、不明である、あるいはそのレコードにとって該当しない、といった様々な理由によるものです。

重要なのは、NULLが何らかの実際の値ではないという点です。例えば、数値の0や、空文字列('')とは全く異なります。

  • 0: 数値であり、数量がゼロであることを明確に示します。
  • 空文字列 (''): 文字列であり、長さがゼロの文字列というが存在します。
  • NULL: 値そのものが存在しません。

例えば、顧客データベースで「電話番号」カラムがNULLの場合、それは「電話番号が登録されていない」「電話番号が不明」「電話番号の入力が必須ではなかったためデータがない」など、様々な状況を示唆しますが、少なくとも「電話番号が0である」「電話番号が空文字列である」という意味ではありません。

2.2. 真偽値ロジック:三値論理 (Three-Valued Logic) と NULL

SQLの真偽値ロジックは、通常のブール論理(TRUEとFALSEの二値)に加えて、NULLによって引き起こされるUNKNOWNという三番目の状態が存在します。これは、NULLを含む比較演算の結果が、真でも偽でもないと判断される場合に発生します。

例えば、score > 70 という条件があったとします。

  • score が 80 なら、80 > 70TRUE です。
  • score が 60 なら、60 > 70FALSE です。
  • score が NULL なら、NULL > 70UNKNOWN です。

NULLを含む比較演算子(=, <, >, <=, >=, <>, != など)の結果は、基本的にはUNKNOWNとなります。このUNKNOWNがWHERE句やHAVING句で評価される場合、結果がTRUEと評価される行だけが選択されるため、UNKNOWNと評価された行は除外されます

これはデータ分析において非常に重要なポイントです。例えば、WHERE sales_amount > 1000 というクエリを実行した場合、sales_amount がNULLである行は、NULL > 1000 がUNKNOWNと評価されるため、たとえ後からNULLが1000より大きい値だったことが判明したとしても、このクエリでは選択されません。

2.3. NULLを正しく判定する:IS NULLIS NOT NULL

前述の通り、通常の比較演算子(=など)ではNULLを正しく判定できません。column = NULL という条件は、常にUNKNOWNを返すため、意図したように機能しません(NULLである行を選択したり、NULLでない行を選択したりすることはできません)。

SQLでは、NULLであるかどうかを判定するために専用の述語(Predicate)が用意されています。

  • IS NULL: 指定したカラムの値がNULLである場合にTRUEを返します。
    例: SELECT * FROM users WHERE email IS NULL; (メールアドレスがNULLのユーザーを選択)
  • IS NOT NULL: 指定したカラムの値がNULLでない場合にTRUEを返します。
    例: SELECT * FROM products WHERE price IS NOT NULL; (価格がNULLでない製品を選択)

データ分析で特定のカラムがNULLである行やNULLでない行に注目したい場合は、必ずこれらの IS NULL または IS NOT NULL を使用する必要があります。

2.4. 集計関数とNULL:挙動の違いを理解する

SQLの集計関数(COUNT, SUM, AVG, MIN, MAXなど)は、NULLをどのように扱うかについて、関数によって異なる挙動を示します。この違いを理解することは、正確な集計結果を得るために不可欠です。

  • COUNT(*) または COUNT(1): これはテーブル内の行数を数えます。NULLを含む全ての行をカウントします。
  • COUNT(column_name): 指定したカラムにおいて、NULL以外の値を持つ行の数を数えます。カラムにNULLが含まれている行はカウントしません。
    例: SELECT COUNT(*), COUNT(email) FROM users; (全ユーザー数と、メールアドレスが登録されているユーザー数を比較する)
  • SUM(column_name): 指定したカラムのNULL以外の値を合計します。NULLは合計計算から無視されます。
    例: SELECT SUM(sales_amount) FROM orders; (売上合計。ただし、売上金額がNULLの注文は合計に含まれない)
  • AVG(column_name): 指定したカラムのNULL以外の値の平均を計算します。NULLは計算から無視されます(合計の対象からも、カウントの対象からも外れる)。
    例: SELECT AVG(score) FROM students; (学生の平均点。ただし、点数がNULLの学生は平均計算に含まれない)
  • MIN(column_name) / MAX(column_name): 指定したカラムのNULL以外の値の中から最小値または最大値を求めます。NULLは無視されます。
    例: SELECT MIN(price), MAX(price) FROM products; (価格の最小値と最大値。価格がNULLの製品は考慮されない)

COUNT(*)COUNT(column_name) の違い、そしてSUMAVGがNULLを無視するという点は、データ分析でNULL数を把握したり、NULLを考慮に入れた集計を行ったりする上で最も基本的な知識です。例えば、平均値を計算する際に「NULLを0として計算したい」という場合は、後述するNULLハンドリング関数を使ってNULLを0に変換してからAVGを適用する必要があります。

2.5. JOINとNULL:外部結合におけるNULLの発生

テーブルを結合する際にも、NULLは重要な役割を果たします。特に外部結合(LEFT OUTER JOIN, RIGHT OUTER JOIN, FULL OUTER JOIN)では、マッチしない行の対応するカラムがNULLになります。

  • INNER JOIN: 結合キーが両方のテーブルに存在する行のみを返します。もし結合キーカラムにNULLが含まれている場合、NULL = NULL は UNKNOWN となるため、NULLキーを持つ行同士はマッチしません。
  • LEFT OUTER JOIN: FROM句で指定した左側のテーブルの全ての行を返します。右側のテーブルにマッチする行がない場合、右側のテーブルのカラムはNULLになります。
  • RIGHT OUTER JOIN: JOIN句で指定した右側のテーブルの全ての行を返します。左側のテーブルにマッチする行がない場合、左側のテーブルのカラムはNULLになります。
  • FULL OUTER JOIN: 両方のテーブルの全ての行を返します。どちらかのテーブルにマッチする行がない場合、対応するもう一方のテーブルのカラムはNULLになります。

外部結合によって生成されるNULLは、データが存在しないことを明示的に示すために利用できます。例えば、LEFT JOIN orders ON users.user_id = orders.user_id とした場合、注文履歴がないユーザーのorder_idsales_amountといったordersテーブルのカラムはNULLになります。このNULLは「このユーザーは注文していない」という意味を持ち、分析上非常に有用な情報となり得ます。

2.6. 制約とNULL:NOT NULL制約、UNIQUE制約

テーブル設計において、カラムに制約を設定することがあります。NULLはこれらの制約とも関連があります。

  • NOT NULL制約: この制約が設定されたカラムには、NULLを格納することができません。データ入力時にNULLが許容されなくなるため、特定のカラムが必ず値を持つことを保証できます。分析の観点からは、NOT NULL制約が付いているカラムについてはNULLの存在を気にする必要がなくなります。
  • UNIQUE制約: この制約は、カラム内の値が一意であることを要求します。NULLの扱いについてはデータベースシステムによって若干異なりますが、一般的には、複数の行でNULLがUNIQUE制約のあるカラムに存在していても、それぞれがユニークであるとみなされます(つまり、NULLは他のNULLと等しくないと判断されます)。これは、NULLが「値がない状態」であり、比較できないという特性に基づいています。

これらの制約は、データの完全性を保つために重要ですが、同時にNULLが許容されているカラムが分析上の課題となり得ることを示唆しています。

3. なぜNULLはデータ分析において問題となるのか

SQLにおけるNULLの基本的な挙動を理解したところで、それが具体的にデータ分析のどのような側面に影響を及ぼし、なぜ問題となるのかを掘り下げていきます。

3.1. 集計の誤り:期待と異なる合計値や平均値

最も頻繁に遭遇する問題の一つは、集計結果が期待と異なることです。前述の通り、SUM, AVG, MIN, MAXといった集計関数はNULLを無視します。これが意図した挙動であれば問題ありませんが、そうでない場合は深刻な誤解を招きます。

例:売上テーブルsales
| order_id | amount | tax |
|—|—|—|
| 1 | 100 | 10 |
| 2 | 200 | NULL |
| 3 | 150 | 15 |
| 4 | NULL | 20 |

  • SELECT SUM(amount) FROM sales; -> (100 + 200 + 150) = 450 (NULLの行は無視される)
  • SELECT AVG(amount) FROM sales; -> (100 + 200 + 150) / 3 = 150 (NULLの行は無視される)
  • SELECT SUM(tax) FROM sales; -> (10 + 15 + 20) = 45 (NULLの行は無視される)
  • SELECT AVG(tax) FROM sales; -> (10 + 15 + 20) / 3 = 15 (NULLの行は無視される)

もし、amountのNULLが「まだ金額が確定していない」という意味なら、SUMやAVGで無視されるのは妥当かもしれません。しかし、もしtaxのNULLが「税金がかからない(税金は0)」という意味だとすれば、SUM(tax)が45となるのは誤りです。本来であれば0として合計に含めるべきです。この場合、後述するCOALESCE関数などを使ってNULLを0に変換してから集計する必要があります。

COUNT(*)COUNT(column_name) の違いも重要です。
* SELECT COUNT(*) FROM sales; -> 4 (全行数)
* SELECT COUNT(amount) FROM sales; -> 3 (amountがNULLでない行数)
* SELECT COUNT(tax) FROM sales; -> 3 (taxがNULLでない行数)

これらの違いを理解していないと、「注文数は4件なのに、売上金額の合計は3件分しかない」といった混乱が生じます。

3.2. フィルターの誤り:意図しないデータの除外

WHERE句でのフィルタリングにおいても、NULLの三値論理が影響します。WHERE conditionでは、conditionがTRUEと評価される行のみが選択されます。UNKNOWNやFALSEと評価される行は除外されます。

例:学生テーブルstudents
| student_id | score | grade |
|—|—|—|
| 1 | 85 | A |
| 2 | 60 | C |
| 3 | NULL | B |
| 4 | 90 | A |

  • SELECT * FROM students WHERE score >= 80;
    • student_id 1: 85 >= 80 -> TRUE (選択)
    • student_id 2: 60 >= 80 -> FALSE (除外)
    • student_id 3: NULL >= 80 -> UNKNOWN (除外)
    • student_id 4: 90 >= 80 -> TRUE (選択)
      結果: student_id 1, 4 の2行

このクエリは「スコアが80点以上の学生」を取得するものとして正しく機能しています。しかし、もし「まだ点数がついていない(NULL)学生も含めて、80点未満ではない学生リストを見たい」といった要件があった場合、上記のクエリでは点数がNULLの学生は含まれません。NULLの学生も含めたい場合は、WHERE score >= 80 OR score IS NULL; のように、明示的にNULL条件を追加する必要があります。

逆に、「スコアが80点未満の学生」を取得するクエリを考えてみましょう。
* SELECT * FROM students WHERE score < 80;
* student_id 1: 85 < 80 -> FALSE (除外)
* student_id 2: 60 < 80 -> TRUE (選択)
* student_id 3: NULL < 80 -> UNKNOWN (除外)
* student_id 4: 90 < 80 -> FALSE (除外)
結果: student_id 2 の1行

もし「80点未満ではない学生」を意図して NOT (score < 80) と書いた場合、どうなるでしょうか?
* SELECT * FROM students WHERE NOT (score < 80);
* student_id 1: NOT (FALSE) -> TRUE (選択)
* student_id 2: NOT (TRUE) -> FALSE (除外)
* student_id 3: NOT (UNKNOWN) -> UNKNOWN (除外)
* student_id 4: NOT (FALSE) -> TRUE (選択)
結果: student_id 1, 4 の2行

点数がNULLの学生は、score < 80 が UNKNOWN となり、NOT (UNKNOWN) も UNKNOWN となるため、やはり除外されます。これは score >= 80 と同じ結果になります。つまり、score < 80 の否定は、NULLを含む場合 score >= 80 とは等価ではないのです。

フィルタリングを行う際には、NULLの行を結果に含める必要があるか、除外する必要があるか、それともNULLはNULLとして別のグループとして扱いたいのかを明確にし、IS NULLIS NOT NULL、またはNULLハンドリング関数を適切に利用する必要があります。

3.3. 結合の誤り:NULLキーによる見落とし

内部結合では、結合キーの値が両方のテーブルで一致する行のみが結合されます。前述の通り、NULLは他のいかなる値とも(NULL自身とも)等しくないと判断されるため、結合キーにNULLが含まれている行は、内部結合では決してマッチしません。

例:ユーザーテーブルusersと注文テーブルorders
users
| user_id | user_name |
|—|—|
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
| 4 | David |

orders
| order_id | user_id | amount |
|—|—|—|
| 101 | 1 | 100 |
| 102 | 2 | 200 |
| 103 | NULL | 150 | — user_idが不明な注文
| 104 | 1 | 50 |

  • SELECT u.user_name, o.amount FROM users u INNER JOIN orders o ON u.user_id = o.user_id;
    • user_id 1 (Alice) と order_id 101, 104 がマッチ。
    • user_id 2 (Bob) と order_id 102 がマッチ。
    • user_id 3 (Charlie) は orders に対応する user_id がない。
    • user_id 4 (David) は orders に対応する user_id がない。
    • order_id 103 は user_id が NULL。users のどの user_id ともマッチしない (NULL = 1 は UNKNOWN, NULL = 2 は UNKNOWN, …)。
      結果:
      | user_name | amount |
      |—|—|
      | Alice | 100 |
      | Alice | 50 |
      | Bob | 200 |

この結果から「注文を行ったユーザーはAliceとBobだけだ」という結論を導き出すと誤りです。実際には、user_idが不明な(NULLの)注文が1件存在します。この注文を見落とさないためには、結合方法を検討する必要があります。

もし「全ての注文」を見たいのであれば、FROM orders o LEFT JOIN users u ON o.user_id = u.user_id のようにLEFT JOINを使うべきです。これにより、user_idがNULLの注文も含まれ、対応するuser_nameはNULLとして表示されます。

user_name amount
Alice 100
Bob 200
NULL 150
Alice 50

このように、結合キーにNULLが含まれる可能性がある場合は、内部結合が必ずしも適切とは限らず、外部結合の利用や、結合キーのNULLを特定の代替値に変換してから結合するといった工夫が必要になる場合があります。

3.4. 計算の伝播:NULLを含む演算の結果はNULL

数値計算や文字列連結など、NULLを含む多くの演算の結果はNULLになります。

例:
* 5 + NULL -> NULL
* 'Hello' || NULL -> NULL (データベースシステムによっては ‘Hello’ になる場合もある)
* NULL * 10 -> NULL

これはデータ分析で計算列を作成する際に問題となります。例えば、「合計金額 = 単価 * 数量」という計算を行う際に、単価や数量のどちらか一方または両方がNULLだと、計算結果である合計金額もNULLになってしまいます。「数量がNULLの場合は0として計算したい」といった要件がある場合は、計算前にNULLを0に変換する必要があります。

3.5. 統計的手法の適用困難:多くの手法は欠損値を扱えない

SQLの集計関数はNULLを無視して計算しますが、より高度な統計分析(相関分析、回帰分析、ANOVAなど)や機械学習アルゴリズムの多くは、入力データにNULLが含まれていると正しく機能しないか、エラーを発生させます。

これらの手法を適用するためには、前処理としてNULLを何らかの値で補完したり、NULLを含む行や列を削除したりする必要があります。NULLの適切な処理は、統計分析や機械学習における「欠損値処理」という重要な前処理ステップの一部となります。

3.6. 可視化の困難:グラフ作成における課題

BIツールやデータ可視化ツールでデータをグラフ化する際にも、NULLは課題となります。NULLをどのように表現するか(例えば、棒グラフでNULLのカテゴリを含めるか、折れ線グラフでNULLの点をどう扱うかなど)は、ツールの設定や、NULLを事前にどのように処理したかに依存します。NULLを適切に処理せずに可視化を行うと、グラフが正しく表示されなかったり、データの分布や傾向を誤って解釈したりする可能性があります。

4. データ分析前にNULLを特定・理解する方法

データ分析を始める前に、まず手元にあるデータにどれくらいのNULLが含まれているのか、どの列にNULLが多いのか、そしてNULLがどのようなパターンで出現しているのかを把握することが重要です。このステップはデータプロファイリングと呼ばれ、NULLの特定はその主要なタスクの一つです。

4.1. 基本的なNULL数の把握

最も基本的な方法は、COUNT(*)COUNT(column_name) を比較することです。

sql
SELECT
COUNT(*) AS total_rows,
COUNT(column_name) AS non_null_rows,
COUNT(*) - COUNT(column_name) AS null_rows,
(CAST(COUNT(*) - COUNT(column_name) AS DECIMAL) / COUNT(*)) * 100 AS null_percentage
FROM
your_table_name;

このクエリを各分析対象カラムに対して実行することで、全行数、NULLでない行数、NULLの行数、そしてNULL率を知ることができます。NULL率が高いカラムは、その後の分析において特に注意が必要です。

すべてのカラムのNULL率を一度に確認するには、情報スキーマを利用したり、各カラムに対して同様のクエリをUNION ALLで結合したりする方法がありますが、テーブルのカラムが多い場合は少し複雑になります。多くの分析ツールやBIツールには、データのサマリーを表示する機能があり、そこで各カラムのNULL数やNULL率を確認できます。

4.2. NULLを含む行の抽出

WHERE column_name IS NULL を使って、特定のカラムがNULLである行を抽出することで、その行の他のカラムの値を確認できます。これにより、NULLを持つレコードの全体像や、他のカラムとの関連性を把握することができます。

sql
SELECT
*
FROM
your_table_name
WHERE
column_name IS NULL
LIMIT 10; -- 例として最初の10行を表示

4.3. NULLの分布パターンの特定

NULLがランダムに発生しているのか、それとも特定の条件下(例:特定のユーザーグループ、特定の期間、特定のデータソース)で集中的に発生しているのかを確認します。これは、GROUP BY句とCOUNTIS NULLを組み合わせて行います。

例:ユーザータイプ別に年齢カラムのNULL率を確認する

sql
SELECT
user_type,
COUNT(*) AS total_by_type,
COUNT(age) AS non_null_age,
COUNT(*) - COUNT(age) AS null_age_count,
(CAST(COUNT(*) - COUNT(age) AS DECIMAL) / COUNT(*)) * 100 AS null_age_percentage
FROM
users
GROUP BY
user_type;

もし特定のユーザータイプで年齢のNULL率が顕著に高い場合、そのユーザータイプのデータ収集プロセスに問題があるか、あるいはそのユーザータイプには年齢情報が不要・収集できないといったビジネス上の理由があるのかもしれません。

期間別のNULL率を確認することも有効です。例えば、特定のデータ収集システムの変更後にNULLが増えた、といった問題を発見できる可能性があります。

sql
SELECT
DATE(created_at) AS creation_date,
COUNT(*) AS total_on_date,
COUNT(some_column) AS non_null_on_date,
(CAST(COUNT(*) - COUNT(some_column) AS DECIMAL) / COUNT(*)) * 100 AS null_percentage
FROM
your_table_name
GROUP BY
DATE(created_at)
ORDER BY
creation_date;

4.4. NULLの「意味」を理解する

統計的なNULLの数や分布を把握するだけでなく、なぜそのカラムにNULLが存在するのかというビジネス的な意味を理解することが、データ分析においては非常に重要です。

  • データ入力漏れ: ユーザーやオペレーターが情報の入力を怠った。
  • 該当しない: そのレコードにとって、そのカラムは概念的に存在しない(例: 会社員テーブルで「配偶者の勤務先」カラムがNULLなのは、未婚の場合や配偶者が働いていない場合など)。
  • 情報が不明: 取得しようとしたが、得られなかった(例: センサーデータの欠損、外部システムからの情報が取れなかった)。
  • システムの制約: データベーススキーマ設計上、NULLが許容されている。
  • データ収集プロセスの問題: 特定の経路からのデータで欠損が多い。

この「意味」によって、NULLに対する適切な対処法が変わってきます。「該当しない」NULLを平均値で補完するのは不適切ですし、「入力漏れ」のNULLを単に無視するのも情報損失に繋がるかもしれません。ドメイン知識を持つ関係者との連携や、データがどのように生成・収集されているかの理解が、NULLの真の意味を知る鍵となります。

4.5. データプロファイリングツールの活用

大規模なデータや多数のカラムを持つテーブルの場合、手動で上記のクエリを実行するのは非効率です。多くのデータ分析プラットフォームやツールには、データプロファイリング機能が搭載されており、各カラムのデータ型、NULL数、ユニーク数、頻出値、分布などを自動でレポートしてくれます。これらのツールを積極的に活用することで、NULLの特定と初期理解を効率的に行うことができます。

5. データ分析におけるNULLへの対処法

NULLを特定し、その性質や意味を理解したら、いよいよ分析に向けてNULLをどのように処理するかを検討します。NULLへの対処法は複数あり、分析の目的、データの性質、NULLの量とパターン、そして利用する分析手法によって最適な方法が異なります。

主な対処法は以下の3つに分類できます。

  1. 削除 (Deletion): NULLを含む行や列を分析対象から除外する。
  2. 補完 (Imputation): NULLを何らかの値で置き換える。
  3. NULLをそのまま利用する: NULLを特別なカテゴリとして扱ったり、NULLを許容する手法を使ったりする。

これらの方法をSQLでどのように実現するかを見ていきましょう。

5.1. 対処法1:削除 (Deletion)

最もシンプルで、かつ最もリスクの高い方法の一つです。

5.1.1. 行単位での削除 (Listwise Deletion)

特定のカラムにNULLが含まれる行全体を分析から除外します。SQLではWHERE column_name IS NOT NULLを使って実現できます。

sql
SELECT
*
FROM
your_table_name
WHERE
analyzed_column IS NOT NULL; -- analyzed_columnがNULLの行を除外

複数の重要なカラムのいずれかにNULLが含まれる行を除外したい場合は、ANDORを組み合わせて条件を指定します。

sql
SELECT
*
FROM
your_table_name
WHERE
column1 IS NOT NULL AND column2 IS NOT NULL AND column3 IS NOT NULL;

利点:
* 実装が非常に簡単。
* NULLを含まない完全なデータセットで分析できるため、多くの標準的な統計手法や機械学習アルゴリズムをそのまま適用できる。

欠点:
* 情報損失: NULLを含む行が多い場合、分析に利用できるデータ量が大幅に減少し、統計的な検出力が低下する可能性がある。
* バイアスの発生: NULLがランダムに発生しているのではなく、特定のグループや条件下で偏って発生している場合(例:低収入層で収入がNULLになりがち)、NULLを含む行を削除することで、残ったデータが元のデータの特性を代表しなくなり、分析結果に偏り(選択バイアス)が生じる可能性がある。

適用を検討する場合:
* NULLを含む行がごく一部である(例:全データの5%未満)。
* NULLが完全にランダムに発生していると強く推測できる。
* 分析の対象が、特定の情報(NULLでないカラムの値)を持つデータに限定される場合。

SQLでの注意点: DELETE文で物理的に削除するのではなく、SELECT文のWHERE句で絞り込むことで、元のデータは保持しつつ分析対象データから除外するのが一般的です。

5.1.2. 列単位での削除 (Column Deletion)

NULL率が非常に高いカラム(例:90%以上がNULL)や、分析の目的から明らかに不要なカラムは、分析対象から除外することを検討します。

利点:
* データセットの次元を削減し、分析を簡略化できる。
* ほとんど情報を含まないカラムを削除することで、分析のノイズを減らせる可能性がある。

欠点:
* そのカラムに(たとえ少数でも)有用な情報が含まれている可能性を完全に放棄することになる。

適用を検討する場合:
* そのカラムのNULL率が極めて高く、利用可能な非NULLデータがほとんどない。
* 分析の目的において、そのカラムが持つ情報が全く重要ではないと判断できる。

SQLでの実現: SELECT句でそのカラムを含めないだけです。

sql
SELECT
column1,
column2, -- NULL率の高い column3 は含めない
column4
FROM
your_table_name;

5.2. 対処法2:補完 (Imputation)

NULLを何らかの値で置き換える方法です。情報損失を抑えることができますが、置き換える値の選択によって分析結果に影響を与える可能性があります。

5.2.1. 定数による補完

NULLを特定の定数で置き換えます。例えば、数値カラムなら0や-1、文字列カラムなら’不明’や’N/A’などです。

SQLでの実現: COALESCE関数やCASE WHENを使います。

“`sql
— 数値カラムのNULLを0に置き換える
SELECT
order_id,
COALESCE(amount, 0) AS amount_filled_zero,
tax
FROM
sales;

— 文字列カラムのNULLを’Unknown’に置き換える
SELECT
user_id,
COALESCE(email, ‘Unknown’) AS email_filled_unknown
FROM
users;

— CASE WHEN を使って、NULLの場合とそうでない場合で異なる値を適用
SELECT
product_id,
CASE
WHEN discount_price IS NULL THEN list_price — 割引価格がNULLなら定価
ELSE discount_price
END AS actual_price
FROM
products;
“`

利点:
* 実装が簡単。
* NULLを特定の意味を持つカテゴリとして扱いたい場合に有効(例:電話番号のNULLを「不明」というカテゴリにする)。
* 集計や計算をNULLエラーなしに行えるようになる。

欠点:
* データの分布を歪める可能性がある(特に、NULLが多い場合に特定の定数で埋めると、その定数の値が異常に多く出現し、ヒストグラムなどに偏りが生じる)。
* 定数に意味がない場合(例:年齢のNULLを0で埋める)、その値が分析結果を誤導する可能性がある。

適用を検討する場合:
* NULLが特定の意味(例:該当なし、未実施)を持つ場合。
* NULLの数が少なく、分布への影響が小さい場合。
* 計算や集計のために一時的にNULLを置き換えたい場合(例:合計金額計算でNULLを0として扱う)。

5.2.2. 統計量による補完(平均値、中央値、最頻値)

数値カラムのNULLを、そのカラムの平均値、中央値、または最頻値で置き換える方法です。これにより、データセットの基本的な統計量(平均値など)を維持しようとします。

SQLでの実現: サブクエリやCTE(Common Table Expression)を使って統計量を計算し、その値でNULLを補完します。

“`sql
— 平均値で年齢のNULLを補完する
WITH AvgAge AS (
SELECT AVG(age) AS mean_age FROM users WHERE age IS NOT NULL
)
SELECT
user_id,
COALESCE(age, (SELECT mean_age FROM AvgAge)) AS age_filled_mean
FROM
users;

— 最頻値(例:最も多いグレード)でグレードのNULLを補完する (より複雑なクエリになることが多い)
— PostgreSQLなどの例 (集計関数とウィンドウ関数を使用)
WITH ModeGrade AS (
SELECT grade FROM students WHERE grade IS NOT NULL
GROUP BY grade
ORDER BY COUNT(*) DESC LIMIT 1
)
SELECT
student_id,
COALESCE(grade, (SELECT grade FROM ModeGrade)) AS grade_filled_mode
FROM
students;
“`

利点:
* 削除による情報損失を防げる。
* データセットの基本的な統計的特性(特に平均値による補完の場合)をある程度維持できる。

欠点:
* データの分散や標準偏差を過小評価する傾向がある。
* 元のデータの変動性を正確に反映しない。
* 外れ値がある場合、平均値補完はその影響を受けやすい(この場合は中央値がより適切)。
* 同じ値で補完されたデータポイントが多数追加されることで、データの形状(分布)が歪む可能性がある。
* 統計量による補完は、NULLが完全にランダムに発生している(MCAR: Missing Completely At Random)と仮定できる場合に比較的有効ですが、そうでない場合はバイアスを導入する可能性があります。

適用を検討する場合:
* 数値データやカテゴリデータで、NULLがランダムに発生している可能性が高い場合。
* 分析の目的が、個々の正確な値よりもデータセット全体の傾向や平均値を把握することにある場合。
* 欠損値を持つレコードの他の情報も分析に含めたい場合。

グループごとの平均値/中央値/最頻値で補完することもよく行われます。例えば、性別ごとの平均年齢で、それぞれの性別のNULL年齢を補完するなどです。これは、GROUP BYとウィンドウ関数を組み合わせて実現できます。

sql
-- 性別ごとの平均年齢でNULL年齢を補完する
SELECT
user_id,
gender,
COALESCE(age, AVG(age) OVER (PARTITION BY gender)) AS age_filled_group_mean
FROM
users;

5.2.3. より高度な補完方法

統計学や機械学習の分野では、さらに sophisticated な補完方法が研究されています。

  • 回帰分析による補完: NULLでない他のカラムの値を説明変数として、NULLになっているカラムの値を予測し、その予測値で補完する。
  • 機械学習モデルによる補完: K近傍法 (KNN Imputation)、多重代入法 (Multiple Imputation) など、より複雑なモデルを使ってNULLを補完する。
  • 時系列データの補完: 前後のデータポイントから線形補間したり、LOCF (Last Observation Carried Forward) や NOCB (Next Observation Carried Backward) を適用したりする。

これらの方法はSQLだけで直接的に行うのは難しく、通常はPython (Pandas, Scikit-learn) やRなどのプログラミング言語や統計ソフトウェアで行います。SQLでデータを抽出・前処理した後、これらのツールに引き渡して高度な補完を行うという流れになります。

注意点: 補完は「真実の値」を知らないまま値を推測して埋める行為です。どのような補完方法を選択しても、元のデータが持つ不確実性やエラーを完全に除去することはできません。補完によってデータが完璧になったと過信せず、補完後のデータでの分析結果が補完方法にどの程度影響されているかを常に意識する必要があります。

5.3. 対処法3:NULLをそのまま利用する(変換して活用)

NULLを特別な値として扱ったり、NULLを許容する分析手法を用いしたりする方法です。

5.3.1. NULLを独立したカテゴリとして扱う

カテゴリ変数において、NULLを「不明」「欠損」といった一つの独立したカテゴリとして扱う方法です。これは、NULL自体に意味がある場合(例:「最終学歴」のNULLが「未回答」を意味する場合)に特に有効です。

SQLでの実現: COALESCECASE WHENを使って、NULLを特定の文字列や数値コードに変換します。

“`sql
— NULLのgradeを’Unknown’カテゴリに変換
SELECT
student_id,
COALESCE(grade, ‘Unknown’) AS grade_as_category
FROM
students;

— 数値カラムのNULLを、後でカテゴリとして扱えるような特定のコード(例:-999)に変換
SELECT
product_id,
COALESCE(weight, -999) AS weight_coded_for_null
FROM
products;
“`

変換後、この新しいカラムをOne-Hot Encodingなどの手法でダミー変数に変換することで、多くの統計モデルや機械学習モデルに入力できるようになります。

利点:
* NULLに関する情報(欠損しているという事実)を失わない。
* NULLがランダムでない場合でも、NULLであること自体が分析上重要な情報となり得る場合に有効。

欠点:
* NULLがランダムに発生している場合にこの方法を使うと、分析モデルに無関係なノイズを導入する可能性がある。
* 欠損が多いカラムを多数この方法で処理すると、特徴量の数が大幅に増加する可能性がある(高次元化)。

適用を検討する場合:
* NULLが特定の意味を持つことが明確な場合。
* NULLであること自体が、分析の目的変数や他の変数と相関があると考えられる場合。
* 決定木やランダムフォレストのような、カテゴリ変数を直接扱えたり、NULLを特徴量の分岐基準として利用できたりする可能性のあるモデルを使う場合(ただし、モデルの実装によります)。

5.3.2. NULLを許容する分析手法の利用

一部の統計手法や機械学習アルゴリズムは、入力データにNULLが含まれていてもそのまま処理できるものがあります。例えば、多くの決定木ベースのアルゴリズム(C4.5, CARTの一部の実装、XGBoost, LightGBMなど)は、特徴量の欠損値を適切に扱えるように設計されていることがあります。これらのアルゴリズムは、NULLを特定の方向へ送るルールを学習したり、NULLを最適な閾値として扱ったりします。

利点:
* 複雑な補完プロセスを省ける。
* 補完によるバイアス導入のリスクを避けられる。

欠点:
* 利用できる手法が限られる。
* 手法のNULLの扱い方を正確に理解していないと、誤った結果を解釈する可能性がある。

適用を検討する場合:
* NULLを削除したり補完したりすることによる情報損失やバイアスを避けたい場合。
* 利用しようとしている分析ツールやライブラリがNULLをネイティブにサポートしている場合。

6. NULLを考慮したSQLクエリの記述例

これまでに学んだNULLの知識と対処法を活かして、実際のデータ分析で役立つSQLクエリの例を見てみましょう。

6.1. 集計時のNULL対応

売上テーブルsales (order_id, amount, tax) を例に、集計におけるNULL対応を示します。

  • 売上金額の合計(NULLを0として扱う):
    NULLのamountがある注文は、その売上金額が未確定かもしれませんが、合計を計算する際には0として扱う方がビジネス的に意味がある場合があります。
    sql
    SELECT SUM(COALESCE(amount, 0)) AS total_sales_amount FROM sales;
  • 税金の合計(NULLを0として扱う):
    税金カラムのNULLが「税金がかからない(0)」を意味する場合。
    sql
    SELECT SUM(COALESCE(tax, 0)) AS total_tax FROM sales;
  • 平均単価(NULLの注文を平均計算から除外):
    これはAVG関数のデフォルトの挙動です。
    sql
    SELECT AVG(amount) AS average_order_amount FROM sales;
  • 平均単価(NULLの注文を金額0として平均計算に含める):
    この計算がビジネス的に意味を持つかは検討が必要ですが、もしこのような定義の平均が必要な場合。
    sql
    SELECT AVG(COALESCE(amount, 0)) AS average_order_amount_including_null_as_zero FROM sales;

    ※ この値は、NULL行を除外した平均値よりも低くなります。どちらの定義が分析目的に合致するかを明確にしてください。
  • NULLでない売上金額の注文数と、全注文数:
    sql
    SELECT
    COUNT(amount) AS orders_with_amount,
    COUNT(*) AS total_orders
    FROM
    sales;

6.2. フィルタリング時のNULL対応

学生テーブルstudents (student_id, score, grade) を例に、フィルタリングにおけるNULL対応を示します。

  • スコアが70点以上、またはスコアがNULLの学生:
    sql
    SELECT * FROM students WHERE score >= 70 OR score IS NULL;
  • スコアが70点未満の学生(NULLは含まない):
    sql
    SELECT * FROM students WHERE score < 70;
  • スコアがNULLでない学生:
    sql
    SELECT * FROM students WHERE score IS NOT NULL;
  • グレードが’A’または’B’の学生(NULLのグレードは含まない):
    IN句や = 演算子は、NULLを含む値に対してはUNKNOWNを返すため、NULLの行は自動的に除外されます。
    sql
    SELECT * FROM students WHERE grade IN ('A', 'B');
    -- は SELECT * FROM students WHERE grade = 'A' OR grade = 'B'; と等価
    -- grade が NULL の場合、'A' とも 'B' とも等しくないので、条件は FALSE になり除外される。
  • グレードが’A’でも’B’でもない学生(NULLのグレードは含む):
    NOT IN句もNULLを含む値に対してはUNKNOWNを返します。grade NOT IN ('A', 'B') は、gradeがNULLの場合UNKNOWNとなり除外されます。NULLの行も含めるには明示的な条件が必要です。
    sql
    SELECT * FROM students WHERE grade NOT IN ('A', 'B') OR grade IS NULL;

    または、COALESCEを使ってNULLを一時的に代替値に変換してから比較する方法もありますが、可読性が落ちる場合があります。

6.3. 結合時のNULL対応

ユーザーテーブルusers (user_id, user_name) と注文テーブルorders (order_id, user_id, amount) を例に、結合におけるNULL対応を示します。ordersテーブルにはuser_idがNULLの注文が存在するとします。

  • 注文を行ったユーザー(内部結合):
    orders.user_idがNULLの注文は除外されます。
    sql
    SELECT u.user_name, o.amount
    FROM users u INNER JOIN orders o ON u.user_id = o.user_id;
  • 全ての注文(ユーザー情報があれば表示、なければNULL):
    orders.user_idがNULLの注文も含まれ、その場合usersテーブルのカラムはNULLになります。
    sql
    SELECT u.user_name, o.amount
    FROM orders o LEFT JOIN users u ON o.user_id = u.user_id;
  • 全てのユーザー(注文情報があれば表示、なければNULL):
    usersテーブルの全てのユーザーが含まれ、注文がないユーザーの場合ordersテーブルのカラムはNULLになります。
    sql
    SELECT u.user_name, o.amount
    FROM users u LEFT JOIN orders o ON u.user_id = o.user_id;
  • user_idがNULLの注文を、特定のダミーユーザー(user_id = 0)に紐づける(結合前にNULLを変換):
    これは、NULLキーを持つ行を結合したい場合に、NULLを特定の代替値(存在しないが結合キーとして利用できる値)に変換してから内部結合を行うテクニックです。ただし、元のデータ構造や分析目的を考慮して慎重に行う必要があります。
    sql
    -- user_id = 0 のダミーユーザーを users テーブルに事前に作成しておく、あるいはCTEで一時的に作成
    WITH users_with_dummy AS (
    SELECT user_id, user_name FROM users
    UNION ALL
    SELECT 0 AS user_id, 'Unknown User' AS user_name -- ダミーユーザー
    )
    SELECT u.user_name, o.amount
    FROM users_with_dummy u
    INNER JOIN orders o ON u.user_id = COALESCE(o.user_id, 0); -- ordersのNULLを0に変換して結合

    この例では、ordersテーブルのuser_idがNULLの行は、user_id=0のダミーユーザー「Unknown User」に紐づけられます。

7. データ分析パイプラインにおけるNULL

データ分析は、単一のSQLクエリで完結することは少なく、データの抽出、変換、ロード(ETL/ELT)、前処理、分析、可視化という一連のパイプラインの中で行われます。NULLの処理は、このパイプラインの様々な段階で発生します。

  • 抽出 (Extraction): データソースからデータを取得する段階です。この時点で、データソースにNULLが存在するかどうかを確認し、NULLを含むデータをどのように抽出するかの戦略を立てます。
  • 変換 (Transformation): 抽出したデータを分析に適した形に加工する段階であり、NULLの処理が最も集中的に行われるフェーズです。
    • 欠損値の特定とカウント。
    • 分析目的に応じたNULLの削除、補完、または特別な値への変換。
    • NULLを含む計算列の処理。
    • 外部結合によるNULLの生成と、そのNULLの意味づけ。
    • これらの変換処理は、SQLだけでなく、ETLツール、データ処理フレームワーク(例:Apache Spark)、プログラミング言語(例:PythonのPandasライブラリ)など、様々な技術を使って行われます。SQLは特にデータベース内での変換処理に強力です。
  • ロード (Loading): 変換されたデータを、分析用のデータウェアハウスやデータマートに格納する段階です。格納先のスキーマ設計において、どのカラムにNULLを許容するか、NOT NULL制約を設けるかなどを適切に設計することが、後の分析の効率と信頼性に影響します。デフォルト値を設定してNULLをなくすという方法も検討されますが、NULLが持つ本来の意味が失われる可能性に注意が必要です。
  • 前処理 (Preprocessing for Analysis/Modeling): データウェアハウス/マートからデータを抽出し、特定の分析手法や機械学習モデルに投入する直前の前処理段階です。ここでも、統計分析や機械学習ライブラリの要求に合わせて、改めてNULLを処理(多くの場合、補完または削除)します。この段階は、PythonのPandas DataFrame上で行われることが多いです。

データ分析パイプライン全体を通してNULLの存在を意識し、各ステップでNULLがどのように扱われるか、そしてどのように処理すべきかを計画的に実行することが重要です。

8. NULLとデータ品質

NULLは単なる技術的な概念ではなく、データの品質を示す重要な指標の一つです。特定のカラムにNULLが多いということは、データの収集、入力、連携、あるいはシステム設計のどこかに問題がある可能性を示唆しています。

データ分析者は、NULLの存在を単に処理すべき技術的な課題として捉えるだけでなく、「なぜここにNULLがあるのだろう?」と疑問を持ち、その原因を探求する姿勢が重要です。原因を特定し、データ生成プロセスの上流を改善することで、データ全体の品質を向上させることができます。

例えば、特定の入力フォームで特定の項目がNULLになりがちな場合、その項目が必須になっていないか、入力方法が分かりにくいか、あるいはその情報をシステムが適切に受け渡せていないか、といった問題が考えられます。NULLの分析は、データ品質改善の第一歩となるのです。

また、「欠損していること自体」が分析上重要な情報である場合があることにも留意してください。例えば、アンケートの特定の質問に対するNULL(無回答)が、その質問の内容に対する忌避感や無関心を示すサインである可能性があります。このような場合、NULLを単に削除したり平均値で補完したりするのではなく、NULLであることを示す新しいカテゴリとして扱うことが、より深い洞察を得るために有効となることがあります。

9. まとめ:NULLと適切に付き合うための心構え

データ分析において、NULLは避けて通れない存在です。この記事では、SQLにおけるNULLの基本的な挙動から、それがデータ分析に与える具体的な影響、そして NULL を特定し、分析に適した形に処理するための様々な方法を詳細に解説しました。

データ分析を始めるにあたって「SQL NULL以外の…」というテーマでNULLに焦点を当てたのは、NULLの理解と適切なハンドリングが、その後のあらゆる分析手法やモデリング手法を適用する上での前提条件となるからです。NULLを無視して進める分析は、砂上の楼閣のように脆いものとなりかねません。

正確で信頼性の高い分析結果を得るためには、以下のステップを常に意識してください。

  1. NULLを認識する: まずは分析対象データにNULLが含まれているかどうか、どのカラムにどれくらい含まれているかを確認することから始めます。COUNT(*)COUNT(column_name) の比較や WHERE IS NULL 句を活用します。
  2. NULLの性質を理解する: 単に数だけでなく、NULLがどのようなパターンで出現しているか、そして最も重要なのは、そのNULLがなぜ存在するのか、どのような意味を持っているのかを、ドメイン知識やデータ収集プロセスへの理解を通じて把握します。
  3. 適切な対処法を選択する: 分析の目的、データの種類、NULLの量や性質、利用する分析手法などを考慮して、削除、補完、またはNULLを活かした変換の中から最適な方法を選択します。
  4. SQLで適切に実装する: 選択した対処法を、IS NULL, IS NOT NULL, COALESCE, NULLIF, CASE WHENなどのSQL関数や構文を駆使して正確に実装します。集計やフィルタリング、結合を行う際には、NULLがどのように扱われるかを常に意識します。
  5. 結果を検証する: NULL処理後のデータで得られた分析結果が、元のデータの性質やNULLの意味と矛盾しないか、補完や削除によるバイアスが導入されていないかなどを批判的に検証します。

NULLとの適切な付き合い方は、データ分析における重要なスキルの一つです。それは単なるSQLのテクニックに留まらず、データの不完全性と向き合い、その限界を理解しつつ、可能な限り正確な知見を引き出そうとするデータ分析の基本的な心構えに根ざしています。

この記事が、あなたがSQLデータを用いたデータ分析を始めるにあたって、NULLという存在に自信を持って対処するための強固な基盤となることを願っています。常にデータと対話し、その裏にあるストーリーや課題(NULLもその一つです)を読み解こうとする探求心を持って、データ分析の旅を進めてください。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール