Repository: sktime/sktime

Issue:
[BUG] ShapeletTransformClassifier doesn't work with y as pd.Series
**Describe the bug**
 ShapeletTransformClassifier doesn't work with y as pd.Series

**To Reproduce**
This breaks: 
```python
from sktime.classification.shapelet_based import ShapeletTransformClassifier
from sktime.datasets import load_arrow_head
from sklearn.model_selection import train_test_split

X, y = load_arrow_head(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y)

c = ShapeletTransformClassifier()
c.fit(X_train, y_train)
```

**Expected behavior**
Works with pd.Series or np.array

**Additional context**
This works: 

```python
from sktime.classification.shapelet_based import ShapeletTransformClassifier
from sktime.datasets import load_arrow_head
from sklearn.model_selection import train_test_split

X, y = load_arrow_head(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y)

c = ShapeletTransformClassifier()
c.fit(X_train, y_train.to_numpy())
```



Discussion:
sorry, forgot about this, I'm in Java land. I suggest we just internally check if its passed y as a pd.Series and if so, pull it out as an array? Should be a quick fix? 
@ABostrom want to do this?
I'll do it now. should take me 10mins. (he says confidently)

Adding `y_train = y_train.to_numpy()` inside fit should probably do it 

Resolving patch:
diff --git a/sktime/classification/base.py b/sktime/classification/base.py
--- a/sktime/classification/base.py
+++ b/sktime/classification/base.py
@@ -33,6 +33,7 @@ def predict(self, X):
             array of predictions of each instance (class value)
         """
         X = check_X(X)
+        self.check_is_fitted()
         distributions = self.predict_proba(X)
         predictions = []
         for instance_index in range(0, X.shape[0]):
diff --git a/sktime/classification/shapelet_based/_stc.py b/sktime/classification/shapelet_based/_stc.py
--- a/sktime/classification/shapelet_based/_stc.py
+++ b/sktime/classification/shapelet_based/_stc.py
@@ -10,14 +10,14 @@
 __all__ = ["ShapeletTransformClassifier"]
 
 import numpy as np
+import pandas as pd
 from sklearn.ensemble import RandomForestClassifier
 from sklearn.pipeline import Pipeline
 from sklearn.utils.multiclass import class_distribution
 from sktime.classification.base import BaseClassifier
 from sktime.transformers.series_as_features.shapelets import \
     ContractedShapeletTransform
-from sktime.utils.validation.series_as_features import check_X
-from sktime.utils.validation.series_as_features import check_X_y
+from sktime.utils.validation.series_as_features import check_X, check_X_y
 
 
 class ShapeletTransformClassifier(BaseClassifier):
@@ -49,17 +49,12 @@ class ShapeletTransformClassifier(BaseClassifier):
     def __init__(
             self,
             time_contract_in_mins=300,
-            n_estimators=500
+            n_estimators=500,
+            random_state=None
     ):
         self.time_contract_in_mins = time_contract_in_mins
         self.n_estimators = n_estimators
-
-        self.classifier = Pipeline([
-            ('st', ContractedShapeletTransform(
-                time_contract_in_mins=time_contract_in_mins,
-                verbose=False)),
-            ('rf', RandomForestClassifier(n_estimators=n_estimators))
-        ])
+        self.random_state = random_state
 
         #        self.shapelet_transform=ContractedShapeletTransform(
         #        time_limit_in_mins=self.time_contract_in_mins, verbose=shouty)
@@ -85,18 +80,26 @@ def fit(self, X, y):
         self : object
          """
         X, y = check_X_y(X, y, enforce_univariate=True)
-        self.n_classes = np.unique(y).shape[0]
+
+        # if y is a pd.series then convert to array.
+        if isinstance(y, pd.Series):
+            y = y.to_numpy()
+
+# generate pipeline in fit so that random state can be propogated properly.
+        self.classifier_ = Pipeline([
+            ('st', ContractedShapeletTransform(
+                time_contract_in_mins=self.time_contract_in_mins,
+                verbose=False,
+                random_state=self.random_state)),
+            ('rf', RandomForestClassifier(n_estimators=self.n_estimators,
+                                          random_state=self.random_state))
+        ])
+
+        self.n_classes_ = np.unique(y).shape[0]
         self.classes_ = class_distribution(np.asarray(y).reshape(-1, 1))[0][0]
 
-        self.classifier.fit(X, y)
+        self.classifier_.fit(X, y)
 
-        #        self.shapelet_transform.fit(X,y)
-        #        print("Shapelet Search complete")
-        #        self.st_X =self.shapelet_transform.transform(X)
-        #        print("Transform complete")
-        #        X = np.asarray([a.values for a in X.iloc[:, 0]])
-        #        self.classifier.fit(X,y)
-        #       print("Build classifier complete")
         self._is_fitted = True
         return self
 
@@ -113,8 +116,10 @@ def predict(self, X):
         -------
         output : array of shape = [n_samples]
         """
-        probs = self.predict_proba(X)
-        return np.array([self.classes_[np.argmax(prob)] for prob in probs])
+        X = check_X(X, enforce_univariate=True)
+        self.check_is_fitted()
+
+        return self.classifier_.predict(X)
 
     def predict_proba(self, X):
         """
@@ -128,6 +133,7 @@ def predict_proba(self, X):
         -------
         output : array of shape = [n_samples, num_classes] of probabilities
         """
-        self.check_is_fitted()
         X = check_X(X, enforce_univariate=True)
-        return self.classifier.predict_proba(X)
+        self.check_is_fitted()
+
+        return self.classifier_.predict_proba(X)
diff --git a/sktime/transformers/series_as_features/shapelets.py b/sktime/transformers/series_as_features/shapelets.py
--- a/sktime/transformers/series_as_features/shapelets.py
+++ b/sktime/transformers/series_as_features/shapelets.py
@@ -20,7 +20,7 @@
 from sklearn.utils.multiclass import class_distribution
 from sktime.transformers.series_as_features.base import \
     BaseSeriesAsFeaturesTransformer
-from sktime.utils.validation.series_as_features import check_X
+from sktime.utils.validation.series_as_features import check_X, check_X_y
 
 warnings.filterwarnings("ignore", category=FutureWarning)
 
@@ -118,7 +118,11 @@ def fit(self, X, y=None):
         self : FullShapeletTransform
             This estimator
         """
-        X = check_X(X, enforce_univariate=True)
+        X, y = check_X_y(X, y, enforce_univariate=True)
+
+        # if y is a pd.series then convert to array.
+        if isinstance(y, pd.Series):
+            y = y.to_numpy()
 
         if type(
                 self) is ContractedShapeletTransform and \
@@ -149,7 +153,7 @@ def fit(self, X, y=None):
         shapelet_heaps_by_class = {i: ShapeletPQ() for i in
                                    distinct_class_vals}
 
-        self.random_state = check_random_state(self.random_state)
+        self._random_state = check_random_state(self.random_state)
 
         # Here we establish the order of cases to sample. We need to sample
         # x cases and y shapelets from each (where x = num_cases_to_sample
@@ -183,7 +187,7 @@ def _round_robin(*iterables):
         if type(self) is _RandomEnumerationShapeletTransform or type(
                 self) is ContractedShapeletTransform:
             for i in range(len(distinct_class_vals)):
-                self.random_state.shuffle(
+                self._random_state.shuffle(
                     case_ids_by_class[distinct_class_vals[i]])
 
         num_train_per_class = {i: len(case_ids_by_class[i]) for i in
@@ -284,7 +288,7 @@ def time_taken():
                 num_candidates_per_case = min(
                     self.num_candidates_to_sample_per_case,
                     num_candidates_per_case)
-                cand_idx = list(self.random_state.choice(
+                cand_idx = list(self._random_state.choice(
                     list(range(0, len(candidate_starts_and_lens))),
                     num_candidates_per_case, replace=False))
                 candidates_to_visit = [candidate_starts_and_lens[x] for x in
@@ -308,7 +312,8 @@ def time_taken():
                 cand_len = candidates_to_visit[candidate_idx][1]
 
                 candidate = ShapeletTransform.zscore(
-                    X[series_id][:, cand_start_pos: cand_start_pos + cand_len])
+                    X[series_id][:, cand_start_pos: cand_start_pos + cand_len]
+                    )
 
                 # now go through all other series and get a distance from
                 # the candidate to each
@@ -434,10 +439,16 @@ def time_taken():
                     time_this_shapelet = (time_now - time_last_shapelet)
                     if time_this_shapelet > max_time_calc_shapelet:
                         max_time_calc_shapelet = time_this_shapelet
+                        print(max_time_calc_shapelet)
                     time_last_shapelet = time_now
-                    if (
-                            time_now + max_time_calc_shapelet) > \
-                            self.time_contract_in_mins * 60:
+
+# add a little 1% leeway to the timing incase one run was slightly faster than
+# another based on the CPU.
+                    time_in_seconds = self.time_contract_in_mins * 60
+                    max_shapelet_time_percentage = (
+                        max_time_calc_shapelet / 100.0) * 0.75
+                    if (time_now + max_shapelet_time_percentage) > \
+                            time_in_seconds:
                         if self.verbose > 0:
                             print(
                                 "No more time available! It's been {0:02d}:{"
@@ -605,7 +616,7 @@ def transform(self, X, y=None, **transform_params):
             The transformed dataframe in tabular format.
         """
         self.check_is_fitted()
-        X = check_X(X, enforce_univariate=True)
+        _X = check_X(X, enforce_univariate=True)
 
         if len(self.shapelets) == 0:
             raise RuntimeError(
@@ -613,17 +624,16 @@ def transform(self, X, y=None, **transform_params):
                 "minimum information gain threshold. Please retry with other "
                 "data and/or parameter settings.")
 
-        X = np.array(
-            [[X.iloc[r, c].values for c in range(len(X.columns))] for r in
-             range(len(
-                 X))])  # may need to pad with nans here for uneq length,
-        # look at later
+        _X = np.array(
+            [[_X.iloc[r, c].values for c in range(len(_X.columns))]
+             for r in range(len(_X))])
+# may need to pad with nans here for uneq length, look at later
 
-        output = np.zeros([len(X), len(self.shapelets)], dtype=np.float32, )
+        output = np.zeros([len(_X), len(self.shapelets)], dtype=np.float32, )
 
         # for the i^th series to transform
-        for i in range(0, len(X)):
-            this_series = X[i]
+        for i in range(0, len(_X)):
+            this_series = _X[i]
 
             # get the s^th shapelet
             for s in range(0, len(self.shapelets)):


Test patch:
diff --git a/sktime/series_as_features/tests/test_all_series_as_features_estimators.py b/sktime/series_as_features/tests/test_all_series_as_features_estimators.py
--- a/sktime/series_as_features/tests/test_all_series_as_features_estimators.py
+++ b/sktime/series_as_features/tests/test_all_series_as_features_estimators.py
@@ -8,18 +8,18 @@
 import numpy as np
 import pandas as pd
 import pytest
-from sktime.tests._config import EXCLUDED
+from sktime.tests._config import EXCLUDED_ESTIMATORS
 from sktime.utils import all_estimators
 from sktime.utils._testing import _construct_instance
 from sktime.utils._testing import _make_args
 
 ALL_CLASSIFIERS = [e[1] for e in
                    all_estimators(estimator_type="classifier")
-                   if e[0] not in EXCLUDED]
+                   if e[0] not in EXCLUDED_ESTIMATORS]
 
 ALL_REGRESSORS = [e[1] for e in
                   all_estimators(estimator_type="regressor")
-                  if e[0] not in EXCLUDED]
+                  if e[0] not in EXCLUDED_ESTIMATORS]
 
 N_CLASSES = 3
 ACCEPTED_OUTPUT_TYPES = (np.ndarray, pd.Series)
diff --git a/sktime/tests/_config.py b/sktime/tests/_config.py
--- a/sktime/tests/_config.py
+++ b/sktime/tests/_config.py
@@ -5,7 +5,8 @@
 __author__ = ["Markus Löning"]
 __all__ = [
     "ESTIMATOR_TEST_PARAMS",
-    "EXCLUDED"
+    "EXCLUDED_ESTIMATORS",
+    "EXCLUDED_TESTS"
 ]
 
 from sklearn.linear_model import LinearRegression
@@ -16,49 +17,44 @@
 from sktime.classification.interval_based import TimeSeriesForest
 from sktime.classification.shapelet_based import ShapeletTransformClassifier
 from sktime.forecasting.arima import AutoARIMA
-from sktime.forecasting.compose import DirectRegressionForecaster
-from sktime.forecasting.compose import DirectTimeSeriesRegressionForecaster
-from sktime.forecasting.compose import EnsembleForecaster
-from sktime.forecasting.compose import RecursiveRegressionForecaster
-from sktime.forecasting.compose import RecursiveTimeSeriesRegressionForecaster
-from sktime.forecasting.compose import StackingForecaster
-from sktime.forecasting.compose import TransformedTargetForecaster
+from sktime.forecasting.compose import (
+    DirectRegressionForecaster, DirectTimeSeriesRegressionForecaster,
+    EnsembleForecaster, RecursiveRegressionForecaster,
+    RecursiveTimeSeriesRegressionForecaster, StackingForecaster,
+    TransformedTargetForecaster)
 from sktime.forecasting.exp_smoothing import ExponentialSmoothing
-from sktime.forecasting.model_selection import ForecastingGridSearchCV
-from sktime.forecasting.model_selection import SingleWindowSplitter
+from sktime.forecasting.model_selection import (ForecastingGridSearchCV,
+                                                SingleWindowSplitter)
 from sktime.forecasting.naive import NaiveForecaster
 from sktime.forecasting.theta import ThetaForecaster
 from sktime.performance_metrics.forecasting import sMAPE
-from sktime.transformers.series_as_features.compose import ColumnTransformer
-from sktime.transformers.series_as_features.compose import RowTransformer
+from sktime.transformers.series_as_features.compose import (ColumnTransformer,
+                                                            RowTransformer)
+from sktime.transformers.series_as_features.interpolate import TSInterpolator
 from sktime.transformers.series_as_features.reduce import Tabularizer
-from sktime.transformers.series_as_features.shapelets import \
-    ContractedShapeletTransform
-from sktime.transformers.series_as_features.shapelets import ShapeletTransform
-from sktime.transformers.series_as_features.summarize import \
-    FittedParamExtractor
-from sktime.transformers.series_as_features.summarize import \
-    TSFreshFeatureExtractor
-from sktime.transformers.series_as_features.summarize import \
-    TSFreshRelevantFeatureExtractor
+from sktime.transformers.series_as_features.shapelets import (
+    ContractedShapeletTransform, ShapeletTransform)
+from sktime.transformers.series_as_features.summarize import (
+    FittedParamExtractor, TSFreshFeatureExtractor,
+    TSFreshRelevantFeatureExtractor)
 from sktime.transformers.single_series.adapt import \
     SingleSeriesTransformAdaptor
 from sktime.transformers.single_series.detrend import Detrender
-from sktime.transformers.series_as_features.interpolate import TSInterpolator
 
 # TODO fix estimators to pass all tests
-EXCLUDED = [
-    'ContractedShapeletTransform',
+EXCLUDED_ESTIMATORS = [
     'ElasticEnsemble',
     'KNeighborsTimeSeriesClassifier',
-    # 'MrSEQLClassifier',
     'ProximityForest',
     'ProximityStump',
     'ProximityTree',
-    'ShapeletTransform',
-    'ShapeletTransformClassifier',
 ]
 
+EXCLUDED_TESTS = {
+    "ShapeletTransformClassifier": ["check_fit_idempotent"],
+    "ContractedShapeletTransform": ["check_fit_idempotent"],
+}
+
 TRANSFORMER = StandardScaler()
 TRANSFORMERS = [
     ("t1", RowTransformer(TRANSFORMER)),
@@ -117,11 +113,13 @@
     AutoARIMA:
         {"d": 0, "suppress_warnings": True},
     ShapeletTransformClassifier:
-        {"time_contract_in_mins": 0.1},
+        {"time_contract_in_mins": 0.125},
     ContractedShapeletTransform:
-        {"time_contract_in_mins": 0.1},
+        {"time_contract_in_mins": 0.125},
     ShapeletTransform:
-        {"max_shapelets_to_store_per_class": 1},
+        {"max_shapelets_to_store_per_class": 1,
+         "min_shapelet_length": 3,
+         "max_shapelet_length": 4},
     TSFreshFeatureExtractor:
         {"disable_progressbar": True, "show_warnings": False},
     TSFreshRelevantFeatureExtractor:
diff --git a/sktime/tests/test_all_estimators.py b/sktime/tests/test_all_estimators.py
--- a/sktime/tests/test_all_estimators.py
+++ b/sktime/tests/test_all_estimators.py
@@ -8,16 +8,17 @@
 ]
 
 import pytest
-from sktime.tests._config import EXCLUDED
+from sktime.tests._config import EXCLUDED_ESTIMATORS
 from sktime.utils import all_estimators
 from sktime.utils._testing.estimator_checks import check_estimator
+from sktime.tests._config import EXCLUDED_TESTS
 
 ALL_ESTIMATORS = [e[1] for e in all_estimators() if
-                  e[0] not in EXCLUDED]
+                  e[0] not in EXCLUDED_ESTIMATORS]
 
 
 @pytest.mark.parametrize("Estimator", ALL_ESTIMATORS)
 def test_estimator(Estimator):
     # We run a number of basic checks on all estimators to ensure correct
     # implementation of our framework and compatibility with scikit-learn
-    check_estimator(Estimator)
+    check_estimator(Estimator, EXCLUDED_TESTS.get(Estimator.__name__, []))
diff --git a/sktime/transformers/series_as_features/tests/test_all_series_as_features_transformers.py b/sktime/transformers/series_as_features/tests/test_all_series_as_features_transformers.py
--- a/sktime/transformers/series_as_features/tests/test_all_series_as_features_transformers.py
+++ b/sktime/transformers/series_as_features/tests/test_all_series_as_features_transformers.py
@@ -7,14 +7,14 @@
 
 import numpy as np
 import pytest
-from sktime.tests.test_all_estimators import EXCLUDED
+from sktime.tests.test_all_estimators import EXCLUDED_ESTIMATORS
 from sktime.utils import all_estimators
 from sktime.utils._testing import _construct_instance
 from sktime.utils._testing import _make_args
 
 ALL_SERIES_AS_FEATURES_TRANSFORMERS = [
     e[1] for e in all_estimators("series_as_features_transformer")
-    if e[0] not in EXCLUDED
+    if e[0] not in EXCLUDED_ESTIMATORS
 ]
 
 
diff --git a/sktime/utils/_testing/estimator_checks.py b/sktime/utils/_testing/estimator_checks.py
--- a/sktime/utils/_testing/estimator_checks.py
+++ b/sktime/utils/_testing/estimator_checks.py
@@ -53,7 +53,7 @@
 ]
 
 
-def check_estimator(Estimator):
+def check_estimator(Estimator, exclude=None):
     """Check whether estimator complies with common interface.
 
     Parameters
@@ -66,7 +66,10 @@ def check_estimator(Estimator):
         If Estimator does not comply
     """
     for check in yield_estimator_checks():
-        check(Estimator)
+
+        # check if associated test is not included in the exclusion list
+        if check.__name__ not in exclude:
+            check(Estimator)
 
 
 def yield_estimator_checks():