【ピーク検出】find_peaksのプロミネンスに関して実例で見てみる

# 0. はじめに

この生成AI時代に私がピーク検出を始めて検討した時も「Claude3」「GPT 4o」等活用してきましたが、どの回答も起きている現象を理解することが出来ず間違って回答しており、**結局ドキュメントを見ながら検証をした**のでそれを共有するものです。

特にscipyのfind_peaksに関して色々検証しましたが、**本記事では生成系AIが間違った解釈を返信し。私が一番最後まで理解が難しかった「prominence」とは何ぞや？というのを図を交えて実例で説明する記事です**
気軽にメモ代わりに書いたので、ググってたどり着いた方は良ければ読んでいってください

https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.find_peaks.html

※以下が同じような概念のpeak_prominencesです(公式ではここの図が一番わかりやすいかもです)

https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.peak_prominences.html#peak-prominences

# 1. prominence(プロミネンス)とは何か

何かデータがあった時に、そのデータの中で「目立った突出度」を持つ部分を示すものです。
要するに、**その中で目立ったピークはどの部分か？を示すための測定パラメータ**だという感じです。

実際にはピークと自分の左右の「谷」を比較してどれくらい突出してるか？を計算します

# 2. 実例で見ていく

今回は検証の可視化でプロミネンスを説明するので、サンプルデータを用意する

```python: 検証用Notebook.ipynb
import numpy as np
import matplotlib.pyplot as plt

# 波形サンプル
x = np.array([2, 3, 10, 6, 4, 12, 12, 12, 4, 4, 4, 14, 14, 5, 3, 1])

# グラフの作成
plt.figure(figsize=(12, 6))
plt.plot(range(0, len(x)), x, marker='o', linestyle='-', markersize=8)

# グラフの装飾
plt.title('prominence sample', fontsize=16)
plt.xlabel('Index', fontsize=12)
plt.ylabel('Value', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)

# X軸を整数値に設定
plt.xticks(range(0, len(x)))

# グラフの表示
plt.tight_layout()
plt.show()
```

![サンプルデータ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/06ee48ea-a9ba-062f-84dc-928d3e0cadd2.png)


# 3. ピークの検出

以下コードをまずは**prominence無しのデフォルトパラメータ**で見てみる。

```python: 検証用Notebook.ipynb
from scipy.signal import find_peaks

peaks, _ = find_peaks(x)

# 検出されたピークとプロパティを表示
print("ピークのインデックス:", peaks)
```

```CMD:出力
ピークのインデックス: [ 2  6 11]
```

つまり何も設定しないと以下ピンク丸部分がピークと捉えられていることがわかる。

ここのピークの設定は単純で「**前のデータより数値が大きく、後ろのデータよりも数値が大きい」箇所がピーク**となる。ただし、**同じ値が連続するケースは「真ん中の点」「もしくは偶数なら真ん中の左側の点」となる**らしい。
※index11と12の場合は中間は11.5なのでその左側の11がピークとみなされる

![サンプル2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/d87775fb-4a73-5df4-9152-f70e5bebd4e1.png)


# 4. プロミネンスを設定したピークの検出

ゴチャゴチャ説明するより図を見た方が早いです。
①まず3の**ピークから左右に線**を引きます（グレー線）
②もし**途中でグラフとぶつかればその`ぶつかる範囲までの間での一番値が低い点`(同率の場合は一番の若番※添付画像はindex10で引いてますが誤りでindex8です、すいません)、ぶつからなければ一番端のindex**を見る
※図では左側を緑、右側を橙で書きました。index4は交点のindexが無いので1個手前です
※同じ値が連続した場合はぶつかった扱いにしない(仮に1回でも下がれば別だが)
③**各ピークとそれぞれ指定したindexの値を減算**する
④**左右で比較し「より低い数字」になったものが「プロミセンス値**」となる(目立ち具合)

![サンプル3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/eed5c9d2-718b-53b7-36fe-4c52bc1b9723.png)

さて、これがわかった後で「プロミネンスを5」としてピークを計算する

```python: 検証用Notebook.ipynb
# prominenceを5で指定
peaks, properties = find_peaks(x, prominence=5)

# 検出されたピークとプロパティを表示
print("ピークのインデックス:", peaks)
print("プロミネンス:", properties['prominences'])
print("左側のベースindex:", properties['left_bases'])
print("右側のベースindex:", properties['right_bases'])
```

```CMD:出力
ピークのインデックス: [ 2  6 11]
プロミネンス: [ 6.  8. 12.]
左側のベースindex: [0 0 0]
右側のベースindex: [ 4  8 15]
```

まだこれでは何も変わっていない。これは上図の赤線のどの数字よりも低い数字だからである。
では7で設定したらどうなる？

```python: 検証用Notebook.ipynb
# prominenceを7で指定
peaks, properties = find_peaks(x, prominence=7)

# 検出されたピークとプロパティを表示
print("ピークのインデックス:", peaks)
print("プロミネンス:", properties['prominences'])
print("左側のベースindex:", properties['left_bases'])
print("右側のベースindex:", properties['right_bases'])
```

```CMD:出力
ピークのインデックス: [ 6 11]
プロミネンス: [ 8. 12.]
左側のベースindex: [0 0]
右側のベースindex: [ 8 15]
```

**今度はindex2が消えた(ピークとみなさなくなった)、つまりこれがprominence設定である**

<font color="Red">※何も設定しないと検出してしまうピーク値に対して、prominence値を計算し、それを閾値として用いて「より目立ったピーク」を検出するための仕組み</font>

# 5. さいごに

今回は簡単な例だったが、これが複雑な波形で検出させるときになぜかピークとして検出されない例があり、この原因を生成AIに問うてきたが、残念ながら厳密な解を出してはくれなかった。※そもそも生成AIはピークの説明もうまくできなかった(連続した同一な値があった時にその中の最初がピークになると私に説明していたが、明らかに実態ではこの記事に書いたように中間が基準になる)

ピーク検出をする必要がある誰かのお役に立てればと思い、**小難しい理論はさておき、実例として載せました**
それこそ細かい定義は生成AIや専門書に譲ります

それでは今回はここまで。