FFT 스펙트럼과 주요 피크
설명
신호 컬럼의 진폭 스펙트럼을 창 함수와 추세 제거를 골라 계산하고 상위 피크를 찾는다
필요 데이터
수치형 신호 컬럼 1개, 샘플 간격
출력
진폭 스펙트럼 그래프 + 상위 피크 표 (주파수·주기·상대비율)
필요 패키지
- matplotlib
- numpy
- pandas
태그
science, timeseries, visualization
작성자
SDSL | MDS 1.0
신호 컬럼의 진폭 스펙트럼을 창 함수와 추세 제거를 골라 계산하고 상위 피크를 찾는다
Computes an amplitude spectrum with selectable window/detrend and lists top peaks
README
신호 컬럼의 진폭 스펙트럼을 창 함수와 추세 제거를 골라 계산하고 상위 피크를 찾는다
수치형 신호 컬럼 1개, 샘플 간격
진폭 스펙트럼 그래프 + 상위 피크 표 (주파수·주기·상대비율)
science, timeseries, visualization
SDSL | MDS 1.0
# 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)