---
title: "Getting started with missknn"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting started with missknn}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

## 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.

```{r setup}
library(missknn)
```

## Single imputation

```{r}
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"))
)

missknn(x, k = 2)
```

With `m = 1` (the default) `missknn()` returns the completed `data.frame` directly, with the
same columns and column types as the input.

`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.

## 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:

```{r}
imps <- missknn(x, k = 2, m = 5, seed = 1, parallel_cores = 1L)
class(imps)
length(imps)
```

The result is a list of `m` completed data frames (class `mimar_mi`). `missknn` is an imputer
only: fit the analysis model on each completed data set, then combine the fits with Rubin's
rules, for example with `mimar::pool_lm()`:

```{r, eval = FALSE}
fits <- lapply(imps, function(d) lm(a ~ b + g, data = d))
mimar::pool_lm(fits)
```

## 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.

The choices are returned in the `"tuning"` attribute of the result:

```{r}
attr(missknn(x, k = 2), "tuning")
```

## 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

```{r, eval = FALSE}
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
)
```
