개요

앞선 글에서 NumPy·Pandas로 데이터를 계산하고, Matplotlib·Seaborn으로 그림을 그리는 데까지 왔다. 그런데 AI를 하겠다고 검색을 시작하면 바로 벽에 부딪힌다.

scikit-learn, PyTorch, TensorFlow, Keras, transformers, XGBoost, LightGBM… 이걸 다 배워야 하나?

처음엔 이름부터 외우려고 했다. 각 라이브러리의 대표 함수를 정리한 표를 만들어두고 외웠는데, 일주일 만에 전부 날아갔다. 맥락 없이 외운 건 남지 않는다는 걸 그때 알았다.

방향을 바꿔서 “이건 어느 자리에 있는 도구인가”부터 정리하니 그제야 붙기 시작했다. 결론부터 말하면 다 배울 필요가 없다. 이것들은 서로 경쟁하는 물건이 아니라 각자 맡은 자리가 다른 도구다.

이번 글은 그 지도를 그리는 작업이다. 어떤 라이브러리가 어느 자리에 있고 언제 무엇을 꺼내 쓰는지부터 정리하고, 그다음 실제로 가장 많은 시간이 들어가는 데이터 정제를 다룬다.


1. 라이브러리는 층으로 쌓여 있다

가장 먼저 알아야 할 사실이 있다. 이 도구들은 나란히 놓인 게 아니라 위아래로 쌓여 있다.

라이브러리 지도

무엇을 하나
NumPy 숫자 덩어리(배열)를 빠르게 계산한다. 모든 것의 바닥
pandas · matplotlib · seaborn 표를 다루고 그림으로 확인한다
scikit-learn 머신러닝 — 분할·전처리·모델·평가를 한 문법으로
PyTorch 딥러닝 — 신경망을 직접 만들고 학습시킨다
transformers 남이 학습해둔 큰 모델을 가져다 쓴다

왜 이게 중요한가

위층은 아래층을 부른다. pandas의 표 안에는 NumPy 배열이 들어 있고, scikit-learn에 넘기는 데이터도 결국 NumPy 배열이다.

그래서 이런 일이 생긴다.

  • pandas에서 배운 인덱싱 감각이 NumPy에서 그대로 통한다
  • scikit-learn 에러 메시지에 NumPy 이야기가 나온다
  • PyTorch 텐서와 NumPy 배열은 거의 같은 문법으로 쓴다

바닥을 알면 위층이 쉬워진다. 반대로 바닥을 건너뛰면 위층에서 계속 헤맨다. 그래서 이 시리즈가 NumPy부터 시작했던 것이다.

더 쉬운 비유로. 라이브러리는 주방 도구와 같다. 칼(NumPy)은 모든 요리의 기본이고, 도마와 그릇(pandas)은 재료를 정리하는 자리다. 오븐(scikit-learn)불(PyTorch)은 실제로 익히는 도구고, 밀키트(transformers)는 누가 손질해둔 재료를 사 오는 것이다.

밀키트만 쓸 거면 칼질을 몰라도 되지만, 조금만 바꾸려 하면 결국 칼을 잡아야 한다.


2. 작업 순서로 보면 자리가 보인다

라이브러리를 목록으로 외우면 금방 헷갈린다. 작업 순서에 놓고 보면 훨씬 명확해진다.

작업 흐름별 라이브러리

단계 쓰는 도구 하는 일
① 불러오기 pandas csv·엑셀을 표로 읽는다
② 살펴보기 seaborn 그림으로 감을 잡는다
③ 정제 pandas 빈칸과 튀는 값을 처리한다
④ 준비 scikit-learn 데이터를 나누고 단위를 맞춘다
⑤ 학습 sklearn / PyTorch 모델을 만들고 돌린다
⑥ 평가 scikit-learn 지표로 채점한다

⑤번에서만 갈라진다. 표 형태의 데이터라면 scikit-learn으로 충분하고, 이미지·텍스트처럼 복잡한 데이터면 PyTorch로 간다. 나머지 단계는 어느 쪽이든 거의 같다.

처음 공부할 때 “딥러닝을 하려면 PyTorch부터 봐야지”라고 생각했는데, 실제로 시간을 가장 많이 쓴 건 ①~③이었다. 모델을 바꾸는 것보다 데이터를 고치는 게 훨씬 효과가 컸다.

이 시리즈에서 다룰 순서

다루는 것
이번 글 라이브러리 지도 + 데이터 정제 (③)
다음 글 scikit-learn — 분할·스케일링·평가 (④⑥)
그다음 PyTorch — 텐서·모델·학습 루프 (⑤)

3. 데이터 정제 ① 빈칸(결측치)

이제 실전이다. 데이터를 받으면 거의 항상 빈칸이 있다.

NaN이라고 표시되는 이 빈칸을 결측치라고 부른다. 그냥 두면 계산이 통째로 망가진다. 평균을 내면 결과가 NaN이 되고, 대부분의 모델은 아예 에러를 낸다.

1단계 — 어디에 얼마나 있는지 확인

채우기 전에 반드시 세어봐야 한다. 이걸 건너뛰는 게 가장 흔한 실수다.

print(df.isnull().sum())        # 열별 빈칸 개수
print(df.isnull().mean() * 100) # 열별 빈칸 비율(%)

isnull()은 각 칸이 비었는지를 True/False로 바꿔주고, sum()은 True를 1로 세어 더한다. 파이썬에서 True가 1이라는 성질을 그대로 이용하는 문법이다.

비율까지 보는 이유가 있다. 10개 중 1개 빈 것과 10000개 중 1개 빈 것은 완전히 다른 상황인데, 개수만 보면 구분이 안 된다.

2단계 — 무엇으로 채울지 고르기

결측치 처리 선택

선택지가 여러 개다. 판단 기준은 “그 열이 어떤 종류인가”다.

# 숫자 · 튀는 값 없음 → 평균
df["가격"] = df["가격"].fillna(df["가격"].mean())

# 숫자 · 튀는 값 있음 → 중앙값 (더 안전)
df["가격"] = df["가격"].fillna(df["가격"].median())

# 글자 · 카테고리 → 최빈값
df["분류"] = df["분류"].fillna(df["분류"].mode()[0])

평균과 중앙값 중 무엇을 쓸지가 자주 나오는 갈림길이다.

반 대부분이 70~80점인데 0점과 100점이 하나씩 섞여 있다고 하자. 평균은 그 극단값에 끌려간다. 중앙값은 “순서대로 세웠을 때 딱 가운데 값”이라 흔들리지 않는다.

그래서 튀는 값이 의심되면 중앙값이 안전하다.

mode() 뒤에 [0]이 붙는 이유도 짚고 가자. 최빈값은 여러 개일 수 있어서 pandas가 목록으로 돌려준다. 그중 첫 번째를 꺼내는 것이다.

나머지 선택지도 상황이 명확하다.

df["할인금액"] = df["할인금액"].fillna(0)   # 빈칸 = 0이라는 의미가 확실할 때
df["기온"] = df["기온"].ffill()             # 시간 순서 데이터 — 앞 값으로 채우기
df_정리 = df.dropna(subset=["가격"])        # 빈칸이 극소수일 때 — 행 삭제

ffill()바로 앞 값을 그대로 가져온다. 매일 기온을 기록하다 하루가 빠졌다면 “어제와 비슷했겠지”라고 보는 것이다. 다만 순서가 의미 있는 데이터에만 써야 한다. 순서가 무의미한 표에 쓰면 아무 상관없는 옆줄 값을 베끼는 셈이 된다.

3단계 — 다시 확인

print(df.isnull().sum())   # 정말 0이 됐는지

이 한 줄을 빼먹고 한참 뒤에 “왜 자꾸 에러가 나지?” 하고 헤매는 경우가 많다. 고쳤다고 믿지 말고 확인하는 습관이 시간을 아낀다.

그런데 채우기 전에 물어야 할 것

기술적인 방법보다 중요한 게 하나 있다.

이 빈칸은 왜 생겼는가?

  • 센서가 고장 나서 빠진 거라면 → 평균으로 채워도 괜찮다
  • 응답을 거부해서 빠진 거라면 → 빈칸 자체가 정보
  • 해당 사항이 없어서 빠진 거라면 → 0이나 “없음”이 맞는 값이다

세 번째 경우가 특히 함정이다. “할인을 안 받은 사람”의 할인금액은 모르는 값이 아니라 0원이다. 여기에 평균을 채워 넣으면 없던 할인을 만들어내는 셈이다.

결측치 처리는 통계 문제처럼 보이지만 실제로는 도메인 판단이다. 데이터를 만든 과정을 모르면 어떤 방법도 근거가 없다.


4. 데이터 정제 ② 튀는 값(이상치)

빈칸 다음으로 만나는 게 이상치다. 반 친구들 키가 대부분 140~160cm인데 한 명이 250cm로 찍혀 있는 상황이다.

왜 문제인가

이상치 하나가 평균을 통째로 끌고 간다. 그리고 나중에 할 스케일링이나 차원 축소도 이상치 하나에 크게 흔들린다. 모델이 그 한 점을 맞추려고 엉뚱한 방향으로 학습하기도 한다.

1단계 — 눈으로 먼저 본다

import seaborn as sns
sns.boxplot(x=df["가격"])

숫자를 계산하기 전에 박스플롯부터 그려보는 게 순서다. 지난 글에서 다룬 그 그래프인데, 이상치를 찾는 데 특히 잘 맞는다. 수염 밖으로 나간 점들이 곧 이상치 후보다.

2단계 — IQR로 경계 긋기

가장 널리 쓰이는 방법이 IQR(사분위 범위)이다.

IQR

발상은 단순하다. 순서대로 줄을 세우고, 가운데 절반을 “평범한 범위”로 본다.

Q1 = df["가격"].quantile(0.25)   # 아래에서 25% 지점
Q3 = df["가격"].quantile(0.75)   # 아래에서 75% 지점
IQR = Q3 - Q1                    # 가운데 절반의 폭

하한 = Q1 - 1.5 * IQR
상한 = Q3 + 1.5 * IQR

이상치 = df[(df["가격"] < 하한) | (df["가격"] > 상한)]
print(f"정상 범위: {하한:.2f} ~ {상한:.2f} / 이상치 {len(이상치)}")

왜 하필 1.5인가? 통계에서 오래 써온 관행이다. “평범한 범위의 폭보다 1.5배 더 벗어나면 확실히 튄다”고 보는 것이고, 더 엄격하게 걸러내고 싶으면 3을 쓰기도 한다. 정해진 진리가 아니라 기준선이다.

왜 평균·표준편차를 안 쓰나? 이게 핵심이다.

평균과 표준편차는 이상치 자체에 끌려간다. 250cm짜리 값 때문에 평균이 올라가고, 그러면 “정상 범위”도 같이 넓어져서 정작 그 이상치를 못 잡는다.

IQR은 순서만 본다. 250cm가 300cm였어도 “제일 큰 값”이라는 사실만 바뀔 뿐 Q1과 Q3은 그대로다. 그래서 흔들리지 않는다.

3단계 — 처리 방법 세 가지

# ① 삭제 — 명백한 오류일 때
df = df[(df["가격"] >= 하한) & (df["가격"] <= 상한)]

# ② 경계값으로 자르기(clipping) — 값을 살리고 싶을 때
df["가격"] = df["가격"].clip(하한, 상한)

# ③ 그대로 두기 — 진짜 값일 때

③번을 잊지 말자. 이상치라고 다 오류가 아니다. 명품 매장의 매출, 재난일의 검색량, 프로 선수의 기록은 튀는 게 정상이다. 그걸 지우면 데이터에서 가장 중요한 사건을 지우는 셈이 된다.

그래서 이상치도 결측치와 같은 질문으로 돌아간다. “왜 이 값이 여기 있는가?”

250cm는 오타(150을 250으로)일 수도 있고, 단위 착오(mm를 cm로)일 수도 있고, 진짜일 수도 있다. 원인마다 처리가 달라진다.


5. 정제 전체 흐름

두 작업을 합치면 이런 순서가 된다.

1. 얼마나 있는지 센다        →  isnull().sum()  /  boxplot
2. 왜 생겼는지 묻는다        →  (코드가 아니라 생각)
3. 방법을 고른다             →  채우기 / 자르기 / 지우기
4. 처리한다
5. 다시 확인한다             →  isnull().sum()  /  boxplot

2번이 코드가 아니라는 게 포인트다. 나머지는 검색하면 나오는데, 2번만은 데이터를 만든 맥락을 알아야 답할 수 있다.

정제를 하면서 생각이 바뀐 것

솔직히 처음엔 이 단계가 모델을 돌리기 전에 치워야 할 잡일 같았다. 빨리 끝내고 학습을 돌리고 싶었다.

그런데 몇 번 해보니 순서가 반대였다. 모델을 바꿔서 얻은 성능 향상보다, 데이터를 제대로 고쳐서 얻은 향상이 훨씬 컸다. 모델은 결국 데이터에 있는 것만 배울 수 있으니 당연한 결과였다.

이전 글에서 “AI 프로젝트 시간의 7~8할이 데이터 가공에서 소모된다”고 적었는데, 그때는 남의 말을 옮긴 것에 가까웠다. 직접 해보고 나서야 그게 비효율이 아니라 원래 그런 일이라는 걸 알았다.

요리로 치면 재료 손질이다. 손질에 시간이 많이 든다고 불평할 일이 아니라, 손질이 요리의 절반인 것이다.


정리

  • AI 라이브러리는 경쟁 관계가 아니라 층 구조다. 바닥은 NumPy
  • 작업 순서에 놓고 보면 어디에 무엇을 쓰는지가 명확해진다
  • 실제로 시간이 가장 많이 드는 건 모델이 아니라 데이터 정제
  • 결측치 — 세고 → 왜 생겼는지 묻고 → 종류에 맞게 채우고 → 다시 확인
    • 숫자·깨끗하면 평균, 튀는 값 있으면 중앙값, 글자면 최빈값
    • “빈칸 = 0”의 의미가 명확하면 0으로. 시간 데이터면 앞뒤 값으로
  • 이상치 — 박스플롯으로 먼저 보고, IQR로 경계를 긋는다
    • 평균 대신 순서를 쓰는 이유는 평균이 이상치에 끌려가기 때문
    • 삭제·클리핑·유지 중 선택. 진짜 값이면 지우면 안 된다
  • 결측치든 이상치든 결국 같은 질문 — “왜 이렇게 되어 있는가?”

다음 글에서

다음 글에서는 scikit-learn을 다룬다. 데이터를 나누고 단위를 맞추는 순서를 왜 지켜야 하는지, 그리고 에러 없이 성능 숫자만 거짓말하는 사고가 어떻게 벌어지는지를 정리한다.

한줄 평

  • AI 이론 이후 Python 을 이용해 직접 실습하니 구조가 조금 더 눈에 잘 들어오는 것 같다.