過去探討的網路模型常將節點視為抽象實體,其在空間中的佈局僅為視覺化輔助。然而,真實世界的系統,如交通網絡、基礎設施或通訊流量,其節點與地理位置密不可分。這種空間屬性引入了新的分析維度:距離。直觀上,距離越遠,互動成本越高,連結強度也隨之減弱。此現象使得我們難以判斷一條強連結,究竟是因節點本身重要,抑或是單純距離較近所致。為了解決此問題,引力模型應運而生,它提供了一套標準化方法,讓我們得以剝離距離的干擾,洞察節點間互動的內在強度,進而揭示更深層的網絡結構與動態。
時空網路的基礎:地理位置、事件與引力模型
本章節將開啓對「時空網路」(Networks in Space and Time) 的探討,這是網路分析領域一個極為重要的分支。我們將首先介紹網路如何表示地理位置和事件之間的關係,以及「引力模型」(Gravity Models) 如何幫助我們理解空間距離對互動強度影響的現象。
時空網路的基礎概念
- 網路的多樣性:
- 到目前為止,我們討論的網路節點通常被視為獨立於時間和空間存在。即使在視覺化時將節點放置在二維空間,也主要是為了區分它們。
- 然而,在許多實際應用中,節點確實與特定的地理位置或時間點緊密相關。
 
- 時空網路的表示:
- 地理位置網路 (Networks in Space):節點代表地理實體(如城市、機場、電網節點),邊代表它們之間的空間聯繫(如道路、航班、電纜)。
- 範例:
- 電力網:節點是發電廠、變電站,邊是輸電線路。
- 道路網:節點是交叉口或城市,邊是道路。
- 航空網路:節點是機場,邊是航班。
 
 
- 範例:
- 時間網路 (Networks in Time):節點代表事件或時間點,邊代表它們之間的順序或因果關係。
- 範例:
- 維基百科連結:頁面之間的超連結,可以視為一種時間上的延續或知識的傳播。
- 事件序列:例如,一系列的交易、通訊記錄等。
 
 
- 範例:
 
- 地理位置網路 (Networks in Space):節點代表地理實體(如城市、機場、電網節點),邊代表它們之間的空間聯繫(如道路、航班、電纜)。
- 空間和時間屬性的影響:
- 在時空網路中,節點之間的距離(無論是地理距離還是時間間隔)和空間/時間屬性會顯著影響網路的性質和行為。
- 例如,在航空網路中,機場之間的距離和航班頻率(由旅客流量體現)是關鍵因素。
 
- 邊權重的多維性:
- 當邊代表物理連接時,其權重可能包含多個維度。例如,光纖電纜的邊權重可以同時考慮:
- 物理長度:影響信號傳輸延遲。
- 頻寬/容量:影響數據傳輸量。
- 成本:鋪設和維護的費用。
 
- 這些因素共同決定了連接的「有用性」和「價值」。
 
- 當邊代表物理連接時,其權重可能包含多個維度。例如,光纖電纜的邊權重可以同時考慮:
引力模型 (Gravity Models)
- 空間距離與互動強度:
- 在空間網路中,距離通常與「成本」相關聯,例如鋪設電纜的金錢成本、長途飛行的時間成本等。
- 因此,空間網路往往由許多短邊和較少的長邊構成。
- 許多互動的強度(例如,機場之間的旅客流量)會隨著距離的增加而減弱。
 
- 引力模型的原理:
- 引力模型是一種統計技術,用於校正空間距離對邊權重的影響,以便更準確地比較不同連接的「真實」強度。
- 核心假設:
- 兩個點之間互動的強度,平均而言,與它們之間距離的平方成反比。這類似於物理學中的萬有引力定律。
- 互動的強度也與節點的某些「質量」屬性(如節點的總流量或規模)成正比。例如,一個大型機場的總旅客流量越大,它與其他機場產生互動的可能性也越大。
 
- 數學形式:
互動強度 $I_{ij} \propto \frac{M_i M_j}{d_{ij}^2}$
其中:
- $I_{ij}$ 是節點 $i$ 和節點 $j$ 之間的互動強度。
- $M_i, M_j$ 是節點 $i$ 和 $j$ 的質量屬性(如總流量)。
- $d_{ij}$ 是節點 $i$ 和 $j$ 之間的距離。
 
 
- 應用:
- 引力模型可以幫助我們區分:一個邊的權重很高,是因為它很短(距離效應),還是因為它連接了兩個「活躍」的節點(質量效應)。
- 這對於理解機場交通、城市間貿易、通訊流量等空間互動模式至關重要。
 
處理空間數據的範例:美國大陸航空交通
- 資料集:
- 本節將展示如何使用 NetworkX處理空間數據,具體範例是分析 2018 年美國大陸的直飛航班數據。
 
- 本節將展示如何使用 
- 網路構建:
- 節點:代表機場,與地理位置相關聯。
- 邊:代表機場之間的直飛航班。
- 邊權重:代表兩個機場之間的旅客流量。
 
- 分析目標:
- 構建這個空間網路。
- 利用引力模型來分析和校正距離對旅客流量的影響。
- 理解機場之間的互動模式。
 
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from pathlib import Path
# --- 時空網路基礎概念與引力模型 ---
print("--- 時空網路基礎概念與引力模型 ---")
print("網路可以表示地理位置和事件之間的關係,其中距離和屬性影響互動強度。")
print("引力模型假設互動強度與節點質量成正比,與距離平方成反比。")
print("這有助於校正距離效應,分析真實的互動模式。")
# --- 處理空間數據範例:美國大陸航空交通 ---
print("\n--- 處理空間數據範例:美國大陸航空交通 (2018) ---")
# 假設數據文件路徑
# 在實際運行中,請確保 'data/BTS2018/carrier.csv' 路徑存在且包含正確的數據
# 為了示範,我們將模擬創建一個簡化的數據文件結構和內容
# 實際數據來源: Bureau of Transportation Statistics (BTS) - Air Carrier Traffic Statistics
# 創建模擬的數據目錄和文件
data_dir = Path('./data_simulated')
carrier_data_path = data_dir / 'BTS2018' / 'carrier.csv'
# 創建模擬數據 (如果文件不存在)
if not carrier_data_path.exists():
    data_dir.mkdir(parents=True, exist_ok=True)
    
    # 模擬一些機場和航班數據
    # 節點: 機場 IATA 代碼
    # 邊: 旅客流量 (Origin, Dest, Passengers)
    mock_data = {
        'ORIGIN_AIRPORT_ID': [10001, 10001, 10001, 10002, 10002, 10003, 10003, 10001, 10002, 10003],
        'DEST_AIRPORT_ID':   [10002, 10003, 10004, 10001, 10003, 10001, 10002, 10005, 10005, 10005],
        'PASSENGERS':        [1500, 800, 200, 1200, 900, 750, 600, 100, 150, 300],
        'ORIGIN_CITY_NAME':  ['Airport A', 'Airport A', 'Airport A', 'Airport B', 'Airport B', 'Airport C', 'Airport C', 'Airport A', 'Airport B', 'Airport C'],
        'DEST_CITY_NAME':    ['Airport B', 'Airport C', 'Airport D', 'Airport A', 'Airport C', 'Airport A', 'Airport B', 'Airport E', 'Airport E', 'Airport E'],
        'ORIGIN_STATE_ABR':  ['CA', 'CA', 'CA', 'NY', 'NY', 'TX', 'TX', 'CA', 'NY', 'TX'],
        'DEST_STATE_ABR':    ['NY', 'TX', 'WA', 'CA', 'TX', 'CA', 'NY', 'FL', 'FL', 'FL']
    }
    df_mock = pd.DataFrame(mock_data)
    df_mock.to_csv(carrier_data_path, index=False)
    print(f"已創建模擬數據文件: {carrier_data_path}")
# 載入網路
G_air = nx.Graph()
airport_data = {} # 儲存機場的屬性,例如城市、州
passenger_flow = {} # 儲存機場對之間的旅客流量
try:
    with open(carrier_data_path) as f:
        # 讀取 CSV 文件,使用 pandas 更方便
        df_carrier = pd.read_csv(carrier_data_path)
        # 處理數據以構建網路
        # 這裡我們需要將 AIRPORT_ID 映射到更易讀的名稱或使用 IATA 代碼 (如果有的話)
        # 為了簡化,我們假設 ID 就是節點名稱,並提取一些屬性
        
        # 獲取所有唯一的機場 ID 作為節點
        all_airport_ids = pd.concat([df_carrier['ORIGIN_AIRPORT_ID'], df_carrier['DEST_AIRPORT_ID']]).unique()
        
        # 提取機場屬性 (例如,城市名稱和州)
        # 注意: 同一個 AIRPORT_ID 可能有多個城市/州記錄,這裡取第一個出現的
        for airport_id in all_airport_ids:
            airport_info = df_carrier[df_carrier['ORIGIN_AIRPORT_ID'] == airport_id].iloc[0] if (df_carrier['ORIGIN_AIRPORT_ID'] == airport_id).any() else df_carrier[df_carrier['DEST_AIRPORT_ID'] == airport_id].iloc[0]
            airport_data[airport_id] = {
                'city': airport_info['ORIGIN_CITY_NAME'] if airport_info['ORIGIN_AIRPORT_ID'] == airport_id else airport_info['DEST_CITY_NAME'],
                'state': airport_info['ORIGIN_STATE_ABR'] if airport_info['ORIGIN_AIRPORT_ID'] == airport_id else airport_info['DEST_STATE_ABR']
            }
            G_air.add_node(airport_id, city=airport_data[airport_id]['city'], state=airport_data[airport_id]['state'])
        # 添加邊,表示航班和旅客流量
        # 使用 groupby 來匯總同一對機場之間的旅客流量 (可能有多個航空公司)
        grouped_flights = df_carrier.groupby(['ORIGIN_AIRPORT_ID', 'DEST_AIRPORT_ID'])['PASSENGERS'].sum().reset_index()
        for index, row in grouped_flights.iterrows():
            origin = row['ORIGIN_AIRPORT_ID']
            dest = row['DEST_AIRPORT_ID']
            passengers = row['PASSENGERS']
            
            # 確保節點已存在
            if origin not in G_air: G_air.add_node(origin)
            if dest not in G_air: G_air.add_node(dest)
            
            # 添加邊,權重為旅客流量
            G_air.add_edge(origin, dest, weight=passengers)
            passenger_flow[(origin, dest)] = passengers
            passenger_flow[(dest, origin)] = passengers # 假設流量是雙向的,或需要單獨處理
    print(f"成功載入網路: {G_air.number_of_nodes()} 個節點, {G_air.number_of_edges()} 條邊。")
    # 為了後續的引力模型計算,我們需要機場的地理位置 (緯度/經度)
    # 在這個範例中,我們沒有實際的地理位置數據,所以我們將模擬一些隨機位置
    # 在真實應用中,您需要從外部數據源 (如 GeoNames, airports database) 獲取
    print("注意:由於缺乏實際地理位置數據,這裡將模擬隨機的經緯度用於引力模型演示。")
    
    # 模擬隨機經緯度
    np.random.seed(42)
    for node in G_air.nodes():
        # 模擬美國大陸的經緯度範圍
        # 經度: -125 (西海岸) 到 -66 (東海岸)
        # 緯度: 24 (佛羅里達南部) 到 49 (北部邊界)
        G_air.nodes[node]['longitude'] = np.random.uniform(-125, -66)
        G_air.nodes[node]['latitude'] = np.random.uniform(24, 49)
    # --- 計算距離和應用引力模型 ---
    
    # 函數:計算兩個地理點之間的距離 (使用 Haversine 公式,近似球面距離)
    def haversine_distance(lat1, lon1, lat2, lon2):
        R = 6371 # 地球半徑 (公里)
        
        lat1_rad = np.radians(lat1)
        lon1_rad = np.radians(lon1)
        lat2_rad = np.radians(lat2)
        lon2_rad = np.radians(lon2)
        
        dlon = lon2_rad - lon1_rad
        dlat = lat2_rad - lat1_rad
        
        a = np.sin(dlat / 2)**2 + np.cos(lat1_rad) * np.cos(lat2_rad) * np.sin(dlon / 2)**2
        c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
        
        distance = R * c
        return distance
    # 計算所有邊的距離,並應用引力模型
    # 引力模型預期值: I_ij ~ (M_i * M_j) / d_ij^2
    # 我們需要計算節點的「質量」(例如,總進出港旅客數)
    
    # 計算每個機場的總進出港旅客數 (質量 M)
    node_mass = {}
    for node in G_air.nodes():
        total_passengers = 0
        # 進港旅客
        in_flights = df_carrier[df_carrier['DEST_AIRPORT_ID'] == node]
        if not in_flights.empty:
            total_passengers += in_flights['PASSENGERS'].sum()
        # 出港旅客
        out_flights = df_carrier[df_carrier['ORIGIN_AIRPORT_ID'] == node]
        if not out_flights.empty:
            total_passengers += out_flights['PASSENGERS'].sum()
        node_mass[node] = total_passengers
        G_air.nodes[node]['mass'] = total_passengers # 將質量屬性添加到節點
    print("\n已計算節點質量 (總進出港旅客數)。")
    # 計算邊的距離和引力模型預期流量
    gravity_model_expected_flow = {}
    
    for u, v, data in G_air.edges(data=True):
        # 獲取節點屬性
        lat1, lon1 = G_air.nodes[u]['latitude'], G_air.nodes[u]['longitude']
        lat2, lon2 = G_air.nodes[v]['latitude'], G_air.nodes[v]['longitude']
        mass_u, mass_v = G_air.nodes[u]['mass'], G_air.nodes[v]['mass']
        
        # 計算距離
        distance = haversine_distance(lat1, lon1, lat2, lon2)
        
        # 計算引力模型預期的流量
        # 避免除以零
        if distance > 0 and mass_u > 0 and mass_v > 0:
            # 使用 log 來處理數值範圍,或直接計算
            # 這裡直接計算,並取 log 來比較
            expected_flow = (mass_u * mass_v) / (distance**2)
            gravity_model_expected_flow[(u, v)] = expected_flow
            gravity_model_expected_flow[(v, u)] = expected_flow # 對稱
        else:
            gravity_model_expected_flow[(u, v)] = 0
            gravity_model_expected_flow[(v, u)] = 0
        # 將距離和預期流量添加到邊的屬性中
        data['distance_km'] = distance
        data['gravity_expected_flow'] = expected_flow
    print("已計算所有邊的距離和引力模型預期流量。")
    # --- 比較實際流量與引力模型預期流量 ---
    
    actual_flows = []
    expected_flows_gravity = []
    
    for u, v, data in G_air.edges(data=True):
        # 獲取實際旅客流量 (注意: 我們的數據是匯總的,可能需要匹配)
        # 由於我們是從頭構建的,這裡使用模擬的 passenger_flow
        # 在真實數據中,這裡應該是從原始數據中查找對應的匯總值
        
        # 為了簡化,我們直接使用邊的 'weight' 屬性作為實際流量
        actual_flow = data.get('weight', 0)
        
        # 獲取引力模型預期流量
        expected_flow = data.get('gravity_expected_flow', 0)
        
        # 僅當兩者都大於零時才進行比較,避免 log(0) 錯誤
        if actual_flow > 0 and expected_flow > 0:
            actual_flows.append(np.log10(actual_flow)) # 使用 log10 尺度
            expected_flows_gravity.append(np.log10(expected_flow))
    # 繪製散點圖比較實際流量與引力模型預期流量
    plt.figure(figsize=(10, 6))
    plt.scatter(actual_flows, expected_flows_gravity, alpha=0.5, s=10) # s 是點的大小
    plt.plot([min(actual_flows), max(actual_flows)], [min(actual_flows), max(actual_flows)], 'r--', label='Perfect Match') # 繪製 y=x 線
    
    plt.title("Actual Passenger Flow vs. Gravity Model Expected Flow (Log Scale)", fontsize=14)
    plt.xlabel("Log10(Actual Passenger Flow)", fontsize=12)
    plt.ylabel("Log10(Gravity Model Expected Flow)", fontsize=12)
    plt.legend()
    plt.grid(True, linestyle='--', alpha=0.6)
    plt.show()
    print("\n散點圖分析:")
    print("  - 如果實際旅客流量與引力模型預期流量高度相關,點會聚集在紅色虛線附近。")
    print("  - 這表明距離和節點質量在一定程度上可以解釋機場間的旅客流量。")
    print("  - 偏差可能源於其他因素,如航班時刻、票價、競爭、地理位置的吸引力等。")
except FileNotFoundError:
    print(f"錯誤: 數據文件 '{carrier_data_path}' 未找到。請確保數據文件存在於正確的路徑。")
except Exception as e:
    print(f"處理數據時發生錯誤: {e}")
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
start
:時空網路的基礎:地理位置、事件與引力模型;
split
:時空網路的基礎概念;
note right
網路的多樣性:
  - 節點與時間/空間相關聯
時空網路的表示:
  - 地理位置網路 (Networks in Space): 節點=地點, 邊=空間聯繫
  - 時間網路 (Networks in Time): 節點=事件/時間點, 邊=順序/因果
屬性影響:
  - 距離、時間間隔影響網路性質
邊權重多維性:
  - 長度、容量、成本等
end note
split again
:引力模型 (Gravity Models);
note right
空間距離與互動強度:
  - 距離增加,互動減弱
  - 網路多短邊,少長邊
模型原理:
  - 校正距離影響,比較真實強度
  - 假設: 互動強度 ~ (M_i * M_j) / d_ij^2
  - M: 節點質量 (如總流量)
  - d: 節點間距離
應用:
  - 區分距離效應與節點活躍度效應
end note
split again
:處理空間數據範例:美國大陸航空交通;
note right
資料集:
  - 2018 年美國大陸直飛航班數據
網路構建:
  - 節點: 機場 (地理位置)
  - 邊: 直飛航班 (旅客流量為權重)
分析目標:
  - 構建空間網路
  - 應用引力模型分析距離影響
  - 理解機場互動模式
程式碼實現:
  - 載入數據
  - 構建節點與邊 (含質量、距離、預期流量)
  - 計算距離 (Haversine)
  - 比較實際流量與引力模型預期流量 (散點圖)
end note
end split
stop
@enduml看圖說話:
此圖示總結了「時空網路的基礎:地理位置、事件與引力模型」的內容,重點在於介紹時空網路的基本概念,以及引力模型在分析空間互動中的作用。流程開頭首先聚焦於「時空網路的基礎概念」,說明了網路如何表示地理位置和時間關係,以及距離和屬性對網路行為的影響,接著詳細闡述了「引力模型」的原理,解釋了其核心假設(互動強度與節點質量成正比,與距離平方成反比)及其應用,最後展示了「處理空間數據範例:美國大陸航空交通」的實現流程,包括如何構建網路、計算距離和應用引力模型,並透過散點圖進行比較分析。
結論
縱觀現代商業生態的複雜連結,單純的數據指標已不足以支撐高階決策。引力模型不僅是網路分析的技術工具,更提供了一個關鍵的「校準思維框架」。它讓我們得以剝離地理距離這類物理限制的必然影響,從而清晰地辨識出哪些互動關係是真正「超乎預期」的強連結。傳統分析的瓶頸在於過度關注流量、銷量等絕對數值,而引力模型的核心價值,恰恰在於揭示那些偏離模型預測的「異常點」——這些點往往隱藏著獨特的市場機會、未被滿足的需求,或是潛在的結構性風險。
展望未來,這類融合物理學、社會學與數據科學的跨領域分析模型,將從學術應用擴展至商業策略的核心,被廣泛應用於供應鏈韌性、人才流動乃至品牌影響力的評估。
玄貓認為,高階管理者學習此模型的精髓,不在於精通其數學公式,而在於培養一種「關係洞察力」:識別出那些超越物理限制的卓越連結,並探究其背後的策略價值。這才是將數據轉化為決策智慧的關鍵躍升。
 
            