% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/char_thresh_local.R
\name{char_thresh_local}
\alias{char_thresh_local}
\title{Calculate a per-sample local threshold for charcoal peak identification}
\usage{
char_thresh_local(
  charcoal,
  smoothing,
  peak_analysis,
  site = NULL,
  results = NULL,
  plot_data = 0L
)
}
\arguments{
\item{charcoal}{Named list with \code{peak} (C_peak series), \code{ybpI}
(resampled ages), and \code{accI}.}

\item{smoothing}{Named list with \code{yr} (window width in years).}

\item{peak_analysis}{Named list with \code{cPeak}, \code{threshMethod},
\code{threshValues} (length 4).}

\item{site}{Character string (site name; unused in R, kept for API
symmetry).}

\item{results}{Named list (unused in R, kept for API symmetry).}

\item{plot_data}{0/1 flag; ignored in R (no diagnostic plots).}
}
\value{
Named list \code{char_thresh} with elements:
  \describe{
    \item{pos}{Numeric matrix [N x 4]: per-sample positive threshold for each
      of the four \code{threshValues}, after Lowess smoothing.}
    \item{neg}{Numeric matrix [N x 4]: per-sample negative threshold, after
      Lowess smoothing.}
    \item{SNI}{Numeric vector [N]: signal-to-noise index time series, Lowess-
      smoothed and clamped to \eqn{\geq 0}.}
    \item{GOF}{Numeric vector [N]: KS goodness-of-fit p-values per sample
      (NA where fewer than 4 noise samples exist).}
  }
}
\description{
Determines a sliding-window threshold value for each sample, based on the
distribution of C_peak within a window centred on that sample.  The noise
component within each window is modelled as a zero-mean Gaussian (residuals),
a one-mean Gaussian (ratios), or via a Gaussian mixture model (GMM).
Mirrors \code{CharThreshLocal.m} from the MATLAB v2.0 codebase.
}
\details{
## Window selection
  The half-window is \code{hw = round(0.5 * smoothing$yr / r)} samples, where
  \code{r = mean(diff(charcoal$ybpI))} is the record resolution.  The window
  is shifted (not shrunk) at record boundaries, matching MATLAB's loop logic.

  ## NaN bridging within windows
  NaN entries in \code{charcoal$peak} (from record gaps) are replaced with the
  neutral value (0 for residuals, 1 for ratios) before distribution fitting.
  This preserves window size while preventing gap samples from biasing the fit,
  matching the v2.0 bug fix documented in \code{CharThreshLocal.m}.

  ## Small-sample fallback
  If fewer than 4 non-neutral samples exist in the window, a simple Gaussian
  is fitted (same as method 2) and the KS test is skipped.

  ## GMM (threshMethod = 3)
  MATLAB's \code{GaussianMixture(X, 2, 2, false)} is replicated by
  \code{gaussian_mixture_em(X)} from \code{utils_gaussian_mixture.R}.  This
  uses the same first/last-point initialisation and loose convergence
  criterion (\eqn{\epsilon = 0.03 \log N}) as the original Bowman CLUSTER
  EM, substantially improving agreement with MATLAB threshold values compared
  to \code{mclust}.  The noise component is identified as the Gaussian with
  the smaller mean.  Poor-fit fallback (mu1 == mu2) re-fits with K = 3 and
  takes the two components with the smallest means, mirroring the MATLAB v2.0
  bug fix (the fallback passes the local window X, not the full record).

  ## KS goodness-of-fit
  MATLAB evaluates the fitted normal CDF at 101 equally-spaced bin centres
  and calls \code{kstest()} with a custom CDF table.  R uses
  \code{stats::ks.test(noise, "pnorm", mu, sigma)}, which evaluates the
  continuous CDF at each observation.  P-values may differ by a small amount
  for small samples; the statistic converges as sample size grows.

  ## Post-loop smoothing
  After the per-sample loop, \code{pos}, \code{neg}, and \code{SNI} are
  smoothed with \code{char_lowess(span = smoothing$yr / r, iter = 0)}.
  SNI is then clamped to \eqn{\geq 0}.
}
\seealso{
[char_thresh_global()], [char_smooth()], [CharAnalysis()]
}
