{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":11000,"databundleVersionId":875412,"sourceType":"competition"}],"dockerImageVersionId":25114,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## 1. About this notebook\n\nIn this notebook I try a few different models to predict the time to failure during earthquake simulations (LANL competition). Every model is implemented trough a Scikit-learn pipeline and compared using cross-validation scores and visualizations.\n\nFor more details about LANL competition you can check my [previous kernel](https://www.kaggle.com/jsaguiar/seismic-data-exploration).","metadata":{"_uuid":"08c0c8baf241a57ba91214df7f43329f106fe0de"}},{"cell_type":"code","source":"import os\nimport time\nimport random\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm_notebook\nfrom boruta import BorutaPy\n# Visualizations\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport plotly.graph_objs as go\nfrom plotly.offline import init_notebook_mode, iplot\n# Sklearn utilities\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.metrics import mean_absolute_error\nfrom sklearn.model_selection import GridSearchCV, KFold, RandomizedSearchCV\nfrom sklearn.feature_selection import RFECV, SelectFromModel\n# Models\nfrom sklearn.linear_model import LinearRegression, Ridge\nfrom sklearn.tree import DecisionTreeRegressor\nfrom sklearn.svm import NuSVR, SVR\nfrom sklearn.kernel_ridge import KernelRidge\nfrom sklearn.ensemble import AdaBoostRegressor\nfrom sklearn.ensemble import RandomForestRegressor, ExtraTreesRegressor\nimport lightgbm as lgb\nimport warnings\nwarnings.simplefilter(action='ignore', category=FutureWarning)\nwarnings.simplefilter(action='ignore', category=UserWarning)\nsns.set()\ninit_notebook_mode(connected=True)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This competition has a single feature (seismic signal) and we must predict the time to failure:","metadata":{}},{"cell_type":"code","source":"data_type = {'acoustic_data': np.int16, 'time_to_failure': np.float32}\ntrain = pd.read_csv('../input/train.csv', dtype=data_type)\ntrain.head()","metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2. Feature Engineering\n\nThe idea is to group the acoustic data in chunks and extract the following features:\n\n* Aggregations: min, max, mean and std\n* Absolute features: max, mean and std\n* Quantile features\n* Trend features\n* Rolling features\n* Ratios","metadata":{"_uuid":"ab25676aa4a93cb6213347bfd3dcd15d9c767fe7"}},{"cell_type":"code","source":"def add_trend_feature(arr, abs_values=False):\n    \"\"\"Fit a univariate linear regression and return the coefficient.\"\"\"\n    idx = np.array(range(len(arr)))\n    if abs_values:\n        arr = np.abs(arr)\n    lr = LinearRegression()\n    lr.fit(idx.reshape(-1, 1), arr)\n    return lr.coef_[0]\n\ndef extract_features_from_segment(x):\n    \"\"\"Returns a dictionary with the features for the given segment of acoustic data.\"\"\"\n    features = {}\n    \n    features['ave'] = x.values.mean()\n    features['std'] = x.values.std()\n    features['max'] = x.values.max()\n    features['min'] = x.values.min()\n    features['q90'] = np.quantile(x.values, 0.90)\n    features['q95'] = np.quantile(x.values, 0.95)\n    features['q99'] = np.quantile(x.values, 0.99)\n    features['q05'] = np.quantile(x.values, 0.05)\n    features['q10'] = np.quantile(x.values, 0.10)\n    features['q01'] = np.quantile(x.values, 0.01)\n    features['std_to_mean'] = features['std'] / features['ave']\n    \n    features['abs_max'] = np.abs(x.values).max()\n    features['abs_mean'] = np.abs(x.values).mean()\n    features['abs_std'] = np.abs(x.values).std()\n    features['trend'] = add_trend_feature(x.values)\n    features['abs_trend'] = add_trend_feature(x.values, abs_values=True)\n    \n    # New features - rolling features\n    for w in [10, 50, 100, 1000]:\n        x_roll_abs_mean = x.abs().rolling(w).mean().dropna().values\n        x_roll_mean = x.rolling(w).mean().dropna().values\n        x_roll_std = x.rolling(w).std().dropna().values\n        x_roll_min = x.rolling(w).min().dropna().values\n        x_roll_max = x.rolling(w).max().dropna().values\n        \n        features['ave_roll_std_' + str(w)] = x_roll_std.mean()\n        features['std_roll_std_' + str(w)] = x_roll_std.std()\n        features['max_roll_std_' + str(w)] = x_roll_std.max()\n        features['min_roll_std_' + str(w)] = x_roll_std.min()\n        features['q01_roll_std_' + str(w)] = np.quantile(x_roll_std, 0.01)\n        features['q05_roll_std_' + str(w)] = np.quantile(x_roll_std, 0.05)\n        features['q10_roll_std_' + str(w)] = np.quantile(x_roll_std, 0.10)\n        features['q95_roll_std_' + str(w)] = np.quantile(x_roll_std, 0.95)\n        features['q99_roll_std_' + str(w)] = np.quantile(x_roll_std, 0.99)\n        \n        features['ave_roll_mean_' + str(w)] = x_roll_mean.mean()\n        features['std_roll_mean_' + str(w)] = x_roll_mean.std()\n        features['max_roll_mean_' + str(w)] = x_roll_mean.max()\n        features['min_roll_mean_' + str(w)] = x_roll_mean.min()\n        features['q05_roll_mean_' + str(w)] = np.quantile(x_roll_mean, 0.05)\n        features['q95_roll_mean_' + str(w)] = np.quantile(x_roll_mean, 0.95)\n        \n        features['ave_roll_abs_mean_' + str(w)] = x_roll_abs_mean.mean()\n        features['std_roll_abs_mean_' + str(w)] = x_roll_abs_mean.std()\n        features['q05_roll_abs_mean_' + str(w)] = np.quantile(x_roll_abs_mean, 0.05)\n        features['q95_roll_abs_mean_' + str(w)] = np.quantile(x_roll_abs_mean, 0.95)\n        \n        features['std_roll_min_' + str(w)] = x_roll_min.std()\n        features['max_roll_min_' + str(w)] = x_roll_min.max()\n        features['q05_roll_min_' + str(w)] = np.quantile(x_roll_min, 0.05)\n        features['q95_roll_min_' + str(w)] = np.quantile(x_roll_min, 0.95)\n\n        features['std_roll_max_' + str(w)] = x_roll_max.std()\n        features['min_roll_max_' + str(w)] = x_roll_max.min()\n        features['q05_roll_max_' + str(w)] = np.quantile(x_roll_max, 0.05)\n        features['q95_roll_max_' + str(w)] = np.quantile(x_roll_max, 0.95)\n    return features","metadata":{"_uuid":"1772c3a5aa5dbe3d672031eb64e6bac596206588","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Functions for extracting features and creating dataframes. Make train also returns one Series with the target variable and another one with the earthquake number for each segment. ","metadata":{}},{"cell_type":"code","source":"def make_train(train_data, size=150000, skip=150000):\n    num_segments = int(np.floor((train_data.shape[0] - size) / skip)) + 1\n    features_list = []\n    target_list = []\n    quake_num = []\n    quake_count = 0\n    \n    for index in tqdm_notebook(range(num_segments)):\n        seg = train_data.iloc[index*skip:index*skip + size]\n        \n        target_list.append(seg.time_to_failure.values[-1])\n        features_list.append(extract_features_from_segment(seg.acoustic_data))\n        \n        # From which quake does the segment come from\n        quake_num.append(quake_count)\n        if any(seg.time_to_failure.diff() > 5):\n            quake_count += 1\n    return pd.DataFrame(features_list), pd.Series(target_list), pd.Series(quake_num)\n\n\ndef make_test():\n    submission = pd.read_csv('../input/sample_submission.csv', index_col='seg_id')\n    X_test = pd.DataFrame(index=submission.index, dtype=np.float64)\n    features_list = []\n    \n    for seg_id in tqdm_notebook(submission.index):\n        seg = pd.read_csv('../input/test/' + seg_id + '.csv')\n        features_list.append(extract_features_from_segment(seg.acoustic_data))\n    return pd.DataFrame(features_list)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train, target, quake = make_train(train, skip=150000)\nprint(\"Train shape:\", X_train.shape)\nX_train.head(3)","metadata":{"_uuid":"ff26be4db60a010ddf7e81554456bbd19e068f96","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 3. Cross-validation strategy\n\nSince there are only four thousand samples, it is better to use cross-validation instead of a simple split for validation. This can be implemented trough a generator, so it is easy to change our strategy. I am using KFold, but you can try a [Group KFold](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GroupKFold.html) with the quake series or [Stratified KFold](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html) if you prefer.","metadata":{}},{"cell_type":"code","source":"def fold_generator(x, y, groups=None, num_folds=10, shuffle=True, seed=2019):\n    folds = KFold(num_folds, shuffle=shuffle, random_state=seed)\n    for train_index, test_index in folds.split(x, y, groups):\n        yield train_index, test_index","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 4. Feature Selection\n\nOne option for selecting features is the [Boruta package](https://www.jstatsoft.org/article/view/v036i11) with a Random Forests model.","metadata":{}},{"cell_type":"code","source":"rf = RandomForestRegressor(n_jobs=-1, max_depth=5)\n# define Boruta feature selection method\nselector = BorutaPy(rf, n_estimators='auto', verbose=0,\n                    random_state=1, max_iter=500)\n# find all relevant features\nselector.fit(X_train.values, target.values)\n# transform values\nX_filtered = selector.transform(X_train.values)\nX_train = pd.DataFrame(X_filtered, columns=X_train.columns[selector.support_])\nX_train.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 5. Making a Pipeline\n\nWe will be using a Sklearn Pipeline to perform hyperparameter search and to make predictions. The advantage of using a pipeline is that we are not leaking information from the training to the validation set.\n\nThe feature selection could also be moved to this pipeline, but it would take too long to perform the grid search.","metadata":{}},{"cell_type":"code","source":"def make_pipeline(estimator):\n    pipeline = Pipeline([\n        # Each item is a tuple with a name and a transformer or estimator\n        ('scaler', StandardScaler()),\n        ('model', estimator)\n    ])\n    return pipeline","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The next cell has two functions: one for searching the best hyperparameters and another for making predictions and ploting.","metadata":{"_uuid":"79675d6eca47c3039e729c937b1498eea8a54b60","trusted":true}},{"cell_type":"code","source":"def search_cv(x, y, pipeline, grid, max_iter=None, num_folds=10, shuffle=True):\n    \"\"\"Search hyperparameters and returns a estimator with the best combination found.\"\"\"\n    t0 = time.time()\n    \n    cv = fold_generator(x, y, num_folds=num_folds)\n    if max_iter is None:\n        search = GridSearchCV(pipeline, grid, cv=cv,\n                              scoring='neg_mean_absolute_error')\n    else:\n        search = RandomizedSearchCV(pipeline, grid, n_iter=max_iter, cv=cv,\n                                    scoring='neg_mean_absolute_error')\n    search.fit(x, y)\n    \n    t0 = time.time() - t0\n    print(\"Best CV score: {:.4f}, time: {:.1f}s\".format(-search.best_score_, t0))\n    print(search.best_params_)\n    return search.best_estimator_\n\n\ndef make_predictions(x, y, pipeline, num_folds=10, shuffle=True, test=None, plot=True):\n    \"\"\"Train, make predictions (oof and test data) and plot.\"\"\"\n    if test is not None:\n        sub_prediction = np.zeros(test.shape[0])\n        \n    oof_prediction = np.zeros(x.shape[0])\n    for tr_idx, val_idx in fold_generator(x, y, num_folds=num_folds):\n        pipeline.fit(x.iloc[tr_idx], y.iloc[tr_idx])\n        oof_prediction[val_idx] = pipeline.predict(x.iloc[val_idx])\n\n        if test is not None:\n            sub_prediction += pipeline.predict(test) / num_folds\n    \n    if plot:\n        plot_predictions(y, oof_prediction)\n    if test is None:\n        return oof_prediction\n    else:\n        return oof_prediction, sub_prediction","metadata":{"_uuid":"c2f28f7269af8bd5ba9fe1bec70da271bc93d3fe","_kg_hide-input":false,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_predictions(y, oof_predictions):\n    \"\"\"Plot out-of-fold predictions vs actual values.\"\"\"\n    fig, axis = plt.subplots(1, 2, figsize=(14, 6))\n    ax1, ax2 = axis\n    ax1.set_xlabel('actual')\n    ax1.set_ylabel('predicted')\n    ax1.set_ylim([-5, 20])\n    ax2.set_xlabel('train index')\n    ax2.set_ylabel('time to failure')\n    ax2.set_ylim([-2, 18])\n    ax1.scatter(y, oof_predictions, color='brown')\n    ax1.plot([(0, 0), (20, 20)], [(0, 0), (20, 20)], color='blue')\n    ax2.plot(y, color='blue', label='y_train')\n    ax2.plot(oof_predictions, color='orange')","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 6. Testing Models\n\nThe predicted values in the following plots are using a out-of-fold scheme.\n\n### Ridge Regression\n\nThe first model is a linear regression with L2 regularization.\n\n","metadata":{"_uuid":"d5255831db3f42ce8c31fcb18363ed7920f2d410"}},{"cell_type":"code","source":"grid = {'model__alpha': np.concatenate([np.linspace(0.001, 1, 200),\n                                        np.linspace(1, 100, 500)])}\n\n\nridge_pipe = make_pipeline(Ridge(random_state=2019))\nridge_pipe = search_cv(X_train, target, ridge_pipe, grid)\nridge_oof = make_predictions(X_train, target, ridge_pipe)","metadata":{"_uuid":"38dd94339081930175c7ca60ed983fa802bd0e0a","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There are some negative predictions when using a linear model. We can try to change negative values for zeros:","metadata":{"_uuid":"37fc4cff531998c5bfae84772795e8c01e607fbe"}},{"cell_type":"code","source":"ridge_oof[ridge_oof < 0] = 0\nprint(\"Mean error: {:.4f}\".format(mean_absolute_error(target, ridge_oof)))","metadata":{"_uuid":"97bbc24d95919791d48766abe1f23f11eea78597","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Kernel Ridge\n\nThis model combines regularized linear regression with a given kernel (radial basis in this case).","metadata":{"_uuid":"7c9f936d7866fea46f5d83401b0e1882291c4d63"}},{"cell_type":"code","source":"grid = {'model__gamma': np.linspace(1e-8, 1, 100),\n        'model__alpha': np.linspace(1e-6, 2, 200)}\nkr_pipe = make_pipeline(KernelRidge(kernel='rbf'))\nkr_pipe = search_cv(X_train, target, kr_pipe, grid, max_iter=60)\nkr_oof = make_predictions(X_train, target, kr_pipe)","metadata":{"_uuid":"f0b7e94dda285927518b226b716c2966e9def90f","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### SVM\nSupport vector machine with radial basis function kernel.","metadata":{"_uuid":"7738cdc47cc603b645b35aa8d931985888fa6d77"}},{"cell_type":"code","source":"grid = {'model__epsilon': np.linspace(0.001, 1, 100),\n        'model__C': np.linspace(0.01, 10, 100)}\nsvm_pipe = make_pipeline(SVR(kernel='rbf', gamma='scale'))\nsvm_pipe = search_cv(X_train, target, svm_pipe, grid, max_iter=60)\nsvm_oof = make_predictions(X_train, target, svm_pipe)","metadata":{"_uuid":"9bb9c43c3a5de20552fbb2a6c7d91e81ddb55e1e","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Random Forests\n\nThis regressor fits several decision trees with a different subset of the original data for each tree. Predictions are the average between trees.","metadata":{"_uuid":"7cc3b80df2183655437dfad7607ecd45acfd02f7"}},{"cell_type":"code","source":"grid = {\n    'model__max_depth': [4, 6, 8, 10, 12],\n    'model__max_features': ['auto', 'sqrt', 'log2'],\n    'model__min_samples_leaf': [2, 4, 8, 12, 14, 16, 20],\n    'model__min_samples_split': [2, 4, 6, 8, 12, 16, 20],\n}\nrf_pipe = make_pipeline(RandomForestRegressor(criterion='mae', n_estimators=40))\nrf_pipe = search_cv(X_train, target, rf_pipe, grid, max_iter=10)\nrf_oof = make_predictions(X_train, target, rf_pipe)","metadata":{"_uuid":"f293eae9eac71f20a299b44270010eaab1f94a60","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Ada Boost\n\nAdaBoost is a sequential ensemble model which begins by fitting a base estimator on the original dataset and then fits additional copies on the same dataset. At each iteration (estimator), the weights of instances are adjusted according to the error of the last prediction. It's similar to the next model, but gradient boosting fits additional estimators on the current pseudo-error and not on the original target.","metadata":{"_uuid":"44aa21f6b7a41bcb47aef88d83c7892614700dc4"}},{"cell_type":"code","source":"grid = {'model__learning_rate': np.linspace(1e-5, 0.9, 100)}\nbase = Ridge(alpha=2)\n\nada_pipe = make_pipeline(AdaBoostRegressor(base_estimator=base, n_estimators=200))\nada_pipe = search_cv(X_train, target, ada_pipe, grid)\nada_oof = make_predictions(X_train, target, ada_pipe)","metadata":{"_uuid":"146acb052cc6bf6bb448b2244b52ec8f92036ab2","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Gradient Boosting\n\nThe last model is a gradient boosting decision tree. It's not possible to use GridSearchCV with early stopping (lightgbm), so I am using a custom function for random search.","metadata":{"_uuid":"2fc9dc56cc1220a8a07f69ab52dad35af7800572"}},{"cell_type":"code","source":"fixed_params = {\n    'objective': 'regression_l1',\n    'boosting': 'gbdt',\n    'verbosity': -1,\n    'random_seed': 19,\n    'n_estimators': 80000,\n    'max_depth': -1\n}\n\nparam_grid = {\n    'learning_rate': [0.1, 0.05, 0.01, 0.005],\n    'num_leaves': list(range(2, 60, 2)),\n    'feature_fraction': [0.8, 0.85, 0.9, 0.95, 1],\n    'subsample': [0.8, 0.9, 0.95, 1],\n    'lambda_l1': [0, 0.2, 0.4, 0.6, 0.8, 2, 5],\n    'lambda_l2': [0, 0.2, 0.4, 0.6, 0.8, 2, 5],\n    'min_data_in_leaf': [10, 20, 40, 60, 100],\n    'min_gain_to_split': [0, 0.001, 0.01, 0.1],\n}\n\nbest_score = 999\ndataset = lgb.Dataset(X_train, label=target)  # no need to scale features\n\nfor i in range(600):\n    params = {k: random.choice(v) for k, v in param_grid.items()}\n    params.update(fixed_params)\n    result = lgb.cv(params, dataset, nfold=10, early_stopping_rounds=100,\n                    stratified=False)\n    \n    if result['l1-mean'][-1] < best_score:\n        best_score = result['l1-mean'][-1]\n        best_params = params\n        best_nrounds = len(result['l1-mean'])","metadata":{"_uuid":"e7e09e34f28daac2d4e7dca1f58f9882ef4f3e8a","_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Best mean score: {:.4f}, num rounds: {}\".format(best_score, best_nrounds))\nprint(best_params)\ngb_pipe = make_pipeline(lgb.LGBMRegressor(**best_params))\ngb_oof = make_predictions(X_train, target, gb_pipe)","metadata":{"_uuid":"08fc682c7ebff2403000a97cf23ea30cda98a65d","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now let's have a look at the <b>feature importance</b>:","metadata":{"_uuid":"1b837edfd77f9eff4b45f452495186d6a2cc11ad"}},{"cell_type":"code","source":"def plot_feature_importance(x, y, columns):\n    importance_frame = pd.DataFrame()\n    for (train_index, valid_index) in fold_generator(x, y):\n        reg = lgb.LGBMRegressor(**best_params)\n        reg.fit(x.iloc[train_index], y.iloc[train_index],\n                early_stopping_rounds=100, verbose=False,\n                eval_set=[(x.iloc[train_index], y.iloc[train_index]),\n                          (x.iloc[valid_index], y.iloc[valid_index])])\n        fold_importance = pd.DataFrame()\n        fold_importance[\"feature\"] = columns\n        fold_importance[\"gain\"] = reg.booster_.feature_importance(importance_type='gain')\n        #fold_importance[\"split\"] = reg.booster_.feature_importance(importance_type='split')\n        importance_frame = pd.concat([importance_frame, fold_importance], axis=0)\n        \n    mean_importance = importance_frame.groupby('feature').mean().reset_index()\n    mean_importance.sort_values(by='gain', ascending=True, inplace=True)\n    trace = go.Bar(y=mean_importance.feature, x=mean_importance.gain,\n                   orientation='h', marker=dict(color='rgb(49,130,189)'))\n\n    layout = go.Layout(\n        title='Feature importance', height=800, width=600,\n        showlegend=False,\n        xaxis=dict(\n            title='Importance by gain',\n            titlefont=dict(size=14, color='rgb(107, 107, 107)'),\n            domain=[0.15, 1]\n        ),\n    )\n\n    fig = go.Figure(data=[trace], layout=layout)\n    iplot(fig)\n    \nplot_feature_importance(X_train, target, X_train.columns)","metadata":{"_kg_hide-input":true,"_uuid":"36c5fa96d7d353285006f7aef366d36eedc41be1","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 7. Submission","metadata":{"_uuid":"7bf1e7fc30de8ba4e4a609af578f562418f5ed4b"}},{"cell_type":"code","source":"X_test = make_test()\nX_test = X_test[X_train.columns]  # feature selection\ngb_oof, gb_sub = make_predictions(X_train, target, gb_pipe,\n                                  test=X_test, plot=False)\nsubmission = pd.read_csv('../input/sample_submission.csv')\nsubmission['time_to_failure'] = gb_sub\nsubmission.to_csv('submission_gb.csv', index=False)\nsubmission.time_to_failure.describe()","metadata":{"_uuid":"eca824c317d8aa9f14aba2ed04f6d08e783c1827","trusted":true},"execution_count":null,"outputs":[]}]}