diff()
比較詮釋資料定義與實際資料的差異。
語法
@staticmethod
def diff(
metadata: Metadata,
data: dict[str, pd.DataFrame]
) -> dict
參數
metadata : Metadata, required
- 詮釋資料定義(期望的結構)
- 必要參數
data : dict[str, pd.DataFrame], required
- 實際資料,鍵為表格名稱,值為 DataFrame
- 必要參數
返回值
- dict
- 差異報告字典
- 如果沒有差異則返回空字典
{}
- 差異報告包含以下可能的鍵:
missing_tables
: metadata 中定義但資料中缺失的表格extra_tables
: 資料中存在但 metadata 未定義的表格table_diffs
: 各表格的詳細差異missing_columns
: 定義但缺失的欄位extra_columns
: 存在但未定義的欄位type_mismatches
: 型別不符的欄位nullable_mismatches
: nullable 屬性不符的欄位
說明
diff()
方法用於檢測期望的資料結構(metadata)與實際資料之間的差異,適用於:
- 資料驗證:確保資料符合預期結構
- 版本控制:追蹤資料結構變更
- 資料品質檢查:在處理前驗證資料完整性
- 除錯:識別資料載入或轉換過程中的問題
基本範例
from petsard.metadater import Metadater
import pandas as pd
# 定義期望的 schema
config = {
'id': 'expected_schema',
'schemas': {
'users': {
'id': 'users',
'attributes': {
'id': {'name': 'id', 'type': 'int', 'nullable': False},
'name': {'name': 'name', 'type': 'str', 'nullable': False},
'age': {'name': 'age', 'type': 'int', 'nullable': True}
}
}
}
}
metadata = Metadater.from_dict(config)
# 實際資料(有差異)
actual_data = {
'users': pd.DataFrame({
'id': [1, 2, 3],
'name': ['Alice', 'Bob', 'Charlie'],
'email': ['alice@ex.com', 'bob@ex.com', 'charlie@ex.com'] # 額外欄位
# 缺少 'age' 欄位
})
}
# 比較差異
diff_report = Metadater.diff(metadata, actual_data)
# 檢查結果
if diff_report:
print("發現資料結構差異:")
print(diff_report)
else:
print("資料結構完全符合")
進階範例
詳細差異分析
from petsard.metadater import Metadater
import pandas as pd
# 定義 schema
config = {
'id': 'user_schema',
'schemas': {
'users': {
'id': 'users',
'attributes': {
'user_id': {'name': 'user_id', 'type': 'int', 'nullable': False},
'username': {'name': 'username', 'type': 'str', 'nullable': False},
'age': {'name': 'age', 'type': 'int', 'nullable': True},
'email': {'name': 'email', 'type': 'str', 'nullable': False}
}
}
}
}
metadata = Metadater.from_dict(config)
# 實際資料有多種差異
actual_data = {
'users': pd.DataFrame({
'user_id': [1, 2, 3],
'username': ['alice', 'bob', 'charlie'],
'age': [25.5, 30.0, 35.0], # 型別錯誤:應為 int 但為 float
'phone': ['123-456', '234-567', '345-678'] # 額外欄位
# 缺少 'email' 欄位
})
}
# 比較差異
diff_report = Metadater.diff(metadata, actual_data)
# 分析差異報告
if 'table_diffs' in diff_report:
for table_name, table_diff in diff_report['table_diffs'].items():
print(f"\n表格: {table_name}")
if 'missing_columns' in table_diff:
print(f" 缺失欄位: {table_diff['missing_columns']}")
if 'extra_columns' in table_diff:
print(f" 額外欄位: {table_diff['extra_columns']}")
if 'type_mismatches' in table_diff:
print(f" 型別不符: {table_diff['type_mismatches']}")
多表格差異檢測
from petsard.metadater import Metadater
import pandas as pd
# 定義多表格 schema
config = {
'id': 'ecommerce',
'schemas': {
'users': {
'id': 'users',
'attributes': {
'user_id': {'name': 'user_id', 'type': 'int', 'nullable': False},
'name': {'name': 'name', 'type': 'str', 'nullable': False}
}
},
'orders': {
'id': 'orders',
'attributes': {
'order_id': {'name': 'order_id', 'type': 'int', 'nullable': False},
'user_id': {'name': 'user_id', 'type': 'int', 'nullable': False},
'amount': {'name': 'amount', 'type': 'float', 'nullable': False}
}
}
}
}
metadata = Metadater.from_dict(config)
# 實際資料(缺少 orders 表)
actual_data = {
'users': pd.DataFrame({
'user_id': [1, 2],
'name': ['Alice', 'Bob']
}),
'products': pd.DataFrame({ # 額外的表格
'product_id': [101, 102],
'name': ['Product A', 'Product B']
})
}
# 比較差異
diff_report = Metadater.diff(metadata, actual_data)
# 檢查表格層級的差異
if 'missing_tables' in diff_report:
print(f"缺失的表格: {diff_report['missing_tables']}")
if 'extra_tables' in diff_report:
print(f"額外的表格: {diff_report['extra_tables']}")
資料驗證工作流程
from petsard.metadater import Metadater
import pandas as pd
import sys
# 載入期望的 schema
with open('expected_schema.yaml', 'r') as f:
import yaml
config = yaml.safe_load(f)
metadata = Metadater.from_dict(config)
# 載入實際資料
actual_data = {
'users': pd.read_csv('users.csv'),
'orders': pd.read_csv('orders.csv')
}
# 驗證資料結構
diff_report = Metadater.diff(metadata, actual_data)
if diff_report:
print("❌ 資料結構驗證失敗")
print("\n差異報告:")
print(diff_report)
# 記錄到日誌檔
with open('validation_errors.log', 'a') as log:
log.write(f"差異報告: {diff_report}\n")
sys.exit(1)
else:
print("✅ 資料結構驗證通過")
# 繼續處理資料...
型別相容性檢查
from petsard.metadater import Metadater
import pandas as pd
import numpy as np
# 定義嚴格的型別 schema
config = {
'id': 'strict_schema',
'schemas': {
'measurements': {
'id': 'measurements',
'attributes': {
'id': {'name': 'id', 'type': 'int', 'nullable': False},
'value': {'name': 'value', 'type': 'float', 'nullable': False},
'timestamp': {'name': 'timestamp', 'type': 'datetime', 'nullable': False}
}
}
}
}
metadata = Metadater.from_dict(config)
# 測試不同的資料型別
test_cases = [
{
'name': '正確型別',
'data': pd.DataFrame({
'id': [1, 2, 3],
'value': [1.5, 2.5, 3.5],
'timestamp': pd.to_datetime(['2024-01-01', '2024-01-02', '2024-01-03'])
})
},
{
'name': '型別錯誤',
'data': pd.DataFrame({
'id': ['A', 'B', 'C'], # 應為 int
'value': [1.5, 2.5, 3.5],
'timestamp': pd.to_datetime(['2024-01-01', '2024-01-02', '2024-01-03'])
})
}
]
for test_case in test_cases:
print(f"\n測試案例: {test_case['name']}")
diff_report = Metadater.diff(metadata, {'measurements': test_case['data']})
if diff_report:
print(f" ❌ 發現差異: {diff_report}")
else:
print(f" ✅ 通過驗證")
注意事項
檢測內容:
- 表格存在性:檢查是否有缺失或額外的表格
- 欄位完整性:檢查欄位是否全部存在
- 型別一致性:檢查資料型別是否符合定義
- 空值屬性:檢查 nullable 設定是否一致
差異報告結構:
- 空字典表示完全相符
- 非空字典包含詳細的差異資訊
- 差異報告可用於生成使用者友善的錯誤訊息
型別比較:
- 型別比較基於 pandas dtype
- 某些型別轉換可能被視為相容(如 int64 vs int32)
- 建議使用嚴格的型別定義
使用時機:
- 資料載入後的驗證
- 資料轉換前的檢查
- 持續整合/部署的資料品質檢查
- 資料契約(Data Contract)驗證
效能考量:
- 大型資料集的差異檢測可能較耗時
- 建議對關鍵欄位優先檢查
- 可考慮抽樣檢查以提升效能
與 align() 的關係:
diff()
僅報告差異,不修改資料align()
會根據 metadata 調整資料結構- 建議先用
diff()
檢查,再決定是否使用align()
錯誤處理:
- 如果輸入格式不正確可能引發例外
- 建議使用 try-except 處理可能的錯誤
- 差異報告可序列化為 JSON 或 YAML 以便記錄