NULL 처리를 활용한 고객 데이터 정제
초급중급
BigQuery 실행 환경 설정
from google.cloud import bigquery
import pandas as pd
# 인증 설정 (서비스 계정 키 사용 시)
# import os
# os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "path/to/your-key.json"
# BigQuery 클라이언트 생성
client = bigquery.Client(project='your-project-id')
# 쿼리 실행 함수
def run_query(query):
return client.query(query).to_dataframe()학습 목표
이 레시피를 완료하면 다음을 할 수 있습니다:
- NULL 값의 개념과 데이터 품질 영향 이해
- IS NULL / IS NOT NULL로 결측치 탐지
- COALESCE로 NULL 대체 및 우선순위 처리
- IFNULL로 간단한 기본값 설정
- CASE WHEN으로 복잡한 조건부 로직 구현
- NULLIF로 특정 조건에서 NULL 생성
1. NULL 확인 - IS NULL, IS NOT NULL
이론
NULL은 “값이 없음” 또는 “알 수 없음”을 나타냅니다.
주의사항
column = NULL❌ (작동 안 함)column IS NULL✅ (올바른 방법)- NULL과의 모든 연산 결과는 NULL
구문
WHERE column IS NULL
WHERE column IS NOT NULL실습: 고객 데이터 NULL 분석
SELECT
'age' AS column_name,
COUNT(*) AS total_rows,
COUNT(age) AS non_null_count,
COUNT(*) - COUNT(age) AS null_count,
ROUND((COUNT(*) - COUNT(age)) * 100.0 / COUNT(*), 2) AS null_percentage
FROM `your-project-id.thelook_ecommerce.users`
UNION ALL
SELECT
'gender' AS column_name,
COUNT(*) AS total_rows,
COUNT(gender) AS non_null_count,
COUNT(*) - COUNT(gender) AS null_count,
ROUND((COUNT(*) - COUNT(gender)) * 100.0 / COUNT(*), 2) AS null_percentage
FROM `your-project-id.thelook_ecommerce.users`
UNION ALL
SELECT
'state' AS column_name,
COUNT(*) AS total_rows,
COUNT(state) AS non_null_count,
COUNT(*) - COUNT(state) AS null_count,
ROUND((COUNT(*) - COUNT(state)) * 100.0 / COUNT(*), 2) AS null_percentage
FROM `your-project-id.thelook_ecommerce.users`
ORDER BY null_percentage DESC실행 결과:
| column_name | total_rows | non_null_count | null_count | null_percentage |
|---|---|---|---|---|
| state | 100000 | 100000 | 0 | 0.0 |
| age | 100000 | 100000 | 0 | 0.0 |
| country | 100000 | 100000 | 0 | 0.0 |
| gender | 100000 | 100000 | 0 | 0.0 |
ℹ️
핵심 개념
COUNT(*): 전체 행 수 (NULL 포함)COUNT(column): NULL이 아닌 행 수만 카운트- NULL 비율 =
(전체 - Non-NULL) / 전체 * 100
퀴즈 1: 주문 아이템 NULL 분석
문제
order_items 테이블에서:
- returned_at, shipped_at, delivered_at 컬럼의 NULL 비율 계산
- 총 행 수, NULL 개수, NULL 비율(%) 출력
- NULL 비율이 높은 순으로 정렬
힌트: COUNT(*) - COUNT(column), UNION ALL
정답 보기
SELECT
'returned_at' AS column_name,
COUNT(*) AS total_rows,
COUNT(returned_at) AS non_null_count,
COUNT(*) - COUNT(returned_at) AS null_count,
ROUND((COUNT(*) - COUNT(returned_at)) * 100.0 / COUNT(*), 2) AS null_percentage
FROM `your-project-id.thelook_ecommerce.order_items`
UNION ALL
SELECT
'shipped_at' AS column_name,
COUNT(*) AS total_rows,
COUNT(shipped_at) AS non_null_count,
COUNT(*) - COUNT(shipped_at) AS null_count,
ROUND((COUNT(*) - COUNT(shipped_at)) * 100.0 / COUNT(*), 2) AS null_percentage
FROM `your-project-id.thelook_ecommerce.order_items`
UNION ALL
SELECT
'delivered_at' AS column_name,
COUNT(*) AS total_rows,
COUNT(delivered_at) AS non_null_count,
COUNT(*) - COUNT(delivered_at) AS null_count,
ROUND((COUNT(*) - COUNT(delivered_at)) * 100.0 / COUNT(*), 2) AS null_percentage
FROM `your-project-id.thelook_ecommerce.order_items`
ORDER BY null_percentage DESC실행 결과:
| column_name | total_rows | non_null_count | null_count | null_percentage |
|---|---|---|---|---|
| returned_at | 180120 | 18058 | 162062 | 89.97 |
| delivered_at | 180120 | 63226 | 116894 | 64.90 |
| shipped_at | 180120 | 117014 | 63106 | 35.04 |
비즈니스 인사이트:
- returned_at NULL 높음: 대부분 제품이 반품되지 않음 (정상)
- shipped_at NULL: 아직 배송되지 않은 주문
- delivered_at NULL: 배송 중이거나 미배송 상태
2. COALESCE - NULL 대체 (우선순위)
이론
COALESCE는 여러 값 중 첫 번째 NOT NULL 값을 반환합니다.
구문
COALESCE(value1, value2, value3, default_value)활용 예시
- 여러 연락처 중 유효한 것 선택
- 기본값 설정
- 우선순위 기반 선택
실습: state 기본값 설정
SELECT
user_id,
first_name,
last_name,
state,
country,
COALESCE(state, 'Unknown State') AS state_cleaned,
COALESCE(country, 'Unknown Country') AS country_cleaned,
COALESCE(state, country, 'Unknown Location') AS location
FROM `your-project-id.thelook_ecommerce.users`
WHERE state IS NULL OR country IS NULL
LIMIT 20퀴즈 2: 평균값으로 NULL 대체
문제
users 테이블에서:
- 전체 평균 연령을 계산 (서브쿼리 또는 CTE 사용)
- COALESCE로 age가 NULL인 경우 평균 연령으로 대체
- 원본 age와 정제된 age_cleaned 비교
힌트: COALESCE(age, (SELECT AVG(age) FROM …))
정답 보기
WITH avg_age AS (
SELECT ROUND(AVG(age), 0) AS average_age
FROM `your-project-id.thelook_ecommerce.users`
WHERE age IS NOT NULL
)
SELECT
user_id,
first_name,
age AS age_original,
COALESCE(age, (SELECT average_age FROM avg_age)) AS age_cleaned,
CASE
WHEN age IS NULL THEN 'Imputed'
ELSE 'Original'
END AS data_source
FROM `your-project-id.thelook_ecommerce.users`
LIMIT 1000결측치 대체 전략:
- 평균값: 숫자형 데이터, 분포가 정규분포에 가까울 때
- 중앙값: 이상치가 많을 때 더 안정적
- 최빈값: 범주형 데이터
- 그룹별 평균: 성별, 지역별로 다른 평균 적용
3. IFNULL - 간단한 NULL 대체
이론
IFNULL은 COALESCE의 간단한 버전으로 2개 인자만 받습니다.
구문
IFNULL(column, default_value)COALESCE vs IFNULL
COALESCE(a, b, c, d): 여러 값 비교IFNULL(a, b): 단순 대체 (더 간결)
실습: 간단한 기본값 설정
SELECT
user_id,
first_name,
last_name,
gender,
state,
IFNULL(gender, 'Unknown') AS gender_cleaned,
IFNULL(state, 'N/A') AS state_cleaned,
CONCAT(IFNULL(first_name, ''), ' ', IFNULL(last_name, '')) AS full_name
FROM `your-project-id.thelook_ecommerce.users`
WHERE gender IS NULL OR state IS NULL
LIMIT 30퀴즈 3: state NULL을 ‘Unknown’으로 대체 후 분석
문제
users 테이블에서:
- IFNULL로 state가 NULL인 경우 ‘Unknown’으로 대체
- 정제된 state별 고객 수 집계
- 상위 15개 주(state)만 조회
힌트: IFNULL(state, ‘Unknown’), GROUP BY, ORDER BY
정답 보기
SELECT
IFNULL(state, 'Unknown') AS state_cleaned,
COUNT(*) AS customer_count,
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM `your-project-id.thelook_ecommerce.users`), 2) AS percentage
FROM `your-project-id.thelook_ecommerce.users`
GROUP BY state_cleaned
ORDER BY customer_count DESC
LIMIT 15실행 결과:
| state_cleaned | customer_count | percentage |
|---|---|---|
| Guangdong | 5376 | 5.38 |
| England | 4126 | 4.13 |
| California | 3748 | 3.75 |
| Shanghai | 2443 | 2.44 |
| Texas | 2407 | 2.41 |
비즈니스 활용:
- Unknown 비율이 높음: 데이터 수집 프로세스 개선 필요
- 지역별 고객 분포: 마케팅 타겟팅, 물류 센터 위치 결정
4. CASE WHEN - 조건부 로직
이론
CASE WHEN은 SQL의 if-else 문입니다.
구문
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE default_result
END활용 예시
- 고객 세분화 (연령대, 등급 등)
- 조건부 집계
- 복잡한 비즈니스 로직 구현
실습: 연령대별 고객 세분화
SELECT
user_id,
first_name,
age,
CASE
WHEN age IS NULL THEN 'Unknown'
WHEN age < 20 THEN '10대'
WHEN age < 30 THEN '20대'
WHEN age < 40 THEN '30대'
WHEN age < 50 THEN '40대'
WHEN age < 60 THEN '50대'
ELSE '60대+'
END AS age_group,
CASE
WHEN age IS NULL THEN 'N/A'
WHEN age >= 40 THEN 'VIP'
ELSE 'Regular'
END AS customer_tier
FROM `your-project-id.thelook_ecommerce.users`
LIMIT 50실행 결과:
| user_id | first_name | age | age_group | customer_tier |
|---|---|---|---|---|
| 92691 | Tara | 42 | 40대 | VIP |
| 64986 | Jennifer | 42 | 40대 | VIP |
| 46547 | Kelly | 46 | 40대 | VIP |
| 26697 | Christina | 27 | 20대 | Regular |
퀴즈 4: 연령대별 VIP/일반 고객 주문 분석
문제
users와 orders를 JOIN하여:
- CASE WHEN으로 연령대 분류 (10대, 20대, 30대, 40대+, Unknown)
- CASE WHEN으로 고객 등급 분류:
- age >= 40: ‘VIP’
- age < 40: ‘Regular’
- age IS NULL: ‘Unknown’
- 연령대별, 등급별 주문 건수와 평균 아이템 수 계산
힌트: JOIN, GROUP BY age_group, customer_tier
정답 보기
SELECT
CASE
WHEN u.age IS NULL THEN 'Unknown'
WHEN u.age < 20 THEN '10대'
WHEN u.age < 30 THEN '20대'
WHEN u.age < 40 THEN '30대'
ELSE '40대+'
END AS age_group,
CASE
WHEN u.age IS NULL THEN 'Unknown'
WHEN u.age >= 40 THEN 'VIP'
ELSE 'Regular'
END AS customer_tier,
COUNT(DISTINCT o.order_id) AS order_count,
AVG(o.num_of_item) AS avg_items_per_order
FROM `your-project-id.thelook_ecommerce.users` u
JOIN `your-project-id.thelook_ecommerce.orders` o
ON u.user_id = o.user_id
GROUP BY age_group, customer_tier
ORDER BY
CASE age_group
WHEN '10대' THEN 1
WHEN '20대' THEN 2
WHEN '30대' THEN 3
WHEN '40대+' THEN 4
WHEN 'Unknown' THEN 5
END,
customer_tier실행 결과:
| age_group | customer_tier | order_count | avg_items_per_order |
|---|---|---|---|
| 10대 | Regular | 16861 | 1.44 |
| 20대 | Regular | 21385 | 1.45 |
| 30대 | Regular | 21076 | 1.45 |
| 40대+ | VIP | 65471 | 1.44 |
비즈니스 인사이트:
- VIP 고객 (40대+): 주문 건수와 구매량 패턴 분석
- Regular 고객 (20-30대): 성장 가능성 높은 세그먼트
5. NULLIF - 조건부 NULL 생성
이론
NULLIF는 두 값이 같으면 NULL을 반환합니다.
구문
NULLIF(expression1, expression2)
-- expression1 == expression2이면 NULL, 아니면 expression1활용 예시
- 0으로 나누기 방지:
value / NULLIF(count, 0) - 기본값 제거:
NULLIF(column, 'default') - 무의미한 값 제외
실습: NULLIF로 0 나누기 방지
SELECT
p.category,
COUNT(*) AS total_products,
SUM(CASE WHEN oi.order_id IS NOT NULL THEN 1 ELSE 0 END) AS sold_products,
-- NULLIF로 0 나누기 방지
ROUND(
SUM(CASE WHEN oi.order_id IS NOT NULL THEN 1 ELSE 0 END) * 100.0 /
NULLIF(COUNT(*), 0),
2
) AS sell_through_rate
FROM `your-project-id.thelook_ecommerce.products` p
LEFT JOIN `your-project-id.thelook_ecommerce.order_items` oi
ON p.product_id = oi.product_id
GROUP BY p.category
ORDER BY sell_through_rate DESC
LIMIT 15퀴즈 5: 안전한 평균 계산
문제
orders에서:
- status별로 그룹화
- 주문 건수, 총 아이템 수, 평균 아이템 수 계산
- NULLIF로 0 나누기 방지하여 평균 계산
힌트: SUM(num_of_item) / NULLIF(COUNT(*), 0)
정답 보기
SELECT
status,
COUNT(*) AS order_count,
SUM(num_of_item) AS total_items,
ROUND(SUM(num_of_item) / NULLIF(COUNT(*), 0), 2) AS avg_items_per_order
FROM `your-project-id.thelook_ecommerce.orders`
GROUP BY status
ORDER BY order_count DESC실행 결과:
| status | order_count | total_items | avg_items_per_order |
|---|---|---|---|
| Shipped | 37302 | 53788 | 1.44 |
| Complete | 31161 | 45168 | 1.45 |
| Processing | 25171 | 36205 | 1.44 |
| Cancelled | 18648 | 26901 | 1.44 |
| Returned | 12511 | 18058 | 1.44 |
NULLIF 활용 시나리오:
- 0 나누기 방지:
value / NULLIF(denominator, 0) - 무의미한 값 제거:
NULLIF(column, '') - 기본값 필터링:
NULLIF(column, 'N/A')
정리
학습한 NULL 처리 함수
| 함수 | 용도 | 예시 |
|---|---|---|
IS NULL | NULL 확인 | WHERE age IS NULL |
COALESCE(a,b,c) | 첫 번째 NOT NULL 값 | COALESCE(state, country, ‘Unknown’) |
IFNULL(a,b) | 간단한 대체 | IFNULL(age, 0) |
CASE WHEN | 조건부 로직 | 연령대 분류, 고객 등급 |
NULLIF(a,b) | 조건부 NULL 생성 | NULLIF(count, 0) |
NULL 처리 베스트 프랙티스
-
데이터 품질 모니터링
- 정기적으로 NULL 비율 측정
- 임계값 초과 시 알림
-
적절한 대체 전략
- 숫자: 평균, 중앙값, 0
- 범주: 최빈값, ‘Unknown’
- 날짜: 특정 기준일, 이전/다음 값
-
비즈니스 로직 구현
- CASE WHEN으로 복잡한 분류
- 고객 세분화, 제품 등급화
-
안전한 연산
- NULLIF로 0 나누기 방지
- COALESCE로 기본값 보장
다음 단계
NULL 처리를 마스터했습니다! 다음으로 CTE와 코호트 분석에서 복잡한 쿼리 구조화와 고급 분석 기법을 배워보세요.
Last updated on