# Getting started with missknn

## Overview

`missknn` imputes missing values in tabular data with a masked k-nearest-neighbor engine: distances are computed only on jointly observed variables, the compute-heavy steps (distance, neighbor ranking, aggregation) run in C++, and each column’s neighborhood size and estimator are chosen automatically from a small internal holdout evaluation rather than fixed globally.

    library(missknn)

## Single imputation

    set.seed(1)
    x <- data.frame(
      a = c(1, 2, NA, 4, 5),
      b = c(2, NA, 3, 4, 6),
      g = factor(c("x", "x", "y", NA, "y"))
    )

    imp <- missknn(x, k = 2, m = 1)
    missknn::complete(imp)
    #>          a        b      g
    #>      <num>    <num> <fctr>
    #> 1: 1.00000 2.000000      x
    #> 2: 2.00000 2.336285      x
    #> 3: 2.74969 3.000000      y
    #> 4: 4.00000 4.000000      y
    #> 5: 5.00000 6.000000      y

`missknn()` detects numeric and categorical (factor/character/logical) columns automatically, and imputes both from the same masked-distance neighborhood: numeric targets get a distance-weighted mean or a local regression fit (whichever validates better for that column), categorical targets get a distance-weighted majority vote.

### A note on `complete()`

`complete()` is a common generic name - packages like `mimar` or `tidyr` may also define one. If another attached package’s `complete()` shadows `missknn`’s, call it explicitly:

    missknn::complete(imp)

## Multiple imputation

Setting `m > 1` draws `m` completed datasets by sampling (rather than averaging) from each receiver’s nearest donors, using the same distance model throughout:

    imp_mi <- missknn(x, k = 2, m = 5, seed = 1)
    length(missknn::complete(imp_mi))
    #> [1] 5

## Per-column adaptive tuning

Rather than using one fixed `k` for every column, `missknn()` runs a small internal holdout evaluation per column to choose:

- how many neighbors (`k`) to use,
- whether a distance-weighted mean or a local weighted-regression fit predicts held-out values better, and
- whether the column has any locally exploitable signal at all – if not, it is filled directly from the global mean/mode, skipping neighbor search entirely for that column.

This is inspected via the fitted object’s `meta`:

    imp$meta$k_per_col
    #> [1] 2 2 2
    imp$meta$estimator_per_col
    #> [1] "regression" "regression" "regression"
    imp$meta$is_global_col
    #> [1] FALSE FALSE FALSE

## Why this matters for larger data

Naive KNN imputation searches every donor for every missing value: cost grows quadratically with the number of rows. `missknn` bounds the neighbor-search candidate pool with `donor_cap` (default 2000), so cost stays close to linear in `n` even for large datasets, while the per-column global fast path above further skips neighbor search for columns where it wouldn’t help anyway. See `vignette` companions in `inst/benchmark/` for runtime and accuracy comparisons against `missForest`, `missRanger`, and `VIM::kNN`, including a scaling study from n = 1,000 to n = 100,000.

## Options at a glance

    missknn(
      data,
      k = 5L,                 # default neighbor count (tuned per column)
      m = 1L,                 # 1 = single imputation, >1 = multiple imputation
      scale = TRUE,            # standardize numeric variables before distances
      weights = "distance",    # "distance" (inverse-distance) or "uniform"
      numeric_estimator = "regression", # "regression" or "mean"
      donor_cap = 2000L,       # cap on the neighbor-search candidate pool
      add_indicator = FALSE,   # append missingness indicator columns
      seed = NULL              # reproducible multiple imputation
    )