한 줄 정의
EDA(탐색적 데이터 분석)는 데이터에 질문을 던져 구조·품질·분포·관계를 확인하고 다음 전처리와 특성 공학 결정을 만드는 과정이다.
이 글은 표 데이터를 처음 받았을 때 pandas로 반복 실행할 수 있는 점검 순서를 만든다. 숫자를 출력하는 데서 끝내지 않고 ‘이 결과 때문에 다음에 무엇을 할 것인가’를 연결한다.
실습 데이터 만들기
import pandas as pd
from io import StringIO
csv = StringIO("""customer_id,age,salary,department,channel,purchased
C001,25,3200,Sales,app,0
C002,32,4500,IT,web,1
C003,,6100,HR,store,1
C004,45,7200,IT,web,1
C005,29,3800,Sales,app,0
C006,29,3800,Sales,app,0
""")
df = pd.read_csv(csv)
print(df.shape)
print(df.head(3).to_string(index=False))
(6, 6)
customer_id age salary department channel purchased
C001 25.0 3200 Sales app 0
C002 32.0 4500 IT web 1
C003 NaN 6100 HR store 1
구조: 몇 행·몇 열이며 자료형은 맞는가
shape는 (행 수, 열 수)를 돌려준다. columns는 열 이름을, info()는 자료형·비결측 개수·메모리 요약을 보여 준다.
print("shape:", df.shape)
print("columns:", df.columns.tolist())
df.info()
여기서는 age가 결측값 때문에 float64다. 숫자로 저장됐다고 해서 연속형이라는 뜻은 아니다. purchased는 0/1이지만 목표 범주이고, customer_id는 문자열이지만 일반 범주형 특성으로 바로 쓰기 어렵다. dtype은 저장 형식이고 의미 유형은 분석가가 결정한다.
미리보기: head만 믿지 말자
head()는 앞부분, tail()은 뒷부분, sample()은 임의 행을 보여 준다. 정렬된 데이터에서는 앞 5행만 보면 특정 기간·지역만 보게 될 수 있다.
print(df.head(2))
print(df.tail(2))
print(df.sample(2, random_state=42))
sample은 무작위 표본일 뿐 품질을 증명하지 않는다. 작은 미리보기는 열의 모양과 파싱 오류를 찾는 용도로만 쓰고, 결측·중복·범위는 전체 집계로 확인한다.
요약통계: 중심만 말고 범위와 분포를 본다
describe()는 수치형 열의 개수, 평균, 표준편차, 사분위수, 최솟값, 최댓값을 요약한다. include="all"을 쓰면 범주형 열의 unique, top, freq도 함께 볼 수 있다.
print(df.describe().round(1))
print(df.describe(include="all").loc[["count", "unique", "top", "freq"]])
평균과 중앙값 차이가 크거나 최댓값이 75% 분위수보다 매우 멀면 치우침·이상치를 의심한다. 그러나 업무상 고액 고객일 수도 있으므로 곧바로 삭제하지 않는다. 단위와 수집 규칙을 먼저 확인한다.
결측: 개수와 비율을 함께 본다
missing = pd.DataFrame({
"count": df.isna().sum(),
"ratio": df.isna().mean(),
}).query("count > 0").sort_values("ratio", ascending=False)
print(missing)
count ratio
age 1 0.166667
6명 중 age 1개가 비어 있으므로 결측률은 약 16.7%다. 다음 질문은 ‘왜 비었는가’다. 무응답인지, 수집 장애인지, 특정 채널에서만 발생하는지 그룹별로 확인한다. 결측 처리 방법은 원인과 모델 목적에 따라 달라진다.
중복: 완전 중복과 업무 키 중복을 나눈다
print("entire duplicate rows:", df.duplicated().sum())
print("duplicate customer ids:", df.duplicated("customer_id").sum())
print("duplicate feature rows:", df.duplicated(
["age", "salary", "department", "channel", "purchased"]
).sum())
entire duplicate rows: 0
duplicate customer ids: 0
duplicate feature rows: 1
C005와 C006은 고객 ID만 다르고 나머지가 같다. 서로 다른 실제 고객일 수 있으므로 삭제할 근거가 없다. 주문 데이터라면 주문번호, 고객-날짜 조합처럼 업무상 한 행을 유일하게 만드는 키를 먼저 정의한다.
카디널리티: 범주의 고유값 수를 확인한다
카디널리티는 범주형 변수의 서로 다른 값 개수다. nunique()는 개수를, value_counts()는 값별 빈도를 센다. 기본값에서는 결측값을 제외하므로 결측도 범주처럼 보고 싶다면 dropna=False를 명시한다.
print(df.nunique(dropna=False).sort_values(ascending=False))
print(df["department"].value_counts(dropna=False))
print(df["department"].value_counts(normalize=True, dropna=False).round(3))
customer_id 6
age 5
salary 5
department 3
channel 3
purchased 2
dtype: int64
department
Sales 3
IT 2
HR 1
Name: count, dtype: int64
department
Sales 0.500
IT 0.333
HR 0.167
Name: proportion, dtype: float64
customer_id는 행 수와 고유값 수가 같아 식별자 가능성이 높다. department는 세 범주이며 HR은 한 건뿐이다. 고카디널리티 열을 무조건 삭제할 필요는 없지만, ID 누수·희소한 원-핫 열·새 범주 처리 문제를 검토해야 한다.
범위와 논리 규칙도 코드로 검사한다
checks = {
"age_out_of_range": (~df["age"].between(0, 120) & df["age"].notna()).sum(),
"salary_negative": (df["salary"] < 0).sum(),
"unknown_target": (~df["purchased"].isin([0, 1])).sum(),
}
print(checks)
{'age_out_of_range': 0, 'salary_negative': 0, 'unknown_target': 0}
통계상 드물다는 것과 업무상 불가능하다는 것은 다르다. 나이 -3은 업무 규칙 위반이고, 나이 105는 드물지만 가능할 수 있다. 데이터 사전과 현업 담당자에게 허용 범위·단위·코드를 확인한다.
한 번에 돌리는 EDA 체크리스트
def basic_eda(frame: pd.DataFrame) -> dict:
return {
"shape": frame.shape,
"dtypes": frame.dtypes.astype(str).to_dict(),
"missing_count": frame.isna().sum().to_dict(),
"missing_ratio": frame.isna().mean().round(3).to_dict(),
"nunique_including_na": frame.nunique(dropna=False).to_dict(),
"duplicate_rows": int(frame.duplicated().sum()),
}
report = basic_eda(df)
for name, value in report.items():
print(name, "=>", value)
자동 리포트는 출발점이다. 다음 행동은 사람이 정한다: 자료형 수정, 키 중복 조사, 결측 원인 확인, 희소 범주 통합 검토, 범위 오류 원본 대조 등이다.
누수 없는 EDA 경계
전체 데이터에서 스키마 오류와 데이터 수집 품질을 확인하는 것과, 목표값 관계를 보며 모델 선택에 영향을 주는 탐색을 구분한다.
행·열 수, 파싱 실패, 불가능한 값처럼 모델과 무관한 품질 점검은 전체 수집 파이프라인에서 수행할 수 있다.
목표값과의 관계로 특성을 선택하거나 대치·스케일 규칙을 정하는 일은 학습 데이터 안에서 수행한다.
테스트 세트는 최종 일반화 성능 확인용으로 보존한다. 반복해서 들여다보며 결정을 바꾸면 테스트 세트에도 과적합한다.
교차검증에서는 학습 fold마다 전처기를 다시 fit하도록 Pipeline을 사용한다.
결과를 다음 결정으로 연결하는 표
dtype 불일치 → 파싱 규칙과 단위를 확인하고 명시적으로 변환한다.
결측 집중 → 발생 조건을 그룹별로 확인하고 삭제·대치·결측 표시 특성을 비교한다.
고카디널리티 → ID 여부, 빈도, 미지 범주, 인코딩 비용을 검토한다.
희소 범주 → 업무상 합칠 수 있는지 확인하고 ‘기타’ 처리나 규제화를 검토한다.
비정상 범위 → 원천 데이터와 대조해 오류와 실제 희귀 사례를 구분한다.
중복 → 업무 키와 발생 시점을 확인한 뒤 제거·집계 여부를 정한다.
참고 자료
박병선, 『실전 Feature Engineering』 강의 PDF, 10~24쪽 (2026-08-05 확인)
pandas — DataFrame.info (2026-08-05 확인)
pandas — DataFrame.describe (2026-08-05 확인)
pandas — DataFrame.nunique (2026-08-05 확인)
pandas — Series.value_counts (2026-08-05 확인)
pandas — Working with missing data (2026-08-05 확인)
scikit-learn — Data leakage (2026-08-05 확인)
댓글 0
댓글을 불러오는 중…