Improve documentation to specify the interface of metric as a callable in KNNImputer

## Repurpose issue to solve

In `KNNImputer`, there is no mention regarding the expected interface of the parameter `metric` apart from the signature when passing a callable. We could reuse the documentation of `NearestNeighors` that provides more details that are necessary to implement the function.

<details>

## Original issue

### Describe the bug

I am trying to run KNNImputer with a custom `nan_manhattan_distances` function from pull request [#23286](https://github.com/scikit-learn/scikit-learn/pull/23286). 

### Steps/Code to Reproduce

```python
import math
import numbers

from sklearn.metrics.pairwise import check_pairwise_arrays
from sklearn.metrics import DistanceMetric
from sklearn.utils._mask import _get_mask
from sklearn.impute import KNNImputer
# from sklearn.utils._missing import is_scalar_nan

import numpy as np

def is_scalar_nan(x):
    return (
        not isinstance(x, numbers.Integral)
        and isinstance(x, numbers.Real)
        and math.isnan(x)
    )


# Copied from a pull request to scikit-learn for a nan_manhattan_distance function for KNNImputer.
# Credit goes to MaxwellLZH
def nan_manhattan_distances(X, Y=None, *, missing_values=np.nan, copy=True):
    """Calculate the manhattan distances in the presence of missing values.
    Compute the manhattan distance between each pair of samples in X and Y,
    where Y=X is assumed if Y=None. When calculating the distance between a
    pair of samples, this formulation ignores feature coordinates with a
    missing value in either sample and scales up the weight of the remaining
    coordinates:
        dist(x,y) = weight * distance from present coordinates
        where,
        weight = Total # of coordinates / # of present coordinates
    For example, the distance between ``[3, na, na, 6]`` and ``[1, na, 4, 5]``
    is:
        .. math::
            \\frac{4}{2}(\\abs{3-1} + \\abs{6-5})
    If all the coordinates are missing or if there are no common present
    coordinates then NaN is returned for that pair.
    Read more in the :ref:`User Guide <metrics>`.
    .. versionadded:: 1.2
    Parameters
    ----------
    X : array-like of shape (n_samples_X, n_features)
        An array where each row is a sample and each column is a feature.
    Y : array-like of shape (n_samples_Y, n_features), default=None
        An array where each row is a sample and each column is a feature.
        If `None`, method uses `Y=X`.
    missing_values : np.nan or int, default=np.nan
        Representation of missing value.
    copy : bool, default=True
        Make and use a deep copy of X and Y (if Y exists).
    Returns
    -------
    distances : ndarray of shape (n_samples_X, n_samples_Y)
        Returns the distances between the row vectors of `X`
        and the row vectors of `Y`.
    See Also
    --------
    paired_distances : Distances between pairs of elements of X and Y.
    Examples
    --------
    >>> from sklearn.metrics.pairwise import nan_manhattan_distances
    >>> nan = float("NaN")
    >>> X = [[0, 1], [1, nan]]
    >>> nan_manhattan_distances(X, X) # distance between rows of X
    array([[0., 2.],
           [2., 0.]])
    >>> # get distance to origin
    >>> nan_manhattan_distances(X, [[0, 0]])
    array([[1.],
           [2.]])
    """

    force_all_finite = "allow-nan" if is_scalar_nan(missing_values) else True
    X, Y = check_pairwise_arrays(
        X, Y, accept_sparse=False, force_all_finite=force_all_finite, copy=copy
    )

    # Get missing mask
    missing_X = _get_mask(X, missing_values)
    missing_Y = missing_X if Y is X else _get_mask(Y, missing_values)

    # set missing values to zero
    X[missing_X] = 0
    Y[missing_Y] = 0

    distances = DistanceMetric.get_metric("manhattan").pairwise(X, Y)

    # Adjust distances for missing values
    distances -= np.dot(X, missing_Y.T)
    distances -= np.dot(missing_X, Y.T)

    np.clip(distances, 0, None, out=distances)

    if X is Y:
        # Ensure that distances between vectors and themselves are set to 0.0.
        # This may not be the case due to floating point rounding errors.
        np.fill_diagonal(distances, 0.0)

    present_count = np.dot(1 - missing_X, (1 - missing_Y).T)
    distances /= np.maximum(1, present_count)
    distances *= X.shape[1]
    distances[present_count == 0] = np.nan
    return distances

# Generate a random encoded 2-D array whereby 0 is the missing value
X = np.random.randint(100, size=(20, 56))
_X = np.random.randint(2, size=(20,56))
X = X*_X

imputer = KNNImputer(n_neighbors=1, weights='distance', metric=nan_manhattan_distances, missing_values=0)
imputed = imputer.fit_transform(X)
```

I have checked that the `nan_manhattan_distances` function works:
```
nan_manhattan_distances(X, missing_values=0).astype(int)
array([[   0, 1167, 2088, 1185, 1892, 1452, 1807, 1862, 2034, 1829, 1838,
        1255, 2208,  984, 1660, 1568, 1680, 1698, 1827, 1493],
       [1167,    0, 1619, 1624, 2089, 2024, 1512, 2213, 1936, 1984, 2269,
        1572, 2382,  995, 1851, 2108, 1658, 1481, 1832, 1837],
       [2088, 1619,    0, 1533, 1859, 1994, 1502, 1745, 2140, 1563, 1533,
        1756, 1908, 2149, 2171, 1843, 1843, 2412, 1344, 1764],
       [1185, 1624, 1533,    0, 1769, 1662, 1990, 2632, 1298, 1796, 1696,
        1184, 1897, 1196, 1589, 2140, 1445, 2523, 1320, 1408],
       [1892, 2089, 1859, 1769,    0, 1493, 1650, 1370, 2320, 1470, 2001,
        1809, 1400, 1857, 1818, 1520, 2050, 1948, 2107, 2217],
       [1452, 2024, 1994, 1662, 1493,    0, 1837, 1839, 1940, 1680, 1738,
        1898, 1708, 1376, 1298, 1990, 2457, 1367, 1328, 2325],
       [1807, 1512, 1502, 1990, 1650, 1837,    0, 1379, 1972, 1607, 1549,
        1969, 2692, 1269, 1360, 1408, 2021,  936, 1908, 1086],
       [1862, 2213, 1745, 2632, 1370, 1839, 1379,    0, 1360, 1818, 1417,
        1988, 2123, 1781, 1512, 1489, 1605, 1684, 2316, 1717],
       [2034, 1936, 2140, 1298, 2320, 1940, 1972, 1360,    0, 1824, 1644,
        2054, 1696, 1804, 1533, 1916, 1877, 1773, 1878, 1913],
       [1829, 1984, 1563, 1796, 1470, 1680, 1607, 1818, 1824,    0, 1851,
        1867, 1879, 2152, 2131, 1650, 1934, 1613, 2169, 1636],
       [1838, 2269, 1533, 1696, 2001, 1738, 1549, 1417, 1644, 1851,    0,
        1683, 2075, 2184, 1176, 1548, 1784, 2032, 1400, 2024],
       [1255, 1572, 1756, 1184, 1809, 1898, 1969, 1988, 2054, 1867, 1683,
           0, 1988, 1176, 1558, 1972, 1750, 2178, 2001, 1607],
       [2208, 2382, 1908, 1897, 1400, 1708, 2692, 2123, 1696, 1879, 2075,
...
        2178, 1934, 1536, 2199, 1776, 1854,    0, 1533, 2032],
       [1827, 1832, 1344, 1320, 2107, 1328, 1908, 2316, 1878, 2169, 1400,
        2001, 2362, 1663, 1863, 2038, 1232, 1533,    0, 2408],
       [1493, 1837, 1764, 1408, 2217, 2325, 1086, 1717, 1913, 1636, 2024,
        1607, 2593, 1524, 1880, 1499, 1913, 2032, 2408,    0]])
```
And that KNNImputer works with the defaults:
```
imputer = KNNImputer(n_neighbors=1, weights='distance', missing_values=0)
imputed = imputer.fit_transform(X)
imputed
array([[26., 45., 48., ..., 59., 57., 43.],
       [71., 37., 40., ..., 10., 33., 13.],
       [71., 94., 69., ..., 41.,  6., 44.],
       ...,
       [15., 94., 49., ...,  6.,  7., 49.],
       [63.,  7., 91., ..., 41., 83., 44.],
       [26., 94., 31., ..., 93.,  7., 99.]])
```

### Expected Results

A 2-D array imputed matrix
```pytb
array([[26., 45., 48., ..., 59., 57., 43.],
       [71., 37., 40., ..., 10., 33., 13.],
       [71., 94., 69., ..., 41.,  6., 44.],
       ...,
       [15., 94., 49., ...,  6.,  7., 49.],
       [63.,  7., 91., ..., 41., 83., 44.],
       [26., 94., 31., ..., 93.,  7., 99.]])
```

### Actual Results

```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-22-d253e3ef84c4> in <module>
      1 imputer = KNNImputer(n_neighbors=1, weights='distance', metric=nan_manhattan_distances, missing_values=0)
----> 2 imputed = imputer.fit_transform(X)

c:\Users\carterc\AppData\Local\Programs\Python\Python38-32\lib\site-packages\sklearn\utils\_set_output.py in wrapped(self, X, *args, **kwargs)
    138     @wraps(f)
    139     def wrapped(self, X, *args, **kwargs):
--> 140         data_to_wrap = f(self, X, *args, **kwargs)
    141         if isinstance(data_to_wrap, tuple):
    142             # only wrap the first output for cross decomposition

c:\Users\carterc\AppData\Local\Programs\Python\Python38-32\lib\site-packages\sklearn\base.py in fit_transform(self, X, y, **fit_params)
    876         if y is None:
    877             # fit method of arity 1 (unsupervised transformation)
--> 878             return self.fit(X, **fit_params).transform(X)
    879         else:
    880             # fit method of arity 2 (supervised transformation)

c:\Users\carterc\AppData\Local\Programs\Python\Python38-32\lib\site-packages\sklearn\utils\_set_output.py in wrapped(self, X, *args, **kwargs)
    138     @wraps(f)
    139     def wrapped(self, X, *args, **kwargs):
--> 140         data_to_wrap = f(self, X, *args, **kwargs)
    141         if isinstance(data_to_wrap, tuple):
...
array=[ 0. 45. 48.  0.  0.  0.  0.  0.  0.  0.  0. 96.  0. 47.  0.  0.  0.  0.
  0.  0. 39. 61. 57.  0.  0. 93. 35.  0. 10.  0.  0. 61.  0.  6. 55. 79.
  0. 39.  9. 25. 51.  0. 19.  6.  0.  0.  0. 28.  0.  0. 38.  0.  0. 59.
  0. 43.].
Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.
```

### Versions

```shell
System:
    python: 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 22:45:29) [MSC v.1916 32 bit (Intel)]
executable: c:\Users\carterc\AppData\Local\Programs\Python\Python38-32\python.exe
   machine: Windows-10-10.0.22621-SP0

Python dependencies:
      sklearn: 1.2.2
          pip: 23.3.2
   setuptools: 41.2.0
        numpy: 1.18.3
        scipy: 1.4.1
       Cython: 0.29.35
       pandas: 1.1.3
   matplotlib: 3.2.1
       joblib: 1.4.2
threadpoolctl: 3.1.0

Built with OpenMP: True

threadpoolctl info:
       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\Users\carterc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\numpy\.libs\libopenblas.SVHFG5YE3RK3Z27NVFUDAPL2O3W6IMXW.gfortran-win32.dll
        version: 0.3.7
threading_layer: pthreads
   architecture: Atom
    num_threads: 8

       user_api: blas
   internal_api: openblas
         prefix: libopenblas
       filepath: C:\Users\carterc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\scipy\.libs\libopenblas.SVHFG5YE3RK3Z27NVFUDAPL2O3W6IMXW.gfortran-win32.dll
        version: 0.3.7
threading_layer: pthreads
   architecture: Atom
    num_threads: 8

       user_api: openmp
   internal_api: openmp
         prefix: vcomp
       filepath: C:\Windows\SysWOW64\vcomp140.dll
        version: None
    num_threads: 8
```

</details>

COMMENT:
The error is raised within your user-defined function:

```python
force_all_finite = "allow-nan" if is_scalar_nan(missing_values) else True
    X, Y = check_pairwise_arrays(
        X, Y, accept_sparse=False, force_all_finite=force_all_finite, copy=copy
    )
```

So it seems that this function does not follow the expected API. It looks that the explanation in the documentation is not enough. Actually, the one in `NearestNeighbors` is actually better:

```
If metric is a callable function, it takes two arrays representing 1D vectors as inputs
and must return one value indicating the distance between those vectors.
```

So the function implemented here does not seem to be accepting 1-D. I think that we could improve our documentation regarding the expectation of the inputs

Labels: Documentation