{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Visualising open collections in DigitalNZ\n",
"\n",
"DigitalNZ's `usage` facet lets you know what you can do with an item. The possible values are:\n",
"\n",
"* Share\n",
"* Modify\n",
"* Use commercially\n",
"* All rights reserved\n",
"* Unknown\n",
"\n",
"I've already harvested the [totals for each facet value](facets/usage.csv). You can find out more about [harvesting facet data in this notebook](harvest_facet_data.ipynb). Let's look at the totals."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" value | \n",
" count | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" Share | \n",
" 27004615 | \n",
"
\n",
" \n",
" 1 | \n",
" Modify | \n",
" 26714337 | \n",
"
\n",
" \n",
" 2 | \n",
" Use commercially | \n",
" 22641258 | \n",
"
\n",
" \n",
" 3 | \n",
" All rights reserved | \n",
" 4200830 | \n",
"
\n",
" \n",
" 4 | \n",
" Unknown | \n",
" 347738 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" value count\n",
"0 Share 27004615\n",
"1 Modify 26714337\n",
"2 Use commercially 22641258\n",
"3 All rights reserved 4200830\n",
"4 Unknown 347738"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import pandas as pd\n",
"from pathlib import Path\n",
"\n",
"df = pd.read_csv(Path('facets', 'usage.csv'))\n",
"df"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In my head, I need to try and translate these back into copyright and CC licence equivalents. So 'Modify' would be CC-BY-ND, and 'Share' would be CC-BY-NC-ND. According to [open licence definitions](https://opendefinition.org/licenses/), NC and ND licences are not regarded as 'open'. So to find 'open' items in DigitalNZ we have to look for items that have a `usage` value of 'Use commercially'. Of course, many of these will have no known copyright restrictions rather than a CC licence.\n",
"\n",
"The 'Share', 'Modify', and 'Use commercially' values are not mututally exclusive. An item you can 'Use commercially', you can also 'Share'. This means items can have multiple values for `usage`. To find the total number of items with `usage` values we can try adding 'Share', 'All rights reserved', and 'Unknown' as there shouldn't be any overlap between these."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"31553183"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.loc[df['value'].isin(['Share', 'All rights reserved', 'Unknown'])]['count'].sum()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So the percentage of open items in DigitalNZ is..."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"71.76%\n"
]
}
],
"source": [
"print(f'{(22641258 / 31553183):.2%}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That's pretty good, though I suspect that this is dominated by out-of-copyright digitised newspaper articles from Papers Past.\n",
"\n",
"In this notebook I'm going to attempt a more fine-grained view by identifying and visualising individual collections within DigitalNZ that have open content."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import what we need"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"import requests_cache\n",
"from tqdm.auto import tqdm\n",
"import altair as alt\n",
"\n",
"s = requests_cache.CachedSession()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"API_KEY = '[YOUR API KEY]'\n",
"API_KEY = '9yXNTynMDb3TUQws7QuD'\n",
"API_URL = 'http://api.digitalnz.org/v3/records.json'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define some functions"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def get_records(params):\n",
" '''\n",
" Get records from a search using the supplied parameters.\n",
" '''\n",
" response = s.get(API_URL, params=params)\n",
" return response.json()\n",
"\n",
"def harvest_facet_values(facet, **kwargs):\n",
" '''\n",
" Get all the values for the given facet.\n",
" Apply filters in kwargs.\n",
" '''\n",
" facets = {}\n",
" more = True\n",
" page = 1\n",
" params = {\n",
" 'api_key': API_KEY,\n",
" 'per_page': 0,\n",
" 'facets': facet,\n",
" 'facets_per_page': 350,\n",
" }\n",
" for k, v in kwargs.items():\n",
" if k == 'text':\n",
" params[k] = v\n",
" else:\n",
" params[f'and[{k}][]'] = v\n",
" while more:\n",
" params['facets_page'] = page\n",
" data = get_records(params)\n",
" if data['search']['facets'][facet]:\n",
" facets.update(data['search']['facets'][facet])\n",
" page += 1\n",
" else:\n",
" more = False\n",
" return facets"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Assemble the data\n",
"\n",
"In another notebook, I've already harvested `primary_collection` facets for each `content_partner`. Here we'll add in the `usage` data for each collection."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Open the collections by content partner data\n",
"df_collections = pd.read_csv(Path('facets', 'collections_by_partner.csv'))"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"dfs_usage = []\n",
"\n",
"# Loop through the collections by partner\n",
"for row in df_collections.itertuples():\n",
" partner = row.content_partner\n",
" collection = row.primary_collection\n",
" \n",
" # Get the usage facet data for the collection\n",
" facets = harvest_facet_values('usage', content_partner=partner, primary_collection=collection)\n",
" if facets:\n",
" \n",
" # Convert the facets dict to a dataframe\n",
" df = pd.DataFrame.from_dict(facets, orient='index').reset_index()\n",
" df.columns = ['usage', 'count']\n",
" \n",
" # Adding numbers with usage values of 'Share', 'All rights reserved', & 'Unknown' should give us a total number of items\n",
" # Items that have usage values of 'Modify' & 'Use commerically' should be included in the 'Share' numbers\n",
" df['usage_total'] = df.loc[df['usage'].isin(['Share', 'All rights reserved', 'Unknown'])]['count'].sum()\n",
" \n",
" # Add partner and collection names to the df\n",
" df['content_partner'] = partner\n",
" df['primary_collection'] = collection\n",
" \n",
" # The number of items from the primary_collection facet\n",
" df['items_total'] = row.count\n",
" \n",
" # Add this df to a list of dfs\n",
" dfs_usage.append(df)\n",
"\n",
"# Merge the list of dfs into one big df\n",
"df_usage = pd.concat(dfs_usage)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we have a dataframe combining all the partner, collection and usage data."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" content_partner | \n",
" primary_collection | \n",
" items_total | \n",
" usage | \n",
" count | \n",
" usage_total | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 95bFM | \n",
" 95bFM | \n",
" 9822 | \n",
" All rights reserved | \n",
" 11617 | \n",
" 11617 | \n",
"
\n",
" \n",
" 0 | \n",
" 95bFM | \n",
" 95bFM YouTube | \n",
" 709 | \n",
" All rights reserved | \n",
" 764 | \n",
" 764 | \n",
"
\n",
" \n",
" 0 | \n",
" AgEcon Search | \n",
" AgEcon Search | \n",
" 1527 | \n",
" All rights reserved | \n",
" 1527 | \n",
" 1527 | \n",
"
\n",
" \n",
" 0 | \n",
" Air Force Museum of New Zealand | \n",
" Air Force Museum of New Zealand Photograph Col... | \n",
" 16608 | \n",
" Share | \n",
" 24669 | \n",
" 24700 | \n",
"
\n",
" \n",
" 1 | \n",
" Air Force Museum of New Zealand | \n",
" Air Force Museum of New Zealand Photograph Col... | \n",
" 16608 | \n",
" Modify | \n",
" 24652 | \n",
" 24700 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" content_partner \\\n",
"0 95bFM \n",
"0 95bFM \n",
"0 AgEcon Search \n",
"0 Air Force Museum of New Zealand \n",
"1 Air Force Museum of New Zealand \n",
"\n",
" primary_collection items_total \\\n",
"0 95bFM 9822 \n",
"0 95bFM YouTube 709 \n",
"0 AgEcon Search 1527 \n",
"0 Air Force Museum of New Zealand Photograph Col... 16608 \n",
"1 Air Force Museum of New Zealand Photograph Col... 16608 \n",
"\n",
" usage count usage_total \n",
"0 All rights reserved 11617 11617 \n",
"0 All rights reserved 764 764 \n",
"0 All rights reserved 1527 1527 \n",
"0 Share 24669 24700 \n",
"1 Modify 24652 24700 "
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Reorder the columns\n",
"df_usage = df_usage[['content_partner', 'primary_collection', 'items_total', 'usage', 'count', 'usage_total']].sort_values(by=['content_partner', 'primary_collection'])\n",
"df_usage.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Before we move on, I'm going to save this dataframe [as a CSV](facets/usage_by_collection_and_partner.csv) because it might be interesting to explore further."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"df_usage.to_csv(Path('facets', 'usage_by_collection_and_partner.csv'), index=False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You might notice that I have two 'total' fields in the dataframe. One is the number of items returned by the `primary_collection` facet. The other is calculated by adding up the number of items with `usage` values of 'Share', 'All rights reserved', and 'Unknown'. All items with a usage value of 'Modify' or 'Use commercially' should also have a value of 'Share', so I don't include them in the total. Assuming that all items have a `usage` value, these two totals should be the same. But are they?\n",
"\n",
"The table below shows collections where the totals don't match. The good news is that most of them do, so my working assumptions seem pretty safe. But it seems that there are some items that don't have a `usage` value. In most cases the differences are small, but there are major disparities in the case of the Kura Heritage collections Online and the Turnbull Library's *Transactions & Proceedings of the Royal Society of NZ*. Below I'm going to use the total in order to calculate the proportion of items that are 'open'. So which value should I use? I'm going to go with the `usage_total` for now, because I know what that represents, but it would be easy to swap."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" content_partner | \n",
" primary_collection | \n",
" items_total | \n",
" usage | \n",
" count | \n",
" usage_total | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 95bFM | \n",
" 95bFM | \n",
" 9822 | \n",
" All rights reserved | \n",
" 11617 | \n",
" 11617 | \n",
"
\n",
" \n",
" 0 | \n",
" 95bFM | \n",
" 95bFM YouTube | \n",
" 709 | \n",
" All rights reserved | \n",
" 764 | \n",
" 764 | \n",
"
\n",
" \n",
" 0 | \n",
" Air Force Museum of New Zealand | \n",
" Air Force Museum of New Zealand Photograph Col... | \n",
" 16608 | \n",
" Share | \n",
" 24669 | \n",
" 24700 | \n",
"
\n",
" \n",
" 0 | \n",
" Alexander Turnbull Library | \n",
" TAPUHI | \n",
" 333206 | \n",
" Unknown | \n",
" 215059 | \n",
" 333317 | \n",
"
\n",
" \n",
" 0 | \n",
" Alexander Turnbull Library | \n",
" Transactions and Proceedings of the Royal Soci... | \n",
" 37625 | \n",
" All rights reserved | \n",
" 4200 | \n",
" 4200 | \n",
"
\n",
" \n",
" ... | \n",
" ... | \n",
" ... | \n",
" ... | \n",
" ... | \n",
" ... | \n",
" ... | \n",
"
\n",
" \n",
" 0 | \n",
" Whangarei Libraries | \n",
" Whangarei Libraries | \n",
" 2662 | \n",
" All rights reserved | \n",
" 2794 | \n",
" 2794 | \n",
"
\n",
" \n",
" 0 | \n",
" Young Ocean Explorers | \n",
" Young Ocean Explorers | \n",
" 289 | \n",
" All rights reserved | \n",
" 322 | \n",
" 322 | \n",
"
\n",
" \n",
" 0 | \n",
" data.govt.nz | \n",
" data.govt.nz | \n",
" 27418 | \n",
" Share | \n",
" 24767 | \n",
" 31485 | \n",
"
\n",
" \n",
" 0 | \n",
" eqnz.chch.2010 | \n",
" Flickr | \n",
" 6111 | \n",
" Share | \n",
" 4237 | \n",
" 6112 | \n",
"
\n",
" \n",
" 0 | \n",
" iNaturalist NZ — Mātaki Taiao | \n",
" iNaturalist NZ — Mātaki Taiao | \n",
" 527674 | \n",
" Share | \n",
" 514936 | \n",
" 571510 | \n",
"
\n",
" \n",
"
\n",
"
146 rows × 6 columns
\n",
"
"
],
"text/plain": [
" content_partner \\\n",
"0 95bFM \n",
"0 95bFM \n",
"0 Air Force Museum of New Zealand \n",
"0 Alexander Turnbull Library \n",
"0 Alexander Turnbull Library \n",
".. ... \n",
"0 Whangarei Libraries \n",
"0 Young Ocean Explorers \n",
"0 data.govt.nz \n",
"0 eqnz.chch.2010 \n",
"0 iNaturalist NZ — Mātaki Taiao \n",
"\n",
" primary_collection items_total \\\n",
"0 95bFM 9822 \n",
"0 95bFM YouTube 709 \n",
"0 Air Force Museum of New Zealand Photograph Col... 16608 \n",
"0 TAPUHI 333206 \n",
"0 Transactions and Proceedings of the Royal Soci... 37625 \n",
".. ... ... \n",
"0 Whangarei Libraries 2662 \n",
"0 Young Ocean Explorers 289 \n",
"0 data.govt.nz 27418 \n",
"0 Flickr 6111 \n",
"0 iNaturalist NZ — Mātaki Taiao 527674 \n",
"\n",
" usage count usage_total \n",
"0 All rights reserved 11617 11617 \n",
"0 All rights reserved 764 764 \n",
"0 Share 24669 24700 \n",
"0 Unknown 215059 333317 \n",
"0 All rights reserved 4200 4200 \n",
".. ... ... ... \n",
"0 All rights reserved 2794 2794 \n",
"0 All rights reserved 322 322 \n",
"0 Share 24767 31485 \n",
"0 Share 4237 6112 \n",
"0 Share 514936 571510 \n",
"\n",
"[146 rows x 6 columns]"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_usage.loc[df_usage['items_total'] != df_usage['usage_total']].drop_duplicates(subset=['content_partner', 'primary_collection', 'items_total', 'usage_total'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we'll calculate what proprtion of each collection is open, by dividing the number with a `usage` value of 'Use commercially', but the total number of items with `usage` values. "
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"# Only use rows where usage value is 'Use commercially'\n",
"df_open = df_usage.loc[df_usage['usage'] == 'Use commercially'].copy()\n",
"\n",
"# Calculate the proportion of items that are open by dividing those you can use commercially by the total\n",
"df_open['open'] = df_open.apply(lambda x: x['count'] / x['usage_total'], axis=1)\n",
"\n",
"# Some collection names are the same, so we'll combine the partner & collection name to create a unique label for Altair\n",
"df_open['label'] = df_open.agg(lambda x: f'{x[\"primary_collection\"]} ({x[\"content_partner\"]})', axis=1)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" content_partner | \n",
" primary_collection | \n",
" items_total | \n",
" usage | \n",
" count | \n",
" usage_total | \n",
" open | \n",
" label | \n",
"
\n",
" \n",
" \n",
" \n",
" 2 | \n",
" Air Force Museum of New Zealand | \n",
" Air Force Museum of New Zealand Photograph Col... | \n",
" 16608 | \n",
" Use commercially | \n",
" 6312 | \n",
" 24700 | \n",
" 0.255547 | \n",
" Air Force Museum of New Zealand Photograph Col... | \n",
"
\n",
" \n",
" 2 | \n",
" Alexander Turnbull Library | \n",
" Alexander Turnbull Library Flickr | \n",
" 4307 | \n",
" Use commercially | \n",
" 4307 | \n",
" 4307 | \n",
" 1.000000 | \n",
" Alexander Turnbull Library Flickr (Alexander T... | \n",
"
\n",
" \n",
" 4 | \n",
" Alexander Turnbull Library | \n",
" TAPUHI | \n",
" 333206 | \n",
" Use commercially | \n",
" 52734 | \n",
" 333317 | \n",
" 0.158210 | \n",
" TAPUHI (Alexander Turnbull Library) | \n",
"
\n",
" \n",
" 2 | \n",
" Antarctica New Zealand | \n",
" Antarctica NZ Digital Asset Manager | \n",
" 55348 | \n",
" Use commercially | \n",
" 1768 | \n",
" 58934 | \n",
" 0.030000 | \n",
" Antarctica NZ Digital Asset Manager (Antarctic... | \n",
"
\n",
" \n",
" 4 | \n",
" Archives Central | \n",
" Archives Central | \n",
" 7113 | \n",
" Use commercially | \n",
" 31 | \n",
" 7619 | \n",
" 0.004069 | \n",
" Archives Central (Archives Central) | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" content_partner \\\n",
"2 Air Force Museum of New Zealand \n",
"2 Alexander Turnbull Library \n",
"4 Alexander Turnbull Library \n",
"2 Antarctica New Zealand \n",
"4 Archives Central \n",
"\n",
" primary_collection items_total \\\n",
"2 Air Force Museum of New Zealand Photograph Col... 16608 \n",
"2 Alexander Turnbull Library Flickr 4307 \n",
"4 TAPUHI 333206 \n",
"2 Antarctica NZ Digital Asset Manager 55348 \n",
"4 Archives Central 7113 \n",
"\n",
" usage count usage_total open \\\n",
"2 Use commercially 6312 24700 0.255547 \n",
"2 Use commercially 4307 4307 1.000000 \n",
"4 Use commercially 52734 333317 0.158210 \n",
"2 Use commercially 1768 58934 0.030000 \n",
"4 Use commercially 31 7619 0.004069 \n",
"\n",
" label \n",
"2 Air Force Museum of New Zealand Photograph Col... \n",
"2 Alexander Turnbull Library Flickr (Alexander T... \n",
"4 TAPUHI (Alexander Turnbull Library) \n",
"2 Antarctica NZ Digital Asset Manager (Antarctic... \n",
"4 Archives Central (Archives Central) "
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_open.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We're ready to make a chart!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Visualise the results!\n",
"\n",
"For something a bit different, and to celebrate the fact that GLAM organisations are making their collections openly available, I thought I'd attempt a fireworks theme for this visualisation. It's a bit of an experiment, but also a demonstration on how easy it is to play around with styles in Altair."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
""
],
"text/plain": [
"alt.LayerChart(...)"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Use the dark theme\n",
"alt.themes.enable('dark')\n",
"\n",
"base = alt.Chart(df_open).encode(\n",
" # The collection/partner label is used on the X axis\n",
" x=alt.X('label:N', title='Collection'),\n",
" # The proportion that is open is on the Y axis (formatted as a percentage)\n",
" y=alt.Y('open:Q', axis=alt.Axis(format='%', grid=False), title='Percent open'),\n",
" # Colour is determined by the content_partner value\n",
" color=alt.Color('content_partner:N', legend=None),\n",
" tooltip=[alt.Tooltip('content_partner', title='Partner'), alt.Tooltip('primary_collection', title='Collection'), alt.Tooltip('usage_total', title='Total items', format=','), alt.Tooltip('open', format='.2%', title='Percent open')]\n",
")\n",
"\n",
"# The bursts of fireworks (the stroke settings make the lines radiating out from the circles)\n",
"# Size is calculated using a log scale because Papers Past is so much bigger than anything else\n",
"circles = base.mark_circle(opacity=0.8, strokeOpacity=0.2,strokeDash=[3,1], strokeWidth=20, stroke='white').encode(\n",
" size=alt.Size('usage_total:Q', scale=alt.Scale(type='log', range=[1, 6000]), title='Total items'),\n",
")\n",
"\n",
"# Use a bar chart to make the lines connecting the explosions to the baseline\n",
"lines = base.mark_bar(size=2, opacity=0.3, strokeDash=[2,2], strokeWidth=1, stroke='white', strokeOpacity=0.2).encode()\n",
"\n",
"# Set various style options\n",
"combined = alt.layer(lines, circles).properties(\n",
" height=500, width=2500, title='Collections in DigitalNZ with open content (commercial use allowed)'\n",
").configure_axis(\n",
" grid=False,\n",
" labelColor='#999999',\n",
" domainColor='#999999',\n",
" tickColor='#999999',\n",
" titleColor='#999999'\n",
").configure_view(\n",
" strokeWidth=0\n",
").configure_title(\n",
" color='#999999'\n",
").configure_legend(\n",
" symbolBaseFillColor='#666666',\n",
" symbolStrokeWidth=0,\n",
" titleColor='#999999',\n",
" labelColor='#999999'\n",
")\n",
"\n",
"combined"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Save the visualisation as an HTML page. You can [see the result here](https://glam-workbench.net/digitalnz-views/open_collections_digitalnz.html)."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"combined.save('open_collections_digitalnz.html')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"----\n",
"\n",
"Created by [Tim Sherratt](https://timsherratt.org/) for the [GLAM Workbench](https://glam-workbench.net/). Support this project by becoming a [GitHub sponsor](https://github.com/sponsors/wragge?o=esb)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.8.5"
}
},
"nbformat": 4,
"nbformat_minor": 4
}