{ "cells": [ { "cell_type": "markdown", "id": "8684bb9d", "metadata": {}, "source": [ "# 💻 Geokoding i geopandas\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/GMGI221-2024/forelesninger/blob/main/04_geokoding_i_geopandas.ipynb)\n", "\n", "Geopandas støtter geokoding via et bibliotek kalt\n", "[geopy](http://geopy.readthedocs.io/), som må være installert for å bruke\n", "[geopandas ' `geopandas.tools.geocode()`\n", "funksjon](https://geopandas.org/en/stable/docs/reference/api/geopandas.tools.geocode.html).\n", "`geocode()` forventer en `liste` eller `pandas.Series` av adresser (strenger) og\n", "returnerer en `GeoDataFrame` med løste adresser og punktgeometrier.\n", "\n", "La oss prøve dette ut.\n", "\n", "Vi vil geokode adresser lagret i en semikolon-separert tekstfil kalt\n", "`adresser.txt`. Disse adressene ligger i alle i Oslo." ] }, { "cell_type": "code", "execution_count": 1, "id": "ca6ab35e", "metadata": {}, "outputs": [], "source": [ "import pathlib\n", "NOTEBOOK_PATH = pathlib.Path().resolve()\n", "DATA_MAPPE = NOTEBOOK_PATH / \"data\"" ] }, { "cell_type": "code", "execution_count": 2, "id": "cbb3191e", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idadr
0100Statsråd Mathiesens vei 25, 0594 OSLO
1101Slimeveien 15, 1275 OSLO
2102Sognsveien 80, 0855 OSLO
3103Ullevålsveien 5, 0165 OSLO
4104Nydalsveien 30b, 0484 OSLO
\n", "
" ], "text/plain": [ " id adr\n", "0 100 Statsråd Mathiesens vei 25, 0594 OSLO\n", "1 101 Slimeveien 15, 1275 OSLO\n", "2 102 Sognsveien 80, 0855 OSLO\n", "3 103 Ullevålsveien 5, 0165 OSLO\n", "4 104 Nydalsveien 30b, 0484 OSLO" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas\n", "adresser = pandas.read_csv(\n", " DATA_MAPPE / \"oslo_adresser\" / \"adresser.txt\",\n", " sep=\";\"\n", ")\n", "\n", "adresser.head()" ] }, { "cell_type": "markdown", "id": "83aea158", "metadata": {}, "source": [ "Vi har en `id` for hver rad og en adresse i `adr` kolonnen.\n", "\n", "\n", "## Geokode adresser ved hjelp av *Nominatim*\n", "\n", "I vårt eksempel vil vi bruke *Nominatim* som en *geokodingstilbyder*. [*Nominatim*](https://nominatim.org/) er et bibliotek og en tjeneste som bruker OpenStreetMap-data, og drives av OpenStreetMap Foundation. Geopandas '\n", "[`geocode()`\n", "funksjon](https://geopandas.org/en/stable/docs/reference/api/geopandas.tools.geocode.html) støtter den innebygd.\n", "\n", "```{admonition} God bruk\n", ":class: note\n", "\n", "[Nominatims brukervilkår](https://operations.osmfoundation.org/policies/nominatim/)\n", "krever at brukere av tjenesten sørger for at de ikke sender mer hyppige\n", "forespørsler enn en per sekund, og at en tilpasset **bruker-agent** streng er\n", "knyttet til hver forespørsel.\n", "\n", "Geopandas' implementering lar oss spesifisere en `user_agent`; biblioteket tar også\n", "hånd om å respektere hastighetsbegrensningen til Nominatim.\n", "\n", "Å slå opp en adresse er en ganske kostbar databaseoperasjon. Derfor er det,\n", "noen ganger, den offentlige og gratise Nominatim-serveren bruker litt lenger tid å på\n", "svare. I dette eksempelet legger vi til en parameter `timeout=10` for å vente opptil 10\n", "sekunder for et svar.\n", "```" ] }, { "cell_type": "code", "execution_count": 3, "id": "0b593bb7", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
geometryaddress
0POINT (10.83648 59.94104)25, Statsråd Mathiesens vei, Linderud, Bjerke,...
1POINT (10.83432 59.83557)15, Slimeveien, Bjørnholt, Søndre Nordstrand, ...
2POINT (10.72956 59.95011)Sognsveien 80, Konvallveien, Sogn, Nordre Aker...
3POINT (10.74356 59.91863)5, Ullevålsveien, Hammersborg, St. Hanshaugen,...
4POINT (10.76403 59.95030)30B, Nydalsveien, Nydalen, Nordre Aker, Oslo, ...
\n", "
" ], "text/plain": [ " geometry \\\n", "0 POINT (10.83648 59.94104) \n", "1 POINT (10.83432 59.83557) \n", "2 POINT (10.72956 59.95011) \n", "3 POINT (10.74356 59.91863) \n", "4 POINT (10.76403 59.95030) \n", "\n", " address \n", "0 25, Statsråd Mathiesens vei, Linderud, Bjerke,... \n", "1 15, Slimeveien, Bjørnholt, Søndre Nordstrand, ... \n", "2 Sognsveien 80, Konvallveien, Sogn, Nordre Aker... \n", "3 5, Ullevålsveien, Hammersborg, St. Hanshaugen,... \n", "4 30B, Nydalsveien, Nydalen, Nordre Aker, Oslo, ... " ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import geopandas\n", "\n", "geokodede_adresser = geopandas.tools.geocode(\n", " adresser[\"adr\"],\n", " provider=\"nominatim\",\n", " user_agent=\"gmgi221\",\n", " timeout=10\n", ")\n", "geokodede_adresser.head()" ] }, { "cell_type": "markdown", "id": "1b52ec03", "metadata": {}, "source": [ "Et voilà! Som et resultat fikk vi tilbake en `GeoDataFrame` som inneholder en analysert\n", "versjon av våre originale adresser og en `geometry` kolonne med\n", "`shapely.geometry.Point`s som vi kan bruke, for eksempel, til å eksportere dataene til\n", "et romlig dataformat.\n", "\n", "Imidlertid ble `id`-kolonnen forkastet i prosessen. For å kombinere inputdatasettet med resultatsettet vårt, kan vi bruke pandas' [*join*\n", "operasjoner](https://pandas.pydata.org/docs/user_guide/merging.html).\n", "\n", "\n", "## Koble sammen dataframes\n", "\n", ":::{admonition} Koble sammen datasett ved hjelp av pandas\n", ":class: note\n", "\n", "For en omfattende oversikt over forskjellige måter å kombinere dataframes og\n", "Series, ta en titt på pandas dokumentasjon om [merge,\n", "join og\n", "concatenate](https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html).\n", ":::\n", "\n", "\n", "Å koble data fra to eller flere dataframes eller tabeller er en vanlig oppgave i mange\n", "(romlige) dataanalysearbeidsflyter. Som du kanskje husker fra våre tidligere\n", "timer, kan kombinering av data fra forskjellige tabeller basert på en felles **nøkkel**-attributt\n", "gjøres enkelt i pandas/geopandas ved hjelp av [`merge()`\n", "funksjonen](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.merge.html).\n", "\n", "Men, noen ganger er det nyttig å koble to dataframes sammen basert på deres\n", "**indeks**. Dataframes må ha **samme antall rader** og\n", "**dele den samme indeksen** (enkelt forklart, de skal ha samme rekkefølge av rader).\n", "\n", "Vi kan bruke denne tilnærmingen, for å koble informasjon fra den originale dataframen `adresser` til de geokodede adressene `geokodede_adresser`, rad for rad.\n", "`join()`-funksjonen, som standard, kobler to dataframes basert på indeksen deres.\n", "Dette fungerer for eksemplet vårt, da rekkefølgen på de to dataframesene er identiske." ] }, { "cell_type": "code", "execution_count": 4, "id": "20d02f02", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
geometryaddressidadr
0POINT (10.83648 59.94104)25, Statsråd Mathiesens vei, Linderud, Bjerke,...100Statsråd Mathiesens vei 25, 0594 OSLO
1POINT (10.83432 59.83557)15, Slimeveien, Bjørnholt, Søndre Nordstrand, ...101Slimeveien 15, 1275 OSLO
2POINT (10.72956 59.95011)Sognsveien 80, Konvallveien, Sogn, Nordre Aker...102Sognsveien 80, 0855 OSLO
3POINT (10.74356 59.91863)5, Ullevålsveien, Hammersborg, St. Hanshaugen,...103Ullevålsveien 5, 0165 OSLO
4POINT (10.76403 59.95030)30B, Nydalsveien, Nydalen, Nordre Aker, Oslo, ...104Nydalsveien 30b, 0484 OSLO
5POINT (10.75292 59.91900)3, Vestre Elvebakke, Fredensborg, Grünerløkka,...105Vestre Elvebakke 3, 0182 OSLO
6POINT (10.79645 59.90968)5, Etterstadsletta, Gamle Oslo, Oslo, 0660, Norge106Etterstadsletta 5, 0660 OSLO
7POINT (10.75544 59.92704)20, Steenstrups gate, Grünerløkka, Oslo, 0554,...107Steenstrups gate 20, 0554 OSLO
8POINT (10.79433 59.91526)21, Fyrstikkalléen, Lilleberg, Gamle Oslo, Osl...108Fyrstikkalleen 21, 0661 OSLO
9POINT (10.71740 59.91862)Niels Juels gate, Uranienborg, Frogner, Oslo, ...109Niels Juels gate 56, 0259 OSLO
10POINT (10.84019 59.91439)6, Wilhelm Stenersens vei, Tveita, Alna, Oslo,...110Wilhelm Stenersens vei 6, 0671 OSLO
11POINT (10.76403 59.91813)20B, Herslebs gate, Grønland, Gamle Oslo, Oslo...111Herslebs gate 20B, 0561 OSLO
12POINT (10.78564 59.88545)124, Ekebergveien, Holtet, Nordstrand, Oslo, 1...112Ekebergveien 124, 1178 OSLO
13POINT (10.74015 59.93902)Ullevål sykehus, Ullevålsalléen, Ullevål hageb...113Ullevål sykehus, 0450 OSLO
14POINT (10.75825 59.89739)30, Kongsveien, Grønlia, Gamle Oslo, Oslo, 019...114Kongsveien 30, 0193 OSLO
15POINT (10.79030 59.91467)Gladengveien 3B, Gladengveien, Ensjø, Gamle Os...115Gladengveien 3B, 0661 OSLO
16POINT (10.81631 59.92734)10, Kabelgata, Mellom-Hovin, Bjerke, Oslo, 058...116Kabelgata 10, 0580 OSLO
17POINT (10.81041 59.87888)6, Cecilie Thoresens vei, Karlsrud, Nordstrand...117Cecilie Thoresens vei 6, 1153 OSLO
18POINT (10.71752 59.95709)67, Sognsvannsveien, Rabben, Nordre Aker, Oslo...118Sognsvannsveien 67, 0372 OSLO
19POINT (10.76286 59.95056)30C, Nydalsveien, Nydalen, Nordre Aker, Oslo, ...119Nydalsveien 30c, 0484 OSLO
20POINT (10.72060 59.91399)65, Parkveien, Ruseløkka, Frogner, Oslo, 0254,...120Parkveien 65, 0254 OSLO
21POINT (10.74160 59.92115)31, Ullevålsveien, Hammersborg, St. Hanshaugen...121Ullevålsveien 31, 0131 OSLO
22POINT (10.65787 59.94535)1, Gamle Hovsetervei, Nordre Huseby, Vestre Ak...122Gamle Hovsetervei 1, 0768 OSLO
23POINT (10.92677 59.96050)25, Karl Fossums vei, Fossum, Stovner, Oslo, 0...123Karl Fossums vei 25, 0913 OSLO
24POINT (10.71712 59.94804)11, Sognsvannsveien, Gaustad, Nordre Aker, Osl...124Sognsvannsveien 11, 0372 OSLO
25POINT (10.66345 59.93113)66, Ullernchausséen, Montebello, Ullern, Oslo,...125Ullernchaussèen 66, 0379 OSLO
26POINT (10.85068 59.88579)5, Tor Jonssons veg, Myrvoll, Østensjø, Oslo, ...126Tor Jonssons veg 5, 0688 OSLO
27POINT (10.80533 59.91732)16H, Innspurten, Gullhaug, Gamle Oslo, Oslo, 0...127Innspurten 16, 0663 OSLO
28POINT (10.72413 59.91304)30, Cort Adelers gate, Ruseløkka, Frogner, Osl...128Cort Adelers gate 30, 0254 OSLO
29POINT (10.78756 59.93246)Lørenveien 11, Lørenveien, Sinsen, Grünerløkka...129Lørenveien 11, 0585 OSLO
\n", "
" ], "text/plain": [ " geometry \\\n", "0 POINT (10.83648 59.94104) \n", "1 POINT (10.83432 59.83557) \n", "2 POINT (10.72956 59.95011) \n", "3 POINT (10.74356 59.91863) \n", "4 POINT (10.76403 59.95030) \n", "5 POINT (10.75292 59.91900) \n", "6 POINT (10.79645 59.90968) \n", "7 POINT (10.75544 59.92704) \n", "8 POINT (10.79433 59.91526) \n", "9 POINT (10.71740 59.91862) \n", "10 POINT (10.84019 59.91439) \n", "11 POINT (10.76403 59.91813) \n", "12 POINT (10.78564 59.88545) \n", "13 POINT (10.74015 59.93902) \n", "14 POINT (10.75825 59.89739) \n", "15 POINT (10.79030 59.91467) \n", "16 POINT (10.81631 59.92734) \n", "17 POINT (10.81041 59.87888) \n", "18 POINT (10.71752 59.95709) \n", "19 POINT (10.76286 59.95056) \n", "20 POINT (10.72060 59.91399) \n", "21 POINT (10.74160 59.92115) \n", "22 POINT (10.65787 59.94535) \n", "23 POINT (10.92677 59.96050) \n", "24 POINT (10.71712 59.94804) \n", "25 POINT (10.66345 59.93113) \n", "26 POINT (10.85068 59.88579) \n", "27 POINT (10.80533 59.91732) \n", "28 POINT (10.72413 59.91304) \n", "29 POINT (10.78756 59.93246) \n", "\n", " address id \\\n", "0 25, Statsråd Mathiesens vei, Linderud, Bjerke,... 100 \n", "1 15, Slimeveien, Bjørnholt, Søndre Nordstrand, ... 101 \n", "2 Sognsveien 80, Konvallveien, Sogn, Nordre Aker... 102 \n", "3 5, Ullevålsveien, Hammersborg, St. Hanshaugen,... 103 \n", "4 30B, Nydalsveien, Nydalen, Nordre Aker, Oslo, ... 104 \n", "5 3, Vestre Elvebakke, Fredensborg, Grünerløkka,... 105 \n", "6 5, Etterstadsletta, Gamle Oslo, Oslo, 0660, Norge 106 \n", "7 20, Steenstrups gate, Grünerløkka, Oslo, 0554,... 107 \n", "8 21, Fyrstikkalléen, Lilleberg, Gamle Oslo, Osl... 108 \n", "9 Niels Juels gate, Uranienborg, Frogner, Oslo, ... 109 \n", "10 6, Wilhelm Stenersens vei, Tveita, Alna, Oslo,... 110 \n", "11 20B, Herslebs gate, Grønland, Gamle Oslo, Oslo... 111 \n", "12 124, Ekebergveien, Holtet, Nordstrand, Oslo, 1... 112 \n", "13 Ullevål sykehus, Ullevålsalléen, Ullevål hageb... 113 \n", "14 30, Kongsveien, Grønlia, Gamle Oslo, Oslo, 019... 114 \n", "15 Gladengveien 3B, Gladengveien, Ensjø, Gamle Os... 115 \n", "16 10, Kabelgata, Mellom-Hovin, Bjerke, Oslo, 058... 116 \n", "17 6, Cecilie Thoresens vei, Karlsrud, Nordstrand... 117 \n", "18 67, Sognsvannsveien, Rabben, Nordre Aker, Oslo... 118 \n", "19 30C, Nydalsveien, Nydalen, Nordre Aker, Oslo, ... 119 \n", "20 65, Parkveien, Ruseløkka, Frogner, Oslo, 0254,... 120 \n", "21 31, Ullevålsveien, Hammersborg, St. Hanshaugen... 121 \n", "22 1, Gamle Hovsetervei, Nordre Huseby, Vestre Ak... 122 \n", "23 25, Karl Fossums vei, Fossum, Stovner, Oslo, 0... 123 \n", "24 11, Sognsvannsveien, Gaustad, Nordre Aker, Osl... 124 \n", "25 66, Ullernchausséen, Montebello, Ullern, Oslo,... 125 \n", "26 5, Tor Jonssons veg, Myrvoll, Østensjø, Oslo, ... 126 \n", "27 16H, Innspurten, Gullhaug, Gamle Oslo, Oslo, 0... 127 \n", "28 30, Cort Adelers gate, Ruseløkka, Frogner, Osl... 128 \n", "29 Lørenveien 11, Lørenveien, Sinsen, Grünerløkka... 129 \n", "\n", " adr \n", "0 Statsråd Mathiesens vei 25, 0594 OSLO \n", "1 Slimeveien 15, 1275 OSLO \n", "2 Sognsveien 80, 0855 OSLO \n", "3 Ullevålsveien 5, 0165 OSLO \n", "4 Nydalsveien 30b, 0484 OSLO \n", "5 Vestre Elvebakke 3, 0182 OSLO \n", "6 Etterstadsletta 5, 0660 OSLO \n", "7 Steenstrups gate 20, 0554 OSLO \n", "8 Fyrstikkalleen 21, 0661 OSLO \n", "9 Niels Juels gate 56, 0259 OSLO \n", "10 Wilhelm Stenersens vei 6, 0671 OSLO \n", "11 Herslebs gate 20B, 0561 OSLO \n", "12 Ekebergveien 124, 1178 OSLO \n", "13 Ullevål sykehus, 0450 OSLO \n", "14 Kongsveien 30, 0193 OSLO \n", "15 Gladengveien 3B, 0661 OSLO \n", "16 Kabelgata 10, 0580 OSLO \n", "17 Cecilie Thoresens vei 6, 1153 OSLO \n", "18 Sognsvannsveien 67, 0372 OSLO \n", "19 Nydalsveien 30c, 0484 OSLO \n", "20 Parkveien 65, 0254 OSLO \n", "21 Ullevålsveien 31, 0131 OSLO \n", "22 Gamle Hovsetervei 1, 0768 OSLO \n", "23 Karl Fossums vei 25, 0913 OSLO \n", "24 Sognsvannsveien 11, 0372 OSLO \n", "25 Ullernchaussèen 66, 0379 OSLO \n", "26 Tor Jonssons veg 5, 0688 OSLO \n", "27 Innspurten 16, 0663 OSLO \n", "28 Cort Adelers gate 30, 0254 OSLO \n", "29 Lørenveien 11, 0585 OSLO " ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "geokodede_adresser_med_id = geokodede_adresser.join(adresser)\n", "geokodede_adresser_med_id" ] }, { "cell_type": "markdown", "id": "5a1cc532", "metadata": {}, "source": [ "Utdataen fra `join()` er en ny `geopandas.GeoDataFrame`:" ] }, { "cell_type": "code", "execution_count": 5, "id": "c8f525c6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "geopandas.geodataframe.GeoDataFrame" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(geokodede_adresser_med_id)" ] }, { "cell_type": "markdown", "id": "ed1a5b79", "metadata": {}, "source": [ "Den nye data rammen har alle originale kolonner pluss nye kolonner for `geometry`\n", "og for en analysert `adresse` som kan brukes til å spot-sjekke resultatene.\n", "\n", ":::{note}\n", "Hvis du skulle gjøre join den andre veien, dvs. `adresser.join(geokodede_adresser)`, ville utdata være en `pandas.DataFrame`, ikke en `geopandas.GeoDataFrame`.\n", ":::\n", "\n", "\n", "---\n", "\n", "\n", "Det er nå enkelt å lagre det nye datasettet som en geospatial fil, for eksempel, i\n", "*GeoPackage* format:" ] }, { "cell_type": "code", "execution_count": 6, "id": "037e6330", "metadata": { "tags": [ "remove-input", "remove-output" ] }, "outputs": [], "source": [ "# slett en muligens eksisterende fil, da den skaper\n", "# problemer i tilfelle sphinx kjøres gjentatte ganger\n", "try:\n", " (DATA_MAPPE / \"oslo_adresser\" / \"adresser.gpkg\").unlink()\n", "except FileNotFoundError:\n", " pass" ] }, { "cell_type": "code", "execution_count": 7, "id": "89fee980", "metadata": {}, "outputs": [], "source": [ "geokodede_adresser_med_id.to_file(DATA_MAPPE / \"oslo_adresser\" / \"adresser.gpkg\")" ] }, { "cell_type": "code", "execution_count": null, "id": "a77f4776-f1ef-4409-a377-f25a1d172d8a", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "gmgi221", "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.9" } }, "nbformat": 4, "nbformat_minor": 5 }