{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":3004,"databundleVersionId":861823}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Ensemble Methods for Classification","metadata":{}},{"cell_type":"code","source":"import pathlib\n\nimport joblib\nimport numpy as np\nimport pandas as pd\nfrom sklearn import calibration, decomposition, ensemble, feature_selection\nfrom sklearn import impute, linear_model, metrics, model_selection, neighbors\nfrom sklearn import pipeline, preprocessing\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Exercise 1: Load the data\n\nUse Pandas to load the training and testing data.","metadata":{}},{"cell_type":"code","source":"%%bash\n\nls /kaggle/input/digit-recognizer","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-01T13:22:36.039940Z","iopub.execute_input":"2026-04-01T13:22:36.040318Z","iopub.status.idle":"2026-04-01T13:22:36.062341Z","shell.execute_reply.started":"2026-04-01T13:22:36.040278Z","shell.execute_reply":"2026-04-01T13:22:36.061710Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Solution","metadata":{}},{"cell_type":"code","source":"TRAIN_DF = pd.read_csv(\n    \"/kaggle/input/digit-recognizer/train.csv\"\n)\n\nTEST_FEATURES_DF = pd.read_csv(\n    \"/kaggle/input/digit-recognizer/test.csv\"\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T07:00:34.176676Z","iopub.execute_input":"2026-04-02T07:00:34.177224Z","iopub.status.idle":"2026-04-02T07:00:39.668746Z","shell.execute_reply.started":"2026-04-02T07:00:34.177172Z","shell.execute_reply":"2026-04-02T07:00:39.667294Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Exercise 2: Train/Val Split\n\nSplit the data into a training set and a validation set. The size of the validation set should be half the size of the test set. Make sure to use stratified sampling so that the distribution of classes is the same in both the training and validation sets.","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Solution","metadata":{}},{"cell_type":"code","source":"PRNG = np.random.RandomState(42)\nn_test_samples, _ = TEST_FEATURES_DF.shape\nn_val_samples = n_test_samples // 2\n_stratify = TRAIN_DF.loc[:, \"label\"]\n\ntrain_df, val_df = model_selection.train_test_split(\n    TRAIN_DF,\n    random_state=PRNG,\n    test_size=n_val_samples,\n    stratify=_stratify,\n)\n\ntrain_features_df = train_df.drop(\"label\", axis=1)\ntrain_label = train_df.loc[:, \"label\"]\n\nval_features_df = val_df.drop(\"label\", axis=1)\nval_label = val_df.loc[:, \"label\"]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T07:00:39.670627Z","iopub.execute_input":"2026-04-02T07:00:39.671282Z","iopub.status.idle":"2026-04-02T07:00:41.152287Z","shell.execute_reply.started":"2026-04-02T07:00:39.671221Z","shell.execute_reply":"2026-04-02T07:00:41.151168Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Exercise 3: Train different classifiers\n\nTrain the following classifiers.\n\n1. [ensemble.ExtraTreesClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesClassifier.html)\n2. [ensemble.HistGradientBoostingClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.HistGradientBoostingClassifier.html)\n3. [neighbors.KNeighborsClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html)\n4. [linear_model.SGDClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDClassifier.html)\n","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Solution","metadata":{}},{"cell_type":"code","source":"NAMED_CLASSIFIERS = {\n    \"extratreesclassifier\": pipeline.make_pipeline(\n        feature_selection.VarianceThreshold(\n            threshold=0.0\n        ),\n        ensemble.ExtraTreesClassifier(\n            n_estimators=100,\n            criterion=\"gini\",\n            max_depth=None,\n            min_samples_split=2,\n            min_samples_leaf=1,\n            min_weight_fraction_leaf=0.0,\n            max_features=\"sqrt\",\n            max_leaf_nodes=None,\n            min_impurity_decrease=0.0,\n            bootstrap=True,               # sample rows with replacement\n            oob_score=False,\n            n_jobs=-1,\n            random_state=PRNG,\n            verbose=0,\n            warm_start=False,\n            class_weight=\"balanced\",      # give more weight to less frequent classes\n            ccp_alpha=0.0,\n            max_samples=None,\n            monotonic_cst=None,\n        ),\n    ),\n    \"histgradientboostingclassifier\": pipeline.make_pipeline(\n        feature_selection.VarianceThreshold(\n            threshold=0.0\n        ),\n        ensemble.HistGradientBoostingClassifier(\n            loss=\"log_loss\",\n            learning_rate=0.1,\n            max_iter=100,\n            max_leaf_nodes=31,\n            max_depth=None,\n            min_samples_leaf=20,\n            l2_regularization=0.0,\n            max_features=1.0,\n            max_bins=255,\n            categorical_features=\"from_dtype\",\n            monotonic_cst=None,\n            interaction_cst=None,\n            warm_start=False,\n            early_stopping=True,                # enable early stopping\n            scoring=\"loss\",\n            validation_fraction=0.1,\n            n_iter_no_change=10,\n            tol=1e-07,\n            verbose=0,\n            random_state=PRNG,\n            class_weight=\"balanced\",            # give more weight to less frequent classes\n        ), \n    ),\n    \"kneighborsclassifier\": pipeline.make_pipeline(\n        impute.SimpleImputer(\n            missing_values=np.nan,\n            strategy=\"most_frequent\",\n            fill_value=None,\n            copy=True,\n            add_indicator=False,\n            keep_empty_features=False,\n        ),\n        feature_selection.VarianceThreshold(\n            threshold=0.0\n        ),\n        neighbors.KNeighborsClassifier(\n            n_neighbors=5,\n            weights=\"distance\",  # give more weight to nearer neighbors\n            algorithm=\"auto\",\n            leaf_size=30,\n            p=2,\n            metric=\"minkowski\",\n            metric_params=None,\n            n_jobs=-1,\n        ),\n    ),\n    \"sgdclassifier\": pipeline.make_pipeline(\n        impute.SimpleImputer(\n            missing_values=np.nan,\n            strategy=\"most_frequent\",\n            fill_value=None,\n            copy=True,\n            add_indicator=False,\n            keep_empty_features=False,\n        ),\n        feature_selection.VarianceThreshold(\n            threshold=0.0\n        ),\n        preprocessing.StandardScaler(\n            copy=True,\n            with_mean=True,\n            with_std=True\n        ),\n        linear_model.SGDClassifier(\n            loss=\"hinge\",\n            penalty=\"l2\",\n            alpha=0.0001,\n            l1_ratio=0.15,\n            fit_intercept=True,\n            max_iter=10000,           # increase to insure convergence!\n            tol=0.001,\n            shuffle=True,\n            verbose=0,\n            epsilon=0.1,\n            n_jobs=-1,\n            random_state=PRNG,\n            learning_rate=\"optimal\",\n            eta0=0.0,\n            power_t=0.5,\n            early_stopping=True,      # enable early stopping\n            validation_fraction=0.1,\n            n_iter_no_change=5,\n            class_weight=\"balanced\",  # give more weight to less frequent classes\n            warm_start=False,\n            average=False,\n        ),\n    ),\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T08:59:18.479175Z","iopub.execute_input":"2026-04-02T08:59:18.479564Z","iopub.status.idle":"2026-04-02T08:59:18.497236Z","shell.execute_reply.started":"2026-04-02T08:59:18.479533Z","shell.execute_reply":"2026-04-02T08:59:18.496103Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for (name, classifier) in NAMED_CLASSIFIERS.items():\n\n    model_filepath = pathlib.Path(f\"/kaggle/working/{name}.pkl\")\n\n    if not model_filepath.exists():\n\n        _ = classifier.fit(\n            train_features_df,\n            train_label\n        )\n\n        _ = joblib.dump(\n            classifier,\n            model_filepath\n        )\n        print(f\"Done training {name}!\")\n\n    NAMED_CLASSIFIERS[name] = joblib.load(model_filepath)\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T08:59:33.243434Z","iopub.execute_input":"2026-04-02T08:59:33.243840Z","iopub.status.idle":"2026-04-02T09:02:26.689839Z","shell.execute_reply.started":"2026-04-02T08:59:33.243803Z","shell.execute_reply":"2026-04-02T09:02:26.688969Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Exercise 4: Assess classifier performance\n\nEvaluate the performance of each classifier using the training and validation data. Which classifier peforms the best? Which classifier has the largest generalization gap?","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T08:10:32.374458Z","iopub.execute_input":"2026-04-02T08:10:32.374807Z","iopub.status.idle":"2026-04-02T08:10:32.934103Z","shell.execute_reply.started":"2026-04-02T08:10:32.374758Z","shell.execute_reply":"2026-04-02T08:10:32.932981Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Solution","metadata":{}},{"cell_type":"code","source":"for (name, classifier) in NAMED_CLASSIFIERS.items():\n    \n    _train_predictions = classifier.predict(train_features_df)\n    _report = metrics.classification_report(\n        train_label,\n        _train_predictions\n    )\n\n    print(f\"{name} training report:\")\n    print(_report)\n    print(\"\\n\")\n\n    _val_predictions = classifier.predict(val_features_df)\n    _report = metrics.classification_report(\n        val_label,\n        _val_predictions\n    )\n    \n    print(f\"{name} validation report:\")\n    print(_report)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T09:03:37.994755Z","iopub.execute_input":"2026-04-02T09:03:37.995193Z","iopub.status.idle":"2026-04-02T09:04:44.564393Z","shell.execute_reply.started":"2026-04-02T09:03:37.995157Z","shell.execute_reply":"2026-04-02T09:04:44.563119Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Exercise 5: Model tuning\n\nFine tune the hyper-parameters of each model using either [GridSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html) or [RandomizedSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html). ","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Solution","metadata":{}},{"cell_type":"code","source":"# TODO!","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Exercise 6: Hard voting ensemble\n\nCombine the fine-tuned classifiers from the previous exercise into a [ensemble.VotingClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.VotingClassifier.html) using hard voting. Does the hard-voting ensemble outperform the best individual classifier? If so, how much better does it perform compared to the best individual classifier?","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Solution","metadata":{}},{"cell_type":"code","source":"_checkpoint = \"/kaggle/working/hard-voting-classifier.pkl\"\nmodel_filepath = pathlib.Path(_checkpoint)\n\nif not model_filepath.exists():\n    \n    _estimators = list(NAMED_CLASSIFIERS.items())\n\n    _classifier = ensemble.VotingClassifier(\n        estimators=_estimators,\n        voting=\"hard\",\n        weights=None,\n        n_jobs=1,\n        flatten_transform=True,\n        verbose=True,\n    )\n\n    _ = _classifier.fit(\n        train_features_df,\n        train_label\n    )\n    \n    _ = joblib.dump(\n        _classifier,\n        model_filepath\n    )\n\nhard_voting_classifier = joblib.load(model_filepath)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T09:15:03.110859Z","iopub.execute_input":"2026-04-02T09:15:03.111320Z","iopub.status.idle":"2026-04-02T09:17:57.761285Z","shell.execute_reply.started":"2026-04-02T09:15:03.111281Z","shell.execute_reply":"2026-04-02T09:17:57.760250Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"_train_predictions = hard_voting_classifier.predict(train_features_df)\n_report = metrics.classification_report(\n    train_label,\n    _train_predictions\n)\n\nprint(f\"Hard-voting ensemble training report:\")\nprint(_report)\nprint(\"\\n\")\n\n_val_predictions = hard_voting_classifier.predict(val_features_df)\n_report = metrics.classification_report(\n    val_label,\n    _val_predictions\n)\n\nprint(f\"Hard-voting ensemble validation report:\")\nprint(_report)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T09:17:57.762878Z","iopub.execute_input":"2026-04-02T09:17:57.763283Z","iopub.status.idle":"2026-04-02T09:19:06.470536Z","shell.execute_reply.started":"2026-04-02T09:17:57.763252Z","shell.execute_reply":"2026-04-02T09:19:06.469590Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Exercise 7: Soft voting ensemble\n\nCombine the fine-tuned classifiers above into a [ensemble.VotingClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.VotingClassifier.html) using soft voting. Remember that you will need to use [calibration.CalibratedClassifierCV](https://scikit-learn.org/stable/modules/generated/sklearn.calibration.CalibratedClassifierCV.html) to wrap the various classifiers in order to insure that any predicted probabilities are well calibrated to observed frequencies.\n\nEvaluate the soft-voting classifier using the [metrics.log_loss](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.log_loss.html) using both the training and the validation data.","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Solution","metadata":{}},{"cell_type":"code","source":"NAMED_CLASSIFIERS = {\n    \"extratreesclassifier\": pipeline.make_pipeline(\n        feature_selection.VarianceThreshold(\n            threshold=0.0\n        ),\n        calibration.CalibratedClassifierCV(\n            ensemble.ExtraTreesClassifier(\n                n_estimators=100,\n                criterion=\"gini\",\n                max_depth=None,\n                min_samples_split=2,\n                min_samples_leaf=1,\n                min_weight_fraction_leaf=0.0,\n                max_features=\"sqrt\",\n                max_leaf_nodes=None,\n                min_impurity_decrease=0.0,\n                bootstrap=True,               # sample rows with replacement\n                oob_score=False,\n                n_jobs=1,                     # avoid nested parallelism!\n                random_state=PRNG,\n                verbose=0,\n                warm_start=False,\n                class_weight=\"balanced\",      # give more weight to less frequent classes\n                ccp_alpha=0.0,\n                max_samples=None,\n                monotonic_cst=None,\n            ),\n            method=\"sigmoid\",\n            cv=5,\n            n_jobs=-1,\n            ensemble=\"auto\",\n        )\n    ),\n    \"histgradientboostingclassifier\": pipeline.make_pipeline(\n        feature_selection.VarianceThreshold(\n            threshold=0.0\n        ),\n        calibration.CalibratedClassifierCV(\n            ensemble.HistGradientBoostingClassifier(\n                loss=\"log_loss\",\n                learning_rate=0.1,\n                max_iter=100,\n                max_leaf_nodes=31,\n                max_depth=None,\n                min_samples_leaf=20,\n                l2_regularization=0.0,\n                max_features=1.0,\n                max_bins=255,\n                categorical_features=\"from_dtype\",\n                monotonic_cst=None,\n                interaction_cst=None,\n                warm_start=False,\n                early_stopping=True,                # enable early stopping\n                scoring=\"loss\",\n                validation_fraction=0.1,\n                n_iter_no_change=10,\n                tol=1e-07,\n                verbose=0,\n                random_state=PRNG,\n                class_weight=\"balanced\",            # give more weight to less frequent classes\n            ),\n            method=\"sigmoid\",\n            cv=5,\n            n_jobs=-1,\n            ensemble=\"auto\",\n        )   \n    ),\n    \"kneighborsclassifier\": pipeline.make_pipeline(\n        impute.SimpleImputer(\n            missing_values=np.nan,\n            strategy=\"most_frequent\",\n            fill_value=None,\n            copy=True,\n            add_indicator=False,\n            keep_empty_features=False,\n        ),\n        feature_selection.VarianceThreshold(\n            threshold=0.0\n        ),\n        calibration.CalibratedClassifierCV(\n            neighbors.KNeighborsClassifier(\n                n_neighbors=5,\n                weights=\"distance\",  # give more weight to nearer neighbors\n                algorithm=\"auto\",\n                leaf_size=30,\n                p=2,\n                metric=\"minkowski\",\n                metric_params=None,\n                n_jobs=1,            # avoid nested parallelism!\n            ),\n            method=\"sigmoid\",\n            cv=5,\n            n_jobs=-1,\n            ensemble=\"auto\",\n        )\n    ),\n    \"sgdclassifier\": pipeline.make_pipeline(\n        impute.SimpleImputer(\n            missing_values=np.nan,\n            strategy=\"most_frequent\",\n            fill_value=None,\n            copy=True,\n            add_indicator=False,\n            keep_empty_features=False,\n        ),\n        feature_selection.VarianceThreshold(\n            threshold=0.0\n        ),\n        preprocessing.StandardScaler(\n            copy=True,\n            with_mean=True,\n            with_std=True\n        ),\n        calibration.CalibratedClassifierCV(\n            linear_model.SGDClassifier(\n                loss=\"hinge\",\n                penalty=\"l2\",\n                alpha=0.0001,\n                l1_ratio=0.15,\n                fit_intercept=True,\n                max_iter=10000,           # increase to insure convergence!\n                tol=0.001,\n                shuffle=True,\n                verbose=0,\n                epsilon=0.1,\n                n_jobs=1,                 # avoid nested parallelism!\n                random_state=PRNG,\n                learning_rate=\"optimal\",\n                eta0=0.0,\n                power_t=0.5,\n                early_stopping=True,      # enable early stopping\n                validation_fraction=0.1,\n                n_iter_no_change=5,\n                class_weight=\"balanced\",  # give more weight to less frequent classes\n                warm_start=False,\n                average=False,\n            ),\n            method=\"sigmoid\",\n            cv=5,\n            n_jobs=-1,\n            ensemble=\"auto\",\n        )\n    ),\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T09:21:09.626284Z","iopub.execute_input":"2026-04-02T09:21:09.626753Z","iopub.status.idle":"2026-04-02T09:21:09.643617Z","shell.execute_reply.started":"2026-04-02T09:21:09.626716Z","shell.execute_reply":"2026-04-02T09:21:09.642318Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"_checkpoint = \"/kaggle/working/soft-voting-classifier.pkl\"\nmodel_filepath = pathlib.Path(_checkpoint)\n\nif not model_filepath.exists():\n    \n    _estimators = list(NAMED_CLASSIFIERS.items())\n\n    _classifier = ensemble.VotingClassifier(\n        estimators=_estimators,\n        voting=\"soft\",\n        weights=None,\n        n_jobs=1,\n        flatten_transform=True,\n        verbose=True,\n    )\n\n    _ = _classifier.fit(\n        train_features_df,\n        train_label\n    )\n    \n    _ = joblib.dump(\n        _classifier,\n        model_filepath\n    )\n\nsoft_voting_classifier = joblib.load(model_filepath)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T09:21:26.064110Z","iopub.execute_input":"2026-04-02T09:21:26.064464Z","iopub.status.idle":"2026-04-02T09:32:41.769421Z","shell.execute_reply.started":"2026-04-02T09:21:26.064434Z","shell.execute_reply":"2026-04-02T09:32:41.768195Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"_train_predictions = soft_voting_classifier.predict_proba(train_features_df)\n_score = metrics.log_loss(\n    train_label,\n    _train_predictions\n)\n\nprint(f\"Soft-voting Ensemble Training Log Loss:\")\nprint(_score)\nprint(\"\\n\")\n\n_val_predictions = soft_voting_classifier.predict_proba(val_features_df)\n_score = metrics.log_loss(\n    val_label,\n    _val_predictions\n)\n\nprint(f\"Soft-voting Ensemble Validation Log Loss:\")\nprint(_score)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T09:46:58.736271Z","iopub.execute_input":"2026-04-02T09:46:58.736723Z","iopub.status.idle":"2026-04-02T09:50:20.728397Z","shell.execute_reply.started":"2026-04-02T09:46:58.736689Z","shell.execute_reply":"2026-04-02T09:50:20.727338Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Exercise 8: Submit predictions\n\nLoad the sample submission file and replace the `Label` column with your best classifier predictions on the test set.","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Solution","metadata":{}},{"cell_type":"code","source":"best_classifier = hard_voting_classifier\n_test_predictions = best_classifier.predict(TEST_FEATURES_DF)\n\n_ = (\n    pd.read_csv(\n        \"/kaggle/input/digit-recognizer/sample_submission.csv\",\n        index_col=\"ImageId\"\n    ).assign(\n        Label=_test_predictions\n    ).to_csv(\n        \"submission.csv\",\n        index=True\n    )\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T09:53:32.455359Z","iopub.execute_input":"2026-04-02T09:53:32.456580Z","iopub.status.idle":"2026-04-02T09:54:11.136394Z","shell.execute_reply.started":"2026-04-02T09:53:32.456524Z","shell.execute_reply":"2026-04-02T09:54:11.135314Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%bash\n\ncat submission.csv | head -n 5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-02T09:54:11.138215Z","iopub.execute_input":"2026-04-02T09:54:11.139390Z","iopub.status.idle":"2026-04-02T09:54:11.163206Z","shell.execute_reply.started":"2026-04-02T09:54:11.139346Z","shell.execute_reply":"2026-04-02T09:54:11.162073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}