판다스 3.1 업그레이드 가이드: inplace Pandas4Warning 해결, df.select()·pd.col() 사용법, read_csv 속도 향상
업데이트

요약: pandas 3.1.0은 2026년 10월 22일에 출시되었고, 버그 수정 릴리스인 3.1.1이 11월 4일에 뒤이어 나왔습니다. 바로 3.1.1로 업그레이드하세요.
pip install --upgrade "pandas>=3.1"
# or, in a uv project
uv add "pandas>=3.1"업그레이드하면 세 가지가 달라집니다.
inplace=True마다Pandas4Warning이 출력됩니다.drop,rename,reset_index,set_index,sort_values,dropna,drop_duplicates등이 대상입니다. 수정은 기계적입니다.df.drop(..., inplace=True)대신df = df.drop(...)으로 바꾸면 됩니다. 경고가 내 코드가 아니라 사용 중인 라이브러리에서 나오는 경우도 많습니다.- 대용량 로컬 파일에서
pd.read_csv가 훨씬 빨라집니다. 테스트에서 72 MB 파일이 pandas 3.0.1의 0.23 s에서 3.1의 0.02 s로 줄었습니다. 자주 쓰는 인자 몇 개가 이 속도 향상을 꺼 버리는데, 아래에 정리했습니다. - Polars 스타일 API가 생겼습니다.
df.select("a", pd.col("b") * 2)와df.filter(pd.col("a") > 1)을 쓸 수 있습니다.
아직 pandas 2.x를 쓰고 있다면 3.x로의 전환(Copy-on-Write, 기본 문자열 dtype)이 더 큰 변화이므로 pandas 3.0 마이그레이션 가이드 (opens in a new tab)부터 보세요. 이 글은 3.0 → 3.1 업그레이드와 그 과정에서 만난 함정을 다룹니다.
테스트 범위: "테스트함"으로 표시한 항목은 모두 2026년 11월 1일 Apple M4 Max(16코어, macOS)에서 새로 만든
uv가상 환경에 CPython 3.14.2, pandas 3.1.0, NumPy 2.5.4, PyArrow 26.0.0을 설치해 실행했습니다. 비교 수치는 같은 머신에서 pandas 3.0.1, NumPy 2.4.2, PyArrow 없이 측정했습니다. Windows, Linux, conda, Docker 동작은 공식 문서를 참고한 것이며 직접 테스트하지 않았습니다. 2026년 11월 4일에는 테스트한 내용을 모두 pandas 3.1.1에서 다시 실행했고 결과는 같았습니다.
3.1에서 체감할 변경 사항
| 변경 사항 | 의미 | 할 일 |
|---|---|---|
11개 메서드에서 inplace deprecated (PDEP-8) | 호출마다 Pandas4Warning 발생, pandas 4.0에서 제거 예정 | 결과를 재할당: df = df.drop(...) |
5 MB 이상의 로컬 비압축 파일에 병렬 read_csv 적용 | 단일 스레드에서 2~3배 빠르고, 기본으로 최대 6개 스레드 사용 | usecols, index_col, nrows, 파일 핸들을 넘기지 않는다면 할 일 없음 (아래 참고) |
DataFrame.select() (3.0부터 있던 pd.col() 표현식을 받음) | Polars처럼 한 번의 호출로 컬럼 선택과 계산 | 선택 사항, 메서드 체인에서 유용 |
filter(cond=...) 가 불리언 마스크를 받음 | df.filter(pd.col("a") > 1)로 행을 유지 | 주의: 불리언 리스트를 위치 인자로 넘기면 여전히 라벨 선택 |
| NumPy 2.0.2 이상 필수 (Python 3.14에서는 2.3.3 이상) | NumPy 1.x 환경에는 3.1을 설치할 수 없음 | NumPy부터 올리거나 pandas<3.1로 고정 |
| 숫자 입력의 기본 단위가 마이크로초 | pd.to_timedelta([1], unit="s")가 [s]가 아닌 timedelta64[us] | 3.0의 반올림 버그 수정, 특정 해상도가 필요하면 .as_unit() 사용 |
read_csv의 dtype="category"가 숫자 카테고리를 추론 | 우편번호와 ID의 앞자리 0이 사라짐 | 해당 컬럼을 먼저 str로 읽기 |
전체 목록은 공식 What's new in 3.1.0 (opens in a new tab)에 있습니다. 이 글의 나머지는 실제 작업 순서대로 진행합니다. 설치, 경고 수정, 새 API 사용, CSV 로딩 속도 향상, 그리고 오류 없이 결과를 바꿀 수 있는 동작 변경 확인 순서입니다.
- pandas 3.1 업그레이드: inplace Pandas4Warning 해결과 read_csv 속도 향상
- OpenCode 2.0 업그레이드 방법: v1 설정·플러그인·세션 마이그레이션 가이드
- 파이썬 3.15 설치·업그레이드 가이드: uv 사용법, 지연 임포트(lazy import), 데이터 패키지 호환성
- Claude Code Mods 사용법: 첫 Mod 만들기·테스트·설치와 커뮤니티 예시 15선
- Claude Code AGENTS.md 지원: 설정 방법, 4가지 로딩 모드, Codex·OpenCode·Cursor와 한 파일 공유하기
- GPT Image 2.5 사용법: Flare·Sunburst 비교와 API 가격
- DeepSeek Harness 사용법: 설치, 설정, 첫 에이전트 실행까지
- Runcell Science: Claude Science를 대체할 오픈소스 AI 연구 워크스페이스
- 맥 잠자기 방지: 맥북 닫아도 Codex와 Claude Code 계속 실행하기
- OpenClaw vs ZeroClaw vs Pi Agent vs Nanobot: 2026년에 어떤 AI 에이전트 스택을 선택해야 할까?
- Claude Code로 Jupyter 노트북을 분석하는 방법 | Data Science 실무 가이드와 한계
- Claude Code 루틴 사용법: AI 에이전트 cron 작업과 자동 트리거
- Claude Code Desktop에서 Bypass permissions 켜는 법
- Google의 A2A 프로토콜을 사용한 두 개의 Python 에이전트 빌드하기 - 단계별 튜토리얼
- 2025년 파이썬에서 가장 성장하는 상위 10개 데이터 시각화 라이브러리
1단계: pandas 3.1 설치
요구 사항 (테스트함)
pandas 3.1.0은 CPython 3.11부터 3.15까지의 휠(wheel)을 제공하며(requires_python >= 3.11), PyPI와 conda-forge 모두에 올라와 있습니다. NumPy 최소 버전은 Python 버전에 따라 다릅니다.
| Python | NumPy 최소 버전 |
|---|---|
| 3.11 – 3.13 | 2.0.2 |
| 3.14 – 3.15 | 2.3.3 |
whatsnew 페이지에는 2.0.2만 적혀 있습니다. Python 3.14에서는 휠 메타데이터가 2.3.3을 요구하고, 리졸버가 이를 강제합니다. NumPy 1.x를 고정해 두면 바로 실패합니다.
× No solution found when resolving dependencies:
╰─▶ Because pandas==3.1.1 depends on numpy>=2.3.3 and you require
pandas==3.1.1, we can conclude that you require numpy>=2.3.3.
And because you require numpy<2, we can conclude that your requirements
are unsatisfiable.어떤 의존성이 아직 numpy<2를 고정하고 있다면 업그레이드를 막는 것은 pandas가 아니라 그 의존성입니다. 그 패키지를 업데이트하거나, 업데이트될 때까지 pandas<3.1을 유지하세요.
업그레이드 (uv로 테스트함)
3.1.0이 아니라 3.1.1을 설치하세요. 3.1.1은 read_csv와 to_csv가 pytest의 tmpdir 같은 경로 객체를 열린 파일 핸들로 취급해 오류를 내거나 파일을 불완전하게 쓰던 회귀 버그를 고쳤습니다(What's new in 3.1.1 (opens in a new tab)).
# pip
pip install --upgrade "pandas>=3.1,<3.2"
# uv project
uv add "pandas>=3.1,<3.2"
# uv, throwaway environment to test first
uv venv p31 --python 3.14
uv pip install --python p31/bin/python "pandas==3.1.1" pyarrow
p31/bin/python -c "import pandas as pd; print(pd.__version__)"conda-forge에는 3.1.1이 올라와 있습니다(2026년 11월 4일 확인, conda install -c conda-forge "pandas>=3.1"). 직접 테스트하지는 않았습니다.
PyArrow가 없다면 함께 설치하세요. pandas 3은 PyArrow가 있으면 기본 문자열 dtype에 사용하고, engine="pyarrow" CSV 리더에도 필요합니다.
2단계: inplace의 Pandas4Warning 해결
업그레이드 후 다음과 같은 코드는
df.drop(columns=["rain"], inplace=True)이런 경고를 출력합니다.
Pandas4Warning: The inplace keyword in DataFrame.drop is deprecated and will be removed in a future version (PDEP-8).
See https://pandas.pydata.org/docs/dev/whatsnew/v3.1.0.html#deprecation-inplace for more details.영향받는 메서드 (테스트함)
이번 deprecation은 inplace=True가 값을 제자리에서 바꾼 적이 없는 메서드를 대상으로 합니다. dropna, sort_values 같은 행 재정렬 계열 메서드는 내부적으로 항상 재할당이었고, drop, rename, set_index가 예전에 피하던 복사는 pandas 3.0에 들어온 Copy-on-Write 아래에서는 어차피 일어나지 않습니다.
| 메서드 | 3.1에서의 inplace | 수정 방법 |
|---|---|---|
drop, rename, rename_axis | Deprecated | df = df.drop(...) |
reset_index, set_index | Deprecated | df = df.reset_index() |
sort_values, sort_index | Deprecated | df = df.sort_values("col") |
dropna, drop_duplicates | Deprecated | df = df.dropna() |
eval, query | Deprecated | df = df.query("a > 1") |
fillna, replace, where, mask, clip, interpolate | 유지 (값을 제자리에서 갱신할 수 있음) | 변경 불필요 |
테스트에서는 deprecated된 11개 메서드가 모두 inplace=True로 호출했을 때 경고를 냈고, fillna와 replace는 문서대로 경고하지 않았습니다.
호출마다 한 줄만 바꾸면 됩니다.
# before
df.rename(columns={"temp": "temp_c"}, inplace=True)
df.sort_values("temp_c", inplace=True)
df.reset_index(drop=True, inplace=True)
# after (same cost, chains cleanly)
df = (
df.rename(columns={"temp": "temp_c"})
.sort_values("temp_c")
.reset_index(drop=True)
)코드베이스에서 모든 호출 찾기 (테스트함)
한 번만 경고를 오류로 바꿔 실행하면 트레이스백이 정확한 줄을 가리킵니다. 단, python -W는 pandas.errors.Pandas4Warning 같은 서드파티 클래스를 받지 않습니다. Invalid -W option ignored: invalid module name: 'pandas.errors'를 출력하고 스크립트를 그냥 실행해 버립니다. 대신 메시지로 매칭하세요.
python -W "error:The inplace keyword:DeprecationWarning" your_script.pyinplace 경고뿐 아니라 모든 Pandas4Warning을 오류로 바꾸려면 다음과 같이 실행합니다.
python -c "import runpy, warnings, pandas as pd; warnings.simplefilter('error', pd.errors.Pandas4Warning); runpy.run_path('your_script.py', run_name='__main__')"두 방법 모두 첫 번째 inplace 줄에서 트레이스백과 함께 종료됩니다. 메시지 필터는 관련 없는 deprecation 경고는 건드리지 않습니다.
테스트 스위트라면 pytest가 pandas를 임포트할 수 있는 시점 이후에 -W 필터를 적용하므로 클래스 이름이 그대로 동작합니다.
pytest -W error::pandas.errors.Pandas4WarningPandas4Warning은 PandasDeprecationWarning의 서브클래스이므로 어느 쪽으로 필터를 걸어도 동작합니다. grep으로도 대부분 찾을 수 있습니다.
grep -rn "inplace=True" --include="*.py" --include="*.ipynb" .경고가 내 코드가 아니라 라이브러리에서 나올 때
릴리스 직후 몇 주 동안 가장 흔한 경우입니다. 2026년 10월 22일부터 11월 1일 사이에 GitHub에는 Pandas4Warning을 언급한 이슈와 PR이 약 300건, "pandas 3.1"을 언급한 것이 약 3,500건 늘었습니다. yfinance(8개 파일에 inplace 호출 29개, yfinance #3009 (opens in a new tab). 11월 3일 yfinance 1.8.0에서 수정됨), FLAML, marimo, cuDF, darts, category_encoders 같은 프로젝트가 포함됩니다.
이런 경고는 내 코드에서 고칠 수 없습니다. 선택지를 권장 순서대로 정리하면 다음과 같습니다.
- 라이브러리를 업데이트합니다. 많은 프로젝트가 며칠 안에 수정을 배포했습니다.
- 라이브러리가 따라올 때까지 이 경고 클래스 하나만 끕니다. 모든 경고를 끄지는 마세요.
import warnings
import pandas as pd
warnings.filterwarnings("ignore", category=pd.errors.Pandas4Warning)- 관리되지 않는 라이브러리를 돌리는 환경에서는
pandas<3.1로 고정합니다. 단, pandas 3.0에 영원히 묶어 두지는 마세요.inplace키워드는 4.0에서 제거되며, 이 경고는 그 유예 기간을 알리는 신호입니다.
3.1에서 만날 수 있는 다른 deprecation (별도 표기가 없으면 테스트함)
| 지금 경고가 나는 코드 | 대체 |
|---|---|
s.dt.dayofweek, .dayofyear, .daysinmonth | s.dt.day_of_week, .day_of_year, .days_in_month |
문자열 컬럼에 df.select_dtypes(include="object") (3.0부터 이미 경고, 3.1에서 exclude="object"도 추가) | "str"을 명시. pandas 3에서 "object"는 더 이상 "텍스트"를 뜻하지 않음 |
float 컬럼에 s.fillna("missing") | 먼저 공통 dtype으로 캐스팅. 향후 버전에서 오류가 됨 |
시간대 인식(timezone-aware) 컬럼의 ser.values | ser.array, ser.to_numpy(), 또는 기존 UTC 배열을 유지하려면 ser.dt.tz_convert(None).to_numpy() |
pd.infer_freq(idx)가 "D"를 반환 | 앞으로 offset 객체를 반환. .freqstr을 쓰거나 future.infer_freq_returns_offset 설정 |
read_parquet(engine="fastparquet") (문서) | fastparquet은 지원 종료. 기본 PyArrow 엔진 사용 |
pd.set_eng_float_format() (문서) | pd.set_option("display.precision", n) |
3단계: df.select(), pd.col(), filter(cond=) 사용하기
3.1에서 눈에 띄는 새 API는 DataFrame.select()와 filter(cond=)입니다. pd.col() 자체는 pandas 3.0에서 assign()과 .loc용으로 먼저 들어왔고, 3.1에서는 두 새 메서드에 자연스럽게 넘기는 인자가 됩니다. Polars를 써 봤다면 익숙할 것입니다.
select는 한 번의 호출로 컬럼을 선택하고 계산합니다 (테스트함)
import pandas as pd
df = pd.DataFrame({
"city": ["Tokyo", "Seoul", "Osaka"],
"temp": [18.2, 15.1, 19.5],
"rain": [12, 30, 8],
})
df.select("city", "temp") # same as df[["city", "temp"]]
df.select("city", temp_f=pd.col("temp") * 1.8 + 32) # keyword names the new column city temp_f
0 Tokyo 64.76
1 Seoul 59.18
2 Osaka 67.10assign처럼, 뒤에 오는 인자는 같은 호출 안에서 앞서 만든 컬럼을 참조할 수 있습니다.
df.select(temp_c=pd.col("temp"), temp_f=pd.col("temp_c") * 1.8 + 32)pd.col("temp")는 pandas.api.typing.Expression입니다. Series에서 쓰던 연산자와 접근자를 지원하며 .str과 .round()도 포함됩니다.
df.select(pd.col("city").str.upper(), pd.col("temp").round(0))테스트 중에 걸린 세 가지입니다 (테스트함).
.alias()가 없습니다. 계산한 컬럼의 이름은 키워드 형태(temp_f=...)로 지정하세요.pd.col("temp").alias("x")는 만들 때는 아무 문제가 없지만, 이어서df.select(...)를 실행하면AttributeError: 'Series' object has no attribute 'alias'로 실패합니다.pd.col("temp").mean()처럼 스칼라로 축약되는 표현식은 키워드로 넘겨야 합니다(mean_temp=pd.col("temp").mean()). 그러면 모든 행에 브로드캐스트됩니다. 위치 인자로 넘기면TypeError가 납니다.- 없는 컬럼은
df[["nope"]]와 마찬가지로KeyError를 냅니다.
filter(cond=)는 불리언 마스크로 행을 유지합니다 (테스트함)
DataFrame.filter는 원래 라벨만 선택했습니다(items=, like=, regex=). 3.1에서는 마스크도 받습니다.
df.filter(cond=df["temp"] > 17) # a boolean Series
df.filter(pd.col("temp") > 17) # an expression, positional is fine
df.filter(lambda d: d["rain"] < 20) # a callable체인으로 묶으면 pandas DataFrame을 유지하면서 Polars처럼 읽힙니다.
(
df.filter(pd.col("rain") < 20)
.select("city", "temp")
.sort_values("temp")
)마스크의 결측값은 기본적으로 False로 처리됩니다. 유지하려면 na=True, 오류를 내려면 na="raise"를 넘기세요.
함정: 불리언 리스트를 그대로 위치 인자로 넘기면 여전히 라벨 경로로 갑니다. 테스트에서 df.filter([True, False, True])는 컬럼이 0개인 DataFrame을 반환하면서 cond=를 쓰라는 UserWarning을 냈습니다. 마스크가 리스트나 배열이라면 항상 cond=를 쓰세요.
Polars와 pandas 3.1 비교
| 작업 | Polars | pandas 3.1 |
|---|---|---|
| 컬럼 선택 | df.select("a", "b") | df.select("a", "b") |
| 컬럼 계산 | df.select(pl.col("a") * 2) | df.select(pd.col("a") * 2) |
| 이름 지정 | (pl.col("a") * 2).alias("a2") | df.select(a2=pd.col("a") * 2) |
| 행 필터 | df.filter(pl.col("a") > 1) | df.filter(pd.col("a") > 1) |
| 집계 | df.select(pl.col("a").mean()) → 1행 | df.select(m=pd.col("a").mean()) → 모든 행에 브로드캐스트 |
pandas가 Polars가 되는 것은 아닙니다. select 뒤에 지연 평가(lazy evaluation)나 쿼리 최적화기는 없습니다. 체인을 읽기 쉽게 만들고 DataFrame 이름을 반복하지 않게 해 주는 문법일 뿐입니다. 엔진 전환을 고민한다면 Polars vs pandas에서 그 판단을 다룹니다.
4단계: 빨라진 read_csv 활용하기, 그리고 무엇이 이를 끄는지 알기
벤치마크 (테스트함)
파일 하나: 3,000,000행 × 3열(float, int, string), 72.5 MB, 비압축, 로컬 SSD. 3회 실행 중 최고 기록입니다.
| 리더 | 시간 | pandas 3.0.1 대비 |
|---|---|---|
pandas 3.0.1, engine="c" | 0.233 s | 1x |
pandas 3.1.0, mode.max_threads = 1 (직렬) | 0.089 s | 2.6x |
| pandas 3.1.0, 스레드 2개 | 0.049 s | 4.8x |
| pandas 3.1.0, 스레드 4개 | 0.025 s | 9.3x |
| pandas 3.1.0, 기본값 (스레드 6개) | 0.018 s | 13x |
pandas 3.1.0, engine="pyarrow" | 0.030 s | 7.8x |
직렬 행은 릴리스 노트의 "2~3배 빠름"과 일치하고, 병렬 행은 그 위에 더해지는 효과입니다. 파일마다 결과는 다르겠지만(문자열 컬럼이 많으면 파싱이 느려집니다), 핵심은 결과의 형태입니다. 이제 기본 engine="c" 리더가 일반 로컬 파일에서는 PyArrow보다 빠릅니다.
적용 조건 (문서 기준, 위 측정값으로 확인)
병렬 경로는 다음 조건이 모두 충족될 때 자동으로 동작합니다.
filepath_or_buffer가 로컬의 비압축 파일 경로일 때- 기본 C 엔진을 사용할 때
- 데이터 행이 총 5 MB 이상일 때
mode.max_threads가 1보다 클 때(기본값: CPU 개수, 최대 6, 프로세스가 CPU 1개로 제한되면 1)- 파일 전체를 한 번에 필요로 하는 옵션이 없을 때:
iterator,chunksize,nrows,usecols,index_col,parse_dates, 리스트나 callable 형태의skiprows, 다중 행 헤더, UTF-8이 아닌 인코딩
그 외에는 모두 직렬 리더로 돌아가며, 결과는 항상 직렬 읽기와 동일합니다.
테스트에서의 실제 결과 (테스트함)
| 호출 | 시간 | 병렬? |
|---|---|---|
pd.read_csv("big.csv") | 0.018 s | 예 |
pd.read_csv("big.csv", usecols=["a", "c"]) | 0.079 s | 아니요 |
pd.read_csv("big.csv", index_col=0) | 0.091 s | 아니요 |
pd.read_csv("big.csv", nrows=1_000_000) | 0.032 s | 아니요 (직렬, 파일의 1/3) |
pd.read_csv("big.csv", converters={"b": int}) | 0.255 s | 아니요 (일반 읽기보다 느리지만 3.0에서 같은 호출보다는 빠름) |
pd.read_csv(open("big.csv", "rb")) | 0.091 s | 아니요 (경로가 아닌 파일 핸들) |
pd.read_csv(io.StringIO(text)) | 0.131 s | 아니요 |
pd.read_csv("big.csv.gz") | 0.163 s | 아니요 (압축) |
pd.concat(pd.read_csv("big.csv", chunksize=500_000)) | 0.099 s | 아니요 |
pd.read_csv("big.csv", dtype={...}) | 0.034 s | 예 (명시적 dtype은 괜찮음) |
pd.read_csv("big.csv", na_values=["z"]) | 0.025 s | 예 |
실무에서 두 가지 결론이 나옵니다.
usecols가 이제 읽기를 빠르게 하는 대신 느리게 만들 수 있습니다. 오랫동안 "필요 없는 컬럼은usecols로 건너뛰라"는 조언이 통용됐습니다. 3.1에서 5 MB 이상의 로컬 파일은, 테스트 기준으로 전체를 병렬로 읽은 뒤 컬럼을 버리는 쪽이usecols보다 4배 빨랐습니다. 예전 습관을 유지하기 전에 측정하세요.- 핸들이 아니라 경로를 넘기세요.
pd.read_csv("file.csv")는 병렬이지만pd.read_csv(open("file.csv"))는 아닙니다. pandas에 경로 대신 열린 파일 객체를 넘기는 래퍼는 속도 향상을 잃습니다.
이미 병렬인 애플리케이션 안에서는 스레드 수 낮추기
pandas가 Dask, Ray, multiprocessing 풀, 또는 이미 모든 코어를 쓰는 웹 워커 안에서 돌아간다면 스레드가 서로 경쟁하지 않도록 상한을 두세요.
import pandas as pd
with pd.option_context("mode.max_threads", 1):
df = pd.read_csv("large.csv")
# or globally
pd.set_option("mode.max_threads", 2)Docker와 Kubernetes에서는 상한이 cgroup CPU 쿼터를 따르므로(문서 기준, 직접 테스트하지 않음) CPU 1개로 제한된 컨테이너는 별도 설정 없이 직렬로 읽습니다.
5단계: 오류 없이 바뀌는 동작 확인하기
조용히 결과를 바꿀 가능성이 가장 큰 변경들입니다. 각각 3.0.1과 3.1.0에서 재현했습니다 (테스트함).
숫자로 만든 Timedelta와 datetime의 기본 단위가 마이크로초로 변경
3.0에서 pd.to_timedelta([1], unit="s")는 timedelta64[s]를 만들고, 연산도 그 해상도를 유지했습니다. 나누기를 하면 초 단위로 반올림됐습니다.
td = pd.to_timedelta([1], unit="s")
print(td / 3)
# pandas 3.0.1 → TimedeltaIndex(['0 days'], dtype='timedelta64[s]', freq=None)
# pandas 3.1.x → TimedeltaIndex(['0 days 00:00:00.333333'], dtype='timedelta64[us]', freq=None)3.1은 변환에 쓴 unit과 무관하게 기본값을 마이크로초로 바꿔 이 문제를 고쳤습니다. dtype을 비교하거나 [s] 해상도에 의존하는 코드라면 .as_unit("s")를 명시하세요. 숫자 입력을 받는 Timestamp와 to_datetime도 같은 방식으로 바뀌었습니다.
관련 변경: Timedelta.total_seconds()가 이제 나노초를 포함합니다. pd.Timedelta(nanoseconds=1500).total_seconds()는 1e-06 대신 1.5e-06을 반환합니다.
read_csv의 dtype="category"가 숫자 카테고리를 추론
from io import StringIO
data = "zip\n00501\n00501\n10001"
pd.read_csv(StringIO(data), dtype="category")["zip"].cat.categories.tolist()
# pandas 3.0.1 → ['00501', '10001']
# pandas 3.1.0 → [501, 10001] leading zeros goneC 엔진이 이제 PyArrow 엔진이 이미 하던 대로 동작합니다. 우편번호, 계좌번호, 그 밖에 0으로 채운 식별자는 컬럼을 str로 읽은 뒤 변환하세요.
df = pd.read_csv("file.csv", dtype={"zip": "str"})
df["zip"] = df["zip"].astype("category")예전에는 조용히 통과했지만 이제 오류가 나는 것들
pd.Timestamp(datetime(2020, 12, 31), hour=5)는 테스트에서ValueError를 냈습니다. 3.0에서는hour를 조용히 무시했습니다. 모든 구성 요소를 키워드로 넘기세요.pd.to_timedelta("3:25:00 PM")은 오류를 냅니다. 3.0은 이를 3시간으로 파싱하고PM을 버렸습니다.- 기본값인
axis="columns"방향으로 DataFrame과categorySeries 사이에 산술 연산이나 순서 비교를 하면(예:df + cat_series) 이제 카테고리에 연산을 적용하는 대신TypeError를 냅니다. 동등 비교는 바뀌지 않았습니다 (문서, 재현함). Index.values와Index.array가 읽기 전용 배열을 반환하므로, 이를 통해 인덱스를 변경하던 코드는 조회 결과를 망가뜨리는 대신 명확하게 실패합니다 (문서).
PyArrow 기반 문자열의 정규식 (테스트함)
PyArrow가 설치된 환경에서 .str.replace, .str.count 등의 \w, \d, \s, \b는 pandas 3.0에서 ASCII만 매칭했습니다. 3.1에서는 Python의 re와 pandas 2.x처럼 다시 Unicode를 매칭합니다. PyArrow 26.0.1에서 pd.Series(["héllo wörld", "naïve 東京"]).str.count(r"\w")는 pandas 3.0.6에서 [8, 4], 3.1.1에서 [10, 7]을 반환했습니다. 영어가 아닌 텍스트를 처리하는데 3.0으로 옮긴 뒤 집계 수치가 줄었다면 이것이 해결책입니다. 3.1.1에서는 PyArrow 문자열에서 \s가 \x0b와 \x1c를 매칭하지 않던 문제도 고쳤습니다.
지금 업그레이드해야 할까?
| 상황 | 권장 |
|---|---|
| 새 프로젝트, Python 3.11 이상 | 예. 3.1에서 시작하고 inplace 없이 작성 |
| 3.0에서 돌아가는 스크립트와 노트북 | 예. -W "error:The inplace keyword:DeprecationWarning"(또는 pytest -W error::pandas.errors.Pandas4Warning)으로 한 번 실행해 해당 줄을 고치고, 빨라진 CSV 읽기를 그대로 누리세요 |
dtype="category"로 0이 채워진 ID를 읽는 라이브러리나 파이프라인 | 업그레이드하되 먼저 dtype={"id": "str"} 추가 |
| FLAML, darts 등에 의존 | 해당 라이브러리가 3.1 수정판을 내면 업그레이드(yfinance는 1.8.0에서 이미 수정), 그동안은 Pandas4Warning 필터링 |
| 아직 pandas 2.x | 2 → 3 마이그레이션(Copy-on-Write, 문자열 dtype)을 먼저. 3.1은 그 위에 얹는 작은 변화 |
| 아직 NumPy 1.x | 막혀 있음. NumPy부터 업그레이드 |
Jupyter에서 pandas 3.1 사용하기
노트북 자체에서 바뀌는 것은 없습니다. 두 가지만 참고하세요.
-W error는 커맨드라인 플래그이므로 실행 중인 커널에는 적용되지 않습니다. 정리하는 동안 첫 셀에warnings.simplefilter("error", pd.errors.Pandas4Warning)을 넣고, 끝나면 제거하세요.- 병렬 CSV 리더는 free-threaded Python 빌드가 필요 없습니다. 표준 인터프리터에서 동작합니다.
대용량 파일이 1초도 안 걸려 로드되면, 이제 느린 부분은 데이터를 살펴보는 일입니다. PyGWalker (opens in a new tab) 0.5.0.1은 테스트에서 pandas 3.1과 함께 동작했습니다(pyg.to_html(df)가 50,000행 프레임을 0.23 s에 렌더링). groupby 코드를 쓰기 전에 방금 로드한 DataFrame을 드래그 앤 드롭으로 한 번 훑어보는 것이 합리적인 다음 단계입니다.
문제 해결
ResolutionImpossible / "requires numpy>=2.3.3" — Python 3.14 또는 3.15에서 오래된 NumPy 고정을 쓰고 있습니다. NumPy를 업그레이드하거나 고정을 풀어 주세요. Python 3.11~3.13에서는 최소 버전이 2.0.2입니다.
site-packages에서 수백 줄의 Pandas4Warning — 내 코드가 아니라 의존성입니다. 업데이트하세요. 아직 수정판이 없다면 warnings.filterwarnings("ignore", category=pd.errors.Pandas4Warning)을 쓰고 트레이스백과 함께 이슈를 올리세요.
read_csv가 빨라지지 않음 — 4단계의 목록을 확인하세요. 흔한 원인은 usecols, index_col, 압축 파일, 경로 대신 넘긴 파일 객체입니다. pd.get_option("mode.max_threads")가 1이 아닌지도 확인하세요(기본값은 None이며 "auto"를 뜻합니다).
df.filter([...])가 컬럼을 하나도 반환하지 않음 — 불리언 리스트를 위치 인자로 넘겼습니다. df.filter(cond=[...])를 쓰세요.
AttributeError: 'Series' object has no attribute 'alias' — pd.col 표현식에서는 alias가 동작하지 않습니다. select의 키워드 형태로 컬럼 이름을 지정하세요.
우편번호의 앞자리 0이 사라짐 — dtype="category"가 이제 정수를 추론합니다. 먼저 str로 읽으세요.
Timedelta dtype이 [s]에서 [us]로 바뀜 — 3.1에서 의도된 변경입니다. 다운스트림 시스템이 초 단위를 요구하면 .as_unit("s")를 쓰세요.
자주 묻는 질문
Pandas4Warning이 무엇이고 어떻게 해결하나요?
4.0에서 바뀌는 동작에 대해 pandas 3.1이 내는 deprecation 경고입니다. 대부분 drop, rename, reset_index, set_index, sort_values, dropna, drop_duplicates의 inplace=True에서 나옵니다. 대신 결과를 재할당하세요: df = df.drop(columns=["a"]). python -W "error:The inplace keyword:DeprecationWarning" your_script.py(테스트 스위트라면 pytest -W error::pandas.errors.Pandas4Warning)를 실행하면 남아 있는 첫 번째 호출에서 트레이스백과 함께 멈춥니다. 고친 뒤 다시 실행하세요.
pandas 3.1에서 inplace=True가 제거되었나요?
아니요. 3.1에서는 여전히 동작하며 경고만 출력합니다. 제거는 pandas 4.0으로 예정되어 있고, 데이터를 제자리에서 수정한 적이 없는 메서드에만 해당합니다. fillna, replace, where, mask, clip, interpolate는 키워드를 유지합니다.
read_csv가 왜 빨라졌고, 내 환경에서는 왜 그렇지 않나요?
C 엔진이 2~3배 빨라졌고 대용량 로컬 파일은 기본으로 병렬로 읽습니다. 변화가 없다면 usecols, index_col, nrows, chunksize, 압축 파일, 또는 열린 파일 객체를 넘기고 있을 가능성이 높습니다. 4단계의 표를 확인하세요.
df.select()는 Polars의 select와 같은가요?
문법은 의도적으로 비슷하지만 pandas는 즉시 평가하며 쿼리 최적화기가 없습니다. 성능이 아니라 읽기 쉬운 체인을 위해 쓰세요.
pandas 3.1은 Python 3.15에서 동작하나요?
네. CPython 3.11부터 3.15까지의 휠이 배포되어 있습니다. 3.14와 3.15에서는 NumPy 2.3.3 이상이 필요합니다. Python 쪽 업그레이드는 파이썬 3.15 업그레이드 가이드를 참고하세요.
관련 가이드
- Pandas read_csv() 튜토리얼: CSV 파일을 전문가처럼 불러오기
- Pandas 열 삭제: DataFrame에서 열을 제거하는 방법
- Pandas 행 필터링: Python에서 조건별로 데이터 선택하기
- Pandas에서 컬럼 이름 바꾸기: 쉽고 효과적인 방법
- Polars 대 Pandas 비교
- 파이썬 3.15 설치·업그레이드 가이드: uv 사용법, 지연 임포트(lazy import), 데이터 패키지 호환성
- Python 패키지 업그레이드 방법