Polars 在資料分析領域以其優異的效能和效率聞名,然而許多開發者仍然偏好使用 SQL 進行資料操作。DuckDB 正好彌補了這個缺口,允許我們直接使用 SQL 查詢 Polars DataFrame,兼顧效能和易用性。

DuckDB 藉由 Apache Arrow 提供了對 Polars DataFrame 的支援。Apache Arrow 是一個專為記憶體內分析設計的開發平台,它提供了一系列技術,讓大資料系統能夠快速儲存、處理和行動資料。PyArrow 則是 Arrow 的 Python 實作,讓 Python 開發者也能享受到 Arrow 的高效能。安裝 PyArrow 後,就能夠在 Polars 中使用 DuckDB 進行 SQL 查詢。實際操作上,我們可以使用 duckdb 模組中的 sql() 函式,直接將 SQL 查詢語法套用在 Polars DataFrame 上。這個函式會回傳一個 DuckDBPyRelation 物件,它代表一個可以使用 DuckDB Relational API 進行查詢的關係。除了直接執行 SQL 查詢外,DuckDBPyRelation 物件也提供了一些便捷的方法,例如 describe() 可以快速產生 DataFrame 的統計資訊,order() 可以排序資料,apply() 可以對特定欄位應用函式。然而,對於複雜的資料操作,SQL 語法仍然更具彈性與易於理解。例如,排序多個欄位、分組統計等操作,使用 SQL 可以更簡潔地表達。此外,Polars 的 Lazy Evaluation 特性也能與 DuckDB 整合,進一步提升效能。透過 lazy() 方法,可以將 Polars DataFrame 轉換為 LazyFrame,讓後續的 SQL 查詢操作延遲執行,並在 collect() 方法被呼叫時才真正執行,從而避免不必要的計算,尤其在處理大型資料集時效果更為顯著。

Polars DataFrame 的 SQL 魔法:解鎖資料查詢新境界

在資料分析的浩瀚世界中,Polars 以其卓越的效能和簡潔的語法脫穎而出。除了 Polars 提供的多種 DataFrame 操作方法外,你是否知道還能使用 SQL 直接查詢 Polars DataFrame 呢?身為玄貓,我將帶領大家探索 Polars 的 SQLContext,體驗 SQL 與 DataFrame 結合的強大威力。

SQLContext:讓 Polars 聽懂你的 SQL 語言

Polars 的 SQLContext 允許你使用熟悉的 SQL 語法,針對 Polars DataFrames 執行查詢。這對於習慣 SQL 的資料科學家來說,無疑是一大福音。

以下是如何使用 SQLContext 的範例:

import polars as pl

# 假設你已經有一個 Polars DataFrame 叫做 df
# 建立 SQLContext,並將 DataFrame 命名為 'cars'
ctx = pl.SQLContext(cars=df)

# 執行 SQL 查詢
result = ctx.execute("SELECT * FROM cars", eager=True)

# 顯示結果
print(result)

在這個例子中,我們首先建立一個 SQLContext,並將名為 df 的 DataFrame 註冊為 cars。接著,我們使用 ctx.execute() 方法執行 SQL 查詢,並設定 eager=True 以立即取得結果。

進階 SQL 查詢:資料分析更上一層樓

SQLContext 不僅支援簡單的 SELECT * 查詢,還能執行更複雜的 SQL 語法,例如聚合、分組等。

以下範例展示如何使用 SQL 查詢,找出每個公司的平均最小和最大引擎容量:

result = ctx.execute(
    """
    SELECT Company,
           AVG(Engine_Min) AS avg_engine_min,
           AVG(Engine_Max) AS avg_engine_max
    FROM cars
    GROUP BY Company;
    """,
    eager=True,
)

print(result)

這個查詢使用 AVG() 函式計算平均值,並使用 GROUP BY 子句將結果按照公司分組。透過 SQLContext,你可以輕鬆地在 Polars DataFrame 上執行各種複雜的資料分析任務。

Polars 的殺手級特性:Lazy Evaluation 延遲評估

Polars 最引人入勝的特性之一,就是它對延遲評估 (Lazy Evaluation) 的支援。延遲評估是一種最佳化技術,它允許 Polars 建構查詢計畫,表示一系列操作,但並非立即執行這些操作。相反地,只有在明確要求最終結果時,才會執行這些操作。這種方法在處理大型資料集或複雜轉換時非常有效,因為它可以避免不必要的計算。

延遲評估 vs. 立即評估:效能大比拚

為了理解延遲評估的重要性,讓玄貓帶你瞭解 pandas 的運作方式。在 pandas 中,你通常使用 read_csv() 函式將 CSV 檔案讀取到 pandas DataFrame 中:

import pandas as pd

df = pd.read_csv("flights.csv")
print(df)

如果你的 CSV 檔案很大,那麼將所有資料列載入到 pandas DataFrame 中會花費大量時間和記憶體。flights.csv 檔案包含超過 580 萬列資料,因此將整個檔案載入記憶體需要大量的資源。

一個典型的 pandas 操作是將 CSV 檔案載入到 DataFrame 中,然後對其執行一些篩選:

df = pd.read_csv('flights.csv')
df = df[(df['MONTH'] == 5) &
        (df['ORIGIN_AIRPORT'] == 'SFO') &
        (df['DESTINATION_AIRPORT'] == 'SEA')]
print(df)

這種做法效率低下,因為你必須將整個 CSV 檔案載入記憶體,才能篩選出其中的一個子集。在 Polars 中,有一種更有效率的 DataFrame 載入方式,稱為延遲評估。

兩種延遲評估方式:隱式與顯式

Polars 支援兩種延遲評估方式:

  • 隱式延遲評估 (Implicit lazy evaluation)

    使用本質上支援延遲評估的函式 (例如 scan_csv() 函式)。

  • 顯式延遲評估 (Explicit lazy evaluation)

    使用本質上不支援延遲評估的函式 (例如 read_csv() 函式),並明確地讓它們使用延遲評估。

接下來,玄貓將探討這兩種方式。

隱式延遲評估:Polars 的預設最佳化

為了理解延遲評估的運作方式,讓玄貓帶你走過一個範例。這次我們不使用 read_csv() 函式,而是使用 scan_csv() 函式:

import polars as pl

q = pl.scan_csv("flights.csv")
print(type(q))

scan_csv() 函式會傳回一個 polars.lazyframe.frame.LazyFrame 型別的物件,它代表針對 DataFrame 的延遲計算圖/查詢。簡單來說,當你使用 scan_csv() 函式載入 CSV 檔案時,CSV 檔案的內容不會立即載入。相反地,該函式會等待進一步的查詢,以便在載入 CSV 檔案的內容之前,最佳化整個查詢集。

將此與使用 read_csv() 函式在 Polars 中載入 CSV 檔案進行比較:

df = pl.read_csv('flights.csv')
print(type(df))

read_csv() 函式會傳回一個 polars.dataframe.frame.DataFrame 物件,這與 pandas DataFrame 類別似。然而,與 scan_csv() 方法不同的是,read_csv() 方法使用立即執行模式,這表示它會立即將整個資料集載入到 DataFrame 中,然後才能執行任何其他查詢。

LazyFrame 的查詢最佳化:效能提升的關鍵

獲得 LazyFrame 物件後,你可以將查詢應用於它:

q = pl.scan_csv('flights.csv')
q = q.select(['MONTH', 'ORIGIN_AIRPORT', 'DESTINATION_AIRPORT'])
q = q.filter(
    (pl.col('MONTH') == 5)
    & (pl.col('ORIGIN_AIRPORT') == 'SFO')
    & (pl.col('DESTINATION_AIRPORT') == 'SEA')
)

select()filter() 方法適用於 Polars DataFrames 以及 LazyFrame 物件。

為了提高可讀性,理想情況下,玄貓建議使用一對括號將 Polars 中的各種方法串聯起來:

q = (
    pl.scan_csv('flights.csv')
    .select(['MONTH', 'ORIGIN_AIRPORT', 'DESTINATION_AIRPORT'])
    .filter(
        (pl.col('MONTH') == 5)
        & (pl.col('ORIGIN_AIRPORT') == 'SFO')
        & (pl.col('DESTINATION_AIRPORT') == 'SEA')
    )
)

查詢計畫視覺化:show_graph() 的妙用

你可以呼叫 show_graph() 方法來顯示執行圖:

q.show_graph(optimized=True)

執行圖會顯示查詢的執行流程。你可以看到它首先掃描 CSV 檔案 (圖表的頂部),然後執行篩選 (圖表的底部)。

相反地,如果你呼叫 show_graph() 方法,並將 optimized 引數設定為 False,你將會看到它掃描 CSV 檔案,載入所有 31 個欄位,然後才逐一執行篩選:

q.show_graph(optimized=False)

預設情況下,show_graph() 會以其最佳化格式列印出查詢。但是,如果你列印出 q 物件,它會以非最佳化模式顯示圖表。

執行查詢:collect() 方法

若要執行查詢,請呼叫 collect() 方法:

q.collect()

collect() 方法會以 Polars DataFrame 的形式傳回查詢的結果。

透過 SQLContext 和 Lazy Evaluation,Polars 為資料分析帶來了前所未有的效能和靈活性。身為玄貓,我鼓勵大家深入探索 Polars 的更多特性,解鎖資料分析的無限可能。

總結來說,Polars 透過 SQLContext 整合了 SQL 查詢的便利性,同時利用 Lazy Evaluation 實作高效能的資料處理。無論是習慣 SQL 語法的資料科學家,還是追求極致效能的開發者,都能在 Polars 中找到理想的解決方案。玄貓認為,Polars 無疑是現代資料分析工具箱中不可或缺的一員。

Polars 與 DuckDB 的完美結合:資料分析的全新境界

在資料分析的領域中,Polars 以其卓越的效能和效率著稱。但對許多開發者來說,SQL 仍然是最熟悉與直觀的資料操作語言。那麼,是否能將兩者的優勢結合,讓資料處理既快速又簡單呢?答案是肯定的。透過 DuckDB,我們可以利用 SQL 直接查詢 Polars DataFrame,實作兩全其美的資料分析體驗。

告別繁瑣:Polars DataFrame 的 SQL 查詢之路

儘管 Polars 提供了豐富的 API,但對於習慣 SQL 的開發者來說,操作 DataFrame 有時仍顯得有些吃力。DuckDB 透過 Apache Arrow 支援 Polars DataFrame,讓開發者可以使用熟悉的 SQL 語法直接查詢資料,無需額外學習成本。

Apache Arrow 是一個專為記憶體分析設計的開發平台,它提供了一系列技術,使大資料系統能夠快速儲存、處理和行動資料。PyArrow 則是 Arrow 的 Python 實作。

sql() 函式:開啟 SQL 查詢 Polars DataFrame 的大門

讓我們先建立一個 Polars DataFrame:

import polars as pl

df = pl.DataFrame(
    {
        'Model': ['Camry', 'Corolla', 'RAV4',
                  'Mustang', 'F-150', 'Escape',
                  'Golf', 'Tiguan'],
        'Year': [1982, 1966, 1994, 1964, 1975, 2000, 1974, 2007],
        'Engine_Min': [2.5, 1.8, 2.0, 2.3, 2.7, 1.5, 1.0, 1.4],
        'Engine_Max': [3.5, 2.0, 2.5, 5.0, 5.0, 2.5, 2.0, 2.0],
        'AWD': [False, False, True, False, True, True, True, True],
        'Company': ['Toyota', 'Toyota', 'Toyota', 'Ford',
                    'Ford', 'Ford', 'Volkswagen', 'Volkswagen'],
    }
)
print(df)

為了使用 DuckDB 查詢 Polars DataFrame,需要先安裝 PyArrow 函式庫:

pip install pyarrow

安裝完成後,重新啟動 Jupyter Notebook 的 Kernel。

現在,可以使用 duckdb 模組中的 sql() 函式來查詢 DataFrame:

import duckdb

result = duckdb.sql('''
SELECT *
FROM df
''')
print(result)

sql() 函式會回傳一個 duckdb.DuckDBPyRelation 物件,它代表一個可以使用 DuckDB Relational API 進行查詢的關係。

DuckDBPyRelation:資料操作的利器

DuckDBPyRelation 物件提供多種方法來操作資料。例如,可以使用 describe() 方法來產生 DataFrame 中每個欄位的基本統計資訊:

print(result.describe())

describe() 方法的回傳值也是一個 DuckDBPyRelation 物件,可以根據需要轉換為 Polars 或 pandas DataFrame。

此外,可以使用 order() 方法對結果進行排序:

print(result.order('Year'))

上述範例會依年份遞增排序。若要依年份遞減排序,可以使用 DESC 關鍵字:

print(result.order('Year DESC'))

apply() 方法則可以將函式應用於特定欄位,例如取得 Year 欄位的最小值:

print(result.apply('min', 'Year'))

SQL 的力量:更直觀的資料操作

雖然 DuckDBPyRelation 物件提供了多種方法來提取資料,但在某些情況下,使用 SQL 可以更輕鬆地完成相同的任務。例如,若要依公司和型號對資料進行排序,使用 SQL 會更為直觀:

print(duckdb.sql('''
SELECT Company, Model
FROM df
ORDER by Company, Model
''').pl())

或者,若要計算每個公司的車款數量,可以使用 SQL 的 GROUP BY 語法:

print(duckdb.sql('''
SELECT Company, count(Model) as count
FROM df
GROUP BY Company
''').pl())

Lazy evaluation 的優勢

在先前的章節中,玄貓曾提到使用 read_csv() 函式讀取 CSV 檔案時,Polars 會使用 eager execution,立即載入 DataFrame。考慮以下程式碼片段:

import polars as pl

df = (
    pl.read_csv('flights.csv')
    .select(['MONTH', 'ORIGIN_AIRPORT', 'DESTINATION_AIRPORT'])
    .filter(
        (pl.col('MONTH') == 5) &
        (pl.col('ORIGIN_AIRPORT') == 'SFO') &
        (pl.col('DESTINATION_AIRPORT') == 'SEA'))
)
print(df)

在這個例子中,載入 CSV 檔案後,會依序執行欄位選擇和列篩選。這是因為 read_csv() 函式預設不支援 lazy evaluation。

為了確保 CSV 檔案載入後的查詢可以被最佳化,可以使用 lazy() 方法,明確指定 read_csv() 函式使用 lazy evaluation:

import polars as pl

q = (
    pl.read_csv('flights.csv')
    .lazy()
    .select(['MONTH', 'ORIGIN_AIRPORT', 'DESTINATION_AIRPORT'])
    .filter(
        (pl.col('MONTH') == 5) &
        (pl.col('ORIGIN_AIRPORT') == 'SFO') &
        (pl.col('DESTINATION_AIRPORT') == 'SEA'))
)
df = q.collect()
print(df)

lazy() 函式會回傳一個 LazyFrame 物件,可以使用 select()filter() 等方法鏈式呼叫。所有查詢都會在執行前進行最佳化。