【Biopython】Bio.Phyloを利用した系統樹操作まとめ

BiopythonのBio.Phyloを利用した系統樹操作まとめ

# 系統樹操作まとめ内容

- [系統樹データ読み込み](#系統樹データ読み込み)
- [系統樹の可視化](#系統樹の可視化)
- [全ノード取得](#全ノード取得)
- [終端ノードのみ取得](#終端ノードのみ取得)
- [内部ノードのみ取得](#内部ノードのみ取得)
- [ノードプロパティ変更](#ノードプロパティ変更)
- [ノード検索](#ノード検索)
- [終端ノード群の単系統判定](#終端ノード群の単系統判定)
- [ルートから対象ノードまでの全ノード経路リスト取得](#ルートから対象ノードまでの全ノード経路リスト取得)
- [最近共通祖先ノード取得](#最近共通祖先ノード取得)
- [各ノード深さ取得](#各ノード深さ取得)
- [対象ノード間の枝長取得](#対象ノード間の枝長取得)
- [系統樹全体の枝長総和取得](#系統樹全体の枝長総和取得)

## 系統樹データ読み込み

`Bio.Phylo.read()`メソッドで系統樹データ読み込み

```python:実行ソース
from io import StringIO

from Bio import Phylo
from Bio.Phylo.BaseTree import Tree

treedata = "((((A:1,B:1)100:1,(C:1,D:1)100:1)100:1,(E:2,F:2)90:1):1,G:4)100;"
tree: Tree = Phylo.read(file=StringIO(treedata), format="newick")
print(tree)
```

```text:出力結果
Tree(rooted=False, weight=1.0)
    Clade(confidence=100)
        Clade(branch_length=1.0)
            Clade(branch_length=1.0, confidence=100)
                Clade(branch_length=1.0, confidence=100)
                    Clade(branch_length=1.0, name='A')
                    Clade(branch_length=1.0, name='B')
                Clade(branch_length=1.0, confidence=100)
                    Clade(branch_length=1.0, name='C')
                    Clade(branch_length=1.0, name='D')
            Clade(branch_length=1.0, confidence=90)
                Clade(branch_length=2.0, name='E')
                Clade(branch_length=2.0, name='F')
        Clade(branch_length=4.0, name='G')
```

## 系統樹の可視化

`Bio.Phylo.draw()`メソッドでTreeオブジェクトの系統樹情報を可視化

```python:実行ソース
Phylo.draw(tree)
```

出力結果
![phylogenetic_tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138429/3e362e50-b109-7ebc-4113-bb69d81c29f6.png)


## 全ノード取得

Treeオブジェクトの`find_clades()`メソッドで取得
各ノードはそれぞれ 1.name, 2.branch_length(枝長), 3.confidence(ブートストラップ値), 4.clades(子孫ノードリスト)プロパティを格納

```python:実行ソース
for node in tree.find_clades():
    print(f"Node name = {node.name}, ", end="")
    print(f"Branch length = {node.branch_length}, ", end="")
    print(f"Confidence = {node.confidence}, ", end="")
    print(f"Descendants = {node.clades}")
```

```text:出力結果
Node name = None, Branch length = None, Confidence = 100, Descendants = [Clade(branch_length=1.0), Clade(branch_length=4.0, name='G')]
Node name = None, Branch length = 1.0, Confidence = None, Descendants = [Clade(branch_length=1.0, confidence=100), Clade(branch_length=1.0, confidence=90)]
Node name = None, Branch length = 1.0, Confidence = 100, Descendants = [Clade(branch_length=1.0, confidence=100), Clade(branch_length=1.0, confidence=100)]
Node name = None, Branch length = 1.0, Confidence = 100, Descendants = [Clade(branch_length=1.0, name='A'), Clade(branch_length=1.0, name='B')]
Node name = A, Branch length = 1.0, Confidence = None, Descendants = []
Node name = B, Branch length = 1.0, Confidence = None, Descendants = []
Node name = None, Branch length = 1.0, Confidence = 100, Descendants = [Clade(branch_length=1.0, name='C'), Clade(branch_length=1.0, name='D')]
Node name = C, Branch length = 1.0, Confidence = None, Descendants = []
Node name = D, Branch length = 1.0, Confidence = None, Descendants = []
Node name = None, Branch length = 1.0, Confidence = 90, Descendants = [Clade(branch_length=2.0, name='E'), Clade(branch_length=2.0, name='F')]
Node name = E, Branch length = 2.0, Confidence = None, Descendants = []
Node name = F, Branch length = 2.0, Confidence = None, Descendants = []
Node name = G, Branch length = 4.0, Confidence = None, Descendants = []
```

## 終端ノードのみ取得

Treeオブジェクトの`get_terminals()`メソッドにより取得
`count_terminals()`メソッドにより終端ノード数の取得も可能

```python:実行ソース
print(tree.get_terminals())
print("終端ノード数 =", tree.count_terminals()
```

```text:出力結果
[Clade(branch_length=1.0, name='A'), Clade(branch_length=1.0, name='B'), Clade(branch_length=1.0, name='C'), Clade(branch_length=1.0, name='D'), Clade(branch_length=2.0, name='E'), Clade(branch_length=2.0, name='F'), Clade(branch_length=4.0, name='G')]
終端ノード数 = 7
```

## 内部ノードのみ取得

Treeオブジェクトの`get_nonterminals()`メソッドにより取得

```python:実行ソース
print(tree.get_nonterminals())
print("内部ノード数 =", len(list(tree.get_nonterminals())))
```

```text:出力結果
[Clade(confidence=100), Clade(branch_length=1.0), Clade(branch_length=1.0, confidence=100), Clade(branch_length=1.0, confidence=100), Clade(branch_length=1.0, confidence=100), Clade(branch_length=1.0, confidence=90)]
内部ノード数 = 6
```

## ノードプロパティ変更

各ノード(Cladeオブジェクト)プロパティは下記のように変更可能
個人的には内部ノードに一意的な識別子を与える目的で利用することが多い。

```python:実行ソース
for cnt, node in enumerate(tree.get_nonterminals()):
    if node.name is None:
        node.name = f"N{cnt}"
Phylo.draw(tree)
```

出力結果
![add_nodes_property.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138429/84bcec12-a980-518c-0d9b-c5f9bc763f4c.png)


## ノード検索

Treeオブジェクトの`find_clades()`メソッドでノード検索・取得
メソッド引数により検索対象・方法を設定可能

```python:実行ソース
# 単純検索
print(list(tree.find_clades("A")))
print(list(tree.find_clades("N1")))
# 終端ノードのみを対象として検索
print(list(tree.find_clades("N1", terminal=True)))
# nameプロパティを対象として、正規表現検索
print(list(tree.find_clades({"name": "N.*"})))
# confidenceプロパティを対象として検索
print(list(tree.find_clades({"confidence": 90})))
```

```text:出力結果
[Clade(branch_length=1.0, name='A')]
[Clade(branch_length=1.0, name='N1')]
[]
[Clade(confidence=100, name='N0'), Clade(branch_length=1.0, name='N1'), Clade(branch_length=1.0, confidence=100, name='N2'), Clade(branch_length=1.0, confidence=100, name='N3'), Clade(branch_length=1.0, confidence=100, name='N4'), Clade(branch_length=1.0, confidence=90, name='N5')]
[Clade(branch_length=1.0, confidence=90, name='N5')]
```

## 終端ノード群の単系統判定

Treeオブジェクトの`is_monophyletic()`メソッドにより対象とする終端ノード群が単系統か判定

- 対象が単系統である場合、単系統のノード名を返す
- 対象が単系統でない場合、Falseを返す

```python:実行ソース
leaf_node_A = next(tree.find_clades("A"))
leaf_node_B = next(tree.find_clades("B"))
leaf_node_C = next(tree.find_clades("C"))
leaf_node_D = next(tree.find_clades("D"))

print("A-B単系統ノード =", tree.is_monophyletic(leaf_node_A, leaf_node_B))
print("A-C単系統ノード =", tree.is_monophyletic(leaf_node_A, leaf_node_C))
print("A-B-C-D単系統ノード =", tree.is_monophyletic(leaf_node_A, leaf_node_B, leaf_node_C, leaf_node_D))
```

```text:出力結果
A-B単系統ノード = N3
A-C単系統ノード = False
A-B-C-D単系統ノード = N2
```

## ルートから対象ノードまでの全ノード経路リスト取得

Treeオブジェクトの`get_path()`メソッドで取得
(全ノード経路リストにはルートの情報は含まない)

```python:実行ソース
print("N3ノードまでの経路\n", tree.get_path(target="N3"))
print("Cノードまでの経路\n", tree.get_path(target="C"))
```

```text:出力結果
N3ノードまでの経路
 [Clade(branch_length=1.0, name='N1'), Clade(branch_length=1.0, confidence=100, name='N2'), Clade(branch_length=1.0, confidence=100, name='N3')]
Cノードまでの経路
 [Clade(branch_length=1.0, name='N1'), Clade(branch_length=1.0, confidence=100, name='N2'), Clade(branch_length=1.0, confidence=100, name='N4'), Clade(branch_length=1.0, name='C')]
```

## 最近共通祖先ノード取得

Treeオブジェクトの`common_ancestor()`メソッドにより、引数指定の対象ノード群における最近共通祖先ノード取得

```python:実行ソース
mrca_node1 = tree.common_ancestor("E", "F")
print("E-F 最近共通祖先 =", mrca_node1.name)
mrca_node2 = tree.common_ancestor("N3", "N4")
print("N3-N4 最近共通祖先 =", mrca_node2.name)

mrca_node1.color = "red"
mrca_node1.width = 2
mrca_node2.color = "blue"
mrca_node2.width = 2
Phylo.draw(tree)
```

```text:出力結果
E-F 最近共通祖先 = N5
N3-N4 最近共通祖先 = N2
```
![mrca_tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138429/d6cb8678-419f-e5b8-f6ed-67dcafaf9f8f.png)

## 各ノード深さ取得

Treeオブジェクトの`depth()`メソッドでノード深さ取得
(Key=各ノード,Value=対象ノードの深さ、としたディクショナリ形式)

```python:実行ソース
print("枝の'数'で深さ算出\n", tree.depths(unit_branch_lengths=True))
print("枝の'長さ'で深さ算出\n", tree.depths(unit_branch_lengths=False)
```

```text:出力結果
枝の'数'で深さ算出
{Clade(confidence=100, name='N0'): 0, Clade(branch_length=1.0, name='N1'): 1, Clade(branch_length=1.0, confidence=100, name='N2'): 2, Clade(branch_length=1.0, confidence=100, name='N3'): 3, Clade(branch_length=1.0, name='A'): 4, Clade(branch_length=1.0, name='B'): 4, Clade(branch_length=1.0, confidence=100, name='N4'): 3, Clade(branch_length=1.0, name='C'): 4, Clade(branch_length=1.0, name='D'): 4, Clade(branch_length=1.0, confidence=90, name='N5'): 2, Clade(branch_length=2.0, name='E'): 3, Clade(branch_length=2.0, name='F'): 3, Clade(branch_length=4.0, name='G'): 1}
枝の'長さ'で深さ算出
{Clade(confidence=100, name='N0'): 0, Clade(branch_length=1.0, name='N1'): 1.0, Clade(branch_length=1.0, confidence=100, name='N2'): 2.0, Clade(branch_length=1.0, confidence=100, name='N3'): 3.0, Clade(branch_length=1.0, name='A'): 4.0, Clade(branch_length=1.0, name='B'): 4.0, Clade(branch_length=1.0, confidence=100, name='N4'): 3.0, Clade(branch_length=1.0, name='C'): 4.0, Clade(branch_length=1.0, name='D'): 4.0, Clade(branch_length=1.0, confidence=90, name='N5'): 2.0, Clade(branch_length=2.0, name='E'): 4.0, Clade(branch_length=2.0, name='F'): 4.0, Clade(branch_length=4.0, name='G'): 4.0}
```

## 対象ノード間の枝長取得

Treeオブジェクトの`distance()`メソッドにより引数指定の対象ノード間の枝長取得

```python:実行ソース
print("A-B distance =", tree.distance(target1="A", target2="B"))
print("C-G distance =", tree.distance(target1="C", target2="G"))
print("N3-N5 distance =", tree.distance(target1="N3", target2="N5")
```

```text:出力結果
A-B distance = 2.0
C-G distance = 8.0
N3-N5 distance = 3.0
```

## 系統樹全体の枝長総和取得

Treeオブジェクトの`total_branch_length()`メソッドで取得

```python:実行ソース
print("枝長総和 =", tree.total_branch_length())
```

```text:出力結果
枝長総和 = 17.0
```

# 全実行ソースまとめ

```python:全実行ソース
from io import StringIO

from Bio import Phylo
from Bio.Phylo.BaseTree import Tree

print("### 系統樹データ読み込み ###")
treedata = "((((A:1,B:1)100:1,(C:1,D:1)100:1)100:1,(E:2,F:2)90:1):1,G:4)100;"
tree: Tree = Phylo.read(file=StringIO(treedata), format="newick")
print(tree)

print("### 系統樹の可視化 ###")
Phylo.draw(tree)

print("### 全ノード取得 ###")
for node in tree.find_clades():
    print(f"Node name = {node.name}, ", end="")
    print(f"Branch length = {node.branch_length}, ", end="")
    print(f"Confidence = {node.confidence}, ", end="")
    print(f"Descendants = {node.clades}")

print("### 終端ノードのみ取得 ###")
print(tree.get_terminals())
print("終端ノード数 =", tree.count_terminals())

print("### 内部ノードのみ取得 ###")
print(tree.get_nonterminals())
print("内部ノード数 =", len(list(tree.get_nonterminals())))

print("### ノードプロパティ変更 ###")
for cnt, node in enumerate(tree.get_nonterminals()):
    if node.name is None:
        node.name = f"N{cnt}"
Phylo.draw(tree)

print("### ノード検索 ###")
# 単純検索
print(list(tree.find_clades("A")))
print(list(tree.find_clades("N1")))
# 終端ノードのみを対象として検索
print(list(tree.find_clades("N1", terminal=True)))
# nameプロパティを対象として、正規表現検索
print(list(tree.find_clades({"name": "N.*"})))
# confidenceプロパティを対象として検索
print(list(tree.find_clades({"confidence": 90})))

print("### 終端ノード群の単系統判定 ###")
leaf_node_A = next(tree.find_clades("A"))
leaf_node_B = next(tree.find_clades("B"))
leaf_node_C = next(tree.find_clades("C"))
leaf_node_D = next(tree.find_clades("D"))

print("A-B単系統ノード =", tree.is_monophyletic(leaf_node_A, leaf_node_B))
print("A-C単系統ノード =", tree.is_monophyletic(leaf_node_A, leaf_node_C))
print("A-B-C-D単系統ノード =", tree.is_monophyletic(leaf_node_A, leaf_node_B, leaf_node_C, leaf_node_D))

print("### ルートから対象ノードまでの全ノード経路リスト取得 ###")
print("N3ノードまでの経路\n", tree.get_path(target="N3"))
print("Cノードまでの経路\n", tree.get_path(target="C"))

print("### 最近共通祖先ノード取得 ###")
mrca_node1 = tree.common_ancestor("E", "F")
print("E-F 最近共通祖先 =", mrca_node1.name)
mrca_node2 = tree.common_ancestor("N3", "N4")
print("N3-N4 最近共通祖先 =", mrca_node2.name)

mrca_node1.color = "red"
mrca_node1.width = 2
mrca_node2.color = "blue"
mrca_node2.width = 2
Phylo.draw(tree)

print("### 各ノード深さ算出 ###")
print("枝の'数'で深さ算出\n", tree.depths(unit_branch_lengths=True))
print("枝の'長さ'で深さ算出\n", tree.depths(unit_branch_lengths=False))

print("### 対象ノード間の枝長取得 ###")
print("A-B distance =", tree.distance(target1="A", target2="B"))
print("C-G distance =", tree.distance(target1="C", target2="G"))
print("N3-N5 distance =", tree.distance(target1="N3", target2="N5"))

print("### 系統樹全体の枝長総和算出 ###")
print("枝長総和 =", tree.total_branch_length())
```

# 参考
[Bio.Phylo.BaseTree module](https://biopython.org/docs/1.75/api/Bio.Phylo.BaseTree.html)
[Phylogenetics with Bio.Phylo](https://biopython.readthedocs.io/en/latest/chapter_phylo.html)