수백만 행짜리 데이터프레임을 통째로 훑어보거나 모델에 넣기 전에, 일부만 빠르게 뽑아 분포를 확인하고 싶을 때가 있다. 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용 미리보기라면 frac과 random_state만 알아도 충분하고, 그룹 간 비율을 유지해야 하는 학습/검증 데이터 분리나 A/B 테스트 표본 설계라면 groupby().sample() 패턴을 기본으로 가져가는 편이 안전하다.