Pandas 3.0 ์™„๋ฒฝ ๊ฐ€์ด๋“œ(2026): ์ƒˆ๋กœ์šด API, ์ฃผ์š” ๋ณ€๊ฒฝ์‚ฌํ•ญ, ๋ฉด์ ‘ ์งˆ๋ฌธ ์ด์ •๋ฆฌ

Pandas 3.0์˜ Copy-on-Write, PyArrow ๋ฌธ์ž์—ด ๋ฐฑ์—”๋“œ, pd.col() ํ‘œํ˜„์‹ ๋นŒ๋” ๋“ฑ ํ•ต์‹ฌ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ƒ์„ธํžˆ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ๋ถ„์„ ์—”์ง€๋‹ˆ์–ด ๋ฉด์ ‘์—์„œ ์ถœ์ œ๋˜๋Š” ํ•ต์‹ฌ ์งˆ๋ฌธ๋„ ํ•จ๊ป˜ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

Pandas 3.0 new APIs and breaking changes guide

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๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

python
# 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 ๋„ค์ดํ‹ฐ๋ธŒ ๋„๊ตฌ์™€์˜ ์ œ๋กœ์นดํ”ผ ๋ฐ์ดํ„ฐ ๊ตํ™˜๋„ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

python
# 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์—์„œ ์˜๊ฐ์„ ๋ฐ›์•˜์œผ๋ฉฐ, ๋žŒ๋‹ค์˜ ์Šค์ฝ”ํ•‘ ๋ฐ ๋ถˆํˆฌ๋ช…์„ฑ ๊ด€๋ จ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

python
# 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]

๋žŒ๋‹ค ๋Œ€๋น„ ํ•ต์‹ฌ ์žฅ์ ์€ ๋ฃจํ”„ ๋‚ด์—์„œ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ๋žŒ๋‹ค ํด๋กœ์ €๋Š” ๋ณ€์ˆ˜๋ฅผ ์ฐธ์กฐ๋กœ ์บก์ฒ˜ํ•˜๋ฏ€๋กœ ์ž˜๋ชป๋œ ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

python
# 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์œผ๋กœ ์ „ํ™˜๋˜๋ฉฐ, ์ตœ์ข…์ ์œผ๋กœ ๋ฉ”์ด์ € ๋ฆด๋ฆฌ์Šค์—์„œ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

๋‘ ๊ฐœ์˜ ์ƒˆ๋กœ์šด ๊ฒฝ๊ณ  ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ๋Œ€์ƒ ๋ฒ„์ „๋ณ„ ๊ฒฝ๊ณ  ํ•„ํ„ฐ๋ง์ด ์šฉ์ดํ•ด์กŒ์Šต๋‹ˆ๋‹ค:

python
# 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๊ฐœ์˜ ๋งˆ์ด๋„ˆ ๋ฆด๋ฆฌ์Šค ์‚ฌ์ดํด์˜ ์œ ์˜ˆ๋ฅผ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค.

Python ๋ฒ„์ „ ์š”๊ตฌ์‚ฌํ•ญ

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 ๋ฌธ์ž์—ด์˜ ๋ณตํ•ฉ ํšจ๊ณผ๋Š” ์‹ค์ œ ์›Œํฌ๋กœ๋“œ์—์„œ ์ธก์ • ๊ฐ€๋Šฅํ•œ ๊ฐœ์„ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

python
# 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 ์ฝ”๋“œ๋ฒ ์ด์Šค์— ํ•„์š”ํ•œ ๊ตฌ์ฒด์ ์ธ ๋ฆฌํŒฉํ„ฐ๋ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

python
# 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 ์ƒํ˜ธ์šด์šฉ์„ฑ, ์‹ค์ „ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒจํ„ด์— ์ค‘์ ์„ ๋‘์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

ํƒœ๊ทธ

#pandas
#data-analytics
#python
#interview

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

๋ฐ์ดํ„ฐ ์• ๋„๋ฆฌํ‹ฑ์Šค ๋ฉด์ ‘ ์งˆ๋ฌธ 2026

2026๋…„ ๋ฐ์ดํ„ฐ ์• ๋„๋ฆฌํ‹ฑ์Šค ๋ฉด์ ‘ ์งˆ๋ฌธ TOP 25

2026๋…„ ๋ฐ์ดํ„ฐ ์• ๋„๋ฆฌํ‹ฑ์Šค ๋ฉด์ ‘ ๋Œ€๋น„ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. SQL, Python, Power BI, ํ†ต๊ณ„, ํ–‰๋™ ๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” 25๊ฐœ ์งˆ๋ฌธ์„ ์ฝ”๋“œ ์˜ˆ์‹œ์™€ ํ•จ๊ป˜ ์ƒ์„ธํžˆ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.

Python Matplotlib๊ณผ Seaborn์„ ํ™œ์šฉํ•œ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ํŠœํ† ๋ฆฌ์–ผ

Python ๋ฐ์ดํ„ฐ ๋ถ„์„: Matplotlib๊ณผ Seaborn์„ ํ™œ์šฉํ•œ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ๋ฉด์ ‘ ๊ฐ€์ด๋“œ

Matplotlib๊ณผ Seaborn์„ ํ™œ์šฉํ•œ Python ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ์‹ค์ „ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. ์ฐจํŠธ ์ž‘์„ฑ, ์Šคํƒ€์ผ๋ง, ์„œ๋ธŒํ”Œ๋กฏ, ๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” ์งˆ๋ฌธ๊นŒ์ง€ ์ƒ์„ธํ•˜๊ฒŒ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

dbt data build tool for data analysts modeling testing

2026๋…„ ๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€๋ฅผ ์œ„ํ•œ dbt ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ๋ชจ๋ธ๋ง, ํ…Œ์ŠคํŠธ, ๋ฉด์ ‘ ์งˆ๋ฌธ

dbt ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ, ๋จธํ‹ฐ๋ฆฌ์–ผ๋ผ์ด์ œ์ด์…˜ ์ „๋žต, ๋ฐ์ดํ„ฐ ํ’ˆ์งˆ ํ…Œ์ŠคํŠธ, Jinja ๋งคํฌ๋กœ, ๊ทธ๋ฆฌ๊ณ  ์‹ค๋ฌด ๋ฉด์ ‘์—์„œ ์ž์ฃผ ๋“ฑ์žฅํ•˜๋Š” ์งˆ๋ฌธ๊นŒ์ง€ ํฌ๊ด„์ ์œผ๋กœ ๋‹ค๋ฃน๋‹ˆ๋‹ค.