Python

SciPy: Scientific Computing dengan Python

Tutorial lengkap SciPy — optimization, interpolation, signal processing, statistik, linear algebra, dan pemecahan masalah numerik

1. Pengenalan SciPy

SciPy (Scientific Python) adalah library Python yang menyediakan algoritma numerik dan fungsi matematika untuk scientific computing. Dibangun di atas NumPy, SciPy melengkapi ekosistem scientific Python dengan modul-modul khusus untuk optimization, integration, interpolation, signal processing, linear algebra, dan banyak lagi.

SciPy adalah fondasi dari banyak riset sains dan engineering. Mulai dari analisis data eksperimen fisika, simulasi struktur jembatan, processing sinyal audio, hingga optimasi portofolio keuangan — semua bisa dilakukan dengan SciPy.

Modul SciPy

Modul Fungsi Contoh Penggunaan
scipy.optimizeOptimasi & root findingMinimalkan biaya, cari akar persamaan
scipy.interpolateInterpolasi dataIsi data yang hilang, kurva fitting
scipy.signalSignal processingFilter audio, FFT, deteksi puncak
scipy.linalgLinear algebraMatrix decomposition, eigenvalues
scipy.statsStatistikDistribusi probabilitas, uji hipotesis
scipy.integrateNumerical integrationIntegral, ODE solver
scipy.sparseSparse matricesGraph problems, large-scale computation
scipy.spatialSpatial algorithmsNearest neighbor, convex hull, KDTree
scipy.ndimageN-dimensional imageImage processing, filtering
# Instalasi
pip install scipy numpy matplotlib

# Import modul yang dibutuhkan
import numpy as np
from scipy import optimize, interpolate, signal, linalg, stats, integrate
import matplotlib.pyplot as plt

2. Optimization

Modul scipy.optimize menyediakan berbagai algoritma untuk menemukan minimum/maximum fungsi, mencari akar persamaan, dan curve fitting. Ini sangat penting dalam machine learning, engineering, dan riset operasi.

Minimisasi Fungsi

import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt

# Fungsi yang akan diminimalkan: f(x) = (x-3)^2 + 2*x + 5
def objective(x):
    return (x - 3)**2 + 2*x + 5

# Minimisasi dari tebakan awal x=0
hasil = minimize(objective, x0=0, method='BFGS')
print(f"Minimum di x = {hasil.x[0]:.4f}")
print(f"Nilai minimum = {hasil.fun:.4f}")
print(f"Konvergen? {hasil.success}")
print(f"Iterasi: {hasil.nit}")

# Visualisasi
x = np.linspace(-2, 8, 100)
y = [objective(xi) for xi in x]
plt.figure(figsize=(10, 6))
plt.plot(x, y, 'b-', linewidth=2, label='f(x) = (x-3)² + 2x + 5')
plt.plot(hasil.x[0], hasil.fun, 'r*', markersize=15, label=f'Minimum ({hasil.x[0]:.2f}, {hasil.fun:.2f})')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('Optimasi Fungsi dengan SciPy')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('optimization.png', dpi=150)
plt.show()

Optimasi Multivariabel

import numpy as np
from scipy.optimize import minimize

# Fungsi Rosenbrock: benchmark standar untuk optimasi
# f(x,y) = (1-x)^2 + 100*(y-x^2)^2
def rosenbrock(params):
    x, y = params
    return (1 - x)**2 + 100 * (y - x**2)**2

# Minimisasi dari titik awal
hasil = minimize(rosenbrock, x0=[0, 0], method='Nelder-Mead')
print(f"Minimum di x={hasil.x[0]:.6f}, y={hasil.x[1]:.6f}")
print(f"Nilai minimum = {hasil.fun:.10f}")  # Harusnya ~0 di (1,1)

# Metode L-BFGS-B dengan batasan (bounds)
from scipy.optimize import minimize, Bounds

bounds = Bounds([0, 0], [5, 5])  # x,y antara 0 dan 5
hasil = minimize(rosenbrock, x0=[0, 0], method='L-BFGS-B', bounds=bounds)
print(f"Dengan batasan: x={hasil.x[0]:.4f}, y={hasil.x[1]:.4f}")

Root Finding

import numpy as np
from scipy.optimize import root, root_scalar

# Cari akar: x^3 - 6x^2 + 11x - 6 = 0
# (Akar: x=1, x=2, x=3)

def equation(x):
    return x**3 - 6*x**2 + 11*x - 6

# Single variable root
for x0 in [0, 1.5, 2.5, 4]:
    sol = root_scalar(equation, bracket=[x0-0.5, x0+0.5], method='brentq')
    print(f"  Akar near {x0}: x = {sol.root:.6f}")

# Multivariable root finding
def equations(vars):
    x, y = vars
    eq1 = x**2 + y**2 - 4  # x^2 + y^2 = 4
    eq2 = x - y              # x = y
    return [eq1, eq2]

sol = root(equations, x0=[1, 1])
print(f"\nSolusi: x={sol.x[0]:.4f}, y={sol.x[1]:.4f}")
print(f"Verifikasi: {sol.fun}")  # Harusnya ~[0, 0]

Curve Fitting

import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt

# Data eksperimen (suhu vs waktu)
waktu = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
suhu = np.array([95, 82, 71, 62, 55, 49, 44, 40, 37, 35, 34])

# Model: pendinginan Newton T(t) = T_env + (T0 - T_env) * exp(-k*t)
def model(t, T_env, T0, k):
    return T_env + (T0 - T_env) * np.exp(-k * t)

# Fit parameter
popt, pcov = curve_fit(model, waktu, suhu, p0=[30, 95, 0.3])
T_env, T0, k = popt
perr = np.sqrt(np.diag(pcov))  # Standard error

print(f"T_env = {T_env:.2f} ± {perr[0]:.2f}")
print(f"T0 = {T0:.2f} ± {perr[1]:.2f}")
print(f"k = {k:.4f} ± {perr[2]:.4f}")

# Visualisasi
t_fit = np.linspace(0, 15, 100)
plt.figure(figsize=(10, 6))
plt.scatter(waktu, suhu, color='red', label='Data Eksperimen')
plt.plot(t_fit, model(t_fit, *popt), 'b-', label=f'Fit: T={T_env:.1f}+{T0-T_env:.1f}*e^(-{k:.3f}t)')
plt.xlabel('Waktu (menit)')
plt.ylabel('Suhu (°C)')
plt.title('Pendinginan Newton - Curve Fitting')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('curve_fitting.png', dpi=150)
plt.show()

3. Interpolation

Interpolasi digunakan untuk memperkirakan nilai di antara titik-titik data yang diketahui. Sangat berguna untuk mengisi data yang hilang, resampling, atau membuat kurva halus dari data kasar.

import numpy as np
from scipy import interpolate
import matplotlib.pyplot as plt

# Data asli (kasar)
x_data = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
y_data = np.array([0, 0.8, 0.9, 0.1, -0.8, -1.0, -0.3, 0.7, 1.0])

# Berbagai metode interpolasi
x_fine = np.linspace(0, 8, 200)

# 1. Linear Interpolation
f_linear = interpolate.interp1d(x_data, y_data, kind='linear')
y_linear = f_linear(x_fine)

# 2. Cubic Spline
f_cubic = interpolate.interp1d(x_data, y_data, kind='cubic')
y_cubic = f_cubic(x_fine)

# 3. Akima (menghindari overshooting)
f_akima = interpolate.Akima1DInterpolator(x_data, y_data)
y_akima = f_akima(x_fine)

# Visualisasi
plt.figure(figsize=(12, 6))
plt.scatter(x_data, y_data, color='red', s=100, zorder=5, label='Data Asli')
plt.plot(x_fine, y_linear, '--', label='Linear', alpha=0.7)
plt.plot(x_fine, y_cubic, '-', linewidth=2, label='Cubic Spline')
plt.plot(x_fine, y_akima, '-.', label='Akima', alpha=0.7)
plt.legend()
plt.title('Perbandingan Metode Interpolasi')
plt.grid(True, alpha=0.3)
plt.savefig('interpolation.png', dpi=150)
plt.show()

# Interpolasi 2D
from scipy.interpolate import griddata

# Data irregular 2D
points = np.random.rand(100, 2)
values = np.sin(points[:, 0] * 2 * np.pi) * np.cos(points[:, 1] * 2 * np.pi)

# Grid reguler
grid_x, grid_y = np.mgrid[0:1:100j, 0:1:100j]
grid_z = griddata(points, values, (grid_x, grid_y), method='cubic')

print(f"Interpolasi 2D: grid shape = {grid_z.shape}")

4. Signal Processing

Modul scipy.signal adalah toolkit lengkap untuk analisis dan pemrosesan sinyal — dari filter digital hingga transformasi Fourier.

FFT (Fast Fourier Transform)

import numpy as np
from scipy import signal, fft
import matplotlib.pyplot as plt

# Buat sinyal: gabungan 3 frekuensi + noise
fs = 1000  # Sample rate (Hz)
t = np.arange(0, 1.0, 1/fs)

# Sinyal: 50Hz + 120Hz + 200Hz + noise
sinyal = (0.7 * np.sin(2*np.pi*50*t) +
          1.0 * np.sin(2*np.pi*120*t) +
          0.5 * np.sin(2*np.pi*200*t) +
          0.3 * np.random.randn(len(t)))

# FFT
N = len(sinyal)
yf = fft.fft(sinyal)
xf = fft.fftfreq(N, 1/fs)[:N//2]
amplitude = 2.0/N * np.abs(yf[:N//2])

# Visualisasi
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
ax1.plot(t[:200], sinyal[:200])
ax1.set_title('Domain Waktu')
ax1.set_xlabel('Waktu (s)')
ax1.set_ylabel('Amplitudo')

ax2.plot(xf, amplitude)
ax2.set_title('Domain Frekuensi (FFT)')
ax2.set_xlabel('Frekuensi (Hz)')
ax2.set_ylabel('Amplitudo')
ax2.set_xlim(0, 300)
plt.tight_layout()
plt.savefig('fft.png', dpi=150)
plt.show()

# Identifikasi frekuensi dominan
top_indices = np.argsort(amplitude)[-3:][::-1]
for idx in top_indices:
    print(f"  Frekuensi: {xf[idx]:.1f} Hz, Amplitudo: {amplitude[idx]:.3f}")

Filter Digital

import numpy as np
from scipy.signal import butter, filtfilt, iirnotch
import matplotlib.pyplot as plt

# Sinyal dengan noise frekuensi tinggi
fs = 1000
t = np.arange(0, 1.0, 1/fs)
sinyal_bersih = np.sin(2*np.pi*5*t)  # 5 Hz signal
noise = 0.5 * np.sin(2*np.pi*100*t) + 0.3 * np.random.randn(len(t))
sinyal_noisy = sinyal_bersih + noise

# Low-pass Butterworth filter (cutoff 20 Hz)
def lowpass_filter(data, cutoff, fs, order=5):
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = butter(order, normal_cutoff, btype='low', analog=False)
    return filtfilt(b, a, data)

sinyal_filtered = lowpass_filter(sinyal_noisy, cutoff=20, fs=fs)

# Notch filter (hilangkan 50Hz interference)
def notch_filter(data, freq, fs, Q=30):
    b, a = iirnotch(freq, Q, fs)
    return filtfilt(b, a, data)

sinyal_notch = notch_filter(sinyal_noisy, 100, fs)

# Visualisasi
fig, axes = plt.subplots(3, 1, figsize=(12, 10), sharex=True)
axes[0].plot(t, sinyal_noisy, 'gray', alpha=0.7)
axes[0].plot(t, sinyal_bersih, 'b-', linewidth=2)
axes[0].set_title('Sinyal Asli + Noise')

axes[1].plot(t, sinyal_filtered, 'r-', linewidth=2)
axes[1].set_title('Low-Pass Filter (cutoff 20Hz)')

axes[2].plot(t, sinyal_notch, 'g-', linewidth=2)
axes[2].set_title('Notch Filter (remove 100Hz)')

for ax in axes:
    ax.set_xlim(0, 0.5)
plt.tight_layout()
plt.savefig('filters.png', dpi=150)
plt.show()

Deteksi Puncak (Peak Detection)

import numpy as np
from scipy.signal import find_peaks, peak_prominences
import matplotlib.pyplot as plt

# Sinyal dengan beberapa puncak
t = np.linspace(0, 10, 1000)
sinyal = (np.sin(t) * np.exp(-0.1*t) +
          0.5*np.sin(3*t) * np.exp(-0.2*t) +
          0.3*np.random.randn(len(t)))

# Deteksi puncak
peaks, properties = find_peaks(sinyal,
    height=0.3,        # Min height
    distance=50,        # Min distance antar puncak
    prominence=0.2,     # Min prominence
)

# Hitung prominences
prominences = peak_prominences(sinyal, peaks)[0]

print(f"Ditemukan {len(peaks)} puncak:")
for i, (idx, prom) in enumerate(zip(peaks, prominences)):
    print(f"  Puncak {i+1}: t={t[idx]:.2f}s, amplitude={sinyal[idx]:.3f}, prominence={prom:.3f}")

plt.figure(figsize=(12, 5))
plt.plot(t, sinyal, 'b-', linewidth=1)
plt.plot(t[peaks], sinyal[peaks], 'rv', markersize=10, label='Puncak terdeteksi')
plt.xlabel('Waktu (s)')
plt.ylabel('Amplitudo')
plt.title('Peak Detection dengan SciPy')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('peaks.png', dpi=150)
plt.show()

5. Linear Algebra

scipy.linalg melengkapi NumPy dengan operasi linear algebra yang lebih lengkap dan seringkali lebih cepat.

import numpy as np
from scipy import linalg

# Matrix operations
A = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 0]])

# Determinan
det = linalg.det(A)
print(f"Determinan: {det:.0f}")  # 27

# Inverse
A_inv = linalg.inv(A)
print(f"A * A_inv = I? {np.allclose(A @ A_inv, np.eye(3))}")

# Eigenvalues & Eigenvectors
eigenvalues, eigenvectors = linalg.eig(A)
print(f"\nEigenvalues: {eigenvalues}")
print(f"Eigenvectors:\n{eigenvectors}")

# SVD (Singular Value Decomposition)
U, s, Vh = linalg.svd(A)
print(f"\nSingular values: {s}")

# Solve linear system: Ax = b
b = np.array([1, 2, 3])
x = linalg.solve(A, b)
print(f"\nSolusi Ax=b: x = {x}")
print(f"Verifikasi: A*x = {A @ x} (harus = {b})")

# LU Decomposition
P, L, U = linalg.lu(A)
print(f"\nLU Decomposition:")
print(f"P:\n{P}")
print(f"L:\n{L}")
print(f"U:\n{U}")
print(f"P@L@U = A? {np.allclose(P @ L @ U, A)}")

Matrix Decomposition Lanjutan

import numpy as np
from scipy import linalg

# Cholesky decomposition (untuk symmetric positive definite matrix)
A_sym = np.array([[4, 2, 1],
                  [2, 5, 3],
                  [1, 3, 6]])

L = linalg.cholesky(A_sym, lower=True)
print(f"Cholesky: A = L * L^T")
print(f"Verifikasi: {np.allclose(L @ L.T, A_sym)}")

# QR Decomposition
A = np.random.randn(5, 3)
Q, R = linalg.qr(A)
print(f"\nQR: Q shape={Q.shape}, R shape={R.shape}")
print(f"Q orthogonal? {np.allclose(Q.T @ Q, np.eye(3))}")

# Matrix norm
A = np.array([[1, -1], [2, 3]])
print(f"\nFrobenius norm: {linalg.norm(A, 'fro'):.4f}")
print(f"Spectral norm: {linalg.norm(A, 2):.4f}")

6. Statistik

scipy.stats menyediakan lebih dari 100 distribusi probabilitas dan berbagai uji statistik.

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

# Distribusi Normal
dist = stats.norm(loc=170, scale=10)  # Mean=170cm, StdDev=10cm

# Probabilitas
print(f"P(X < 160) = {dist.cdf(160):.4f}")  # CDF
print(f"P(X > 180) = {1 - dist.cdf(180):.4f}")
print(f"P(160 < X < 180) = {dist.cdf(180) - dist.cdf(160):.4f}")

# Percentiles
print(f"\nMedian: {dist.median():.1f} cm")
print(f"95th percentile: {dist.ppf(0.95):.1f} cm")

# Random sampling
samples = dist.rvs(size=1000, random_state=42)
print(f"Sample mean: {samples.mean():.2f}")
print(f"Sample std: {samples.std():.2f}")

# Visualisasi distribusi
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

# Normal
x = np.linspace(130, 210, 200)
axes[0].plot(x, dist.pdf(x), 'b-', linewidth=2)
axes[0].fill_between(x, dist.pdf(x), where=(x < 160) | (x > 180), alpha=0.3)
axes[0].set_title('Distribusi Normal (μ=170, σ=10)')
axes[0].set_xlabel('Tinggi (cm)')

# Binomial
n, p = 20, 0.5
x_binom = np.arange(0, 21)
axes[1].bar(x_binom, stats.binom.pmf(x_binom, n, p), color='green', alpha=0.7)
axes[1].set_title(f'Distribusi Binomial (n={n}, p={p})')

# Poisson
lam = 5
x_poisson = np.arange(0, 15)
axes[2].bar(x_poisson, stats.poisson.pmf(x_poisson, lam), color='red', alpha=0.7)
axes[2].set_title(f'Distribusi Poisson (λ={lam})')

plt.tight_layout()
plt.savefig('distributions.png', dpi=150)
plt.show()

Hypothesis Testing

import numpy as np
from scipy import stats

np.random.seed(42)

# Data: kelompok kontrol vs treatment
kontrol = np.random.normal(loc=100, scale=15, size=50)
treatment = np.random.normal(loc=110, scale=15, size=50)

# 1. T-test (apakah rata-rata berbeda signifikan?)
t_stat, p_value = stats.ttest_ind(kontrol, treatment)
print(f"=== Independent T-Test ===")
print(f"t-statistic: {t_stat:.4f}")
print(f"p-value: {p_value:.6f}")
print(f"Signifikan (α=0.05)? {'Ya' if p_value < 0.05 else 'Tidak'}")

# 2. Uji normalitas (Shapiro-Wilk)
stat_k, p_k = stats.shapiro(kontrol)
stat_t, p_t = stats.shapiro(treatment)
print(f"\n=== Uji Normalitas (Shapiro-Wilk) ===")
print(f"Kontrol: W={stat_k:.4f}, p={p_k:.4f}")
print(f"Treatment: W={stat_t:.4f}, p={p_t:.4f}")

# 3. Chi-square test
observed = np.array([[50, 30, 20], [35, 40, 25]])
chi2, p_val, dof, expected = stats.chi2_contingency(observed)
print(f"\n=== Chi-Square Test ===")
print(f"Chi2 = {chi2:.4f}, p = {p_val:.4f}, dof = {dof}")

# 4. Korelasi Pearson
x = np.random.randn(100)
y = 2 * x + np.random.randn(100) * 0.5
r, p = stats.pearsonr(x, y)
print(f"\n=== Korelasi Pearson ===")
print(f"r = {r:.4f}, p = {p:.6f}")

7. Integration & ODE

import numpy as np
from scipy import integrate
import matplotlib.pyplot as plt

# Numerical integration: ∫₀¹ x² dx = 1/3
def f(x):
    return x**2

hasil, error = integrate.quad(f, 0, 1)
print(f"∫₀¹ x² dx = {hasil:.6f} (exact: {1/3:.6f}), error: {error:.2e}")

# Double integral: ∫₀¹ ∫₀¹ xy dx dy = 1/4
def f2(y, x):  # Note: inner integral variable first
    return x * y

hasil2, _ = integrate.dblquad(f2, 0, 1, 0, 1)
print(f"∫₀¹∫₀¹ xy dx dy = {hasil2:.6f} (exact: 0.250000)")

# ODE Solver: Model SIR (Epidemiology)
def sir_model(t, y, beta=0.3, gamma=0.1):
    S, I, R = y
    dSdt = -beta * S * I
    dIdt = beta * S * I - gamma * I
    dRdt = gamma * I
    return [dSdt, dIdt, dRdt]

# Kondisi awal: 99% susceptible, 1% infected, 0% recovered
y0 = [0.99, 0.01, 0]
t_span = (0, 100)
t_eval = np.linspace(0, 100, 1000)

sol = integrate.solve_ivp(sir_model, t_span, y0, t_eval=t_eval)

plt.figure(figsize=(10, 6))
plt.plot(sol.t, sol.y[0], 'b-', label='Susceptible')
plt.plot(sol.t, sol.y[1], 'r-', label='Infected')
plt.plot(sol.t, sol.y[2], 'g-', label='Recovered')
plt.xlabel('Hari')
plt.ylabel('Proporsi Populasi')
plt.title('Model SIR - Simulasi Epidemi')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('sir_model.png', dpi=150)
plt.show()

print(f"Peak Infected: {sol.y[1].max():.2%} pada hari {sol.t[sol.y[1].argmax()]:.0f}")

8. Sparse Matrices

import numpy as np
from scipy import sparse

# Matrix besar tapi mayoritas nol
n = 10000
density = 0.001  # Hanya 0.1% non-zero

# Buat sparse random matrix
dense = np.random.choice([0.0, 1.0], size=(n, n), p=[1-density, density])
sparse_matrix = sparse.csr_matrix(dense)

print(f"Dense: {dense.nbytes / 1e6:.1f} MB")
print(f"Sparse: {sparse_matrix.data.nbytes / 1e6:.1f} MB")
print(f"Ratio: {dense.nbytes / sparse_matrix.data.nbytes:.0f}x lebih hemat")

# Operasi sparse
A = sparse.random(1000, 1000, density=0.01, format='csr')
b = np.random.randn(1000)

# Sparse linear solve (jauh lebih cepat untuk matrix besar)
from scipy.sparse.linalg import spsolve, eigsh

# Solve Ax = b
x = spsolve(A, b)
print(f"Residual norm: {np.linalg.norm(A @ x - b):.2e}")

# Eigenvalues terbesar
eigenvalues, eigenvectors = eigsh(A, k=5)
print(f"Top 5 eigenvalues: {eigenvalues}")

9. Spatial & Image Processing

import numpy as np
from scipy import spatial, ndimage
import matplotlib.pyplot as plt

# KDTree untuk nearest neighbor search
np.random.seed(42)
points = np.random.randn(10000, 2)
tree = spatial.KDTree(points)

# Cari 5 titik terdekat dari query point
query = np.array([0.5, 0.5])
distances, indices = tree.query(query, k=5)

print(f"Query point: {query}")
print(f"5 nearest neighbors:")
for i, (d, idx) in enumerate(zip(distances, indices)):
    print(f"  {i+1}. Point {idx}: {points[idx]}, distance={d:.4f}")

# Convex Hull
hull = spatial.ConvexHull(points[:100])
print(f"\nConvex Hull: {hull.vertices.shape[0]} vertices")
print(f"Area: {hull.area:.4f}")

# Image processing dengan ndimage
# Buat "image" random
img = np.random.rand(100, 100)
img[30:70, 30:70] += 2  # Tambah "objek"

# Gaussian blur
img_blur = ndimage.gaussian_filter(img, sigma=3)

# Edge detection
img_sobel = ndimage.sobel(img)

# Label connected components
threshold = img > 1.5
labeled, num_features = ndimage.label(threshold)
print(f"\nDitemukan {num_features} objek terpisah")

fig, axes = plt.subplots(1, 4, figsize=(16, 4))
axes[0].imshow(img, cmap='gray')
axes[0].set_title('Original')
axes[1].imshow(img_blur, cmap='gray')
axes[1].set_title('Gaussian Blur')
axes[2].imshow(img_sobel, cmap='gray')
axes[2].set_title('Sobel Edge')
axes[3].imshow(labeled, cmap='nipy_spectral')
axes[3].set_title(f'Labeling ({num_features} objek)')
for ax in axes:
    ax.axis('off')
plt.tight_layout()
plt.savefig('ndimage.png', dpi=150)
plt.show()

10. Studi Kasus Praktis

Studi Kasus: Analisis Sinyal EKG

import numpy as np
from scipy import signal, stats
import matplotlib.pyplot as plt

# Simulasi sinyal EKG sederhana
fs = 500  # Sample rate
t = np.arange(0, 5, 1/fs)

# Sinyal EKG sintetik (QRS complex sederhana)
def ecg_wave(t, freq=1.2):
    """Generate synthetic ECG-like signal"""
    phase = (t * freq) % 1
    # P wave, QRS, T wave (simplified)
    p = 0.15 * np.exp(-((phase - 0.1)**2) / 0.002)
    qrs = 1.0 * np.exp(-((phase - 0.3)**2) / 0.0005)
    t_wave = 0.3 * np.exp(-((phase - 0.55)**2) / 0.008)
    return p + qrs + t_wave

# Sinyal EKG + noise
heart_rate = 72  # BPM
ecg_clean = ecg_wave(t, freq=heart_rate/60)
noise = 0.1 * np.random.randn(len(t))
ecg_noisy = ecg_clean + noise

# Filter: bandpass 1-40 Hz
b, a = signal.butter(4, [1, 40], btype='bandpass', fs=fs)
ecg_filtered = signal.filtfilt(b, a, ecg_noisy)

# Deteksi R-peaks (heartbeat detection)
r_peaks, _ = signal.find_peaks(ecg_filtered,
    height=0.5,
    distance=int(fs * 0.5)  # Min 0.5s between beats
)

# Hitung heart rate
rr_intervals = np.diff(r_peaks) / fs  # dalam detik
heart_rates = 60 / rr_intervals  # BPM

print(f"R-Peaks terdeteksi: {len(r_peaks)}")
print(f"Rata-rata Heart Rate: {heart_rates.mean():.1f} BPM")
print(f"HRV (Std RR): {rr_intervals.std()*1000:.1f} ms")
print(f"Min HR: {heart_rates.min():.1f}, Max HR: {heart_rates.max():.1f}")

# Visualisasi
fig, axes = plt.subplots(3, 1, figsize=(14, 8))
axes[0].plot(t[:1000], ecg_noisy[:1000], 'gray', alpha=0.7)
axes[0].set_title('Sinyal EKG Noisy')

axes[1].plot(t[:1000], ecg_filtered[:1000], 'b-')
r_in_range = r_peaks[r_peaks < 1000]
axes[1].plot(t[r_in_range], ecg_filtered[r_in_range], 'rv', markersize=10)
axes[1].set_title('EKG Filtered + R-Peaks')

axes[2].bar(range(len(heart_rates)), heart_rates, color='green', alpha=0.7)
axes[2].axhline(heart_rates.mean(), color='red', linestyle='--', label=f'Mean: {heart_rates.mean():.1f}')
axes[2].set_title('Heart Rate per Beat')
axes[2].set_xlabel('Beat Number')
axes[2].set_ylabel('BPM')
axes[2].legend()

plt.tight_layout()
plt.savefig('ecg_analysis.png', dpi=150)
plt.show()

11. Quiz Pemahaman

1. Modul SciPy apa yang digunakan untuk curve fitting?

2. Fungsi apa yang digunakan untuk mengubah domain waktu ke domain frekuensi?

3. Metode interpolasi mana yang paling cocok untuk menghindari overshooting?

4. Apa fungsi dari scipy.sparse.csr_matrix?

5. Fungsi solve_ivp digunakan untuk menyelesaikan apa?

🔍 Zoom
100%
🎨 Tema