在複雜系統研究中,網路結構會隨時間動態演進,理解此過程對預測趨勢至關重要。本章節聚焦於時序網路分析的實務操作,介紹如何將連續時間資料切分為離散的網路快照。我們將結合視覺化技術與量化指標,揭示網路在不同時間點的拓撲特徵與結構變遷。此方法不僅能直觀呈現連結的增減,更能精確捕捉社群結構等微觀屬性的動態變化,為決策提供數據基礎。

網路時序演化的視覺化與屬性分析

本章節將實際執行前文所述的快照生成與視覺化步驟,透過觀察一系列時間點的網路結構,來理解荷蘭維基百科連結網路如何隨時間演變。我們將重點關注視覺化結果中的「邊」的變化,以及如何透過這些視覺呈現來識別網路結構的演變模式。此外,我們將引導至對網路屬性(如平均聚類係數)進行時序分析的討論,為量化網路演化提供方法。

多時間點網路結構的視覺化

  • 視覺化策略
    • 多子圖呈現:程式碼採用了 plt.figure()plt.subplot() 的方式,在一張圖中展示多個時間點的網路快照。這使得使用者可以直觀地比較不同時間點的網路結構。
    • 反向時間順序for i, date in enumerate(reversed(dates)) 循環從最後一個日期開始繪製,並使用 6 - i 作為子圖的索引,這通常是為了讓時間順序在視覺上從左到右、從上到下遞增。
    • 佈局的連續性
      • pos = None:初始化佈局變量。
      • pos = nx.spring_layout(G, pos=pos, k=0.09):在每次迭代中,nx.spring_layout 函數會計算節點的位置。透過將前一個快照的佈局 pos 作為參數傳入,可以嘗試讓不同時間點的節點位置盡可能保持一致,從而更容易觀察結構的變化,而不是節點位置的隨機移動。k 參數用於調整節點間的平均距離。
  • 繪製元素
    • plt.title(date):為每個子圖設置標題,標明該快照對應的日期。
    • nx.draw_networkx(...)
      • G:當前時間點的網路快照。
      • pos=pos:使用先前計算好的節點位置。
      • alpha=0.5:設置邊的透明度,使得重疊的邊不會過於擁擠。
      • edge_color='#333333':設置邊的顏色為深灰色。
      • node_size=0關鍵點,這裡將節點大小設置為 0,意味著圖中只繪製了邊,而沒有繪製節點本身。這樣做是為了在節點數量眾多、連接複雜時,減少視覺上的雜亂感,專注於觀察連結模式的變化。
      • with_labels=False:不顯示節點標籤。
  • 視覺化結果的解讀
    • 邊的密集程度
      • 「高度互連的節點區域會呈現為邊的團塊 (clumps of edges)」。這表示在這些區域,節點之間存在大量的連結,形成緊密的社群。
      • 「孤立的節點群組則會出現在每個視覺化的邊緣」。這可能表示一些節點或社群在該時間點的連接性較弱,或者與網路的主體部分連接較少。
    • 結構演化的觀察
      • 透過逐一觀察這些時間點的快照,可以識別出:
        • 新連結的出現與消失。
        • 節點之間連接強度的變化(雖然此處只繪製了邊,沒有顯示權重,但邊的密集度可以間接反映)。
        • 社群結構的形成、擴張或收縮。
        • 網路的整體連通性變化。

網路屬性隨時間的演化分析

  • 量化網路變化
    • 僅僅依靠視覺化來理解網路演化是有限的,尤其當網路規模較大時。量化指標可以提供更精確的分析。
    • 網路快照的價值:透過為每個時間點生成快照,我們可以計算一系列時間點上的各種網路屬性,從而追蹤這些屬性如何隨時間變化。
  • 平均聚類係數的時序分析
    • 目標:分析網路的局部社群結構如何演變。
    • 程式碼邏輯
      1. year = 2001:設定分析的起始年份。
      2. 設定時間間隔:程式碼預告將以「一個月」為間隔,並涵蓋「兩年」的時間範圍。這意味著需要生成一系列的日期,例如 ‘2001-10-01’, ‘2001-11-01’, …, ‘2002-10-01’。
      3. 循環生成快照與計算屬性
        • 對於每一個選定的時間點:
          • 調用 get_snapshot(G_wiki, specific_date) 獲取該時間點的網路快照。
          • 計算該快照的平均聚類係數:nx.average_clustering(snapshot_G)
          • 將計算出的平均聚類係數值記錄下來,通常會與對應的時間點一起儲存(例如,在一個字典或列表中)。
      4. 結果呈現:最終可以將時間點作為橫軸,平均聚類係數作為縱軸,繪製一個折線圖,直觀展示網路平均聚類係數隨時間的變化趨勢。
  • 其他可分析的屬性
    • 除了平均聚類係數,還可以分析其他重要的網路屬性,例如:
      • 節點數量:網路的規模如何變化。
      • 邊數量:連結的總數如何變化。
      • 平均路徑長度:節點之間平均需要多少步才能到達。
      • 直徑:網路中最長的最短路徑。
      • 中心性指標:例如度中心性、介數中心性、緊密度中心性等,以了解哪些節點在不同時間點扮演更重要的角色。
      • 連通分量:網路的連通性如何變化。
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from pathlib import Path
import datetime
import time
import math

# --- 模擬數據和網路載入 (接續前文) ---
data_dir = Path('./data_simulated')
ligtenberg_dir = data_dir / 'ligtenberg2017'
wikilinks_csv_path = ligtenberg_dir / 'wikilinks.csv'

# 模擬創建 wikilinks.csv (如果不存在)
if not wikilinks_csv_path.exists():
    print("創建模擬 wikilinks.csv...")
    mock_edges_data = []
    # Unix timestamp 示例: 2001-10-01 00:00:00 -> 1001865600
    # 2001-11-06 00:00:00 -> 1005004800
    # 2001-10-08 00:00:00 -> 1002556800
    # 2001-10-10 00:00:00 -> 1002730000
    # 2001-10-21 00:00:00 -> 1003500000
    
    mock_edges_data.append({'node1': 'A', 'node2': 'B', 'begin': 1001865600, 'end': 1005004800}) # 存在整個時間段
    mock_edges_data.append({'node1': 'B', 'node2': 'C', 'begin': 1002556800, 'end': 1005004800}) # 2001-10-08 出現
    mock_edges_data.append({'node1': 'A', 'node2': 'C', 'begin': 1002730000, 'end': 1005004800}) # 2001-10-10 出現
    mock_edges_data.append({'node1': 'A', 'node2': 'D', 'begin': 1001865600, 'end': 1002556800}) # 2001-10-08 消失
    mock_edges_data.append({'node1': 'C', 'node2': 'D', 'begin': 1001865600, 'end': 1005004800}) # 存在整個時間段
    mock_edges_data.append({'node1': 'B', 'node2': 'D', 'begin': 1003500000, 'end': 1005004800}) # 2001-10-21 出現

    df_mock_edges = pd.DataFrame(mock_edges_data)
    df_mock_edges.to_csv(wikilinks_csv_path, index=False, header=['node1', 'node2', 'begin', 'end'])
    print("模擬 wikilinks.csv 創建完成。")

# 載入 MultiGraph
G_wiki = nx.MultiGraph()
try:
    G_wiki = nx.read_edgelist(
        str(wikilinks_csv_path), 
        data=[('begin', int), ('end', int)],
        create_using=nx.MultiGraph() 
    )
    print(f"成功載入 G_wiki MultiGraph: {G_wiki.number_of_nodes()} 個節點, {G_wiki.number_of_edges()} 條邊。")
except FileNotFoundError:
    print(f"錯誤:找不到模擬數據文件 {wikilinks_csv_path}")
except Exception as e:
    print(f"載入 G_wiki 時發生錯誤: {e}")

# --- 快照生成函數 (接續前文) ---
def get_snapshot(G, date_str):
    try:
        dt = datetime.datetime.strptime(date_str, '%Y-%m-%d')
        timestamp = time.mktime(dt.timetuple()) 
        
        snapshot_edges_data = [] 
        for u, v, key, data in G.edges(keys=True, data=True):
            if 'begin' in data and 'end' in data and \
               isinstance(data['begin'], (int, float)) and \
               isinstance(data['end'], (int, float)):
                if data['begin'] <= timestamp and data['end'] >= timestamp:
                    snapshot_edges_data.append((u, v, key)) 
        
        snapshot_G = G.edge_subgraph(snapshot_edges_data)
        return nx.Graph(snapshot_G) 

    except ValueError as ve:
        print(f"日期格式錯誤或無效日期 '{date_str}': {ve}")
        return nx.Graph() 
    except KeyError as ke:
        print(f"處理邊時缺少屬性: {ke}。請確保邊具有 'begin' 和 'end' 屬性。")
        return nx.Graph() 
    except Exception as e:
        print(f"生成快照時發生未知錯誤: {e}")
        return nx.Graph() 

# --- 定義需要視覺化的日期 ---
dates_to_visualize = [
    '2001-10-01',
    '2001-10-08',
    '2001-10-15',
    '2001-10-22',
    '2001-10-29',
    '2001-11-06',
]

print("\n--- 網路時序演化的視覺化 ---")

# --- 視覺化多個時間點的網路快照 ---
plt.figure(figsize=(10, 15))
pos = None # 初始化佈局,以便後續迭代使用

# 確保 G_wiki 有數據
if G_wiki.number_of_edges() > 0:
    for i, date in enumerate(reversed(dates_to_visualize)):
        # 獲取當前日期的網路快照
        G_snapshot = get_snapshot(G_wiki, date)
        
        # 創建子圖
        plt.subplot(3, 2, 6 - i) # 佈局為 3x2
        plt.title(date)
        
        # 計算佈局,並嘗試保持節點位置一致
        # k 值調整節點間的平均距離,可以根據需要調整
        current_pos = nx.spring_layout(G_snapshot, pos=pos, k=0.09, iterations=50) 
        pos = current_pos # 更新 pos 以便下一次迭代使用
        
        # 視覺化:只繪製邊,不繪製節點,以減少雜亂
        if G_snapshot.number_of_edges() > 0:
            nx.draw_networkx(
                G_snapshot, 
                pos=pos, 
                alpha=0.5, 
                edge_color='#333333', 
                node_size=0, # 不繪製節點
                with_labels=False,
                width=0.5 # 設置邊的寬度
            )
        else:
            # 如果快照沒有邊,可以繪製一個空的圖或提示
            plt.text(0.5, 0.5, "No edges at this time", ha='center', va='center')
            
        plt.gca().margins(0.05) # 調整邊距
        plt.axis('off') # 關閉坐標軸

    plt.tight_layout() # 自動調整子圖間距
    plt.show()

    print("\n已生成並顯示多個時間點的網路快照視覺化。")
    print("視覺化結果顯示了邊的密集程度和孤立群組,用於觀察網路結構的演變。")

else:
    print("\nG_wiki 圖為空,無法生成網路快照進行視覺化。")

# --- 網路屬性隨時間的演化分析 ---
print("\n--- 網路屬性隨時間的演化分析 ---")

# 設定分析的時間範圍和間隔
start_year = 2001
end_year = 2002
interval_months = 1

# 生成需要分析的日期列表
analysis_dates = []
current_date = datetime.datetime(start_year, 10, 1) # 從 2001 年 10 月 1 日開始
while current_date.year <= end_year or (current_date.year == end_year and current_date.month <= 10): # 分析到 2002 年 10 月
    analysis_dates.append(current_date.strftime('%Y-%m-%d'))
    # 增加一個月
    if current_date.month == 12:
        current_date = current_date.replace(year=current_date.year + 1, month=1)
    else:
        current_date = current_date.replace(month=current_date.month + interval_months)

print(f"將分析從 {analysis_dates[0]}{analysis_dates[-1]} 的網路屬性演化。")

# 儲存網路屬性(例如平均聚類係數)
network_properties_evolution = {
    'date': [],
    'avg_clustering': [],
    'num_nodes': [],
    'num_edges': []
}

# 遍歷每個分析日期,生成快照並計算屬性
if G_wiki.number_of_edges() > 0:
    for date_str in analysis_dates:
        snapshot_G = get_snapshot(G_wiki, date_str)
        
        if snapshot_G.number_of_nodes() > 0: # 確保快照不為空
            avg_clustering = nx.average_clustering(snapshot_G)
            num_nodes = snapshot_G.number_of_nodes()
            num_edges = snapshot_G.number_of_edges()
            
            network_properties_evolution['date'].append(date_str)
            network_properties_evolution['avg_clustering'].append(avg_clustering)
            network_properties_evolution['num_nodes'].append(num_nodes)
            network_properties_evolution['num_edges'].append(num_edges)
            
    print(f"已計算 {len(network_properties_evolution['date'])} 個時間點的網路屬性。")

    # --- 繪製網路屬性演化圖 ---
    
    # 繪製平均聚類係數演化圖
    plt.figure(figsize=(12, 6))
    plt.plot(network_properties_evolution['date'], network_properties_evolution['avg_clustering'], marker='o', linestyle='-', color='blue')
    plt.title("Evolution of Average Clustering Coefficient Over Time")
    plt.xlabel("Date")
    plt.ylabel("Average Clustering Coefficient")
    plt.xticks(rotation=45, ha='right')
    plt.grid(True, linestyle='--', alpha=0.6)
    plt.tight_layout()
    plt.show()
    
    # 繪製節點和邊數量演化圖
    fig, ax1 = plt.subplots(figsize=(12, 6))
    
    color = 'tab:red'
    ax1.set_xlabel('Date')
    ax1.set_ylabel('Number of Nodes', color='tab:red')
    ax1.plot(network_properties_evolution['date'], network_properties_evolution['num_nodes'], marker='o', linestyle='-', color='tab:red', label='Nodes')
    ax1.tick_params(axis='y', labelcolor='tab:red')
    ax1.tick_params(axis='x', rotation=45)
    
    ax2 = ax1.twinx() # 共享 x 軸的第二個 y 軸
    color = 'tab:green'
    ax2.set_ylabel('Number of Edges', color='tab:green')
    ax2.plot(network_properties_evolution['date'], network_properties_evolution['num_edges'], marker='x', linestyle='--', color='tab:green', label='Edges')
    ax2.tick_params(axis='y', labelcolor='tab:green')
    
    plt.title("Evolution of Network Size (Nodes and Edges) Over Time")
    fig.tight_layout()
    plt.xticks(rotation=45, ha='right')
    plt.grid(True, linestyle='--', alpha=0.6)
    plt.show()

    print("\n已繪製網路屬性演化圖,展示了平均聚類係數、節點數和邊數隨時間的變化趨勢。")

else:
    print("\nG_wiki 圖為空,無法進行網路屬性演化分析。")
@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

:網路時序演化的視覺化與屬性分析;:多時間點網路結構的視覺化;
note right
視覺化策略:
  - 多子圖呈現 (plt.figure, plt.subplot)
  - 觀察不同時間點的網路結構
佈局連續性:
  - 使用前一快照的 pos 作為當前快照的輸入
  - 保持節點位置相對穩定,便於觀察變化
繪製元素:
  - 標題: 日期
  - 僅繪製邊 (node_size=0, with_labels=False)
  - 邊的透明度 (alpha)
  - 邊的顏色和寬度
視覺化結果解讀:
  - 邊的密集程度 (團塊)
  - 孤立群組
  - 識別連結模式的變化
end note

:網路屬性隨時間的演化分析;
note right
量化網路變化:
  - 使用網路測量指標追蹤演化
目標: 分析平均聚類係數的時序變化
程式碼邏輯:
  - 設定時間範圍和間隔 (月度)
  - 生成日期列表
  - 循環:
    - 調用 get_snapshot 生成快照
    - 計算 avg_clustering, num_nodes, num_edges
    - 記錄結果
結果呈現:
  - 繪製折線圖: 時間 vs. 屬性值
  - 分析節點數、邊數的變化
其他可分析屬性:
  - 平均路徑長度, 直徑, 中心性指標, 連通分量
end note

stop

@enduml

看圖說話:

此圖示總結了「網路時序演化的視覺化與屬性分析」的內容,重點在於展示如何實際執行網路快照的生成與視覺化,並引導至對網路屬性進行量化分析以理解其隨時間的演變。流程開頭首先聚焦於「多時間點網路結構的視覺化」,說明了如何透過多子圖展示不同時間點的網路快照,並強調了使用連續佈局和僅繪製邊來觀察連結模式變化的視覺化策略,接著詳細闡述了「網路屬性隨時間的演化分析」,介紹了如何透過生成一系列時間點的快照,計算並追蹤如平均聚類係數、節點數和邊數等關鍵網路屬性的變化趨勢,為量化理解網路動態性提供了方法。

好的,這是一篇針對「網路時序演化的視覺化與屬性分析」此一技術主題,以「玄貓風格高階管理者個人與職場發展文章結論撰寫系統」生成的結論。

本次寫作選用 創新與突破視角


結論

縱觀現代數據分析的演進,此方法的核心突破在於將靜態結構觀點,提升至動態演化視角。本章節展示了兩種互補路徑的整合價值:多快照視覺化提供宏觀變遷的直觀感受,而量化指標的時序追蹤則提供嚴謹數據佐證,克服了純視覺判讀的主觀性與規模限制,形成從定性洞察到定量驗證的分析閉環。展望未來,此方法與外部事件數據的融合,將使分析從探究「如何變」躍升至洞察「為何變」。玄貓認為,掌握動態演化分析思維,是分析者從「描述現狀」進階到「理解變遷」的必要修煉,其終極價值在於培養洞察系統生命週期的全局視野。