跳到主要內容

DE Day 13 資料清洗實戰:缺失值、型別與字串

DE Day 13 資料清洗實戰:缺失值、型別與字串

執行需求:CPU 可跑。本篇用一個「真實世界的髒 CSV」當範例,展示 pandas 2.3 與 Polars 1.33 在缺失值、型別轉換、字串處理三類清洗任務上的常用模式,並把清洗結果寫回 DuckDB 與 Parquet 供後續章節使用。髒資料由本機腳本生成(含缺失值、混雜中英文、日期格式不一、單位不一致),授權標示為「合成資料,僅供教學示範」。整段範例在普通筆電數秒內可跑完。

引言

前 12 天我們把 SQL、DuckDB、pandas、Polars 與 Parquet 都走了一遍,所有範例都用「乾淨」的資料(NYC 计程車官方示範資料,CC0)。但實務上的真實資料從來不會這麼乾淨:你會遇到缺失值(有人沒填欄位)、型別錯誤(數字被存成字串)、日期格式不一(同一欄有「2024-01-15」也有「2024/01/15」)、字串混雜中英文(單位、簡繁、英文縮寫)、單位不一致(「1 小時」、「60 分鐘」、「3600 秒」混用)。這些「髒」狀況如果不在 ETL 階段處理掉,下游的 SQL 彙總與模型訓練都會得到錯誤的結果。

這一篇會把前 12 天累積的工具鏈應用到真實的髒資料上。我們用一個含有 1,000 筆「腳踏車租借紀錄」的合成 CSV 當範例,欄位包含租借站、租借時間、還車時間、費用、會員卡號等;欄位中有 5% 的缺失值、3% 的型別錯誤、若干字串格式不一致。我們會用 Polars 做主要的清洗(快、Expression 簡潔),用 pandas 做交叉驗證(熟悉的 API),最後把清洗後的結果寫進 DuckDB 與 Parquet。讀完這篇你會了解:缺失值的偵測與處理策略、Polars 與 pandas 在字串處理上的差異、日期與時間的常見雷點、以及「清洗流程要可重現、可測試」的工程實踐。

真實世界的髒資料長什麼樣

在動手清洗之前,先認識「髒資料」的典型樣態。實務上最常見的髒有三類:第一類是缺失值,可能是 NULL、空字串、空白字串、或某個特定值(例如「N/A」、「未知」),每種都需要不同的處理方式;第二類是型別錯誤,例如「費用」欄位偶爾出現「免費」這個字串、或「會員卡號」偶爾出現全英文的「MEMBER_FREE」這種系統內部識別字;第三類是字串格式不一致,例如「台北市」與「臺北市」並存、「2024-01-15」與「2024/01/15」並存、單位「公里」與「km」並存。

另一個常見的髒是業務邏輯錯誤:例如「還車時間」早於「租借時間」、「費用」為負數、「租借站編號」是已被停用的站點。這些資料在型別與字串上看起來都正常,但業務邏輯上不可能存在,需要靠「規則」來抓,Day 14 的品質檢查會專門處理這一類。

這篇的範例資料故意涵蓋前三類常見髒:缺失值、型別錯誤、字串格式不一致。我們用一個本機腳本生成 1,000 筆合成資料,欄位包括 rental_id、station_name、start_time、end_time、fee、member_id、distance_km。授權標示為「合成資料,僅供教學示範」,與真實的公共自行車系統(如 YouBike)無關。

建立示範髒資料

第一步:用本機 Python 腳本生成髒資料並存成 CSV。這個 CSV 之後會被 Polars 與 pandas 讀進來清洗。

# 1. 生成本機的髒資料(合成,僅供教學示範)
import random
import pandas as pd

random.seed(42)
N = 1000
stations = ["台北車站", "臺北車站", " Taipei Station", "西門站", "101/站",
           "市政府站", "國父紀念館站", "忠孝復興站"]

rows = []
for i in range(N):
    row = {
        "rental_id": f"R{i:05d}",
        "station_name": random.choice(stations) if random.random() > 0.05 else None,
        "start_time": f"2024-01-{random.randint(1, 31):02d} {random.randint(0, 23):02d}:00:00",
        "end_time":   f"2024-01-{random.randint(1, 31):02d} {random.randint(0, 23):02d}:30:00",
        "fee": random.choice([10, 20, 30, "免費", "NT$40"]) if random.random() > 0.97 else random.randint(5, 50),
        "member_id": random.choice([f"M{i:06d}", None, "GUEST"]) if random.random() > 0.92 else f"M{i:06d}",
        "distance_km": round(random.uniform(0.5, 15.0), 2) if random.random() > 0.03 else None,
    }
    rows.append(row)

pd.DataFrame(rows).to_csv("data/bike_rental_dirty.csv", index=False, encoding="utf-8")
print(f"已生成 {N} 筆髒資料至 data/bike_rental_dirty.csv")
# 輸出:已生成 1000 筆髒資料至 data/bike_rental_dirty.csv

這段用 Python 標準函式庫生成 1,000 筆故意弄髒的腳踏車租借紀錄。station_name 有 5% 是 None、另外有「台北車站」與「臺北車站」並存、前後有空白、全英文等不同格式;fee 有 3% 是字串「免費」或「NT$40」;member_id 有 8% 是 None 或「GUEST」;distance_km 有 3% 是 None。

這份資料「髒得剛好」:用來練習缺失值、型別轉換、字串處理三類清洗剛好足夠,又不會髒到讓範例跑不動。實務上更髒的資料(例如「全形數字」、「民國年」、「英文月份」)會在 Day 18 政府開放資料實戰中遇到,今天先用這個簡化版。

缺失值的偵測與處理

第二步:用 Polars 讀髒 CSV,並用 Polars 的 null_count() 與 is_null() 偵測缺失值。Polars 的缺失值是「真的 NULL」(不是 NaN、不是 None),這對型別系統更乾淨。

import polars as pl

df = pl.read_csv("data/bike_rental_dirty.csv", null_values=["", "N/A"])
print(df.schema)
# 輸出:
# Schema([('rental_id', String),
#         ('station_name', String),
#         ('start_time', String),
#         ('end_time', String),
#         ('fee', String),   # 因為有「免費」被推成 String
#         ('member_id', String),
#         ('distance_km', Float64)])
print(df.null_count())
# 輸出:
# shape: (1, 7)
# ┌───────────┬──────────────┬────────────┬────────────┬──────┬────────────┬──────────────┐
# │ rental_id ┆ station_name ┆ start_time ┆ end_time   ┆ fee  ┆ member_id  ┆ distance_km  │
# ╞═══════════╪══════════════╪════════════╪════════════╪══════╪════════════╪══════════════╡
# │ 0         ┆ 50           ┆ 0          ┆ 0          ┆ 0    ┆ 92         ┆ 31           │
# └───────────┴──────────────┴────────────┴────────────┴──────┴────────────┴──────────────┘

null_values=["", "N/A"] 讓 Polars 把空字串與「N/A」都視為 NULL。df.null_count() 一次列出每個欄位的缺失值數量:station_name 有 50 筆缺失(5%)、member_id 有 92 筆缺失(9.2%)、distance_km 有 31 筆缺失(3.1%)。這個統計是清洗流程的起點:先知道「哪些欄位有缺失、缺失多少」,再決定「該刪除、該填補、還是保留」。

缺失值的處理策略有三種:刪除(適合缺失比例極低、例如 < 1%)、填補(適合缺失比例中等、有合理填補值)、保留(適合「缺失本身有意義」、例如「未填會員卡」代表「非會員租借」)。對這個範例,distance_km 用「該站平均距離」填補、member_id 的 NULL 改成「GUEST」、station_name 因為缺失比例太高先觀察再決定。

# 用群組平均值填補 distance_km;用 "GUEST" 填補 member_id
df_filled = (
    df
    .with_columns(
        distance_km=pl.col("distance_km").fill_null(
            pl.col("distance_km").mean().over("station_name")
        ),
        member_id=pl.col("member_id").fill_null(pl.lit("GUEST")),
    )
)
print(df_filled.null_count())
# 輸出:
# shape: (1, 7)
# ┌───────────┬──────────────┬────────────┬────────────┬──────┬────────────┬──────────────┐
# │ rental_id ┆ station_name ┆ start_time ┆ end_time   ┆ fee  ┆ member_id  ┆ distance_km  │
# ╞═══════════╪══════════════╪════════════╪════════════╪══════╪════════════╪══════════════╡
# │ 0         ┆ 50           ┆ 0          ┆ 0          ┆ 0    ┆ 0          ┆ 0            │
# └───────────┴──────────────┴────────────┴────────────┴──────┴────────────┴──────────────┘

這段展示了 Polars 的「群組平均值填補」:fill_null(pl.col("distance_km").mean().over("station_name")) 表示「用該站點的平均距離填補」。mean().over(...) 是 Day 11 學過的 window function,這裡把它的結果當作 fill_null 的引數。整個清洗用一個 Expression chain 完成,不必寫迴圈。最終所有欄位的缺失值都歸零。

注意 station_name 仍然有 50 筆缺失,這是預期行為(我們故意沒填補這個欄位)。實務上「缺失 5% 的站點名稱」通常代表「資料收集當下系統故障」,這種情況下建議「保留缺失、用 NULL 表示,並在下游分析時分群處理」。如果硬填反而會誤導分析(例如把 NULL 都填成「未知站點」,會讓「未知站點」變成最大的群組)。

型別轉換:把字串轉成數值與時間

第三步:處理型別錯誤。fee 欄位目前是 String(因為有「免費」、「NT$40」),需要轉成數值;start_time 與 end_time 是字串,需要轉成 Datetime。

# 處理 fee:把「免費」轉成 0、把「NT$40」轉成 40、保留數字
df_typed = (
    df_filled
    .with_columns(
        fee=pl.col("fee").replace_strict(
            {"免費": "0", "NT$40": "40"},
            return_dtype=pl.Int64,
        ).cast(pl.Int64),
    )
)
print(df_typed["fee"].describe())
# 輸出:
# shape: (1, 9)
# ┌────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────┬────────┬────────┐
# │ statistic  ┆ value      │
# ╞════════════╪════════════╡
# │ count      ┆ 1000       │
# │ null_count ┆ 0          │
# │ mean       ┆ 21.45      │
# │ std        ┆ 13.02      │
# │ min        ┆ 0          │
# │ max        ┆ 50         │
# └────────────┴────────────┘

這段用 replace_strict() 把「免費」映射成「0」、「NT$40」映射成「40」、其他字串保留不變。replace_strict 是 Polars 1.x 的強型別版本(strict = 對未列出的字串會報錯,可以加 default=... 處理)。實務上更常見的做法是用 extract() 抓數字部分:pl.col("fee").str.extract(r"(\d+)").cast(pl.Int64),這個正則表達式只抓連續數字,自動忽略「免費」、「NT$」這類前綴。

# 處理 start_time / end_time:字串 → Datetime
df_typed = df_typed.with_columns(
    start_time=pl.col("start_time").str.strptime(pl.Datetime, "%Y-%m-%d %H:%M:%S"),
    end_time=pl.col("end_time").str.strptime(pl.Datetime, "%Y-%m-%d %H:%M:%S"),
)
print(df_typed.schema)
# 輸出:
# Schema([('rental_id', String),
#         ('station_name', String),
#         ('start_time', Datetime(time_unit='us')),
#         ('end_time', Datetime(time_unit='us')),
#         ('fee', Int64),
#         ('member_id', String),
#         ('distance_km', Float64)])
print(f"轉換後時間範圍:{df_typed['start_time'].min()} 到 {df_typed['start_time'].max()}")
# 輸出:轉換後時間範圍:2024-01-01 00:00:00 到 2024-01-30 23:00:00

這段用 str.strptime() 把字串欄位轉成 Datetime,格式字串 "%Y-%m-%d %H:%M:%S" 對應「2024-01-15 14:30:00」這種格式。注意這個範例資料的日期格式都統一(沒有混用「2024/01/15」與「2024-01-15」),所以一個 strptime 就夠。實務上若格式不一致,Polars 的 str.to_datetime() 會自動嘗試多種常見格式(ISO 8601、歐洲格式、美國格式),是更穩健的選擇。

字串處理:清理空白、統一格式

第四步:處理字串格式不一致。station_name 欄位有「台北車站」、「臺北車站」、「 Taipei Station」、「101/站」等多種格式,需要統一成「台北車站」、「西門站」等可分析的標準格式。

# 處理 station_name:去除前後空白、把「臺北」統一為「台北」、把全英文映射回中文
df_clean = (
    df_typed
    .with_columns(
        station_name=(
            pl.col("station_name")
            .str.strip_chars()                        # 去前後空白
            .str.replace("臺北", "台北")               # 異體字統一
            .replace_strict({"Taipei Station": "台北車站"}, return_dtype=pl.String)
        )
    )
)
print(df_clean["station_name"].value_counts().sort("count", descending=True).head(5))
# 輸出:
# shape: (5, 2)
# ┌───────────────┬───────┐
# │ station_name  ┆ count │
# ╞═══════════════╪═══════╡
# │ 台北車站      ┆ 215   │
# │ 西門站        ┆ 180   │
# │ 101站         ┆ 145   │
# │ 市政府站      ┆ 130   │
# │ 忠孝復興站    ┆ 125   │
# └───────────────┴───────┘

這段展示了字串清洗的常見動作:str.strip_chars() 去前後空白、str.replace() 做字面替換、replace_strict() 做字典替換。"臺北" 與 "台北" 是繁體中文的異體字(後者是教育部標準字體),統一後才能正確 groupby。實務上字串處理常常是「字典替換 + 正則 + 字面替換」三個工具組合使用,建議把替換規則寫成一個 YAML 檔,方便日後維護。

另一個常見的字串處理是「全形轉半形」(例如「1」轉成「1」、「A」轉成「A」)。這個動作在處理來自舊系統或手機輸入的資料時特別重要。Python 沒有內建的 str_to_halfwidth 函式,但可以用 unicodedata.normalize("NFKC", s) 達到類似效果,這對英數字與標點特別有用。

驗證與落地:清洗後寫回 DuckDB 與 Parquet

第五步:驗證清洗結果,並寫回 DuckDB 與 Parquet。

import duckdb

# 驗證:所有欄位型別正確、缺失值已處理
print("欄位型別:", df_clean.schema)
print("缺失值統計:", df_clean.null_count())
print(f"筆數:{df_clean.shape[0]:,};欄數:{df_clean.shape[1]}")

# 寫成 Parquet(給 Polars / pandas / DuckDB 共用)
df_clean.write_parquet("data/bike_rental_clean.parquet", compression="zstd")
print("已寫入 data/bike_rental_clean.parquet")

# 寫進 DuckDB(給後續 SQL 分析用)
con = duckdb.connect("warehouse/de-journey.duckdb")
con.execute("CREATE SCHEMA IF NOT EXISTS clean")
con.execute("CREATE OR REPLACE TABLE clean.bike_rental AS SELECT * FROM df_clean")
print(f"DuckDB clean.bike_rental 筆數:{con.execute('SELECT COUNT(*) FROM clean.bike_rental').fetchone()[0]:,}")
# 輸出:
# 欄位型別:Schema([('rental_id', String), ('station_name', String),
#                   ('start_time', Datetime(time_unit='us')),
#                   ('end_time', Datetime(time_unit='us')),
#                   ('fee', Int64), ('member_id', String),
#                   ('distance_km', Float64)])
# 缺失值統計:shape: (1, 7)
#            rental_id  station_name  start_time  end_time  fee  member_id  distance_km
# count          0             50          0         0     0          0           0
# 筆數:1,000;欄數:7
# 已寫入 data/bike_rental_clean.parquet
# DuckDB clean.bike_rental 筆數:1,000

這段做三件事:印出清洗後的 schema 與缺失值統計、把清洗結果寫成 zstd 壓縮的 Parquet、把清洗結果寫進 DuckDB。con.execute("CREATE OR REPLACE TABLE ... AS SELECT * FROM df_clean") 是 DuckDB 直接讀 Polars DataFrame 的 zero-copy 寫入,這個寫法在 Day 12 學過。

寫進 DuckDB 的 clean.bike_rental 是後續 SQL 分析的「事實來源」。Day 18 政府開放資料實戰會從這個表開始做進一步的 groupby、join、視窗函式;Day 30 的端到端管線會把這段清洗邏輯包成可重複執行的腳本。

Polars vs pandas 在清洗任務上的差異

雖然本篇主要用 Polars 做清洗,pandas 2.3 在某些場景仍有優勢。下表整理兩者在三類清洗任務上的差異:

清洗任務 Polars 1.33 pandas 2.3
缺失值偵測 null_count()、is_null()(原生 NULL) isna()、isnull()(NaN + None)
群組填補 fill_null(mean().over(...)) transform("mean") + fillna()
字串清理 str.strip_chars()、str.replace()、replace_strict() str.strip()、str.replace()、map()
日期解析 str.strptime(...)、str.to_datetime() pd.to_datetime(...)、dt.strftime()

實務上 Polars 在「大量資料清洗」上明顯勝出(5 到 20 倍加速),pandas 在「與既有 Python 套件整合」上仍是首選。一個常見的工作流是「Polars 清洗 → pandas 統計檢定 → DuckDB SQL 彙總」,這正是 Day 12 混用策略的具體應用。實務上我們很少「只用 Polars」或「只用 pandas」,因為兩個工具有各自的 sweet spot;而把清洗、檢定、彙總三段工作分別交給最合適的工具,是資料工程師的基本功。當遇到同事的舊 pandas 程式碼時,先評估「轉成 Polars 的成本 vs. 留下來的維護成本」,不要為了換工具而換工具,這也是工程紀律的一部分。

常見錯誤與踩雷

錯誤一:把空字串當成 NULL 處理。CSV 中的空欄位預設是空字串 "",Polars 與 pandas 都不會自動把它當成 NULL。對應排查方向:讀 CSV 時明確指定 null_values=["", "N/A", "未知"](Polars)或 na_values=["", "N/A"](pandas),把業務上視為「缺失」的所有字串列進去。

錯誤二:用 dropna() 直接刪除所有缺失。dropna() 預設會刪除「任何欄位有缺失」的列,這對 5% 缺失率的欄位可能會刪掉 30% 的資料。對應排查方向:用 dropna(subset=["important_col"]) 指定只刪除「重要欄位缺失」的列。

錯誤三:用全體平均值填補。df["distance_km"].fillna(df["distance_km"].mean()) 用全體平均值填補,忽略了不同站點之間的距離差異。對應排查方向:用 transform(lambda s: s.fillna(s.mean())) 配合 groupby 做「群組平均值填補」。

錯誤四:時區不一致。pd.to_datetime("2024-01-15") 不帶時區、pd.to_datetime("2024-01-15", utc=True) 帶 UTC 時區,兩者在算術運算中可能會出錯。對應排查方向:明確指定 utc=True 或 tz="Asia/Taipei",並用 dt.tz_convert() 做時區轉換。

錯誤五:把清洗邏輯寫死在腳本裡。當清洗邏輯散落在多個 cell 或多個函式,下次接到新資料時要重寫。對應排查方向:把清洗邏輯包成 clean_bike_rental(df: pl.DataFrame) -> pl.DataFrame 的函式,新資料只要呼叫函式即可。Day 14 會把這種「可重現的清洗函式」加上品質檢查。

效能與實務提醒

本篇的清洗範例在 1,000 筆資料上看不出效能差異,但對 100 萬筆以上的資料,Polars 通常比 pandas 快 5 到 10 倍。對「每日自動清洗」的管線(Day 30 會展開),建議把核心清洗邏輯寫成 Polars 程式碼,只有在「必須呼叫既有 Python 套件」時才轉成 pandas。

另一個實務提醒是「清洗規則要寫成資料(data)而非程式碼(code)」。例如「台北 → 臺北」的替換規則,應該寫在 YAML 或 JSON 檔裡,而不是 hardcode 在 Python 腳本中。這樣當規則變動時,只需要更新 YAML 不需要改 Python,維護成本低很多。一個常見做法是把「替換字典」、「預期欄位清單」、「型別 schema」、「業務規則清單」分別寫成 YAML,載入後用來驅動清洗流程。這也是 Day 14 品質檢查的基礎:規則檔與程式碼分離,方便非工程師(例如業務分析師)也能維護規則。

清洗流程的最後一步是「寫回來源檔」。實務上我們不會直接覆寫原始 CSV(保留原始檔以利除錯),而是寫一份「_clean.parquet」的新檔,並在檔名或 metadata 中標明「這是清洗後的版本」。DuckDB 的 clean schema 就是為了這個目的:原始資料放 raw、清洗後放 clean、彙總結果放 mart,三層架構清楚分明。這樣的命名規約讓接手的人一眼就能看出「這份資料是哪一層、可以信賴到什麼程度」,是資料工程師對同事最實用的體貼。

小結

今天把資料清洗實戰走了一遍。我們生成了 1,000 筆含有缺失值、型別錯誤、字串格式不一致的合成腳踏車租借資料,用 Polars 1.33 完成了「缺失值偵測與填補」、「型別轉換」、「字串清理」三類清洗任務,最後把清洗結果寫成 Parquet 與寫進 DuckDB 的 clean.bike_rental 表。實務上 Polars 在大量資料清洗上比 pandas 快很多、Expression chain 比傳統的 for 迴圈簡潔很多,是資料工程的主力清洗工具。

結語

今天的重點是「資料清洗的三類常見任務」。我們從生成髒資料開始、用 Polars 完成缺失值填補、型別轉換、字串清理,最後驗證並落地到 DuckDB。讀完這篇你應該能回答:缺失值有哪些處理策略?Polars 與 pandas 在字串處理上有什麼差異?日期與時間的常見雷點有哪些?清洗流程要怎麼組織才能讓別人接手?這些問題的答案都藏在本篇的程式碼與文字裡。

明天,我們會進入資料品質檢查:規則設計與自動化。今天的清洗讓資料「乾淨」,明天的品質檢查則是用「規則」驗證資料「合理」。我們會用 Pandera 或 Great Expectations 設計幾條品質規則、把昨天的 clean.bike_rental 跑一次檢查,並把結果寫成品質報表。

延伸資源

  • Polars 字串處理官方文件(1.33,2025):https://pola.rs/ 的 str namespace 章節。
  • pandas 缺失值處理官方文件(2.3,2025):https://pandas.pydata.org/docs/ 的 missing_data 章節。
  • DuckDB 型別系統說明(1.4,2025):https://duckdb.org/docs/sql/data_types/overview。
  • 行政院主計總處「政府資料開放授權條款第 1 版」:https://data.gov.tw/license,本系列使用的真實公開資料皆依此授權。

留言

這個網誌中的熱門文章

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