← .mpy 공유

FFT 스펙트럼과 주요 피크

신호 컬럼의 진폭 스펙트럼을 창 함수와 추세 제거를 골라 계산하고 상위 피크를 찾는다

English

Computes an amplitude spectrum with selectable window/detrend and lists top peaks

실행 결과 미리보기가 없는 스크립트입니다

README

FFT 스펙트럼과 주요 피크

설명

신호 컬럼의 진폭 스펙트럼을 창 함수와 추세 제거를 골라 계산하고 상위 피크를 찾는다

필요 데이터

수치형 신호 컬럼 1개, 샘플 간격

출력

진폭 스펙트럼 그래프 + 상위 피크 표 (주파수·주기·상대비율)

필요 패키지

  • matplotlib
  • numpy
  • pandas

태그

science, timeseries, visualization

작성자

SDSL | MDS 1.0

Source · 97줄
# MDS_TITLE: FFT 스펙트럼과 주요 피크
# MDS_REQUIRES: matplotlib, numpy, pandas
# MDS_DESC: 신호 컬럼의 진폭 스펙트럼을 창 함수와 추세 제거를 골라 계산하고 상위 피크를 찾는다
# MDS_DESC[en]: Computes an amplitude spectrum with selectable window/detrend and lists top peaks
# MDS_TAGS: science, timeseries, visualization
# MDS_INPUT: 수치형 신호 컬럼 1개, 샘플 간격
# MDS_OUTPUT: 진폭 스펙트럼 그래프 + 상위 피크 표 (주파수·주기·상대비율)
# MDS_AUTHOR: SDSL
# MDS_VERSION: 1.0
def preprocess(y: pd.Series, detrend: str, window: str) -> tuple[np.ndarray, float]:
    arr = pd.to_numeric(y, errors='coerce').dropna().to_numpy(dtype=float)
    if arr.size < 2:
        return arr, 1.0
    if detrend == '선형추세 제거':
        x = np.arange(arr.size, dtype=float)
        arr = arr - np.polyval(np.polyfit(x, arr, 1), x)
    elif detrend == '평균 제거':
        arr = arr - float(np.mean(arr))
    if window == 'Hann':
        w = np.hanning(arr.size)
    elif window == 'Hamming':
        w = np.hamming(arr.size)
    else:
        return arr, 1.0
    return arr * w, 1.0 / float(np.mean(w))


def amplitude_spectrum(arr: np.ndarray, delta: float, corr: float) -> pd.DataFrame:
    n = arr.size
    amp = np.abs(np.fft.rfft(arr)) * (2.0 / n) * corr
    amp[0] = amp[0] / 2.0
    if n % 2 == 0 and amp.size > 1:
        amp[-1] = amp[-1] / 2.0
    return pd.DataFrame({'주파수': np.fft.rfftfreq(n, d=delta), '진폭': amp})


def top_peaks(spec: pd.DataFrame, n_peaks: int) -> pd.DataFrame:
    amp = spec['진폭'].to_numpy()
    idx = [i for i in range(1, amp.size - 1) if amp[i] >= amp[i - 1] and amp[i] > amp[i + 1]]
    idx = sorted(idx, key=lambda i: amp[i], reverse=True)[:n_peaks]
    if not idx:
        return pd.DataFrame(columns=['순위', '주파수', '주기', '진폭', '상대비율(%)'])
    ref = float(amp[idx[0]]) or 1.0
    rows = []
    for rank, i in enumerate(idx, start=1):
        f = float(spec['주파수'].iloc[i])
        rows.append({'순위': rank, '주파수': f, '주기': 1.0 / f if f else np.nan,
                     '진폭': float(amp[i]), '상대비율(%)': round(float(amp[i]) / ref * 100.0, 2)})
    return pd.DataFrame(rows)


def plot_spectrum(spec: pd.DataFrame, peaks: pd.DataFrame, title: str) -> None:
    try:
        plt.style.use('seaborn-v0_8-whitegrid')
    except OSError:
        plt.style.use('seaborn-whitegrid')
    plt.rcParams['font.family'] = 'Malgun Gothic'
    plt.rcParams['axes.unicode_minus'] = False
    fig, ax = plt.subplots(figsize=(11, 5))
    ax.plot(spec['주파수'], spec['진폭'], linewidth=1.2, color='#4C72B0')
    ax.scatter(peaks['주파수'], peaks['진폭'], color='#C44E52', s=30, zorder=3, label='상위 피크')
    ax.set_title(f'{title} — 진폭 스펙트럼', fontsize=12, fontweight='semibold')
    ax.set_xlabel('주파수')
    ax.set_ylabel('진폭')
    ax.legend()
    ax.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.show()
    return None


def run_fft_spectrum(df: pd.DataFrame) -> pd.DataFrame | None:
    numeric_cols = df.select_dtypes(include='number').columns.tolist()
    if not numeric_cols:
        mIO.print('수치형 컬럼이 없습니다.')
        return None
    col = mIO.combo('분석할 신호 컬럼', numeric_cols, default=numeric_cols[0])
    if col is None:
        return None
    detrend = mIO.combo('추세 처리', ['평균 제거', '선형추세 제거', '없음'], default='평균 제거')
    window = mIO.combo('창 함수', ['Hann', 'Hamming', '없음'], default='Hann')
    try:
        delta = float(mIO.input('샘플 간격을 입력하세요 (기본 1)') or 1.0)
    except ValueError:
        delta = 1.0
    arr, corr = preprocess(df[col], detrend, window)
    if arr.size < 8:
        mIO.print('유효한 샘플이 8개 미만이라 FFT를 계산할 수 없습니다.')
        return None
    spec = amplitude_spectrum(arr, delta if delta > 0 else 1.0, corr)
    peaks = top_peaks(spec, 5)
    plot_spectrum(spec, peaks, str(col))
    return peaks


result = run_fft_spectrum(df)