tfd_mv / tfb_mv classes for vector-valued functions f: R -> R^d#233
Open
fabian-s wants to merge 171 commits into
Open
tfd_mv / tfb_mv classes for vector-valued functions f: R -> R^d#233fabian-s wants to merge 171 commits into
fabian-s wants to merge 171 commits into
Conversation
Introduces a prototype representation for multivariate-output functional data (curves into R^d, e.g. movement trajectories), addressing issues #18 and #27. Uses a composition design: a tf_mv vector bundles d univariate tf vectors (one per output dimension) and delegates all numeric work to the existing univariate machinery, so both tfd and tfb representations and regular/irregular sampling are supported with no new numeric kernels. - new classes tfd_mv / tfb_mv (parent tf_mv) built on vctrs::new_vctr - custom vec_proxy/vec_restore (data-frame-of-components proxy) plus component-wise vec_ptype2/vec_cast for full vctrs compatibility (subset, c(), casting, tibble columns) - constructors from lists of tf vectors / matrices, 3-d arrays and long data.frames; accessors tf_ncomp/tf_components/tf_component + $ sugar - component-wise arithmetic, Math/Summary, mean/median/sd/var, ==/!= - [ evaluation returns a [curve, arg, component] array (issue #18's array-valued j) with a component= selector; facet and trajectory plots - design/multivariate.md compares the candidate approaches - tests for construction, vctrs, brackets and methods https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Adds component-wise tf_mv methods for the remaining univariate verbs: tf_rebase (so tfd_mv<->tfb_mv conversion via tf_rebase works), tf_derive, tf_integrate (definite -> n x d matrix; indefinite -> tfd_mv), tf_smooth and tf_zoom. Registration is handled specially: a vector-valued curve shares one time axis, so tf_estimate_warps.tf_mv estimates a single warp per curve from a univariate registration signal (default: the first component; ref_component can select another component, "norm" for the pointwise Euclidean norm, or a custom function) and tf_warp.tf_mv / tf_align.tf_mv apply that shared warp to every component. tf_register then composes unchanged, yielding a tf_registration whose registered/template are tf_mv and whose warps are univariate. Adds tests in test-mv-verbs.R and notes in design/multivariate.md. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
The previous all.equal comparison across component args was implicitly detecting "all components are regular with the same shared grid"; check that directly via is_irreg() instead. Same observable behaviour, clearer intent. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Two refinements to how tf_mv accommodates irregular data: 1. new_tf_mv no longer rejects components with differing domains. By default it takes the union as the mv domain and widens each component to match (warnings about the widening are suppressed; the widening is intentional). Users can supply an explicit `domain` to tfd_mv() as long as it contains every component's observed range. This fixes the common case where independent irregular sampling yields components whose auto-derived domains differ by floating- point amounts. 2. tf_arg.tf_mv now collapses to a single per-curve list (length n) when every component is irregular AND the per-curve args agree across components -- the canonical "movement data with irregular timestamps" shape, where reporting two redundant copies was misleading. Per-component shapes still emerge for genuinely differing arg structures. Tests cover the auto-union, user-supplied-domain, out-of-range-domain and arg-collapse cases. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Adds an "Irregularity cases" table to design/multivariate.md covering the four qualitatively different shapes a tf_mv can have (fully regular; per-curve shared across components; per-component grid; per-(curve, component) grids), what tf_arg() and tf_evaluations() return in each, and explicitly acknowledges the storage redundancy in case 1 as the cost of the composition design. Also updates the internal-layout description to reflect that new_tf_mv() unions differing component domains by default rather than rejecting them, and refreshes the files list. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Captures four follow-up items in design/multivariate.md: - convenience verbs to regularize tf_mv args across components or entries, - shared-basis tfb_mv, - multivariate FPCA (MFPCA) as a first-class tfb_mv subclass, - a proper vignette with real-data case studies (e.g. gait). https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Notes the dispersal target for each *.tf_mv method (e.g. [.tf_mv into brackets.R, registration methods into register.R, calculus methods into calculus.R, etc.) once the feature stabilizes, while keeping the core constructors and shared mv helpers together. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
- tfb_mv.list short-circuits the empty-list case explicitly so the returned prototype is tfb_mv rather than tfd_mv (the all(map_lgl(empty, is_tf)) check is vacuously TRUE, which previously routed empty input into new_tf_mv() with the default tfd_mv class). - tfb_mv.list for non-tf input forwards ... to tfd_mv() only (not also to the subsequent tfb_mv.tf_mv() call), so user-supplied arg/domain are consumed once. - New test-mv-edge.R covers gaps surfaced by covr::package_coverage: empty prototype, n=1 / d=1, NA-curve propagation through ops/subset, Summary group generic (sum/min/max), var/sd, unary minus and the incompatible-op error path, tfb_mv.list (all-tf and non-tf branches), c(tfb_mv, tfb_mv) and ptype_abbr/full for tfb_mv, tfd_mv re-evaluation on a new grid, tf_rebase with an mv basis_from, tf_evaluate direct call, as.matrix(arg=) and as.data.frame both modes, ref_component = "norm" registration, tf_component<- adding components and length- mismatch rejection. mv code coverage: 63.9% -> 83.1% (the remainder is print/plot/format visual code). Full suite 1387/1387, zero regressions. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Replace "R^d" in the print header with the actual per-component evaluation ranges joined by " x " (e.g. "tfd_mv<d=2>[4] (x, y): [0, 1] -> [-2.19, 1.75] x [-8.51, 10.93]"), matching how the univariate print.tf header shows the range of f. Empty d=0 prototype keeps "R^0". https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
…nents Previously the unnest path assumed every component had the same long-form (id, arg) rows and assigned them side-by-side; that failed with a row-count mismatch when mixing tfd_reg + tfd_irreg components (or any two components with different arg structures). Build each component's long data.frame independently and merge() them with all = TRUE on (id, arg); components without an observation at a given (id, arg) get NA in their column. For already-aligned components the result is the same shape as before. Adds a "mixed regular/irregular components work across the API" test exercising construction, accessors, subset, c(), arithmetic, and the joined as.data.frame. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Previously tfb_mv(f, k = 10) shared a single ... across every component (same k, bs, sp, etc. for all dimensions); users wanting different specs had to pre-build each component with tfb() and wrap with tfb_mv.list(). Now any ... argument that is a list named by component names is distributed per-component, while everything else stays shared: tfb_mv(f, k = list(x = 5, y = 15), bs = "tp") fits component x with k = 5 and component y with k = 15, both with bs = "tp". A list whose names do not match the component names is treated as a shared argument value (back-compatible). This is "per-component basis spec, independent fits" -- distinct from the still-TODO "one basis shared across components" item. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
For f: [a,b] -> R^d the arc length is integral_a^b ||f'(t)|| dt. The
implementation is pure composition of existing verbs -- tf_derive.tf_mv for
per-component differentiation, sqrt(Reduce("+", map(., ^2))) for the
pointwise Euclidean norm of the derivative, then tf_integrate -- so no new
numeric kernels.
Signature mirrors tf_integrate (arg, lower, upper, definite, ...):
definite = TRUE (default) -> numeric vector of total lengths per curve
definite = FALSE -> univariate tfd giving the cumulative
arc length s(t) = integral_a^t ||f'(u)|| du
Tests in test-mv-verbs.R: unit-circle total length (~ 2*pi), vectorised
batch (k-loop -> 2*pi*k), partial integration via lower/upper, definite
vs indefinite mode, and a 3-d helix (2*pi*sqrt(1 + c^2)).
https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Picks up the per-component-basis-spec @details I added to tfb_mv and adds tf_arclength() to the @family tf_mv-class cross-reference block on the four sibling man pages -- both should have been committed alongside the corresponding R changes; the stop hook caught the omission. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Three changes:
1. Add tier-1 geometric helpers in R/mv-methods.R (all are 1-3 lines of
composition over existing univariate Ops/Math + tf_derive/tf_warp):
tf_norm(f) -- pointwise ||f(t)|| as univariate tfd
tf_speed(f) -- pointwise ||f'(t)|| as univariate tfd
tf_inner(f, g) -- pointwise <f, g> as univariate tfd
tf_distance(f, g) -- pointwise ||f - g|| as univariate tfd
tf_tangent(f) -- unit tangent f' / ||f'|| as tf_mv
tf_reparam_arclength(f) -- re-parametrize curve at constant speed
Also refactors mv_registration_signal's "norm" branch to use tf_norm.
2. tf_arclength now defaults to a polyline (sum-of-segments) method
rather than the derive+integrate composition. Polyline computes the
sum of Euclidean lengths of segments between consecutive sample
points in R^d, evaluating each component on each curve's grid (the
union across components/curves when those differ). This avoids the
compounding error of numerical differentiation followed by
quadrature on raw tfd_mv data; the derive method is kept available
via method = "derive" for analytic (tfb) settings or custom
tf_integrate forwarding. New tests confirm polyline beats derive on
the unit-circle benchmark.
3. design/multivariate.md gains a tier-2/tier-3 TODO list including
tf_curvature, tf_frenet, tf_rotate/translate/affine, tf_project,
tf_is_closed, tf_self_intersection, tf_align_rigid, and
tf_landmarks_extrema.tf_mv.
Tests (test-mv-geom.R, test-mv-verbs.R additions): tf_norm on a
constant (3,4)->5 vector, tf_speed = tf_norm o tf_derive, tf_inner
dot-product identity, tf_distance(f,f) = 0, unit-circle tangent has
unit norm, tf_reparam_arclength of f(t) = (t^2, 0) gives g(0.5) =
(0.5, 0) at speed 1, polyline vs derive accuracy comparison.
Full suite 1435/1435.
https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
tf_mv columns work end-to-end in the tidyverse pipeline via the existing vctrs proxy/restore plumbing -- this commit just locks that in with asserted tests rather than relying on smoke checks. Covers: tibble column construction & printing, dplyr::filter (incl. with a tf_mv-derived predicate), mutate (scalar reductions like tf_arclength, tfd reductions like tf_speed, in-place tfd_mv transforms like 2*path), summarize (mean(path) -> length-1 tfd_mv), group_by + summarize, arrange, slice, bind_rows, left_join, pull, distinct, tidyr::nest / unnest round trip, and rowwise mutate. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #233 +/- ##
==========================================
+ Coverage 85.58% 88.51% +2.92%
==========================================
Files 36 51 +15
Lines 4365 6876 +2511
==========================================
+ Hits 3736 6086 +2350
- Misses 629 790 +161 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Two roxygen mismatches caused R CMD check (and thus pkgdown) to fail on every CI runner: 1. tf_rebase.tf_mv had `@rdname tf_mv-methods`, which appended its \usage line (with formals `object, basis_from, arg, ...`) to the tf_mv-methods Rd page; those args were undocumented there. Move the explanatory @details block onto the tf_ncomp roxygen and leave tf_rebase.tf_mv with a bare @export so it stays attached to its own generic's Rd. 2. The @param list declared `f, x` but no \usage line on the Rd uses `x` (the `$` accessor is exported separately and not aliased to this page). Drop `x` from @param. Also tidies the @details to remove the contradiction between "by default from the pointwise Euclidean norm" and the immediately following "the registration signal is, by default, the first component" -- only the latter is true. R CMD check now reports only environment-induced messages (locale warning, blocked CRAN, missing fda/fdasrvf/refund Suggests, and the pre-existing `fdasrvf` Rd xref NOTE). Full suite 1474/1474. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
pkgdown evaluates `_pkgdown.yml` `contents:` entries as R expressions, so a topic name with a hyphen (`tf_mv-methods`) gets parsed as the subtraction `tf_mv - methods` and the build aborts. Renames the topic to `tf_mv_methods` (underscore) -- four `@rdname` directives in R/mv-methods.R, one `_pkgdown.yml` entry, and the generated Rd file (now man/tf_mv_methods.Rd, the stale hyphenated one is removed). Two more pkgdown errors surfaced from the topic-vs-alias mismatch on the first roxygen block of a multi-function topic: the Rd's \name and the implicit \alias both default to the first @export'ed object, so `tf_mv_methods` and `tf_geom` weren't registered as aliases. Adds an explicit `@name tf_mv_methods` / `@name tf_geom` to each topic's first block so the topic name resolves as an alias. Adds `tf_geom` and `tf_arclength` to the "Vector-valued functional data" section of `_pkgdown.yml` so all new reference pages are included in the site index (pkgdown errors out on missing topics). Local pkgdown::build_reference() now completes without errors; full test suite 1474/1474. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
…check
R-release / R-devel R CMD check flags `pkg::fn` references where `pkg`
is not declared in DESCRIPTION Imports or in NAMESPACE importFrom
("'::' or ':::' imports not declared from:"). plot.tf_mv and
lines.tf_mv use graphics::par, graphics::lines, and grDevices::n2mfrow;
only graphics::lines was implicitly imported (older code paths).
Add explicit @importFrom directives on the lines.tf_mv roxygen block
so roxygen2 emits the required NAMESPACE entries.
Reproduced and fixed locally: R CMD check now reports only the
environment-induced WARNING (locale) and the two pre-existing NOTEs
(fdasrvf Rd xref, missing CRAN Suggests in the sandbox).
https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Two more R CMD check "'::' or ':::' imports not declared from:" warnings: 1. test-mv-tidyverse.R uses tibble::tibble and tidyr::nest/unnest. R CMD check scans test files regardless of skip_if_not_installed(), so the suggesting packages need to be declared. Add tibble and tidyr to DESCRIPTION Suggests. 2. plot.tf_mv's roxygen had a [plot.tfd][tf::plot.tf] cross-reference that self-qualifies the host package; R CMD check treats that as an undeclared self-import. Use [plot.tf()] instead -- pkgdown and help() both resolve it cleanly. Local R CMD check is clean modulo the environment-induced WARNING (locale) and the pre-existing NOTEs (fdasrvf Rd xref, missing CRAN Suggests in the sandbox). Full suite still 1474/1474. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Address review findings and add missing functionality for the vector-valued (tfd_mv / tfb_mv) classes: - Fix Reduce-based ops on zero-component objects: ==, tf_norm, tf_inner now return zero-length results instead of NULL / erroring. - tf_reparam_arclength leaves zero-length (constant) curves unchanged with a clear warning instead of producing NaN warps. - tf_count(tfb_mv) aborts with an informative message. - Trajectory plots: recycle per-curve graphical params (col/lty/lwd via matlines), default to "trajectory" for d == 2, and evaluate components on a common grid so mixed / irregular grids no longer error. - Add [<-.tf_mv (component-wise replacement; supports NA assignment and casting) and names<-.tf_mv (curve names round-trip through subset / c()). - print.tf_mv reports per-component gridpoints + interpolator (tfd) or basis spec (tfb), collapsing when components agree. - tfd_mv docs: drop GitHub-issue references, add examples for the list, matrix, array and data.frame constructors. - Regression tests for all of the above. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make tf_norm/tf_inner/tf_tangent S3 generics so the pointwise geometric primitives also work on univariate tfd/tfb (norm = |f|, inner = f*g, tangent = f'/|f'|), with .default methods that emit informative cli errors. tf_speed/tf_distance generalize for free via the now-generic tf_norm. tf_mv inherits from "tf", so the .tf_mv methods stay selected for vector-valued input. Add input validation to user-facing tf_mv functions: - assert_tf_mv() helper - check_component_index() for tf_component()/tf_component<-(), rejecting out-of-range, fractional, multi-element, NA and logical selectors; fixes a crash where a length>1 character selector hit `&&` coercion - max_iter/tol checks in tf_estimate_warps.tf_mv() - lower<=upper / finite limits in tf_arclength.tf_mv() - explicit non-tf_mv rejection in tf_inner.tf_mv() Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
devtools::document() re-rendered all help topics with the locally installed roxygen2 8.0.0 (Config/roxygen2/version bumped from 7.3.3), which also reflows existing man pages to the newer link syntax. Fix the tf_component<- multi-length-selector test to match the checkmate "length 1" assertion message. R CMD check: 0 errors | 0 warnings | 0 notes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 tasks
- New pkgdown article walks through tfd_mv/tfb_mv end-to-end on the built-in gait data and on Atlantic hurricane tracks from dplyr::storms, covering construction, accessors, plotting (facet + trajectory), arithmetic/summaries, geometric primitives, basis fitting, and dplyr integration. - tf_arclength.tf_mv: per-curve clamp to the intersection of [lower, upper] with each curve's observed argument range, so irregular curves that don't span the global domain return their actual path length instead of erroring on NA paired evaluations. Fix cli plural marker on the now-defensive abort path. Regression test added. - plot.tf_mv (trajectory mode): honour user-supplied xlab/ylab via modifyList (prev: "matched by multiple actual arguments"); accept an alpha argument and apply it via grDevices::adjustcolor, matching plot.tf semantics. - Wire the article into _pkgdown.yml and ignore built HTML/artefacts under vignettes/articles/. Add knitr/rmarkdown to Suggests for building.
pkgdown's vignette index keys nest articles under `articles/<slug>`, so the bare `vector-valued-functions` entry in `_pkgdown.yml#articles` did not match any known topic and broke `navbar_articles()` during the site build.
Article (vignettes/articles/vector-valued-functions.Rmd) - Reframed around concrete analytical questions instead of an API tour. - Gait: pointwise mean+/-sd envelope; min/max arc-length subjects; variance-share / RMSE for an FPC basis; phase alignment via tf_register(method = "cc", ref_component = "hip"); unit-speed reparameterization via tf_reparam_arclength. - Storms: project (long, lat) into per-storm local-km coordinates so tf_arclength reports kilometres and tf_speed reports km/h (deg/h artefactually overweights northward motion via the cos(lat) shrink of a longitude degree). Map by peak Saffir-Simpson category; path-length boxplot vs intensity; forward-speed time courses split TS/TD vs Cat 4+; tfb_mv smoothing of the longest 6 tracks. R/mv-geom.R - tf_reparam_arclength: out[good] <- tf_warp(...) failed with a vctrs ptype mismatch when tf_warp upgraded tfd_reg -> tfd_irreg. Build the output by ptype-common vec_c() of warped + untouched curves followed by an index reordering instead of in-place subassign. R/mv-plot.R - plot.tf_mv(type = "facet"): prefer mfrow = c(1, d) for d <= 3 (the typical "small multiples in a row" layout fits standard figure widths without "figure margins too large"); fall back to n2mfrow for larger d.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Fix: drop unresolvable \link{} targets in tf_mv_unimplemented docs (urgent, CI red)
Hygiene B: code cleanup — globals, nocov, cli, internalize (#260)
PR #281 made prep_plotting_arg internal and deleted its Rd topic, but left the reference to it in _pkgdown.yml — pkgdown's reference-index build aborts with 'must be a known topic name or alias' (the topic no longer exists). Fixes the pkgdown CI failure introduced by #281. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Hygiene C: dedup backlog — NA helper, cum dispatcher, factories (#260)
Fix: drop orphan prep_plotting_arg entry from _pkgdown.yml (urgent, pkgdown CI red)
Split exported tf_landmarks_extrema from internal helpers. The shared @Rdname plus @Keywords internal previously caused R CMD check WARNING because non-exported detect_landmarks/cluster_landmarks/build_landmark_matrix appeared in \usage. Marked helpers @nord. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Without an explicit @name on the first-collated block (vctrs-mv.R), roxygen named the Rd \name{vec_ptype2.tfd_mv.tfd_mv}, breaking reference/vctrs.html. Pin @name vctrs on the first collated block. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
- Correct gait reference: data is in fda, not datasets (and the dataset is 39 children, not 39 boys). - Fix growth gender description: factor levels are female/male, not boy/girl. - Note in @Format that height/knee_angle/hip_angle are tfd columns. - Fix grammar typo "Data is also include" -> "Data is also included". https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Replace "as usual" / "dots" placeholders with proper one-line param descriptions; they were rendering literally in man/tfmethods.Rd. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
"an tf object" -> "a tf object". Replace the cryptic "for the x-axis...!" @param y description with a clear note that y is the evaluation grid passed through as arg, not y-axis values. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
These topics previously shipped without examples, which is unhelpful for discovery and breaks the rendered "Examples" sections on pkgdown. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
coef.tfb cleared attributes via attributes(object) <- NULL, which also stripped any user-set names. Save and restore names so coef(x) follows the same naming contract as the rest of the methods. Add a regression test in tests/testthat/test-names.R. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
"Matèrn" -> "Matérn" (the correct accent). https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
R style: avoid the F/T shorthand in example code. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
After 78a256b, coef.tfb returns a named list. Those names propagated through evaluate.R into per-element results, producing named numeric vectors in tf_evaluate output. test-evaluator.R:66 caught it: unlist() of the concatenated single-element results disambiguated duplicate inner names to "1.1","2.2","3.3", breaking equality with the bulk call. Strip names at the two internal call sites (the pmap branch and the length(arg)==1 cbind branch) so internal computation is name-agnostic while coef(x) still preserves names at the user level. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
The docs state empty strings are replaced with "NA" before deduplication, but the substitution only ran for inputs that were already character vectors, so factor or other inputs coercing to "" kept empty names. Move the substitution after as.character() so the documented behavior holds for all inputs, and add a regression test for factor input. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Replace the single use of mvtnorm::rmvnorm in tf_rgp() with a direct Cholesky factorization. Falls back to a small diagonal jitter when the covariance is numerically singular (e.g. squared-exp kernel with zero nugget), matching what mvtnorm's eigen-based default did implicitly. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Replace the single use of pracma::savgol in R/smooth.R with an inline re-implementation (~15 lines) based on Savitzky & Golay (1964): build the centered Vandermonde-style design matrix, take its SVD pseudoinverse (matching pracma::pinv's tolerance), then linearly convolve and trim fc samples from each end to recover the input length -- identical end-effect handling to pracma::savgol. Filter coefficients are cached per (fl, forder, dorder) so repeated tf_smooth() calls reuse them. Numerical output is bit-identical to pracma::savgol across the tested combinations. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
The previous chol()+jitter sampler added full-rank noise to near-singular
GP kernels, changing the effective rank of `tf_rgp()` draws compared to
the original `mvtnorm::rmvnorm(method = "eigen")` implementation. This
broke `test-tfb-fpc.R:75`, where the default `pve` cutoff began dropping
components that previously contributed to reconstruction.
Replace with a direct eigen-based sampler using the symmetric square root
`V D^{1/2} V^T` of the covariance. Negative eigenvalues from numerical
noise are clamped to zero, so rank-deficient kernels are sampled in their
effective subspace. Draws now match the prior `mvtnorm`-based output
bit-for-bit under `set.seed()`.
https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
The project pins roxygen via `Config/roxygen2/version: 8.0.0`; `devtools::document()` had injected a duplicate `RoxygenNote: 7.3.1`. NEWS: document the `tf_rgp()` sampler change and the drop of `mvtnorm` and `pracma` from Imports. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Address copilot review on #283: - Validate fl as integer scalar (rejects fl = 2.5), forder/dorder as non-negative integers, and require dorder <= forder and fl > forder (prevents indexing error in .savgol_coefs()) - Qualify convolve() as stats::convolve() to avoid R CMD check note - Fix stale chol() comment in test-rgp.R (sampler is eigen-based) https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
PR #278 (mfpc invariants) introduced new_tfb_fpc_demoted() which calls mfpc_quad_weights(arg). PR #282 (hygiene dedup) consolidated the four quadrature-weight implementations into a single trapezoid_weights() helper and removed mfpc_quad_weights. The two PRs were merged without detecting the semantic conflict, so all mfpc demote paths now abort with 'could not find function mfpc_quad_weights'. One-line fix: call trapezoid_weights() (same math, just the new name). Fixes 9 test failures in test-mfpc.R covering arithmetic on demoted tfb_mfpc, $<- demote, and chained Math/Ops. https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Fix: use trapezoid_weights() in new_tfb_fpc_demoted (urgent, CI red)
Hygiene A: documentation fixes (#260)
Hygiene D: drop mvtnorm and pracma (#260)
PR #280 added an @examples block to man/fpc_wsvd.Rd that calls fpc_wsvd(data, ...) at the user level, but the generic itself was never exported -- only its .matrix and .data.frame methods carried @export tags (registered via S3method() in NAMESPACE). R CMD check --as-cran runs the examples in a fresh session that only sees exported symbols, so 'could not find function fpc_wsvd' aborts the check with 2 ERRORs. One-line @export on the generic + matching export(fpc_wsvd) in NAMESPACE (hand-added; no document() to avoid the roxygen drift churn on ~40 Rd files). https://claude.ai/code/session_01M1QMfji5MpKJvzJYw5Kjb9
Fix: export fpc_wsvd generic (urgent, CI red — 2 ERRORs in R CMD check)
- tf_fwise(), tf_fmean(), tf_fvar(), tf_fsd() gain tf_mv methods returning component-wise (n x d) matrices - tf_interpolate(), tf_sparsify(), tf_jiggle() gain component-wise tf_mv methods; same_arg = TRUE keeps shared component grids - [.tf_mv supports multi-component subsets via component = c(...) - tfb_mfpc: preserve/reorder the mfpc spec across component renaming - export prep_plotting_arg() as a developer tool; landmark docs reorganized under tf_landmarks_extrema Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Joint conditions across components, referenced by name (e.g. tf_where(f, x > 0 & y < 1)); per-component use remains available via f[, component = ...]. Closes part of the tf_mv verb gap (#255). - using `value` in a condition aborts with classed error tf_mv_where_value and a hint listing the component names - components on different grids abort with tf_mv_incommensurate_args (interpolate first, or supply a single numeric `arg`); the per-component list returned by tf_arg() on such objects is rejected since it would be misread as per-curve grids - "arg" is now a reserved component name: it would silently shadow the grid column in evaluation data.frames - shared workhorse tf_where_impl() no longer uses subset(), whose NSE would let component names capture local variables; logical indexing preserves subset()'s recycling and NA semantics - return = "range" on zero-length input now returns an empty begin/end data.frame instead of erroring Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implementation of vector-valued functional data — curves into$\mathbb{R}^d$ , e.g. movement trajectories — addressing #18 ("multivariate evaluations (curves)") and #27 (movement data, à la Joo et al. 2019). What began as a prototype is now a fairly complete feature: regular + irregular sampling, the full vctrs/tidyverse surface, registration up to elastic shape space, multivariate FPCA, and a worked vignette. Package version bumped to 0.4.2;
R CMD checkis clean.Design
Composition approach: a
tf_mvvector of lengthnbundlesdunivariatetfvectors (one per output dimension) as an attribute, with a customvec_proxy/vec_restorepair (data-frame-of-components proxy). Almost every method falls out of mapping the existing univariate code over thedcomponents — very few new numeric kernels. Bothtfdandtfbrepresentations work, regular + irregular sampling work, and per-component differing arg grids are accommodated. The three candidate designs (composition vs. matrix-valued evaluations vs. long/stacked) are compared inattic/design/multivariate.md.Core classes & API
tfd_mv/tfb_mv(parenttf_mv); thetf_mv ⊂ tfinheritance contract is made explicit.c(), casting, ptype2, tibble columns,vec_ptype_abbr/vec_ptype_full.tf, list-of-matrices, 3-d[curve, arg, component]array, and long/wide data frames.tf_ncomp,tf_components,tf_component(<-),$/$<-,tf_arg,tf_evaluations,tf_count,is.na.[returns a[curve, arg, component]array (classes for surfaces / images #18's "array-valuedj") with acomponent=selector (incl. multi-component selection); matrix-index(curve, arg)pairs return one row per pair ×dcolumns.Math,Summary,mean/median/sd/var,==/!=.tfb_mvaccepts per-component basis specs via component-named list...args (e.g.k = list(x = 5, y = 12)).tf_rebase,tf_derive,tf_integrate(definite →n × dmatrix; indefinite →tf_mv),tf_smooth,tf_zoom.as.matrix→[curve, arg, component]array;as.data.frame(unnest = TRUE)full-outer-joins on(id, arg)(mixed reg/irreg components handled); long/wide schemas.plot.tf_mvwith"facet"(one panel per component) or"trajectory"(d == 2); print/format header reports per-component value ranges.Registration & alignment — a 4-rung ladder
A single shared time-warp is estimated per curve and applied jointly to all components, via
tf_register()/tf_estimate_warps()/tf_warp()/tf_align()with accessorstf_aligned(),tf_inv_warps(),tf_template().tf_reparam_arclength(): constant-speed (parametrization-only) reparametrization.method = "cc": warp from a single 1-d reference signal (ref_component,"norm"for the pointwise Euclidean norm, or a customfunction(tf_mv) → tf); also"affine","landmark", and per-component"srvf".method = "srvf_mv": true multivariate elastic (Fisher–Rao / SRVF) registration that aligns the joint(hip, knee, …)trajectory using all components at once, with the multivariate Karcher-mean template.tf_register_shape(): full elastic shape registration — warp + rotation + scale — landing in a centered, normalized shape space;tf_rotations()/tf_scales()expose the estimated rotations and template-relative scale factors.(
srvf/srvf_mv/ shape registration use thefdasrvfpackage, in Suggests.)Multivariate FPCA —
tfb_mfpc()(new)tfb_mvwhose components aretfb_fpcobjects sharing identical scores, so reconstruction / printing / plotting work via the existing machinery."inverse_variance"(default),"snr","equal", or a numeric vector; separate univariate (uni_pve) and multivariate (pve/npc) truncation.tfd_mvdata is projected onto a fitted basis (joint re-scoring) viatf_rebase()/vec_cast().tf_mfpc_scores(),tf_mfpc_efunctions(); predicateis_tfb_mfpc().Geometry primitives
tf_norm,tf_speed,tf_inner,tf_distance,tf_tangent,tf_reparam_arclength, andtf_arclength(method = "polyline"default, or"derive"; definite and indefinite modes). Generalized to work on univariatetftoo.Vignette & docs
attic/vector-valued-functions.Rmd: two real-data case studies (tf::gait;dplyr::stormsas 4-d(long, lat, wind, pres)), the 4-rung alignment ladder with a shape-space quotient demo, and FPC + MFPCA sections — with a proper literature bibliography (attic/references.bib).converters-mv,tf_register_shape,tfb_mfpc, …); design doc and prototype history live underattic/.NEWS.mdupdated for 0.4.2.Status
R CMD check: 0 errors / 0 warnings / 0 notes (tf 0.4.2).srvf_mvand shape), MFPCA, and tibble/dplyr/tidyr interop. Relevant files:test-tfd-mv,test-tfb-mv,test-mv-vctrs,test-mv-methods,test-mv-verbs,test-mv-edge,test-mv-geom,test-mv-contract,test-mv-tidyverse,test-register-mv-srvf,test-mfpc.devtools::check(),srvfregistration ontf_mv(now a first-class multivariate method + tests), a real-datagaitwalkthrough (the vignette), and the design-doc tone (now an internalattic/record with the user-facing vignette alongside).Future work (out of scope; tracked in
attic/design/multivariate.md)tfb_mv(one basis system across components, singlebasis_matrix+dcoefficient vectors);tf_curvature,tf_frenet,tf_rotate/tf_translate/tf_affine,tf_project,tf_is_closed,tf_self_intersection;R/*-mv.Rmethods into the existing per-topic files.🤖 Generated with Claude Code