HOWTO · Python

Python에서 피크 찾기

이 튜토리얼은 Python의 피크 찾기 알고리즘을 보여줍니다.

이 페이지의 내용

국소 최댓값은 주변 샘플보다 큰 값이며, 전체 수열에서 가장 큰 값일 필요는 없습니다. 1차원 수열에서 간단히 찾으려면 양 끝을 제외한 각 값을 이웃 값과 비교하고, 후보를 필터링하려면 scipy.signal.find_peaks()를 사용할 수 있습니다. 이 함수들은 원본 데이터의 0부터 시작하는 인덱스를 반환하므로, 해당 인덱스로 피크 값을 가져오면 됩니다.

NumPy로 간단한 국소 최댓값 찾기

이 간단한 예제는 1차원 배열을 사용해 감지된 각 피크의 인덱스와 값을 출력합니다.

import numpy as np

data = np.array([1, 3, 7, 1, 2, 6, 0, 5, 0, 4])
local_maxima = (data[1:-1] > data[:-2]) & (data[1:-1] > data[2:])
indices = np.flatnonzero(local_maxima) + 1
print("indices:", indices.tolist())
print("values:", data[indices].tolist())
indices: [2, 5, 7]
values: [7, 6, 5]

두 비교식은 샘플이 바로 양옆의 값보다 모두 엄격히 커야 한다는 조건을 적용합니다. 슬라이스는 내부 샘플만 비교하므로 첫 번째와 마지막 요소는 후보가 아니며, 같은 값이 이어지는 평탄한 구간도 제외됩니다. 인덱스 오프셋은 길이가 더 짧은 비교 배열의 위치를 data의 원래 위치로 되돌립니다. 데이터에 이 규칙이 맞는다면 이 NumPy 방식이 유용합니다. 이 입력에서 감지된 인덱스는 [2, 5, 7]이고, 해당 값은 [7, 6, 5]입니다.

scipy.signal.find_peaks()로 피크 필터링

일반적인 1차원 피크 검색에는 SciPy의 scipy.signal.find_peaks()를 사용할 수 있습니다. 이 함수는 국소 최댓값의 인덱스를 반환하며 선택 조건을 적용할 수도 있습니다. 다음 예제는 높이가 3 이상인 피크를 선택하고 인덱스와 값을 모두 출력합니다.

import numpy as np
from scipy.signal import find_peaks

data = np.array([1, 3, 7, 1, 2, 6, 0, 5, 0, 4])
indices, properties = find_peaks(data, height=3, distance=2, prominence=1)
print("indices:", indices.tolist())
print("values:", data[indices].tolist())
indices: [2, 5, 7]
values: [7, 6, 5]

height는 피크 값으로 필터링하고, distance는 유지할 피크 사이의 최소 간격을 샘플 수로 지정하며, prominence는 피크가 주변보다 얼마나 두드러지는지를 나타냅니다. distance는 유지되는 피크에 영향을 주지만 속성으로 반환되지는 않습니다. 그 밖의 선택 조건으로는 width와 threshold가 있습니다. SciPy는 인덱스와 함께, 적용 가능한 요청 조건에 대해 계산된 속성을 담은 사전을 반환합니다. 이 입력과 인수 조합에서 반환된 인덱스는 [2, 5, 7]이고 값은 [7, 6, 5]입니다. 모든 국소 최댓값이 의미 있다고 가정하지 말고, 신호의 특성을 설명하는 조건을 선택하세요.

scipy.signal.argrelextrema()로 상대 극값 찾기

지정한 주변 범위에 걸쳐 샘플을 비교하려면 scipy.signal.argrelextrema()를 사용하세요. 최댓값을 찾을 때는 np.greater와 같은 비교 함수를 전달하고, order에는 비교에 포함할 양쪽 샘플 수를 지정합니다. 결과는 입력 차원마다 하나씩 인덱스 배열을 담은 튜플입니다. 1차원 배열에서는 [0]으로 인덱스 배열에 접근합니다.

import numpy as np
from scipy.signal import argrelextrema

data = np.array([1, 3, 7, 1, 2, 6, 0, 5, 0, 4])
indices = argrelextrema(data, np.greater, order=1)[0]
print("indices:", indices.tolist())
print("values:", data[indices].tolist())
indices: [2, 5, 7]
values: [7, 6, 5]

order=1이면 각 후보를 양쪽의 샘플 하나씩과 비교합니다. order를 더 크게 하면 비교 범위가 넓어져 상대 극값의 결과가 달라질 수 있습니다. 이 입력에서 order=1일 때 반환된 인덱스는 [2, 5, 7]이고 해당 값은 [7, 6, 5]입니다.

detecta.detect_peaks()를 대안으로 사용하기

detecta.detect_peaks()는 1차원 수열에 사용할 수 있는 타사 라이브러리 함수입니다. 프로젝트 환경에 python -m pip install detecta를 실행해 의존성을 설치한 다음, 샘플 데이터를 detect_peaks()에 전달하세요.

data = [1, 3, 7, 1, 2, 6, 0, 5, 0, 4]
try:
    from detecta import detect_peaks
except ImportError:
    print("Install detecta with: python -m pip install detecta")
else:
    indices = detect_peaks(data)
    values = [data[index] for index in indices]
    print("indices:", list(indices))
    print("values:", values)

샘플 수열에서 예상되는 0부터 시작하는 피크 인덱스는 [2, 5, 7]이며, 이에 해당하는 값은 [7, 6, 5]입니다. 제공된 실행 근거에서 확인된 것은 의존성이 없다는 메시지뿐이므로, detecta의 결과는 실제 실행으로 관찰된 출력이 아니라 예상 결과를 비교용으로 제시한 것입니다. 가져오기 보호 코드를 유지하세요. detecta를 사용할 수 없으면 탐지를 시도하는 대신 설치 명령을 출력합니다.

피크 감지 방법 선택

바로 이웃한 샘플보다 엄격히 큰 값만 찾으면 되고 동작 원리를 직접 확인하고 싶다면 NumPy 비교 방식을 사용하세요. 높이, 간격, prominence 또는 너비 같은 조건으로 필터링해야 한다면 scipy.signal.find_peaks()를 선택하세요. 극값의 정의에서 비교 범위와 비교 함수가 중요하다면 scipy.signal.argrelextrema()를 사용하세요. 타사 API가 프로젝트에 맞고 의존성을 추가할 수 있다면 detecta.detect_peaks()도 고려할 수 있습니다.

반환되는 모든 위치는 입력 수열에서 0부터 시작하는 인덱스입니다. 결과를 해석하기 전에 사용한 방법의 정의가 데이터에 맞는지 확인하세요. 양 끝 처리, 평탄한 구간 처리, 필터 조건은 방법마다 다릅니다. 위 예제는 1차원 숫자 데이터의 국소 최댓값을 다룹니다. 다차원 피크 감지에는 해당 데이터 형태에 맞는 방법이 필요합니다.