流下能力図をmatplotlibで描く

**実行環境**
Windows10
Python 3.9.5
matpotlib 3.7.1

## はじめに
流下能力図という階段状のグラフを [matplotlib](https://matplotlib.org/) で描く方法を紹介します。

土木、なかでも河川計画分野の話になります。

私自身は専門家ではないのでふんわりとした説明になってしまうのですが、まず流下能力の前知識について記載しておきます。（ご存じの方はさっくり読み流してください）

流下能力というのは、川がどのくらいの量の水を流すことができるのかという能力をあらわす指標のことです。

単位は m3/s が使われますが、この分野の技術者の方はこれを「トン」と表現したりします。（1.0 m3 の水の重さが 1 t ）

流下能力図というのは、その流下能力を見える化したグラフのことです。

x 軸に下流からの距離をとり、y 軸に流下能力を示します。

これだけの話なら棒グラフや折れ線グラフを描けば終わるのですが、現実は複雑です。

たとえば、以下のリンクを見てください。

http://www.shizuoka-kasen-navi.jp/html/koishi/project_02.html

静岡県の小石川という河川の現況流下能力図が載っています。

多くの人にとっては、なじみの薄いグラフでしょう。

河川は上流から見て右側を右岸、左側を左岸といいます。

ひとつの川の流下能力図は一般的に（少なくとも私の知るかぎり）、右岸の流下能力図をグラフ上側に、左岸の流下能力図を y 軸を反転させてグラフ下側に描きます。

また、各距離地点の流下能力が x 軸方向に幅を持って表現されるため、流下能力を示す青色の線が階段状になっています。

これをエクセルで描くとなると、階段の全頂点の xy 座標を散布図で描く、あるいは、隙間のない棒グラフを描いてから隣どうし共有する線を消す、という相応のノウハウが必要になります。

そこで、この手間のかかる流下能力図を matplotlib を使って簡単に描こう、というのが今回の趣旨になります。

## グラフ化する要素
ここでは、流下能力と計画流量の2項目をグラフ化したいと思います。

先の小石川のグラフでいうと、ステップの多い青色の線が流下能力、ステップの少ない赤色の線が計画流量になります。

凡例には、それぞれ「流下能力（堤防満杯）」と「基本高水流量（確率規模1/30）」という専門用語で示されています。

流下能力は左右岸それぞれの堤防高に応じて値が異なることがあるので、左右岸それぞれの値を使います。

計画流量は目標とする流下能力のことで、河川をいくつかの区間に区切って、区間ごとに左右岸とも同じ値が決められています。

橋や水門などの位置を示すラベルについては、話をコンパクトにするために省略します。

## step() と stairs()
前置きが長くなりましたが、ここから matplotlib でグラフを描く具体的な方法について説明します。

matplotlib で階段状のグラフを描く場合、step 関数を使う方法と、stairs 関数を使う方法があります。

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.step.html

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.stairs.html

詳細は上記リファレンスを見ていただくとして、かいつまんで説明します。

### step()

まず step 関数は、同じ要素数の xy を使い、x 方向に幅を持たせることで階段を表現します。

幅の方向は where 引数で指定でき、'pre', 'post', 'mid' から選べます。

### stairs()

一方 stairs 関数では、x の要素数を y の要素数より 1 多くします。

これは x が y の値を区切る境界値として使われるためで、したがって step 関数のような where 引数はありません。

引数の名前も y が values、x が edges となっていて、step 関数と意味的にも区別されています。

### 比較

両関数の比較については、こちらのデモがわかりやすいです。

https://matplotlib.org/stable/gallery/lines_bars_and_markers/stairs_demo.html#comparison-of-pyplot-step-and-pyplot-stairs

今回は step 関数で流下能力を、stairs 関数で計画流量を実装してみます。

なお、位置指定で引数を渡す場合は、両関数で xy の指定の順番が違うので注意してください。

## サンプルデータ
河川の計画流量の値は公表されていますが、流下能力の値は一般に出回っていません。

したがってここでは、実在する河川ではなく、架空の川の流下能力図を描きたいと思います。

以下のコードを使ってかなり乱暴に値を設定していますが、実務では実際の緒元、計画で使われている値を使ってください。

```python
import random

import matplotlib.pyplot as plt

pitch = 0.1  # 距離間隔 [km]
y_max = 150  # 最下流のだいたいの流下能力 [m3/s]
x_max = 5.0  # 最上流の距離 [km]
dy = 20  # 流下能力のブレに使う係数


def makexy():
    """
    左右岸の流下能力のサンプルを生成する関数
    距離 x のリストと、バラつきをもった流下能力 y のリストを生成する
    """
    xlist = []  # 距離のリスト
    ylist = []  # 流下能力のリスト

    n = int(x_max/pitch)  # リストの要素数

    for i in range(n + 1):
        x = i * pitch
        y = y_max - x * y_max/x_max + (random.random() - 0.5) * dy + 10
        if y < 0:
            y = 0
        xlist.append(x)
        ylist.append(y)

    return xlist, ylist


# 計画の距離区分 (n=6)
x_plan = [0, 1.0, 2.0, 3.0, 4.0, 5.0]
# 計画流量 (n=5)
y_plan = [160, 120, 90, 60, 30]

# 左岸の距離と流下能力 (それぞれ n=51)
x_lbank, y_lbank = makexy()
# 右岸の距離と流下能力 (それぞれ n=51)
x_rbank, y_rbank = makexy()

```

計画流量を stairs 関数で実装するので、距離の要素数が計画流量の要素数より 1 多くなります。

## グラフ化
先ほどのコードの続きに、matplotlib を使った処理を追加します。

右岸用と左岸用の2つの axes に対してそれぞれ流下能力と計画流量をプロットし、流下能力図っぽくなるように、もろもろの設定を調整しています。

```python
# 日本語フォントを指定
plt.rcParams['font.family'] = "MS Gothic"

# 右岸用と左岸用の2つのグラフを用意、x軸を共有
fig, axs = plt.subplots(2, figsize=(10, 5), sharex=True)

# 両グラフ間の間隔をつめる
fig.subplots_adjust(hspace=0)

# グラフの上にタイトルを表示
fig.suptitle('架空川')
# グラフの左にy軸ラベルを表示
fig.supylabel('流下能力 [m3/s]')

# 右岸用のグラフの左にy軸ラベルを表示
axs[0].set_ylabel('右岸')
# 左岸用のグラフの左にy軸ラベルを表示
axs[1].set_ylabel('左岸')

# 左岸用のグラフの下にx軸ラベルを表示
axs[1].set_xlabel('距離 [km]')

# 右岸用の流下能力を step で描画、距離の中間で値を変える
axs[0].step(x_lbank, y_lbank, where='mid', color='blue')
axs[1].step(x_rbank, y_rbank, where='mid', color='blue')

# 左右岸共通の設定
for ax in axs:
    # 左右岸の計画流量を stairs で描画、端点に垂線を設けない
    ax.stairs(y_plan, x_plan, baseline=None, color='red')
    # グリッド表示
    ax.grid()
    # グリッドを最下層に表示
    ax.set_axisbelow(True)
    # 軸の範囲
    ax.set_xlim(0, 5.0)
    ax.set_ylim(0, 200)

# 左岸用のグラフの y 軸を反転
axs[1].invert_yaxis()

plt.show()

```
step 関数の where 引数を 'mid' とし、指定の距離 x に対して前後（上下流）に階段の平らな面がくるようにしています。

また、stairs 関数の baseline 引数を None とし、端点（上流下流端）に垂線が生じないようにしています。

出力されるグラフがこちらになります。

![流下能力図_架空川.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1409923/b288e251-5bcb-128b-c630-2b0565ccfd93.png)


それなりに流下能力図らしいグラフになりました。

## おわりに
matplotlib を使うことで、エクセルよりも直観的に流下能力図を描くことができました。

ただ実務上は、この上に橋梁や水門の位置を載せたり、流下能力不足の区間を示したりすることがあろうかと思いますので、そのあたりに改良の余地がまだまだあります。

とか言っている昨今ですが、エクセルで python が使えるように[^1] なったみたいなので、なんかうまいこと楽にエクセル上で流下能力図を描けるようになるのかもですね。

[^1]: [Announcing Python in Excel](https://techcommunity.microsoft.com/t5/excel-blog/announcing-python-in-excel-combining-the-power-of-python-and-the/ba-p/3893439)