繪制蘇式五代家譜圖:Excel數(shù)據(jù)轉(zhuǎn)規(guī)范世系圖)
簡介本資源是一份面向Python初學(xué)者與數(shù)據(jù)可視化實(shí)踐者的家譜圖生成技術(shù)指南聚焦于從Excel結(jié)構(gòu)化數(shù)據(jù)出發(fā)利用pandas讀取家族成員信息、graphviz構(gòu)建蘇式五代世系圖的完整實(shí)現(xiàn)路徑。文檔以清晰步驟講解庫安裝、數(shù)據(jù)預(yù)處理、節(jié)點(diǎn)關(guān)系建模、兄弟節(jié)點(diǎn)橫向?qū)R含subgraph與ranksame技巧、PDF渲染等核心環(huán)節(jié)并附帶可直接運(yùn)行的代碼及關(guān)鍵注釋說明。資源為1個(gè)15KB的docx文件內(nèi)容涵蓋方法原理、代碼實(shí)現(xiàn)、Excel格式規(guī)范姓名/父親/世代三列、常見適配建議及graphviz進(jìn)階配置提示便于讀者快速復(fù)現(xiàn)并按實(shí)際家譜結(jié)構(gòu)調(diào)整。目前已有122人學(xué)習(xí)下載適合需將家族數(shù)據(jù)轉(zhuǎn)化為專業(yè)級譜系圖的技術(shù)愛好者、家譜數(shù)字化項(xiàng)目參與者或Python數(shù)據(jù)處理教學(xué)輔助者。1. 用 Python Graphviz 把 Excel 里的五代家譜數(shù)據(jù)自動(dòng)畫成蘇式世系圖不是畫樹狀圖而是嚴(yán)格按“蘇式體例”排版父在上、子居下、同輩橫排、長幼有序、配偶單列、出繼/入繼標(biāo)注清晰——這解決的是家譜數(shù)字化中最難落地的一環(huán)結(jié)構(gòu)化數(shù)據(jù)到規(guī)范譜圖的精準(zhǔn)映射很多做家族文化整理的人卡在同一個(gè)環(huán)節(jié)Excel 表格里存著幾百行親屬關(guān)系姓名、性別、出生年、父親、母親、配偶、是否出繼、是否入繼、備注但導(dǎo)出 PDF 或打印時(shí)要么靠手動(dòng)拖拽 Visio 拼接耗時(shí)易錯(cuò)要么用通用樹圖工具如 networkx matplotlib生成的圖輩分錯(cuò)位、配偶擠在節(jié)點(diǎn)里、繼嗣關(guān)系無標(biāo)識完全不符合《蘇氏家譜凡例》中“世系分明、支派不淆、繼嗣必書、配氏單列”的排版要求。本方案不依賴任何 GUI 工具或在線服務(wù)全程用pandas清洗 Excel 數(shù)據(jù)、用graphviz的 DOT 語言精確控制節(jié)點(diǎn)位置與連接邏輯、用自定義布局規(guī)則實(shí)現(xiàn)“蘇式五代”特有的層級壓縮——祖父輩居頂、父輩居中、己輩居下、子輩再下、孫輩最底且每代內(nèi)部嚴(yán)格按長幼順序橫向排列配偶節(jié)點(diǎn)右懸于夫/妻節(jié)點(diǎn)旁出繼者加出繼X房標(biāo)注入繼者加入繼X房標(biāo)注。適合有 Excel 家譜數(shù)據(jù)但無編程經(jīng)驗(yàn)的文史工作者也適合需要批量生成多支系譜圖的修譜機(jī)構(gòu)。2. 解析 Excel 家譜數(shù)據(jù)結(jié)構(gòu)并構(gòu)建蘇式五代拓?fù)潢P(guān)系從寬表到父子-配偶-繼嗣三元組2.1 蘇式家譜對原始 Excel 數(shù)據(jù)的字段約束與清洗邏輯蘇式體例要求世系圖必須體現(xiàn)“以父系為主線、配偶為附屬、繼嗣為變通”的結(jié)構(gòu)因此 Excel 原始數(shù)據(jù)不能是扁平寬表如 A1:姓名, B1:父名, C1:母名, D1:配偶名, E1:性別…而應(yīng)滿足以下最小字段集字段名必填說明示例name?本人姓名唯一標(biāo)識張明遠(yuǎn)father?父親姓名空值表示始祖張守仁gender?性別僅限 男/女男spouse?配偶姓名可為空女性配偶字段不填李秀英is_adopted_in?是否入繼填入繼房名如“張守義房”張守義房is_adopted_out?是否出繼填出繼房名如“張守禮房”張守禮房birth_year?出生年份用于排序非必需1942提示若原始 Excel 含重復(fù)姓名如多名“張建國”必須添加唯一 ID 列如id并用id作為主鍵關(guān)聯(lián)name僅作顯示文本。pandas默認(rèn)以name為索引會(huì)因重名報(bào)錯(cuò)這是新手最常踩的第一個(gè)坑。2.2 用 pandas 構(gòu)建父子邊、配偶邊、繼嗣邊三類關(guān)系圖譜我們不直接用networkx建圖而是先用pandas生成三張關(guān)系表parent_edges父→子、spouse_edges夫?妻、adoption_edges繼入/繼出標(biāo)注。這樣能顯式控制每條邊的語義和渲染樣式。import pandas as pd # 讀取 Excel假設(shè)工作表名為 family df pd.read_excel(su_shi_family.xlsx, sheet_namefamily, dtypestr) # 步驟1補(bǔ)全空值避免后續(xù) merge 失敗 df df.fillna() # 步驟2構(gòu)建父子邊表 —— 每個(gè)非始祖成員生成一條 father → name 邊 parent_edges df[df[father] ! ].copy() parent_edges parent_edges[[father, name]].rename(columns{father: src, name: dst}) # 步驟3構(gòu)建配偶邊表 —— 僅當(dāng) spouse 非空且 gender 為 男 時(shí)生成避免女性配偶重復(fù) spouse_edges df[(df[spouse] ! ) (df[gender] 男)].copy() spouse_edges spouse_edges[[name, spouse]].rename(columns{name: src, spouse: dst}) # 步驟4構(gòu)建繼嗣標(biāo)注表 —— 合并 is_adopted_in 和 is_adopted_out 到同一列 df[adoption_note] df.loc[df[is_adopted_in] ! , adoption_note] 入繼 df[is_adopted_in] df.loc[df[is_adopted_out] ! , adoption_note] 出繼 df[is_adopted_out] # 步驟5提取五代核心成員 —— 以某位“己身”如張明遠(yuǎn)為基準(zhǔn)向上推兩代、向下推兩代 target 張明遠(yuǎn) ancestors set() descendants set() # 向上找祖父、曾祖最多兩代 current target for _ in range(2): father_row df[df[name] current] if not father_row.empty and father_row.iloc[0][father]: ancestors.add(father_row.iloc[0][father]) current father_row.iloc[0][father] else: break # 向下找子女、孫子女最多兩代 def get_descendants(name, depth0): if depth 2: return children df[df[father] name][name].tolist() for child in children: descendants.add(child) get_descendants(child, depth 1) get_descendants(target, 0) # 合并五代成員祖先 自身 子孫 five_gen_names list(ancestors) [target] list(descendants) core_df df[df[name].isin(five_gen_names)].copy()這段代碼的核心價(jià)值不在語法而在邏輯它把 Excel 中隱含的“血緣鏈”顯式拆解為可驗(yàn)證的邊關(guān)系并通過get_descendants遞歸確保向下兩代不漏人比如張明遠(yuǎn)有子張立新、張立業(yè)張立新又有子張承志則張承志必須包含在五代內(nèi)。core_df即為最終繪圖的數(shù)據(jù)源后續(xù)所有節(jié)點(diǎn)屬性如排序序號、輩分層級都基于此子集計(jì)算。2.3 計(jì)算每個(gè)成員的蘇式輩分層級與同輩排序序號蘇式圖譜的視覺秩序依賴兩個(gè)坐標(biāo)垂直方向的輩分層rank和水平方向的長幼序order。graphviz的ranksame只能保證同層但無法自動(dòng)排序必須由pandas預(yù)先算出每個(gè)節(jié)點(diǎn)的rank和order值。# 步驟1確定各成員輩分層級rank # 規(guī)則始祖 rank0其子 rank1孫 rank2依此類推 rank_map {} # 先找始祖father 為空者 founders core_df[core_df[father] ] for _, row in founders.iterrows(): rank_map[row[name]] 0 # BFS 方式逐層推算后代 rank from collections import deque queue deque(founders[name].tolist()) while queue: person queue.popleft() children core_df[core_df[father] person][name].tolist() for child in children: if child not in rank_map: rank_map[child] rank_map[person] 1 queue.append(child) core_df[rank] core_df[name].map(rank_map).fillna(0).astype(int) # 步驟2計(jì)算同輩內(nèi)長幼序order—— 按 birth_year 升序無年份則按 Excel 行序 core_df[birth_year] pd.to_numeric(core_df[birth_year], errorscoerce) core_df core_df.sort_values([rank, birth_year, name]).reset_index(dropTrue) core_df[order] core_df.groupby(rank).cumcount() 1 # 步驟3為每個(gè)節(jié)點(diǎn)生成唯一 graphviz ID防中文亂碼、空格問題 core_df[node_id] core_df[name].str.replace(r[^\w], _, regexTrue) _ core_df[rank].astype(str)關(guān)鍵參數(shù)說明rank整數(shù)0 表示最高輩曾祖4 表示最低輩孫五代即rank∈ {0,1,2,3,4}order每rank組內(nèi)從 1 開始編號決定節(jié)點(diǎn)在該層的水平位置node_id將中文姓名轉(zhuǎn)為合法 DOT 標(biāo)識符如“張明遠(yuǎn)”→Zhang_Mingyuan_2避免graphviz解析失敗。3. 用 Graphviz DOT 語言生成蘇式布局rankdirTB ranksame edge constraints 控制五代縱向壓縮與同輩橫排3.1 構(gòu)建符合蘇式體例的 DOT 文件骨架subgraph 分層 node 屬性定制Graphviz 默認(rèn)的dot引擎會(huì)自動(dòng)調(diào)整布局但蘇式圖譜要求嚴(yán)格縱向分層、同輩絕對橫排、配偶右懸、繼嗣標(biāo)注緊貼節(jié)點(diǎn)。必須用subgraph顯式聲明每層并設(shè)置ranksame同時(shí)禁用邊的自動(dòng)排序constraintfalse以避免配偶邊干擾父子層級。from graphviz import Digraph dot Digraph(commentSu-style Five-Generation Genealogy, enginedot, formatpng) dot.attr(rankdirTB, splinesortho, nodesep25, ranksep60, fontsize12, fontnameSimSun) # 步驟1為每個(gè) rank 創(chuàng)建 subgraph并設(shè)置 ranksame ranks sorted(core_df[rank].unique()) for r in ranks: with dot.subgraph() as s: s.attr(ranksame) # 添加該層所有節(jié)點(diǎn) layer_nodes core_df[core_df[rank] r] for _, row in layer_nodes.iterrows(): # 節(jié)點(diǎn)標(biāo)簽姓名 繼嗣標(biāo)注如有 label row[name] if row[adoption_note]: label row[adoption_note] # 節(jié)點(diǎn)形狀男性用 box女性用 ellipse shape box if row[gender] 男 else ellipse # 節(jié)點(diǎn)樣式淺灰填充、黑邊、居中對齊 dot.node( row[node_id], labellabel, shapeshape, stylefilled, fillcolor#f0f0f0, color#333333, fontnameSimSun, fontsize12, width1.8, height0.6, fixedsizetrue ) # 步驟2添加父子邊垂直向下帶 arrowheadvee for _, edge in parent_edges.iterrows(): src_id core_df[core_df[name] edge[src]][node_id].iloc[0] if not core_df[core_df[name] edge[src]].empty else None dst_id core_df[core_df[name] edge[dst]][node_id].iloc[0] if not core_df[core_df[name] edge[dst]].empty else None if src_id and dst_id: dot.edge(src_id, dst_id, arrowheadvee, arrowsize0.8, penwidth1.2, constrainttrue) # 步驟3添加配偶邊水平右向無箭頭加 spouse 標(biāo)簽 for _, edge in spouse_edges.iterrows(): src_id core_df[core_df[name] edge[src]][node_id].iloc[0] dst_id core_df[core_df[name] edge[dst]][node_id].iloc[0] if src_id and dst_id: dot.edge( src_id, dst_id, arrowheadnone, penwidth1.0, label配, fontsize10, fontcolor#555555, constraintfalse # 關(guān)鍵禁用約束讓配偶邊不拉高父輩層 )注意constraintfalse是配偶邊不破壞父子垂直流的關(guān)鍵。若設(shè)為trueGraphviz 會(huì)強(qiáng)制配偶邊兩端節(jié)點(diǎn)在同一 rank導(dǎo)致父輩被拉低、子輩被抬高徹底打亂五代縱向壓縮結(jié)構(gòu)。3.2 強(qiáng)制同輩節(jié)點(diǎn)按 order 值嚴(yán)格左→右排列用 invisible edges 錨定順序僅靠ranksame不能保證節(jié)點(diǎn)按order排列——Graphviz 可能因邊權(quán)重隨機(jī)排序。必須添加不可見的invis邊形成“虛擬鏈”強(qiáng)制order1→order2→order3的線性順序。# 為每個(gè) rank 層內(nèi)節(jié)點(diǎn)添加 invisible 邊錨定水平順序 for r in ranks: layer_nodes core_df[core_df[rank] r].sort_values(order) ids layer_nodes[node_id].tolist() for i in range(len(ids) - 1): dot.edge(ids[i], ids[i1], styleinvis, constrainttrue) # 可選為曾祖層rank0添加標(biāo)題 if 0 in ranks: founder_ids core_df[core_df[rank] 0][node_id].tolist() if founder_ids: dot.node(title_node, label曾祖輩, shapeplaintext, fontsize14, fontweightbold) dot.edge(title_node, founder_ids[0], styleinvis)這段代碼生成的 DOT 結(jié)構(gòu)等價(jià)于手寫如下片段subgraph cluster_0 { ranksame; title_node [label曾祖輩 shapeplaintext fontsize14 fontweightbold]; Zhang_Shouren_0 [label張守仁 shapebox ...]; title_node - Zhang_Shouren_0 [styleinvis]; } subgraph cluster_1 { ranksame; Zhang_Mingyuan_1 [label張明遠(yuǎn) shapebox ...]; Li_Xiuying_1 [label李秀英配 shapeellipse ...]; Zhang_Mingyuan_1 - Li_Xiuying_1 [arrowheadnone label配 constraintfalse]; Zhang_Mingyuan_1 - Zhang_Lixin_2 [arrowheadvee]; }3.3 生成并渲染 PNG 圖像處理中文字體與路徑權(quán)限問題Mac / Windows 用戶常因字體缺失導(dǎo)致中文顯示為方框。graphviz默認(rèn)不加載系統(tǒng)中文字體需顯式指定fontname并確保字體文件存在。# 設(shè)置中文字體路徑macOS 示例Windows 請?zhí)鎿Q為 simsun.ttc 路徑 import platform if platform.system() Darwin: # macOS font_path /System/Library/Fonts/PingFang.ttc elif platform.system() Windows: font_path C:/Windows/Fonts/simsun.ttc else: # Linux font_path /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf # 將字體路徑注入 DOT 屬性 dot.attr(fontnamePingFang SC if platform.system() Darwin else SimSun) # 渲染圖像 try: dot.render(filenamesu_shi_genealogy, directory./output, cleanupTrue, formatpng, viewFalse) print(? 蘇式五代家譜圖已生成./output/su_shi_genealogy.png) except Exception as e: print(f? 渲染失敗{e}) print(請檢查1. Graphviz 是否已安裝2. 字體路徑是否正確3. output 目錄是否有寫入權(quán)限)常見失敗原因及修復(fù)ExecutableNotFound未安裝 Graphviz 二進(jìn)制。Mac 用brew install graphvizWindows 從官網(wǎng)下載安裝包并添加到 PATH中文亂碼確認(rèn)fontname與系統(tǒng)字體名一致macOS 用PingFang SCWindows 用SimSunLinux 用WenQuanYi Zen Hei權(quán)限錯(cuò)誤directory./output會(huì)創(chuàng)建該文件夾若當(dāng)前目錄只讀改用絕對路徑如/tmp/output。4. 調(diào)整蘇式圖譜的視覺精度配偶節(jié)點(diǎn)右懸距離、繼嗣標(biāo)注字號、輩分層間距的三個(gè)必調(diào)參數(shù)4.1 控制配偶節(jié)點(diǎn)與主體節(jié)點(diǎn)的水平偏移量用 xlabel pos 實(shí)現(xiàn)像素級定位默認(rèn)的edge連接會(huì)讓配偶節(jié)點(diǎn)緊貼主體右側(cè)但蘇式譜圖要求配偶節(jié)點(diǎn)略下沉、右懸 30–50px且不參與同輩排序。graphviz不支持絕對坐標(biāo)但可用xlabel在邊上添加標(biāo)注并用pos屬性微調(diào)。# 替換原 spouse_edges 渲染邏輯改為主體節(jié)點(diǎn) 右側(cè)獨(dú)立配偶節(jié)點(diǎn) 無邊連接線 for _, edge in spouse_edges.iterrows(): src_row core_df[core_df[name] edge[src]].iloc[0] dst_name edge[dst] # 生成配偶節(jié)點(diǎn) ID加 _spouse 后綴 spouse_id src_row[node_id] _spouse # 渲染配偶節(jié)點(diǎn)橢圓、淺藍(lán)填充、小字號 dot.node( spouse_id, labeldst_name, shapeellipse, stylefilled, fillcolor#e6f7ff, color#1890ff, fontnameSimSun, fontsize10, width1.2, height0.5, fixedsizetrue ) # 添加不可見邊但用 pos 指定配偶節(jié)點(diǎn)相對位置x 偏移 60pxy 偏移 10px # 注意pos 是 x,y! 格式單位為磅pt1pt ≈ 1.33px dot.edge( src_row[node_id], spouse_id, styleinvis, posf{src_row[node_id]}:{src_row[node_id]}n -- {spouse_id}:{spouse_id}n [pos\e,60,10!\] )pose,60,10!含義從主體節(jié)點(diǎn)東側(cè)e出發(fā)向右偏移 60pt≈80px向下偏移 10pt≈13px!表示絕對定位。此參數(shù)可精確控制配偶節(jié)點(diǎn)懸浮位置避免與子節(jié)點(diǎn)重疊。4.2 繼嗣標(biāo)注字號與顏色分離用 HTML-like label 實(shí)現(xiàn)多色文本adoption_note若直接拼在label中會(huì)與姓名同字號同色。蘇式規(guī)范要求繼嗣標(biāo)注用小號灰色字如 9pt、#888姓名用 12pt 黑色。graphviz支持 HTML 標(biāo)簽可嵌套font。# 修改節(jié)點(diǎn) label 構(gòu)建邏輯 for _, row in core_df.iterrows(): base_label ffont point-size12{row[name]}/font if row[adoption_note]: note_label ffont point-size9 color#888{row[adoption_note]}/font full_label f{base_label}br/{note_label} else: full_label base_label dot.node( row[node_id], labelfull_label, shapebox if row[gender] 男 else ellipse, stylefilled, fillcolor#f0f0f0, color#333333, fontnameSimSun, fontsize12, # 此處僅影響非 HTML labelHTML 內(nèi)字號以 font 為準(zhǔn) width1.8, height0.8, fixedsizetrue )br/換行確保姓名與標(biāo)注分兩行視覺上更清晰。point-size單位是磅pt9pt 約等于 12px符合印刷譜圖小字規(guī)范。4.3 五代層間垂直間距微調(diào)ranksep 與 nodesep 的協(xié)同作用表ranksep控制層與層之間的最小距離nodesep控制同層節(jié)點(diǎn)間的最小距離。蘇式圖譜要求“代際分明、同輩緊湊”典型值如下參數(shù)推薦值效果調(diào)整場景ranksep60曾祖→祖父層間距 60pt≈80px避免上下代文字重疊若曾祖名過長調(diào)至70nodesep25同輩兄弟間最小水平距 25pt≈33px保證姓名不擠若某代人多如祖父有 5 子調(diào)至30fontsize全局12節(jié)點(diǎn)內(nèi)文字基礎(chǔ)字號打印 A3 圖時(shí)可升至14提示修改后務(wù)必重新render()ranksep變化會(huì)觸發(fā) Graphviz 全局重排可能影響配偶節(jié)點(diǎn)位置需同步檢查pos偏移值是否仍合適。5. 驗(yàn)證蘇式五代圖譜的合規(guī)性用三步法檢查輩分、長幼、繼嗣三大硬性指標(biāo)5.1 輩分層級驗(yàn)證導(dǎo)出節(jié)點(diǎn) rank 表并人工比對 Excel 原始關(guān)系鏈生成圖后不能只看圖像是否“像”必須驗(yàn)證rank值是否嚴(yán)格對應(yīng)血緣深度。導(dǎo)出core_df[[name, father, rank]]到 CSV逐行核對# 導(dǎo)出輩分驗(yàn)證表 rank_check core_df[[name, father, rank]].sort_values(rank) rank_check.to_csv(./output/rank_verification.csv, indexFalse, encodingutf-8-sig) print( 輩分驗(yàn)證表已導(dǎo)出./output/rank_verification.csv)打開 CSV檢查所有father的人rank0若張守仁的rank0則其子張明遠(yuǎn)的rank必須為1不能是0或2若張明遠(yuǎn)的rank1其子張立新的rank必須為2依此類推。5.2 同輩長幼順序驗(yàn)證用 pandas groupby idxmin 檢查 birth_year 排序穩(wěn)定性蘇式要求“長幼有序”即同輩內(nèi)birth_year小者在左。用代碼驗(yàn)證排序是否被graphviz打亂# 檢查每層內(nèi) order 是否與 birth_year 一致 for r in ranks: layer core_df[core_df[rank] r].sort_values(order) # 獲取實(shí)際 birth_year 序列 years layer[birth_year].dropna().tolist() # 若有 birth_year檢查是否升序 if len(years) 1: is_sorted all(years[i] years[i1] for i in range(len(years)-1)) print(f? rank{r} 層 birth_year 排序{正確 if is_sorted else 異常}) else: print(f?? rank{r} 層無足夠 birth_year 數(shù)據(jù)按姓名字典序排列)若輸出“異常”說明 Excel 中birth_year有錄入錯(cuò)誤如 1920 錄成 2019需回溯修正。5.3 繼嗣標(biāo)注完整性驗(yàn)證正則匹配所有 “出繼.” 和 “入繼.” 字符串最后一步用 Python 打開生成的 PNGOCR 識別文字或直接檢查 DOT 源碼確認(rèn)所有繼嗣標(biāo)注無遺漏、無錯(cuò)字# 直接檢查 core_df 中 adoption_note 是否全覆蓋 missing_adoption core_df[(core_df[is_adopted_in] ! ) | (core_df[is_adopted_out] ! )][adoption_note] if missing_adoption.any(): print(? 發(fā)現(xiàn)繼嗣成員未生成標(biāo)注請檢查 is_adopted_in/out 字段是否含空格或全角字符) else: print(? 所有繼嗣成員均已標(biāo)注)真正落地的家譜圖不是“看起來像”而是每一處標(biāo)注、每一根連線、每一層間距都經(jīng)得起族老拿著放大鏡逐條核對。這套方法把 Excel 數(shù)據(jù)變成可驗(yàn)證、可復(fù)用、可批量生成的蘇式譜圖下次修譜你只需更新 Excel運(yùn)行一次腳本五代圖就自動(dòng)生成——省下的不是幾小時(shí)而是修譜人最珍貴的耐心與時(shí)間。本文還有配套的精品資源點(diǎn)擊獲取