KNeighborsClassifier: allow p value for Minkowski calculation to be less than 1.0 (and greater than 0)

### Describe the workflow you want to enable

I would like to be able to use the KNeighborsClassifier with something like:

`neigh = KNeighborsClassifier(n_neighbors=2, p=0.1)`

The error you get is:

```
File ~/opt/anaconda3/envs/tensorflow-3/lib/python3.10/site-packages/sklearn/neighbors/_base.py:395, in NeighborsBase._check_algorithm_metric(self)
    392     effective_p = self.p
    394 if self.metric in ["wminkowski", "minkowski"] and effective_p < 1:
--> 395     raise ValueError("p must be greater or equal to one for minkowski metric")

ValueError: p must be greater or equal to one for minkowski metric
```

Change the above line to limit minkowski p value to < 0:

`if self.metric in ["wminkowski", "minkowski"] and effective_p < 0:`

There is nothing wrong with using a p value > 0 and < 1.

### Describe your proposed solution

Don't throw an exception if p is < 1.0.

Calculating Minkowski distance is most definitely valid for p > 0 and p < 1.  I.e.: 0.1, 0.2, 0.3, 0.4, etc

The only requirement is compute for each dimension raised to the p power. In this case **p and after the sum of all dimensions. Take the p-th root of the sum of dimensions:  `distance = distance_sum**p`

There are many cases where it is desirable to compute minkowski > 0 and < 1

### Describe alternatives you've considered, if relevant

Write my own kNN classifier with my own minkowski distance calculator:

```
def minkowski_distance(row1, row2, p=3):
    distance = 0.0
    for i in range(len(row1)-1):
        distance += abs(row1[i] - row2[i])**p
    return distance**(1/p)
```

### Additional context

_No response_

COMMENT:
Yes, I agree it's not a metric. My advisor who spent his entire life studying distance (Euclidean and non-Eucliean) would say that Minkowski < 1 has much value. I have a problem where I need to iterate over different values of p for Minkowski but  there is a way around this issue (for p < 1). I can use a callable for my own Minkowski method and pass a parameter like this.

Here "minkowski_distance" is my method...

```
mink_p = 0.5
step = 0.1

while mink_p <= 2.5:
    
    neigh = KNeighborsClassifier(n_neighbors=1, metric=minkowski_distance, metric_params={"minkowski_param" : mink_p})
    neigh.fit(X_ref_normalized, y_ref)

    ...

    mink_p += step
    mink_p = round(mink_p, 1)
```

This will solve my original problem and for anyone else that wants p < 0 for Minkowski.

I have another very closely related issue to KNeightborsClassifier - if it needs a new issue I can open it.

What about adding a "weights_params" option? Just like in the above code there is "metric" and "metric_params" but for weights it's just weights (for a callable) - but no weights_params.

I have a case where I need to iterate over the value of p for the weights method and I have do something like this:

```
for w in [weights_p_5, weights_p1, weights_p1_5, weights_p2, weights_p2_5]:
    
    neigh = KNeighborsClassifier(n_neighbors=k, weights=w)
    neigh.fit(X_ref_normalized, y_ref)
    ...
```

In this example `weights_p_5` is a method that computes the weight as 1/(dist**.5), `weights_p1_5` computes weights as 1/(dist**1.5).

How about adding a `weights_params` option just like there is for metric (both callable and a dictionary parameter to pass.)

How about including something like this new param "weight_param":

```
weight_p = 0.5
step = 0.5

while weight_p <= 2.5:
    
    neigh = KNeighborsClassifier(n_neighbors=1, weights=my_weight_method, weights_params={"weight_param" : weight_p})
    neigh.fit(X_ref_normalized, y_ref)

    ...

    weight_p += step
    weight_p = round(weight_p, 1)
```

Thanks -Ray

Labels: New Feature, module:neighbors, Needs Info