FPCA: basic workflow#

This example demonstrates a minimal FPCA workflow: generate synthetic functional data, run Functional PCA, and inspect the results.

Generate synthetic functional data#

FunctionalDataGenerator builds a stationary covariance surface from a mean function, a marginal variance function, and a correlation kernel, then samples low-rank functional signals with Gaussian noise.

import numpy as np
from scipy.special import j0
from pflm.fpca import FunctionalDataGenerator, FunctionalPCA

# 1) Prepare a regular time grid
t = np.linspace(0.0, 10.0, 201)

# 2) Define mean/variance and correlation kernel
mean_func = lambda tt: np.sin(tt) * 0.5
var_func  = lambda tt: 1.0 + 0.2 * np.cos(tt)
corr_func = j0  # Bessel J0 — the default

# 3) Generate 50 synthetic curves
gen = FunctionalDataGenerator(
    t, mean_func, var_func, corr_func,
    variation_prop_thresh=0.99,
)
y_list, t_list = gen.generate(n=50, seed=42)
print(f"Generated {len(y_list)} curves, each with {len(y_list[0])} points")

Fit FPCA#

FunctionalPCA follows the scikit-learn estimator API. Calling fit() performs mean/covariance smoothing, eigen decomposition, and PC-score estimation in one go.

fpca = FunctionalPCA(
    assume_measurement_error=True,
    num_points_reg_grid=101,
)
fpca.fit(t_list, y_list)

# 5) Access results
print("Selected #PCs:", fpca.num_pcs_)
print("Eigenvalues:", fpca.fpca_model_params_.fpca_lambda)
print("Score matrix shape:", fpca.xi_.shape)         # (50, k)
print("Fitted curves:", len(fpca.fitted_y_))          # 50

Inspect the smoothed mean and covariance#

# Smoothed mean on the regular grid
reg_result = fpca.smoothed_model_result_reg_
print("Regular grid size:", reg_result.grid.shape)
print("Mean (first 5):", reg_result.mu[:5])
print("Covariance shape:", reg_result.cov.shape)

# Measurement error variance
print("σ²:", fpca.fpca_model_params_.measurement_error_variance)

Choose a different selection method#

By default method_select_num_pcs='FVE' is used. You can switch to AIC or BIC, or fix the number of PCs directly.

# Use AIC to choose the number of PCs
fpca_aic = FunctionalPCA(num_points_reg_grid=101)
fpca_aic.fit(t_list, y_list, method_select_num_pcs="AIC")
print("AIC selected #PCs:", fpca_aic.num_pcs_)

# Fix the number of PCs to 3
fpca_fixed = FunctionalPCA(num_points_reg_grid=101)
fpca_fixed.fit(t_list, y_list, method_select_num_pcs=3)
print("Fixed #PCs:", fpca_fixed.num_pcs_)

Predict scores for new curves#

# Generate 10 new curves
y_new, t_new = gen.generate(n=10, seed=99)

xi_new, xi_var_new, fitted_y_mat_new, fitted_y_new = fpca.predict(y_new, t_new)
print("New scores shape:", xi_new.shape)        # (10, k)
print("New fitted curves:", len(fitted_y_new))   # 10

Simulate sparse / irregular data#

FunctionalDataGenerator.make_missing randomly drops observations from each curve to simulate irregular designs.

y_sparse, t_sparse = FunctionalDataGenerator.make_missing(
    y_list, t_list, missing_number=150, seed=0,
)
print(f"Curve 0: {len(t_sparse[0])} points (was {len(t_list[0])})")

fpca_sparse = FunctionalPCA(num_points_reg_grid=101)
fpca_sparse.fit(t_sparse, y_sparse, method_pcs="CE")
print("Sparse #PCs:", fpca_sparse.num_pcs_)