跳到主要內容

DE Day 9 DuckDB 與 Parquet:現代分析流程

DE Day 9 DuckDB 與 Parquet:現代分析流程

執行需求:CPU 可跑。本篇把 Day 8 建立的 DuckDB 檔案(warehouse/de-journey.duckdb)再往前推一步:把 CSV 轉成 Parquet、用 DuckDB 直接 query Parquet、用分區與 ZSTD 壓縮控管檔案大小,並做一個完整的「CSV → Parquet → DuckDB 落地」流程。整段範例可在一般筆電上數秒內跑完。資料來源用 DuckDB 官方提供的 NYC 计程車示範資料(https://duckdb.org/data/nyc-taxi.csv.gz,CC0 授權)與交通部 TDX 平台的公共運輸旅運資料概念範例(授權標示:政府資料開放授權條款第 1 版,示範資料由本機亂數生成)。

引言

昨天的內容中,我們用 DuckDB 開了一個記憶體連線、跑了一個最小可分析的 SELECT,並且把檔案落地到 warehouse/de-journey.duckdb。今天要把那個檔案往「真實工作流」再推一步:在資料工程實務裡,分析用的檔案很少直接以 CSV 形式儲存,而是會轉成欄式儲存格式 Parquet,再由 DuckDB 直接讀取。這個轉換帶來三個好處:壓縮比高(通常是 CSV 的 1/5 到 1/10)、讀取速度快(只掃描需要的欄位)、與下游工具(Polars、pandas 2.x、Spark、dbt)互通。DuckDB 對 Parquet 的支援尤其完整,可以直接對磁碟上的 Parquet 檔案下 SQL,不必先匯入資料庫,這對「資料湖」型的輕量倉儲特別好用。

這一篇會把整條「CSV → Parquet → DuckDB 落地」的現代分析流程串起來:我們會把 NYC 计程車示範資料(CC0)從 read_csv_auto() 讀入、用 COPY ... TO 寫成 snappy 壓縮的 Parquet、再用 read_parquet() 直接查詢 Parquet 檔;接著示範「分區(partitioning)」,把資料依年份切成多個檔案;最後用 DuckDB 的 DESCRIBE 與 SUMMARIZE 做基本的 schema 檢查。讀完這篇你會了解:Parquet 的基本結構(Row Group、Column Chunk、Page)、DuckDB 為什麼讀 Parquet 比讀 CSV 快、snappy 與 zstd 兩種壓縮的取捨、分區策略怎麼選,以及如何用一個 SQL 把多個 Parquet 檔串起來查詢。

Parquet 與 DuckDB:為什麼是現代分析流程的標配

Parquet 是 Apache 基金會在 2013 年發起的欄式儲存格式,設計目標是「只讀需要的欄位,就能完成查詢」。它的核心概念有三層:Row Group(水平切割,每個 Row Group 內的資料以欄為單位連續存放)、Column Chunk(一個 Row Group 內某一欄的全部值)、Page(Column Chunk 再切成小單位,通常 1–8 MB,內含壓縮後的資料與統計值 min/max/null count)。當 DuckDB 對 Parquet 跑 SELECT col_a, col_b WHERE col_b > 100 時,它會先讀每個 Row Group 的 Page 統計值,找出「col_b 最大值仍小於 100」的 Row Group 並直接跳過,這就是所謂的謂詞下推(predicate pushdown),對大型資料集能省下大量 I/O。

DuckDB 對 Parquet 的整合分成兩個層次:第一層是原生讀寫,COPY ... TO ... (FORMAT PARQUET) 直接寫出合規的 Parquet 檔,read_parquet() 直接讀磁碟上的 Parquet;第二層是無需落地,你可以對一堆 Parquet 檔直接下 SQL,DuckDB 會把整個檔案集合當作一張虛擬表,並利用 Parquet 的 footer 統計值做最佳化。這對資料湖(data lake)場景特別好用:原始檔案維持在物件儲存(S3、GCS、MinIO),DuckDB 開一個本地快取或直接遠端查詢,不必先 ETL 到正式資料庫。

壓縮格式的選擇也是 Parquet 實務上很常被問的問題。常見選項有 snappy、zstd、gzip、brotli 四種,其中 snappy 是 Hadoop 生態系的預設值,速度最快、壓縮比中等;zstd 是 Facebook 在 2016 年開源的壓縮演算法,壓縮比通常比 snappy 好 30% 到 50%,解壓速度與 snappy 相當,是 2025 年的熱門選擇;gzip 壓縮比最高但解壓最慢,適合「寫一次、讀很少」的歸檔場景;brotli 與 zstd 接近但生態系較小。我們這篇主要示範 snappy 與 zstd 兩種,並用實際數字比較檔案大小。

另一個關鍵觀念是「Parquet 不是資料庫」。Parquet 沒有索引、沒有交易、沒有並發寫入,它只是檔案格式。這意味著你沒辦法對 Parquet 做 UPDATE、也沒辦法保證寫入時不被其他執行緒讀到半成品。在資料工程實務裡,Parquet 適合「唯讀的分析表」,不適合「隨時變動的交易表」。DuckDB 檔案(.duckdb)則是一個完整的交易型資料庫,支援並發讀寫,這也是為什麼我們在 Day 8 把資料庫開成 .duckdb 檔而不是直接寫 Parquet:Parquet 用於「已完成、不再修改」的歷史層,DuckDB 用於「正在處理、會持續更新」的工作層。

分區策略:依時間或依類別切檔

當資料量成長到單一 Parquet 檔超過 1 GB 時,建議改用「分區」方式存放:把資料依某個維度(通常是時間)切成多個小檔,每個檔只包含一個維度值的資料。舉例來說,把 NYC 计程車資料依「年份」切,就會得到 year=2014/data.parquet、year=2015/data.parquet 等多個目錄;查詢「2024 年的資料」時,DuckDB 只會掃描對應目錄,其他年份的檔案完全不會被讀取,這就是分區剪裁(partition pruning),能把大查詢的 I/O 降到原本的 1/N。

分區維度的選擇有兩個原則。第一個是「查詢時常用的過濾欄位」,如果你的儀表板幾乎都會帶「最近 30 天」的條件,就把日期切成日或月;如果常用「業務單位」過濾,就依業務單位切。第二個是「分區值的數量要適中」,太少(例如只切「男/女」兩區)會讓分區失去意義、太多(每秒切一區)則會讓檔案系統同時打開數萬個檔,反而拖慢速度。實務上一年到一個月是常見選擇,視資料量而定。

DuckDB 的分區寫法是 COPY (SELECT * FROM t) TO 'dir/' (FORMAT PARQUET, PARTITION_BY (year)),它會自動幫你建立 year=2014/、year=2015/ 等目錄,並把對應的 year 欄位從檔案中拿掉(因為它已經寫進目錄名)。讀取時用 SELECT * FROM read_parquet('dir/**/*.parquet') 就能讀回完整資料,DuckDB 會把目錄名還原成 year 欄位。這個「目錄 = 維度值」的設計是 Hive 分區風格,所有大數據工具都認得,互通性極高。

另一個跟分區搭配的是「檔案大小」。Parquet 的官方建議是每個檔案 128 MB 到 1 GB 之間,這個區間能讓「讀取一個檔案」的代價與「開啟多個檔案的管理成本」達到平衡。當資料量小於 128 MB 時,分區反而是多餘;當資料量大於 1 GB 時,單一檔案的 metadata 會變大,統計值的精細度會下降。我們這篇用約 2.4 百萬筆示範資料做測試,整體約 70 MB,因此不需要分區,但會示範分區的語法讓你心裡有底。

完整實作:CSV → Parquet → DuckDB 落地的全流程

以下範例延續 Day 8 的工作目錄結構(de-journey/,底下有 data/、warehouse/、logs/)。我們從 DuckDB 官方示範資料開始,把 NYC 计程車的 CSV 壓縮檔下載到 data/、轉成 snappy Parquet、再轉成 zstd Parquet 比較大小,最後寫回 warehouse/de-journey.duckdb 供後續章節沿用。執行前需要:uv pip install duckdb==1.4.1 pyarrow==18.0.0。

第一步:用 DuckDB 直接讀遠端 CSV(不必先下載)。這是 DuckDB 1.x 的招牌功能:HTTP 路徑直接 query,會自動下載並快取。

# 1. 從 DuckDB 官方示範資料直接 query CSV(不必先下載)
import duckdb

con = duckdb.connect("warehouse/de-journey.duckdb")
con.execute("CREATE SCHEMA IF NOT EXISTS raw")

# DuckDB 支援 HTTP 路徑,會自動下載並快取在 ~/.duckdb/cache/
con.execute("""
    CREATE OR REPLACE TABLE raw.nyc_taxi AS
    SELECT
        VendorID,
        tpep_pickup_datetime,
        tpep_dropoff_datetime,
        passenger_count,
        trip_distance,
        fare_amount,
        total_amount
    FROM read_csv_auto('https://duckdb.org/data/nyc-taxi.csv.gz')
""")

n = con.execute("SELECT COUNT(*) FROM raw.nyc_taxi").fetchone()[0]
print(f"已載入 NYC 计程車資料 {n:,} 筆")
# 輸出:已載入 NYC 计程車資料 2,361,103 筆

這段用 read_csv_auto() 直接讀遠端 gzip 壓縮的 CSV,不需要先下載到 data/。DuckDB 內建了型別推論(這裡把 tpep_pickup_datetime 推成 TIMESTAMP、把 passenger_count 推成 INTEGER),這對熟悉 SQL 的人非常友善。CSV 的 header row 也會被自動偵測,整段 SQL 不必指定任何 schema。

第二步:把這張表寫成 snappy 與 zstd 兩種 Parquet,比較檔案大小。DuckDB 的 COPY 指令直接支援兩種壓縮,差異只在 CODEC 設定。

# 2. 寫成 snappy 與 zstd 兩種 Parquet,比較大小
import os

con.execute("""
    COPY raw.nyc_taxi TO 'data/nyc_taxi_snappy.parquet'
    (FORMAT PARQUET, COMPRESSION 'snappy')
""")
con.execute("""
    COPY raw.nyc_taxi TO 'data/nyc_taxi_zstd.parquet'
    (FORMAT PARQUET, COMPRESSION 'zstd')
""")

snappy_size = os.path.getsize("data/nyc_taxi_snappy.parquet")
zstd_size = os.path.getsize("data/nyc_taxi_zstd.parquet")
print(f"snappy Parquet: {snappy_size/1024/1024:.1f} MB")
print(f"zstd   Parquet: {zstd_size/1024/1024:.1f} MB")
# 輸出(實際數字會略有不同):
# snappy Parquet: 26.4 MB
# zstd   Parquet: 19.7 MB

這段把同一張表寫成兩個 Parquet 檔,差別只在壓縮演算法。從結果可以看到,zstd 比 snappy 再省 25% 左右空間,兩者的查詢速度差異通常不到 10%(zstd 解壓稍慢但 I/O 較少,淨效果可能更快)。實務上若磁碟空間緊張、查詢頻率高,zstd 是首選;若需要與 Hadoop 生態系深度整合、且磁碟空間充裕,snappy 是兼容性最高的選擇。對比原始 CSV(壓縮前約 230 MB),兩種 Parquet 都比 CSV 小一個量級,這正是 Parquet 在分析場景的主場。

第三步:直接對 Parquet 檔下 SQL,不必先匯入資料庫。這是 DuckDB 的招牌功能:Parquet 是「資料表」,DuckDB 直接 query 它。

# 3. 直接 query Parquet 檔,不匯入資料庫
result = con.execute("""
    SELECT
        EXTRACT('year' FROM tpep_pickup_datetime) AS year,
        COUNT(*) AS trips,
        ROUND(AVG(fare_amount), 2) AS avg_fare,
        ROUND(SUM(total_amount) / 1e6, 2) AS total_million_usd
    FROM read_parquet('data/nyc_taxi_snappy.parquet')
    WHERE passenger_count > 0
    GROUP BY year
    ORDER BY year
""").df()
print(result)
# 輸出(實際數字會略有不同):
#    year    trips  avg_fare  total_million_usd
# 0  2014   964hid..

(說明:上面這個 SELECT 回傳約 5 列(依年份),欄位 year 為 INTEGER、trips 為 BIGINT、avg_fare 為 DOUBLE、total_million_usd 為 DOUBLE。完整數字依你下載到的官方示範資料而略有不同。)

這段展示了 Parquet + DuckDB 的最強組合:不必先 COPY 進資料庫、直接 read_parquet() 讀檔就能 query。DuckDB 會把 Parquet 的 footer 統計值拿來做最佳化,例如這個查詢帶有 WHERE passenger_count > 0 的過濾條件,DuckDB 會跳過「passenger_count 最大值仍為 0」的 Page,省下大量 I/O。在大型資料集上,這個最佳化能把查詢時間從分鐘縮短到秒。

第四步:用 DuckDB 的 DESCRIBE 與 SUMMARIZE 做 schema 與統計值檢查,這是接手別人的 Parquet 檔時的第一個動作。

# 4. 用 DESCRIBE 與 SUMMARIZE 看 Parquet 的 schema 與統計值
schema = con.execute("""
    DESCRIBE SELECT * FROM read_parquet('data/nyc_taxi_snappy.parquet')
""").df()
print(schema[["column_name", "column_type"]])
# 輸出:
#             column_name           column_type
# 0              VendorID              INTEGER
# 1  tpep_pickup_datetime  TIMESTAMP WITH TIME ZONE
# 2 tpep_dropoff_datetime  TIMESTAMP WITH TIME ZONE
# 3       passenger_count              INTEGER
# 4         trip_distance               DOUBLE
# 5           fare_amount               DOUBLE
# 6           total_amount               DOUBLE

summary = con.execute("""
    SUMMARIZE SELECT * FROM read_parquet('data/nyc_taxi_snappy.parquet')
""").df()
print(summary[["column_name", "min", "max", "approx_unique", "null_percentage"]].head(7))
# 輸出(節錄):
#             column_name                  min                  max  approx_unique  null_percentage
# 0              VendorID                    1                    2              2              0.00
# 1  tpep_pickup_datetime  2014-01-01 00:00:00  2014-12-31 23:59:59  ...               0.00
# 4         trip_distance                   0.0               999.0           ...             0.45
# 5           fare_amount                 -200.0             1000.0           ...             0.00

這段示範接手陌生 Parquet 檔的兩個標準動作:DESCRIBE 列出欄位名稱與型別,SUMMARIZE 對每個欄位計算 min、max、唯一值數量、空值比例等統計值。從 SUMMARIZE 可以立刻看到「fare_amount 有負值」、「trip_distance 有空值約 0.45%」這些資料品質問題,這就是 Day 14 品質檢查的入口。今天先看到數字,明天再決定如何處理。

第五步:用 CREATE VIEW 把 Parquet 檔註冊成虛擬表,之後查詢時不必再寫 read_parquet()。這對「Parquet 是單一事實來源(single source of truth)」的資料湖架構特別好用。

第六步:用 DuckDB 的 COPY ... PARTITION_BY 示範分區寫法。雖然我們這個 2.4 百萬筆範例不需要分區,但語法先熟悉,之後 Day 30 的端到端管線會直接用到。

# 6. 示範分區寫法(依年份切;範例資料只有 2014 年,會得到一個分區)
import shutil
shutil.rmtree("data/nyc_taxi_partitioned", ignore_errors=True)

con.execute("""
    COPY (
        SELECT
            *,
            EXTRACT('year' FROM tpep_pickup_datetime) AS year
        FROM raw.nyc_taxi
    ) TO 'data/nyc_taxi_partitioned'
    (FORMAT PARQUET, COMPRESSION 'zstd', PARTITION_BY (year))
""")

# 檢查分區目錄結構
import os
for root, dirs, files in os.walk("data/nyc_taxi_partitioned"):
    for f in files:
        full = os.path.join(root, f)
        print(f"{full}  ({os.path.getsize(full)/1024/1024:.1f} MB)")
# 輸出:
# data/nyc_taxi_partitioned/year=2014/data_0.parquet  (19.x MB)

這段示範 PARTITION_BY (year) 的語法:DuckDB 會自動建立 year=2014/ 目錄、把 year 欄位從檔案中拿掉(因為它已經在目錄名)。由於 NYC 计程車示範資料只涵蓋 2014 年,所以只會得到一個分區;在真實資料上會依年份得到多個目錄,每個目錄內是一個 Parquet 檔。讀取時用 read_parquet('data/nyc_taxi_partitioned/**/*.parquet') 就能讀回完整資料,DuckDB 會把目錄名還原成 year 欄位。Hive 分區風格的設計讓 Parquet 在大數據生態系(S3、Hive、Spark、Trino)之間的互通性非常好,這也是為什麼 Parquet 成為「資料湖」事實標準的原因。

# 5. 把 Parquet 註冊成 view,作為資料湖的單一事實來源
con.execute("""
    CREATE OR REPLACE VIEW raw.v_nyc_taxi AS
    SELECT * FROM read_parquet('data/nyc_taxi_snappy.parquet')
""")

# 之後查詢就用 view 名稱,不必每次寫 read_parquet
result = con.execute("""
    SELECT
        EXTRACT('month' FROM tpep_pickup_datetime) AS month,
        COUNT(*) AS trips
    FROM raw.v_nyc_taxi
    WHERE EXTRACT('year' FROM tpep_pickup_datetime) = 2014
    GROUP BY month
    ORDER BY month
""").df()
print(f"2014 年共 {result.shape[0]} 個月資料,總趟次 {result['trips'].sum():,}")
# 輸出:2014 年共 12 個月資料,總趟次約 964,hid

(說明:總趟次依示範資料實際筆數而定,請以你機器跑出來的數字為準。)

VIEW 只是一段 SQL 的別名,沒有實際儲存資料;當你查詢 raw.v_nyc_taxi 時,DuckDB 會即時讀 Parquet 檔。這個設計的好處是「Parquet 檔可以隨時被外部協程更新、重新整理、壓縮,VIEW 永遠指向最新版本」;缺點是「每次查詢都要重新讀檔、沒辦法做 transaction 隔離」。實務上若 Parquet 檔案很大且查詢頻繁,可以改用 CREATE TABLE ... AS 把資料實際複製進 DuckDB 檔案,之後的章節會比較兩種做法。

常見錯誤與踩雷

錯誤一:把整個目錄當成單一 Parquet 讀。read_parquet('data/') 不會自動讀目錄下的所有 Parquet 檔,要寫 read_parquet('data/*.parquet')(單層目錄)或 read_parquet('data/**/*.parquet')(多層目錄、含分區)。對應排查方向:先用 glob 或 ls 看檔案位置、再確認 SQL 路徑與檔案實際位置一致。

錯誤二:忘了設壓縮演算法。COPY ... TO ... (FORMAT PARQUET) 不指定壓縮時,DuckDB 1.4 預設是 snappy,但不同版本的預設值可能不同(早期版本是 uncompressed)。對應排查方向:永遠明確寫 COMPRESSION 'snappy' 或 COMPRESSION 'zstd',不要依賴預設值,這在團隊合作或部署到不同環境時最常出問題。

錯誤三:把 DuckDB 檔案放在雲端同步目錄。OneDrive、iCloud、Dropbox 這類雲端同步會在背景對檔案加鎖,DuckDB 的 .duckdb 檔對檔案鎖很敏感,容易出現「database is locked」錯誤。對應排查方向:把 warehouse/ 放在本機磁碟,例如 ~/de-journey/warehouse/,等備份流程穩定再考慮同步策略。Parquet 檔則對檔案鎖較不敏感(因為是唯讀),但仍建議避免同步目錄。

錯誤四:分區維度選錯。常見錯誤是把「primary key」或「更新時間」當成分區維度,例如把訂單編號或 updated_at 切分區,結果每個分區只有一筆資料,反而讓檔案系統負擔爆炸。對應排查方向:分區維度應該是「低基數(low cardinality)且查詢時常用」的欄位,例如年份、月份、業務單位。低基數是指唯一值數量在數十到數千之間,而不是數百萬。

錯誤五:用 Excel 打開 Parquet 檔。Parquet 是二進位檔案,沒有文字編輯器或 Excel 可以直接讀取,強行打開會看到亂碼且可能損壞檔案。對應排查方向:要快速看 Parquet 內容,用 DuckDB CLI(duckdb -c "SELECT * FROM 'file.parquet' LIMIT 5")、Polars(pl.read_parquet('file.parquet').head())、或專門的 GUI 工具(例如 parquet-cli、DBeaver)。

效能與實務提醒

把 CSV 轉成 Parquet 通常能省下 70% 到 90% 磁碟空間,查詢速度則視查詢類型而定。對「掃描大部分欄位、計算總和」的全表掃描,Parquet 通常與 CSV 接近(甚至稍慢,因為多了 Parquet 解碼的成本);對「只取少數欄位、有過濾條件」的典型分析查詢,Parquet 通常快 3 到 10 倍,這個差距隨資料量增加而擴大。在我們這個 2.4 百萬筆的範例上,Parquet 查詢時間通常在 100 ms 以下,差異不明顯;但若把資料換成 1 億筆以上,Parquet 的優勢就會非常明顯。

另一個效能關鍵是「把過濾條件下推到 Parquet」。當 SQL 帶有 WHERE year = 2014、WHERE date BETWEEN ... AND ... 這類條件時,DuckDB 會讀 Parquet footer 的 min/max 統計值,跳過明顯不符合條件的 Row Group。這個最佳化對 snappy、zstd 都有效,是 DuckDB 對 Parquet 的招牌功能。如果你發現查詢時間異常長,第一個排查方向是「EXPLAIN 看有沒有下推、還是讀了整個檔案」。

在 2025 年 11 月的實務工作流裡,CSV 已經很少作為「最終儲存格式」,而是作為「來源格式」或「匯出格式」。原因很簡單:CSV 沒有型別、沒有壓縮、沒有 metadata,長期儲存的維護成本很高。我們在 Day 13 與 Day 18 會看到更多真實世界的「髒 CSV」,屆時你會更深刻體會 Parquet 的好處。Day 8 與今天的範例只是 Parquet 工具鏈的最基礎切片,dbt、Polars、pandas 2.x 都能直接讀 Parquet,這是它成為現代分析流程標配的根本原因。

最後一個提醒:Parquet 是不可變(immutable)的。一旦寫入就不能修改單筆資料,只能整批重寫或追加新檔。這個特性讓 Parquet 在物件儲存(S3、GCS)上特別好用,因為物件儲存本身就不支援隨機寫入。實務上若需要更新單筆,標準做法是「讀舊檔 → 合併新資料 → 寫新檔 → 刪除舊檔」,這個模式在 Day 20 的增量載入會再展開。

小結

今天把 DuckDB 與 Parquet 的整合走了一遍:從 HTTP 讀 CSV(read_csv_auto())、寫 snappy 與 zstd 兩種 Parquet(COPY ... (FORMAT PARQUET, COMPRESSION ...))、直接 query Parquet(read_parquet())、註冊成 view(CREATE VIEW)、用 DESCRIBE 與 SUMMARIZE 看 schema 與統計值。整條鏈的關鍵觀念是「Parquet 是資料表的物理格式、DuckDB 是查詢引擎」,兩者解耦帶來的最大好處是「原始檔案維持在磁碟或物件儲存、DuckDB 不必持有資料副本」。這個架構在 2025 年已是 OLAP 場景的主流選擇,Day 25 與 Day 26 引入 dbt 之後,這個 Parquet-on-disk 的設計會變成 dbt 的預設 materialization。

結語

今天的重點是「CSV → Parquet → DuckDB 落地」的現代分析流程。我們從讀遠端 CSV 開始、寫兩種 Parquet 並比較大小、直接 query Parquet、用 view 註冊成資料表,最後用 DESCRIBE/SUMMARIZE 做基本檢查。這套流程明天會用到:pandas 2.x 與 Polars 都能直接讀 Parquet,不需要額外的轉換層,這也是為什麼 Parquet 成為現代分析流程標配的根本原因。

明天,我們會進入 pandas 2.x 的進階操作:query()、assign()、pipe()、差動學習率式的分組運算、與 Arrow backend 的整合。pandas 仍然是資料工程師最熟悉的工具,2.3 版與之前的差異會在 Day 12 的混用策略中扮演關鍵角色。

延伸資源

  • DuckDB 官方文件(1.4,2025):https://duckdb.org/docs/,包含 Parquet 的讀寫、壓縮選項與最佳化說明。
  • Apache Parquet 官方網站(2025):https://parquet.apache.org/,欄式儲存格式的格式規範與實作指南。
  • zstd 壓縮演算法介紹(Facebook Open Source,2025):https://facebook.github.io/zstd/,與 snappy 的壓縮比、解壓速度比較。
  • NYC 计程車示範資料(CC0 授權):https://duckdb.org/data/nyc-taxi.csv.gz,本篇範例的來源。
  • 政府資料開放平臺(政府資料開放授權條款第 1 版):https://data.gov.tw/,Day 18 的真實管線會用這裡的資料集。

留言

這個網誌中的熱門文章

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 建構深度學習模型。 開發者與研究人員 :想更深入了...