返回文章列表

Pandas 單元測試與資料驗證最佳實踐

本文介紹如何使用 Pandas 進行單元測試的最佳實踐,包含 Series 相等性測試、失敗案例分析以及 pytest 測試框架的應用。此外,文章也探討了 Pandas 生態系統中的 NumPy 與 PyArrow,並示範如何利用 YData Profiling 和 Great Expectations

資料科學 軟體測試

Pandas 在資料處理中扮演著關鍵角色,而確保程式碼正確性則需要倚賴單元測試。使用 tm.assert_series_equal 可以有效比較 Pandas Series,處理索引、值和缺失值等細節。pytest 框架則提供更靈活的測試方式。理解 Pandas 生態系統中的 NumPy 和 PyArrow 有助於深入底層機制。YData Profiling 能夠自動生成資料報告,快速掌握資料集特徵。Great Expectations 則提供資料驗證功能,確保資料品質符合預期。這些工具和技術共同構成了穩健的資料處理流程,提升程式碼可靠性和資料分析效率。

使用 Pandas 進行單元測試的最佳實踐

在進行資料分析或處理時,Pandas 是一個非常有用的工具。然而,當我們需要確保程式碼的正確性時,單元測試就變得至關重要。本文將介紹如何使用 Pandas 進行單元測試,並提供一些最佳實踐。

測試 Pandas Series 的相等性

當我們需要測試兩個 Pandas Series 是否相等時,不能直接使用 assertEqual 方法。這是因為 Pandas 會對 == 運算元進行過載,傳回一個逐元素比較的結果,而不是一個簡單的布林值。

import pandas as pd
import pandas.testing as tm
import unittest

def some_cool_numbers():
    return pd.Series([42, 555, pd.NA], dtype=pd.Int64Dtype())

class MyTests(unittest.TestCase):
    def test_cool_numbers(self):
        result = some_cool_numbers()
        expected = pd.Series([42, 555, pd.NA], dtype=pd.Int64Dtype())
        tm.assert_series_equal(result, expected)

def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTests("test_cool_numbers"))
    return suite

runner = unittest.TextTestRunner()
runner.run(suite())

內容解密:

  • tm.assert_series_equal 是用來比較兩個 Pandas Series 是否相等的函式。
  • 它可以處理不同的索引、值和缺失值語義,並提供詳細的錯誤報告。
  • 在這個例子中,我們使用 tm.assert_series_equal 來測試 some_cool_numbers 函式的傳回值是否符合預期。

測試失敗的案例

讓我們故意製造一個測試失敗的案例,以觀察錯誤報告的內容。

def some_cool_numbers():
    return pd.Series([42, 555, pd.NA], dtype=pd.Int64Dtype())

class MyTests(unittest.TestCase):
    def test_cool_numbers(self):
        result = some_cool_numbers()
        expected = pd.Series([42, 555, pd.NA], dtype=pd.Int32Dtype())
        tm.assert_series_equal(result, expected)

def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTests("test_cool_numbers"))
    return suite

runner = unittest.TextTestRunner()
runner.run(suite())

內容解密:

  • 在這個例子中,我們故意將預期的資料型別設為 pd.Int32Dtype(),而實際傳回的是 pd.Int64Dtype()
  • tm.assert_series_equal 正確地報告了資料型別的不匹配,並提供了詳細的錯誤訊息。

使用 pytest 進行單元測試

雖然本文使用了 unittest 模組,但許多大型 Python 專案使用 pytest 函式庫來編寫和執行單元測試。pytest 提供了一個根據測試夾具的方法,而不是類別為基礎的測試結構。

圖表翻譯: 此圖示展示了使用 pytest 進行單元測試的基本流程。從開始測試到定義測試函式,最後執行測試,每一步都清晰地標示出來。

pandas 生態系統

pandas 函式庫提供了令人印象深刻的功能,但其受歡迎程度很大程度上歸功於大量與之相輔相成的第三方函式庫。我們無法在本章涵蓋所有這些函式庫,甚至無法探討任何一個函式庫的工作原理。然而,瞭解這些工具的存在和它們提供的功能,可以為未來的學習提供很大的啟發。

基礎函式庫

像許多開源函式庫一樣,pandas 在其他基礎函式庫之上構建功能,讓它們管理底層細節,而 pandas 提供更友好的功能。如果你想深入瞭解技術細節,超越了使用 pandas 所學到的知識,那麼這些是你需要關注的函式庫。

NumPy

NumPy 將自己標榜為 Python 科學計算的基礎套件,它是 pandas 最初構建的基礎。NumPy 實際上是一個 n 維函式庫,因此你不受限於像 pd.DataFrame 這樣的二維資料(pandas 曾經提供過 3 維和 4 維的面板結構,但現在已經不存在了)。

import numpy as np
import pandas as pd

# 從 NumPy 陣列建立 DataFrame
arr = np.arange(1, 10).reshape(3, -1)
df = pd.DataFrame(arr)
print(df)

內容解密:

  • np.arange(1, 10) 生成一個從 1 到 9 的一維陣列。
  • .reshape(3, -1) 將一維陣列轉換為 3x3 的二維陣列。
  • pd.DataFrame(arr) 將 NumPy 陣列轉換為 pandas DataFrame。
# 將 DataFrame 轉換為 NumPy 陣列
print(df.to_numpy())

內容解密:

  • df.to_numpy() 將 DataFrame 轉換為 NumPy 陣列。

許多 NumPy 函式接受 pd.DataFrame 作為引數,甚至仍然傳回 pd.DataFrame

# 使用 NumPy 函式處理 DataFrame
print(np.log(df))

內容解密:

  • np.log(df) 對 DataFrame 中的每個元素計算自然對數。

PyArrow

另一個 pandas 所依賴的主要函式庫是 Apache Arrow,它將自己標榜為記憶體分析的跨語言開發平台。由 pandas 的建立者 Wes McKinney 發起,Apache Arrow 專案定義了一維資料結構的記憶體佈局,使得不同的語言、程式和函式庫能夠使用相同的資料。

import pyarrow as pa

# 將 DataFrame 轉換為 PyArrow Table
tbl = pa.Table.from_pandas(df)
print(tbl)

內容解密:

  • pa.Table.from_pandas(df) 將 pandas DataFrame 轉換為 PyArrow Table。
# 將 PyArrow Table 轉換回 DataFrame
print(tbl.to_pandas())

內容解密:

  • tbl.to_pandas() 將 PyArrow Table 轉換回 pandas DataFrame。

探索性資料分析

通常,你會發現自己面對一個幾乎一無所知的資料集。在本文中,我們已經展示了手動篩選資料的方法,但也有工具可以幫助自動化潛在的繁瑣任務,並幫助你在更短的時間內掌握資料。

YData Profiling

YData Profiling 自稱為“資料剖析的領先套件,可以自動生成詳細報告,包括統計和視覺化。”雖然我們在視覺化章節中展示瞭如何手動探索資料,但這個套件可以用作快速啟動,自動生成許多有用的報告和功能。

import pandas as pd

# 載入 vehicles 資料集的子集
df = pd.read_csv(
    "data/vehicles.csv.zip",
    dtype_backend="numpy_nullable",
    usecols=[
        "id",
        "engId",
        "make",
        "model",
        "cylinders",
        "city08",
        "highway08",
        "year",
        "trany",
    ]
)
print(df.head())

內容解密:

  • pd.read_csv 用於從壓縮的 CSV 檔案中讀取資料。
  • usecols 引數用於選擇要載入的列的子集。

資料分析與驗證:YData Profiling 與 Great Expectations 的應用

在資料科學和分析領域中,資料的品質和理解是至關重要的。本章將介紹兩個強大的工具:YData Profiling 和 Great Expectations,分別用於自動化資料分析和資料驗證,確保資料的可靠性和可用性。

使用 YData Profiling 自動生成資料報告

YData Profiling 是一個強大的工具,能夠自動生成詳細的資料報告,幫助分析人員快速瞭解資料集的特徵。該工具能夠產生多種常見的視覺化圖表,並對資料集中的欄位進行詳細描述。

步驟一:建立 Profile Report

首先,我們需要安裝並匯入 YData Profiling 函式庫。然後,建立一個 ProfileReport 物件,並將 DataFrame 作為輸入引數。

from ydata_profiling import ProfileReport

profile = ProfileReport(df, title="Vehicles Profile Report")

步驟二:顯示或匯出報告

在 Jupyter Notebook 中,可以直接呼叫 to_widgets() 方法來顯示報告。

profile.to_widgets()

若不在 Jupyter 環境中,可以將報告匯出為 HTML 檔案。

profile.to_file("vehicles_profile.html")

報告內容解析

YData Profiling 生成的報告包含多個部分,包括 Overview、欄位描述和相關性分析等。

  1. Overview:提供資料集的整體概覽,包括缺失值數量、重複列數等資訊。

    此圖示展示了資料集的整體概覽。

  2. 欄位描述:對每個欄位進行詳細描述。對於連續型變數,會生成直方圖;對於類別型變數,則會生成詞雲圖。

    直方圖解讀

    直方圖能夠直觀地展示連續型變數的分佈情況。

    圖表翻譯: 此圖示展示了 YData Profiling 如何根據連續型變數生成直方圖,直觀地展示其分佈情況。

  3. 相關性分析:透過熱力圖展示連續型變數之間的相關性。

    熱力圖能夠清晰地展示變數之間的相關程度。

@startuml skinparam backgroundColor #FEFEFE skinparam componentStyle rectangle

title Pandas 單元測試與資料驗證最佳實踐

package “Pandas 資料處理” { package “資料結構” { component [Series 一維陣列] as series component [DataFrame 二維表格] as df component [Index 索引] as index }

package "資料操作" {
    component [選取 Selection] as select
    component [篩選 Filtering] as filter
    component [分組 GroupBy] as group
    component [合併 Merge/Join] as merge
}

package "資料轉換" {
    component [重塑 Reshape] as reshape
    component [透視表 Pivot] as pivot
    component [聚合 Aggregation] as agg
}

}

series –> df : 組成 index –> df : 索引 df –> select : loc/iloc df –> filter : 布林索引 df –> group : 分組運算 group –> agg : 聚合函數 df –> merge : 合併資料 df –> reshape : melt/stack reshape –> pivot : 重新組織

note right of df 核心資料結構 類似 Excel 表格 end note

@enduml

   
   **圖表翻譯:** 此圖示說明瞭 YData Profiling 如何利用熱力圖呈現連續型變數之間的相關性。

### 使用 Great Expectations 進行資料驗證

Great Expectations 是一個用於資料驗證的強大工具,能夠幫助資料科學家確保資料的品質和可靠性。

#### 步驟一:建立 Great Expectations Context

首先,需要匯入 Great Expectations 函式庫並建立一個 Context 物件。

```python
import great_expectations as gx

context = gx.get_context()

步驟二:建立資料來源和資產

接下來,建立一個 Pandas 資料來源和一個 DataFrame 資產。

datasource = context.data_sources.add_pandas(name="pandas_datasource")
data_asset = datasource.add_dataframe_asset(name="vehicles")

步驟三:定義批次並進行驗證

定義一個批次定義,並使用它來取得批次資料。然後,對資料進行斷言驗證,例如檢查某個欄位是否包含空值。

batch_definition_name = "dataframe_definition"
batch_definition = data_asset.add_batch_definition_whole_dataframe(batch_definition_name)
batch = batch_definition.get_batch(batch_parameters={"dataframe": df})

city_exp = gx.expectations.ExpectColumnValuesToNotBeNull(column="city08")
result = batch.validate(city_exp)
print(result)

驗證結果解析

驗證結果將傳回一個包含成功與否、意外值數量等資訊的字典。

{
    "success": true,
    "expectation_config": {
        "type": "expect_column_values_to_not_be_null",
        "kwargs": {
            "batch_id": "pandas_datasource-vehicles",
            "column": "city08"
        },
        "meta": {}
    },
    "result": {
        "element_count": 48130,
        "unexpected_count": 0,
        "unexpected_percent": 0.0,
        "partial_unexpected_list": [],
        "partial_unexpected_counts": [],
        "partial_unexpected_index_list": []
    },
    "meta": {},
    "exception_info": {
        "raised_exception": false,
        "exception_traceback": null,
        "exception_message": null
    }
}

程式碼詳細解說

  1. 匯入函式庫與建立 Context

    import great_expectations as gx
    context = gx.get_context()
    

    內容解密: 這段程式碼匯入了 Great Expectations 函式庫,並建立了一個 Context 物件,用於管理資料來源和驗證流程。

  2. 建立資料來源和資產

    datasource = context.data_sources.add_pandas(name="pandas_datasource")
    data_asset = datasource.add_dataframe_asset(name="vehicles")
    

    內容解密: 這裡建立了一個 Pandas 資料來源和一個名為 “vehicles” 的 DataFrame 資產,用於組織和管理資料。

  3. 定義批次並進行驗證

    batch_definition_name = "dataframe_definition"
    batch_definition = data_asset.add_batch_definition_whole_dataframe(batch_definition_name)
    batch = batch_definition.get_batch(batch_parameters={"dataframe": df})
    

    內容解密: 這段程式碼定義了一個批次定義,並使用它來取得整個 DataFrame 的批次資料,為後續的驗證做好準備。

  4. 進行欄位驗證

    city_exp = gx.expectations.ExpectColumnValuesToNotBeNull(column="city08")
    result = batch.validate(city_exp)
    print(result)
    

    內容解密: 這裡定義了一個期望,即檢查 “city08” 欄位的值是否不為空,並對批次資料進行驗證,最後列印出驗證結果。