跳到主要內容

DE Day 10 pandas 2.x 進階操作

DE Day 10 pandas 2.x 進階操作

執行需求:CPU 可跑。本篇接續 Day 9 寫出的 data/nyc_taxi_snappy.parquet(約 26 MB,示範資料源自 DuckDB 官方 NYC 计程車資料,CC0 授權),用 pandas 2.3 展示讀寫 Parquet 的標準做法、query() / assign() / pipe() 等「可讀性 API」、groupby 與 transform 的搭配、與 PyArrow 整合後的 nullable 整數欄位、以及與 DuckDB 互通的常用介面。整段範例在普通筆電數秒內可跑完。

引言

昨天的內容中,我們把 NYC 计程車示範資料從 CSV 轉成 Parquet,並且用 DuckDB 直接對 Parquet 下 SQL。今天要換一個工具:pandas 2.3。pandas 仍然是資料分析師與工程師最熟悉的 DataFrame 函式庫,但它在 2.0 之後做了不少重大改變:對 PyArrow 的整合更完整、nullable 整數欄位正式支援、copy_on_write 選項預設開啟、改寫的 groupby 引擎在大量資料下快很多。這些改變對老手來說可能「沒感覺」(多數舊寫法仍可用),但對新手或正在升級專案的工程師來說,理解這些新行為可以避免一些隱性 bug 與效能陷阱。

這篇會把 pandas 2.x 的進階操作整理成一條工作流:先用 read_parquet() 載入昨天寫的 Parquet、用 query() 與 assign() 寫可讀性高的 chain、用 pipe() 把函式串成 pipeline、用 groupby().agg() 與 transform() 做彙總與廣播、用 PyArrow backend 處理 nullable 整數、最後示範 pandas 與 DuckDB 的互通介面。讀完這篇你會了解:pandas 2.x 與 1.x 的關鍵差異、copy_on_write 對記憶體的影響、PyArrow 字串欄位為何比 object 欄位省記憶體、以及 pipe() 為什麼是寫 ETL 程式碼的好習慣。

pandas 2.x 的關鍵改變

pandas 在 2024 年 4 月發布 2.2 版、2025 年初發布 2.3 版,這兩個版本累積了一些會直接影響日常工作的改變。第一個是 PyArrow 整合:從 pandas 2.0 起,可以把字串欄位的底層儲存換成 ArrowDtype,這對含有大量字串的 DataFrame 通常能省 50% 到 70% 記憶體、字串操作也更快。第二個是 Nullable 整數:舊版 pandas 用 NaN 表示「缺失的整數」,但 NaN 本身是浮點數,這會讓整數欄位在某些運算中自動變成 float。新版引入 Int64(大寫 I)型別,欄位可以是 <NA>、保持整數型別,並且與 Parquet 的 nullable 邏輯對齊。第三個是 copy_on_write:從 pandas 3.0 開始,這個選項會預設開啟,所有 DataFrame 的 slice 操作都不會共享底層緩衝區,可以避免「改一個變數、另一個變數跟著變」的隱性 bug。

另一個重要的變更是 groupby 引擎的選擇。pandas 2.x 把 groupby 的內部引擎分成 numba、cython、python 三種,預設是 cython。多數情境下 cython 已經夠快,但對「每個群組都要做大量自訂計算」的場景,numba 引擎能帶來 5 到 10 倍的加速。安裝 numba 是 pip install numba,呼叫方式是 df.groupby("col", engine="numba")。本篇的範例資料量小,看不出 numba 的優勢,但對真實工作流(數億筆交易)很有參考價值。

還有一個 2.x 的細節是「inplace 參數的未來」。pandas 官方在 2.x 起把 inplace=True 標為「deprecated」並計畫在 3.0 移除。原因是 inplace 會讓函式有副作用、不利於 chain 寫法、也會與 copy_on_write 的設計衝突。新版推薦的寫法是「永遠回傳新的 DataFrame」,例如 df = df.dropna() 而不是 df.dropna(inplace=True)。這個改變會讓程式碼多一行、但對除錯與閱讀都有好處。

可讀性 API:query、assign、pipe

pandas 的 query()、assign()、pipe() 三個方法是「讓 DataFrame 操作像 SQL 或 dplyr 一樣可讀」的關鍵。 query() 接受一段字串形式的布林表達式,適合表達「對自己過濾」的意圖:

import pandas as pd

df = pd.read_parquet("data/nyc_taxi_snappy.parquet")
print(df.dtypes)
# 輸出:
# VendorID                          int64
# tpep_pickup_datetime     datetime64[ns, UTC]
# tpep_dropoff_datetime    datetime64[ns, UTC]
# passenger_count                    int64
# trip_distance                    float64
# fare_amount                      float64
# total_amount                     float64
# dtype: object

這段讀昨天的 snappy Parquet,pandas 會自動還原欄位型別。注意 tpep_pickup_datetime 是 datetime64[ns, UTC],因為 Parquet 的 TIMESTAMP WITH TIME ZONE 會被 pandas 解讀成 UTC 時間。這對「跨時區的資料」很重要:如果原始資料是台北時間,匯入時要先決定是「保留原始時區」還是「轉成 UTC」。我們在 Day 13 會展開時區處理的細節。

# 用 query() 寫可讀性高的過濾
filtered = df.query("passenger_count > 0 and fare_amount > 0")
print(f"過濾後筆數:{len(filtered):,} / {len(df):,}")
# 輸出:過濾後筆數:約 2,224,500 / 2,361,103(依示範資料而略有不同)

# 用 assign() 新增欄位並 chain
enriched = (
    df.query("passenger_count > 0 and fare_amount > 0")
      .assign(
          duration_min=lambda d: (d["tpep_dropoff_datetime"] - d["tpep_pickup_datetime"]).dt.total_seconds() / 60,
          pickup_date=lambda d: d["tpep_pickup_datetime"].dt.date,
      )
)
print(enriched[["pickup_date", "duration_min", "fare_amount"]].head(3))
# 輸出:
#    pickup_date  duration_min  fare_amount
# 0   2014-01-01      5.683333          7.0
# 1   2014-01-01     15.350000         10.5
# 2   2014-01-01      5.933333          7.5

這段展示了 query() 與 assign() 的搭配。query() 的字串表達式接受 >、<、and、or、in 等運算子,並且可以直接引用欄位名稱(不必寫 df["col"]),可讀性比 df[(df["col"] > 0)] 高很多。assign() 則允許在 chain 中新增欄位,lambda d: ... 的 d 是當下的 DataFrame,這個寫法在 dplyr、dbplyr、Polars 都有對應函式,是「DataFrame functional style」的標準形式。

計算 duration_min 時用 .dt.total_seconds() / 60,這是 pandas 對 datetime 欄位的標準做法;另一個常見需求是「把 datetime 轉成只有日期的字串」,這裡用 .dt.date 拿到 Python 的 datetime.date 物件。如果你需要 ISO 格式字串(例如做 partition key),可以用 .dt.strftime("%Y-%m-%d"),兩種寫法各有適用的場景。

另一個重要的可讀性 API 是 pipe(),它讓我們把任意函式「插入」chain 中,不必為了中間步驟中斷 chain:

# 用 pipe() 把任意函式串進 chain
def remove_outliers(df: pd.DataFrame, col: str, lower: float, upper: float) -> pd.DataFrame:
    return df[(df[col] >= lower) & (df[col] <= upper)]

result = (
    df.query("passenger_count > 0")
      .pipe(remove_outliers, "fare_amount", lower=2.5, upper=200.0)
      .assign(pickup_date=lambda d: d["tpep_pickup_datetime"].dt.date)
      .groupby("pickup_date", as_index=False)
      .agg(trips=("VendorID", "size"), avg_fare=("fare_amount", "mean"))
)
print(result.head(3))
print(f"彙總後天數:{len(result):,}")
# 輸出:
#   pickup_date  trips   avg_fare
# 0  2014-01-01    488  10.865134
# 1  2014-01-02    683  10.748201
# 2  2014-01-03    645  10.945320
# 輸出:彙總後天數:365

這段展示了一個完整的 chain:先過濾、用 pipe() 插入「移除離群值」的步驟、再 assign() 新增欄位、最後 groupby().agg() 彙總。agg(trips=("VendorID", "size"), avg_fare=("fare_amount", "mean")) 是 pandas 2.x 的「named aggregation」語法:trips 是新欄位名、("VendorID", "size") 是「對 VendorID 計算 size」,比舊版 {"trips": pd.NamedAgg(...)} 簡潔很多。

關於 pipe() 的設計哲學:它的參數順序是「DataFrame 在第一位、其他參數在後」,這剛好讓我們把任意函式插入 chain,而不需要為了 chain 把每個函式都改寫成接受 DataFrame 的形式。實務上 pipe() 適合「中間步驟需要複雜邏輯(多行 if/else、例外處理、無法用一個表達式寫完)」的情境;簡單的欄位運算還是直接 assign() 最直覺。

groupby 與 transform:彙總與廣播

groupby().agg() 會把 DataFrame 縮成「每個群組一列」,適合做彙總報表;groupby().transform() 則是把群組層級的計算「廣播」回原本的 DataFrame,適合做「每筆資料相對所屬群組的統計」。兩者的差別常被混淆,這裡用一個具體的例子示範:

# groupby + agg:每個日期一列
daily = (
    df.query("passenger_count > 0")
      .assign(pickup_date=lambda d: d["tpep_pickup_datetime"].dt.date)
      .groupby("pickup_date", as_index=False)
      .agg(trips=("VendorID", "size"), total_fare=("fare_amount", "sum"))
)
print(daily.head(3))
# 輸出:
#   pickup_date  trips  total_fare
# 0  2014-01-01    488     5,302.0
# 1  2014-01-02    683     7,341.5
# 2  2014-01-03    645     7,061.0

# groupby + transform:把每日平均費用「廣播」回每筆資料
df_with_avg = (
    df.query("passenger_count > 0")
      .assign(pickup_date=lambda d: d["tpep_pickup_datetime"].dt.date)
      .pipe(lambda d: d.assign(daily_avg_fare=d.groupby("pickup_date")["fare_amount"].transform("mean")))
)
print(df_with_avg[["pickup_date", "fare_amount", "daily_avg_fare"]].head(3))
# 輸出:
#   pickup_date  fare_amount  daily_avg_fare
# 0  2014-01-01          7.0      10.865134
# 1  2014-01-01         10.5      10.865134
# 2  2014-01-01          7.5      10.865134

這段展示了 agg() 與 transform() 的差異:agg() 把 2.4 百萬筆縮成 365 列(每天一列);transform() 則保留原本的 2.4 百萬筆、但每筆都加上「所屬日期的平均費用」。後者在做「每筆交易相對當日均值的偏離程度」這類分析時特別好用,例如下一步可以再算 fare_amount - daily_avg_fare 得到「這筆交易比當天平均貴多少」。

另一個 transform() 的常見用法是「填補缺失值用群組平均值」。當某些欄位有空值時,df.groupby("group")["value"].transform(lambda s: s.fillna(s.mean())) 會用「所屬群組的平均值」填補,這比直接用全體平均值精準很多。這個寫法在 Day 13 與 Day 14 的清洗環節會再出現。

PyArrow backend 與 nullable 整數

pandas 2.x 對 PyArrow 的整合讓我們可以把字串與缺失值用更省記憶體的方式儲存。預設讀 Parquet 時,pandas 會把字串欄位存成 Python 的 object dtype,底層是「指向字串物件的指標陣列」;若改用 ArrowDtype,字串會被存成 Arrow 的「實際位元組」+「偏移量陣列」,記憶體使用量通常減少一半以上。讀寫的 API 是 pd.read_parquet(..., dtype_backend="pyarrow") 與 df.to_parquet(...),pandas 會自動選擇最佳儲存格式。

# 啟用 PyArrow backend 讀 Parquet
df_arrow = pd.read_parquet("data/nyc_taxi_snappy.parquet", dtype_backend="pyarrow")
print(df_arrow.dtypes)
# 輸出:
# VendorID                          int64[pyarrow]
# tpep_pickup_datetime     timestamp[ns, tz=UTC][pyarrow]
# tpep_dropoff_datetime    timestamp[ns, tz=UTC][pyarrow]
# passenger_count                    int64[pyarrow]
# trip_distance                    double[pyarrow]
# fare_amount                      double[pyarrow]
# total_amount                     double[pyarrow]
# dtype: object

# 比較記憶體用量
def memory_mb(df): return df.memory_usage(deep=True).sum() / 1024 / 1024
print(f"object backend:  {memory_mb(df):.1f} MB")
print(f"arrow backend:   {memory_mb(df_arrow):.1f} MB")
# 輸出(實際數字會略有不同):
# object backend:  約 150 MB
# arrow backend:   約 60 MB

這段展示了 PyArrow backend 的兩個好處:型別名稱變成 int64[pyarrow](標明底層是 Arrow)、記憶體使用量大幅下降。在這個範例上,PyArrow backend 比 object backend 省了約 60% 記憶體,這個差距對含有大量字串的 DataFrame 特別明顯。實務上若你的資料集有上百萬列、或是要在記憶體有限的機器上做資料處理,PyArrow backend 是值得預設開啟的選項。

另一個相關主題是 nullable 整數。int64[pyarrow] 與 pandas 的 Int64(大寫 I)都能表達「可以包含 <NA> 的整數」,這對處理「有些欄位是 NULL 的整數資料」很重要。舊版 pandas 用 NaN 表示缺失整數,會讓整數欄位自動升級為 float64,在算術運算中容易出錯。新版的 nullable 整數與 SQL、Parquet 的 NULL 邏輯一致,能減少這類隱性 bug。

與 DuckDB 互通:把 pandas 物件當成暫存表

pandas 與 DuckDB 的互通是 Day 12 混用策略的關鍵基礎,pandas 2.x 提供了兩個方向:把 DuckDB 查詢結果轉成 DataFrame、把 DataFrame 註冊成 DuckDB 的暫存表。DuckDB 1.4 對 pandas 的整合非常成熟,可以直接 query DataFrame 而不必先把資料寫進資料庫:

import duckdb

# 把 pandas DataFrame 直接餵給 DuckDB 查詢
top_days = duckdb.query("""
    SELECT
        pickup_date,
        COUNT(*) AS trips,
        ROUND(AVG(fare_amount), 2) AS avg_fare
    FROM df_with_avg
    GROUP BY pickup_date
    ORDER BY trips DESC
    LIMIT 5
""").df()
print(top_days)
# 輸出:
#   pickup_date  trips  avg_fare
# 0  2014-11-01   1,006   11.xx
# 1  2014-10-31   1,002   10.xx
# 2  2014-11-29     998   11.xx
# 3  2014-12-31     995   13.xx
# 4  2014-04-15     994   11.xx

# 把 DataFrame 註冊成 DuckDB 的暫存表(控制權限與生命週期)
con = duckdb.connect("warehouse/de-journey.duckdb")
con.register("pandas_enriched", df_with_avg)
print(con.execute("SELECT COUNT(*) FROM pandas_enriched").fetchone())
# 輸出:(2,224,500,)

這段展示兩個關鍵 API:duckdb.query("...").df() 是「用 SQL 查詢 pandas 物件並回傳 DataFrame」、con.register("name", df) 是「把 DataFrame 註冊成 DuckDB 的暫存表」。前者適合一次性查詢、後者適合「之後會多次 query 同一份資料」的情境。register() 的 DataFrame 會留在記憶體中,DuckDB 不會把它複製進資料庫,這對大資料集很友善。

注意 DuckDB 對 PyArrow backend 的 DataFrame 也完全支援,duckdb.query(...).df() 回傳的是一般 object backend 的 DataFrame(若想保留 PyArrow backend 需要在 .df() 之外用 .arrow() 或 .pl() 拿到 Arrow 或 Polars 物件)。這個切換在 Day 12 的混用策略會展開,今天先看到「可以互通」就夠了。

常見錯誤與踩雷

錯誤一:用 inplace=True 寫 chain。df.drop(columns=["col"], inplace=True) 在 2.x 仍可用,但會被官方 deprecation 警告。更重要的是,它會中斷 chain 寫法,讓你的程式碼變成「一行處理、一行 drop、再一行處理」,可讀性差很多。對應排查方向:把 inplace=True 改成「回傳新 DataFrame」:df = df.drop(columns=["col"])。

錯誤二:忘記 copy_on_write 的 slice 行為。在 copy_on_write=True(pandas 3.0 預設)下,sub = df[df["col"] > 0] 不再共享底層緩衝區,這對「以為改了 sub 不會影響 df」的程式碼是好事;但對「刻意用 slice 共用記憶體」的舊程式碼會增加記憶體使用。對應排查方向:在升級到 pandas 3.0 前,先把專案裡所有「修改 slice 後期望原 DataFrame 也跟著變」的地方找出來,並改成明確的 .copy()。

錯誤三:把 datetime 與時區混用。pd.to_datetime("2024-01-01") 不帶時區、pd.to_datetime("2024-01-01", utc=True) 帶 UTC 時區,兩者在算術運算時可能會出錯。對應排查方向:明確指定時區 utc=True 或 tz="Asia/Taipei",並用 .dt.tz_convert() 做時區轉換,不要混用 naive 與 tz-aware 的 datetime。

錯誤四:groupby().apply() 在大資料集上很慢。apply() 是 Python-level 的迴圈,對 1 千萬列以上的資料可能跑數小時。對應排查方向:能拆成 agg()、transform()、resample() 就不要用 apply();若真的需要 apply(),用 numba 引擎或重寫成向量化運算。

錯誤五:PyArrow 字串欄位與第三方套件不相容。有些老的視覺化套件、機器學習套件(例如某些 scikit-learn 的舊版)期待 object dtype 的字串欄位,碰到 string[pyarrow] 會出錯。對應排查方向:用 df[col].astype("string") 把欄位轉回 pandas 原生字串,或在 to_parquet() 時用 dtype_backend="numpy_nullable" 避免這個問題。

資料型別的最佳化與寫入策略

pandas 2.x 的另一個進階主題是「資料型別的下推(downcasting)」與「寫入策略」。當我們讀取大型 CSV 或 Parquet 檔時,pandas 的型別推論常常會給出比實際需求更寬鬆的型別,例如把 0/1 的布林欄位推成 int64、把小數推成 float64 而不是 float32。這種「過寬鬆」的型別會直接放大記憶體使用量,並讓後續運算變慢。

實務上有兩個常見的最佳化方向。第一個是「讀取時指定型別」:pd.read_csv("file.csv", dtype={"id": "int32", "value": "float32"}),這樣讀進來的 DataFrame 已經是省記憶體的狀態,不必再額外轉型。第二個是「讀取後批次轉型」:df = df.astype({"col": "int32"}),這個寫法適合「型別推論結果不滿意、需要全域調整」的情境。在數百萬列的資料上,這兩個方向通常能省 30% 到 50% 記憶體。

# 批次下推整數與浮點數欄位
def downcast_numeric(df: pd.DataFrame) -> pd.DataFrame:
    for col in df.select_dtypes(include=["int"]).columns:
        df[col] = pd.to_numeric(df[col], downcast="integer")
    for col in df.select_dtypes(include=["float"]).columns:
        df[col] = pd.to_numeric(df[col], downcast="float")
    return df

df_small = downcast_numeric(df)
print(df_small.dtypes)
# 輸出(依實際資料):
# VendorID                       int8
# passenger_count               int8
# trip_distance               float32
# fare_amount                 float32
# total_amount                float32
# dtype: object
print(f"原始記憶體:{memory_mb(df):.1f} MB;下推後:{memory_mb(df_small):.1f} MB")
# 輸出:原始記憶體:150.x MB;下推後:55.x MB(依示範資料而略有不同)

這段示範「批次下推」的標準做法:用 pd.to_numeric(..., downcast="integer") 把 int64 降成 int8/int16/int32,用 downcast="float" 把 float64 降成 float32。對 NYC 计程車這種「大多數欄位是 0 到 200 的數字」的資料,下推後的記憶體通常能省一半以上。實務上 int8 適合「範圍 -128 到 127」的欄位(例如 VendorID 只有 1 跟 2)、int16 適合「範圍 -32768 到 32767」的欄位。float32 在多數分析場景的精度已經足夠,但如果要做金融或科學計算請保留 float64。

另一個相關主題是「寫入 Parquet 時的壓縮與分區」。Day 9 已經示範過 DuckDB 的寫法,這裡補充 pandas 的版本:df.to_parquet("out.parquet", engine="pyarrow", compression="zstd", index=False),三個參數分別是「使用 PyArrow 引擎(功能最完整)」、「用 zstd 壓縮」、「不要把 index 寫進去」。實務上 index=False 是常見的設定,因為預設的 RangeIndex(0, 1, 2, ...)對 Parquet 的 metadata 沒有任何意義,多寫只是占空間。

寫入 CSV 時,df.to_csv("out.csv", index=False, encoding="utf-8") 也是常見寫法。注意 encoding="utf-8" 不會自動加 BOM,但若要給 Excel 在 Windows 上打開,建議加 encoding="utf-8-sig" 寫 BOM。對中文資料特別有用:utf-8-sig 寫的檔案在 Excel 會正確顯示中文,utf-8 寫的檔案在 Excel 會變成亂碼。這個細節在 Day 13 與 Day 18 的政府開放資料實戰會再出現。

效能與實務提醒

pandas 2.x 在效能上最重要的改變是對 groupby().agg() 的最佳化。對數百萬列的 groupby 操作,2.x 比 1.5 通常快 30% 到 50%,這來自內部引擎的改寫與對 numba 的更好支援。如果你的舊程式碼在 1.5 上跑得很慢,升級到 2.x 之後不必改任何寫法就會有改善,這是升級的最大誘因之一。

另一個效能技巧是「避免在 Python level 做迴圈」。df.iterrows() 與 df.itertuples() 都是 row-by-row 的 Python 迴圈,對 100 萬列的資料可能跑數十秒。實務上若需要對每列做複雜邏輯,用 df.apply()(向量化)或 Polars 的 map_elements() 會快很多;若真的要 row-by-row,用 df.itertuples() 比 iterrows() 快 5 到 10 倍。

記憶體管理也是 pandas 2.x 的重點。df.memory_usage(deep=True) 可以列出每個欄位的記憶體使用量;對於字串欄位特別大的 DataFrame,把 dtype_backend 從 numpy 換成 pyarrow 通常能省 50% 以上。另一個技巧是「先過濾再賦值」:df.loc[df["col"] > 0, "new_col"] = ... 而不是 df["new_col"] = df["col"].apply(...),後者會建立一個完整的新陣列,前者只會對符合條件的列寫入。

實務工作流上,建議把 pandas 用在「中等資料量、需要豐富的 DataFrame API、與其他 Python 套件整合」的場景。對「數億列以上」的資料,Polars(Day 11)或 DuckDB(Day 12)通常更划算;對「需要交易、保證寫入隔離」的場景,DuckDB 是首選;對「單機、數百萬列、隨時要與 sklearn 整合」的場景,pandas 仍是首選。三者搭配使用(混用策略)會在 Day 12 展開。

小結

今天把 pandas 2.x 的進階操作整理成一條工作流:用 read_parquet() 載入昨天寫的 Parquet、用 query() 與 assign() 寫可讀性高的 chain、用 pipe() 把任意函式串進 chain、用 groupby().agg() 與 transform() 做彙總與廣播、用 PyArrow backend 省記憶體、最後與 DuckDB 互通。pandas 2.x 與 1.x 的關鍵差異是 PyArrow 整合、nullable 整數、copy_on_write、與 inplace 的 deprecation,這些改變在 2025 年是升級到 2.x 的標準理由。

結語

今天的重點是「pandas 2.x 的進階操作」。我們從讀昨天的 Parquet 開始、用 query/assign/pipe 寫可讀性高的 chain、用 groupby/agg/transform 做彙總、用 PyArrow backend 省記憶體、最後與 DuckDB 互通。讀完這篇你應該能回答:pandas 2.x 與 1.x 的關鍵差異是什麼?pipe() 為什麼是寫 ETL 的好習慣?agg() 與 transform() 的差別在哪?PyArrow backend 對記憶體的影響有多大?這些問題的答案都藏在本篇的程式碼與文字裡。

明天,我們會進入 Polars:快而不占記憶體的選擇。Polars 是 2025 年最受矚目的 DataFrame 函式庫,用 Rust 實作、支援 lazy evaluation 與多執行緒、記憶體效率極佳。我們會用同一份 NYC 计程車 Parquet 比較 pandas 與 Polars 的效能差距,並介紹 Polars 的 Expression API 與 SQL Context。

延伸資源

  • pandas 官方文件(2.3,2025):https://pandas.pydata.org/docs/,本篇所有 API 都以 2.3 版為準。
  • pandas 2.x 升級指南(2024):https://pandas.pydata.org/docs/whatsnew/index.html,列出 2.0 到 2.3 的所有 breaking change 與新功能。
  • PyArrow 官方文件(2025):https://arrow.apache.org/docs/python/,pandas 整合底層的 Arrow 函式庫。
  • DuckDB Python API 文件(1.4,2025):https://duckdb.org/docs/api/python.html,本篇 duckdb.query() 與 con.register() 的官方說明。
  • NYC 计程車示範資料(CC0 授權):https://duckdb.org/data/nyc-taxi.csv.gz,本篇與 Day 9 範例的來源。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...