Skip to content

Add log-triangular (ltriangle) distribution#475

Closed
joethorley wants to merge 26 commits into
bcgov:mainfrom
poissonconsulting:add-triangle-distribution
Closed

Add log-triangular (ltriangle) distribution#475
joethorley wants to merge 26 commits into
bcgov:mainfrom
poissonconsulting:add-triangle-distribution

Conversation

@joethorley

Copy link
Copy Markdown
Collaborator

Adds a log-triangular distribution (ltriangle): log(concentration) follows a symmetric triangular distribution with mode locationlog and half-width scalelog. Symmetric on the log scale like lnorm/llogis, but with bounded support and no tails. Wired in following the llogis pattern (R/ltriangle.R, src/TMB/ll_ltriangle.hpp, dist_data row, multi/params, test-ltriangle.R, distributions.Rmd vignette). Fits ccme_boron to an interior optimum and runs in ssd_fit_dists(dists = ssd_dists_all()). Two pre-existing test-hc.R failures (percent deprecation) are unrelated. Full body to follow.

🤖 Generated with Claude Code

joethorley and others added 26 commits May 24, 2026 12:50
The small-sample-bias vignette depended on mle.tools, which has been
removed from CRAN (bcgov#474), preventing it from knitting. It has been
transferred to the ssdvignettes package. Removes the vignette source,
rendered PDF and stub, the _pkgdown.yml article entry, the release
question, and the now-unused mle.tools and reshape2 Suggests.

Fixes bcgov#474

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove small sample bias vignette (moved to ssdvignettes)
…ot2 aes

- mean_weighted_values() now reads data$weight rather than misusing the
  logical weight flag as the weight vector, so weighted fits get weighted
  starting values (uniform-weight results are unchanged).
- Remove duplicate ssd_elgumbel() definition (the first also carried an
  incorrect ssd_einvpareto() @examples); sync man/ssd_e.Rd.
- Replace deprecated ..density.. with after_stat(density) in StatSsdpoint
  and StatSsdsegment default aesthetics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- dplyr is used unconditionally throughout R/, so move it from Suggests to
  Imports (a Suggests-only dependency used without a guard is a latent
  failure when dplyr is absent).
- Replace the sole plyr::summarise call in GeomXribbon with base
  data.frame and drop plyr from Imports, NAMESPACE, and the package import.
- Remove unused Suggests: magrittr, reshape2, tidyselect (no references in
  R/, tests/, or vignettes/).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The six model-averaged multi functions each hard-coded the full set of
~33 distribution parameters: ssd_pmulti/ssd_qmulti/ssd_rmulti as a
signature plus a verbatim forwarding call, and pmulti_ssd/qmulti_ssd/
rmulti_ssd as a signature plus a verbatim list(). Adding or renaming a
distribution parameter meant editing six identical blocks, where a typo
could silently diverge.

- Make .relist_estimates() order-robust by reshaping the flat arguments
  by name rather than position. The internal list() blocks were
  previously load-bearing because relist() is positional; with the
  name-based reshape the order parameters arrive in no longer matters.
- Collapse the internal pmulti_ssd/qmulti_ssd/rmulti_ssd to
  function(q/p/n, ...) forwarding list(...) to .relist_estimates(), and
  rename qmulti_ssd's first argument q -> p (it is a probability).
- Replace the forwarding bodies of the exported ssd_pmulti/ssd_qmulti/
  ssd_rmulti with do.call(<dist>, c(list("multi"), as.list(environment()))).
  Their explicit signatures are retained so argument names and defaults
  remain validated and documented.

multi.R drops from 632 to 296 lines. Output verified byte-identical to
the previous implementation across p/q/r, mixtures, unnormalised
weights, seeded random generation, round-trips, the non-canonical
argument-order path, and the zero-weight error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The model-averaged CDF was assembled by pasting R source into a string
and eval(parse())-ing it, and tdist() resolved a function by parsing its
name. Both defeat static analysis and are fragile to formatting.

- pmulti_fun() now returns a closure that sums weight * pXXX_ssd(q, ...)
  over the component distributions (using get() for lexical lookup of the
  component CDF) rather than building and parsing a source string. The
  now-unused string helpers value_args() and pmulti_dist() are removed.
- tdist() resolves ssd_p<dist> with match.fun() instead of eval(parse()).

Output verified identical to the string-based implementation across the
p/q/r paths, distribution mixtures, lower.tail/log.p, round-trips and
seeded random generation; match.fun() resolves the same ssd_p<dist>
function as eval(parse()) for every distribution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Extract the repeated percent->proportion deprecation-and-validation
  block (duplicated across ssd_hc.list/.fitdists/.fitburrlioz and
  predict.fitdists/.fitburrlioz) into a single .hc_proportion() helper.
- ssd_plot(): validate that hc is numeric (before chk_subset) and that
  decimal.mark is a string (previously only big.mark and suffix were
  checked, while ssd_plot_data() checked decimal.mark but not suffix).
- ssd_plot_data(): validate that suffix is a string.
- .pd()/.qd(): replace the bare stop() length-1 assertion with an err()
  carrying a message.
- Fix incorrect function names in two ssd_hp() deprecation messages
  (they referred to ssd_hc() and ssd_hp_bcanz()).
- Fix "can not" -> "cannot" in the GeomXribbon aesthetics error.

The .hc_proportion() helper is verbatim the previous inline logic;
output verified identical across the supplied / not-supplied / invalid
percent and proportion paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
None of these change behaviour; they remove unused arguments and dead or
fragile code flagged in the code review.

- no_hcp(): drop the unused `hc` parameter (both call sites pass none).
- bburrIII3()/blnorm_lnorm()/bllogis_llogis(): drop the unused leading
  `x` parameter. These are dispatched by bdist() via named arguments
  (data, min_pmix, range_shape1, range_shape2), so `x` was never filled.
- sample_parametric(): drop the self-referential `args = args`,
  `weighted = weighted`, `censoring = censoring` default bindings; the
  sole caller (generate_data) always supplies them.
- clean_hcp(): guard the min_pboot mask with !is.na(pboot), matching
  replace_min_pboot_na(); identical when pboot has no NA, NA-safe when it
  does (previously any(NA < min_pboot) could error the if()).
- hcp_combine_samples(): drop the redundant nboot1 alias (nboot is a
  function argument, not a column, so it resolves directly like the
  sibling weight/geometric arguments in the same call).
- hcp_noci(): remove a trailing comma in the tibble() call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove repeated 33-parameter blocks in multi.R
…messages

Deduplicate proportion handling, tighten validation and messages
…al-parse

Replace eval(parse()) metaprogramming with direct dispatch
The previous commit replaced the nboot1 alias with a bare nboot inside
dplyr::summarise(), but the bound hcp tibble has an nboot column, so the
bare reference resolved to that column vector rather than the scalar
function argument, breaking combine_samples()/rep(). Reference the
argument explicitly via .env$nboot (the original nboot1 alias existed to
dodge exactly this data-mask shadowing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mean_weighted_values() fix means weighted fits now derive starting
values from properly weighted data, which shifts the converged estimates
at the 6th significant figure (e.g. 0.546531 -> 0.546532 in hcallw10,
meanlog 0.6239 -> 0.623898 in the print snapshot). Regenerated the two
affected snapshots; no other snapshots changed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
update range_shape1 and range_shape2 parameter descriptions
…-fixes

Fix weighted starting values, duplicate definition, and dependency cleanup
…leanup

Remove dead parameters and tidy minor cleanups
Add a log-triangular distribution where log(concentration) follows a
symmetric triangular distribution with mode `locationlog` and half-width
`scalelog`. Wired in following the `llogis` pattern:

- R/ltriangle.R: ssd_pltriangle/qltriangle/rltriangle/eltriangle, the
  base symmetric-triangular helpers (p/q/rtriangle_ssd), the
  concentration-scale helpers (pltriangle_ssd/qltriangle_ssd) used in
  model averaging, and sltriangle starting values.
- src/TMB/ll_ltriangle.hpp: TMB negative log-likelihood (CondExp-based
  CDF, soft floor on the density) registered in ssdtools_TMBExports.cpp.
- dist_data: ltriangle row (bcanz=FALSE, tails=FALSE, npars=2,
  valid=TRUE, bound=FALSE) - bounded support, no tails, no bounded
  parameter.
- ssd_pmulti/qmulti/rmulti and params.R documentation extended.
- tests/testthat/test-ltriangle.R mirrors test-llogis.R (test_dist plus
  snapshots); test-dists.R lists updated; affected snapshots regenerated.
- distributions.Rmd gains a log-triangular section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joethorley

Copy link
Copy Markdown
Collaborator Author

Closing as duplicate; superseded by poissonconsulting#169.

@joethorley joethorley closed this Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants