본문 바로가기
Python/Data analysis

데이터 전처리 1

by curious week 2025. 10. 10.

데이터 전처리 1

1) 학습개요

  • 전처리는 분석 전 단계에서 원시 데이터를 정확·일관 상태로 만들고, 측정(요약통계)으로 구조와 품질을 파악하는 과정입니다.
  • 핵심은 결측치·이상치·불일치를 다루고, 통합·축소·변환으로 분석 목적에 맞는 데이터셋을 만드는 것입니다.
  • pandas의 describe()·info()·집계 함수로 기본 통계량을 산출하고 전처리 우선순위를 정합니다.

2) 학습목표 매핑

  1. 데이터 품질 요소: 정확성, 완전성, 일관성, 유효성, 적시성, 상호운용성.
  2. 전처리 단계: 측정 → 정제(결측/이상치/불일치/중복) → 통합 → 축소 → 변환(반복·지속).
  3. pandas로 요약통계/분포 파악정제 코드를 작성·검증.

3) 전처리의 필요성과 정의

  • GIGO(잘못 들어가면, 잘못 나온다): 입력 품질이 결과 품질을 결정.
  • 정의: 원시데이터를 정제·통합·축소·변환분석 적합 형태로 만드는 일련의 작업(반복·지속).

4) 전처리

4.1 데이터 측정(진단)

  • 구조 확인: df.info(), 결측/고유값/단위/타입 점검.
  • 요약통계: df.describe()(수치형), df.describe(include=['object'])(범주형).
  • 분포/왜도/첨도·사분위 확인 → 이상치 후보·스케일 차이 파악.

4.2 정제(Cleaning)

  • 결측치: 삭제(dropna), 대치(mean/median/mode), 그룹별 대치, 시계열 전방/후방 보간.
  • 이상치: IQR·Z-score·로버스트 스케일·Winsorization·도메인 규칙으로 처리.
  • 불일치: 값 표준화(공백/대소문/동의어), 단위/스케일 정합, 날짜/타임존 통일.
  • 중복: 키 정의 후 drop_duplicates().

4.3 통합(Integration)

  • 조인 키 명확화(정합성·중복키 확인), 조인 후 레코드 수 검증(전/후 비교).

4.4 축소(Reduction)

  • 필요 컬럼 선별, 표본추출, 차원축소(예: PCA), 요약 집계.

4.5 변환(Transformation)

  • 스케일링(표준화/정규화), 로그/박스-콕스, 범주형 인코딩(원-핫/타깃), 날짜 파생(연-월-요일).

5) pandas: 측정 → 정제 → 저장

아래 코드는 바로 실행 가능하며, 각 인자의 역할/타입을 주석으로 명시했습니다.

import numpy as np
import pandas as pd

# 예시 데이터 구성 (결측/오타/이상치 포함)
df = pd.DataFrame({
    "지역": ["인천","수원","대구","부산","울산","수원","창원","대전","인천","창원","대구"],
    "상품명": ["태블릿","mouse","TV","스마트폰","모니터","스피커","키보드","TV","노트북","스마트워치","마우스"],
    "가격": [np.nan,"오류",600000.0,50000.0,400000.0,50000.0,np.nan,400000.0,1000000.0,300000.0,600000.0],
    "판매수량": [102.0,120.0,139.0,147.0,133.0,-10.0,47.0,73.0,149.0,np.nan,71.0],
    "세일광고여부": ["N","Y",np.nan,"Y","N","Y","N","Y","N","N","Y"]
})

# 1) 측정: 구조/기술통계
print(df.info())                # 각 컬럼 dtype/결측 개요
print(df.describe())            # 수치형 요약통계
print(df.describe(include=['object']))  # 범주형 요약

# 2) 정제: 타입/오타/결측/이상치 처리

# 2-1) '가격'을 숫자로 강제 캐스팅 (errors='coerce' → 변환 불가를 NaN으로)
df["가격"] = pd.to_numeric(df["가격"], errors="coerce")  # (role: 타입 정규화, type: str|float→float)

# 2-2) 불일치 표준화: 상품명 소문자/공백 정리 후 한국어 표준 라벨로 매핑
normalize = {
    "mouse": "마우스",
    "tv": "TV"
}
df["상품명"] = (df["상품명"]
                .str.strip()
                .str.lower()
                .replace(normalize)
                .str.replace("tv", "TV", regex=False))

# 2-3) 결측치 처리 전략
# - 가격: 중앙값 대치(이상치에 덜 민감)
# - 판매수량: 음수는 결함치로 간주 → NaN 후 중앙값 대치
price_median = df["가격"].median(skipna=True)
df.loc[df["가격"].isna(), "가격"] = price_median

df.loc[df["판매수량"] < 0, "판매수량"] = np.nan
qty_median = df["판매수량"].median(skipna=True)
df.loc[df["판매수량"].isna(), "판매수량"] = qty_median

# 2-4) 이상치(IQR) 처리 예: 가격 상/하한 바운딩
q1, q3 = df["가격"].quantile([0.25, 0.75])
iqr = q3 - q1
lower, upper = q1 - 1.5*iqr, q3 + 1.5*iqr
df["가격"] = df["가격"].clip(lower, upper)  # (role: 완화, type: float→float)

# 2-5) 중복 제거: (지역, 상품명, 가격, 판매수량) 기준
before = len(df)
df = df.drop_duplicates(subset=["지역","상품명","가격","판매수량"])
after = len(df)
print(f"중복 제거: {before} → {after}")

# 3) 측정(재진단): describe로 변화 확인
print(df.describe())
print(df.describe(include=['object']))

선택적: 그룹별 기술통계 및 이상치 후보 탐지

# 그룹별(지역) 요약: 평균/표준편차/개수
group_stats = (df.groupby("지역")[["가격","판매수량"]]
                 .agg(["count","mean","std","min","max"]))
print(group_stats)

# Z-score로 이상치 후보 마킹 (판매수량)
z = (df["판매수량"] - df["판매수량"].mean()) / df["판매수량"].std(ddof=0)
df["판매수량_이상치"] = (z.abs() > 3)  # (role: 탐지 플래그, type: bool)

describe() 활용 팁

  • df.describe(percentiles=[0.01,0.5,0.99])로 테일을 확인해 이상치를 빠르게 포착.
  • 범주형 빈도는 value_counts(dropna=False)를 함께 사용.

6) 체크리스트

  • 결측: 어디에, 왜 생겼는가?(기기 결함/입력 누락/수집 지연) → 대치 근거 기록.
  • 이상치: 오류 vs 진짜 극단값 구분(도메인 규칙/센서 범위).
  • 일관성: 라벨 표준화, 단위 통일, 타임존/포맷 통합(ISO 8601).
  • 통합: 조인 키 무결성, 조인 전후 건수·NULL 변화 점검.
  • 재현성: 전처리 파이프라인 함수화/버전 고정/로그 남기기.

7) 핵심 포인트

  • 전처리는 품질을 확보해 모델·통계 결과의 신뢰도를 끌어올리는 과정.
  • 측정 → 정제 → 통합 → 축소 → 변환을 반복하며, describe()로 상태를 수치화해 의사결정.
  • 결측·이상치·불일치는 명시적 규칙으로 처리하고, 근거/영향을 기록해야 함.

8) 약어 풀이

  • GIGO: Garbage In, Garbage Out(입력 품질이 결과를 좌우).
  • IQR: InterQuartile Range(사분위 범위).
  • Z-score: 표준점수(평균으로부터 표준편차 단위 거리).

오픈소스 기반 데이터 분석 6강 — 데이터 전처리 1

 

Data-Analysis-with-Open-Source/오픈소스_데이터_분석_6강.ipynb at main · mors119/Data-Analysis-with-Open-Source

Data Analysis with Open Source. Contribute to mors119/Data-Analysis-with-Open-Source development by creating an account on GitHub.

github.com


6-1 데이터 훑어보기 (요약 통계/타입별 기술통계)

# --- 요약 통계 살펴보기: df.describe 계열 ---
import pandas as pd
import numpy as np

data = {
    'name': ['김민수', '이지영', '박준호', '최서연', '정도윤'],
    'age': [25, 30, 28, 22, 35],
    'city': ['서울', '부산', '인천', '서울', '대전'],
    'score': [90, 85, 95, 80, np.nan],
}
df = pd.DataFrame(data)

# 1) 기본 요약(수치형만)
desc_num = df.describe()               # 역할: count/mean/std/min/사분위수/max (수치형 컬럼만)
print(desc_num)

# 2) 전체 타입 포함
desc_all = df.describe(include='all')  # 역할: 수치+범주형 통합 요약(고유값 수, 최빈값 등)
print(desc_all)

# 3) 수치형만 명시
desc_numbers = df.describe(include=[np.number])
print(desc_numbers)

# 4) 범주형만 명시
desc_object = df.describe(include=['object'])
print(desc_object)

# 5) 특정 열만
score_desc = df['score'].describe()    # 역할: 단일 시리즈에 대한 요약
print(score_desc)

# 옵션/대안:
# - 결측 제외 여부: describe는 기본적으로 NaN 제외하여 통계 계산
# - 상세 기술통계: [import scipy] from scipy import stats; stats.describe(df['score'].dropna())
# - 그룹별 요약: df.groupby('city')['score'].describe()
# - 날짜형 파싱 후 리샘플 요약: df.resample('W')... (DatetimeIndex 필요)

6-2 DataFrame 열 선택 및 조작 (선택/순서변경/이름변경)

# --- 열 선택/순서변경/이름변경 ---
import pandas as pd

data = {
    "이름": ["김철수", "이영희", "박민수", "최지훈", "정소희"],
    "학년": [1, 2, 3, 4, 2],
    "학점": [4.2, 3.8, 4.5, 3.9, 3.5],
    "학과": ["컴퓨터공학", "경영학", "전자공학", "의학", "심리학"],
    "동아리": ["프로그래밍", "독서토론", "로봇공학", "봉사활동", "음악감상"],
}
df = pd.DataFrame(data)

# 1) 단일/다중 열 선택
col_series = df['이름']                   # 역할: Series 반환
selected = df[['이름','학과','학점']]      # 역할: DataFrame 반환
print(col_series.head(), selected.head(), sep="\n")

# 2) 열 순서 변경
reordered = df[['동아리', '학년', '이름', '학과', '학점']]
print(reordered.head())

# 3) 열 이름 변경
renamed = df.rename(columns={'이름':'학생명', '학과':'소속', '학점':'평점'})
print(renamed.head())

# 대안/옵션:
# - 열 선택 대안: df.loc[:, ['이름','학과']] (라벨 기반), df.filter(items=['이름','학과'])
# - 이름 일괄 규칙 변경: df.set_axis(['a','b',...], axis=1)
# - 컬럼명 정리(스네이크케이스 등): [import janitor] import janitor; df = df.clean_names()
# - inplace=False가 기본(원본 보존). 원본 변경 원하면 df.rename(..., inplace=True)

6-3 DataFrame 행 선택 (loc/iloc/조건/슬라이스)

# --- 행 선택: 라벨/위치/조건 ---
import pandas as pd

data = {
    "이름": ["김철수", "이영희", "박민수", "최지훈", "정소희"],
    "학년": [1, 2, 3, 4, 2],
    "학점": [4.2, 3.8, 4.5, 3.9, 3.5],
    "학과": ["컴퓨터공학", "경영학", "전자공학", "의학", "심리학"],
    "동아리": ["프로그래밍", "독서토론", "로봇공학", "봉사활동", "음악감상"],
}
df = pd.DataFrame(data).set_index('이름')  # 역할: '이름'을 인덱스로 지정

# 1) 라벨 기반 선택: loc
row_one = df.loc['김철수']                     # 단일 라벨
rows_some = df.loc[['김철수','이영희']]         # 여러 라벨
rows_slice = df.loc['김철수':'박민수']          # 라벨 슬라이스(종단 포함)
print(row_one, rows_some, rows_slice, sep="\n\n")

# 2) 위치 기반 선택: iloc
row_pos = df.iloc[1]                            # 두 번째 행(0-index)
rows_pos_many = df.iloc[[2,3]]                  # 3/4번째
rows_pos_slice = df.iloc[1:3]                   # 2~3번째(종단 미포함)
print(row_pos, rows_pos_many, rows_pos_slice, sep="\n\n")

# 3) 불리언(조건) 필터
mask_list = df.loc[[True, False, True, False, False]]  # 길이 일치 필요
gpa_ge_4 = df.loc[df['학점'] >= 4.0]                   # 학점 4.0 이상
grade_eq_2 = df[df['학년'] == 2]                       # 학년==2
mix_cond = df[(df['학년']==2) & (df['학점']>=3.7)]     # and 조건
print(mask_list, gpa_ge_4, grade_eq_2, mix_cond, sep="\n\n")

# 대안/옵션:
# - query 문법(가독성↑): df.query("학년 == 2 and 학점 >= 3.7")
# - isin 다중 조건: df[df['학과'].isin(['경영학','의학'])]
# - between 범위: df[df['학점'].between(3.7, 4.5, inclusive='both')]
# - 복합 인덱스: df.set_index(['학년','학과']).loc[(2,'경영학')]

6-4 DataFrame 정렬 (값/인덱스/복수키/결측처리)

# --- 정렬: sort_values / sort_index ---
import pandas as pd

data = {
    "이름": ["김철수", "이영희", "박민수", "최지훈", "정소희"],
    "학년": [1, 2, 3, 4, 2],
    "학점": [4.2, 3.8, 4.5, 3.9, 3.5],
    "학과": ["컴퓨터공학", "경영학", "전자공학", "의학", "심리학"],
    "동아리": ["프로그래밍", "독서토론", "로봇공학", "봉사활동", "음악감상"],
}
df = pd.DataFrame(data).set_index('이름')

# 1) 단일 열 정렬
asc_gpa  = df.sort_values(by='학점')                  # 오름차순
desc_gpa = df.sort_values(by='학점', ascending=False) # 내림차순
print(asc_gpa, desc_gpa, sep="\n\n")

# 2) 복수 열 정렬(학년↑, 학점↓)
multi = df.sort_values(by=['학년','학점'], ascending=[True, False])
print(multi)

# 3) 인덱스 정렬(내림차순)
idx_sorted = df.sort_index(ascending=False)
print(idx_sorted)

# 옵션/대안:
# - 결측 위치: na_position='first'|'last' (기본 last)
# - 안정 정렬 필요시: kind='mergesort' (동일 키 보존)
# - 다중 키로 정렬 후 상위 n개: df.sort_values(...).head(n)
# - nlargest/nsmallest: df['학점'].nlargest(3)

6-5 실습 시나리오 — Kaggle을 활용한 데이터 수집 및 측정 (Colab 기준)

# --- Kaggle API 준비(Colab) ---
# 역할: kaggle.json(토큰) 업로드 → 권한 설정 → 대회 데이터 다운로드
# 주의: kaggle 패키지가 없으면 !pip install kaggle 필요

# 0) 패키지 설치 (필요시)
# !pip install kaggle -q

from google.colab import files
files.upload()                                    # 역할: kaggle.json 업로드 UI

# 업로드된 kaggle.json을 표준 경로로 이동하고 권한 설정
!mkdir -p ~/.kaggle
!cp kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json

# 1) 데이터셋 다운로드(예: Store Sales - Time Series Forecasting)
!kaggle competitions download -c store-sales-time-series-forecasting
!unzip -o store-sales-time-series-forecasting.zip

# 대안(Colab이 아닐 때):
# - 로컬에서 kaggle 패키지 설치 후 환경변수 KAGGLE_CONFIG_DIR 설정
# - 수동 다운로드(브라우저) 후 파일 업로드
# - [import kaggle] from kaggle import api; api.competition_download_files(...)
# --- CSV 로드 및 구조 확인 ---
import pandas as pd

train = pd.read_csv('train.csv')          # 판매 데이터(큰 파일 → dtype/parse_dates 권장)
stores = pd.read_csv('stores.csv')
transactions = pd.read_csv('transactions.csv')
oil = pd.read_csv('oil.csv')
holidays_events = pd.read_csv('holidays_events.csv')

# 메모리/타입 힌트(속도/메모리 최적화):
# train = pd.read_csv('train.csv',
#     dtype={'id':'int64','store_nbr':'int16','onpromotion':'int32','family':'category'},
#     parse_dates=['date']
# )

# 1) info/describe로 대략 살펴보기
train.info()                     # 역할: 행수/컬럼/타입/메모리
print(train.describe())          # 역할: 수치형 기본 통계

# 2) 다른 테이블 샘플
print(stores.head())
print(oil.head())

# 대안/옵션:
# - 큰 파일은 필요한 컬럼만: usecols=['date','store_nbr','sales']
# - 고속 CSV: [import pyarrow] pd.read_csv(..., engine='pyarrow')
# - 날짜 파싱: parse_dates=['date']; 인덱스로: df.set_index('date')
# - 결측치 탐색: train.isna().sum().sort_values(ascending=False)
# - 샘플링: train.sample(100_000, random_state=42)
# --- (선택) 간단 측정/확인 예시 ---
# 역할: 전처리로 이어질 탐색의 최소 뼈대
# - 날짜 범위/결측/기초 통계/고유값 수 등
print("기간:", train['date'].min(), "~", train['date'].max())
print("결측치 수:\n", train.isna().sum())
print("family 고유값 수:", train['family'].nunique())

 

'Python > Data analysis' 카테고리의 다른 글

데이터 분석 1  (0) 2025.10.10
데이터 전처리 2  (0) 2025.10.10
데이터 저장  (0) 2025.10.07
데이터 수집  (0) 2025.10.07
언패킹, 예외처리, 함수형 프로그래밍  (0) 2025.10.03