Pandas 3.0 ์๋ฒฝ ๊ฐ์ด๋(2026): ์๋ก์ด API, ์ฃผ์ ๋ณ๊ฒฝ์ฌํญ, ๋ฉด์ ์ง๋ฌธ ์ด์ ๋ฆฌ
Pandas 3.0์ Copy-on-Write, PyArrow ๋ฌธ์์ด ๋ฐฑ์๋, pd.col() ํํ์ ๋น๋ ๋ฑ ํต์ฌ ๋ณ๊ฒฝ์ฌํญ์ ์์ธํ ๋ถ์ํฉ๋๋ค. ๋ฐ์ดํฐ ๋ถ์ ์์ง๋์ด ๋ฉด์ ์์ ์ถ์ ๋๋ ํต์ฌ ์ง๋ฌธ๋ ํจ๊ป ๋ค๋ฃน๋๋ค.

Pandas 3.0์ 2026๋
1์ 21์ผ์ ๋ฆด๋ฆฌ์ค๋์์ผ๋ฉฐ, ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ 1.x ์๋ ์ดํ ๊ฐ์ฅ ๋๊ท๋ชจ์ ์ํคํ
์ฒ ๋ณ๊ฒฝ์ ๋์
ํ์ต๋๋ค. Copy-on-Write๊ฐ ๊ธฐ๋ณธ ๋์์ด ๋์๊ณ , ๋ฌธ์์ด ์ปฌ๋ผ์ PyArrow ๊ธฐ๋ฐ์ ์ ์ฉ dtype์ผ๋ก ์ ํ๋์์ผ๋ฉฐ, ์๋ก์ด pd.col() ํํ์ ๋น๋๊ฐ ๋๋ค ํจ์์ ๊น๋ํ ๋์์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฌํ ๋ณ๊ฒฝ์ฌํญ์ ๊ธฐ์กด์ ๋ชจ๋ ์ฝ๋๋ฒ ์ด์ค์ ์ํฅ์ ๋ฏธ์น๋ฉฐ, ๋ฐ์ดํฐ ์์ง๋์ด๋ง ๋ฉด์ ์์๋ ๋น์ถ ์ฃผ์ ๋ก ์๋ฆฌ์ก์์ต๋๋ค.
Pandas 3.0์ Python 3.11 ์ด์์ ํ์๋ก ์๊ตฌํ๋ฉฐ, Copy-on-Write ์๋งจํฑ์ค๋ฅผ ๊ธฐ๋ณธ ์ ์ฉํ๊ณ , ๋ฌธ์์ด ์ปฌ๋ผ์ PyArrow ๋ฐฑ์๋์ str dtype์ผ๋ก ์ถ๋ก ํฉ๋๋ค. ์ฒด์ธ ํ ๋น์ ๊ฒฝ๊ณ ๊ฐ ์๋ ์๋ฌ๋ฅผ ๋ฐ์์ํต๋๋ค.
Copy-on-Write: SettingWithCopyWarning์ ์ข ๋ง
Copy-on-Write(CoW)๋ pandas๊ฐ DataFrame ๊ฐ ๋ฉ๋ชจ๋ฆฌ ๊ณต์ ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๊ทผ๋ณธ์ ์ผ๋ก ๋ณ๊ฒฝํฉ๋๋ค. ๋ชจ๋ ์ธ๋ฑ์ฑ ์ฐ์ฐ์ ๋ณต์ฌ๋ณธ์ฒ๋ผ ๋์ํ์ง๋ง, ๋ด๋ถ์ ์ผ๋ก๋ ์ค์ ๋ณํ(mutation)์ด ๋ฐ์ํ ๋๊น์ง ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ณต์ ํฉ๋๋ค.
์ค์ง์ ์ธ ์ํฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค: SettingWithCopyWarning์ด ์์ ํ ์ฌ๋ผ์ก์ต๋๋ค. df[df['A'] > 0]['B'] = 1๊ณผ ๊ฐ์ ์ฒด์ธ ํ ๋น ํจํด์ ์ค๊ฐ ์ธ๋ฑ์ฑ ๊ฒฐ๊ณผ๊ฐ ๋ณต์ฌ๋ณธ์ด๋ฏ๋ก ChainedAssignmentError๋ฅผ ๋ฐ์์ํต๋๋ค.
# migration_cow.py
import pandas as pd
df = pd.DataFrame({"price": [100, 200, 300], "category": ["A", "B", "A"]})
# Pandas 2.x ํจํด (3.0์์๋ ChainedAssignmentError ๋ฐ์)
# df[df["category"] == "A"]["price"] = 150 # 3.0์์ ๋์ํ์ง ์์
# Pandas 3.0 ์ฌ๋ฐ๋ฅธ ํจํด: .loc[] ์ฌ์ฉ
df.loc[df["category"] == "A", "price"] = 150
# CoW ๋ฉ๋ชจ๋ฆฌ ๊ณต์ ๋์
df2 = df[["price"]] # df์ ๋ฉ๋ชจ๋ฆฌ ๊ณต์
df2["price"] = df2["price"] * 2 # ์ด ์์ ์์๋ง ๋ณต์ฌ ๋ฐ์
# df๋ ๋ณ๊ฒฝ๋์ง ์์ - ๋ถ์์ฉ ์์๋ชจ๋ ๋ฉ์๋์ copy ํค์๋ ์ธ์๋ ๋ ์ด์ ํจ๊ณผ๊ฐ ์์ผ๋ฉฐ, ๊ธฐ์กด ์ฝ๋์์ ์์ ํ๊ฒ ์ ๊ฑฐํ ์ ์์ต๋๋ค. inplace=True๋ฅผ ์ง์ํ๋ ๋ฉ์๋(replace(), fillna(), ffill(), bfill(), clip())๋ None ๋์ self๋ฅผ ๋ฐํํ์ฌ, ์ธํ๋ ์ด์ค ์ฐ์ฐ์์๋ ๋ฉ์๋ ์ฒด์ด๋์ด ๊ฐ๋ฅํด์ก์ต๋๋ค.
PyArrow ๋ฌธ์์ด ๋ฐฑ์๋: ๋ฌธ์์ด ์ฐ์ฐ 5~10๋ฐฐ ๊ณ ์ํ
Pandas 3.0์ ๋ฌธ์์ด ์ปฌ๋ผ์ Apache Arrow ๋ฐฑ์๋์ ์ ์ฉ str dtype์ผ๋ก ์ถ๋ก ํ๋ฉฐ, ๋ ๊ฑฐ์ object dtype์ ๋์ฒดํฉ๋๋ค. PyArrow๊ฐ ์ค์น๋์ด ์์ง ์์ ๊ฒฝ์ฐ NumPy ์ค๋ธ์ ํธ ๋ฐฐ์ด๋ก ํด๋ฐฑํฉ๋๋ค.
์ฑ๋ฅ ํฅ์์ ํ์ ํฉ๋๋ค: .str.contains(), .str.lower() ๋ฑ์ ๋ฌธ์์ด ๋ฉ์๋๊ฐ 5~10๋ฐฐ ๋น ๋ฅด๊ฒ ๋์ํฉ๋๋ค. ํ
์คํธ ๋ค๋ ์ปฌ๋ผ์ ๋ฉ๋ชจ๋ฆฌ ์๋น๋ ์ต๋ 50%๊น์ง ๊ฐ์ํฉ๋๋ค. Arrow์ ์ปฌ๋ผํ ํฌ๋งท์ ํตํด Polars, DuckDB ๋ฑ Arrow ๋ค์ดํฐ๋ธ ๋๊ตฌ์์ ์ ๋ก์นดํผ ๋ฐ์ดํฐ ๊ตํ๋ ๊ฐ๋ฅํด์ง๋๋ค.
# string_dtype_comparison.py
import pandas as pd
import numpy as np
# Pandas 3.0: ๋ฌธ์์ด ์ปฌ๋ผ์ด ์๋์ผ๋ก str[pyarrow]๋ก ์ค์
df = pd.DataFrame({"name": ["Alice", "Bob", "Charlie", None]})
print(df.dtypes)
# name string[pyarrow]
# dtype: object
# ๊ฒฐ์ธก๊ฐ์ NaN ์ฌ์ฉ (pd.NA๊ฐ ์๋), ๋ค๋ฅธ ๊ธฐ๋ณธ dtype๊ณผ ์ผ์น
print(df["name"].isna()) # None ํญ๋ชฉ์ ๋ํด True
# DuckDB์์ ์ง์ ์ํธ์ด์ฉ (์ ๋ก์นดํผ)
import duckdb
result = duckdb.sql("SELECT name FROM df WHERE name LIKE '%li%'").df()์ค์ํ ์ ์ฝ์ฌํญ์ผ๋ก, PyArrow ๋ฐฐ์ด์ ๋ถ๋ณ(immutable)์
๋๋ค. PyArrow ๋ฐฑ์๋ ์ปฌ๋ผ์ ์ฐ๊ธฐ ๊ฐ๋ฅํ NumPy ๋ฐฐ์ด๋ก ๋ณํํ๋ ค๋ฉด .to_numpy(copy=True)๋ฅผ ํตํ ๋ช
์์ ๋ณต์ฌ๊ฐ ํ์ํฉ๋๋ค.
๋ฌธ์์ด ๊ฐ์ง๋ฅผ ์ํด df['col'].dtype == object๋ฅผ ํ์ธํ๋ ์ฝ๋๋ ๋ ์ด์ ๋์ํ์ง ์์ต๋๋ค. pd.api.types.is_string_dtype(df['col']) ๋๋ pd.StringDtype() ํ์ธ์ผ๋ก ๋์ฒดํด์ผ ํฉ๋๋ค.
pd.col() ํํ์ ๋น๋
Pandas 3.0์ pd.col()์ DataFrame ์ปฌ๋ผ ์ฐธ์กฐ ๋ฐ ํํ์ ๊ตฌ์ถ์ ์ํ ์ ์ธ์ ๋ฐฉ๋ฒ์ผ๋ก ๋์
ํ์ต๋๋ค. ์ด ๊ตฌ๋ฌธ์ PySpark์ Polars์์ ์๊ฐ์ ๋ฐ์์ผ๋ฉฐ, ๋๋ค์ ์ค์ฝํ ๋ฐ ๋ถํฌ๋ช
์ฑ ๊ด๋ จ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
# col_expressions.py
import pandas as pd
df = pd.DataFrame({
"revenue": [1000, 2500, 800, 3200],
"cost": [400, 1200, 600, 1500],
"region": ["US", "EU", "US", "APAC"]
})
# ๊ธฐ์กด ๋ฐฉ์: ๋๋ค ๊ธฐ๋ฐ (๋ถํฌ๋ช
, ๋ฃจํ ๋ด ์ค์ฝํ ๋ฌธ์ )
df = df.assign(profit=lambda x: x["revenue"] - x["cost"])
# ์๋ก์ด ๋ฐฉ์: pd.col() (์ ์ธ์ , ์ธํธ๋ก์คํ์
๊ฐ๋ฅ)
df = df.assign(
profit=pd.col("revenue") - pd.col("cost"),
margin=((pd.col("revenue") - pd.col("cost")) / pd.col("revenue") * 100)
)
# pd.col()์ ์ฌ์ฉํ ํํฐ๋ง
high_margin = df.loc[pd.col("margin") > 50]๋๋ค ๋๋น ํต์ฌ ์ฅ์ ์ ๋ฃจํ ๋ด์์ ๋ํ๋ฉ๋๋ค. ๋๋ค ํด๋ก์ ๋ ๋ณ์๋ฅผ ์ฐธ์กฐ๋ก ์บก์ฒํ๋ฏ๋ก ์๋ชป๋ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํฉ๋๋ค:
# loop_scoping_fix.py
import pandas as pd
df = pd.DataFrame({"base": [10, 20, 30]})
# ๋๋ค ๋ฒ๊ทธ: ๋ชจ๋ ์ปฌ๋ผ์ด factor=30 ์ฌ์ฉ (๋ง์ง๋ง ๋ฃจํ ๊ฐ)
# cols = {}
# for factor in [2, 5, 10]:
# cols[f"x{factor}"] = lambda x: x["base"] * factor # ๋ฒ๊ทธ
# pd.col() ํด๊ฒฐ: ๊ฐ ํํ์์ด ์ฌ๋ฐ๋ฅธ ๊ฐ์ ์บก์ฒ
cols = {}
for factor in [2, 5, 10]:
cols[f"x{factor}"] = pd.col("base") * factor # ์ ํ
df = df.assign(**cols)pandas 3.0.2 ๊ธฐ์ค์ผ๋ก, pd.col()์ Series.case_when()์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. GroupBy ์ง๊ณ๋ ์์ง ์ง์๋์ง ์์ต๋๋ค.
Data Analytics ๋ฉด์ ์ค๋น๊ฐ ๋์ จ๋์?
์ธํฐ๋ํฐ๋ธ ์๋ฎฌ๋ ์ดํฐ, flashcards, ๊ธฐ์ ํ ์คํธ๋ก ์ฐ์ตํ์ธ์.
์ฃผ์ ๋ณ๊ฒฝ์ฌํญ: ์์ ํ ๋ง์ด๊ทธ๋ ์ด์ ์ฒดํฌ๋ฆฌ์คํธ
๋ค์ ํ๋ pandas 2.x์์ ๋ง์ด๊ทธ๋ ์ด์ ํ ๋ ๊ฐ์ฅ ๋น๋ฒํ๊ฒ ๋ฐ์ํ๋ ์ฃผ์ ๋ณ๊ฒฝ์ฌํญ์ ์ ๋ฆฌํ ๊ฒ์ ๋๋ค:
| ๋ณ๊ฒฝ์ฌํญ | Pandas 2.x ๋์ | Pandas 3.0 ๋์ | ์์ ๋ฐฉ๋ฒ |
|--------|---------------------|---------------------|-----|
| ์ฒด์ธ ํ ๋น | SettingWithCopyWarning | ChainedAssignmentError | .loc[] ์ฌ์ฉ |
| ๋ฌธ์์ด dtype | object | string[pyarrow] | dtype ํ์ธ ๋ก์ง ์
๋ฐ์ดํธ |
| copy= ํค์๋ | ๋ณต์ฌ๋ณธ ์์ฑ | ํจ๊ณผ ์์ (๋น๊ถ์ฅ) | ์ธ์ ์ ๊ฑฐ |
| groupby(observed=) | ๊ธฐ๋ณธ๊ฐ False | ๊ธฐ๋ณธ๊ฐ True | ๋ช
์์ ์ผ๋ก ์ค์ |
| Index.sort_values() | ์์น ์ธ์ ํ์ฉ | ํค์๋ ์ ์ฉ ์ธ์ | ๋ชจ๋ ์ธ์์ ์ด๋ฆ ์ง์ |
| offsets.Day | ๊ณ ์ 24์๊ฐ | ๋ฌ๋ ฅ์ผ (DST ์ธ์) | ํ์์กด ๋ก์ง ๊ฒํ |
| Categorical.map(na_action=) | ๊ธฐ๋ณธ๊ฐ None | ๊ธฐ๋ณธ๊ฐ ๋ณ๊ฒฝ | ๋ช
์์ ์ผ๋ก ์ค์ |
| str.contains(na=) | ๋นbool ํ์ฉ | bool ๋๋ None๋ง | na ๋งค๊ฐ๋ณ์ ์ ๋ฆฌ |
๊ถ์ฅ ์ ๊ทธ๋ ์ด๋ ๊ฒฝ๋ก๋ ๋จผ์ pandas 2.3์ผ๋ก ์ ๊ทธ๋ ์ด๋ํ์ฌ ๋ชจ๋ ๋น๊ถ์ฅ ๊ฒฝ๊ณ ๋ฅผ ํด๊ฒฐํ ํ, 3.0์ผ๋ก ์ดํํ๋ ๊ฒ์ ๋๋ค.
์๋ก์ด ๋น๊ถ์ฅ ์ ์ฑ : Pandas4Warning๊ณผ Pandas5Warning
Pandas 3.0์ ๊ตฌ์กฐํ๋ 3๋จ๊ณ ๋น๊ถ์ฅ ์ฌ์ดํด์ ๋์
ํ์ต๋๋ค. ๊ธฐ๋ฅ์ ๋จผ์ ํ์ค DeprecationWarning์ ๋ฐ์์ํค๊ณ , ๋ค์ ๋ฉ์ด์ ๋ฒ์ ์ง์ ์ ๋ง์ง๋ง ๋ง์ด๋ ๋ฆด๋ฆฌ์ค์์ FutureWarning์ผ๋ก ์ ํ๋๋ฉฐ, ์ต์ข
์ ์ผ๋ก ๋ฉ์ด์ ๋ฆด๋ฆฌ์ค์์ ์ ๊ฑฐ๋ฉ๋๋ค.
๋ ๊ฐ์ ์๋ก์ด ๊ฒฝ๊ณ ํด๋์ค๋ฅผ ํตํด ๋์ ๋ฒ์ ๋ณ ๊ฒฝ๊ณ ํํฐ๋ง์ด ์ฉ์ดํด์ก์ต๋๋ค:
# filter_warnings.py
import warnings
import pandas as pd
# pandas 4.0์ ์์ ๋ ๋ณ๊ฒฝ์ฌํญ๋ง ์บ์น
warnings.filterwarnings("error", category=pd.errors.Pandas4Warning)
# pandas 5.0์ ์์ ๋ ๋ณ๊ฒฝ์ฌํญ ์บ์น
warnings.filterwarnings("default", category=pd.errors.Pandas5Warning)์ด ์ ์ฑ ์ ํตํด ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์ง๋ณด์์๋ ์ฃผ์ ๋ณ๊ฒฝ์ฌํญ์ด ์ ์ฉ๋๊ธฐ ์ ์ต์ 2๊ฐ์ ๋ง์ด๋ ๋ฆด๋ฆฌ์ค ์ฌ์ดํด์ ์ ์๋ฅผ ํ๋ณดํฉ๋๋ค.
Pandas 3.0์ Python 3.11 ์ด์์ ํ์๋ก ์๊ตฌํฉ๋๋ค. Python 3.9 ๋๋ 3.10์ ์ฌ์ฉํ๋ ํ๋ก์ ํธ๋ pandas ๋ง์ด๊ทธ๋ ์ด์ ์ ์ Python ์ ๊ทธ๋ ์ด๋๊ฐ ํ์ํฉ๋๋ค.
๋ฉด์ ์ง๋ฌธ: Pandas 3.0 ์ฌ์ธต ๋ถ์
๋ค์ ์ง๋ฌธ๋ค์ 2026๋ ๋ฐ์ดํฐ ์์ง๋์ด๋ง ๋ฐ ๋ถ์ ๋ฉด์ ์์ ์ถ์ ๋๋ฉฐ, ์ด๋ก ์ ์ดํด์ ์ค์ ๋ง์ด๊ทธ๋ ์ด์ ๊ฒฝํ์ ๋ชจ๋ ํ๊ฐํฉ๋๋ค.
Q1: Pandas 3.0์ Copy-on-Write์ ๋ํด ์ค๋ช ํ์ญ์์ค. ์ ๋์ ๋์์ต๋๊น?
CoW๋ ์ธ๋ฑ์ฑ ์ฐ์ฐ์์ ๋ฐํ๋๋ ๋ชจ๋ DataFrame ๋๋ Series๊ฐ ๋
๋ฆฝ์ ์ธ ๋ณต์ฌ๋ณธ์ฒ๋ผ ๋์ํ๋๋ก ๋ณด์ฅํฉ๋๋ค. ๋ด๋ถ์ ์ผ๋ก๋ ์๋ณธ๊ณผ ๊ฒฐ๊ณผ ์ฌ์ด์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ณต์ ํ๋ค๊ฐ, ์ด๋ ํ์ชฝ์ด ๋ณํ๋๋ ์์ ์ ๋ฌผ๋ฆฌ์ ๋ณต์ฌ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ด๋ฅผ ํตํด SettingWithCopyWarning์ ์์ธ์ด์๋ ๋ทฐ์ ๋ณต์ฌ์ ๋ชจํธ์ฑ์ด ์ ๊ฑฐ๋๊ณ , ๋ถ์์ฉ์ผ๋ก ์ธํ ์ฐ๋ฐ์ ๋ฐ์ดํฐ ์์์ด ๋ฐฉ์ง๋๋ฉฐ, ์ฝ๊ธฐ ์ค์ฌ ์ํฌ๋ก๋์์๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๊ฐ์ํฉ๋๋ค.
Q2: Pandas 3.0์์ df[condition]['col'] = value๋ ์ด๋ป๊ฒ ๋ฉ๋๊น?
ChainedAssignmentError๊ฐ ๋ฐ์ํฉ๋๋ค. ์ค๊ฐ์ df[condition]์ CoW๋ก ์ธํด ํญ์ ๋ณต์ฌ๋ณธ์ด๋ฏ๋ก, ํด๋น ๋ณต์ฌ๋ณธ์ ์ปฌ๋ผ์ ํ ๋นํ๋ ๊ฒ์ ์๋ณธ DataFrame์ ์ ํ ์ํฅ์ ๋ฏธ์น์ง ์์ต๋๋ค. ์ฌ๋ฐ๋ฅธ ํจํด์ df.loc[condition, 'col'] = value์
๋๋ค.
Q3: ์๋ก์ด ๋ฌธ์์ด dtype์ ๋ค๋ฅธ ๋๊ตฌ์์ ์ํธ์ด์ฉ์ฑ์ ์ด๋ค ์ํฅ์ ๋ฏธ์นฉ๋๊น?
PyArrow ๊ธฐ๋ฐ ๋ฌธ์์ด dtype์ Apache Arrow์ ์ปฌ๋ผํ ํฌ๋งท์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ค๋ฅธ Arrow ๋ค์ดํฐ๋ธ ๋๊ตฌ(Polars, DuckDB, Spark via PyArrow)๋ก์ ์ ๋ก์นดํผ ๋ฐ์ดํฐ ์ ์ก์ด ์ง๋ ฌํ ์ค๋ฒํค๋ ์์ด ๊ฐ๋ฅํฉ๋๋ค. ๋ํ ๊ฐ๋ณ Python ๋ฌธ์์ด ๊ฐ์ฒด ๋์ ์ปดํฉํธํ ๋ฐ์ด๋๋ฆฌ ๋ฒํผ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก Python ์ค๋ธ์ ํธ ๋ฐฐ์ด ๋๋น ๋ฉ๋ชจ๋ฆฌ ํํ๋ฆฐํธ๋ ๊ฐ์ํฉ๋๋ค.
Q4: pd.col()์ ๋๋ค๋ก ํด๊ฒฐํ ์ ์๋ ์ด๋ค ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๊น?
pd.col()์ ํํ์ ์์ฑ ์์ ์ ์ปฌ๋ผ ์ฐธ์กฐ์ ๊ฐ์ ์บก์ฒํฉ๋๋ค(์คํ ์์ ์ด ์๋). Python์ ๋๋ค๋ ๋ณ์๋ฅผ ์ฐธ์กฐ๋ก ์บก์ฒํ๊ธฐ ๋๋ฌธ์, ๋ฃจํ ๋ด์์ ๋ชจ๋ ๋๋ค๊ฐ ๋ง์ง๋ง ๋ฃจํ ๋ณ์๋ฅผ ์ฐธ์กฐํ๋ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํฉ๋๋ค. ๋ํ pd.col() ํํ์์ ์ธํธ๋ก์คํ์
์ด ๊ฐ๋ฅํ์ฌ(pandas๊ฐ ์ต์ ํ ๊ฐ๋ฅ) ๋ถํฌ๋ช
ํ callable์ธ ๋๋ค์ ์ฐจ๋ณํ๋ฉ๋๋ค.
Q5: pandas 2.x์์ 3.0์ผ๋ก์ ์ฝ๋๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ์ ์ฐจ๋ฅผ ์ค๋ช ํ์ญ์์ค.
๋จ๊ณ 1: pandas 2.3์ผ๋ก ์
๊ทธ๋ ์ด๋ํ๊ณ ๋ชจ๋ ๋น๊ถ์ฅ ๊ฒฝ๊ณ ๋ฅผ ์์ ํฉ๋๋ค. ๋จ๊ณ 2: pd.options.mode.copy_on_write = True๋ก CoW ์ตํธ์ธ์ ํ์ฑํํ๊ณ (2.0๋ถํฐ ์ฌ์ฉ ๊ฐ๋ฅ) ์ฒด์ธ ํ ๋น ํจํด์ ์์ ํฉ๋๋ค. ๋จ๊ณ 3: PyArrow๋ฅผ ์ค์นํ๊ณ ๋ฌธ์์ด dtype ์ถ๋ก ์ด ๋ค์ด์คํธ๋ฆผ ๋ก์ง์ ๊นจ๋จ๋ฆฌ์ง ์๋์ง ํ
์คํธํฉ๋๋ค(ํนํ dtype == object ํ์ธ). ๋จ๊ณ 4: 3.0์ผ๋ก ์
๊ทธ๋ ์ด๋ํ๊ณ ์ ์ฒด ํ
์คํธ ์ค์ํธ๋ฅผ ์คํํฉ๋๋ค. ๋จ๊ณ 5: ๋ถํ์ํ copy= ์ธ์๋ฅผ ์ ๊ฑฐํ๊ณ groupby(observed=) ํธ์ถ์ ์
๋ฐ์ดํธํฉ๋๋ค.
์ฑ๋ฅ ๋ฒค์น๋งํฌ: Before and After
CoW์ PyArrow ๋ฌธ์์ด์ ๋ณตํฉ ํจ๊ณผ๋ ์ค์ ์ํฌ๋ก๋์์ ์ธก์ ๊ฐ๋ฅํ ๊ฐ์ ์ ์ ๊ณตํฉ๋๋ค:
# benchmark_example.py
import pandas as pd
import numpy as np
# 100๋ง ํ์ ํผํฉ ๋ฐ์ดํฐ DataFrame ์์ฑ
rng = np.random.default_rng(42)
df = pd.DataFrame({
"user_id": rng.integers(0, 100_000, size=1_000_000),
"event": rng.choice(["click", "view", "purchase", "scroll"], size=1_000_000),
"value": rng.exponential(50, size=1_000_000)
})
# ๋ฌธ์์ด ํํฐ๋ง: PyArrow ๋ฐฑ์๋๋ก ์ฝ 6๋ฐฐ ๊ณ ์ํ
clicks = df.loc[pd.col("event").str.contains("click")]
# ๋ฉ๋ชจ๋ฆฌ: ๋ฌธ์์ด ์ปฌ๋ผ RAM ์ฌ์ฉ๋ ์ฝ 50% ๊ฐ์
print(df["event"].memory_usage(deep=True)) # object dtype ~16MB ๋๋น ~8MB
# ์๋ธ์
: CoW๋ก ๋ณํ ์๊น์ง ๋ณต์ฌ ํํผ
subset = df[["user_id", "value"]] # ์ ๋ก์นดํผ (๋ฉ๋ชจ๋ฆฌ ๊ณต์ )
subset["value"] = subset["value"].clip(upper=500) # ์ด ์์ ์์๋ง ๋ณต์ฌ ๋ฐ์ํ ์คํธ ๋ค๋์ CSV๋ฅผ ์ฒ๋ฆฌํ๋ ํ๋ก๋์ ETL ํ์ดํ๋ผ์ธ์์, PyArrow ๋ฌธ์์ด ๋ฐฑ์๋ ๋จ๋ ์ผ๋ก ํผํฌ ๋ฉ๋ชจ๋ฆฌ๋ฅผ 30~40% ๊ฐ์์ํค๊ณ , ๋ฌธ์์ด ์ง์ค ๋ณํ์ ์ด ์คํ ์๊ฐ์ 20~30% ๋จ์ถํฉ๋๋ค.
์ค์ ๋ง์ด๊ทธ๋ ์ด์ : ์ค์ ํจํด ์์
์ผ๋ฐ์ ์ธ pandas 2.x ์ฝ๋๋ฒ ์ด์ค์ ํ์ํ ๊ตฌ์ฒด์ ์ธ ๋ฆฌํฉํฐ๋ง์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
# migration_patterns.py
import pandas as pd
# ํจํด 1: ์ฒด์ธ ํ ๋น ๋์ฒด
# ๋ณ๊ฒฝ ์ (pandas 2.x)
# df[df["status"] == "active"]["score"] = 100
# ๋ณ๊ฒฝ ํ (pandas 3.0)
df.loc[df["status"] == "active", "score"] = 100
# ํจํด 2: copy= ์ธ์ ์ ๊ฑฐ
# ๋ณ๊ฒฝ ์
# subset = df[["a", "b"]].copy() # CoW์์๋ ๋ถํ์
# ๋ณ๊ฒฝ ํ
subset = df[["a", "b"]] # CoW๊ฐ ์๋์ผ๋ก ๊ฒฉ๋ฆฌ ์ฒ๋ฆฌ
# ํจํด 3: ๋ฌธ์์ด dtype ํ์ธ ์
๋ฐ์ดํธ
# ๋ณ๊ฒฝ ์
# if df["name"].dtype == object:
# ๋ณ๊ฒฝ ํ
if pd.api.types.is_string_dtype(df["name"]):
pass
# ํจํด 4: groupby์์ observed= ๋ช
์
# ๋ณ๊ฒฝ ์ (๊ธฐ๋ณธ observed=False์ ์์กด)
# df.groupby("category")["value"].sum()
# ๋ณ๊ฒฝ ํ (๋ช
ํ์ฑ์ ์ํด ๋ช
์)
df.groupby("category", observed=True)["value"].sum()
# ํจํด 5: ํค์๋ ์ ์ฉ Index.sort_values()
# ๋ณ๊ฒฝ ์
# idx.sort_values(True, "first")
# ๋ณ๊ฒฝ ํ
idx.sort_values(ascending=True, na_position="first")๊ธฐ์ด์ ์ธ pandas ๋ฐ Python ๋ฐ์ดํฐ ๋ถ์ ๊ธฐ์ ์ ๋ํ ๋ฉด์ ๋ฌธ์ ๋ชจ๋์์ ์ด๋ฌํ ํจํด์ ์ฌ๋ ์๊ฒ ๋ค๋ฃจ๊ณ ์์ต๋๋ค. SQL ์๋์ฐ ํจ์ ๋ชจ๋์ SQL/Python ํ์ด๋ธ๋ฆฌ๋ ๋ถ์ ์ง๋ฌด๋ฅผ ์ํ pandas ์ง์์ ๋ณด์ํฉ๋๋ค.
์ฐ์ต์ ์์ํ์ธ์!
๋ฉด์ ์๋ฎฌ๋ ์ดํฐ์ ๊ธฐ์ ํ ์คํธ๋ก ์ง์์ ํ ์คํธํ์ธ์.
๊ฒฐ๋ก
- Copy-on-Write๋
SettingWithCopyWarning์ ์์ ํ ์ ๊ฑฐํ๊ณ ๊ณต์ ์ฐธ์กฐ๋ฅผ ํตํ ์ฐ๋ฐ์ ๋ฐ์ดํฐ ๋ณํ์ ๋ฐฉ์งํฉ๋๋ค - PyArrow ๋ฌธ์์ด ๋ฐฑ์๋๋ 5~10๋ฐฐ ๋น ๋ฅธ ๋ฌธ์์ด ์ฐ์ฐ๊ณผ ํ ์คํธ ์ปฌ๋ผ์ 50% ๋ฉ๋ชจ๋ฆฌ ๊ฐ์๋ฅผ ์ ๊ณตํฉ๋๋ค
pd.col()์ ์ค๋ฅ๋ฅผ ์ ๋ฐํ๊ธฐ ์ฌ์ด ๋๋ค ํจํด์ ์ ์ธ์ ์ด๊ณ ์ธํธ๋ก์คํ์ ๊ฐ๋ฅํ ํํ์์ผ๋ก ๋์ฒดํฉ๋๋ค- ์ฒด์ธ ํ ๋น(
df[cond]['col'] = val)์ ์ด์ ํ๋ ์๋ฌ์ด๋ฉฐ.loc[]๋ก์ ๋ง์ด๊ทธ๋ ์ด์ ์ด ํ์์ ๋๋ค - ๊ตฌ์กฐํ๋ ๋น๊ถ์ฅ ์ ์ฑ
(
Pandas4Warning,Pandas5Warning)์ด ๋ช ํํ ์ ๊ทธ๋ ์ด๋ ํ์๋ผ์ธ์ ์ ๊ณตํฉ๋๋ค - ์ ๊ทธ๋ ์ด๋ ๊ฒฝ๋ก: ๋จผ์ pandas 2.3(๊ฒฝ๊ณ ์์ ), ๊ทธ ๋ค์ 3.0(PyArrow ์ค์น ๋ฐ Python 3.11 ์ด์)
- ๋ฉด์ ์ค๋น ์ CoW ๋ฉ์ปค๋์ฆ, PyArrow ์ํธ์ด์ฉ์ฑ, ์ค์ ๋ง์ด๊ทธ๋ ์ด์ ํจํด์ ์ค์ ์ ๋์ด์ผ ํฉ๋๋ค
์ฐ์ต์ ์์ํ์ธ์!
๋ฉด์ ์๋ฎฌ๋ ์ดํฐ์ ๊ธฐ์ ํ ์คํธ๋ก ์ง์์ ํ ์คํธํ์ธ์.
ํ๊ทธ
๊ณต์
๊ด๋ จ ๊ธฐ์ฌ

2026๋ ๋ฐ์ดํฐ ์ ๋๋ฆฌํฑ์ค ๋ฉด์ ์ง๋ฌธ TOP 25
2026๋ ๋ฐ์ดํฐ ์ ๋๋ฆฌํฑ์ค ๋ฉด์ ๋๋น ๊ฐ์ด๋์ ๋๋ค. SQL, Python, Power BI, ํต๊ณ, ํ๋ ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ 25๊ฐ ์ง๋ฌธ์ ์ฝ๋ ์์์ ํจ๊ป ์์ธํ ํด์คํฉ๋๋ค.

Python ๋ฐ์ดํฐ ๋ถ์: Matplotlib๊ณผ Seaborn์ ํ์ฉํ ๋ฐ์ดํฐ ์๊ฐํ ๋ฉด์ ๊ฐ์ด๋
Matplotlib๊ณผ Seaborn์ ํ์ฉํ Python ๋ฐ์ดํฐ ์๊ฐํ ์ค์ ๊ฐ์ด๋์ ๋๋ค. ์ฐจํธ ์์ฑ, ์คํ์ผ๋ง, ์๋ธํ๋กฏ, ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ ์ง๋ฌธ๊น์ง ์์ธํ๊ฒ ๋ค๋ฃน๋๋ค.

2026๋ ๋ฐ์ดํฐ ๋ถ์๊ฐ๋ฅผ ์ํ dbt ์๋ฒฝ ๊ฐ์ด๋: ๋ชจ๋ธ๋ง, ํ ์คํธ, ๋ฉด์ ์ง๋ฌธ
dbt ํ๋ก์ ํธ ๊ตฌ์กฐ, ๋จธํฐ๋ฆฌ์ผ๋ผ์ด์ ์ด์ ์ ๋ต, ๋ฐ์ดํฐ ํ์ง ํ ์คํธ, Jinja ๋งคํฌ๋ก, ๊ทธ๋ฆฌ๊ณ ์ค๋ฌด ๋ฉด์ ์์ ์์ฃผ ๋ฑ์ฅํ๋ ์ง๋ฌธ๊น์ง ํฌ๊ด์ ์ผ๋ก ๋ค๋ฃน๋๋ค.