在資料科學領域,Pandas 與資料函式庫的互動至關重要。本文將探討如何利用 SQLAlchemy 和 ADBC 銜接 Pandas 與資料函式庫,進行高效的資料讀寫操作。同時,我們將比較兩者的效能差異,並介紹如何使用 Apache Parquet 作為通用的高效儲存格式。此外,本文也將深入 pandas 的 I/O 系統,講解如何處理 Parquet 分割槽和 JSON 資料。

Pandas 提供了便捷的 to_sqlread_sql 方法,方便與資料函式庫互動。SQLAlchemy 作為一個成熟的 ORM 框架,提供了更高階的抽象和功能。而 ADBC 則更著重於效能,尤其在處理大型資料集時更具優勢。Apache Parquet 作為一種列式儲存格式,能夠有效壓縮資料並提升查詢效率,非常適合儲存 Pandas DataFrame。此外,Parquet 支援分割槽和中繼資料儲存,方便資料管理。在 pandas 中,使用 read_parquetto_parquet 可以輕鬆讀寫 Parquet 檔案。對於 JSON 資料,pandas 也提供了 read_jsonto_json 方法,方便資料交換。

使用SQLAlchemy與ADBC進行資料函式庫操作

本章節將介紹如何使用SQLAlchemy和ADBC這兩種不同的技術來與資料函式庫進行互動,包括資料的讀取和寫入。

使用SQLAlchemy進行資料函式庫操作

SQLAlchemy是一種流行的Python SQL工具包和物件關係對映(ORM)系統,能夠提供高層次的SQL抽象和資料函式庫互動功能。首先,需要安裝SQLAlchemy:

python -m pip install sqlalchemy

建立資料函式庫引擎

首先,需要建立一個SQLAlchemy引擎。引擎是用於與資料函式庫進行互動的關鍵物件。對於SQLite資料函式庫,可以使用以下程式碼建立引擎:

import sqlalchemy as sa
engine = sa.create_engine("sqlite:///:memory:")

將DataFrame寫入資料函式庫

使用pd.DataFrame.to_sql方法,可以將一個DataFrame寫入資料函式庫表:

import pandas as pd

df = pd.DataFrame([
    ["dog", 4],
    ["cat", 4],
], columns=["animal", "num_legs"])
df = df.convert_dtypes(dtype_backend="numpy_nullable")
df.to_sql("table_name", engine, index=False)

從資料函式庫讀取資料

使用pd.read_sql函式,可以從資料函式庫表中讀取資料:

pd.read_sql("table_name", engine, dtype_backend="numpy_nullable")

輸出結果:

  animal  num_legs
0     dog         4
1     cat         4

也可以使用SQL查詢陳述式來讀取資料:

pd.read_sql(
    "SELECT SUM(num_legs) AS total_legs FROM table_name",
    engine,
    dtype_backend="numpy_nullable"
)

輸出結果:

   total_legs
0           8

處理已存在的表格

如果嘗試寫入一個已經存在的表格,將會引發錯誤。可以使用if_exists="replace"引數來取代現有的表格:

df = pd.DataFrame([
    ["dog", 4],
    ["cat", 4],
    ["human", 2],
], columns=["animal", "num_legs"])
df = df.convert_dtypes(dtype_backend="numpy_nullable")
df.to_sql("table_name", engine, index=False, if_exists="replace")

或者使用if_exists="append"引數來追加資料到現有的表格:

new_data = pd.DataFrame([["centipede", 100]], columns=["animal", "num_legs"])
new_data.to_sql("table_name", engine, index=False, if_exists="append")

內容解密:

  1. 建立SQLAlchemy引擎:使用sa.create_engine函式建立一個與SQLite資料函式庫連線的引擎。
  2. 寫入DataFrame到資料函式庫:使用pd.DataFrame.to_sql方法將DataFrame寫入指定的資料函式庫表格。
  3. 從資料函式庫讀取資料:使用pd.read_sql函式從指定的資料函式庫表格或執行SQL查詢陳述式讀取資料。
  4. 處理已存在的表格:使用if_exists引數控制寫入操作的行為,可以選擇取代或追加到現有的表格。

使用ADBC進行資料函式庫操作

ADBC(Arrow Database Connectivity)是Apache Arrow專案的一部分,提供了一種新的、更高效的與資料函式庫互動的方式。首先,需要安裝ADBC驅動程式:

python -m pip install adbc-driver-sqlite

連線到資料函式庫

使用ADBC驅動程式連線到SQLite資料函式庫:

from adbc_driver_sqlite import dbapi
with dbapi.connect("file::memory:") as conn:
    # 資料函式庫操作
    pass

將DataFrame寫入資料函式庫和從資料函式庫讀取資料

與SQLAlchemy類別似,可以使用pd.DataFrame.to_sqlpd.read_sql進行資料的寫入和讀取:

df = pd.DataFrame([
    ["dog", 4],
    ["cat", 4],
    ["human", 2],
], columns=["animal", "num_legs"])
df = df.convert_dtypes(dtype_backend="numpy_nullable")

with dbapi.connect("file::memory:") as conn:
    df.to_sql("table_name", conn, index=False, if_exists="replace")
    df_read = pd.read_sql("SELECT * FROM table_name", conn, dtype_backend="numpy_nullable")
    print(df_read)

輸出結果:

  animal  num_legs
0     dog         4
1     cat         4
2   human         2

效能比較

比較使用SQLAlchemy和ADBC寫入大型DataFrame的效能:

import timeit
import numpy as np
import pandas as pd
import sqlalchemy as sa
from adbc_driver_sqlite import dbapi

np.random.seed(42)
df = pd.DataFrame(np.random.randn(10_000, 10), columns=list("abcdefghij"))

# SQLAlchemy效能測試
with sa.create_engine("sqlite:///:memory:").connect() as conn:
    func = lambda: df.to_sql("test_table", conn, if_exists="replace")
    print(timeit.timeit(func, number=100))

# ADBC效能測試
with dbapi.connect("file::memory:") as conn:
    func = lambda: df.to_sql("test_table", conn, if_exists="replace")
    print(timeit.timeit(func, number=100))

一般來說,ADBC在處理大型資料集時表現出更好的效能。

圖表翻譯:

此圖示展示了SQLAlchemy與ADBC在寫入大型DataFrame時的效能比較,清楚地表明瞭ADBC在處理大量資料時的優勢。

內容解密:

  1. 連線到資料函式庫:使用ADBC驅動程式的dbapi.connect方法建立與SQLite資料函式庫的連線。
  2. 寫入和讀取DataFrame:使用pd.DataFrame.to_sqlpd.read_sql進行資料的寫入和讀取操作。
  3. 效能比較:透過時間測試比較了SQLAlchemy和ADBC在寫入大型DataFrame時的效能,表明了ADBC在處理大量資料時的效率優勢。

綜上所述,SQLAlchemy和ADBC都是強大的工具,用於與資料函式庫進行互動。選擇哪一種取決於具體的需求和資料規模。對於大多數應用,SQLAlchemy提供了豐富的功能和廣泛的資料函式庫支援,而ADBC則在處理大型資料集時提供了更好的效能。

資料函式庫連線標準的演進與ADBC的重要性

在瞭解ADBC的作用和重要性之前,先來回顧一下資料函式庫標準的歷史。1990年代,開放資料函式庫連線(Open Database Connectivity, ODBC)和Java資料函式庫連線(Java Database Connectivity, JDBC)標準的出現,簡化了不同客戶端與各種資料函式庫之間的互動。在這些標準出現之前,若應用程式需要與多個資料函式庫合作,必須使用每個資料函式庫特定的語言進行溝通。

資料函式庫互動的挑戰

想像一個應用程式需要取得不同資料函式庫中的表格列表。PostgreSQL資料函式庫將此資訊儲存在pg_catalog.pg_tables表格中,而SQLite資料函式庫則儲存在sqlite_schema表格中,條件是type='table'。應用程式需要根據這些特定資訊進行開發,並且每當資料函式庫變更儲存資訊的方式或應用程式需要支援新的資料函式庫時,都需要進行更新。

ODBC/JDBC標準的優勢

有了像ODBC這樣的標準後,應用程式只需與驅動程式溝通,讓驅動程式知道它需要系統中的所有表格。這樣就將與資料函式庫正確互動的責任從應用程式本身轉移到了驅動程式上,為應用程式提供了一層抽象。當新的資料函式庫或版本釋出時,應用程式本身無需更改;它只需與新的ODBC/JDBC驅動程式合作即可繼續運作。SQLAlchemy正是這樣一個應用程式,它透過ODBC/JDBC驅動程式與資料函式庫互動,而不是試圖自行管理無數的資料函式庫互動。

ADBC的出現

雖然ODBC/JDBC標準對於許多用途來說非常出色,但值得注意的是,1990年代的資料函式庫與現在的資料函式庫有很大不同。當時的標準主要針對行導向的資料函式庫,而這種資料函式庫在當時非常普遍。列導向的資料函式庫在十多年後才出現,並且已經在分析領域佔據了主導地位。不幸的是,由於缺乏針對列導向資料的傳輸標準,許多這些資料函式庫不得不改造設計以相容ODBC/JDBC。這使得它們能夠與現有的無數資料函式庫客戶端工具協同工作,但需要在效能上做出妥協。ADBC是解決這個問題的列導向規範。

ADBC的優勢

對於像BigQuery、Redshift或Snowflake這樣的列導向資料函式庫,使用列導向的驅動程式來交換資訊可以帶來數量級的效能提升。即使不與列導向資料函式庫互動,ADBC驅動程式針對Apache Arrow進行了高度最佳化,因此相比SQLAlchemy使用的任何ODBC/JDBC驅動程式,仍然是一個升級。

使用Apache Parquet作為通用儲存格式

就通用儲存格式而言,Apache Parquet是儲存pd.DataFrame的最佳選擇。Apache Parquet允許:

  • 儲存中繼資料:能夠追蹤資料型別等功能
  • 分割槽:不必將所有內容儲存在一個檔案中
  • 查詢支援:可以在磁碟上查詢Parquet檔案,無需將所有資料載入記憶體
  • 平行化:可以平行讀取資料以提高吞吐量
  • 緊湊性:資料被壓縮並以高效的方式儲存

除非您正在使用舊系統,否則在工作流程中應該用Apache Parquet格式取代CSV檔案,用於本地持久化資料、與團隊成員共用以及跨系統交換資料。

如何使用Apache Parquet

讀寫Apache Parquet的API與我們迄今為止所見的所有其他pandas API一致;對於讀取,有pd.read_parquet,對於寫入,有pd.DataFrame.to_parquet方法。

示例程式碼

import io
import pandas as pd

# 建立範例DataFrame
df = pd.DataFrame([
    ["Paul", "McCartney", 1942],
    ["John", "Lennon", 1940],
    ["Richard", "Starkey", 1940],
    ["George", "Harrison", 1943],
], columns=["first", "last", "birth"])

# 轉換為適當的資料型別
df = df.convert_dtypes(dtype_backend="numpy_nullable")

# 將DataFrame寫入BytesIO緩衝區
buf = io.BytesIO()
df.to_parquet(buf, index=False)

# 從BytesIO緩衝區讀取Parquet資料
buf.seek(0)
read_df = pd.read_parquet(buf)

print(read_df)

內容解密:

  1. 建立範例DataFrame:首先,我們建立了一個包含Beatles成員資訊的DataFrame。
  2. 轉換資料型別:使用convert_dtypes方法將DataFrame的資料型別轉換為適當的型別,以支援nullable整數等功能。
  3. 寫入Parquet:將DataFrame寫入io.BytesIO緩衝區中,使用to_parquet方法,並設定index=False以避免儲存索引。
  4. 讀取Parquet:將緩衝區的位置重置為0,然後使用pd.read_parquet方法讀取Parquet資料,得到新的DataFrame。
  5. 結果驗證:列印讀取的DataFrame以驗證其內容。

為什麼不需要dtype_backend引數?

與CSV格式不同,Apache Parquet格式不僅儲存資料,還儲存中繼資料。在中繼資料中,Apache Parquet能夠追蹤所使用的資料型別,因此無論您寫入什麼資料型別,都應該準確地取回相同的型別。

圖表翻譯:

此圖示展示了使用Apache Parquet格式儲存和讀取資料的流程。首先,建立一個包含資料的DataFrame,接著將其寫入Parquet格式並儲存在緩衝區中。然後,從緩衝區中讀取Parquet資料,並將其轉換回DataFrame。整個過程展示了Apache Parquet在資料儲存和檢索中的高效性和便利性。

  graph LR;
    A[建立DataFrame] --> B[寫入Parquet];
    B --> C[儲存在BytesIO緩衝區];
    C --> D[重置緩衝區位置];
    D --> E[讀取Parquet];
    E --> F[轉換為DataFrame];

圖表翻譯: 此圖示詳細展示了從建立DataFrame到最終讀取Parquet資料的全過程。每一步驟清晰地呈現了資料處理和轉換的流程,有助於理解使用Apache Parquet進行資料儲存和檢索的工作原理。

pandas I/O 系統詳解

pandas 提供了強大的 I/O 功能,能夠處理多種資料格式,包括 Apache Parquet 和 JSON。這些功能使得資料的讀取和寫入變得高效且方便。

Apache Parquet 格式的優勢

Apache Parquet 是一種高效的資料儲存格式,支援分割槽(partitioning),使得資料可以分散儲存在不同的目錄和檔案中。這種特性使得資料的管理和查詢變得更加容易。

分割槽範例

假設我們有以下的資料結構,每個年份的資料儲存在不同的目錄中,而每個季度的資料儲存在個別的檔案中:

2022/
q1_sales.parquet
q2_sales.parquet
2023/
q1_sales.parquet
q2_sales.parquet

每個 Parquet 檔案中包含了年份、季度、地區和銷售額等資訊。

讀取 Parquet 檔案

使用 pd.read_parquet 可以輕鬆讀取 Parquet 檔案。如果我們想要讀取所有分割槽的資料,可以直接傳遞目錄路徑給 pd.read_parquet

pd.read_parquet("data/partitions/")

篩選資料

為了避免讀取過多的資料,我們可以使用 filters 引數來篩選資料。例如,如果我們只想要讀取 region 為 “Europe” 的資料:

pd.read_parquet(
    "data/partitions/",
    filters=[("region", "==", "Europe")],
)

JSON 資料處理

JSON(JavaScript Object Notation)是一種常見的資料交換格式。Python 的標準函式庫中包含了 json 模組,可以用來序列化和反序列化 Python 物件。

使用 pandas 處理 JSON 資料

pandas 提供了 pd.read_jsonpd.DataFrame.to_json 方法來讀取和寫入 JSON 資料。

讀取 JSON 資料範例
import io
data = io.StringIO(serialized)
pd.read_json(data, dtype_backend="numpy_nullable")
寫入 JSON 資料範例
df = pd.DataFrame(beatles)
print(df.to_json())

程式碼詳解:

在上述範例中,我們使用了 io.StringIO 將字串轉換為檔案物件,以便傳遞給 pd.read_jsondtype_backend="numpy_nullable" 引數用於指定資料型別的處理方式。

在寫入 JSON 資料時,pd.DataFrame.to_json 方法將 DataFrame 物件轉換為 JSON 字串。

資料處理流程

  graph LR;
    A[原始資料] -->|序列化|> B(JSON 資料);
    B -->|反序列化|> C[pandas DataFrame];
    C -->|處理|> D[結果];
    D -->|輸出|> E[JSON/Parquet 檔案];

圖表翻譯: 此圖示呈現了資料從原始格式到最終輸出的處理流程。首先,原始資料被序列化為 JSON 格式。接著,JSON 資料被反序列化為 pandas DataFrame。然後,DataFrame 中的資料被處理,並最終輸出為 JSON 或 Parquet 檔案。