Pythonで円グラフを書いてみる

# はじめに
先日、某所で円グラフを使って特許分析をしているところを見ました。そういえば、円グラフを書いたことがないことに気がつき、今回挑戦してみました。

ただ円グラフを書くだけではつまらないので、今回はChatGPTの力を借りつつ、バブルチャートの各バブルを円グラフとした図を書きたいと思います。

# プログラム
## 仮データ
いきなり特許情報を使用しますとわけがわからなくなりますので、今回は仮データを使用してプログラムの動作を確認したいと思います。

仮データは、課題がAとB、解決手段がCとD、出願人が、甲と乙と丙の適当な組み合わせとしました。

```python
# ライブラリのインポート
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
import japanize_matplotlib

# 仮データの作成
# 課題、解決手段、出願人からなるデータセット
data = {'課題': ['A', 'A', 'B', 'A', 'A', 'A', 'B', 'B'],
        '解決手段': ['C', 'D', 'C', 'D', 'D', 'C', 'C', 'C'],
        '出願人': ['甲', '乙', '甲', '甲', '乙', '乙', '乙', '丙']}
df = pd.DataFrame(data)
```
## バブルチャート用データ集計
groubyを使用して、いつものバブルチャート用のデータを集計します。
```python
# データの集計
# 課題、解決手段、出願人ごとの件数を計算
grouped = df.groupby(['課題', '解決手段', '出願人']).size().reset_index(name='件数')
```
## 円グラフ用の構成比の計算

円グラフ用の構成比を計算し、ピボットテーブルに変換します。
```python
# 構成比の計算
# 課題と解決手段の組み合わせごとに、出願人の構成比を計算し、ピボットテーブルを作成します。
grouped['構成比'] = grouped.groupby(['課題', '解決手段'])['件数'].apply(lambda x: x / x.sum() * 100)

# データをピボットテーブルに変換
# 課題と解決手段の組み合わせをインデックスに、出願人をカラムに、構成比を値に持つピボットテーブルを作成します。
pivoted = grouped.pivot_table(values='構成比', index=['課題', '解決手段'], columns='出願人', fill_value=0).reset_index()
```
pivotedを出力すると以下のようになってます。課題、解決手段と出願人の比率のデータフレームが生成されています。
```
出願人 課題 解決手段          丙          乙          甲
0    A    C   0.000000  50.000000  50.000000
1    A    D   0.000000  66.666667  33.333333
2    B    C  33.333333  33.333333  33.333333
```
## グラフの出力
円グラフの出力はpieとなります。カラーチャートについては、レインボーをやめて、エコっぽいYlGnを使用してみました。

```python
# グラフの作成
fig, ax = plt.subplots()

# データの一意な値を取得
unique_resolution_methods = df['解決手段'].unique()
unique_issues = df['課題'].unique()
unique_applicants = pivoted.columns.tolist()[2:]

# ax2を作成し、axと同じx軸を共有するが独立したy軸を持つようにする
# これにより、円グラフを重ねて表示することができます
ax2 = ax.twinx()

# 円グラフをプロット
for index, row in pivoted.iterrows():
    # 解決手段および課題のインデックスに基づいてグリッド上の位置を決定
    x = unique_resolution_methods.tolist().index(row['解決手段'])
    y = unique_issues.tolist().index(row['課題'])

    # 円グラフのサイズを計算（解決手段と課題の組み合わせごとの件数に基づく）
    size = grouped.loc[grouped['課題'].eq(row['課題']) & grouped['解決手段'].eq(row['解決手段']), '件数'].sum() * 5

    # 各出願人の構成比を取得
    ratio = [row[applicant] for applicant in unique_applicants]

    # 色のマッピングを定義
    colors = cm.get_cmap('YlGn')(np.linspace(0, 1, len(unique_applicants)))

    # 円グラフを描画（ax2を使用し、中心点、半径、色を指定）
    wedges, _ = ax2.pie(ratio, radius=size / 100, center=(x, y), colors=colors, wedgeprops=dict(edgecolor='black'))
```
## 枠線等の描画
枠線の太さが細かったり太かったりしましたので、一旦枠線を消去して、書き直すことをしております。このあたりは趣味の問題となりますので、気にせずともよいかもしれません。
```python
# 軸ラベルの設定
ax.set_xlabel('解決手段')
ax.set_ylabel('課題')

# 軸目盛りの設定
ax.set_xticks(range(len(unique_resolution_methods)))
ax.set_xticklabels(unique_resolution_methods)
ax.set_yticks(range(len(unique_issues)))
ax.set_yticklabels(unique_issues)

# 軸の範囲を設定
ax.set_xlim(-0.5, len(unique_resolution_methods) - 0.5)
ax.set_ylim(-0.5, len(unique_issues) - 0.5)

# ax2のy軸の範囲をaxと同じに設定
ax2.set_ylim(ax.get_ylim())

# ax2の四角を非表示にする
ax2.spines['top'].set_visible(False)
ax2.spines['bottom'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax2.spines['right'].set_visible(False)
ax2.get_yaxis().set_ticks([])

# 各辺の太さを1に設定
for spine in ax.spines.values():
    spine.set_linewidth(1)

# 凡例を追加
ax.legend(wedges, unique_applicants, title="出願人", loc="center left", bbox_to_anchor=(1, 0, 0.5, 1))

# グラフの表示
plt.show()
```
## 出力された図
以下が出力された図です。イメージ通りの図を作成することができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3110569/1802ac80-2ae7-6011-ac7b-fcdfca61c838.png)
# 感想
Pythonを使用しますと、基本的には、どんな図でも作成できるのではないかという気がしてきました。matplotlibの本を買って、もう少し研究しようと思います。

次回は実際の特許データを使用して、マップ化したいと思います。