Pythonで円形の積み上げ棒グラフ(Circular Stacked Bar)をプロットする

個人開発している円形データプロットのためのPython製OSSライブラリ「**pyCirclize**」に積み上げ棒グラフのプロット機能を最近追加してみた。この記事では、新しく追加した円形の積み上げ棒プロット機能の使い方について簡単に紹介します。

https://github.com/moshi4/pyCirclize

![stacked_bar_gallery.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138429/541f9a6d-83fa-65bb-1eb8-4554c98feb92.png)
**↑ 垂直・水平方向の円形積み上げ棒グラフのプロット例**

## pyCirclizeのインストール

`pip` または `conda`コマンドでインストール可能

```text:PyPIからインストール
pip install pycirclize
```

```text:conda-forgeからインストール
conda install -c conda-forge pycirclize
```

## 積み上げ棒グラフのプロット

pyCirclizeでは`行名・列名付きの表データ`から円形の積み上げ棒グラフを垂直・水平方向にそれぞれプロットする機能を実装してます。

### 垂直方向プロット例①

```python
from pycirclize import Circos
import pandas as pd
import numpy as np
np.random.seed(0)

# Generate matrix data for stacked bar plot
row_num, col_num = 12, 6
matrix = np.random.randint(5, 20, (row_num, col_num))
row_names = [f"R{i}" for i in range(row_num)]
col_names = [f"group{i}" for i in range(col_num)]
table_df = pd.DataFrame(matrix, index=row_names, columns=col_names)
table_df.to_csv("stacked_bar_table.tsv", sep="\t")

# Initialize Circos sector & track
circos = Circos(sectors=dict(bar=len(table_df.index)))
sector = circos.sectors[0]
track = sector.add_track((50, 100))
track.grid()

# Plot stacked bar 
colormap = "Set3"
sb_table = track.stacked_bar(
    table_df,
    width=0.6,
    cmap=colormap,
    bar_kws=dict(ec="black", lw=0.2),
    label_pos="bottom",
    label_kws=dict(size=10, orientation="horizontal"),
)

circos.savefig("stacked_bar01.png")
```

**入力**

```text:行名・列名付き表データ(TSV形式)
	group0	group1	group2	group3	group4	group5
R0	17	10	5	8	16	8
R1	12	14	8	10	7	9
R2	12	11	13	13	17	15
R3	6	11	12	12	19	13
R4	6	10	14	18	13	14
R5	9	8	5	8	10	19
R6	5	7	8	13	6	8
R7	18	8	8	19	12	5
R8	6	14	14	5	15	9
R9	12	8	19	16	7	12
R10	17	7	5	5	9	10
R11	10	11	13	9	6	9
```

**出力図**

![stacked_bar01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138429/8d440f42-c45b-caf4-f0be-d695fe7d3316.png)

### 垂直方向プロット例②

最小限のプロットのみだった`垂直方向プロット例①`から凡例や最小・最大値ラベルを追加した例

```python
from pycirclize import Circos
from matplotlib.patches import Patch
import pandas as pd
import numpy as np
np.random.seed(0)

# Generate matrix data for stacked bar plot
row_num, col_num = 12, 6
matrix = np.random.randint(5, 20, (row_num, col_num))
row_names = [f"R{i}" for i in range(row_num)]
col_names = [f"group{i}" for i in range(col_num)]
table_df = pd.DataFrame(matrix, index=row_names, columns=col_names)

# Initialize Circos sector & track (5 <= range <= 355)
circos = Circos(sectors=dict(bar=len(table_df.index)), start=5, end=355)
sector = circos.sectors[0]
track = sector.add_track((50, 100))
track.grid(x_grid_interval=track.size)

# Plot stacked bar & label
colormap = "tab10"
sb_table = track.stacked_bar(
    table_df,
    width=0.6,
    cmap=colormap,
    bar_kws=dict(ec="black", lw=0.2),
    label_pos="top",
    label_kws=dict(size=10, orientation="vertical"),
)
vmin, vmax = 0, sb_table.row_sum_vmax
circos.text(str(vmin), r=min(track.r_lim), color="grey")
circos.text(str(vmax), r=max(track.r_lim), color="grey")

fig = circos.plotfig()

# Plot legends
col_name2color = sb_table.get_col_name2color(colormap)
_ = circos.ax.legend(
    handles=[Patch(label=n, fc=c, ec="black", lw=0.5) for n, c in col_name2color.items()],
    bbox_to_anchor=(0.5, 0.5),
    loc="center",
    ncols=2,
)

fig.savefig("stacked_bar02.png")
```

**出力図**

![stacked_bar02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138429/1f33f545-82a0-34a1-986b-0ef63a49510f.png)


### 水平方向プロット例

```python
from pycirclize import Circos
from pycirclize.parser import StackedBarTable
from matplotlib.patches import Patch
import pandas as pd
import numpy as np
np.random.seed(0)

# Generate & load matrix data for horizontal stacked bar plot
row_names = list("ABCDEF")
col_names = ["group1", "group2", "group3", "group4", "group5", "group6"]
matrix = np.random.randint(5, 20, (len(row_names), len(col_names)))
table_df = pd.DataFrame(matrix, index=row_names, columns=col_names)
table_df.to_csv("stacked_barh_table.tsv", sep="\t")
sb_table = StackedBarTable(table_df)

# Initialize Circos sector & track (0 <= range <= 270)
circos = Circos(sectors=dict(bar=sb_table.row_sum_vmax), start=0, end=270)
sector = circos.sectors[0]
track = sector.add_track((30, 100))
track.axis(fc="lightgrey", ec="black", alpha=0.5)

# Plot horizontal stacked bar & label & xticks
colormap = "tab10"
track.stacked_barh(sb_table.dataframe, cmap=colormap, width=0.6)
label_r_list = sb_table.calc_barh_label_r_list(track.r_plot_lim)
for label_r, row_name in zip(label_r_list, sb_table.row_names):
    track.text(f"{row_name} ", x=0, r=label_r, ha="right")
track.xticks_by_interval(interval=5)
track.xticks_by_interval(interval=1, tick_length=1, show_label=False)

fig = circos.plotfig()

# Plot legends
col_name2color = sb_table.get_col_name2color(colormap)
_ = circos.ax.legend(
    handles=[Patch(label=n, fc=c, ec="black", lw=0.5) for n, c in col_name2color.items()],
    bbox_to_anchor=(0.2, 0.8),
    fontsize=12,
    loc="center",
    ncols=2,
)

fig.savefig("stacked_barh.png")
```

**入力**

```text:行名・列名付き表データ(TSV形式)
	group1	group2	group3	group4	group5	group6
A	17	10	5	8	16	8
B	12	14	8	10	7	9
C	12	11	13	13	17	15
D	6	11	12	12	19	13
E	6	10	14	18	13	14
F	9	8	5	8	10	19
```

**出力図**

![stacked_barh.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138429/15574fe1-6ea7-7d88-42c3-323271e68bca.png)

## おわりに

matplotlibで円形の積み上げ棒グラフをプロットするコードを一から実装しようとすると、そこそこ手間がかかります。pyCirclizeを利用することで、比較的簡単に積み上げ棒グラフを作成できるようになると思います。円形の積み上げ棒グラフを使うことはあまりないと思いますが、興味を持った人がいれば使ってみてください。

https://qiita.com/moshi/items/b0d5b287bc9447948cae