三角図表（ternary diagram）を描く

以下の文献に三角図表が使われていたので，さほど使うこともないので放っておいたのだが，今回一念発起で描いてみた。
当然，Pythonで書かれたプログラムは山ほどある。車輪の再発明である。自分で書くと，思うように書ける。

(1) は (2) の紹介記事。

(1) アストロアーツ
分子雲内で複雑有機分子ができる過程を量子化学計算で検証
https://news.google.com/articles/CBMiNGh0dHBzOi8vd3d3LmFzdHJvYXJ0cy5jby5qcC9hcnRpY2xlL2hsL2EvMTMyMDlfcnl1Z3XSAQA?hl=ja&gl=JP&ceid=JP%3Aja

(2) Yu Komatsu* and Kenji Furuya: The Automated Reaction Pathway Search Reveals the Energetically Favorable Synthesis of Interstellar CH3OCH3 and HCOOCH3
Cite this: ACS Earth Space Chem. 2023, 7, 9, 1753–1760
Publication Date:August 17, 2023
Copyright © 2023 American Chemical Society
https://pubs.acs.org/doi/10.1021/acsearthspacechem.3c00117

以下の文献に三角図表が使われていたので，描いてみた。

アストロアーツ
分子雲内で複雑有機分子ができる過程を量子化学計算で検証
https://news.google.com/articles/CBMiNGh0dHBzOi8vd3d3LmFzdHJvYXJ0cy5jby5qcC9hcnRpY2xlL2hsL2EvMTMyMDlfcnl1Z3XSAQA?hl=ja&gl=JP&ceid=JP%3Aja

アストロバイオロジーセンター：複雑有機分子が極低温の分子雲内でできる過程を量子化学計算で検証
https://www.abc-nins.jp/570/

Yu Komatsu* and Kenji Furuya: The Automated Reaction Pathway Search Reveals the Energetically Favorable Synthesis of Interstellar CH3OCH3 and HCOOCH3
Cite this: ACS Earth Space Chem. 2023, 7, 9, 1753–1760
Publication Date:August 17, 2023
https://doi.org/10.1021/acsearthspacechem.3c00117
Copyright © 2023 American Chemical Society
https://pubs.acs.org/doi/10.1021/acsearthspacechem.3c00117

3つの変数の観測値（構成比の合計が 100% になるような場合）を正三角形内にプロットする三角図表（ternary diagram）を描く。

このプログラム（関数）では、Python の Matplotlib, numpy ライブラリを使用する。
軸（変数）の名前を正三角形の頂点に描くやり方もあるが，読み方がちょっと面倒な気がする。


```python
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

def ternary_diagram(df, title='Ternary Diagram', lw=0.25, color='blue', marker='o', s=10, index=False):
    """
    3 変数 `var1`、`var2`、`var3` の3列からなるデータフレームで与える（変数名はデータフレームの列名を転用する）。
    このプログラムは、正三角形の頂点座標を `[0, 0]`，`[1, 0]`，`[1/2, np.sqrt(3) / 2]` に設定し、
    `var1` を AB 方向の長さ、`var2` を BC 方向の長さ、`var3` を CA 方向の長さとして計算しプロットする。
    プロットに使う色は `color`，マーカーの種類は `marker`，サイズは `s` で指定できる。
    `index=True` を指定するとデータポイントの上に df.index をラベルとして表示する。
    使用例
    df = pd.DataFrame({'x' : [0.2, 0.3, 0.4],
                   'y' : [0.3, 0.2, 0.3],
                   'z' : [0.5, 0.5, 0.3]})
    ternary_diagram(df, title='三角グラフ')
    """

    # 正三角形の頂点座標（外枠を閉じるために4点目を追加）
    vertices = np.array([[0, 0], [1, 0], [1/2, np.sqrt(3) / 2], [0, 0]])

    # プロットデータの初期化
    n = len(df)
    labels = df.columns
    indexes = df.index
    plot_data = np.zeros((n, 2))
    var1 = df.iloc[:, 0].copy()
    var2 = df.iloc[:, 1].copy()
    var3 = df.iloc[:, 2].copy()        

    # 各変数の値を正規化してデータポイントの x-y 座標値を計算）
    for i in range(n):
        total = var1[i] + var2[i] + var3[i]
        var1[i] /= total
        var2[i] /= total
        var3[i] /= total
        plot_data[i] = vertices[0] + var1[i] * (vertices[1] - vertices[0]) + var2[i] * (vertices[2] - vertices[0])
        if index == True:
            plt.text(plot_data[i, 0], plot_data[i][1]+0.02, indexes[i], ha='center', va='bottom', fontsize=8)

    # グリッド線の描画（AB, BC, CA に平行な破線）
    for a in [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]:
        plt.text(1 - a/2, a * np.sqrt(3) / 2, f' {a}', ha='left', fontsize=9)
        plt.text(a/2, a * np.sqrt(3) / 2, f'{round(1 - a, 1)} ', ha='right', fontsize=9)
        plt.text(a, -0.05, a, ha='center', va='center', fontsize=9)
        if a != 0.0 and a != 1.0:
            plt.plot([a/2, 1 - a/2], [a * np.sqrt(3) / 2, a * np.sqrt(3) / 2], 'k--', linewidth=lw)
            plt.plot([a, a/2], [0, a * np.sqrt(3) / 2], 'k--', linewidth=lw)
            plt.plot([a, (1 + a)/2], [0, (1 - a) * np.sqrt(3) / 2], 'k--', linewidth=lw)

    # プロット（外側の正三角形を描画，プロットポイントをマーカーで描画
    plt.plot(vertices[:, 0], vertices[:, 1], 'k-', linewidth=lw*2)
    plt.scatter(plot_data[:, 0], plot_data[:, 1], color=color, marker=marker, s=s)

    # 軸のラベルを追加
    delta = 0.125
    plt.text(0.5, -0.1, labels[0], ha='center', va='center', fontsize=14)
    plt.text(0.75+delta, np.sqrt(3)/4, labels[1], ha='center', va='center', fontsize=14, rotation=-60)
    plt.text(0.25-delta, np.sqrt(3)/4, labels[2], ha='center', va='center', fontsize=14, rotation=60)
    
    plt.xlim(-0.05, 1.05)
    plt.ylim(-0.15, 1.0)
    plt.title(title, fontsize=16)
    plt.axis('off')  # 座標軸を非表示
    plt.axis('equal')  # アスペクト比を1:1に設定
    plt.show()
```


```python
# 使用例
df = pd.DataFrame({'x' : [0.2, 0.3, 0.4],
                   'y' : [0.3, 0.2, 0.3],
                   'z' : [0.5, 0.5, 0.3]})
df
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>x</th>
      <th>y</th>
      <th>z</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.2</td>
      <td>0.3</td>
      <td>0.5</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.3</td>
      <td>0.2</td>
      <td>0.5</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.4</td>
      <td>0.3</td>
      <td>0.3</td>
    </tr>
  </tbody>
</table>
</div>




```python
ternary_diagram(df, title='三角グラフ')
```


    
![output_5_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611739/20c7025b-30c8-29c2-cd2d-10c2f9349c5b.png)




```python
df2 = pd.DataFrame({'第1次産業' : [3.8, 47.2, 33.6, 1.1, 1.5, 13.6],
                    '第2次産業' : [24.9, 24.7, 30.3, 18.7, 18.3, 23.8],
                    '第3次産業' : [70.0, 28.1, 36.1, 79.3, 80.2, 62.0]},
                    index=['日本', 'インド', '中国', 'イギリス', 'アメリカ', 'メキシコ'])
df2
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>第1次産業</th>
      <th>第2次産業</th>
      <th>第3次産業</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>日本</th>
      <td>3.8</td>
      <td>24.9</td>
      <td>70.0</td>
    </tr>
    <tr>
      <th>インド</th>
      <td>47.2</td>
      <td>24.7</td>
      <td>28.1</td>
    </tr>
    <tr>
      <th>中国</th>
      <td>33.6</td>
      <td>30.3</td>
      <td>36.1</td>
    </tr>
    <tr>
      <th>イギリス</th>
      <td>1.1</td>
      <td>18.7</td>
      <td>79.3</td>
    </tr>
    <tr>
      <th>アメリカ</th>
      <td>1.5</td>
      <td>18.3</td>
      <td>80.2</td>
    </tr>
    <tr>
      <th>メキシコ</th>
      <td>13.6</td>
      <td>23.8</td>
      <td>62.0</td>
    </tr>
  </tbody>
</table>
</div>




```python
ternary_diagram(df2, title='国別産業人口割合', index=True)
```


    
![output_7_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611739/94467884-3170-df4f-25f1-a82c520210c9.png)