← 전체 해설 목록  ·  week-02 · 1강

1강 — 텍스트 정제는 왜 하는가 & 정규표현식 입문

🎯 학습 목표

📖 해설

1. 수집했다고 바로 분석하면 안 됩니다

이틀째입니다. 어제 우리는 원하는 게임의 리뷰 데이터를 직접 수집했죠. 그럼 이걸 바로 AI에게 넘기면 될까요? 실무에서 가장 흔한 실수가 바로 이겁니다. 수집에 성공했으니 곧장 분석하고 싶은 마음은 충분히 이해합니다. 그런데 실제 리뷰를 열어 보면 이런 게 잔뜩 섞여 있습니다.

이 상태로 GPT·Claude 같은 AI에 보내면 두 가지가 나빠집니다. 한 달에 50달러 쓸 걸 70달러 쓰게 되고, 분석 품질도 떨어집니다. 쓸데없는 데이터에 AI가 혼란을 느끼거든요. 그래서 오늘은 수집과 분석 사이의 핵심 단계, 텍스트 정제를 배웁니다. 정제를 잘 하면 비용도 아끼고 품질도 올리는, 두 마리 토끼를 잡는 작업입니다.

오늘의 흐름: ① 1일차 핵심 복습 → ② 정제를 왜 하나(비용 관점) → ③ 비용 절감 시뮬레이터 체험 → ④ 정제 기준 세우기 → ⑤ 정규표현식 입문.

2. 1일차 핵심 3가지 복습

오늘 정제를 배우는 데 기초가 되는 어제 개념 세 가지부터 짚겠습니다.

  1. 텍스트 분석의 가치. 사람이 리뷰 10만 개를 하루 100개씩 읽으면 1,000일이 걸립니다. AI는 같은 일을 훨씬 짧은 시간에 끝내고 패턴까지 뽑아냅니다. 그런데 사실 AI를 통한 리뷰 분석은 그동안 잘 이뤄지지 않던 영역이에요. 우리가 배우는 게 그만큼 최신·최첨단 기술이라는 뜻입니다.
  2. AI가 텍스트를 이해하는 원리. 컴퓨터는 숫자만 계산합니다. 그런데 텍스트는 숫자가 아니죠. 그래서 "재미있다"라는 단어를 [0.8, 0.2, 0.5] 같은 숫자 배열로 바꿉니다. 이걸 벡터라고 하고, 비슷한 의미는 비슷한 숫자로 구성됩니다. (자세한 건 이번 주 3강 임베딩에서)
  3. API 구조. "이 게임 리뷰 주세요"라고 요청하면 JSON 형식으로 데이터가 옵니다. 은행 창구에서 양식을 제출하고 서류를 받는 것과 같아요. 어제 쓴 steamreviews 라이브러리가 이 API 호출을 대신해 준 거고요. 수집 방법 중에서도 API는 가장 근본적이고 대표적인 방식입니다.

3. 자주 헷갈리는 개념 3가지

  1. 수집 방법은 3가지뿐. ① 공개 데이터셋 ② API ③ 웹 스크래핑. — 임베딩은 수집이 아닙니다. 이미 가진 텍스트를 숫자로 바꾸는 후속 작업이에요.
  2. API vs 스크래핑. 어떤 스타트업이 스크래핑으로 데이터를 모았다가 소송당한 사례가 있었습니다. 둘 다 가능하면 대부분 API가 정답입니다. 공식 API가 있으면 안전하게 쓸 수 있으니까요.
  3. "할 수 있다(Can)"와 "해도 된다(May)"는 다릅니다. 면접에도 종종 나오는 개념이에요. 기술적으로 Steam API로 리뷰를 수집할 수 있다는 것과, 그걸 상업적으로 팔아도 되느냐는 완전히 다른 문제입니다. 후자는 이용약관을 봐야 합니다. 이걸 구분 못 하면 법적 문제가 생길 수 있어요. AI가 발전할수록 윤리·법적 기준은 점점 강화되는 추세입니다.

4. 어제 프로젝트(8차시) 회고

어제 여러분은 직접 게임을 고르고 리뷰를 수집했습니다. 성과를 짚어 보죠. - 처음으로 진짜 데이터를 수집했습니다. 교과서 예제가 아니라 내가 궁금한 게임의 실제 리뷰예요. 실무는 늘 이런 실제 데이터로 일합니다. - 라이브러리의 힘을 느꼈을 겁니다. API를 직접 호출하려면 URL 구성·페이지네이션·에러 처리를 다 해야 하는데, steamreviews가 그걸 다 해줘서 코드 몇 줄로 끝났죠. - 문제가 생긴 경우도 있었을 텐데, 대부분 정상입니다. - 리뷰가 100개밖에 안 나왔다 → 그 게임에 한국어 리뷰가 원래 적을 수 있습니다. - CSV가 깨졌다 → 한국어를 다룰 때 자주 나오는 인코딩 문제예요. 인코딩만 맞춰 주면 됩니다. - 이런 건 실수가 아니라 "이럴 땐 이렇게 해결한다"로 기억해 두면 됩니다.

핵심은, 이제 여러분이 원하는 게임의 실제 데이터를 수집할 수 있게 됐다는 사실입니다.

5. 정제를 왜 하나 ① — 노이즈는 분석을 망칩니다

정제 없이 그냥 GPT-4에 리뷰를 보내면, 분석 결과에 "ㅋㅋ가 많이 나타난" 같은 엉뚱한 인사이트가 나올 수 있습니다. 의미가 없죠. 노이즈가 분석을 방해하는 겁니다. 게다가 AI는 ㅋㅋㅋ와 ㅋ 중 뭐가 더 긍정적인지 헷갈려 합니다. 분석 정확도가 떨어지는 거예요.

6. 정제를 왜 하나 ② — 노이즈는 곧 돈입니다

ㅋㅋㅋ가 10개 있으면 의미는 같은데 토큰은 10배입니다. AI API는 토큰당 과금이거든요. 그러니 노이즈가 그대로 돈을 버리는 셈입니다.

구체적으로 계산해 볼까요. 스팀 리뷰 1만 건을 GPT-4급 모델로 분석한다고 합시다.

리뷰당 평균 토큰 총 토큰 비용(약)
정제 안 함 150토큰 150만 토큰 15달러
정제 함 100토큰 100만 토큰 10달러

한 번에 5달러 절약입니다. 별것 아닌 것 같죠? 그런데 규모를 생각해 보세요.

스타트업에서 이 정도면 인턴 한 달 월급 수준입니다. 정제 코드 30분 짜는 게 한 사람 월급을 아끼는 활동일 수 있다는 거예요. 일반화하면, 노이즈를 20% 제거하면 API 비용도 약 20% 줄어든다고 보면 됩니다.

강의에서는 수치를 직접 조정하며 절감액을 체감하는 시뮬레이터도 소개했습니다. 코드를 어떻게 짜고 얼마나 정제하느냐에 따라 비용이 크게 달라지는 걸 피부로 느껴 보라는 취지예요.

7. 정제 기준은 "분석 목적"에 따라 달라집니다 (가장 중요)

무엇을 남기고 무엇을 버릴까요? 정답은 분석 목적마다 다릅니다. 이게 핵심이에요. 그리고 이 지점이 바로 기업들이 강조하는 도메인 지식과 직접 연결됩니다. 같은 리뷰 데이터로 두 가지 분석을 한다고 해 봅시다.

리뷰 예시 감성 분석이라면 버그 추출이라면
"이 게임 너무 재밌어요 ㅋㅋㅋ 추천" ㅋㅋㅋ는 남김 (긍정 감정 표현) ㅋㅋㅋ·"재밌어요" 감정은 버림
"라이젠 5600에서 잘 돌아감" 사양 정보라 버림 (감성과 무관) 남김 (성능 관련 정보)
"이 스테이지에서 렉 발생, 짜증나요" "짜증나요" 남김 "렉 발생"만 남기고 "짜증나요"는 버림

같은 데이터인데 정제 기준이 정반대죠. 그래서 정제 전에 "이 분석으로 뭘 알고 싶은가"를 먼저 정해야 합니다. 목적 없이 정제하면 필요한 정보를 버리는 실수를 하게 됩니다.

8. 정제 체크리스트

  1. 명백히 제거할 것 — 빈 리뷰, 한 글자 리뷰(점 하나 등), 그림(아스키 아트)만 있는 리뷰. "추천 버튼만 누르고 대충 쓴" 것들이라 의미가 없고 토큰만 낭비합니다.
  2. 압축할 것 — ㅋㅋㅋㅋ 같은 반복. 감정 표현은 유지하면서 토큰만 줄이도록 ㅋㅋ 정도로 압축합니다.
  3. 상황별로 판단할 것 — 대표적으로 이모지. 감성 분석에선 웃는/우는 이모지가 중요한 정보라 남기고, 토픽(키워드) 추출에선 방해가 되니 제거합니다.

9. 정규표현식 — 외우는 문법이 아니라 도구입니다

이제 정규표현식(Regular Expression, 줄여서 regex)을 봅시다. 복잡해 보이고 외울 게 많아 보이죠. 그런데 비용 관점에서 보면 생각이 달라집니다.

상사가 "리뷰 1만 건에서 ㅋㅋㅋ 반복을 다 정리해 와"라고 했다고 합시다. 수작업이면 엑셀 열고, 리뷰 하나 확인하고, 찾아 고치고… 한 건에 3초만 잡아도 1만 건이면 3만 초 = 약 8시간. 하루 종일 기계처럼 이 작업만 해야 하고 실수도 납니다. 정규표현식이면 코드 한 줄 + 실행 3초면 끝나요. 8시간 대 3초, 약 9,600배입니다. 이게 정규표현식의 비즈니스 가치예요.

정규표현식은 텍스트에서 패턴을 찾는 검색 언어입니다. 엑셀의 "찾기·바꾸기"의 고급 버전이라고 보면 돼요. 예를 들어 개인정보 보호를 위해 리뷰에서 전화번호를 다 지워야 한다고 합시다. 엑셀 찾기·바꾸기로는 010-1234-5678처럼 가능한 번호를 하나하나 다 검색해야 하니 사실상 불가능합니다. 정규표현식으로는 "010으로 시작 + 숫자 4개 + 하이픈 + 숫자 4개"라는 패턴 하나면 모든 전화번호를 한 번에 찾습니다.

핵심: 정확한 문자열을 몰라도, 패턴만 알면 찾을 수 있다. "ㅋ가 3번 이상 반복", "느낌표가 연속", "숫자로만 이뤄짐" 같은 식으로요.

10. 실무 80%를 커버하는 패턴 3가지

패턴을 다 외울 필요는 없습니다. 이 세 가지면 대부분의 정제 작업이 됩니다.

  1. 반복 압축 — 어떤 문자든 연속 반복되면 찾아 줄여서 토큰을 확 줄입니다. (ㅋㅋㅋㅋㅋ → ㅋㅋ)
  2. 패턴 제거 — 대괄호 [...] 안에 지우고 싶은 문자들을 넣어 한 번에 제거합니다.
  3. 숫자 추출 — 텍스트에서 숫자만 쏙 뽑아냅니다. ("10시간 플레이함" → 10)

더 복잡한 패턴이 필요하면 그때 ChatGPT나 검색으로 만들면 됩니다.

# 정규표현식은 파이썬 기본 라이브러리 re 로 씁니다. (re = Regular Expression)
import re

text = "이 게임 진짜 재밌다ㅋㅋㅋㅋㅋ!!!!! 010-1234-5678 로 연락주세요. 10시간 플레이함"

# ① 반복 압축: 같은 문자가 3번 이상 연속되면 2번으로 줄이기
#    re.sub(찾을_패턴, 바꿀_내용, 원본_텍스트)
#    (.)\1{2,} → 어떤 문자 하나(.)가 그 자신(\1)을 2번 이상 더 반복
compressed = re.sub(r'(.)\1{2,}', r'\1\1', text)
print(compressed)  # ...재밌다ㅋㅋ!! ...

# ② 패턴 제거: 전화번호(010-숫자4-숫자4)를 통째로 지우기
no_phone = re.sub(r'010-\d{4}-\d{4}', '', compressed)
print(no_phone)

# ③ 숫자 추출: 텍스트에서 숫자만 모두 뽑기
numbers = re.findall(r'\d+', text)
print(numbers)  # ['010', '1234', '5678', '10']

이 코드도 통째로 외울 필요는 없습니다. "반복 문자를 압축하고 싶다 / 특정 문자를 제거하고 싶다 / 숫자를 뽑고 싶다"고 AI에게 말로 요청하면 깔끔한 정규표현식을 만들어 줍니다. "한국 전화번호 찾는 정규식 만들어 줘"처럼요. AI 시대에 이런 걸 암기하는 데 시간을 쓰는 건 낭비에 가깝습니다.

정규식이 의도대로 동작하는지 테스트하려면 regex101.com 같은 사이트에 패턴을 넣고 확인하면 편합니다. 강의에서도 테스트용 사이트를 하나 소개했어요. 치트시트로 챙겨 둘 기호는 몇 개 안 됩니다. \d는 숫자, \s는 공백, +는 "1번 이상 반복", [ ]는 "이 안의 문자 중 하나"라는 의미예요. 이 정도만 알면 나머지는 그때그때 찾아 쓰면 됩니다.

핵심은 이겁니다. 정규표현식은 문법이 아니라 도구입니다. 망치 사용법을 다 외우고 못 박는 사람은 없죠. 못 박을 일이 생기면 그때 들고 두드리면 됩니다. (다 외울 필요 없이 기본 치트시트 정도만 갖고 있어도 됩니다.)

11. 노이즈만 노이즈가 아닙니다 — 결측치와 중복

지금까지는 ㅋㅋㅋ·느낌표 같은 "글자 안의 노이즈"를 다뤘습니다. 그런데 표 형태로 수집한 리뷰 데이터에는 글자가 아니라 칸 단위의 노이즈도 섞여 있습니다. 어제 CSV로 저장한 데이터를 표로 펼쳐 보면 두 가지가 꼭 눈에 띕니다.

이걸 그냥 두고 분석하면 결과가 통째로 어긋납니다. 데이터 분석에는 오래된 격언이 하나 있어요.

GIGO — Garbage In, Garbage Out. 쓰레기를 넣으면 쓰레기가 나옵니다.

아무리 좋은 AI나 통계 기법을 써도, 입력 데이터가 엉망이면 결과도 엉망이라는 뜻입니다. 그래서 "글자 정제"와 함께 "칸 정제"도 분석 전에 반드시 거쳐야 하는 단계입니다.

결측치 — 먼저 "얼마나 비었나"부터 확인합니다

결측치는 무작정 지우거나 채우기 전에, 어느 열이 얼마나 비었는지 숫자로 확인하는 게 먼저입니다. 게임 리뷰를 흉내 낸 작은 표로 연습해 보겠습니다.

import pandas as pd   # 표(엑셀 같은 데이터)를 다루는 라이브러리
import numpy as np

# 결측치(None)와 빈 문자열, 중복이 섞인 샘플 데이터
data = {
    'review_id': ['r001', 'r002', 'r003', 'r004', 'r005', 'r003', 'r006', 'r007'],
    'review_text': ['정말 재미있어요!', '별로예요...', '최고의 게임입니다',
                    None,            # 결측치
                    '그냥 그래요',
                    '최고의 게임입니다',   # r003 중복
                    '강추합니다',
                    ''],             # 빈 문자열
    'voted_up': [True, False, True, True, False, True, True, None],
    'playtime_hours': [120.5, 5.0, 200.0, None, 15.0, 200.0, None, 50.0],
    'helpful_votes': [10, 2, None, 5, 0, None, 3, 1],
}
df = pd.DataFrame(data)

# ① 열별 결측치 "개수" — isnull()은 빈 칸이면 True, sum()으로 더하기
print(df.isnull().sum())

# ② 열별 결측치 "비율(%)" — 개수만으로는 심각한지 모르니 전체 대비 비율도 본다
print((df.isnull().sum() / len(df) * 100).round(2))

playtime_hours처럼 25%가 비어 있으면 "버릴지 채울지" 고민이 필요하고, review_id처럼 0%면 그대로 두면 됩니다. 개수와 비율을 같이 보는 습관이 중요합니다. (실제 출력은 데이터에 따라 달라집니다.)

결측치 처리 — 제거할지 채울지는 "경우에 따라"

결측치를 다루는 방법은 크게 두 가지이고, 정제 기준과 마찬가지로 상황에 따라 골라 씁니다.

(1) 제거 — dropna. 결측치가 적을 때(대략 5% 이하)나, 리뷰 텍스트처럼 채워 넣을 수 없는 값일 때 씁니다. 간단하지만 데이터가 줄어드는 단점이 있어요.

# 결측치가 하나라도 있는 행을 전부 제거 — 데이터가 확 줄 수 있어 주의
df_dropped = df.dropna()

# 더 흔한 방식: "이 열이 비면 분석이 불가능하다" 싶은 열만 골라 제거
#   리뷰 텍스트가 없으면 감성분석 자체가 안 되므로 그 행만 버린다
df_dropped = df.dropna(subset=['review_text'])

전체 dropna()는 한 칸만 비어도 행을 통째로 날리기 때문에, 위 예시 데이터에서는 8행이 3행까지 줄어듭니다. 그래서 보통은 꼭 필요한 열만 subset으로 지정해 제거하는 쪽이 안전합니다.

(2) 대체 — fillna. 결측치가 많아 버리기 아까울 때, 비어 있는 칸을 적당한 값으로 채웁니다. 무엇으로 채우느냐는 열의 성격에 따라 다릅니다.

열 유형 대체 값
숫자(연속값) 열 평균 또는 중앙값
카운트(횟수) 열 0
카테고리(범주) 열 최빈값(가장 많이 나온 값)
df_filled = df.copy()   # 원본은 보존하고 복사본에서 작업

# 플레이 시간(숫자)은 평균으로 채우기 — 비어 있던 칸이 평균값으로 채워진다
df_filled['playtime_hours'] = df_filled['playtime_hours'].fillna(
    df_filled['playtime_hours'].mean())

# 추천수(횟수)는 0으로 채우기 — "추천이 없었다"고 보는 게 자연스럽다
df_filled['helpful_votes'] = df_filled['helpful_votes'].fillna(0)

여기서도 도메인 지식이 갈립니다. 추천수가 비었을 때 평균으로 채우면 "받지도 않은 추천"을 만들어 내는 셈이라 0이 맞고, 플레이 시간은 0으로 채우면 "안 한 사람"으로 왜곡되니 평균이 낫습니다. 무엇으로 채울지는 그 값이 게임에서 무엇을 뜻하는지 알아야 제대로 정할 수 있어요.

중복 — 같은 리뷰 하나가 통계를 흔듭니다

중복이 위험한 이유는 분명합니다. 긍정 리뷰 1개가 10번 중복되면 긍정 비율이 부풀려집니다. "추천이 압도적이네요"라는 결론이 사실은 같은 리뷰 한 개였던 거죠. 그래서 중복도 확인하고 제거해야 합니다.

# 완전히 똑같은 행이 몇 개인지 확인
print(df.duplicated().sum())

# 리뷰 고유 ID(review_id)가 같으면 같은 리뷰로 보고 확인할 수도 있다
print(df.duplicated(subset=['review_id']).sum())

# 중복 제거 — 기본 동작은 "처음 것만 남기고 뒤의 중복을 버린다"
df_unique = df.drop_duplicates(subset=['review_id'])

전체 행이 똑같아야 중복으로 보는 drop_duplicates()보다, subset=['review_id']처럼 "무엇이 같으면 같은 리뷰인가"를 지정하는 편이 실무에선 더 정확합니다. 텍스트는 같아도 다른 사람이 우연히 똑같이 쓴 경우와, 정말 같은 리뷰가 두 번 들어온 경우를 구분해 주거든요.

전처리는 한 번 만들어 재사용합니다

결측치 처리와 중복 제거는 데이터를 받을 때마다 반복할 작업입니다. 그래서 함수로 묶어 두면 다음에 다른 게임 리뷰를 받아도 한 줄로 끝납니다. 어제 steamreviews 라이브러리가 복잡한 API 호출을 한 줄로 줄여 준 것과 같은 발상이에요.

def preprocess_reviews(df):
    """리뷰 데이터 전처리: 결측치 처리 + 중복 제거를 한 번에"""
    df = df.copy()                       # 원본 보존

    # 1) 결측치 처리
    df = df.dropna(subset=['review_text'])            # 텍스트 없는 행 제거
    df = df[df['review_text'].str.strip() != '']      # 빈 문자열(공백뿐) 행도 제거
    df['playtime_hours'] = df['playtime_hours'].fillna(df['playtime_hours'].mean())
    df['helpful_votes'] = df['helpful_votes'].fillna(0)

    # 2) 중복 제거 (리뷰 ID 기준)
    df = df.drop_duplicates(subset=['review_id'])

    return df

df_clean = preprocess_reviews(df)   # 다음부터는 이 한 줄이면 끝

이렇게 하면 8행짜리 예시 데이터가 결측·빈 문자열·중복을 걷어낸 5행으로 정리됩니다. 실제 수만 건짜리 리뷰에서도 같은 함수가 그대로 동작해요. (수치는 데이터에 따라 달라집니다.)

💡 오늘을 한 문장으로: "1시간 정제가 10시간 분석보다 가치 있다." 정제는 귀찮은 작업이 아니라, 비용을 줄이고 분석 품질을 올리는 투자입니다.

🔑 핵심 용어

용어 쉬운 설명
정제(cleansing) 분석 전에 리뷰에서 노이즈를 걷어내고 다듬는 작업
노이즈 ㅋㅋㅋ·느낌표 떼·빈 리뷰처럼 의미 없이 토큰만 먹는 것
토큰(token) AI가 글을 처리하는 단위. API 과금의 기준
정규표현식(regex) 텍스트에서 "패턴"으로 검색·치환하는 언어 (re 라이브러리)
re.sub 패턴을 찾아 다른 것으로 바꾸기
re.findall 패턴에 맞는 부분을 모두 찾아 리스트로
Can vs May "기술적으로 가능" ≠ "법적으로 허용". 이용약관 확인
GIGO "Garbage In, Garbage Out". 입력이 엉망이면 결과도 엉망
결측치(Missing Value) 값이 비어 있는 칸. NaN·None·null로 표시
중복(Duplicate) 같은 리뷰가 두 번 이상 들어온 행 (통계 왜곡)
dropna / fillna 결측치를 제거 / 다른 값으로 채우기
drop_duplicates 중복 행을 제거 (기본은 처음 것만 남김)

🎮 게임 기획자 시점

정제 기준이 "분석 목적에 따라 다르다"는 말은, 곧 도메인 지식이 정제의 품질을 좌우한다는 뜻입니다. "라이젠 5600에서 잘 돌아감"이 감성 분석엔 노이즈지만 버그·성능 분석엔 금쪽같은 신호라는 걸 아는 사람은, 게임을 아는 기획자예요. 즉 무엇을 남기고 버릴지 결정하는 단계에서 기획자의 강점이 그대로 발휘됩니다. 또 "정제 30분이 연 수백만 원을 아낀다"는 감각은, 기능 하나의 비용 대비 가치를 따지는 기획자의 BM 감각과도 똑같습니다.

⚠️ 흔한 오해 / 짚고 갈 점

✅ 30초 정리

  1. 정제는 비용 절감입니다. 노이즈 20%를 줄이면 API 비용도 약 20% 줄어요(연 수백만 원 규모).
  2. 정제 기준은 분석 목적에 따라 달라집니다. 감성 분석과 버그 추출은 남길 것·버릴 것이 정반대예요.
  3. 정규표현식은 생산성 도구입니다. 8시간 걸릴 일을 3초에. 패턴 3가지만 알거나, AI에게 시키면 됩니다.
  4. 글자뿐 아니라 칸도 정제합니다. 결측치(dropna/fillna)와 중복(drop_duplicates)을 거르지 않으면 GIGO — 입력이 엉망이면 결과도 엉망입니다.

▶️ 다음 강 연결

오늘은 왜 정제하는지와 기준을 배웠습니다. 다음 2강에서는 실제로 노이즈를 제거하고 텍스트를 표준화(정규화)하는 구체적 기법을 하나하나 코드로 해봅니다.

전체📚 강의 목록