- Published on
Pandas SettingWithCopyWarning 완전 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서브셋을 만든 뒤 값을 대입했는데, 어떤 때는 반영되고 어떤 때는 안 되는 경험이 있다면 SettingWithCopyWarning을 이미 만난 겁니다. 이 경고는 “당신이 수정한 대상이 원본인지, 원본에서 떨어져 나온 복사본인지 Pandas가 확신할 수 없다”는 신호입니다.
핵심은 간단합니다.
- 필터링, 슬라이싱 결과는 뷰일 수도 있고 복사본일 수도 있습니다.
- 그 상태에서 체인 인덱싱(chained indexing) 으로 대입하면, Pandas는 “원본이 바뀌는지” 보장할 수 없어 경고를 띄웁니다.
- 경고를 무시하면, 조용히 값이 반영되지 않는 데이터 누락/오염 버그가 생길 수 있습니다.
이 글은 경고를 “끄는 법”이 아니라, 경고가 다시는 안 나오게 만드는 안전한 작성 패턴을 정리합니다. 더 넓은 사례와 내부 동작 중심 설명은 Pandas SettingWithCopyWarning 완전정복도 함께 참고하면 좋습니다.
SettingWithCopyWarning이 발생하는 정확한 조건
가장 흔한 트리거는 아래 형태입니다.
- 1단계:
df[mask]또는df[col_list]등으로 부분 DataFrame을 만든다 - 2단계: 그 결과에 다시
[...]로 접근해 대입한다
즉, df[mask]["col"] = value 같은 패턴입니다.
재현 예시: 체인 인덱싱으로 경고 만들기
import pandas as pd
df = pd.DataFrame({
"user": ["a", "b", "c", "d"],
"age": [10, 20, 30, 40],
"city": ["seoul", "busan", "seoul", "daegu"],
})
# 잘못된 패턴: 체인 인덱싱
seoul = df[df["city"] == "seoul"]
seoul["age"] = seoul["age"] + 1 # SettingWithCopyWarning 가능
위 코드는 겉으로는 seoul만 수정하는 것처럼 보이지만, seoul이 원본 df의 뷰인지 복사본인지 상황에 따라 달라질 수 있습니다. Pandas는 이 불확실성을 경고로 알려줍니다.
“경고만 끄기”가 위험한 이유
다음 설정으로 경고를 숨길 수는 있습니다.
import pandas as pd
pd.options.mode.chained_assignment = None
하지만 이건 연기 감지기를 꺼버리는 것에 가깝습니다. 경고가 사라져도 다음 문제가 남습니다.
- 수정이 원본에 반영되지 않았는데도 코드가 계속 진행됨
- 결과가 환경, Pandas 버전, 최적화 경로에 따라 달라질 가능성
- 테스트에서는 통과했는데 운영 데이터에서만 틀어지는 “데이터 파이프라인 특유의 버그”로 발전
따라서 목표는 경고를 억누르는 것이 아니라, 대입을 항상 단일 단계로, 명시적으로 만드는 것입니다.
완전 해결 1: loc로 한 번에 선택하고 한 번에 대입
가장 정석이자 권장되는 해결책입니다.
올바른 패턴
mask = df["city"] == "seoul"
df.loc[mask, "age"] = df.loc[mask, "age"] + 1
이 코드는 “원본 df의 특정 행과 특정 열을 선택해 대입한다”가 한 문장으로 끝납니다. 중간 결과 DataFrame을 만들지 않으니 뷰/복사본 논쟁이 사라집니다.
여러 컬럼을 동시에 갱신하기
mask = df["city"] == "seoul"
df.loc[mask, ["age", "city"]] = [
df.loc[mask, "age"] + 1,
"SEOUL",
]
다만 여러 컬럼을 한 번에 갱신할 때는 우변의 shape가 맞는지 주의해야 합니다. 실무에서는 컬럼별로 명시하는 편이 디버깅에 유리합니다.
완전 해결 2: 서브셋이 필요하면 .copy()를 명시
서브셋을 별도 객체로 들고 가서 가공해야 한다면, 처음부터 복사본임을 선언하세요.
seoul = df.loc[df["city"] == "seoul"].copy()
seoul["age"] = seoul["age"] + 1 # 경고 없음
이 경우 seoul은 원본과 독립입니다. 즉, seoul을 수정해도 df는 바뀌지 않습니다. 원본에 반영하려면 다시 머지하거나 할당해야 합니다.
가공 후 원본에 반영하는 안전한 방법
인덱스를 유지했다면 다음처럼 되돌릴 수 있습니다.
seoul = df.loc[df["city"] == "seoul"].copy()
seoul["age"] += 1
df.loc[seoul.index, "age"] = seoul["age"]
핵심은 “원본에 반영하는 단계”에서도 loc로 단일 단계 대입을 유지하는 것입니다.
완전 해결 3: assign과 where로 함수형 스타일 유지
체인 호출을 선호한다면, “대입”을 직접 하지 말고 새 DataFrame을 리턴하는 연산으로 바꾸면 깔끔합니다.
import numpy as np
mask = df["city"] == "seoul"
df2 = df.assign(
age=np.where(mask, df["age"] + 1, df["age"])
)
이 방식은 원본 df를 건드리지 않고 df2를 생성하므로, 경고가 발생할 여지가 거의 없습니다. 데이터 파이프라인에서 불변성을 선호한다면 특히 유용합니다.
자주 터지는 패턴별 처방전
실무에서 경고가 자주 발생하는 “전형적인 코드 냄새”와 대체안을 정리합니다.
패턴 A: df[mask][col] = ...
- 문제 코드
df[df["city"] == "seoul"]["age"] = 0
- 해결 코드
df.loc[df["city"] == "seoul", "age"] = 0
패턴 B: 부분 컬럼 추출 후 대입
- 문제 코드
sub = df[["user", "age"]]
sub["age"] = sub["age"] + 1
- 해결 코드 1: 원본을 바꾸고 싶은 경우
df.loc[:, "age"] = df["age"] + 1
- 해결 코드 2: 서브셋을 독립적으로 쓰고 싶은 경우
sub = df[["user", "age"]].copy()
sub["age"] = sub["age"] + 1
패턴 C: 정렬, 드롭 이후 대입
정렬이나 결측 제거 후 이어서 대입할 때도 경고가 나올 수 있습니다.
- 문제 코드
clean = df.dropna().sort_values("age")
clean["age"] = clean["age"] * 2
- 해결 코드
clean = df.dropna().sort_values("age").copy()
clean["age"] = clean["age"] * 2
dropna나 sort_values 결과가 내부적으로 어떤 형태로 최적화될지 확정할 수 없기 때문에, 이후 대입이 있다면 .copy()로 의도를 고정하는 편이 안전합니다.
디버깅: “원본이 실제로 바뀌었는지” 확인하는 습관
경고가 떴을 때 가장 무서운 건, 코드가 계속 돌아가면서 결과가 그럴듯해 보이는 겁니다. 다음을 습관화하면 조기 발견에 도움이 됩니다.
1) 대입 전후로 동일 인덱스/컬럼을 직접 비교
mask = df["city"] == "seoul"
before = df.loc[mask, "age"].copy()
df.loc[mask, "age"] = df.loc[mask, "age"] + 1
after = df.loc[mask, "age"]
assert (after.values == (before.values + 1)).all()
2) 파이프라인 중간 산출물을 명시적으로 복사
특히 ETL에서 “중간 테이블” 개념으로 DataFrame을 들고 간다면, 애매한 뷰를 허용하지 않는 것이 좋습니다.
stage1 = df.loc[df["age"] >= 20].copy()
stage1["segment"] = "adult"
성능 관점에서의 선택 기준
- 원본을 수정해야 한다:
loc로 한 번에 대입이 가장 빠르고 메모리 효율적입니다. - 중간 결과를 독립적으로 안전하게 다뤄야 한다:
.copy()비용을 지불하고 명확성을 얻는 편이 낫습니다. - 불변 데이터 흐름을 선호한다:
assign,where,mask같은 “새 DataFrame 생성” 패턴이 테스트/재현성에서 강합니다.
대규모 데이터에서 .copy()가 부담이라면, 애초에 “서브셋을 만들고 거기에 대입”하는 구조를 줄이고 loc 기반으로 원본에 직접 반영하는 형태로 리팩터링하는 것이 좋습니다.
체크리스트: SettingWithCopyWarning을 다시는 안 보려면
df[mask][col] = ...형태를 발견하면 무조건df.loc[mask, col] = ...로 바꾼다- 서브셋에 대입이 필요하면
.copy()를 붙여 의도를 고정한다 - 경고를 전역 옵션으로 끄지 않는다
- “중간 DataFrame을 만든 뒤 대입”하는 설계를 줄이고, 가능한 한 원본 기준
loc대입으로 끝낸다
추가로, 데이터 전처리에서 자주 함께 터지는 이슈로 merge 이후 행 수가 폭증하는 문제도 있습니다. 조인 키 중복이 원인인 경우가 많으니, 관련 상황이라면 pandas merge 후 행 수 폭증? 중복키 진단·해결도 같이 점검해보세요.
마무리
SettingWithCopyWarning은 Pandas가 친절하게 알려주는 “잠재적 데이터 버그” 경고입니다. 해결의 본질은 뷰/복사본의 불확실성을 제거하는 것이고, 방법은 두 가지로 정리됩니다.
- 원본을 고치려면
loc로 단일 단계 대입 - 서브셋을 고치려면
.copy()로 독립성 선언
이 두 규칙만 지켜도 경고는 대부분 사라지고, 무엇보다 결과가 환경에 따라 달라지는 불안정한 코드를 안정적으로 바꿀 수 있습니다.