{"action":"created","comment":{"url":"https://api.github.com/repos/X-DataInitiative/tick/pulls/comments/167440854","pull_request_review_id":95661526,"id":167440854,"diff_hunk":"@@ -0,0 +1,922 @@\n+import numpy as np\n+from abc import ABC\n+from operator import itemgetter\n+from collections import namedtuple\n+from sklearn.model_selection import StratifiedKFold\n+from tick.base import Base\n+from tick.prox import ProxZero, ProxMulti, ProxTV, ProxEquality, \\\n+    ProxGroupL1\n+from tick.solver import SVRG\n+from tick.survival import SimuSCCS, ModelSCCS\n+from tick.preprocessing import LongitudinalSamplesFilter, \\\n+    LongitudinalFeaturesLagger\n+from scipy.stats import uniform\n+import matplotlib.pylab as plt\n+from mpl_toolkits.mplot3d import Axes3D\n+from matplotlib import cm\n+from tick.preprocessing.utils import check_longitudinal_features_consistency, \\\n+    check_censoring_consistency\n+\n+# Case classes\n+Bootstrap_CI = namedtuple('Bootstrap_CI', ['refit_coeffs', 'median',\n+                                           'lower_bound', 'upper_bound',\n+                                           'confidence'])\n+\n+Strengths = namedtuple('Strengths', ['strength_tv', 'strength_group_l1'])\n+\n+\n+# TODO later: exploit new options of SVRG (parallel fit, variance_reduction...)\n+class ConvSCCS(ABC, Base):\n+    _const_attr = [\n+        # constructed attributes\n+        '_preprocessor_obj',\n+        '_model_obj',\n+        '_solver_obj',\n+        # user defined parameters\n+        '_n_lags',\n+        '_penalized_features',\n+        '_strength_tv',\n+        '_strength_group_l1',\n+        '_random_state',\n+        # computed attributes\n+        'n_cases',\n+        'n_intervals',\n+        'n_features',\n+        'n_coeffs',\n+        'coeffs',\n+        '_features_offset',\n+        '_fitted',\n+        '_step_size',\n+        # refit coeffs, median, and CI data\n+        'bootstrap_coeffs',\n+    ]\n+\n+    _attrinfos = {key: {'writable': False} for key in _const_attr}\n+\n+    def __init__(self, n_lags: np.array,\n+                 penalized_features: np.array,\n+                 strength_tv=None, strength_group_l1=None,\n+                 step: float = None, tol: float = 1e-5, max_iter: int = 100,\n+                 verbose: bool = False, print_every: int = 10,\n+                 record_every: int = 10, random_state: int = None):\n+        \"\"\"ConvSCCS model. This class allows to estimate a lagged effect for\n+        each feature. TV and Group L1 penalties might be used to penalize the\n+        coefficient groups modelling the lagged effects.\n+\n+        Parameters\n+        ----------\n+        n_lags : `numpy.ndarray`, shape=(n_features,), dtype=\"uint64\"\n+            Number of lags per feature. The model will regress labels on the\n+            last observed values of the features over their corresponding\n+            `n_lags` time intervals. `n_lags` values must be between 0 and\n+            `n_intervals` - 1.\n+\n+        penalized_features : `numpy.ndarray`, shape=(n_features,), dtype=\"bool\"\n+            Booleans indicating whether the features should be penalised or\n+            not.\n+\n+        strength_tv : `float`, default=None\n+            Strength of the TV penalization. This value should be `None` or\n+            greater than 0.\n+\n+        strength_group_l1 : `float`, default=None\n+            Strength of the group Lasso penalization. This value should be\n+            `None` or greater than 0.\n+\n+        step : `float`\n+            Step-size parameter, the most important parameter of the solver.\n+            Whenever possible, this can be automatically tuned as\n+            ``step = 1 / model.get_lip_max()``. Otherwise, use a\n+            try-an-improve approach.\n+\n+        tol : `float`, default=1e-5\n+            The tolerance of the solver (iterations stop when the stopping\n+            criterion is below it).\n+\n+        max_iter : `int`, default=100\n+            Maximum number of iterations of the solver, namely maximum\n+            number of epochs.\n+\n+        verbose : `bool`, default=False\n+            If `True`, solver verboses history, otherwise nothing is\n+            displayed.\n+\n+        random_state : `int`, default=None\n+            If not None, the seed of the random sampling.\n+\n+\n+        Attributes\n+        ----------\n+        n_cases : `int` (read-only)\n+            Number of samples\n+\n+        n_intervals : `int` (read-only)\n+            Number of time intervals\n+\n+        n_features : `int` (read-only)\n+            Number of features\n+\n+        n_coeffs : `int` (read-only)\n+            Total number of coefficients of the model\n+\n+        coeffs : `numpy.ndarray`, shape=(n_coeffs,), dtype=\"float64\" (read-only)\n+            Coefficients of the model.\n+\n+        bootstrap_coeffs : `Bootstrap_CI` (read-only)\n+            Bootstrap coefficients and confidence intervals of the model.\n+        \"\"\"\n+        Base.__init__(self)\n+        # Init objects to be computed later\n+        self.n_cases = None\n+        self.n_intervals = None\n+        self.n_features = None\n+        self.n_coeffs = None\n+        self.coeffs = None\n+        self.bootstrap_coeffs = Bootstrap_CI(list(), list(), list(), list(),\n+                                             None)\n+        self._fitted = None\n+        self._step_size = None\n+\n+        # Init user defined parameters\n+        self._features_offset = None\n+        self._n_lags = None\n+        self.n_lags = n_lags\n+        self._penalized_features = None\n+        self.penalized_features = penalized_features\n+        self._strength_tv = None\n+        self._strength_group_l1 = None\n+        self.strength_tv = strength_tv\n+        self.strength_group_l1 = strength_group_l1\n+\n+        self._step_size = step\n+        self.tol = tol\n+        self.max_iter = max_iter\n+        self.verbose = verbose,\n+        self.print_every = print_every\n+        self.record_every = record_every\n+        random_state = int(np.random.randint(0, 1000, 1)[0]\n+                           if random_state is None else random_state)\n+        self._random_state = None\n+        self.random_state = random_state\n+\n+        # Construct objects\n+        self._preprocessor_obj = self._construct_preprocessor_obj()\n+        self._model_obj = None\n+        self._solver_obj = self._construct_solver_obj(step, max_iter, tol,\n+                                                      print_every, record_every,\n+                                                      verbose, random_state)\n+\n+    # Interface\n+    def fit(self, features: list, labels: list,\n+            censoring: np.array, bootstrap: bool = False,\n+            bootstrap_rep: int = 200,\n+            bootstrap_confidence: float = .95):\n+        \"\"\"Fit the model according to the given training data.\n+\n+        Parameters\n+        ----------\n+        features : `list` of `numpy.ndarray` or `list` of `scipy.sparse.csr_matrix`,\n+            list of length n_cases, each element of the list of\n+            shape=(n_intervals, n_features)\n+            The list of features matrices.\n+\n+        labels : `list` of `numpy.ndarray`,\n+            list of length n_cases, each element of the list of\n+            shape=(n_intervals,)\n+            The labels vector\n+\n+        censoring : `numpy.ndarray`, shape=(n_cases,), dtype=\"uint64\"\n+            The censoring data. This array should contain integers in\n+            [1, n_intervals]. If the value i is equal to n_intervals, then there\n+            is no censoring for sample i. If censoring = c < n_intervals, then\n+            the observation of sample i is stopped at interval c, that is, the\n+            row c - 1 of the corresponding matrix. The last n_intervals - c rows\n+            are then set to 0.\n+\n+        bootstrap : `bool`, default=False\n+            Activate parametric bootstrap confidence intervals computation.\n+\n+        bootstrap_rep : `int`, default=200\n+            Number of parametric bootstrap iterations\n+\n+        bootstrap_confidence : `float`, default=.95\n+            Confidence level of the bootstrapped confidence intervals\n+\n+        Returns\n+        -------\n+        output : `LearnerSCCS`\n+            The current instance with given data\n+        \"\"\"\n+        p_features, p_labels, p_censoring = self._prefit(features, labels,\n+                                                         censoring)\n+        self._fit(project=False)\n+        self._postfit(p_features, p_labels, p_censoring, False,\n+                      bootstrap, bootstrap_rep, bootstrap_confidence)\n+\n+        return self.coeffs, self.bootstrap_coeffs\n+\n+    def score(self, features=None, labels=None, censoring=None):\n+        \"\"\"Returns the negative log-likelihood of the model, using the current\n+        fitted coefficients on the passed data.\n+        If no data is passed, the negative log-likelihood is computed using the\n+        data used for training.\n+\n+        Parameters\n+        ----------\n+        features : `None` or `list` of `numpy.ndarray` or `list` of `scipy.sparse.csr_matrix`,\n+            list of length n_cases, each element of the list of\n+            shape=(n_intervals, n_features)\n+            The list of features matrices.\n+\n+        labels : `None` or `list` of `numpy.ndarray`,\n+            list of length n_cases, each element of the list of\n+            shape=(n_intervals,)\n+            The labels vector\n+\n+        censoring : `None` or `numpy.ndarray`, shape=(n_cases,), dtype=\"uint64\"\n+            The censoring data. This array should contain integers in\n+            [1, n_intervals]. If the value i is equal to n_intervals, then there\n+            is no censoring for sample i. If censoring = c < n_intervals, then\n+            the observation of sample i is stopped at interval c, that is, the\n+            row c - 1 of the corresponding matrix. The last n_intervals - c rows\n+            are then set to 0.\n+\n+        Returns\n+        -------\n+        output : `float`\n+            The value of the negative log-likelihood\n+        \"\"\"\n+\n+        return self._score(features, labels, censoring, preprocess=True)\n+\n+    def fit_kfold_cv(self, features, labels, censoring,\n+                     strength_tv_range: tuple=(),\n+                     strength_group_l1_range: tuple=(), logspace=True,\n+                     n_cv_iter: int= 30, n_folds: int = 3, shuffle: bool = True,\n+                     bootstrap: bool = False, bootstrap_rep: int = 100,\n+                     bootstrap_confidence: float = .95):\n+        \"\"\"Perform a cross validation to find optimal hyperparameters given\n+        training data. Cross validation using stratified K-folds and random\n+        search, parameters being sampled uniformly in the range (logspace or\n+        linspace) specified by the user.\n+\n+        Parameters\n+        ----------\n+        features : `list` of `numpy.ndarray` or `list` of `scipy.sparse.csr_matrix`\n+            list of length n_cases, each element of the list of\n+            shape=(n_intervals, n_features)\n+            The list of features matrices.\n+\n+        labels : `list` of `numpy.ndarray`,\n+            list of length n_cases, each element of the list of\n+            shape=(n_intervals,)\n+            The labels vector\n+\n+        censoring : `numpy.ndarray`, shape=(n_cases,), dtype=\"uint64\"\n+            The censoring data. This array should contain integers in\n+            [1, n_intervals]. If the value i is equal to n_intervals, then there\n+            is no censoring for sample i. If censoring = c < n_intervals, then\n+            the observation of sample i is stopped at interval c, that is, the\n+            row c - 1 of the corresponding matrix. The last n_intervals - c rows\n+            are then set to 0.\n+\n+        strength_tv_range : `tuple`, shape=(2,), dtype=\"int\"\n+            Range in which sampling TV penalization strength during the\n+            random search. `logspace=True`, range values are understood as\n+            powers of ten, i.e `(-5, -1)` will result in samples being in\n+            `10**(-5), 10**(-1)`.\n+\n+        strength_group_l1_range : `tuple`, shape=(2,), dtype=\"int\"\n+            Range in which sampling group L1 penalization strength during the\n+            random search. `logspace=True`, range values are understood as\n+            powers of ten, i.e `(-5, -1)` will result in samples being in\n+            `10**(-5), 10**(-1)`.\n+\n+        logspace : `bool`\n+            If `True`, hyperparameters are samples in logspace.\n+\n+        n_cv_iter : `int`\n+            Number of hyperparameters samples to draw when performing\n+            random search.\n+\n+        n_folds : `int`\n+            Number of folds used to perform the cross validation.\n+\n+        shuffle : `bool`\n+            If `True`, the data is shuffled before performing train / validation\n+            / test splits.\n+\n+        bootstrap : `bool`, default=False\n+            Activate parametric bootstrap confidence intervals computation.\n+\n+        bootstrap_rep : `int`, default=200\n+            Number of parametric bootstrap iterations\n+\n+        bootstrap_confidence : `float`, default=.95\n+            Confidence level of the bootstrapped confidence intervals\n+\n+        Returns\n+        -------\n+        output : `LearnerSCCS`\n+            The current instance with given data\n+        \"\"\"\n+        # setup the model and preprocess the data\n+        p_features, p_labels, p_censoring = self._prefit(features, labels,\n+                                                         censoring)\n+        # split the data with stratified KFold\n+        kf = StratifiedKFold(n_folds, shuffle, self.random_state)\n+        labels_interval = np.nonzero(p_labels)[1]\n+\n+        # Training loop\n+        model_global_parameters = {\n+            \"n_intervals\": self.n_intervals,\n+            \"n_lags\": self.n_lags,\n+            \"n_features\": self.n_features,\n+        }\n+        cv_tracker = CrossValidationTracker(model_global_parameters)\n+        generators = self._construct_generator_obj(strength_tv_range,\n+                                                   strength_group_l1_range,\n+                                                   logspace)\n+        # TODO later: parallelize CV\n+        i = 0\n+        while i < n_cv_iter:\n+            self._set('coeffs', np.zeros(self.n_coeffs))\n+            self._strengths = [g.rvs(1)[0] for g in generators]\n+\n+            train_scores = []\n+            test_scores = []\n+            for train_index, test_index in kf.split(p_features, labels_interval):\n+                train = itemgetter(*train_index.tolist())\n+                test = itemgetter(*test_index.tolist())\n+                X_train, X_test = list(train(p_features)), list(test(p_features))\n+                y_train, y_test = list(train(p_labels)), list(test(p_labels))\n+                censoring_train, censoring_test = p_censoring[train_index], \\\n+                    p_censoring[test_index]\n+\n+                self._model_obj.fit(X_train, y_train, censoring_train)\n+                self._fit(project=False)\n+\n+                train_scores.append(self._score())\n+                test_scores.append(self._score(X_test, y_test, censoring_test))\n+\n+            cv_tracker.log_cv_iteration({'strength': self._strengths},\n+                                        np.array(train_scores),\n+                                        np.array(test_scores))\n+            i += 1\n+\n+        best_parameters = cv_tracker.find_best_params()\n+        best_strength = best_parameters[\"strength\"]\n+\n+        # refit best model on all the data\n+        self._set('coeffs', np.zeros(self.n_coeffs))\n+        self._strengths = best_strength\n+\n+        self._model_obj.fit(p_features, p_labels, p_censoring)\n+        coeffs, bootstrap_ci = self._postfit(p_features, p_labels, p_censoring,\n+                                             True, bootstrap, bootstrap_rep,\n+                                             bootstrap_confidence)\n+\n+        cv_tracker.log_best_model(self._strengths, self.coeffs.tolist(),\n+                                  self.score(),\n+                                  self.bootstrap_coeffs)\n+\n+        return coeffs, cv_tracker\n+\n+    # Internals\n+    def _prefit(self, features, labels, censoring):\n+        n_intervals, n_features = features[0].shape\n+        n_cases = len(features)\n+        censoring = check_censoring_consistency(censoring, n_cases)\n+        features = check_longitudinal_features_consistency(features,\n+                                                           (n_intervals,\n+                                                            n_features),\n+                                                           \"float64\")\n+        labels = check_longitudinal_features_consistency(labels,\n+                                                         (n_intervals,),\n+                                                         \"int32\")\n+        self._set('n_features', n_features)\n+        self._set('n_intervals', n_intervals)\n+\n+        features, labels, censoring = self._preprocess_data(features,\n+                                                            labels,\n+                                                            censoring)\n+        n_coeffs = int(np.sum(self._n_lags) + self.n_features)\n+        self._set('n_coeffs', n_coeffs)\n+        self._set('coeffs', np.zeros(n_coeffs))\n+        self._set('n_cases', len(features))\n+\n+        # Step computation\n+        self._set(\"_model_obj\", self._construct_model_obj())\n+        self._model_obj.fit(features, labels, censoring)\n+        if self.step is None:\n+            self.step = 1 / self._model_obj.get_lip_max()\n+\n+        return features, labels, censoring\n+\n+    def _fit(self, project):\n+        prox_obj = self._construct_prox_obj(self.coeffs, project)\n+        solver_obj = self._solver_obj\n+        model_obj = self._model_obj\n+\n+        # Now, we can pass the model and prox objects to the solver\n+        solver_obj.set_model(model_obj).set_prox(prox_obj)\n+\n+        coeffs_start = self.coeffs\n+\n+        # Launch the solver\n+        coeffs = solver_obj.solve(coeffs_start, step=self.step)\n+\n+        self._set(\"coeffs\", coeffs)\n+        self._set(\"_fitted\", True)\n+\n+        return coeffs\n+\n+    def _postfit(self, p_features, p_labels, p_censoring,\n+                 refit, bootstrap, bootstrap_rep, bootstrap_confidence):\n+        # WARNING: _refit uses already preprocessed p_features, p_labels\n+        # and p_censoring\n+        if not self._fitted:\n+            raise RuntimeError('You must fit the model first')\n+\n+        if refit:\n+            # refit coeffs on all the data (after Cross Validation for example)\n+            self._model_obj.fit(p_features, p_labels, p_censoring)\n+            coeffs = self._fit(project=False)\n+            self._set('coeffs', coeffs)\n+\n+        if bootstrap:\n+            self._model_obj.fit(p_features, p_labels, p_censoring)\n+            refit_coeffs = self._fit(project=True)\n+            bootstrap_ci = self._bootstrap(p_features, p_labels, p_censoring,\n+                                           refit_coeffs, bootstrap_rep,\n+                                           bootstrap_confidence)\n+            self._set('bootstrap_coeffs', bootstrap_ci._asdict())\n+\n+        return self.coeffs, self.bootstrap_coeffs\n+\n+    # Utilities #\n+    def _preprocess_data(self, features, labels, censoring):\n+        for preprocessor in self._preprocessor_obj:\n+            features, labels, censoring = preprocessor.fit_transform(\n+                features,\n+                labels,\n+                censoring)\n+        return features, labels, censoring\n+\n+    def _bootstrap(self, p_features, p_labels, p_censoring, coeffs,\n+                   rep, confidence):\n+        # WARNING: _bootstrap inputs are already preprocessed p_features,\n+        # p_labels and p_censoring\n+        if confidence <= 0 or confidence >= 1:\n+            raise ValueError(\"`bootstrap_confidence` should be in (0, 1)\")\n+        confidence = 1 - confidence\n+        if not self._fitted:\n+            raise RuntimeError('You must fit the model first')\n+\n+        bootstrap_coeffs = []\n+        sim = SimuSCCS(self.n_cases, self.n_intervals, self.n_features,\n+                       self.n_lags, coeffs=coeffs)\n+        # TODO later: parallelize bootstrap (everything should be pickable...)\n+        for k in range(rep):\n+            y = sim._simulate_multinomial_outcomes(p_features, coeffs)\n+            self._model_obj.fit(p_features, y, p_censoring)\n+            bootstrap_coeffs.append(self._fit(True))\n+\n+        bootstrap_coeffs = np.exp(np.array(bootstrap_coeffs))\n+        bootstrap_coeffs.sort(axis=0)\n+        lower_bound = np.log(bootstrap_coeffs[\n+                                 int(np.floor(rep * confidence / 2))])\n+        upper_bound = np.log(bootstrap_coeffs[\n+                                 int(np.floor(rep * (1 - confidence / 2)))])\n+        median_coeffs = np.log(bootstrap_coeffs[\n+                                   int(np.floor(rep * .5))])\n+        return Bootstrap_CI(coeffs, median_coeffs, lower_bound,\n+                            upper_bound, confidence)\n+\n+    def _score(self, features=None, labels=None, censoring=None,\n+               preprocess=False):\n+        if not self._fitted:\n+            raise RuntimeError('You must fit the model first')\n+\n+        all_none = all(e is None for e in [features, labels, censoring])\n+        if all_none:\n+            loss = self._model_obj.loss(self.coeffs)\n+        else:\n+            if features is None:\n+                raise ValueError('Passed ``features`` is None')\n+            elif labels is None:\n+                raise ValueError('Passed ``labels`` is None')\n+            elif censoring is None:\n+                raise ValueError('Passed ``censoring`` is None')\n+            else:\n+                # Avoid calling preprocessing during CV\n+                if preprocess:\n+                    features, labels, censoring = self._preprocess_data(\n+                        features,\n+                        labels,\n+                        censoring)\n+                model = self._construct_model_obj().fit(features, labels,\n+                                                        censoring)\n+                loss = model.loss(self.coeffs)\n+        return loss\n+\n+    # Factories #\n+    def _construct_preprocessor_obj(self):\n+        # TODO later: fix parallel preprocessing\n+        preprocessors = list()\n+        preprocessors.append(LongitudinalSamplesFilter(n_jobs=1))\n+        if len(self.n_lags) > 0:\n+            preprocessors.append(LongitudinalFeaturesLagger(self.n_lags,\n+                                                            n_jobs=1))\n+        return preprocessors\n+\n+    def _construct_model_obj(self):\n+        return ModelSCCS(self.n_intervals, self.n_lags)\n+\n+    def _construct_prox_obj(self, coeffs=None, project=False):\n+        n_penalized_features = len(self.penalized_features) \\\n+            if self.penalized_features is not None else 0\n+\n+        if project:\n+            # project future coeffs on the support of given coeffs\n+            if all(self.n_lags) == 0:\n+                proxs = [ProxZero()]\n+            elif coeffs is not None and any(self.n_lags) > 0:\n+                prox_ranges = self._detect_support(coeffs)\n+                proxs = [ProxEquality(0, range=r) for r in prox_ranges]\n+            else:\n+                raise ValueError(\"Coeffs are None. \" +\n+                                 \"Equality penalty cannot infer the \"\n+                                 \"coefficients support.\")\n+        elif n_penalized_features > 0 and self._strength_tv is not None or \\\n+                self._strength_group_l1 is not None:\n+            # TV and GroupLasso penalties\n+            blocks_start = np.zeros(n_penalized_features)\n+            blocks_end = np.zeros(n_penalized_features)\n+            proxs = []\n+\n+            for i in self.penalized_features:\n+                start = int(self._features_offset[i])\n+                blocks_start[i] = start\n+                end = int(blocks_start[i] + self._n_lags[i] + 1)\n+                blocks_end[i] = end\n+                if self._strength_tv is not None:\n+                    proxs.append(ProxTV(self._strength_tv, range=(start, end)))\n+\n+            if self._strength_group_l1 is not None:\n+                blocks_size = blocks_end - blocks_start\n+                proxs.append(ProxGroupL1(self._strength_group_l1,\n+                                         blocks_start.tolist(),\n+                                         blocks_size.tolist()))\n+        else:\n+            # Default prox: does nothing\n+            proxs = [ProxZero()]\n+\n+        prox_obj = ProxMulti(tuple(proxs))\n+\n+        return prox_obj\n+\n+    def _detect_support(self, coeffs):\n+        \"\"\"Return the ranges over which consecutive coefficients are equal in\n+        case at least two coefficients are equal.\n+\n+        This method is used to compute the ranges for ProxEquality,\n+        to enforce a support corresponding to the support of `coeffs`.\n+\n+         example:\n+         coeffs = np.array([ 1.  2.  2.  1.  1.])\n+         self._detect_support(coeffs)\n+         >>> [(1, 3), (3, 5)]\n+         \"\"\"\n+        kernel = np.array([1, -1])\n+        groups = []\n+        for i in self.penalized_features:\n+            idx = int(self._features_offset[i])\n+            n_lags = int(self._n_lags[i])\n+            if n_lags > 0:\n+                acc = 1\n+                for change in np.convolve(coeffs[idx:idx+n_lags+1], kernel,\n+                                          'valid'):\n+                    if change:\n+                        if acc > 1:\n+                            groups.append((idx, idx + acc))\n+                        idx += acc\n+                        acc = 1\n+                    else:\n+                        acc += 1\n+                # Last coeff always count as a change\n+                if acc > 1:\n+                    groups.append((idx, idx + acc))\n+        return groups\n+\n+    @staticmethod\n+    def _construct_solver_obj(step, max_iter, tol, print_every,\n+                              record_every, verbose, seed):\n+        # seed cannot be None in SVRG\n+        solver_obj = SVRG(step=step, max_iter=max_iter, tol=tol,\n+                          print_every=print_every,\n+                          record_every=record_every,\n+                          verbose=verbose, seed=seed)\n+\n+        return solver_obj\n+\n+    def _construct_generator_obj(self, strength_tv_range,\n+                                 strength_group_l1_range,\n+                                 logspace=True):\n+        generators = []\n+        if len(strength_tv_range) == 2:\n+            if logspace:\n+                generators.append(Log10UniformGenerator(*strength_tv_range))\n+            else:\n+                generators.append(uniform(strength_tv_range))\n+        else:\n+            generators.append(null_generator)\n+\n+        if len(strength_group_l1_range) == 2:\n+            if logspace:\n+                generators.append(\n+                    Log10UniformGenerator(*strength_group_l1_range))\n+            else:\n+                generators.append(uniform(strength_group_l1_range))\n+        else:\n+            generators.append(null_generator)\n+\n+        return generators\n+\n+    # Properties #\n+    @property\n+    def step(self):\n+        return self._step_size\n+\n+    @step.setter\n+    def step(self, value):\n+        self._set('_step_size', value)","path":"tick/survival/convolutional_sccs.py","position":653,"original_position":653,"commit_id":"9545da466746f8de88319e4a3e869c983c1335cd","original_commit_id":"51ed6cd863046a048d9a7b834083bd60302a193a","user":{"login":"MaryanMorel","id":5260162,"avatar_url":"https://avatars3.githubusercontent.com/u/5260162?v=4","gravatar_id":"","url":"https://api.github.com/users/MaryanMorel","html_url":"https://github.com/MaryanMorel","followers_url":"https://api.github.com/users/MaryanMorel/followers","following_url":"https://api.github.com/users/MaryanMorel/following{/other_user}","gists_url":"https://api.github.com/users/MaryanMorel/gists{/gist_id}","starred_url":"https://api.github.com/users/MaryanMorel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MaryanMorel/subscriptions","organizations_url":"https://api.github.com/users/MaryanMorel/orgs","repos_url":"https://api.github.com/users/MaryanMorel/repos","events_url":"https://api.github.com/users/MaryanMorel/events{/privacy}","received_events_url":"https://api.github.com/users/MaryanMorel/received_events","type":"User","site_admin":false},"body":"ok","created_at":"2018-02-11T16:37:40Z","updated_at":"2018-02-11T16:37:41Z","html_url":"https://github.com/X-DataInitiative/tick/pull/158#discussion_r167440854","pull_request_url":"https://api.github.com/repos/X-DataInitiative/tick/pulls/158","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/X-DataInitiative/tick/pulls/comments/167440854"},"html":{"href":"https://github.com/X-DataInitiative/tick/pull/158#discussion_r167440854"},"pull_request":{"href":"https://api.github.com/repos/X-DataInitiative/tick/pulls/158"}},"in_reply_to_id":167291469},"pull_request":{"url":"https://api.github.com/repos/X-DataInitiative/tick/pulls/158","id":166272158,"html_url":"https://github.com/X-DataInitiative/tick/pull/158","diff_url":"https://github.com/X-DataInitiative/tick/pull/158.diff","patch_url":"https://github.com/X-DataInitiative/tick/pull/158.patch","issue_url":"https://api.github.com/repos/X-DataInitiative/tick/issues/158","number":158,"state":"open","locked":false,"title":"Add ConvSCCS model + refactoring of SCCS-related stuff (preprocessing, simulations, etc.)","user":{"login":"MaryanMorel","id":5260162,"avatar_url":"https://avatars3.githubusercontent.com/u/5260162?v=4","gravatar_id":"","url":"https://api.github.com/users/MaryanMorel","html_url":"https://github.com/MaryanMorel","followers_url":"https://api.github.com/users/MaryanMorel/followers","following_url":"https://api.github.com/users/MaryanMorel/following{/other_user}","gists_url":"https://api.github.com/users/MaryanMorel/gists{/gist_id}","starred_url":"https://api.github.com/users/MaryanMorel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MaryanMorel/subscriptions","organizations_url":"https://api.github.com/users/MaryanMorel/orgs","repos_url":"https://api.github.com/users/MaryanMorel/repos","events_url":"https://api.github.com/users/MaryanMorel/events{/privacy}","received_events_url":"https://api.github.com/users/MaryanMorel/received_events","type":"User","site_admin":false},"body":"Add ConvSCCS model + refactoring of SCCS-related stuff (preprocessing, simulations, etc.) according to the paper\r\n\r\nFeature products are not shipped as a learner option for now, but it can be used independently.\r\n\r\nMost objects are not picklable for now, this should be fixed in another task to allow parallel CV and bootstrap.","created_at":"2018-01-31T14:20:30Z","updated_at":"2018-02-11T16:37:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"03fabad115a0493445d614bff54032ccdac18cd8","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/X-DataInitiative/tick/pulls/158/commits","review_comments_url":"https://api.github.com/repos/X-DataInitiative/tick/pulls/158/comments","review_comment_url":"https://api.github.com/repos/X-DataInitiative/tick/pulls/comments{/number}","comments_url":"https://api.github.com/repos/X-DataInitiative/tick/issues/158/comments","statuses_url":"https://api.github.com/repos/X-DataInitiative/tick/statuses/9545da466746f8de88319e4a3e869c983c1335cd","head":{"label":"X-DataInitiative:TICK-361","ref":"TICK-361","sha":"9545da466746f8de88319e4a3e869c983c1335cd","user":{"login":"X-DataInitiative","id":17986992,"avatar_url":"https://avatars2.githubusercontent.com/u/17986992?v=4","gravatar_id":"","url":"https://api.github.com/users/X-DataInitiative","html_url":"https://github.com/X-DataInitiative","followers_url":"https://api.github.com/users/X-DataInitiative/followers","following_url":"https://api.github.com/users/X-DataInitiative/following{/other_user}","gists_url":"https://api.github.com/users/X-DataInitiative/gists{/gist_id}","starred_url":"https://api.github.com/users/X-DataInitiative/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/X-DataInitiative/subscriptions","organizations_url":"https://api.github.com/users/X-DataInitiative/orgs","repos_url":"https://api.github.com/users/X-DataInitiative/repos","events_url":"https://api.github.com/users/X-DataInitiative/events{/privacy}","received_events_url":"https://api.github.com/users/X-DataInitiative/received_events","type":"Organization","site_admin":false},"repo":{"id":75284069,"name":"tick","full_name":"X-DataInitiative/tick","owner":{"login":"X-DataInitiative","id":17986992,"avatar_url":"https://avatars2.githubusercontent.com/u/17986992?v=4","gravatar_id":"","url":"https://api.github.com/users/X-DataInitiative","html_url":"https://github.com/X-DataInitiative","followers_url":"https://api.github.com/users/X-DataInitiative/followers","following_url":"https://api.github.com/users/X-DataInitiative/following{/other_user}","gists_url":"https://api.github.com/users/X-DataInitiative/gists{/gist_id}","starred_url":"https://api.github.com/users/X-DataInitiative/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/X-DataInitiative/subscriptions","organizations_url":"https://api.github.com/users/X-DataInitiative/orgs","repos_url":"https://api.github.com/users/X-DataInitiative/repos","events_url":"https://api.github.com/users/X-DataInitiative/events{/privacy}","received_events_url":"https://api.github.com/users/X-DataInitiative/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/X-DataInitiative/tick","description":"Module for statistical learning, with a particular emphasis on time-dependent modelling","fork":false,"url":"https://api.github.com/repos/X-DataInitiative/tick","forks_url":"https://api.github.com/repos/X-DataInitiative/tick/forks","keys_url":"https://api.github.com/repos/X-DataInitiative/tick/keys{/key_id}","collaborators_url":"https://api.github.com/repos/X-DataInitiative/tick/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/X-DataInitiative/tick/teams","hooks_url":"https://api.github.com/repos/X-DataInitiative/tick/hooks","issue_events_url":"https://api.github.com/repos/X-DataInitiative/tick/issues/events{/number}","events_url":"https://api.github.com/repos/X-DataInitiative/tick/events","assignees_url":"https://api.github.com/repos/X-DataInitiative/tick/assignees{/user}","branches_url":"https://api.github.com/repos/X-DataInitiative/tick/branches{/branch}","tags_url":"https://api.github.com/repos/X-DataInitiative/tick/tags","blobs_url":"https://api.github.com/repos/X-DataInitiative/tick/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/X-DataInitiative/tick/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/X-DataInitiative/tick/git/refs{/sha}","trees_url":"https://api.github.com/repos/X-DataInitiative/tick/git/trees{/sha}","statuses_url":"https://api.github.com/repos/X-DataInitiative/tick/statuses/{sha}","languages_url":"https://api.github.com/repos/X-DataInitiative/tick/languages","stargazers_url":"https://api.github.com/repos/X-DataInitiative/tick/stargazers","contributors_url":"https://api.github.com/repos/X-DataInitiative/tick/contributors","subscribers_url":"https://api.github.com/repos/X-DataInitiative/tick/subscribers","subscription_url":"https://api.github.com/repos/X-DataInitiative/tick/subscription","commits_url":"https://api.github.com/repos/X-DataInitiative/tick/commits{/sha}","git_commits_url":"https://api.github.com/repos/X-DataInitiative/tick/git/commits{/sha}","comments_url":"https://api.github.com/repos/X-DataInitiative/tick/comments{/number}","issue_comment_url":"https://api.github.com/repos/X-DataInitiative/tick/issues/comments{/number}","contents_url":"https://api.github.com/repos/X-DataInitiative/tick/contents/{+path}","compare_url":"https://api.github.com/repos/X-DataInitiative/tick/compare/{base}...{head}","merges_url":"https://api.github.com/repos/X-DataInitiative/tick/merges","archive_url":"https://api.github.com/repos/X-DataInitiative/tick/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/X-DataInitiative/tick/downloads","issues_url":"https://api.github.com/repos/X-DataInitiative/tick/issues{/number}","pulls_url":"https://api.github.com/repos/X-DataInitiative/tick/pulls{/number}","milestones_url":"https://api.github.com/repos/X-DataInitiative/tick/milestones{/number}","notifications_url":"https://api.github.com/repos/X-DataInitiative/tick/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/X-DataInitiative/tick/labels{/name}","releases_url":"https://api.github.com/repos/X-DataInitiative/tick/releases{/id}","deployments_url":"https://api.github.com/repos/X-DataInitiative/tick/deployments","created_at":"2016-12-01T10:59:08Z","updated_at":"2018-02-09T23:20:29Z","pushed_at":"2018-02-09T18:07:15Z","git_url":"git://github.com/X-DataInitiative/tick.git","ssh_url":"git@github.com:X-DataInitiative/tick.git","clone_url":"https://github.com/X-DataInitiative/tick.git","svn_url":"https://github.com/X-DataInitiative/tick","homepage":"https://x-datainitiative.github.io/tick/","size":35526,"stargazers_count":115,"watchers_count":115,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":16,"mirror_url":null,"archived":false,"open_issues_count":17,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause"},"forks":16,"open_issues":17,"watchers":115,"default_branch":"master"}},"base":{"label":"X-DataInitiative:master","ref":"master","sha":"161138475145b5ab50cdc89528f62863cb164d37","user":{"login":"X-DataInitiative","id":17986992,"avatar_url":"https://avatars2.githubusercontent.com/u/17986992?v=4","gravatar_id":"","url":"https://api.github.com/users/X-DataInitiative","html_url":"https://github.com/X-DataInitiative","followers_url":"https://api.github.com/users/X-DataInitiative/followers","following_url":"https://api.github.com/users/X-DataInitiative/following{/other_user}","gists_url":"https://api.github.com/users/X-DataInitiative/gists{/gist_id}","starred_url":"https://api.github.com/users/X-DataInitiative/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/X-DataInitiative/subscriptions","organizations_url":"https://api.github.com/users/X-DataInitiative/orgs","repos_url":"https://api.github.com/users/X-DataInitiative/repos","events_url":"https://api.github.com/users/X-DataInitiative/events{/privacy}","received_events_url":"https://api.github.com/users/X-DataInitiative/received_events","type":"Organization","site_admin":false},"repo":{"id":75284069,"name":"tick","full_name":"X-DataInitiative/tick","owner":{"login":"X-DataInitiative","id":17986992,"avatar_url":"https://avatars2.githubusercontent.com/u/17986992?v=4","gravatar_id":"","url":"https://api.github.com/users/X-DataInitiative","html_url":"https://github.com/X-DataInitiative","followers_url":"https://api.github.com/users/X-DataInitiative/followers","following_url":"https://api.github.com/users/X-DataInitiative/following{/other_user}","gists_url":"https://api.github.com/users/X-DataInitiative/gists{/gist_id}","starred_url":"https://api.github.com/users/X-DataInitiative/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/X-DataInitiative/subscriptions","organizations_url":"https://api.github.com/users/X-DataInitiative/orgs","repos_url":"https://api.github.com/users/X-DataInitiative/repos","events_url":"https://api.github.com/users/X-DataInitiative/events{/privacy}","received_events_url":"https://api.github.com/users/X-DataInitiative/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/X-DataInitiative/tick","description":"Module for statistical learning, with a particular emphasis on time-dependent modelling","fork":false,"url":"https://api.github.com/repos/X-DataInitiative/tick","forks_url":"https://api.github.com/repos/X-DataInitiative/tick/forks","keys_url":"https://api.github.com/repos/X-DataInitiative/tick/keys{/key_id}","collaborators_url":"https://api.github.com/repos/X-DataInitiative/tick/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/X-DataInitiative/tick/teams","hooks_url":"https://api.github.com/repos/X-DataInitiative/tick/hooks","issue_events_url":"https://api.github.com/repos/X-DataInitiative/tick/issues/events{/number}","events_url":"https://api.github.com/repos/X-DataInitiative/tick/events","assignees_url":"https://api.github.com/repos/X-DataInitiative/tick/assignees{/user}","branches_url":"https://api.github.com/repos/X-DataInitiative/tick/branches{/branch}","tags_url":"https://api.github.com/repos/X-DataInitiative/tick/tags","blobs_url":"https://api.github.com/repos/X-DataInitiative/tick/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/X-DataInitiative/tick/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/X-DataInitiative/tick/git/refs{/sha}","trees_url":"https://api.github.com/repos/X-DataInitiative/tick/git/trees{/sha}","statuses_url":"https://api.github.com/repos/X-DataInitiative/tick/statuses/{sha}","languages_url":"https://api.github.com/repos/X-DataInitiative/tick/languages","stargazers_url":"https://api.github.com/repos/X-DataInitiative/tick/stargazers","contributors_url":"https://api.github.com/repos/X-DataInitiative/tick/contributors","subscribers_url":"https://api.github.com/repos/X-DataInitiative/tick/subscribers","subscription_url":"https://api.github.com/repos/X-DataInitiative/tick/subscription","commits_url":"https://api.github.com/repos/X-DataInitiative/tick/commits{/sha}","git_commits_url":"https://api.github.com/repos/X-DataInitiative/tick/git/commits{/sha}","comments_url":"https://api.github.com/repos/X-DataInitiative/tick/comments{/number}","issue_comment_url":"https://api.github.com/repos/X-DataInitiative/tick/issues/comments{/number}","contents_url":"https://api.github.com/repos/X-DataInitiative/tick/contents/{+path}","compare_url":"https://api.github.com/repos/X-DataInitiative/tick/compare/{base}...{head}","merges_url":"https://api.github.com/repos/X-DataInitiative/tick/merges","archive_url":"https://api.github.com/repos/X-DataInitiative/tick/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/X-DataInitiative/tick/downloads","issues_url":"https://api.github.com/repos/X-DataInitiative/tick/issues{/number}","pulls_url":"https://api.github.com/repos/X-DataInitiative/tick/pulls{/number}","milestones_url":"https://api.github.com/repos/X-DataInitiative/tick/milestones{/number}","notifications_url":"https://api.github.com/repos/X-DataInitiative/tick/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/X-DataInitiative/tick/labels{/name}","releases_url":"https://api.github.com/repos/X-DataInitiative/tick/releases{/id}","deployments_url":"https://api.github.com/repos/X-DataInitiative/tick/deployments","created_at":"2016-12-01T10:59:08Z","updated_at":"2018-02-09T23:20:29Z","pushed_at":"2018-02-09T18:07:15Z","git_url":"git://github.com/X-DataInitiative/tick.git","ssh_url":"git@github.com:X-DataInitiative/tick.git","clone_url":"https://github.com/X-DataInitiative/tick.git","svn_url":"https://github.com/X-DataInitiative/tick","homepage":"https://x-datainitiative.github.io/tick/","size":35526,"stargazers_count":115,"watchers_count":115,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":16,"mirror_url":null,"archived":false,"open_issues_count":17,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause"},"forks":16,"open_issues":17,"watchers":115,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/X-DataInitiative/tick/pulls/158"},"html":{"href":"https://github.com/X-DataInitiative/tick/pull/158"},"issue":{"href":"https://api.github.com/repos/X-DataInitiative/tick/issues/158"},"comments":{"href":"https://api.github.com/repos/X-DataInitiative/tick/issues/158/comments"},"review_comments":{"href":"https://api.github.com/repos/X-DataInitiative/tick/pulls/158/comments"},"review_comment":{"href":"https://api.github.com/repos/X-DataInitiative/tick/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/X-DataInitiative/tick/pulls/158/commits"},"statuses":{"href":"https://api.github.com/repos/X-DataInitiative/tick/statuses/9545da466746f8de88319e4a3e869c983c1335cd"}},"author_association":"OWNER"}}
{"actor":{"display_login":"MaryanMorel"}}