Partial Functional Linear Model#

PartialFunctionalLinearModel combines scalar predictors with one or more functional predictors (via FPCA) and fits an ElasticNet on the concatenated design matrix [scalar_features | FPCA_scores].

Single functional feature#

import numpy as np
from pflm.fpca import FunctionalDataGenerator, FunctionalPCA
from pflm.pflm.partial_flm import PartialFunctionalLinearModel, FPCAConfig

# --- Synthetic functional data ---
t = np.linspace(0.0, 10.0, 51)
gen = FunctionalDataGenerator(
    t,
    lambda x: np.sin(x) * 0.5,
    lambda x: 1.0 + 0.2 * np.cos(x),
)
n = 100
y_list, t_list = gen.generate(n=n, seed=42)

# --- Scalar features and response ---
rng = np.random.default_rng(0)
scalar = rng.standard_normal((n, 3))
response = scalar @ np.array([1.0, -0.5, 0.3]) + rng.normal(0, 0.2, n)

# --- Fit ---
model = PartialFunctionalLinearModel()
model.fit([t_list], [y_list], scalar, response)

print("Scalar features:", model.n_scalar_features_in_)
print("Functional features:", model.n_functional_features_in_)
print("Selected #PCs:", model.fpca_models_[0].num_pcs_)
print("Total coefficients:", model.linear_model_.coef_.shape)

Customise ElasticNet and FPCA#

Use linear_opts to control the ElasticNet regularisation and fpca_configs to tune the FPCA pipeline.

from pflm.fpca import FunctionalPCAMuCovParams

model = PartialFunctionalLinearModel(
    linear_opts=dict(alpha=0.1, l1_ratio=0.8, max_iter=2000),
    fpca_configs=FPCAConfig(
        num_points_reg_grid=101,
        mu_cov_params=FunctionalPCAMuCovParams(
            method_select_mu_bw="gcv",
            method_select_cov_bw="gcv",
        ),
        fit_params=dict(
            fve_threshold=0.95,
            method_pcs="CE",
        ),
    ),
)
model.fit([t_list], [y_list], scalar, response)
print("Coefficients:", model.linear_model_.coef_)

Multiple functional features#

When more than one functional predictor is available, pass each as a separate element in the lists and optionally provide per-feature FPCA configs.

# Generate a second functional feature
gen2 = FunctionalDataGenerator(
    t,
    lambda x: np.cos(x) * 0.3,
    lambda x: 0.5 + 0.1 * np.sin(x),
)
y_list2, t_list2 = gen2.generate(n=n, seed=7)

model = PartialFunctionalLinearModel(
    fpca_configs=[
        FPCAConfig(fit_params=dict(method_pcs="IN")),
        FPCAConfig(fit_params=dict(method_pcs="CE", fve_threshold=0.99)),
    ],
)
model.fit(
    [t_list, t_list2],
    [y_list, y_list2],
    scalar,
    response,
)
print("PCs feature 0:", model.fpca_models_[0].num_pcs_)
print("PCs feature 1:", model.fpca_models_[1].num_pcs_)

Prediction#

y_pred = model.predict(
    [t_list[:10], t_list2[:10]],
    [y_list[:10], y_list2[:10]],
    scalar[:10],
)
print("Predicted (first 5):", y_pred[:5])