Monday, January 12, 2026

「偽疾病現象」(Pseudo-disease phenomenon): 分析工具的邏輯與組織生物學特性之間產生的「美麗誤會」

「卵巢富集神經退化通路」在生物資訊學中是一個已知的「偽疾病現象」?

(回答來自Google Gemini)



這是一個非常精闢的觀察。在生物資訊學(Bioinformatics)中,這確實是一個經典的**「註釋偏差」(Annotation Bias)**範例,我們可以稱之為「偽疾病現象」(Pseudo-disease phenomenon)。

這並不代表卵巢真的患病了,而是分析工具的邏輯組織生物學特性之間產生的「美麗誤會」。


1. 為什麼會產生「偽疾病」富集?

當我們將卵巢的轉錄組數據丟進 KEGG  GO 數據庫進行富集分析時,軟體會看到以下現象:

A. 粒線體基因的「高度重合」

  • 數據庫端: 阿茲海默症(AD)、巴金森症(PD)和亨丁頓舞蹈症(HD)在 KEGG 數據庫中的定義,包含了大量的**線粒體電子傳遞鏈(ETC)**基因(如 , , )。

  • 組織端: 卵母細胞(Oocytes)是人體中粒線體數量最龐大、代謝最活躍的細胞。

  • 結果: 軟體發現卵巢中這些「能量基因」表達極高,便根據算法將其歸類為「富集了 AD/PD 通路」。

B. 蛋白質質量控制(Proteostasis)

  • 疾病端: 神經退化是因為蛋白清理系統(如泛素-蛋白酶體系統 UPS)失效。

  • 卵巢端: 卵巢為了讓卵子在體內存放 40 年而不變質,必須維持極其強大的 UPS 活性。

  • 關鍵分子: 例如 。它在 PD 通路中被標記為致病基因,但它在正常卵巢中表達量極高,負責清除受損蛋白。軟體「看到」 高表達,就自動判定為 PD 通路激活。


2. 這種現象的「生物學啟示」:並非真的無關

雖然在資訊分析上是「偽富集」,但在生物學本質上,這揭示了卵巢與神經系統的**「共同挑戰」**:

  1. 不可再生性: 神經元和卵子都是「非分裂細胞」(Post-mitotic cells),一旦受損無法通過分裂更換,因此必須共用最尖端的維護路徑。

  2. 極長壽命: 兩者都面臨數十年的蛋白質堆積壓力。

  3. 高代謝壓力: 神經元需要維持膜電位,卵子需要為受精後的早期發育儲備能量,兩者都是「粒線體大戶」。


3. 跨器官的「功能借用」

研究發現,許多神經毒性蛋白在卵巢中其實有生理功能

基因在神經退化通路的角色在正常卵巢中的角色
 (類澱粉前驅蛋白)形成 A$\beta$ 斑塊 (AD)促進細胞黏附與卵泡發育
 (α-突觸核蛋白)形成路易氏體 (PD)調節激素分泌與小泡運輸
 (亨丁頓蛋白)形成蛋白聚集體 (HD)維護減數分裂與微管轉運

4. 如何在研究中處理這種現象?

如果你在做數據分析時看到卵巢富集了神經通路,資深的生物資訊學家通常會建議:

  • 過濾通用性代謝基因: 排除線粒體與核糖體基因後再看剩餘基因的富集情況。

  • 組織特異性背景: 使用「卵巢特異性」的背景基因集(Background genes),而不是使用「全基因組」作為背景。

  • 謹慎解讀: 意識到這反映的是**「高代謝活性」「強效蛋白清理」**,而非真正的神經系統病變。


總結

「卵巢富集神經通路」是演化上的合理設計。大腦與卵巢共享了生命中最精密的「抗衰老系統」。大腦在老年時這套系統崩潰了,於是產生了疾病;而健康的卵巢則展現了這套系統在巔峰狀態下的樣貌。

這也引出了一個迷人的假說:或許我們可以透過研究「為何卵巢能如此高效地管理這些蛋白」,來尋找治療阿茲海默症的新策略?

鰻魚初級卵巢組織和人體健康的卵巢組織一樣竟然高度富集(Enrichment)如阿茲海默症(AD)、巴金森症(PD)、亨丁頓舞蹈症(HD)等通路

不同組織進行基因表達譜(Transcriptome)分析時,科學家驚訝地發現:健康的卵巢組織竟然高度富集(Enrichment)了原本被認為是神經系統特有的通路,如阿茲海默症(AD)、巴金森症(PD)、亨丁頓舞蹈症(HD)等通路。

最有力的證據來自 GTEx (Genotype-Tissue Expression) 計畫,這是全球最大的人體組織基因表達數據庫。 關鍵文獻: Lopes-Ramos et al. (2020), "Sex differences in gene expression and regulatory networks across 29 human tissues", Cell Reports. 證據內容: 該研究在分析各組織的調控網絡時發現,卵巢組織中的基因表達模式與大腦高度相似。

在通路分析(GSEA)中,卵巢顯著富集了 "Alzheimer's disease"、"Parkinson's disease" 與 "Huntington's disease" 等 KEGG 通路。


以下是鰻魚卵巢轉錄體以NFM的分析:




























Monday, January 05, 2026

雄性素如何影響鰻魚初級卵巢發育 – 以神經網路學習 (Autoencoder Component) 分析



Python code of Leiden Communities on Japanese eel ovarian transcriptome

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

import networkx as nx

import seaborn as sns

import requests

import time

from pathlib import Path

from typing import List, Tuple, Optional, Dict

from gseapy import enrichr

import warnings


warnings.filterwarnings('ignore')


# --- 1. CNA 核心分析類別 ---

class CNA_Dual_Analyzer:

    def __init__(self, species: int = 9606, max_api_genes: int = 200):

        self.species = species

        self.max_api_genes = max_api_genes

        self.api_url = "https://string-db.org/api/json/network"

        

    def _safe_string_api(self, genes: List[str]) -> pd.DataFrame:

        if len(genes) < 2: return pd.DataFrame()

        batch_genes = genes[:self.max_api_genes]

        params = {

            "identifiers": "%0d".join(batch_genes),

            "species": self.species,

            "caller_identity": "cna_v2_1"

        }

        try:

            resp = requests.post(self.api_url, data=params, timeout=30)

            resp.raise_for_status()

            data = resp.json()

            if not data: return pd.DataFrame()

            df = pd.DataFrame(data)

            return df[['preferredName_A', 'preferredName_B', 'score']].dropna()

        except Exception as e:

            print(f"⚠️ STRING API失敗: {e}")

            return pd.DataFrame()


    def compute_cna_metrics(self, cluster_data: pd.DataFrame, at_cols: List[str], mt_cols: List[str]) -> pd.DataFrame:

        metrics = []

        for _, row in cluster_data.iterrows():

            gene = str(row.get('Gene_symbol', row.index[0])).strip().upper()

            if not gene or gene in ['NAN', 'NONE', '']: continue

            mean_at = row[at_cols].mean() if at_cols else 0.0

            mean_mt = row[mt_cols].mean() if mt_cols else 0.0

            asinh_diff = np.arcsinh(mean_mt) - np.arcsinh(mean_at)

            log2fc = np.log2((mean_mt + 1e-6) / (mean_at + 1e-6))

            metrics.append({

                'Gene': gene, 'Mean_AT': float(mean_at), 'Mean_MT': float(mean_mt),

                'asinh_Diff': float(asinh_diff), 'Log2FC': float(log2fc),

                'Expr_Change': 'UP' if asinh_diff > 0 else 'DOWN'

            })

        return pd.DataFrame(metrics)


    def build_control_network(self, genes: List[str], metrics_df: pd.DataFrame) -> Tuple[Optional[nx.Graph], pd.DataFrame]:

        ppi_df = self._safe_string_api(genes)

        if ppi_df.empty: return None, pd.DataFrame()

        G = nx.Graph()

        for _, row in ppi_df.iterrows():

            G.add_edge(row['preferredName_A'], row['preferredName_B'], weight=float(row['score']) / 1000)

        

        centrality = nx.degree_centrality(G)

        results = []

        for node in G.nodes():

            gene_metrics = metrics_df[metrics_df['Gene'] == node]

            asinh_diff = gene_metrics['asinh_Diff'].iloc[0] if not gene_metrics.empty else 0.0

            ctrl_score = centrality.get(node, 0) * (abs(asinh_diff) + 1)

            results.append({

                'Gene': node, 'Control_Score': ctrl_score, 'Centrality': centrality.get(node, 0),

                'asinh_Diff': asinh_diff, 'Log2FC': gene_metrics['Log2FC'].iloc[0] if not gene_metrics.empty else 0.0,

                'Expr_Change': gene_metrics['Expr_Change'].iloc[0] if not gene_metrics.empty else 'NONE'

            })

        stats_df = pd.DataFrame(results).sort_values('Control_Score', ascending=False)

        return G, stats_df


# --- 2. 視覺化類別 (整合 CNetplot) ---

class NetworkVisualizer:

    @staticmethod

    def create_cna_network(G, stats_df, output_path, cluster_id):

        """核心 PPI 控制網絡圖"""

        if G is None or stats_df.empty: return

        plt.figure(figsize=(12, 10), dpi=100)

        pos = nx.spring_layout(G, k=0.3, iterations=50)

        sizes = 100 + stats_df.set_index('Gene').reindex(G.nodes())['Control_Score'] * 2000

        colors = stats_df.set_index('Gene').reindex(G.nodes())['asinh_Diff']

        nx.draw_networkx_nodes(G, pos, node_size=sizes, node_color=colors, cmap='RdYlBu_r', alpha=0.8)

        nx.draw_networkx_edges(G, pos, alpha=0.2)

        top_genes = stats_df.head(20)['Gene'].tolist()

        nx.draw_networkx_labels(G, pos, {n: n for n in G.nodes() if n in top_genes}, font_size=8)

        plt.title(f"CNA Network: Cluster {cluster_id}")

        plt.savefig(output_path, bbox_inches='tight')

        plt.close()


    @staticmethod

    def create_cnet_plot(stats_df: pd.DataFrame, output_path: Path, cluster_id: str):

        """生成 CNetplot (Gene-Pathway Network)"""

        print(f"🧬 正在為 Cluster {cluster_id} 生成 CNetplot...")

        genes = stats_df.head(100)['Gene'].tolist() # 取控制力前100的基因

        try:

            enr = enrichr(gene_list=genes, gene_sets='KEGG_2021_Human', organism='Human', outdir=None)

            res = enr.results.nsmallest(5, 'Adjusted P-value') # 取前5個通路

            

            if res.empty: return


            G = nx.Graph()

            # 建立基因與通路的連接

            gene_colors = dict(zip(stats_df['Gene'], stats_df['asinh_Diff']))

            

            for _, row in res.iterrows():

                pathway = row['Term'].split('__')[-1]

                G.add_node(pathway, type='pathway')

                path_genes = row['Genes'].split(';')

                for g in path_genes:

                    if g in genes:

                        G.add_node(g, type='gene')

                        G.add_edge(pathway, g)


            plt.figure(figsize=(14, 10))

            pos = nx.spring_layout(G, k=0.5)

            

            # 繪製路徑節點 (大橘色)

            path_nodes = [n for n, d in G.nodes(data=True) if d['type'] == 'pathway']

            nx.draw_networkx_nodes(G, pos, nodelist=path_nodes, node_color='#FF7F0E', 

                                   node_size=1200, node_shape='p', label='Pathways')

            

            # 繪製基因節點 (顏色代表 asinh_Diff)

            gene_nodes = [n for n, d in G.nodes(data=True) if d['type'] == 'gene']

            g_colors = [gene_colors.get(n, 0) for n in gene_nodes]

            g_sizes = [stats_df[stats_df['Gene']==n]['Control_Score'].iloc[0]*800 + 100 for n in gene_nodes]

            

            nodes = nx.draw_networkx_nodes(G, pos, nodelist=gene_nodes, node_color=g_colors, 

                                           node_size=g_sizes, cmap='RdYlBu_r', label='Genes')

            

            nx.draw_networkx_edges(G, pos, alpha=0.3, edge_color='gray')

            nx.draw_networkx_labels(G, pos, font_size=9, font_weight='bold')

            

            plt.colorbar(nodes, label='asinh Difference')

            plt.title(f"CNetplot: Top Pathways & Control Genes (Cluster {cluster_id})")

            plt.legend(scatterpoints=1)

            plt.savefig(output_path, dpi=300, bbox_inches='tight')

            plt.close()

        except Exception as e:

            print(f"⚠️ CNetplot失敗: {e}")


# --- 3. 主程序 ---

def main_pipeline(excel_path: Path):

    output_dir = Path.home() / "Desktop" / "CNA_Analysis_CNet"

    output_dir.mkdir(exist_ok=True)

    

    df = pd.read_excel(excel_path)

    at_cols = [c for c in df.columns if 'AT' in str(c).upper()]

    mt_cols = [c for c in df.columns if 'MT' in str(c).upper()]

    gene_col = next(c for c in ['Gene_symbol', 'GeneID'] if c in df.columns)

    

    analyzer = CNA_Dual_Analyzer()

    visualizer = NetworkVisualizer()

    

    clusters = sorted(df['leiden'].unique().astype(str))

    

    for cid in clusters:

        print(f"\n🚀 處理 Cluster {cid}...")

        cluster_df = df[df['leiden'].astype(str) == cid]

        cluster_dir = output_dir / f"Cluster_{cid}"

        cluster_dir.mkdir(exist_ok=True)

        

        # 1. 計算指標

        metrics_df = analyzer.compute_cna_metrics(cluster_df, at_cols, mt_cols)

        

        # 2. 構建 CNA 網絡

        genes = metrics_df['Gene'].tolist()

        G, stats_df = analyzer.build_control_network(genes, metrics_df)

        

        if not stats_df.empty:

            stats_df.to_excel(cluster_dir / "CNA_Report.xlsx", index=False)

            

            # 3. 繪製 PPI 控制圖

            visualizer.create_cna_network(G, stats_df, cluster_dir / "CNA_Network.png", cid)

            

            # 4. 繪製 CNetplot (路徑-基因交互圖)

            visualizer.create_cnet_plot(stats_df, cluster_dir / "CNetplot.png", cid)

            

        time.sleep(1)


if __name__ == "__main__":

    file_path = Path.home() / "Desktop" / "Aja 21649_LeidCom_clustered.xlsx"

    main_pipeline(file_path)

Python coding of AUTOENCODER on Japanese eel ovarian transcriptome features-selection

import scanpy as sc

import anndata as ad

import numpy as np0

import torch

import torch.nn as nn

import torch.optim as optim





# 1. 讀取原始檔案

AData = sc.read_h5ad("6ovaries_analysis_results.h5ad")

adata = sc.read_h5ad("6ovaries_analysis_results.h5ad")


# 2. 檢查目前結構

# 如果讀進來已經是 6 x 20899,就不要執行 .T

# 如果讀進來是 20899 x 6,才執行一次 .T 把它轉正

if adata.n_obs > adata.n_vars:

    adata = adata



print(f"數據維度: {adata.n_obs} 樣本, {adata.n_vars} 基因")

print("目前的 obs 欄位:", adata.obs.columns)


# 標準化與對數轉換 (深度學習對數值範圍敏感)

sc.pp.log1p(adata)

sc.pp.scale(adata, max_value=10) # 建議增加 scale 步驟


# 轉換為 Tensor

X = adata.X.toarray() if hasattr(adata.X, "toarray") else adata.X

X_tensor = torch.FloatTensor(X)


# 2. 定義自編碼器模型

class Autoencoder(nn.Module):

    def __init__(self, input_dim, latent_dim=2):

        super(Autoencoder, self).__init__()

        # 編碼器:降低維度

        self.encoder = nn.Sequential(

            nn.Linear(input_dim, 128),

            nn.ReLU(),

            nn.Linear(128, latent_dim),

        )

        # 解碼器:重建原始維度

        self.decoder = nn.Sequential(

            nn.Linear(latent_dim, 128),

            nn.ReLU(),

            nn.Linear(128, input_dim),

        )


    def forward(self, x):

        latent = self.encoder(x)

        reconstructed = self.decoder(latent)

        return reconstructed, latent


# 3. 初始化模型、損失函數與優化器

input_dim = X.shape[1]

latent_dim = 1000 # 只有 6 個樣本,建議將隱藏層設小 (如 2),方便可視化

model = Autoencoder(input_dim, latent_dim)

criterion = nn.MSELoss()

optimizer = optim.Adam(model.parameters(), lr=1e-3)


# 4. 訓練模型

epochs = 1000

model.train()

for epoch in range(epochs):

    # 前向傳播

    reconstructed, latent = model(X_tensor)

    loss = criterion(reconstructed, X_tensor)

    

    # 反向傳播與優化

    optimizer.zero_grad()

    loss.backward()

    optimizer.step()

    

    if (epoch + 1) % 20 == 0:

        print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}")


# 5. 提取隱藏層特徵 (Latent Space)

model.eval()

with torch.no_grad():

    _, latent_features = model(X_tensor)

    # 將訓練好的特徵存回 AnnData 的 obsm 中,方便 Scanpy 繪圖

    adata.obsm['X_ae'] = latent_features.numpy()


# 6. 可視化

sc.pl.scatter(adata, basis='ae', color='treatment', title="Autoencoder Latent Space")




import matplotlib.pyplot as plt

import pandas as pd

import numpy as np

import seaborn as sns



# 1. 提取編碼器第一層的權重 (Shape: 64 x 20899)

# model.encoder[0] 對應 nn.Linear(input_dim, 64)

weights = model.encoder[0].weight.detach().numpy()


# 2. 計算每個基因(column)的綜合影響力得分

# 我們取絕對值的總和,因為正向和負向權重都代表該基因具有資訊量

gene_importance = np.sum(np.abs(weights), axis=0)


# 3. 建立 DataFrame 並與基因名稱對應

importance_df = pd.DataFrame({

    'gene_symbol': adata.var_names,

    'importance_score': gene_importance

})


print("-^-" * 30)

# --- 1. 計算統計基準 ---

mean_score = gene_importance.mean()

std_score = gene_importance.std()

suggested_cutoff = mean_score + 2 * std_score


print(f"\n[統計資訊]")

print(f"平均得分 (Mean): {mean_score:.4f}")

print(f"標準差 (Std): {std_score:.4f}")

print(f"建議門檻值 (Mean + 2SD): {suggested_cutoff:.4f}")

print("-" * 30)



# --- 2. 暫停並等待輸入 ---

user_input = input(f"請輸入您想要的 Cutoff 門檻值 (直接按 Enter 則使用建議值 {suggested_cutoff:.4f}): ")


# 判斷輸入邏輯

try:

    if user_input.strip() == "":

        cutoff = suggested_cutoff

    else:

        cutoff = float(user_input)

except ValueError:

    print("輸入格式錯誤,將自動使用建議門檻值。")

    cutoff = suggested_cutoff


print(f"\n確認使用 Cutoff = {cutoff:.4f} 進行篩選...")


plt.figure(figsize=(8, 4))

sns.histplot(importance_df['importance_score'], bins=100, kde=True)

plt.axvline(cutoff, color='red', linestyle='--', label='Mean + 2SD')

plt.title("Distribution of Gene Importance Scores")

plt.legend()

plt.show()


# --- 3. 執行篩選 ---

# 標記是否顯著

importance_df['is_significant'] = importance_df['importance_score'] > cutoff


# 挑選出符合條件的基因並排序

top_genes_df = importance_df[importance_df['is_significant']].sort_values(

    by='importance_score', ascending=False

)


# --- 4. 顯示結果 ---

num_sig = len(top_genes_df)

print(f"篩選完成!共找到 {num_sig} 個顯著基因。")

print('============================'*2)





print("--- Autoencoder 識別出的最具影響力基因 ---  基因數目 =", len(top_genes_df))

print(top_genes_df)


import matplotlib.pyplot as plt

# 5. 可視化:將這些重要基因的得分畫出來

import seaborn as sns

plt.figure(figsize=(10, 6))

sns.barplot(data=top_genes_df, x='importance_score', y='gene_symbol', palette='viridis')

plt.title(f"Influential Genes identified by AE, n = {num_sig}")

plt.xlabel("Cumulative Absolute Weights")

plt.show()


results_dict = {}


for i in range(1, 3): # 跑兩次

    print(f"\n>>> 開始第 {i} 輪訓練...")

    

    # 這裡放入你原本的代碼:定義模型、訓練、計算 importance...

    # (為了簡潔,假設你已將 AE 訓練邏輯包裝成一個函數或直接放在這裡)

    

    # 訓練與篩選邏輯結束後:

    current_genes = set(importance_df[importance_df['importance_score'] > cutoff]['gene_symbol'])

    results_dict[f'run_{i}'] = current_genes

    print(f"第 {i} 輪完成,篩選出 {len(current_genes)} 個基因")


# 最後提取出來

genes_run1 = results_dict['run_1']

genes_run2 = results_dict['run_2']


# 假設第一次篩選出的基因清單為 genes_run1,第二次為 genes_run2

# 這裡根據你提供的 top_genes_df 邏輯提取基因名稱



# 1. 計算交集 (Intersection)

stable_genes = genes_run1.intersection(genes_run2)


# 2. 計算聯集 (Union)

all_detected_genes = genes_run1.union(genes_run2)


# 3. 計算重合率 (Jaccard Index)

jaccard_index = len(stable_genes) / len(all_detected_genes)


print("\n" + "="*40)

print("基因穩定性分析結果")

print("="*40)

print(f"第一次運行基因數: {len(genes_run1)}")

print(f"第二次運行基因數: {len(genes_run2)}")

print(f"真正穩定的基因數 (交集): {len(stable_genes)}")

print(f"重合率 (Jaccard Index): {jaccard_index:.2%}")


# 4. 顯示前 10 個最穩定的基因

print("\n前 10 個穩定基因範例:")

print(list(stable_genes)[:10])


# 5. 存檔:將穩定基因導出

pd.DataFrame({'stable_genes': list(stable_genes)}).to_csv("stable_genes_intersection.csv", index=False)

print("\n已將穩定基因清單存至: stable_genes_intersection.csv")



import pandas as pd

import gseapy as gp

import networkx as nx

import matplotlib.pyplot as plt

from pathlib import Path


# --- 1. 路徑設定 ---

desktop = Path.home() / "Desktop"

stable_csv = Path("stable_genes_intersection.csv") # AE 產出的檔案

excel_source = desktop / "Aja 21649_LeidCom_clustered.xlsx"

output_dir = desktop / "Autoncoder_Enrichment_Results"

output_dir.mkdir(exist_ok=True)


# --- 2. 數據讀取與映射 (Mapping) ---

print("🔍 正在進行基因映射...")

try:

    # 讀取 AE 篩選出的穩定基因 (假設欄位名稱為 stable_genes)

    ae_genes = pd.read_csv(stable_csv)['stable_genes'].tolist()

    

    # 讀取原始含有註解的 Excel

    df_ref = pd.read_excel(excel_source)

    

    # 進行映射:在 Excel 中找尋 Transcriptid 存在於 ae_genes 清單中的行

    # 注意:請確保兩者的名稱格式一致

    mapped_df = df_ref[df_ref['Transcriptid'].isin(ae_genes)].copy()

    

    # 提取 Gene_symbol 用於富集分析 (去除空值與重複)

    gene_list = mapped_df['Gene_symbol'].replace('#N/A', pd.NA).dropna().unique().tolist()

    

    print(f"✅ 映射完成!AE 篩選 {len(ae_genes)} 個 ID,成功對應到 {len(gene_list)} 個有效基因。")

except Exception as e:

    print(f"❌ 讀取或映射失敗: {e}")

    exit()


# --- 3. 基因富集分析 (Enrichr) ---

print("🚀 執行 KEGG 富集分析...")

try:

    enr = gp.enrichr(gene_list=gene_list,

                     gene_sets=['KEGG_2021_Human'], # 或選取其他適合的物種資料庫

                     organism='Human', 

                     cutoff=0.5)

    res = enr.results

    

    # 儲存完整富集結果

    res.to_excel(output_dir / "AE_Stable_Genes_Enrichment.xlsx", index=False)

    

    # 篩選出顯著的通路 (Adj P < 0.05) 作為繪圖對象

    top_paths = res[res['Adjusted P-value'] < 0.1].head(10) # 取前 10 名

    if top_paths.empty:

        print("⚠️ 無顯著富集通路,調整門檻取前 5 名展示...")

        top_paths = res.nsmallest(10, 'Adjusted P-value')


except Exception as e:

    print(f"❌ 富集分析失敗: {e}")

    exit()


# --- 4. 繪製 Cnetplot (Gene-Concept Network) ---

print("🎨 正在生成 Cnetplot...")

try:

    G = nx.Graph()

    pathway_label_map = {}


    for _, row in top_paths.iterrows():

        pathway = row['Term']

        p_val = row['Adjusted P-value']

        pathway_label_map[pathway] = f"{pathway}\n(p={p_val:.2e})"

        

        # 獲取該通路中的基因並與我們的清單取交集

        path_genes = str(row['Genes']).split(';')

        valid_genes = [g for g in path_genes if g in gene_list]

        

        # 加入通路節點

        G.add_node(pathway, node_type='pathway', size=len(valid_genes))

        

        # 加入基因節點與邊 (限制顯示數量避免畫面太亂)

        for gene in valid_genes[:15]: 

            G.add_node(gene, node_type='gene')

            G.add_edge(pathway, gene)


    # 繪圖佈局

    plt.figure(figsize=(15, 12))

    pos = nx.spring_layout(G, k=0.5, iterations=50, seed=42)

    

    # 分別繪製通路與基因

    path_nodes = [n for n, a in G.nodes(data=True) if a.get('node_type') == 'pathway']

    gene_nodes = [n for n, a in G.nodes(data=True) if a.get('node_type') == 'gene']


    # 繪製邊

    nx.draw_networkx_edges(G, pos, edge_color='#dfe6e9', alpha=0.5)

    

    # 繪製 Pathway 節點 (紅圈)

    sizes = [G.nodes[n]['size'] * 500 for n in path_nodes]

    nx.draw_networkx_nodes(G, pos, nodelist=path_nodes, node_color='#ff7675', 

                           node_size=sizes, edgecolors='black', label='Pathways')

    

    # 繪製 Gene 節點 (藍圈)

    nx.draw_networkx_nodes(G, pos, nodelist=gene_nodes, node_color='#74b9ff', 

                           node_size=150, alpha=0.8, label='Genes')


    # 加入標籤 (Pathway)

    path_labels = {n: pathway_label_map[n] for n in path_nodes}

    nx.draw_networkx_labels(G, pos, labels=path_labels, font_size=10, font_weight='bold')

    

    # 加入標籤 (Gene)

    gene_labels = {n: n for n in gene_nodes}

    nx.draw_networkx_labels(G, pos, labels=gene_labels, font_size=8)


    plt.title("Cnetplot: AE Stable Genes & Top KEGG Pathways", fontsize=15)

    plt.axis('off')

    

    # 存檔

    plt.savefig(output_dir / "AE_Stable_Cnetplot.png", dpi=300, bbox_inches='tight')

    plt.show()

    print(f"✅ 分析完成!結果已存至: {output_dir}")


except Exception as e:

    print(f"❌ 繪圖失敗: {e}")

    

import pandas as pd

import gseapy as gp

import networkx as nx

import matplotlib.pyplot as plt

import numpy as np

from pathlib import Path

import traceback

import sys  # 修正 exit() 問題


# --- 1. 路徑與參數設定 ---

desktop = Path.home() / "Desktop"

stable_csv = Path("stable_genes_intersection.csv")

excel_source = desktop / "Aja 21649_LeidCom_clustered.xlsx"

output_dir = desktop / "AE_Stability_Network_Analysis"

output_dir.mkdir(exist_ok=True)


# 你的目標數值欄位

value_col = 'avg_diff' 


# --- 2. 數據映射與預處理 ---

print("🔍 正在進行基因數據映射...")

try:

    ae_ids = pd.read_csv(stable_csv)['stable_genes'].tolist()

    df_ref = pd.read_excel(excel_source)

    

    # --- 自動欄位檢查機制 ---

    if value_col not in df_ref.columns:

        print(f"⚠️ 找不到欄位 '{value_col}'。")

        print(f"現有欄位清單: {df_ref.columns.tolist()}")

        

        # 嘗試找尋備用欄位

        alternatives = ['log2FoldChange', 'logFC', 'p_val_adj', 'cluster']

        found_alt = next((alt for alt in alternatives if alt in df_ref.columns), None)

        

        if found_alt:

            value_col = found_alt

            print(f"💡 自動切換至備用欄位: '{value_col}'")

        else:

            print("❗ 找不到任何數值欄位,將建立預設數值 (1.0) 以維持繪圖。")

            df_ref['default_value'] = 1.0

            value_col = 'default_value'


    mapped_df = df_ref[df_ref['Transcriptid'].isin(ae_ids)].copy()

    

    if mapped_df.empty:

        print("❌ 映射後沒有任何基因!請檢查 Transcriptid 是否匹配。")

        sys.exit()


    # 建立對照字典

    gene_value_map = mapped_df.groupby('Gene_symbol')[value_col].mean().to_dict()

    gene_list = list(gene_value_map.keys())

    

    print(f"✅ 映射完成:{len(gene_list)} 個獨特基因符號。")


except Exception as e:

    print(f"❌ 映射出錯: {e}")

    traceback.print_exc()

    sys.exit() # 修正後的安全退出


# --- 3. 基因富集分析與長條圖繪製 ---

print("🚀 執行 KEGG 富集分析...")

try:

    enr = gp.enrichr(gene_list=gene_list,

                     gene_sets=['KEGG_2021_Human'],

                     organism='Human', 

                     cutoff=0.5)

    res = enr.results

    

    if res.empty or 'Adjusted P-value' not in res.columns:

        print("⚠️ 富集分析沒有結果,可能基因清單太少或不匹配。")

    else:

        res['-log10_Adj_P'] = -np.log10(res['Adjusted P-value'].replace(0, 1e-10))

        

# 繪製 Pathway Bar Plot

        # 修正:確保 n 是整數

        n_top = 20 

        top_10 = res.nsmallest(n_top, 'Adjusted P-value').iloc[::-1]

        

        plt.figure(figsize=(10, 8)) # 稍微增加高度以容納 20 條

        colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(top_10)))

        

        bars = plt.barh(top_10['Term'], top_10['-log10_Adj_P'], color=colors)

        

        # 加入顯著性參考線 (P < 0.05 -> -log10(P) > 1.3)

#        plt.axvline(x=-np.log10(0.1), color='red', linestyle='--', alpha=0.5, label='P=0.1')

        

        plt.xlabel('-log10(Adjusted P-value)', fontsize=12)

        plt.ylabel('Pathway Term', fontsize=12)

        plt.title('Top Enriched KEGG Pathways (AE Stable Genes)', fontsize=14)

        plt.legend()

        plt.tight_layout()

        

        plt.savefig(output_dir / "Pathway_Enrichment_Barplot.png", dpi=300)

        plt.show()


# --- 4. 繪製純基因網路圖 (Network Graph) ---

    print("🎨 生成穩定基因網路圖...")

    G = nx.Graph()

    top_paths = res.nsmallest(5, 'Adjusted P-value')

    

    for _, row in top_paths.iterrows():

        path_genes = [g for g in str(row['Genes']).split(';') if g in gene_list]

        for i in range(len(path_genes)):

            for j in range(i + 1, len(path_genes)):

                G.add_edge(path_genes[i], path_genes[j])


    if len(G.nodes) == 0:

        print("⚠️ 基因間無共同通路聯繫,跳過網路圖製作。")

    else:

        plt.figure(figsize=(12, 12))

        pos = nx.kamada_kawai_layout(G)

        node_colors = [gene_value_map.get(n, 0) for n in G.nodes()]

        

        nx.draw_networkx_edges(G, pos, alpha=0.2, edge_color='gray')

        nodes = nx.draw_networkx_nodes(G, pos, node_size=500, node_color=node_colors, 

                                       cmap=plt.cm.RdBu_r, edgecolors='black', linewidths=0.5)

        nx.draw_networkx_labels(G, pos, font_size=8, font_weight='bold')

        

        plt.colorbar(nodes, label=f'Value ({value_col})', shrink=0.7)

        plt.title("Gene Interaction Network")

        plt.axis('off')

        plt.savefig(output_dir / "Gene_Interaction_Network.png", dpi=300)

        plt.show()


    print(f"✨ 分析完成!結果已儲存至: {output_dir}")


except Exception as e:

    traceback.print_exc()

Thursday, November 27, 2025

The late arrival of domestic cats in China via the Silk Road after 3,500 years of human-leopard cat commensalism

 The late arrival of domestic cats in China via the Silk Road after 3,500 years of human-leopard cat commensalism" http://spkl.io/6187Ad0Ct

He Yu, Joel M. Alves, Greger Larson, Shu-Jin Luo & colleagues
#CellGenomics https://www.cell.com/cell-genomics/fulltext/S2666-979X(25)00355-6?utm_source=fb&utm_medium=organic&utm_campaign=hl 為了追溯人貓互動的歷史以及家貓(Felis catus)傳入東亞的起源,我們分析了從中國14個考古遺址出土的22塊小型貓科動物骨骼,這些遺址的時間跨度長達5000年。基因組和放射性碳定年結果表明,至少在5400年前,與人類共生的豹貓(Prionailurus bengalensis)就已出現在人類活動頻繁的環境中,並一直延續到公元150年。幾個世紀之後,中國已知最早的家貓(約公元730年)在唐代的陝西被發現,據推測是一隻全身或部分白色的貓。結合130個現代和古代歐亞貓科動物樣本的基因組分析表明,中國家貓起源於黎凡特地區,並很可能是透過絲綢之路由商人傳播開來的。豹貓和家貓曾獨立地生活在中國古代人類聚居地,但它們走上了不同的社會文化道路,只有家貓被完全馴化並引入全球。


CITES会议否决将所有鳗鱼种类列为管制对象的提案

CITES会议否决将所有鳗鱼种类列为管制对象的提案
CITES conference rejects proposal on restricting eel trade
《濒危野生动植物种国际贸易公约》(CITES)缔约方大会委员会会议于日本时间11月27日举行,会议对欧盟等提出的“应将日本鳗等所有鳗鱼种类列为管制对象”的提案进行了表决,结果该提案以反对票占多数被否决。
本届《濒危野生动植物种国际贸易公约》缔约方大会在中亚乌兹别克斯坦的撒马尔罕召开。在日本时间27日举行的委员会会议上,与会国代表就欧盟等提出的“应将日本鳗等所有鳗鱼种类列为管制对象”的提案进行了讨论。
之后由143个国家进行了投票,投票结果为:35票赞成、100票反对、8票弃权。为此,以上提案因反对票占多数而被否决。
日本对鳗鱼进口依赖度很高,如果该提案获得通过、贸易受到限制,恐将导致鳗鱼价格飙升。因此,日本与鳗鱼养殖兴盛的中国和韩国一致表明了反对意见,并获得了认可。
该结果将在12月4日召开的全体会议上正式敲定。