2026年1月22日 星期四

星際大戰角色同框頻率視覺化 (graphviz 飆速初體驗 / 用 jq 把 json 轉 csv / perl 的試算表模式)

星際大戰第四集人物同框頻率圖 想知道誰最常跟莉雅公主同框嗎? 不用人工計算,我們用幾行指令就能畫出來。 StarWars-social-network 這個專案整理了六集的星際大戰裡每一集內的角色同框頻率。 我們用 graphviz 把它畫出來, 順便學一下如何用 jq 把 json 格式轉成 csv, 以及 perl 處理 csv 檔的好用模式。

一、 操作

  1. 回到家目錄: cd ~
  2. 下載: wget https://github.com/evelinag/StarWars-social-network/archive/refs/heads/master.zip
  3. 解壓縮: unzip master.zip
  4. 進入資料目錄: cd ~/StarWars-social-network-master/networks/
  5. 拿 (最古老的) 第四集的資料來測試。 一次剪貼執行一句。 第一句先用 jq 產生一個 "第四集內所有同框" 的 csv 檔 ~/ep4.csv。 第二句再用 perl 把每一列轉成一條 "邊" (edge) , 存成 ~/ep4.dot 。 轉換時, 採用 graphviz 的 dot 語法, 並且以同框次數作為這條邊的權重 (weight)、 以它決定線的重要程度與粗細:
    jq -r '.nodes as $n | .links[] |
        [ $n[.source].name, $n[.target].name, .value ] | @csv' \
        starwars-episode-4-interactions.json > ~/ep4.csv
    perl -F, -nale 'printf("$F[0] -- $F[1] [weight=$F[2]; penwidth=$F[2]; "); \
        printf(qq(style="dashed")) if $F[2]<=1; print("];")' \
        ~/ep4.csv > ~/ep4.dot
    
  6. 用 geany 或 nano 編輯 ~/ep4.dot , 在第一列之前加一列: graph { 又在最後一列之後加一列: }
  7. 存檔、 執行: dot -Tsvg ~/ep4.dot > ~/ep4.svg 再用瀏覽器 (按 ctrl-o) 或 geeqie 打開 ep4.svg 觀察成品。 差點忘記了: 原來 C-3PO 也是蠻重要的角色呢!
  8. 把以下內容貼到 geany 或 nano, 存檔在你自己的家目錄 (home directory) 底下, 命名為 "swsn.sh":
    jq -r '.nodes as $n | .links[] |
        [ $n[.source].name, $n[.target].name, .value ] | @csv' \
        starwars-episode-$1-interactions.json > ~/ep$1.csv
    echo 'graph {' > ~/ep$1.dot
    perl -F, -nale 'printf("$F[0] -- $F[1] [weight=$F[2]; penwidth=$F[2]; "); \
        printf(qq(style="dashed")) if $F[2]<=1; print("];")' \
        ~/ep$1.csv >> ~/ep$1.dot
    echo '}' >> ~/ep$1.dot
    dot -Tsvg ~/ep$1.dot > ~/ep$1.svg
    
  9. 執行: bash ~/swsn.sh 5 然後查看: ls -ltr ~/ 應該會出現 ep5.dot、 ep5.csv、 ep5.svg 。 可以用瀏覽器或 geeqie 打開 ep5.svg 來看。 對,沒錯,這一集也可以說是莉亞公主的愛情故事...
  10. 改一下參數, 同樣的方式可以產生其他各集的 「角色同框資料視覺化」。
  11. 欣賞一下更強大的互動版: The Star Wars social network by Evelina Gabasova, 點其中的 「Open network」 連結, 用滑鼠拉圖中的任一個角色玩一下。

二、 比技術更重要的事

離散數學裡面的 Binary Relation 與 Graph 可以用來 表達人事物之間的關係, 像是 「握手」、「愛戀」、「先修」、「接壤」、「獵食」、「整除」、... 等等。 任何社會/自然/醫學/工程/...學科領域裡, 都有機會用到這個概念! 它可能以表格的方式呈現, 通常會是資料表裡面的兩個文字欄位。 而 graphviz 可以把這種關係畫成容易理解的圖像, 例如生物學裡的 「食物鏈食物網。 (<== 這張美圖當然不是 graphviz 畫的, 但它可以畫這類圖的多節點樸素版。)

今天展示的, 就是如何把一個 .json 格式的資料檔轉成 graphviz 認得的 .dot 語法。 大部分時候, 我們用程式處理資料; 但今天這個例子, 你可以說: 我們從資料產生 "程式"! 這就是 linux 命令列上文字工具的強大力量!

即使在 AI 時代的今天, 這種能力仍舊很有價值, 因為 「叫 AI 幫你下指令來完成這件事」 比 「叫 AI 直接幫你轉檔」 更不容易出錯、 耗用的運算資源也較低。 尤其當資料量超過幾百筆時, 差異更明顯。 希望這可以說服你採用 模組化繪圖工具箱 的思維來學習電腦、 學會 「重視檔案格式、 重視 轉換跑道的成本 更甚於軟體的花俏功能」。 不需要練成 「下指令 黑手師傅 高手」, 光是知道這個工作流程, 你就已經贏過許多人。 (通識課講義結束)

另一方面, 如果你有興趣成為黑手師傅, 那就繼續往下讀吧! 我來解釋上面的兩個指令。

三、 用 jq 把 json 轉 csv

使用 jq 把 .json 轉成 .csv 的公式如下: jq -r '... | @csv' data.json 如果忘了加 "-r", 會多出一些 \" 之類的。

至於 ... 的部分, 最重要的就是產生一個 (二維) 陣列, 每個元素代表一列, 每一列都包含相同的那些欄位。 以這個例子來說, 我們期待的輸出長這樣:

"CAMIE","LUKE",2
"BIGGS","CAMIE",2
"BIGGS","LUKE",4
"DARTH VADER","LEIA",1
...

先用 .links[] 拆出每一列的原始資料。 也可以把它想成是一個迴圈: 在它之後, 頭腦只需要思考如何處理這樣一小串資料:

    {
      "source": 3,
      "target": 1,
      "value": 2
    }

因為 json 檔的 .link 部分只記載代號; 人物姓名必須從 node 欄位查詢, 所以我們必須提早把 .nodes 存在變數 $n 裡面, 也就是最前面的那一小句 .nodes as $n

最後, 把查詢出兩個姓名及同框次數的一整列三欄位資料組裝成一個小陣列, 就完成了: [ $n[.source].name, $n[.target].name, .value ]

對了, 為了方便閱讀, 我把指令拆成好幾列。 一般在 bash 裡面, 橫跨多列的指令必須在列尾用 \ 表示 "指令未完, 下列繼續", 但是如果換列斷在 jq 的指令裡面, 則不必。

四、 perl 的 "試算表模式"

"試算表模式" 是我自己取的名字啦, 應該找不到其他文章這樣稱呼它。 在正規表示式講義裡的 perl 三種常用句型, 我們用到 perl -ne ...perl -pe ..., 這個 -n 就是 "靜音列模式", 我們在寫後面的指令時, 只需要專注思考如何處理一列即可; -l 則是 "處理結束後自動列印該列的列模式"。 現在我們改用 -nale 選項, 於是 perl 不只進入靜音列模式, 還會自動把一整列拆成許多欄位, 存放在 @F 陣列裡。 至於要用哪個字元來當作欄位分隔字串? 則由 -F 選項指定。

這裡, perl 做的事很單純: 把這樣的一列:
"CAMIE","LUKE",2
變成這樣的一列:
"CAMIE" -- "LUKE" [weight=2; penwidth=2; ];
如果同框僅一次, 例如:
"DARTH VADER","LEIA",1
那就額外指定用虛線畫:
"DARTH VADER" -- "LEIA" [weight=1; penwidth=1; style="dashed"];

IT 領域的人力分佈 你說我們用 jq 跟 perl -nale 所做的事, 算不算寫程式呢? 它比完整地學習 python 語言要簡單很多, 但又有一點寫程式的味道。 照理來說, 能夠接受這種難度的人口, 應該要多於學習 python 的人口。 但是, 過去幾十年來, 在專屬軟體商業力量 (例如微軟、 Adobe、 Oracle、 ...) 主導的資訊教育環境下, 情況卻是顛倒。 如果我們的教育界選擇的是自由軟體/開放原始碼軟體 (FLOSS) 的道路, 就會有更多人被培力 (empowered)、 學會這些其實並不困難, 而且具有長遠價值的技術。

沒有留言:

張貼留言

因為垃圾留言太多,現在改為審核後才發佈,請耐心等候一兩天。