# Introduction to sshist

The `sshist` package provides a suite of tools for optimal density estimation based on the work of Shimazaki and Shinomoto (2007, 2010). The core idea — minimizing the expected Mean Integrated Squared Error (MISE) between an estimator and the unknown underlying density — is available in four flavors:

Function | Estimator | Dimension | Bandwidth
--- | --- | --- | ---
`sshist` | Histogram | 1D | Fixed (single bin width)
`sshist_2d` | Histogram | 2D | Fixed per axis
`sskernel` | Kernel density | 1D | Fixed global
`ssvkernel` | Kernel density | 1D | Locally adaptive (Abramson)
`sskernel2d` | Kernel density | 2D | Fixed global (isotropic)
`ssvkernel2d` | Kernel density | 2D | Locally adaptive (Abramson)

These methods are particularly useful when data exhibits multimodal structure, because naive rules (Sturges, Freedman–Diaconis) tend to over- or under-smooth.

Note: The package supports OpenMP C++ multithreading and parallel bootstrapping out of the box. For CRAN compliance, it defaults to 1 core. You can globally increase this by setting the option below.

    library(sshist)
    options(sshist.ncores = 2) # Use 2 cores for vignette building

## 1. Optimal 1D Histogram: `sshist`

The `sshist` function performs an exhaustive search over candidate bin counts to find the optimal bin width that minimizes the MISE cost function.

Complete Parameter List:

- `x`: Numeric vector of data points. Missing values (`NA`) are automatically removed.
- `n_max`: Integer or `NULL`. Maximum number of bins to consider. If `NULL`, it is determined automatically based on data resolution to prevent comb artifacts.
- `sn`: Integer. Number of histogram shifts used for the shift-average estimation (default `30`).
- `ncores`: Integer. Number of OpenMP threads to use for the C++ cost calculation (default `1` or via `getOption("sshist.ncores")`).

### Comparison with the standard approach

    data(faithful)
    x_data <- faithful$eruptions

    oldpar <- par(mfrow = c(1, 2))

    # Standard R histogram (Sturges rule)
    hist(x_data,
         main = "Standard hist()  (Sturges)",
         xlab = "Eruption duration (min)", col = "grey90")

    # Shimazaki-Shinomoto optimal histogram
    res <- sshist(x_data)
    hist(x_data, breaks = res$edges,
         main = paste0("sshist()  (N = ", res$opt_n, " bins)"),
         xlab = "Eruption duration (min)", col = "grey90")

    par(oldpar)

`sshist` automatically detects the bimodal structure that the default rule obscures.

### The S3 object

`sshist()` returns an object of class `"sshist"`. The `plot()` method draws the optimal histogram with a data rug.

    print(res)
    #> Shimazaki-Shinomoto Histogram Optimization
    #> ------------------------------------------
    #> Optimal Bins (N): 20
    #> Bin Width (D):    0.175
    plot(res)

## 2. Optimal 1D Kernel Density: `sskernel` & `ssvkernel`

For smooth continuous distributions, `sskernel` evaluates a single fixed global bandwidth, while `ssvkernel` computes locally adaptive bandwidths based on Abramson’s square-root scaling law. Both use a fast FFT implementation.

Complete Parameter List:

- `x`: Numeric vector of sample data.
- `tin`: Optional numeric vector specifying the grid of evaluation points. If `NULL`, a grid is generated automatically.
- `W` (sskernel only): Optional numeric vector of bandwidths to evaluate. If provided, the internal grid search is skipped.
- `M` (ssvkernel only): Integer. Number of bandwidths to examine during the adaptive process (default `80`).
- `WinFunc` (ssvkernel only): Character string specifying the window function for local weights. Options: `"Gauss"`, `"Boxcar"` (default), `"Laplace"`, or `"Cauchy"`.
- `nbs`: Integer. Number of bootstrap samples for calculating confidence intervals. Set to `0` to skip (default `0`).
- `ncores`: Integer. Number of CPU cores to use for parallel bootstrapping (default 1L or via getOption(`"sshist.ncores"`, 1L)).

### Fixed Global Bandwidth (`sskernel`)

Estimates a 1D density using a Gaussian kernel with the globally optimal bandwidth.

    res_k <- sskernel(x_data)
    print(res_k)
    #> Shimazaki-Shinomoto Kernel Density Estimation
    #> ----------------------------------------------
    #> Optimal Bandwidth: 0.1103
    #> Grid Points:       1000
    plot(res_k, xlab = "Eruption duration (min)")

### Locally Adaptive Bandwidth (`ssvkernel`)

Uses a stiffness parameter `gamma` (optimized via MISE) to control how much the bandwidth can vary locally. A Nadaraya–Watson smoother with the selected window function (`WinFunc`) stabilizes the bandwidth sequence.

    res_sv <- ssvkernel(x_data)
    print(res_sv)
    #> Shimazaki-Shinomoto Adaptive Kernel Density Estimation
    #> -------------------------------------------------------
    #> Optimal Stiffness (gamma): 1
    #> Grid Points:               1000
    #> Bandwidth Range:           0.04199282 -- 0.2519655
    plot(res_sv, xlab = "Eruption duration (min)")

### Estimating Uncertainty with Bootstrap

By setting the `nbs` parameter, you can instruct the function to perform a multi-core bootstrap resampling. For `sskernel`, this is a standard non-parametric bootstrap. For `ssvkernel`, it utilizes a Poisson bootstrap suited for point processes.

When bootstrap intervals are calculated, the S3 `plot()` method automatically overlays a 90% confidence band (between the 5th and 95th percentiles).

    # Run 300 bootstrap iterations using 2 cores
    res_boot <- sskernel(x_data, nbs = 300)
    print(res_boot)
    #> Shimazaki-Shinomoto Kernel Density Estimation
    #> ----------------------------------------------
    #> Optimal Bandwidth: 0.1103
    #> Grid Points:       1000
    #> Bootstrap CI:      90%  ( 300 samples)

    # The plot function automatically detects and renders the confb95 band
    plot(res_boot,
         main = "Optimal KDE with 90% Bootstrap Confidence Band",
         xlab = "Eruption duration (min)",
         col = "#d6604d",
         band_col = adjustcolor("#d6604d", alpha.f = 0.2))

    res_sv_boot <- ssvkernel(x_data, nbs = 300, WinFunc = "Gauss")

    print(res_sv_boot)
    #> Shimazaki-Shinomoto Adaptive Kernel Density Estimation
    #> -------------------------------------------------------
    #> Optimal Stiffness (gamma): 0.9947
    #> Grid Points:               1000
    #> Bandwidth Range:           0.0490827 -- 0.2415164
    #> Bootstrap CI:              90%  ( 300 samples)

    # The plot function automatically detects and renders the confb95 band
    plot(res_sv_boot,
         main = "Locally Adaptive KDE with 90% Bootstrap Confidence Band",
         xlab = "Eruption duration (min)",
         col = "#d6604d",
         band_col = adjustcolor("#d6604d", alpha.f = 0.2))

Note: The raw density values for every bootstrap iteration are preserved in the `yb` matrix inside the returned object, giving you full flexibility to calculate custom percentiles if needed.

## 3. Optimal 2D Density Estimation

When analyzing bivariate relationships, the package offers both grid-based histograms and smooth kernel density estimators.

### 2D Histogram: `sshist_2d`

Solves the X and Y bin-count problem simultaneously using the same MISE cost function extended to two dimensions.

Complete Parameter List:

- `x`: Numeric vector for X coordinates, or a 2-column matrix/data.frame containing X and Y.
- `y`: Numeric vector for Y coordinates (ignored if `x` is a matrix).
- `n_min`: Integer. Minimum number of bins per axis (default `2`).
- `n_max`: Integer or `NULL`. Maximum number of bins per axis (default `200`). Automatically clamped to the data resolution limit.

    res_2d <- sshist_2d(faithful$waiting, faithful$eruptions)
    print(res_2d)
    #> Shimazaki-Shinomoto 2D Histogram Optimization
    #> ---------------------------------------------
    #> Optimal Bins X:   9
    #> Optimal Bins Y:   20
    #> Bin Width X:      5.889
    #> Bin Width Y:      0.175

    # plot() renders the optimal 2D histogram as a heatmap
    plot(res_2d, xlab = "Waiting time (min)", ylab = "Eruption duration (min)")

### 2D Kernel Density: `sskernel2d` and `ssvkernel2d`

Complete Parameter List:

- `x`: Numeric vector for X coordinates, or a 2-column matrix containing X and Y.
- `y`: Numeric vector for Y coordinates (required if `x` is a vector).
- `W` (sskernel2d only): Optional numeric vector of bandwidths to evaluate. If `NULL`, a smart log-spaced grid is used.
- `n_grid`: Integer specifying the resolution of the output density matrix (default `100`).
- `sensitivity` (ssvkernel2d only): Numeric scalar controlling the sensitivity of local bandwidths to the pilot density. A value of `0.5` (default) corresponds to Abramson’s inverse square-root law.
- `ncores`: Integer. Number of OpenMP threads to use for the heavy C++ 2D grid computations.

`sskernel2d` finds the globally optimal isotropic bandwidth. To handle variables on different scales, the data are internally standardized before optimization; the resulting bandwidth is then rescaled back to each original axis.

    df <- faithful   # eruptions: ~2-5 min,  waiting: ~40-90 min

    # Fixed global bandwidth (C++ accelerated)
    res_2d_fixed <- sskernel2d(df$eruptions, df$waiting, n_grid = 256)
    print(res_2d_fixed)
    #> Shimazaki-Shinomoto 2D Kernel Density Estimation
    #> -------------------------------------------------
    #> Optimal Bandwidth X: 0.1947
    #> Optimal Bandwidth Y: 2.319
    #> Grid Size:           256 x 256
    plot(res_2d_fixed,
         xlab = "Eruption duration (min)",
         ylab = "Waiting time (min)")

`ssvkernel2d` wraps `sskernel2d` as a pilot estimator and applies Abramson’s square-root law to derive a per-point local bandwidth.

    # Locally adaptive bandwidth (Abramson's method)
    res_2d_adap <- ssvkernel2d(df$eruptions, df$waiting,
                                n_grid = 256, sensitivity = 0.5)
    print(res_2d_adap)
    #> Shimazaki-Shinomoto Adaptive 2D Kernel Density Estimation
    #> ----------------------------------------------------------
    #> Pilot Bandwidth X:   0.1947
    #> Pilot Bandwidth Y:   2.319
    #> Grid Size:           256 x 256
    #> Lambda Range:        0.653 -- 3.33   (median 0.921 )
    plot(res_2d_adap,
         xlab = "Eruption duration (min)",
         ylab = "Waiting time (min)")

The adaptive estimator dramatically sharpens the two high-density clusters while smoothing the near-empty valley between them — dynamically matching local data density.

## References

- Shimazaki, H. and Shinomoto, S. (2007). A method for selecting the bin size of a time histogram. Neural Computation, 19(6), 1503–1527. doi:10.1162/neco.2007.19.6.1503

- Shimazaki, H. and Shinomoto, S. (2010). Kernel bandwidth optimization in spike rate estimation. Journal of Computational Neuroscience, 29(1–2), 171–182. doi:10.1007/s10827-009-0180-4

- https://www.neuralengine.org/res/histogram.html

- https://www.neuralengine.org/res/kernel.html

- https://github.com/shimazaki/density_estimation

- https://s-shinomoto.com/toolbox/sshist/hist.html