多表格關聯資料

適用於資料分散在多個關聯表格的情境。

在進行合成前需先使用資料庫反正規化技術整合多表,避免使用不成熟的多表格合成技術。根據下游任務選擇適當的顆粒度與聚合方式。

如果您的資料已經整合為單一表格,可以直接跳到商業邏輯約束

個案背景

某政策性金融機構擁有豐富的企業融資相關數據,包含企業基本資訊、融資申請、財務變化等多面向歷史紀錄。機構希望透過合成資料技術來推動與金融科技業者的創新合作,讓第三方能在確保資料隱私的前提下,利用這些資料開發風險預測模型,協助機構提升風險管理效能。

資料特性

  • 複雜的表格結構:原始資料分散在多個業務系統的資料表中,涉及企業基本資料、申請紀錄、財務追蹤等不同面向
  • 時序性資料:包含多個關鍵時間點(如申請日期、核准日期、追蹤時間等),且這些時間點之間具有邏輯順序關係

資料表關聯與業務意義

本案例的資料結構反映了企業融資的完整業務流程,主要包含三個核心資料表:

企業基本資料表

  • 包含企業識別碼、產業類別、子產業、地理位置和資本額等靜態資訊
  • 每筆記錄代表一個獨立的企業實體
  • 此表作為主表,與其他資料表形成一對多的關係

融資申請紀錄表

  • 記錄企業向金融機構提出的每一次融資申請詳情
  • 包含申請類型、申請日期、核准日期、申請狀態及金額等資訊
  • 一個企業可能有多次融資申請,時間跨度可達數年
  • 申請結果分為核准、拒絕和撤回三種狀態

財務追蹤紀錄表

  • 記錄企業獲得融資後的財務表現追蹤資料
  • 包含利潤率指標、追蹤時間範圍、營收指標及風險等級評估
  • 每個融資申請可能產生多筆追蹤紀錄,代表不同時間點的財務狀況

這三個資料表之間形成層次性的關聯結構:企業基本資料(1) → 融資申請紀錄(N) → 財務追蹤紀錄(N)。在實際業務流程中,企業首先建立基本檔案,隨後提交融資申請,而每筆申請案件均會觸發財務追蹤機制。

模擬資料示範

考量資料隱私,以下使用模擬資料展示資料結構與商業邏輯。這些資料雖然是模擬的,但保留了原始資料的關鍵特性與業務限制:

企業基本資料

company_idindustrysub_industrycitydistrictestablished_datecapital
C000001營建工程環保工程新北市板橋區2019-11-0319899000
C000002營建工程建築工程臺北市內湖區2017-01-0217359000
C000003製造業金屬加工臺北市內湖區2012-05-295452000

融資申請紀錄

application_idcompany_idloan_typeapply_dateapproval_datestatusamount_requestedamount_approved
A00000001C000001廠房擴充2022-01-212022-03-19approved1284800012432000.0
A00000002C000001營運週轉金2025-01-052025-02-11approved20760001516000.0
A00000004C000002營運週轉金2020-12-12NaNrejected5533000NaN

財務追蹤紀錄

application_idprofit_ratio_avgtracking_monthslast_tracking_dateavg_revenuerisk_level
A000000010.0332253.02024-09-041.840486e+07high_risk
A00000002-0.0026363.02027-07-311.926350e+07normal

為何選擇反正規化而非多表格合成?

目前開源的多表格合成技術(如 SDV 的 HMA)雖然能夠直接處理多表格資料,但存在明顯限制:

  1. 規模與複雜度限制:最適合不超過 5 張表格且僅有一層父子關係的結構
  2. 欄位類型限制:主要支援數值欄位,類別欄位需要前處理
  3. 約束處理限制:複雜的業務邏輯約束支援不足
  4. 品質問題:實際測試顯示跨表相關性和類別變數關聯度偏低

相較之下,採用反正規化策略整合為單一寬表後:

  • 可以使用成熟穩定的單表合成技術
  • 能夠完整定義業務邏輯約束
  • 合成品質更可控且可預測
  • 明確保留業務邏輯關係

步驟 1:資料探索與分析

分析表格關聯結構

繪製 ER 圖(Entity-Relationship Diagram)理解表格間的關係:

  • 識別主表與子表的關聯(一對多、多對多)
  • 確認主鍵/外鍵的對應關係
  • 觀察資料的時序性依賴

確認下游需求

與資料使用者(如資料科學家、業務分析師)討論分析目的:

  • 企業級分析:以企業為單位(一筆資料代表一家企業)
  • 申請案分析:以申請案為單位(一筆資料代表一次申請)
  • 時間點分析:以特定時間點為單位(一筆資料代表某個時間切面)

步驟 2:設計整合策略

選擇合適的顆粒度

延續上述企業融資個案,若下游任務關心每家企業的整體風險評估,適合的顆粒度為「一筆資料一家企業」。我們將整合以下資訊:

企業基本資料(直接帶入)

  • 企業識別碼 (company_id)
  • 產業類別 (industry)、子產業 (sub_industry)
  • 城市 (city)、行政區 (district)
  • 資本額 (capital)
  • 成立日期 (established_date)

第一次申請記錄(保留時序起點)

  • 申請日期 (first_apply_date)
  • 申請類型 (first_apply_loan_type)
  • 申請金額 (first_apply_amount_requested)
  • 核准金額 (first_apply_amount_approved)
  • 申請狀態 (first_apply_status)

最新申請記錄(反映當前狀態)

  • 申請日期 (latest_apply_date)
  • 申請類型 (latest_apply_loan_type)
  • 申請金額 (latest_apply_amount_requested)
  • 核准金額 (latest_apply_amount_approved)
  • 申請狀態 (latest_apply_status)

最新財務追蹤(當前財務健康度)

  • 追蹤日期 (latest_track_date)
  • 平均利潤率 (latest_track_profit_ratio_avg)
  • 追蹤月數 (latest_track_tracking_months)
  • 平均營收 (latest_track_avg_revenue)
  • 風險等級 (latest_track_risk_level)

處理一對多關係

對於一對多關係,選擇適當的處理方式:

  • 取特定記錄:如最新、最早、最大、最小值
  • 統計聚合:如計算平均值、總和、計數
  • 展開欄位:為不同時間點或狀態創建獨立欄位
  • 保留多筆:如果下游任務確實需要,標記序號

步驟 3:執行反正規化

方式 A:使用 Python pandas

適用於資料量適中且需要靈活處理的情境:

import pandas as pd

# 讀取原始資料表
companies = pd.read_csv('companies.csv')
applications = pd.read_csv('applications.csv')
tracking = pd.read_csv('tracking.csv')

# 標記每個公司的第一次和最新一次申請
applications['sort_tuple'] = list(zip(applications['apply_date'], applications['application_id']))

# 找出每個公司的最早申請
min_tuples = applications.groupby('company_id')['sort_tuple'].transform('min')
applications['is_first_application'] = (applications['sort_tuple'] == min_tuples)

# 找出每個公司的最晚申請
max_tuples = applications.groupby('company_id')['sort_tuple'].transform('max')
applications['is_latest_application'] = (applications['sort_tuple'] == max_tuples)

applications.drop(columns=['sort_tuple'], inplace=True, errors='ignore')

# 將財務追蹤資料串接上申請資料,以獲得公司編號
tracking_w_company = tracking.merge(
    applications[['company_id', 'application_id']],
    how='left',
    left_on='application_id',
    right_on='application_id'
)

# 標記每個公司的最新一次財務追蹤
tracking_w_company['sort_tuple'] = list(zip(
    tracking_w_company['tracking_date_last_tracking_date'],
    tracking_w_company['application_id']
))

max_tuples = tracking_w_company.groupby('company_id')['sort_tuple'].transform('max')
tracking_w_company['is_latest_tracking'] = (tracking_w_company['sort_tuple'] == max_tuples)

tracking_w_company.drop(columns=['sort_tuple'], inplace=True, errors='ignore')

# 合併企業資料與申請資料
denorm_data = companies.merge(
    applications[applications['is_first_application']].add_prefix('first_apply_'),
    how='left',
    left_on='company_id',
    right_on='first_apply_company_id'
).drop(columns=['first_apply_company_id', 'first_apply_is_first_application', 'first_apply_is_latest_application']).merge(
    applications[applications['is_latest_application']].add_prefix('latest_apply_'),
    how='left',
    left_on='company_id',
    right_on='latest_apply_company_id'
).drop(columns=['latest_apply_company_id', 'latest_apply_is_first_application', 'latest_apply_is_latest_application'])

# 加入彙整後的追蹤資料
denorm_data = denorm_data.merge(
    tracking_w_company[tracking_w_company['is_latest_tracking']].drop(columns=['sort_tuple'], errors='ignore').add_prefix('latest_track_'),
    how='left',
    left_on='company_id',
    right_on='latest_track_company_id'
).drop(columns=['latest_track_company_id', 'latest_track_is_latest_tracking'])

# 儲存整合後的寬表
denorm_data.to_csv('denormalized_data.csv', index=False)

方式 B:使用 SQL

適用於資料量大且已在資料庫中的情境:

-- 在資料庫中直接執行反正規化
WITH first_applications AS (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY company_id ORDER BY apply_date) as rn
    FROM applications
),
latest_applications AS (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY company_id ORDER BY apply_date DESC) as rn
    FROM applications
),
latest_tracking AS (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY application_id ORDER BY tracking_date DESC) as rn
    FROM tracking
)
SELECT
    c.*,
    fa.* AS first_apply_,
    la.* AS latest_apply_,
    lt.* AS latest_track_
FROM companies c
LEFT JOIN first_applications fa ON c.company_id = fa.company_id AND fa.rn = 1
LEFT JOIN latest_applications la ON c.company_id = la.company_id AND la.rn = 1
LEFT JOIN latest_tracking lt ON la.application_id = lt.application_id AND lt.rn = 1;

整合後使用 PETsARD

整合完成後,即可使用標準的 PETsARD 流程:

Loader:
  data:
    filepath: 'denormalized_data.csv'
    schema: 'denormalized_schema.yaml'
Preprocessor:
  default:
    method: 'default'
Synthesizer:
  default:
    method: 'default'
Postprocessor:
  default:
    method: 'default'
Reporter:
  output:
    method: 'save_data'
    source: 'Postprocessor'

注意事項

在進行資料整合時,需特別注意:

  • 確認資料的主鍵關係:避免重複或遺漏
  • 妥善處理時間序列資訊:例如使用摘要統計保留重要特徵
  • 資料表合併順序:會影響最終結果,建議先處理關聯性較強的表格
  • 下游任務需求:為了降低合成複雜度,可以僅保留必要的欄位

透過預先的反正規化處理,能夠明確保留業務邏輯關係、降低合成過程中的資料失真、提升最終合成資料的實用性與品質。