xarray-plotly 0.0.3__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/.github/workflows/ci.yml +3 -3
  2. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/.github/workflows/docs.yml +2 -2
  3. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/.github/workflows/release.yml +2 -2
  4. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/PKG-INFO +9 -9
  5. xarray_plotly-0.0.4/docs/examples/dimensions.ipynb +232 -0
  6. xarray_plotly-0.0.4/docs/examples/figure.ipynb +236 -0
  7. xarray_plotly-0.0.4/docs/examples/kwargs.ipynb +251 -0
  8. xarray_plotly-0.0.4/docs/examples/plot-types.ipynb +170 -0
  9. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/docs/index.md +2 -1
  10. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/mkdocs.yml +3 -1
  11. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/pyproject.toml +8 -8
  12. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/tests/test_accessor.py +89 -3
  13. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/xarray_plotly/__init__.py +36 -9
  14. xarray_plotly-0.0.4/xarray_plotly/accessor.py +586 -0
  15. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/xarray_plotly/config.py +1 -0
  16. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/xarray_plotly/plotting.py +61 -0
  17. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/xarray_plotly.egg-info/PKG-INFO +9 -9
  18. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/xarray_plotly.egg-info/SOURCES.txt +3 -1
  19. xarray_plotly-0.0.4/xarray_plotly.egg-info/requires.txt +19 -0
  20. xarray_plotly-0.0.3/docs/examples/advanced.ipynb +0 -320
  21. xarray_plotly-0.0.3/docs/examples/plot-types.ipynb +0 -382
  22. xarray_plotly-0.0.3/xarray_plotly/accessor.py +0 -275
  23. xarray_plotly-0.0.3/xarray_plotly.egg-info/requires.txt +0 -19
  24. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/.github/dependabot.yml +0 -0
  25. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/.github/workflows/dependabot-auto-merge.yml +0 -0
  26. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/.gitignore +0 -0
  27. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/.pre-commit-config.yaml +0 -0
  28. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/CONTRIBUTING.md +0 -0
  29. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/LICENSE +0 -0
  30. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/README.md +0 -0
  31. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/docs/api.md +0 -0
  32. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/docs/getting-started.ipynb +0 -0
  33. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/setup.cfg +0 -0
  34. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/tests/__init__.py +0 -0
  35. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/tests/test_common.py +0 -0
  36. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/tests/test_config.py +0 -0
  37. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/xarray_plotly/common.py +0 -0
  38. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/xarray_plotly/py.typed +0 -0
  39. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/xarray_plotly.egg-info/dependency_links.txt +0 -0
  40. {xarray_plotly-0.0.3 → xarray_plotly-0.0.4}/xarray_plotly.egg-info/top_level.txt +0 -0
@@ -14,10 +14,10 @@ jobs:
14
14
  python-version: ["3.10", "3.11", "3.12", "3.13"]
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v6
18
18
 
19
19
  - name: Install uv
20
- uses: astral-sh/setup-uv@v4
20
+ uses: astral-sh/setup-uv@v7
21
21
 
22
22
  - name: Set up Python ${{ matrix.python-version }}
23
23
  run: uv python install ${{ matrix.python-version }}
@@ -35,7 +35,7 @@ jobs:
35
35
  run: uv run pytest --cov=xarray_plotly --cov-report=xml
36
36
 
37
37
  - name: Upload coverage
38
- uses: codecov/codecov-action@v4
38
+ uses: codecov/codecov-action@v5
39
39
  if: matrix.python-version == '3.12'
40
40
  with:
41
41
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -14,10 +14,10 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v6
18
18
 
19
19
  - name: Install uv
20
- uses: astral-sh/setup-uv@v4
20
+ uses: astral-sh/setup-uv@v7
21
21
 
22
22
  - name: Install dependencies
23
23
  run: uv sync --extra docs
@@ -13,10 +13,10 @@ jobs:
13
13
  contents: write # for creating GitHub release
14
14
 
15
15
  steps:
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v6
17
17
 
18
18
  - name: Install uv
19
- uses: astral-sh/setup-uv@v4
19
+ uses: astral-sh/setup-uv@v7
20
20
 
21
21
  - name: Build package
22
22
  run: uv build
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray_plotly
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: Interactive Plotly Express plotting accessor for xarray
5
5
  Author: Felix
6
6
  License: MIT
@@ -25,16 +25,16 @@ Requires-Dist: xarray>=2023.1.0
25
25
  Requires-Dist: plotly>=5.0.0
26
26
  Requires-Dist: pandas>=1.5.0
27
27
  Provides-Extra: dev
28
- Requires-Dist: pytest==8.3.5; extra == "dev"
29
- Requires-Dist: pytest-cov==6.0.0; extra == "dev"
30
- Requires-Dist: mypy==1.14.1; extra == "dev"
31
- Requires-Dist: ruff==0.9.2; extra == "dev"
32
- Requires-Dist: pre-commit==4.0.1; extra == "dev"
33
- Requires-Dist: nbstripout==0.8.1; extra == "dev"
28
+ Requires-Dist: pytest==9.0.2; extra == "dev"
29
+ Requires-Dist: pytest-cov==7.0.0; extra == "dev"
30
+ Requires-Dist: mypy==1.19.1; extra == "dev"
31
+ Requires-Dist: ruff==0.14.11; extra == "dev"
32
+ Requires-Dist: pre-commit==4.5.1; extra == "dev"
33
+ Requires-Dist: nbstripout==0.8.2; extra == "dev"
34
34
  Provides-Extra: docs
35
35
  Requires-Dist: mkdocs==1.6.1; extra == "docs"
36
- Requires-Dist: mkdocs-material==9.5.49; extra == "docs"
37
- Requires-Dist: mkdocstrings[python]==0.27.0; extra == "docs"
36
+ Requires-Dist: mkdocs-material==9.7.1; extra == "docs"
37
+ Requires-Dist: mkdocstrings[python]==1.0.0; extra == "docs"
38
38
  Requires-Dist: mkdocs-jupyter==0.25.1; extra == "docs"
39
39
  Requires-Dist: mkdocs-plotly-plugin==0.1.3; extra == "docs"
40
40
  Requires-Dist: jupyter==1.1.1; extra == "docs"
@@ -0,0 +1,232 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Dimensions, Facets & Animation\n",
8
+ "\n",
9
+ "How to control which dimensions map to which visual properties."
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": null,
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "import plotly.express as px\n",
19
+ "import xarray as xr\n",
20
+ "\n",
21
+ "from xarray_plotly import config, xpx\n",
22
+ "\n",
23
+ "config.notebook()"
24
+ ]
25
+ },
26
+ {
27
+ "cell_type": "code",
28
+ "execution_count": null,
29
+ "metadata": {},
30
+ "outputs": [],
31
+ "source": [
32
+ "# Sample data: 2D\n",
33
+ "df = px.data.stocks().set_index(\"date\")\n",
34
+ "df.index = df.index.astype(\"datetime64[ns]\")\n",
35
+ "\n",
36
+ "stocks = xr.DataArray(\n",
37
+ " df.values,\n",
38
+ " dims=[\"date\", \"company\"],\n",
39
+ " coords={\"date\": df.index, \"company\": df.columns.tolist()},\n",
40
+ " name=\"price\",\n",
41
+ ")\n",
42
+ "\n",
43
+ "# Sample data: 3D\n",
44
+ "df_gap = px.data.gapminder()\n",
45
+ "countries = [\"United States\", \"China\", \"Germany\", \"Brazil\"]\n",
46
+ "metrics = [\"lifeExp\", \"gdpPercap\"]\n",
47
+ "\n",
48
+ "arrays = []\n",
49
+ "for metric in metrics:\n",
50
+ " df_pivot = df_gap[df_gap[\"country\"].isin(countries)].pivot(\n",
51
+ " index=\"year\", columns=\"country\", values=metric\n",
52
+ " )\n",
53
+ " arrays.append(df_pivot.values)\n",
54
+ "\n",
55
+ "data_3d = xr.DataArray(\n",
56
+ " arrays,\n",
57
+ " dims=[\"metric\", \"year\", \"country\"],\n",
58
+ " coords={\n",
59
+ " \"metric\": metrics,\n",
60
+ " \"year\": df_pivot.index.tolist(),\n",
61
+ " \"country\": df_pivot.columns.tolist(),\n",
62
+ " },\n",
63
+ " name=\"value\",\n",
64
+ ")"
65
+ ]
66
+ },
67
+ {
68
+ "cell_type": "markdown",
69
+ "metadata": {},
70
+ "source": [
71
+ "## Default Dimension Assignment\n",
72
+ "\n",
73
+ "Dimensions are assigned to slots in order:"
74
+ ]
75
+ },
76
+ {
77
+ "cell_type": "code",
78
+ "execution_count": null,
79
+ "metadata": {},
80
+ "outputs": [],
81
+ "source": [
82
+ "# date → x, company → color\n",
83
+ "xpx(stocks).line()"
84
+ ]
85
+ },
86
+ {
87
+ "cell_type": "markdown",
88
+ "metadata": {},
89
+ "source": [
90
+ "## Explicit Assignment\n",
91
+ "\n",
92
+ "Override the defaults by specifying which dimension goes where:"
93
+ ]
94
+ },
95
+ {
96
+ "cell_type": "code",
97
+ "execution_count": null,
98
+ "metadata": {},
99
+ "outputs": [],
100
+ "source": [
101
+ "# Swap: company → x, date → color\n",
102
+ "xpx(stocks.isel(date=[0, 50, 100])).bar(x=\"company\", color=\"date\")"
103
+ ]
104
+ },
105
+ {
106
+ "cell_type": "markdown",
107
+ "metadata": {},
108
+ "source": [
109
+ "## Skipping Slots with None\n",
110
+ "\n",
111
+ "Use `None` to skip a slot, so dimensions shift to the next available slot:"
112
+ ]
113
+ },
114
+ {
115
+ "cell_type": "code",
116
+ "execution_count": null,
117
+ "metadata": {},
118
+ "outputs": [],
119
+ "source": [
120
+ "# Skip color → company goes to line_dash instead\n",
121
+ "xpx(stocks.sel(company=[\"GOOG\", \"AAPL\", \"MSFT\"])).line(color=None)"
122
+ ]
123
+ },
124
+ {
125
+ "cell_type": "markdown",
126
+ "metadata": {},
127
+ "source": [
128
+ "## Available Slots\n",
129
+ "\n",
130
+ "Different plot types have different slots:\n",
131
+ "\n",
132
+ "| Plot | Slots (in order) |\n",
133
+ "|------|------------------|\n",
134
+ "| line | x, color, line_dash, facet_col, facet_row, animation_frame |\n",
135
+ "| scatter | x, color, symbol, facet_col, facet_row, animation_frame |\n",
136
+ "| bar | x, color, facet_col, facet_row, animation_frame |\n",
137
+ "| imshow | x, y, facet_col, facet_row, animation_frame |"
138
+ ]
139
+ },
140
+ {
141
+ "cell_type": "code",
142
+ "execution_count": null,
143
+ "metadata": {},
144
+ "outputs": [],
145
+ "source": [
146
+ "# line_dash for third dimension\n",
147
+ "xpx(stocks.sel(company=[\"GOOG\", \"AAPL\"])).line(line_dash=\"company\")"
148
+ ]
149
+ },
150
+ {
151
+ "cell_type": "code",
152
+ "execution_count": null,
153
+ "metadata": {},
154
+ "outputs": [],
155
+ "source": [
156
+ "# symbol for scatter\n",
157
+ "xpx(stocks.sel(company=[\"GOOG\", \"AAPL\"])).scatter(symbol=\"company\")"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "markdown",
162
+ "metadata": {},
163
+ "source": [
164
+ "## Faceting\n",
165
+ "\n",
166
+ "Create a grid of subplots with `facet_col` and `facet_row`:"
167
+ ]
168
+ },
169
+ {
170
+ "cell_type": "code",
171
+ "execution_count": null,
172
+ "metadata": {},
173
+ "outputs": [],
174
+ "source": [
175
+ "# One subplot per metric\n",
176
+ "xpx(data_3d).line(facet_col=\"metric\")"
177
+ ]
178
+ },
179
+ {
180
+ "cell_type": "code",
181
+ "execution_count": null,
182
+ "metadata": {},
183
+ "outputs": [],
184
+ "source": [
185
+ "# Grid: metric x country\n",
186
+ "xpx(data_3d).line(x=\"year\", facet_col=\"metric\", facet_row=\"country\")"
187
+ ]
188
+ },
189
+ {
190
+ "cell_type": "markdown",
191
+ "metadata": {},
192
+ "source": [
193
+ "## Animation\n",
194
+ "\n",
195
+ "Animate over a dimension with `animation_frame`:"
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "code",
200
+ "execution_count": null,
201
+ "metadata": {},
202
+ "outputs": [],
203
+ "source": [
204
+ "# Animate through years\n",
205
+ "xpx(data_3d.sel(metric=\"lifeExp\")).bar(x=\"country\", animation_frame=\"year\")"
206
+ ]
207
+ },
208
+ {
209
+ "cell_type": "code",
210
+ "execution_count": null,
211
+ "metadata": {},
212
+ "outputs": [],
213
+ "source": [
214
+ "# Animate heatmap\n",
215
+ "xpx(data_3d).imshow(animation_frame=\"metric\")"
216
+ ]
217
+ }
218
+ ],
219
+ "metadata": {
220
+ "kernelspec": {
221
+ "display_name": "Python 3",
222
+ "language": "python",
223
+ "name": "python3"
224
+ },
225
+ "language_info": {
226
+ "name": "python",
227
+ "version": "3.12.0"
228
+ }
229
+ },
230
+ "nbformat": 4,
231
+ "nbformat_minor": 4
232
+ }
@@ -0,0 +1,236 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Working with Figures\n",
8
+ "\n",
9
+ "All methods return a [Plotly Figure](https://plotly.com/python/figure-structure/) that can be modified and exported."
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": null,
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "import plotly.express as px\n",
19
+ "import plotly.graph_objects as go\n",
20
+ "import xarray as xr\n",
21
+ "\n",
22
+ "from xarray_plotly import config, xpx\n",
23
+ "\n",
24
+ "config.notebook()"
25
+ ]
26
+ },
27
+ {
28
+ "cell_type": "code",
29
+ "execution_count": null,
30
+ "metadata": {},
31
+ "outputs": [],
32
+ "source": [
33
+ "df = px.data.stocks().set_index(\"date\")\n",
34
+ "df.index = df.index.astype(\"datetime64[ns]\")\n",
35
+ "\n",
36
+ "stocks = xr.DataArray(\n",
37
+ " df.values,\n",
38
+ " dims=[\"date\", \"company\"],\n",
39
+ " coords={\"date\": df.index, \"company\": df.columns.tolist()},\n",
40
+ " name=\"price\",\n",
41
+ ")"
42
+ ]
43
+ },
44
+ {
45
+ "cell_type": "markdown",
46
+ "metadata": {},
47
+ "source": [
48
+ "## update_layout\n",
49
+ "\n",
50
+ "Modify [layout properties](https://plotly.com/python/reference/layout/): title, legend, margins, fonts."
51
+ ]
52
+ },
53
+ {
54
+ "cell_type": "code",
55
+ "execution_count": null,
56
+ "metadata": {},
57
+ "outputs": [],
58
+ "source": [
59
+ "fig = xpx(stocks).line()\n",
60
+ "fig.update_layout(\n",
61
+ " title={\"text\": \"Stock Prices\", \"x\": 0.5},\n",
62
+ " legend={\"orientation\": \"h\", \"y\": 1.02},\n",
63
+ ")\n",
64
+ "fig"
65
+ ]
66
+ },
67
+ {
68
+ "cell_type": "markdown",
69
+ "metadata": {},
70
+ "source": [
71
+ "## update_traces\n",
72
+ "\n",
73
+ "Modify [trace properties](https://plotly.com/python/reference/): line width, markers, opacity."
74
+ ]
75
+ },
76
+ {
77
+ "cell_type": "code",
78
+ "execution_count": null,
79
+ "metadata": {},
80
+ "outputs": [],
81
+ "source": [
82
+ "fig = xpx(stocks).line()\n",
83
+ "fig.update_traces(line={\"width\": 3})\n",
84
+ "fig"
85
+ ]
86
+ },
87
+ {
88
+ "cell_type": "code",
89
+ "execution_count": null,
90
+ "metadata": {},
91
+ "outputs": [],
92
+ "source": [
93
+ "fig = xpx(stocks).scatter()\n",
94
+ "fig.update_traces(marker={\"size\": 10, \"opacity\": 0.7})\n",
95
+ "fig"
96
+ ]
97
+ },
98
+ {
99
+ "cell_type": "markdown",
100
+ "metadata": {},
101
+ "source": [
102
+ "## update_xaxes / update_yaxes\n",
103
+ "\n",
104
+ "Modify [axis properties](https://plotly.com/python/axes/)."
105
+ ]
106
+ },
107
+ {
108
+ "cell_type": "code",
109
+ "execution_count": null,
110
+ "metadata": {},
111
+ "outputs": [],
112
+ "source": [
113
+ "fig = xpx(stocks).line()\n",
114
+ "fig.update_xaxes(rangeslider_visible=True)\n",
115
+ "fig.update_yaxes(tickformat=\".0%\")\n",
116
+ "fig"
117
+ ]
118
+ },
119
+ {
120
+ "cell_type": "markdown",
121
+ "metadata": {},
122
+ "source": [
123
+ "## add_hline / add_vline\n",
124
+ "\n",
125
+ "Add reference lines. See [shapes](https://plotly.com/python/shapes/)."
126
+ ]
127
+ },
128
+ {
129
+ "cell_type": "code",
130
+ "execution_count": null,
131
+ "metadata": {},
132
+ "outputs": [],
133
+ "source": [
134
+ "fig = xpx(stocks).line()\n",
135
+ "fig.add_hline(y=1.0, line_dash=\"dash\", line_color=\"gray\", annotation_text=\"Baseline\")\n",
136
+ "fig.add_vline(x=\"2018-10-01\", line_dash=\"dot\", line_color=\"red\")\n",
137
+ "fig"
138
+ ]
139
+ },
140
+ {
141
+ "cell_type": "markdown",
142
+ "metadata": {},
143
+ "source": [
144
+ "## add_annotation\n",
145
+ "\n",
146
+ "Add text annotations. See [annotations](https://plotly.com/python/text-and-annotations/)."
147
+ ]
148
+ },
149
+ {
150
+ "cell_type": "code",
151
+ "execution_count": null,
152
+ "metadata": {},
153
+ "outputs": [],
154
+ "source": [
155
+ "fig = xpx(stocks).line()\n",
156
+ "fig.add_annotation(x=\"2018-10-01\", y=1.4, text=\"Peak\", showarrow=True, arrowhead=2)\n",
157
+ "fig"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "markdown",
162
+ "metadata": {},
163
+ "source": [
164
+ "## add_trace\n",
165
+ "\n",
166
+ "Add custom traces using [Graph Objects](https://plotly.com/python/graph-objects/)."
167
+ ]
168
+ },
169
+ {
170
+ "cell_type": "code",
171
+ "execution_count": null,
172
+ "metadata": {},
173
+ "outputs": [],
174
+ "source": [
175
+ "fig = xpx(stocks.sel(company=\"GOOG\")).line()\n",
176
+ "\n",
177
+ "# Add moving average\n",
178
+ "goog = stocks.sel(company=\"GOOG\")\n",
179
+ "ma = goog.rolling(date=20, center=True).mean()\n",
180
+ "\n",
181
+ "fig.add_trace(\n",
182
+ " go.Scatter(\n",
183
+ " x=ma.coords[\"date\"].values,\n",
184
+ " y=ma.values,\n",
185
+ " mode=\"lines\",\n",
186
+ " name=\"20-day MA\",\n",
187
+ " line={\"dash\": \"dash\", \"color\": \"red\"},\n",
188
+ " )\n",
189
+ ")\n",
190
+ "fig"
191
+ ]
192
+ },
193
+ {
194
+ "cell_type": "markdown",
195
+ "metadata": {},
196
+ "source": [
197
+ "## Export\n",
198
+ "\n",
199
+ "### HTML\n",
200
+ "\n",
201
+ "See [HTML export](https://plotly.com/python/interactive-html-export/).\n",
202
+ "\n",
203
+ "```python\n",
204
+ "fig.write_html(\"plot.html\")\n",
205
+ "fig.write_html(\"plot.html\", include_plotlyjs=\"cdn\") # smaller file\n",
206
+ "```\n",
207
+ "\n",
208
+ "### Images\n",
209
+ "\n",
210
+ "Requires [kaleido](https://github.com/plotly/Kaleido): `pip install kaleido`\n",
211
+ "\n",
212
+ "See [static image export](https://plotly.com/python/static-image-export/).\n",
213
+ "\n",
214
+ "```python\n",
215
+ "fig.write_image(\"plot.png\")\n",
216
+ "fig.write_image(\"plot.png\", scale=2) # higher resolution\n",
217
+ "fig.write_image(\"plot.svg\")\n",
218
+ "fig.write_image(\"plot.pdf\")\n",
219
+ "```"
220
+ ]
221
+ }
222
+ ],
223
+ "metadata": {
224
+ "kernelspec": {
225
+ "display_name": "Python 3",
226
+ "language": "python",
227
+ "name": "python3"
228
+ },
229
+ "language_info": {
230
+ "name": "python",
231
+ "version": "3.12.0"
232
+ }
233
+ },
234
+ "nbformat": 4,
235
+ "nbformat_minor": 4
236
+ }