Introduction to FSHybridPLS

Overview

FSHybridPLS fits Hybrid Penalized Partial Least Squares models when predictors include both functional curves and scalar covariates. Functional and scalar parts are treated jointly in a hybrid Hilbert space, with roughness penalties on functional coefficient directions.

The method is described in Mun and Jang (2026), doi:10.48550/arXiv.2601.16364.

Construct hybrid predictors

A predictor_hybrid object stores scalar covariates, functional fd objects, and precomputed Gram / penalty matrices used by the algorithm.

library(FSHybridPLS)
set.seed(1)

sim <- simulate_hybrid_data(
  n = 50,
  n_functional = 1,
  n_scalar = 3,
  n_basis = 6
)

sim$W
#> $Z
#>                 Z1           Z2          Z3
#>  [1,]  0.893673702  1.473881181  1.07444096
#>  [2,] -1.047298149  0.677268492  1.89565477
#>  [3,]  1.971337386  0.379962687 -0.60299730
#>  [4,] -0.383632106 -0.192798426 -0.39086782
#>  [5,]  1.654145302  1.577891795 -0.41622203
#>  [6,]  1.512212694  0.596234109 -0.37565742
#>  [7,]  0.082965734 -1.173576941 -0.36663095
#>  [8,]  0.567220915 -0.155642535 -0.29567745
#>  [9,] -1.024548480 -1.918909820  1.44182041
#> [10,]  0.323006503 -0.195258846 -0.69753829
#> [11,]  1.043612458 -2.592327670 -0.38816751
#> [12,]  0.099078487  1.314002167  0.65253645
#> [13,] -0.454136909 -0.635543001  1.12477245
#> [14,] -0.655781852 -0.429978839 -0.77211080
#> [15,] -0.035922423 -0.169318332 -0.50808622
#> [16,]  1.069161461  0.612218174  0.52362059
#> [17,] -0.483974930  0.678340177  1.01775423
#> [18,] -0.121010111  0.567951972 -0.25116459
#> [19,] -1.294140004 -0.572542604 -1.42999345
#> [20,]  0.494312836 -1.363291256  1.70912103
#> [21,]  1.307901520 -0.388722244  1.43506957
#> [22,]  1.497041009  0.277914132 -0.71037115
#> [23,]  0.814702731 -0.823081122 -0.06506757
#> [24,] -1.869788790 -0.068840934 -1.75946874
#> [25,]  0.482029504 -1.167662326  0.56972297
#> [26,]  0.456135603 -0.008309014  1.61234680
#> [27,] -0.353400286  0.128855402 -1.63728065
#> [28,]  0.170489471 -0.145875628 -0.77956851
#> [29,] -0.864035954 -0.163910957 -0.64117693
#> [30,]  0.679230774  1.763552003 -0.68113139
#> [31,] -0.327101015  0.762586512 -2.03328560
#> [32,] -1.569082185  1.111431081  0.50096356
#> [33,] -0.367450756 -0.923206953 -1.53179814
#> [34,]  1.364434929  0.164341838 -0.02499764
#> [35,] -0.334281365  1.154825187  0.59298472
#> [36,]  0.732750042 -0.056521425 -0.19819542
#> [37,]  0.946585640 -2.129360648  0.89200839
#> [38,]  0.004398704  0.344845762 -0.02571507
#> [39,] -0.352322306 -1.904955446 -0.64766045
#> [40,] -0.529695509 -0.811170153  0.64635942
#> [41,]  0.739589226  1.324004321 -0.43383274
#> [42,] -1.063457415  0.615636849  1.77261118
#> [43,]  0.246210844  1.091668956 -0.01825971
#> [44,] -0.289499367  0.306604862  0.85281499
#> [45,] -2.264889356 -0.110158762  0.20516290
#> [46,] -1.408850456 -0.924312773 -3.00804860
#> [47,]  0.916019329  1.592913754 -1.36611193
#> [48,] -0.191278951  0.045010598 -0.42410226
#> [49,]  0.803283216 -0.715128401  0.23680366
#> [50,]  1.887474463  0.865223100 -2.34272312
#> 
#> $functional_list
#> $functional_list[[1]]
#> $coefs
#>          reps 1     reps 2      reps 3      reps 4      reps 5      reps 6
#> [1,] -0.6264538  0.4874291 -0.62124058  0.82122120  0.61982575  1.35867955
#> [2,]  0.1836433  0.7383247 -2.21469989  0.59390132 -0.05612874 -0.10278773
#> [3,] -0.8356286  0.5757814  1.12493092  0.91897737 -0.15579551  0.38767161
#> [4,]  1.5952808 -0.3053884 -0.04493361  0.78213630 -1.47075238 -0.05380504
#> [5,]  0.3295078  1.5117812 -0.01619026  0.07456498 -0.47815006 -1.37705956
#> [6,] -0.8204684  0.3898432  0.94383621 -1.98935170  0.41794156 -0.41499456
#>          reps 7     reps 8     reps 9    reps 10     reps 11    reps 12
#> [1,] -0.3942900  0.6969634 -0.1123462  1.4330237  2.40161776 -1.8049586
#> [2,] -0.0593134  0.5566632  0.8811077  1.9803999 -0.03924000  1.4655549
#> [3,]  1.1000254 -0.6887557  0.3981059 -0.3672215  0.68973936  0.1532533
#> [4,]  0.7631757 -0.7074952 -0.6120264 -1.0441346  0.02800216  2.1726117
#> [5,] -0.1645236  0.3645820  0.3411197  0.5697196 -0.74327321  0.4755095
#> [6,] -0.2533617  0.7685329 -1.1293631 -0.1350546  0.18879230 -0.7099464
#>           reps 13     reps 14    reps 15    reps 16     reps 17    reps 18
#> [1,]  0.610726353  0.07434132  0.5939462 -0.5425200 -1.27659221 -0.9109216
#> [2,] -0.934097632 -0.58952095  0.3329504  1.2078678 -0.57326541  0.1580288
#> [3,] -1.253633400 -0.56866873  1.0630998  1.1604026 -1.22461261 -0.6545846
#> [4,]  0.291446236 -0.13517862 -0.3041839  0.7002136 -0.47340064  1.7672873
#> [5,] -0.443291873  1.17808700  0.3700188  1.5868335 -0.62036668  0.7167075
#> [6,]  0.001105352 -1.52356680  0.2670988  0.5584864  0.04211587  0.9101742
#>         reps 19    reps 20    reps 21     reps 22    reps 23     reps 24
#> [1,]  0.3841854 -0.2073807 -0.5059575 -0.07356440  0.5314962 -0.65209478
#> [2,]  1.6821761 -0.3928079  1.3430388 -0.03763417 -1.5183941 -0.05689678
#> [3,] -0.6357365 -0.3199929 -0.2145794 -0.68166048  0.3065579 -1.91435943
#> [4,] -0.4616447 -0.2791133 -0.1795565 -0.32427027 -1.5364498  1.17658331
#> [5,]  1.4322822  0.4941883 -0.1001907  0.06016044 -0.3009761 -1.66497244
#> [6,] -0.6506964 -0.1773305  0.7126663 -0.58889449 -0.5282799 -0.46353040
#>          reps 25     reps 26    reps 27    reps 28     reps 29     reps 30
#> [1,] -1.11592011  0.45018710  1.0000288  1.0584830 -0.14439960 -0.33400084
#> [2,] -0.75081900 -0.01855983 -0.6212667  0.8864227  0.20753834 -0.03472603
#> [3,]  2.08716655 -0.31806837 -1.3844268 -0.6192430  2.30797840  0.78763961
#> [4,]  0.01739562 -0.92936215  1.8692906  2.2061025  0.10580237  2.07524501
#> [5,] -1.28630053 -1.48746031  0.4251004 -0.2550270  0.45699881  1.02739244
#> [6,] -1.64060553 -1.07519230 -0.2386471 -1.4244947 -0.07715294  1.20790840
#>         reps 31    reps 32    reps 33    reps 34     reps 35    reps 36
#> [1,] -1.2313234  1.4645873 -0.7317482  0.4119747 -2.28523554 -0.1643758
#> [2,]  0.9838956 -0.7660820  0.8303732 -0.3810761  2.49766159  0.4206946
#> [3,]  0.2199248 -0.4302118 -1.2080828  0.4094018  0.66706617 -0.4002467
#> [4,] -1.4672500 -0.9261095 -1.0479844  1.6888733  0.54132734 -1.3702079
#> [5,]  0.5210227 -0.1771040  1.4411577  1.5865884 -0.01339952  0.9878383
#> [6,] -0.1587546  0.4020118 -1.0158475 -0.3309078  0.51010842  1.5197450
#>          reps 37    reps 38    reps 39      reps 40    reps 41    reps 42
#> [1,] -0.30874057 -0.6303003  0.1971934 -0.059723276  0.7073107 -0.5447907
#> [2,] -1.25328976 -0.3409686  0.2631756 -0.098178744  1.0341077 -0.2556707
#> [3,]  0.64224131 -1.1565724 -0.9858267  0.560820729  0.2234804 -0.1661210
#> [4,] -0.04470914  1.8031419 -2.8889207 -1.186458639 -0.8787076  1.0204639
#> [5,] -1.73321841 -0.3311320 -0.6404817  1.096777044  1.1629646  0.1362219
#> [6,]  0.00213186 -1.6055134  0.5705076 -0.005344028 -2.0001649  0.4071676
#>          reps 43    reps 44    reps 45    reps 46    reps 47    reps 48
#> [1,] -0.06965481  0.3747244  1.7196273 -0.2589326 -2.2891240  1.3242586
#> [2,] -0.24766434 -0.4252677  0.2700549  0.3943792  0.7410012 -0.7012317
#> [3,]  0.69555081  0.9510128 -0.4221840 -0.8518571 -1.3162452 -0.5806143
#> [4,]  1.14622836 -0.3892372 -1.1891133  2.6491669  0.9198037 -1.0010722
#> [5,] -2.40309621 -0.2843307 -0.3310330  0.1560117  0.3981302 -0.6681786
#> [6,]  0.57273956  0.8574098 -0.9398293  1.1302073 -0.4075286  0.9451850
#>         reps 49    reps 50
#> [1,]  0.4337021  1.7784293
#> [2,]  1.0051592  0.1344477
#> [3,] -0.3901187  0.7655990
#> [4,]  0.3763703  0.9551367
#> [5,]  0.2441649 -0.0505657
#> [6,] -1.4262573 -0.3058154
#> 
#> $basis
#> $call
#> basisfd(type = type, rangeval = rangeval, nbasis = nbasis, params = params, 
#>     dropind = dropind, quadvals = quadvals, values = values, 
#>     basisvalues = basisvalues)
#> 
#> $type
#> [1] "bspline"
#> 
#> $rangeval
#> [1] 0 1
#> 
#> $nbasis
#> [1] 6
#> 
#> $params
#> [1] 0.3333333 0.6666667
#> 
#> $dropind
#> NULL
#> 
#> $quadvals
#> NULL
#> 
#> $values
#> list()
#> 
#> $basisvalues
#> list()
#> 
#> $names
#> [1] "bspl4.1" "bspl4.2" "bspl4.3" "bspl4.4" "bspl4.5" "bspl4.6"
#> 
#> attr(,"class")
#> [1] "basisfd"
#> 
#> $fdnames
#> $fdnames$args
#> [1] "time"
#> 
#> $fdnames$reps
#>  [1] "reps 1"  "reps 2"  "reps 3"  "reps 4"  "reps 5"  "reps 6"  "reps 7" 
#>  [8] "reps 8"  "reps 9"  "reps 10" "reps 11" "reps 12" "reps 13" "reps 14"
#> [15] "reps 15" "reps 16" "reps 17" "reps 18" "reps 19" "reps 20" "reps 21"
#> [22] "reps 22" "reps 23" "reps 24" "reps 25" "reps 26" "reps 27" "reps 28"
#> [29] "reps 29" "reps 30" "reps 31" "reps 32" "reps 33" "reps 34" "reps 35"
#> [36] "reps 36" "reps 37" "reps 38" "reps 39" "reps 40" "reps 41" "reps 42"
#> [43] "reps 43" "reps 44" "reps 45" "reps 46" "reps 47" "reps 48" "reps 49"
#> [50] "reps 50"
#> 
#> $fdnames$funs
#> [1] "values"
#> 
#> 
#> attr(,"class")
#> [1] "fd"
#> 
#> 
#> $gram_list
#> $gram_list[[1]]
#>              [,1]         [,2]         [,3]         [,4]         [,5]
#> [1,] 0.0476190476 0.0291666667 0.0061507937 0.0003968254 0.0000000000
#> [2,] 0.0291666667 0.0738095238 0.0520833333 0.0114583333 0.0001488095
#> [3,] 0.0061507937 0.0520833333 0.1089285714 0.0709821429 0.0114583333
#> [4,] 0.0003968254 0.0114583333 0.0709821429 0.1089285714 0.0520833333
#> [5,] 0.0000000000 0.0001488095 0.0114583333 0.0520833333 0.0738095238
#> [6,] 0.0000000000 0.0000000000 0.0003968254 0.0061507937 0.0291666667
#>              [,6]
#> [1,] 0.0000000000
#> [2,] 0.0000000000
#> [3,] 0.0003968254
#> [4,] 0.0061507937
#> [5,] 0.0291666667
#> [6,] 0.0476190476
#> 
#> 
#> $gram_deriv_list
#> $gram_deriv_list[[1]]
#>        [,1]     [,2]     [,3]     [,4]     [,5]   [,6]
#> [1,]  324.0 -445.500   94.500   27.000    0.000    0.0
#> [2,] -445.5  648.000 -182.250  -30.375   10.125    0.0
#> [3,]   94.5 -182.250  121.500  -30.375  -30.375   27.0
#> [4,]   27.0  -30.375  -30.375  121.500 -182.250   94.5
#> [5,]    0.0   10.125  -30.375 -182.250  648.000 -445.5
#> [6,]    0.0    0.000   27.000   94.500 -445.500  324.0
#> 
#> 
#> $eval_point
#>  [1] 0.00 0.02 0.04 0.06 0.08 0.10 0.12 0.14 0.16 0.18 0.20 0.22 0.24 0.26 0.28
#> [16] 0.30 0.32 0.34 0.36 0.38 0.40 0.42 0.44 0.46 0.48 0.50 0.52 0.54 0.56 0.58
#> [31] 0.60 0.62 0.64 0.66 0.68 0.70 0.72 0.74 0.76 0.78 0.80 0.82 0.84 0.86 0.88
#> [46] 0.90 0.92 0.94 0.96 0.98 1.00
#> 
#> $n_basis_list
#> [1] 6
#> 
#> $n_sample
#> [1] 50
#> 
#> $n_functional
#> [1] 1
#> 
#> $n_scalar
#> [1] 3
#> 
#> attr(,"class")
#> [1] "predictor_hybrid"
length(sim$y)
#> [1] 50

Split and normalize

split_and_normalize_all() performs a train/test split, within-modality standardization, between-modality variance balancing, and response standardization using training statistics.

prep <- split_and_normalize_all(sim$W, sim$y, train_ratio = 0.7)
prep$predictor_train$n_sample
#> [1] 35
prep$predictor_test$n_sample
#> [1] 15

Fit and predict

fit <- fit_hybridPLS(
  prep$predictor_train,
  prep$response_train,
  n_iter = 3,
  lambda = 1e-3,
  validation_data = list(
    W_test = prep$predictor_test,
    y_test = prep$response_test
  )
)

fit
#> Hybrid Penalized PLS model
#>   Components (n_iter): 3
#>   Functional predictors: 1
#>   Lambda: 0.001
#>   Validation RMSE by component:
#>     0.5779, 0.3936, 0.3822
preds <- predict(fit, prep$predictor_test)
rmse <- sqrt(mean((prep$response_test - preds)^2))
rmse
#> [1] 0.3822361

Choosing the number of components

cv <- cv_fit_hybridPLS(
  prep$predictor_train,
  prep$response_train,
  n_iter = 4,
  lambda = 1e-3,
  n_fold = 3,
  seed = 1
)
cv$rmse_by_component
#> [1] 0.5643767 0.4480987 0.4290852 0.4529231
cv$best_n_iter
#> [1] 3

Session info

sessionInfo()
#> R version 4.5.0 (2025-04-11 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 26200)
#> 
#> Matrix products: default
#>   LAPACK version 3.12.1
#> 
#> locale:
#> [1] LC_COLLATE=C                          
#> [2] LC_CTYPE=English_United States.utf8   
#> [3] LC_MONETARY=English_United States.utf8
#> [4] LC_NUMERIC=C                          
#> [5] LC_TIME=English_United States.utf8    
#> 
#> time zone: America/Los_Angeles
#> tzcode source: internal
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] FSHybridPLS_0.1.0
#> 
#> loaded via a namespace (and not attached):
#>  [1] cli_3.6.5          knitr_1.50         rlang_1.1.6        xfun_0.52         
#>  [5] KernSmooth_2.23-26 mclust_6.1.1       fds_1.8            deSolve_1.40      
#>  [9] jsonlite_2.0.0     hdrcde_3.4         RCurl_1.98-1.17    colorspace_2.1-1  
#> [13] htmltools_0.5.8.1  pracma_2.4.4       sass_0.4.10        rmarkdown_2.29    
#> [17] grid_4.5.0         evaluate_1.0.4     jquerylib_0.1.4    bitops_1.0-9      
#> [21] MASS_7.3-65        ks_1.15.1          fastmap_1.2.0      yaml_2.3.10       
#> [25] mvtnorm_1.3-3      lifecycle_1.0.5    rainbow_3.8        cluster_2.1.8.1   
#> [29] compiler_4.5.0     fda_6.3.0          lattice_0.22-6     digest_0.6.37     
#> [33] R6_2.6.1           splines_4.5.0      pcaPP_2.0-5        bslib_0.9.0       
#> [37] Matrix_1.7-3       tools_4.5.0        cachem_1.1.0