Repository: sktime/sktime

Issue:
KNN Classifier: 'function' object has no attribute '__wrapped__'
**Describe the bug**

I am using the following the demo for [univariate time series classification](https://github.com/alan-turing-institute/sktime/blob/master/examples/02_classification_univariate.ipynb)
Calling the first example KNeighborsTimeSeriesClassifier gives the error:  'function' object has no attribute '__wrapped__'


**To Reproduce**


```python
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.tree import DecisionTreeClassifier
from sktime.classification.compose import TimeSeriesForestClassifier
from sktime.classification.distance_based import KNeighborsTimeSeriesClassifier
from sktime.datasets import load_gunpoint
from sktime.series_as_features.compose import FeatureUnion
from sktime.transformers.series_as_features.compose import RowTransformer
from sktime.transformers.series_as_features.reduce import Tabularizer
from sktime.transformers.series_as_features.segment import \
    RandomIntervalSegmenter
from sktime.utils.time_series import time_series_slope
from statsmodels.tsa.ar_model import AutoReg
from statsmodels.tsa.stattools import acf

X, y = load_gunpoint(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y)

knn = KNeighborsTimeSeriesClassifier(metric='dtw')
knn.fit(X_train, y_train)

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-40-5eb9acd6a688> in <module>
      1 knn = KNeighborsTimeSeriesClassifier(metric='dtw')
----> 2 knn.fit(X_train, y_train)

~/anaconda3/lib/python3.7/site-packages/sktime/classification/distance_based/_time_series_neighbors.py in fit(self, X, y)
    221             self._y = self._y.ravel()
    222 
--> 223         temp = check_array.__wrapped__.__code__
    224         check_array.__wrapped__.__code__ = _check_array_ts.__code__
    225         fx = self._fit(X)

AttributeError: 'function' object has no attribute '__wrapped__'

```

**Expected behavior**
I expect the code to run :-)




**Versions**


Linux-4.9.0-12-amd64-x86_64-with-debian-9.12
Python 3.7.3 (default, Mar 27 2019, 22:11:17) 
[GCC 7.3.0]
NumPy 1.18.1
SciPy 1.4.1
Pandas 0.25.3
sktime 0.4.0



<!-- Thanks for contributing! -->



Discussion:
Thanks for raising the issue! We'll look into this! 
I am interested to have a look, seems like it got something to do with the wrapper decorator.
@Quaterion may I know which version of Sci-Kit learn you are using?

I suspect that this error will occur if using sci-kit learn 0.22 or below. The decorator for check_array was added in 27th April 2020 so was released in 0.23.0rc1 and after
@Cheukting Thanks for offering to work on this! KNN needs some refactoring I believe, the current solution isn't very clean. But check if any changes have been made already in #287. I'd really appreciate a clean up of KNN! 
I can have a look, it's quite easy to solve this problem to make it compatible, just need to add check if this function is wrapped.
@mloning seems the problem code is still there in #287 
https://github.com/alan-turing-institute/sktime/blob/bd050d986e2c38c193fd30056060f33c7cdca2e3/sktime/classification/distance_based/_time_series_neighbors.py#L224
How can I contribute? Do you want me to make a PR? if so, to where?
Creating a PR into our dev branch would be great! 

Ideally, I'd prefer a solution in which we do not temporarily overwrite the `check_array` function which seems really hacky to me. But that might require more refactoring to make it work with sklearn's classes. 
> 
> 
> @Quaterion may I know which version of Sci-Kit learn you are using?

It is version '0.22.1'

> Creating a PR into our dev branch would be great!
> 
> Ideally, I'd prefer a solution in which we do not temporarily overwrite the `check_array` function which seems really hacky to me. But that might require more refactoring to make it work with sklearn's classes.

I see the whole issue begins with overriding sci-kit learn's check_array. Give me some time tomorrow and let me see if I can find a solution to that.

Resolving patch:
diff --git a/sktime/classification/distance_based/_time_series_neighbors.py b/sktime/classification/distance_based/_time_series_neighbors.py
--- a/sktime/classification/distance_based/_time_series_neighbors.py
+++ b/sktime/classification/distance_based/_time_series_neighbors.py
@@ -220,10 +220,19 @@ def fit(self, X, y):
             self.classes_ = self.classes_[0]
             self._y = self._y.ravel()
 
-        temp = check_array.__wrapped__.__code__
-        check_array.__wrapped__.__code__ = _check_array_ts.__code__
+        if hasattr(check_array, '__wrapped__'):
+            temp = check_array.__wrapped__.__code__
+            check_array.__wrapped__.__code__ = _check_array_ts.__code__
+        else:
+            temp = check_array.__code__
+            check_array.__code__ = _check_array_ts.__code__
+
         fx = self._fit(X)
-        check_array.__wrapped__.__code__ = temp
+
+        if hasattr(check_array, '__wrapped__'):
+            check_array.__wrapped__.__code__ = temp
+        else:
+            check_array.__code__ = temp
 
         self._is_fitted = True
         return fx
@@ -383,8 +392,14 @@ def predict(self, X):
             Class labels for each data sample.
         """
         self.check_is_fitted()
-        temp = check_array.__wrapped__.__code__
-        check_array.__wrapped__.__code__ = _check_array_ts.__code__
+
+        if hasattr(check_array, '__wrapped__'):
+            temp = check_array.__wrapped__.__code__
+            check_array.__wrapped__.__code__ = _check_array_ts.__code__
+        else:
+            temp = check_array.__code__
+            check_array.__code__ = _check_array_ts.__code__
+
         neigh_dist, neigh_ind = self.kneighbors(X)
         classes_ = self.classes_
         _y = self._y
@@ -409,7 +424,10 @@ def predict(self, X):
         if not self.outputs_2d_:
             y_pred = y_pred.ravel()
 
-        check_array.__wrapped__.__code__ = temp
+        if hasattr(check_array, '__wrapped__'):
+            check_array.__wrapped__.__code__ = temp
+        else:
+            check_array.__code__ = temp
         return y_pred
 
     def predict_proba(self, X):
@@ -431,8 +449,12 @@ def predict_proba(self, X):
         """
         self.check_is_fitted()
 
-        temp = check_array.__wrapped__.__code__
-        check_array.__wrapped__.__code__ = _check_array_ts.__code__
+        if hasattr(check_array, '__wrapped__'):
+            temp = check_array.__wrapped__.__code__
+            check_array.__wrapped__.__code__ = _check_array_ts.__code__
+        else:
+            temp = check_array.__code__
+            check_array.__code__ = _check_array_ts.__code__
 
         X = check_array(X, accept_sparse='csr')
 
@@ -470,16 +492,15 @@ def predict_proba(self, X):
         if not self.outputs_2d_:
             probabilities = probabilities[0]
 
-        check_array.__wrapped__.__code__ = temp
+        if hasattr(check_array, '__wrapped__'):
+            check_array.__wrapped__.__code__ = temp
+        else:
+            check_array.__code__ = temp
         return probabilities
 
 
 # overwrite sklearn internal checks, this is really hacky
 # we now need to replace: check_array.__wrapped__.__code__ since it's
 # wrapped by a future warning decorator
-def _check_array_ts(array, accept_sparse=False, *, accept_large_sparse=True,
-                    dtype="numeric", order=None, copy=False,
-                    force_all_finite=True,
-                    ensure_2d=True, allow_nd=False, ensure_min_samples=1,
-                    ensure_min_features=1, estimator=None):
+def _check_array_ts(array, *args, **kwargs):
     return array


Test patch: