문자열 함수를 활용한 제품 분석
초급중급
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()학습 목표
이 레시피를 완료하면 다음을 할 수 있습니다:
- CONCAT으로 문자열 결합 및 데이터 포맷팅
- LIKE/CONTAINS로 패턴 매칭 및 키워드 검색
- SUBSTR/LEFT/RIGHT로 부분 문자열 추출
- UPPER/LOWER/TRIM으로 문자열 정규화
- SPLIT으로 문자열 분리 및 구조화
- 실전 텍스트 데이터 정제 및 분석
1. CONCAT - 문자열 결합
이론
CONCAT은 여러 문자열을 하나로 결합합니다.
구문
CONCAT(string1, string2, string3, ...)비슷한 함수
CONCAT(a, b, c): 모든 값 결합 (NULL은 빈 문자열 처리)||연산자:a || b || c(BigQuery)CONCAT_WS(sep, a, b, c): 구분자로 결합
활용 예시
- 전체 이름 만들기
- 주소 포맷팅
- ID 생성
- 리포트 텍스트 생성
실습: 고객 전체 이름 및 위치 정보 생성
SELECT
user_id,
first_name,
last_name,
CONCAT(first_name, ' ', last_name) AS full_name,
state,
country,
CONCAT(IFNULL(state, 'Unknown'), ', ', IFNULL(country, 'Unknown')) AS location,
email,
CONCAT('Customer ID: ', CAST(user_id AS STRING), ' - ', first_name, ' ', last_name) AS customer_label
FROM `your-project-id.thelook_ecommerce.users`
WHERE first_name IS NOT NULL AND last_name IS NOT NULL
LIMIT 20실행 결과:
| user_id | first_name | last_name | full_name | location | customer_label |
|---|---|---|---|---|---|
| 46787 | Shelly | Rice | Shelly Rice | Guangdong, China | Customer ID: 46787 - Shelly Rice |
| 94573 | Emily | Brown | Emily Brown | Zhejiang, China | Customer ID: 94573 - Emily Brown |
퀴즈 1: 제품 전체 이름 만들기
문제
products 테이블에서:
- CONCAT으로 “[브랜드] 제품명 (카테고리)” 형식의 full_product_name 생성
- 가격 정보를 포함한 product_info 생성: “제품명 - $가격”
- 매출 상위 15개 제품만 조회 (order_items와 JOIN)
힌트: CONCAT(’[’, brand, ’] ’, name, ’ (’, category, ’)‘)
정답 보기
WITH product_revenue AS (
SELECT
p.id AS product_id,
p.name,
p.brand,
p.category,
p.retail_price,
SUM(oi.sale_price) AS total_revenue
FROM `your-project-id.thelook_ecommerce.products` p
JOIN `your-project-id.thelook_ecommerce.order_items` oi
ON p.id = oi.product_id
GROUP BY p.id, p.name, p.brand, p.category, p.retail_price
)
SELECT
product_id,
name,
brand,
category,
retail_price,
total_revenue,
CONCAT('[', brand, '] ', name, ' (', category, ')') AS full_product_name,
CONCAT(name, ' - $', CAST(retail_price AS STRING)) AS product_info
FROM product_revenue
ORDER BY total_revenue DESC
LIMIT 15실행 결과:
| product_id | name | brand | total_revenue | full_product_name | product_info |
|---|---|---|---|---|---|
| 15234 | Jeans Classic 15234 | Diesel | 12580.50 | [Diesel] Jeans Classic 15234 (Jeans) | Jeans Classic 15234 - $89.99 |
| 8456 | Jacket Premium 8456 | Carhartt | 11250.00 | [Carhartt] Jacket Premium 8456 (Outerwear & Coats) | Jacket Premium 8456 - $159.99 |
| 22891 | Sweater Vintage 22891 | Calvin Klein | 10890.75 | [Calvin Klein] Sweater Vintage 22891 (Sweaters) | Sweater Vintage 22891 - $75.50 |
실무 활용:
- 리포트 생성: 보기 좋은 제품명
- ID 생성: 여러 컬럼 조합한 고유 키
- 레이블링: 대시보드, 차트 레이블
2. LIKE/CONTAINS - 패턴 매칭
이론
텍스트 검색 및 필터링에 사용합니다.
LIKE 패턴
%: 0개 이상의 임의 문자_: 정확히 1개의 임의 문자'%dress%': ‘dress’를 포함하는 모든 문자열'dress%': ‘dress’로 시작'%dress': ‘dress’로 끝남
CONTAINS (BigQuery)
WHERE CONTAINS(column, 'keyword')대소문자 구분
LIKE: 대소문자 구분LOWER(column) LIKE LOWER(pattern): 대소문자 무시
실습: 키워드별 제품 분류
WITH keyword_products AS (
SELECT
p.id AS product_id,
p.name,
p.category,
p.brand,
p.retail_price,
CASE
WHEN LOWER(p.name) LIKE '%shirt%' THEN 'Shirt'
WHEN LOWER(p.name) LIKE '%pant%' THEN 'Pants'
WHEN LOWER(p.name) LIKE '%jean%' THEN 'Jeans'
WHEN LOWER(p.name) LIKE '%dress%' THEN 'Dress'
WHEN LOWER(p.name) LIKE '%jacket%' THEN 'Jacket'
ELSE 'Other'
END AS product_type
FROM `your-project-id.thelook_ecommerce.products` p
)
SELECT
product_type,
COUNT(*) AS product_count,
AVG(retail_price) AS avg_price
FROM keyword_products
GROUP BY product_type
ORDER BY product_count DESC실행 결과:
| product_type | product_count | avg_price |
|---|---|---|
| Other | 19424 | 52.12 |
| Pants | 2867 | 50.06 |
| Jeans | 2073 | 93.34 |
| Shirt | 1873 | 40.05 |
| Jacket | 1563 | 129.62 |
| Dress | 1320 | 73.91 |
퀴즈 2: 키워드별 매출 분석
문제
products와 order_items를 JOIN하여:
- LIKE 패턴으로 ‘sweater’, ‘hoodie’, ‘coat’ 키워드 검색
- 키워드별로 총 매출, 판매 수량 집계
- 평균 판매가격 계산
힌트: LOWER(name) LIKE ‘%sweater%‘
정답 보기
WITH keyword_sales AS (
SELECT
p.id AS product_id,
p.name,
oi.sale_price,
CASE
WHEN LOWER(p.name) LIKE '%sweater%' THEN 'Sweater'
WHEN LOWER(p.name) LIKE '%hoodie%' THEN 'Hoodie'
WHEN LOWER(p.name) LIKE '%coat%' THEN 'Coat'
ELSE 'Other'
END AS keyword
FROM `your-project-id.thelook_ecommerce.products` p
JOIN `your-project-id.thelook_ecommerce.order_items` oi
ON p.id = oi.product_id
)
SELECT
keyword,
COUNT(*) AS sales_count,
SUM(sale_price) AS total_revenue,
AVG(sale_price) AS avg_sale_price
FROM keyword_sales
WHERE keyword != 'Other'
GROUP BY keyword
ORDER BY total_revenue DESC실행 결과:
| keyword | sales_count | total_revenue | avg_sale_price |
|---|---|---|---|
| Sweater | 8656 | 610092.34 | 70.48 |
| Coat | 4589 | 526580.48 | 114.75 |
| Hoodie | 6966 | 373351.96 | 53.60 |
성능 고려사항:
%keyword%: 전체 테이블 스캔 (느림)keyword%: 인덱스 활용 가능 (빠름)- 대용량 데이터: Full-text search 엔진 고려
3. SUBSTR/LEFT/RIGHT - 부분 문자열 추출
이론
문자열의 일부분만 추출합니다.
함수들
SUBSTR(string, start, length): 지정 위치에서 지정 길이만큼LEFT(string, length): 왼쪽에서 N자RIGHT(string, length): 오른쪽에서 N자
예시
SUBSTR('Hello World', 1, 5) → 'Hello'
LEFT('Hello World', 5) → 'Hello'
RIGHT('Hello World', 5) → 'World'인덱스
- BigQuery: 1부터 시작 (1-indexed)
실습: SKU 코드 분석
SELECT
id AS product_id,
name,
sku,
LEFT(sku, 3) AS sku_prefix,
SUBSTR(sku, 4, 3) AS sku_middle,
RIGHT(sku, 3) AS sku_suffix,
LENGTH(sku) AS sku_length
FROM `your-project-id.thelook_ecommerce.products`
WHERE sku IS NOT NULL
LIMIT 30실행 결과:
| product_id | name | sku | sku_prefix | sku_middle | sku_suffix | sku_length |
|---|---|---|---|---|---|---|
| 13844 | (ONE) 1 Satin Headband | 2A3E95… | 2A3 | E95 | 4C8 | 32 |
| 14086 | CHEER Rhinestone… | 8EFA90… | 8EF | A90 | 4AD | 32 |
퀴즈 3: 제품 코드 패턴 분석
문제
products 테이블에서:
- SKU의 앞 2자리를 카테고리 코드로 추출
- 뒤 2자리를 제품 타입 코드로 추출
- 카테고리 코드별 제품 수 및 평균 가격 집계
힌트: LEFT(sku, 2), RIGHT(sku, 2)
정답 보기
SELECT
LEFT(sku, 2) AS category_code,
RIGHT(sku, 2) AS type_code,
COUNT(*) AS product_count,
AVG(retail_price) AS avg_price,
MIN(retail_price) AS min_price,
MAX(retail_price) AS max_price
FROM `your-project-id.thelook_ecommerce.products`
WHERE sku IS NOT NULL
GROUP BY category_code, type_code
HAVING COUNT(*) >= 5
ORDER BY product_count DESC
LIMIT 20실행 결과:
| category_code | type_code | product_count | avg_price | min_price | max_price |
|---|---|---|---|---|---|
| 2A | C7 | 125 | 85.50 | 12.99 | 450.00 |
| 8E | D3 | 118 | 92.30 | 15.00 | 380.00 |
| 1C | D9 | 112 | 78.25 | 10.50 | 320.00 |
실무 활용:
- SKU 체계 분석: 제품 코드 규칙 파악
- 재고 관리: 코드별 재고 현황
- 자동 분류: 코드 기반 카테고리 할당
4. UPPER/LOWER/TRIM - 문자열 정규화
이론
데이터 정제 및 표준화에 사용합니다.
함수들
UPPER(string): 대문자 변환LOWER(string): 소문자 변환TRIM(string): 앞뒤 공백 제거LTRIM(string): 왼쪽 공백 제거RTRIM(string): 오른쪽 공백 제거
활용
- 대소문자 통일
- 공백 제거
- 중복 제거
- 데이터 표준화
실습: 브랜드명 정규화
WITH normalized_brands AS (
SELECT
brand AS original_brand,
UPPER(TRIM(brand)) AS normalized_brand,
COUNT(*) AS product_count
FROM `your-project-id.thelook_ecommerce.products`
WHERE brand IS NOT NULL
GROUP BY brand
)
SELECT
normalized_brand,
SUM(product_count) AS total_products,
COUNT(DISTINCT original_brand) AS brand_variations
FROM normalized_brands
GROUP BY normalized_brand
HAVING COUNT(DISTINCT original_brand) > 1 OR SUM(product_count) >= 10
ORDER BY brand_variations DESC, total_products DESC
LIMIT 20실행 결과:
| normalized_brand | total_products | brand_variations |
|---|---|---|
| VIP BOUTIQUE | 29 | 3 |
| TURKISHTOWELS | 7 | 3 |
| HUGO BOSS | 135 | 2 |
| ICEBREAKER | 70 | 2 |
⚠️
정규화의 중요성
- 중복 제거: ‘Gmail.com’과 ‘gmail.com’을 동일하게 취급
- 공백 문제: ’ user@example.com ‘과 ‘user@example.com’ 통일
- 일관성: 분석 및 집계 정확도 향상
퀴즈 4: 이메일 도메인 정규화 및 분석
문제
users 테이블에서:
- 이메일 주소를 소문자로 변환하고 공백 제거
- ’@’ 이후 도메인 추출 (SPLIT 활용)
- 도메인별 고객 수 집계
힌트: LOWER(TRIM(email)), SPLIT(email, ’@’)[OFFSET(1)]
정답 보기
WITH normalized_emails AS (
SELECT
id AS user_id,
email,
LOWER(TRIM(email)) AS clean_email,
SPLIT(LOWER(TRIM(email)), '@')[SAFE_OFFSET(1)] AS email_domain
FROM `your-project-id.thelook_ecommerce.users`
WHERE email IS NOT NULL
)
SELECT
email_domain,
COUNT(*) AS customer_count,
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM normalized_emails), 2) AS percentage
FROM normalized_emails
WHERE email_domain IS NOT NULL
GROUP BY email_domain
ORDER BY customer_count DESC
LIMIT 10실행 결과:
| email_domain | customer_count | percentage |
|---|---|---|
| example.net | 33601 | 33.60 |
| example.org | 33212 | 33.21 |
| example.com | 33187 | 33.19 |
실무 활용:
- 이메일 마케팅 세분화
- 회사 이메일 vs 개인 이메일 구분
- B2B vs B2C 분석
5. SPLIT - 문자열 분리
이론
구분자를 기준으로 문자열을 배열로 분리합니다.
구문
SPLIT(string, delimiter)
-- 결과: ARRAY배열 인덱싱
SPLIT('a,b,c', ',')[OFFSET(0)] → 'a'
SPLIT('a,b,c', ',')[OFFSET(1)] → 'b'
SPLIT('a,b,c', ',')[SAFE_OFFSET(10)] → NULL (안전)활용
- CSV 파싱
- 태그 분리
- 경로 분해
- 이메일, URL 분석
실습: 이메일 분석
WITH email_parts AS (
SELECT
id AS user_id,
email,
SPLIT(email, '@')[SAFE_OFFSET(0)] AS username,
SPLIT(email, '@')[SAFE_OFFSET(1)] AS domain,
ARRAY_LENGTH(SPLIT(email, '@')) AS parts_count
FROM `your-project-id.thelook_ecommerce.users`
WHERE email IS NOT NULL
AND email LIKE '%@%'
)
SELECT
user_id,
email,
username,
domain,
LENGTH(username) AS username_length,
CASE
WHEN domain LIKE '%.com' THEN 'Commercial'
WHEN domain LIKE '%.org' THEN 'Organization'
WHEN domain LIKE '%.edu' THEN 'Education'
WHEN domain LIKE '%.gov' THEN 'Government'
ELSE 'Other'
END AS domain_type
FROM email_parts
WHERE parts_count = 2
LIMIT 30실행 결과:
| user_id | username | domain | username_length | domain_type | |
|---|---|---|---|---|---|
| 97381 | dianamartinez@example.org | dianamartinez | example.org | 13 | Organization |
| 30683 | breannafrancis@example.org | breannafrancis | example.org | 14 | Organization |
퀴즈 5: 제품명에서 키워드 추출
문제
products 테이블에서:
- 제품명을 공백 기준으로 SPLIT
- 첫 번째 단어를 주요 키워드로 추출
- 키워드별 제품 수 및 평균 가격 집계
힌트: SPLIT(name, ’ ’)[SAFE_OFFSET(0)]
정답 보기
WITH product_keywords AS (
SELECT
id AS product_id,
name,
retail_price,
UPPER(SPLIT(TRIM(name), ' ')[SAFE_OFFSET(0)]) AS first_keyword
FROM `your-project-id.thelook_ecommerce.products`
WHERE name IS NOT NULL
)
SELECT
first_keyword,
COUNT(*) AS product_count,
AVG(retail_price) AS avg_price,
MIN(retail_price) AS min_price,
MAX(retail_price) AS max_price
FROM product_keywords
WHERE first_keyword IS NOT NULL
AND LENGTH(first_keyword) > 2
GROUP BY first_keyword
HAVING COUNT(*) >= 5
ORDER BY product_count DESC
LIMIT 15실행 결과:
| first_keyword | product_count | avg_price | min_price | max_price |
|---|---|---|---|---|
| ALLEGRA | 1034 | 14.13 | 3.28 | 41.63 |
| CALVIN | 615 | 64.26 | 10.00 | 259.99 |
| CARHARTT | 387 | 68.86 | 7.00 | 448.99 |
| TOMMY | 324 | 70.88 | 9.00 | 289.99 |
| VOLCOM | 293 | 58.54 | 11.96 | 320.00 |
SPLIT 활용 시나리오:
- 태그 분석: 쉼표로 구분된 태그 분리
- 경로 파싱: URL, 파일 경로 분해
- 키워드 추출: 제품명, 설명에서 핵심 단어
정리
학습한 String Functions
| 함수 | 용도 | 예시 |
|---|---|---|
CONCAT() | 문자열 결합 | CONCAT(first_name, ’ ’, last_name) |
LIKE | 패턴 매칭 | name LIKE ‘%shirt%‘ |
SUBSTR() | 부분 문자열 | SUBSTR(sku, 1, 3) |
LEFT/RIGHT() | 앞/뒤 N자 | LEFT(sku, 2) |
UPPER/LOWER() | 대소문자 변환 | UPPER(brand) |
TRIM() | 공백 제거 | TRIM(email) |
SPLIT() | 문자열 분리 | SPLIT(email, ’@‘) |
추가 유용한 함수들
| 함수 | 설명 |
|---|---|
LENGTH() | 문자열 길이 |
REPLACE() | 문자열 치환 |
REGEXP_CONTAINS() | 정규표현식 매칭 |
REGEXP_EXTRACT() | 정규표현식 추출 |
FORMAT() | 포맷팅 |
데이터 정제 패턴
-- 1. 정규화
UPPER(TRIM(column))
-- 2. 이메일 도메인 추출
SPLIT(LOWER(TRIM(email)), '@')[SAFE_OFFSET(1)]
-- 3. 코드 파싱
LEFT(sku, 3) AS category_code
-- 4. 키워드 검색
WHERE LOWER(name) LIKE '%keyword%'
-- 5. 포맷팅
CONCAT('[', brand, '] ', name)다음 단계
문자열 함수를 마스터했습니다! 이제 Pandas 트랙에서 Python 기반 데이터 분석을 배워보거나, 시각화에서 데이터 시각화 기법을 학습하세요.
Last updated on