{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Statsmodels Methods Overview" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2025-06-27T05:20:48.158445Z", "iopub.status.busy": "2025-06-27T05:20:48.158267Z", "iopub.status.idle": "2025-06-27T05:20:48.164422Z", "shell.execute_reply": "2025-06-27T05:20:48.163933Z" } }, "outputs": [], "source": [ "# This cells setups the environment when executed in Google Colab.\n", "try:\n", " import google.colab\n", " !curl -s https://raw.githubusercontent.com/ibs-lab/cedalion/dev/scripts/colab_setup.py -o colab_setup.py\n", " # Select branch with --branch \"branch name\" (default is \"dev\")\n", " %run colab_setup.py\n", "except ImportError:\n", " pass" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2025-06-27T05:20:48.166179Z", "iopub.status.busy": "2025-06-27T05:20:48.165864Z", "iopub.status.idle": "2025-06-27T05:20:50.440251Z", "shell.execute_reply": "2025-06-27T05:20:50.439774Z" }, "nbsphinx": "hidden" }, "outputs": [], "source": [ "# this cell won't be rendered in the sphinx documentation to keep the page short\n", "\n", "import matplotlib.pyplot as p\n", "import numpy as np\n", "import pandas as pd\n", "import xarray as xr\n", "\n", "import cedalion\n", "import cedalion.datasets\n", "import cedalion.io\n", "import cedalion.models.glm as glm\n", "import cedalion.nirs\n", "import cedalion.plots as plots\n", "import cedalion.sigproc.frequency\n", "from cedalion import units\n", "\n", "xr.set_options(display_expand_data=False);\n", "\n", "rec = cedalion.datasets.get_fingertapping()\n", "\n", "# rename trials\n", "rec.stim.cd.rename_events(\n", " {\n", " \"1.0\": \"control\",\n", " \"2.0\": \"Tapping/Left\",\n", " \"3.0\": \"Tapping/Right\",\n", " \"15.0\": \"sentinel\",\n", " }\n", ")\n", "rec.stim = rec.stim[rec.stim.trial_type != \"sentinel\"]\n", "\n", "# differential pathlenght factors\n", "dpf = xr.DataArray(\n", " [6, 6],\n", " dims=\"wavelength\",\n", " coords={\"wavelength\": rec[\"amp\"].wavelength},\n", ")\n", "\n", "# calculate optical density and concentrations\n", "rec[\"od\"] = cedalion.nirs.int2od(rec[\"amp\"])\n", "rec[\"conc\"] = cedalion.nirs.od2conc(rec[\"od\"], rec.geo3d, dpf, spectrum=\"prahl\")\n", "\n", "# Bandpass filter remove cardiac component and slow drifts.\n", "# Here we use a highpass to remove drift. Another possible option would be to\n", "# use drift regressors in the design matrix.\n", "fmin = 0.02 * units.Hz\n", "fmax = 0 * units.Hz\n", "\n", "rec[\"conc_filtered\"] = cedalion.sigproc.frequency.freq_filter(rec[\"conc\"], fmin, fmax)\n", "\n", "\n", "ts = rec[\"conc_filtered\"]\n", "\n", "# split time series into two based on channel distance\n", "ts_long, ts_short = cedalion.nirs.split_long_short_channels(\n", " rec[\"conc_filtered\"], rec.geo3d, distance_threshold=1.5 * units.cm\n", ")\n", "\n", "dms = (\n", " glm.design_matrix.hrf_regressors(\n", " ts_long, rec.stim, glm.Gamma(tau=0 * units.s, sigma=3 * units.s)\n", " )\n", " & glm.design_matrix.closest_short_channel_regressor(ts_long, ts_short, rec.geo3d)\n", ")\n", "dms.channel_wise[0] = dms.channel_wise[0].pint.dequantify()\n", "dms.channel_wise[0] /= dms.channel_wise[0].max(\"time\")\n", "\n", "dm = dms.common\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cedalion uses statsmodels for its GLM fitting functionality, and this notebook gives an overview of some common statsmodels methods. The glm.fit function returns an xr.DataArray of statsmodels RegressionResults objects with dimensions (channel, chromo). Any RegressionResults method can be called on this DataArray using the .sm accessor. A full list of available methods and attribute can be found in the [statsmodels documentation](https://www.statsmodels.org/dev/generated/statsmodels.regression.linear_model.RegressionResults.html).\n", "\n", "In this notebook, we'll assume that we have already loaded our data and set up the GLM. See the other GLM notebooks for details on setup.\n", "\n", "We'll start by fitting the GLM and displaying the resulting object." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2025-06-27T05:20:50.442210Z", "iopub.status.busy": "2025-06-27T05:20:50.442045Z", "iopub.status.idle": "2025-06-27T05:20:50.871568Z", "shell.execute_reply": "2025-06-27T05:20:50.871041Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\r", " 0%| | 0/3 [00:00\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2)> Size: 320B\n",
       "<statsmodels.regression.linear_model.RegressionResultsWrapper object at 0x7f5...\n",
       "Coordinates:\n",
       "  * chromo    (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel   (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source    (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector  (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 320B\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, regressor: 4)> Size: 1kB\n",
       "-0.001429 0.1604 0.2017 0.7729 -0.003184 ... -0.01349 -0.01729 -0.01956 -0.03845\n",
       "Coordinates:\n",
       "  * regressor  (regressor) object 32B 'HRF control' ... 'short'\n",
       "  * chromo     (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel    (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source     (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector   (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 1kB\n", "-0.001429 0.1604 0.2017 0.7729 -0.003184 ... -0.01349 -0.01729 -0.01956 -0.03845\n", "Coordinates:\n", " * regressor (regressor) object 32B 'HRF control' ... 'short'\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, regressor: 4)> Size: 1kB\n",
       "0.004972 0.005014 0.005029 0.005514 0.0024 ... 0.00284 0.00284 0.002842 0.006336\n",
       "Coordinates:\n",
       "  * regressor  (regressor) object 32B 'HRF control' ... 'short'\n",
       "  * chromo     (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel    (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source     (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector   (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 1kB\n", "0.004972 0.005014 0.005029 0.005514 0.0024 ... 0.00284 0.00284 0.002842 0.006336\n", "Coordinates:\n", " * regressor (regressor) object 32B 'HRF control' ... 'short'\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, regressor: 4, conf_int: 2)> Size: 3kB\n",
       "-0.01117 0.008315 0.1505 0.1702 0.1918 ... -0.02513 -0.01399 -0.05087 -0.02603\n",
       "Coordinates:\n",
       "  * regressor  (regressor) object 32B 'HRF control' ... 'short'\n",
       "  * conf_int   (conf_int) int64 16B 0 1\n",
       "  * chromo     (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel    (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source     (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector   (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 3kB\n", "-0.01117 0.008315 0.1505 0.1702 0.1918 ... -0.02513 -0.01399 -0.05087 -0.02603\n", "Coordinates:\n", " * regressor (regressor) object 32B 'HRF control' ... 'short'\n", " * conf_int (conf_int) int64 16B 0 1\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, regressor_r: 4, regressor_c: 4)> Size: 5kB\n",
       "2.472e-05 -4.509e-08 -5.209e-08 3.787e-07 ... 2.327e-07 7.423e-07 4.014e-05\n",
       "Coordinates:\n",
       "  * regressor_r  (regressor_r) object 32B 'HRF control' ... 'short'\n",
       "  * regressor_c  (regressor_c) object 32B 'HRF control' ... 'short'\n",
       "  * chromo       (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel      (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source       (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S8' 'S8'\n",
       "    detector     (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D7' 'D7' 'D8'\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 5kB\n", "2.472e-05 -4.509e-08 -5.209e-08 3.787e-07 ... 2.327e-07 7.423e-07 4.014e-05\n", "Coordinates:\n", " * regressor_r (regressor_r) object 32B 'HRF control' ... 'short'\n", " * regressor_c (regressor_c) object 32B 'HRF control' ... 'short'\n", " * chromo (chromo) " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "p.imshow(results.sm.cov_params()[0,0,:,:]);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The convenience function `sm.regressor_variances` computes the variances of the regressors, i.e. the diagonal elements of the covariance matrices." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2025-06-27T05:20:51.206206Z", "iopub.status.busy": "2025-06-27T05:20:51.206033Z", "iopub.status.idle": "2025-06-27T05:20:51.254212Z", "shell.execute_reply": "2025-06-27T05:20:51.253699Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, regressor: 4)> Size: 1kB\n",
       "2.472e-05 2.514e-05 2.529e-05 3.04e-05 ... 8.064e-06 8.076e-06 4.014e-05\n",
       "Coordinates:\n",
       "  * regressor  (regressor) object 32B 'HRF control' ... 'short'\n",
       "  * chromo     (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel    (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source     (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector   (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 1kB\n", "2.472e-05 2.514e-05 2.529e-05 3.04e-05 ... 8.064e-06 8.076e-06 4.014e-05\n", "Coordinates:\n", " * regressor (regressor) object 32B 'HRF control' ... 'short'\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, regressor: 4)> Size: 1kB\n",
       "-0.2875 31.98 40.1 140.2 -1.327 -17.83 ... 97.04 -4.749 -6.089 -6.882 -6.069\n",
       "Coordinates:\n",
       "  * regressor  (regressor) object 32B 'HRF control' ... 'short'\n",
       "  * chromo     (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel    (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source     (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector   (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 1kB\n", "-0.2875 31.98 40.1 140.2 -1.327 -17.83 ... 97.04 -4.749 -6.089 -6.882 -6.069\n", "Coordinates:\n", " * regressor (regressor) object 32B 'HRF control' ... 'short'\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2)> Size: 320B\n",
       "                             Test for Constraints                            ...\n",
       "Coordinates:\n",
       "  * chromo    (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel   (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source    (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector  (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 320B\n", " Test for Constraints ...\n", "Coordinates:\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, hypothesis: 2)> Size: 640B\n",
       "22.89 28.69 -11.67 -14.53 18.91 20.18 ... -12.03 -3.51 11.8 9.7 -0.947 -1.511\n",
       "Coordinates:\n",
       "  * chromo    (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel   (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source    (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector  (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Dimensions without coordinates: hypothesis\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 640B\n", "22.89 28.69 -11.67 -14.53 18.91 20.18 ... -12.03 -3.51 11.8 9.7 -0.947 -1.511\n", "Coordinates:\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, hypothesis: 2)> Size: 640B\n",
       "22.89 28.69 -11.67 -14.53 18.91 20.18 ... -12.03 -3.51 11.8 9.7 -0.947 -1.511\n",
       "Coordinates:\n",
       "  * chromo    (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel   (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source    (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector  (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Dimensions without coordinates: hypothesis\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 640B\n", "22.89 28.69 -11.67 -14.53 18.91 20.18 ... -12.03 -3.51 11.8 9.7 -0.947 -1.511\n", "Coordinates:\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, hypothesis: 2)> Size: 640B\n",
       "9.929e-115 6.125e-178 2.164e-31 1.18e-47 ... 5.073e-32 3.313e-22 0.3436 0.1307\n",
       "Coordinates:\n",
       "  * chromo    (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel   (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source    (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector  (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Dimensions without coordinates: hypothesis\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 640B\n", "9.929e-115 6.125e-178 2.164e-31 1.18e-47 ... 5.073e-32 3.313e-22 0.3436 0.1307\n", "Coordinates:\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (channel: 20, chromo: 2, regressor: 4, sample: 100)> Size: 128kB\n",
       "-0.004786 -0.001207 -0.006483 0.002115 ... -0.054 -0.04183 -0.03155 -0.04055\n",
       "Coordinates:\n",
       "  * regressor  (regressor) object 32B 'HRF control' ... 'short'\n",
       "  * chromo     (chromo) <U3 24B 'HbO' 'HbR'\n",
       "  * channel    (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    source     (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8'\n",
       "    detector   (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8'\n",
       "Dimensions without coordinates: sample\n",
       "Attributes:\n",
       "    description:  OLS model via statsmodels.regression
" ], "text/plain": [ " Size: 128kB\n", "-0.004786 -0.001207 -0.006483 0.002115 ... -0.054 -0.04183 -0.03155 -0.04055\n", "Coordinates:\n", " * regressor (regressor) object 32B 'HRF control' ... 'short'\n", " * chromo (chromo) \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (time: 23239, channel: 20, chromo: 2, sample: 100)> Size: 744MB\n",
       "-0.09512 -0.09486 -0.09518 -0.09504 ... 0.0001133 8.782e-05 6.622e-05 8.512e-05\n",
       "Coordinates:\n",
       "  * time           (time) float64 186kB 0.0 0.128 0.256 ... 2.974e+03 2.974e+03\n",
       "  * chromo         (chromo) <U3 24B 'HbO' 'HbR'\n",
       "    samples        (time) int64 186kB 0 1 2 3 4 ... 23235 23236 23237 23238\n",
       "  * channel        (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8'\n",
       "    short_channel  (channel) object 160B 'S1D9' 'S1D9' ... 'S8D16' 'S8D16'\n",
       "    comp_group     (channel) int64 160B 0 0 0 1 1 1 2 2 3 3 4 4 4 5 5 5 6 6 7 7\n",
       "    source         (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S8' 'S8'\n",
       "    detector       (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D7' 'D7' 'D8'\n",
       "Dimensions without coordinates: sample
" ], "text/plain": [ " Size: 744MB\n", "-0.09512 -0.09486 -0.09518 -0.09504 ... 0.0001133 8.782e-05 6.622e-05 8.512e-05\n", "Coordinates:\n", " * time (time) float64 186kB 0.0 0.128 0.256 ... 2.974e+03 2.974e+03\n", " * chromo (chromo) " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Plot a band between mean-3*std and mean+3*std\n", "# We select a 20 second window for better visualization\n", "pred_mean = pred.mean(\"sample\")\n", "pred_std = pred.std(\"sample\")\n", "\n", "mm = pred_mean.loc[slice(60,80), \"S5D5\", \"HbO\"]\n", "ss = pred_std.loc[slice(60,80), \"S5D5\", \"HbO\"]\n", "\n", "p.plot(mm.time, mm, c=\"r\")\n", "p.fill_between(mm.time, mm-3*ss, mm+3*ss, fc=\"y\", alpha=.8)\n", "\n", "p.xlabel(\"time / s\")\n", "p.ylabel(r\"$\\Delta$ c / uM\");" ] } ], "metadata": { "kernelspec": { "display_name": "cedalion_250620", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.13" } }, "nbformat": 4, "nbformat_minor": 2 }