Pandas sampling(샘플링)

수백만 행짜리 데이터프레임을 통째로 훑어보거나 모델에 넣기 전에, 일부만 빠르게 뽑아 분포를 확인하고 싶을 때가 있다. df.head()는 앞부분만 보여주기 때문에 데이터가 정렬돼 있으면 편향된 인상을 준다. 반대로 그룹별 비율은 유지한 채 표본만 줄이고 싶다거나, 매번 같은 표본이 나오도록 재현성을 확보하고 싶다면 단순히 행을 잘라내는 것만으로는 부족하다. pandas는 이런 요구를 DataFrame.sample()GroupBy.sample()로 처리한다. 이 글에서는 두 메서드의 주요 옵션과 층화추출(stratified sampling) 패턴을 정리한다.

기본 사용법: n과 frac

가장 단순한 형태는 개수(n) 또는 비율(frac)로 뽑는 것이다. 둘은 동시에 쓸 수 없다.

import pandas as pd

df = pd.DataFrame({"id": range(1000), "group": ["A", "B", "C"] * 333 + ["A"]})

# 행 100개를 무작위로 추출
df.sample(n=100)

# 전체의 10%를 무작위로 추출
df.sample(frac=0.1)

# 재현 가능한 결과가 필요하면 random_state 지정
df.sample(n=100, random_state=42)

random_state를 지정하지 않으면 실행할 때마다 다른 표본이 나온다. 노트북에서 결과를 재현하거나 팀원과 같은 표본으로 디버깅하려면 정수든 numpy.random.Generator든 반드시 고정값을 넘겨야 한다.

복원추출과 가중치

replace=True를 주면 같은 행이 여러 번 뽑힐 수 있는 복원추출이 된다. 부트스트랩 샘플링을 만들거나, 원본보다 많은 표본(frac > 1)을 뽑아야 할 때 필요하다.

# 복원추출로 부트스트랩 샘플 생성 (원본과 같은 크기)
boot = df.sample(n=len(df), replace=True, random_state=42)

# frac > 1은 replace=True가 필수
oversampled = df.sample(frac=1.5, replace=True, random_state=42)

특정 행이 뽑힐 확률을 다르게 주고 싶다면 weights를 쓴다. 컬럼 이름을 문자열로 넘기면 그 컬럼 값을 가중치로 사용하고, 합이 1이 아니어도 내부적으로 정규화된다.

# score가 높은 행일수록 더 자주 뽑히도록 가중치 부여
df["score"] = df["id"] % 10 + 1
df.sample(n=50, weights="score", random_state=42)

그룹별 비율을 유지하는 층화추출

df.sample()은 전체 데이터프레임에서 무작위로 뽑기 때문에, 그룹 간 비율이 불균등하면 표본에서 특정 그룹이 과소/과대 대표될 수 있다. 그룹별 비율을 그대로 유지하면서 표본을 줄이려면 groupby() 뒤에 sample()을 연결한다.

# 그룹(group)별로 각각 20%씩 추출 → 그룹 비율이 원본과 동일하게 유지된다
stratified = df.groupby("group", group_keys=False).sample(frac=0.2, random_state=42)

# 그룹마다 정확히 30개씩 뽑고 싶다면 n을 사용 (그룹 크기가 30 미만이면 에러)
fixed_per_group = df.groupby("group", group_keys=False).sample(n=30, random_state=42)

print(df["group"].value_counts(normalize=True))
print(stratified["group"].value_counts(normalize=True))

그룹마다 크기가 다른 상태에서 n을 고정값으로 주면 가장 작은 그룹의 크기를 넘는 순간 에러가 난다. 그룹 크기가 들쭉날쭉하다면 frac으로 비율을 맞추는 편이 안전하다.

axis와 ignore_index

기본적으로 sample()은 행(axis=0)을 뽑지만 axis=1을 주면 컬럼을 무작위로 골라낼 수 있다. 컬럼이 아주 많은 데이터셋에서 일부 피처만 빠르게 훑어볼 때 유용하다. 또한 표본을 뽑은 뒤 인덱스가 원본의 위치 그대로 남는데, 이후 reset_index()를 따로 호출하지 않으려면 ignore_index=True를 준다.

# 컬럼 중 60%만 무작위로 선택
df.sample(frac=0.6, axis=1, random_state=42)

# 표본의 인덱스를 0, 1, 2, ...로 재설정
df.sample(n=100, random_state=42, ignore_index=True)

주의사항

  • random_state를 생략하면 매 실행마다 결과가 달라진다. EDA 노트북을 공유하거나 CI에서 테스트할 때는 반드시 고정값을 지정한다.
  • replace=False(기본값) 상태에서 n이 전체 행 수보다 크면 ValueError가 발생한다. 복원추출이 필요한 상황인지 먼저 확인한다.
  • df.sample(frac=1)은 전체를 한 번 셔플하는 용도로도 자주 쓰이는데, 이 경우 인덱스가 뒤섞인 채로 남으므로 reset_index(drop=True) 또는 ignore_index=True를 함께 쓰는 편이 좋다.
  • groupby().sample()group_keys=False를 빼먹으면 결과 인덱스에 그룹 키가 추가로 붙어 원본과 구조가 달라질 수 있다.

마무리

sample()은 단순 무작위 추출뿐 아니라 가중치, 복원추출, groupby와의 조합으로 층화추출까지 처리할 수 있는 범용 도구다. 단순 EDA용 미리보기라면 fracrandom_state만 알아도 충분하고, 그룹 간 비율을 유지해야 하는 학습/검증 데이터 분리나 A/B 테스트 표본 설계라면 groupby().sample() 패턴을 기본으로 가져가는 편이 안전하다.

참고

답글 남기기