{
"cells": [
{
"cell_type": "markdown",
"id": "6fda8556",
"metadata": {},
"source": [
"## With Time Alignment Boundary you define when your days, weeks, months and years begin\n",
"\n",
"We are excited to announce that Amazon Forecast now offers a new feature called Time Alignment Boundary. With this feature, customers who generate predictions using forecast frequencies of daily or higher can start defining when those periods begin.\n",
"\n",
"Prior to Time Alignment Boundary, daily frequencies began at midnight; weekly frequencies began on Monday; monthly frequencies began on the first day of the month while yearly frequencies began in January. Customers can now pick when the period begins to better meet their unique needs.\n",
"\n",
"Time Alignment Boundary is specified when a new predictor is created, either through the AWS Console or through API. \n",
"\n",
"Using the same input dataset, this notebook provides an example of training two weekly-frequency predictors that are bound against Friday and Sunday as the start of the week. The provided notebook is saved in an executed state, so you may review outputs without having to run each cell, unless you choose to do so.\n",
"\n",
"For this exercise, a small slice of yellow taxi trip records is used from [NYC Taxi and Limousine Commission (TLC)](https://www1.nyc.gov/site/tlc/about/tlc-trip-record-data.page)"
]
},
{
"cell_type": "markdown",
"id": "7234b07f",
"metadata": {},
"source": [
"\n",
"## Table of Contents\n",
"* [Initial Setup](#setup)\n",
"* Step 1: [Upload sample data to S3](#upload)\n",
"* Step 2: [Create a dataset, import data and dataset group](#dataset)\n",
"* Step 3: [Create predictors](#predictors)\n",
"* Step 4: [Create forecasts](#forecasts)\n",
"* Step 5: [View forecasted data](#view)\n",
"* Step 6: [Cleanup](#cleanup)\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "7bd3cacd",
"metadata": {},
"source": [
"# Initial Setup\n",
"\n",
"### Upgrade boto3\n",
"\n",
"Before proceeding, ensure you have upgraded boto3."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "1f50c6ef",
"metadata": {},
"outputs": [],
"source": [
"!pip install boto3 --upgrade > /dev/null"
]
},
{
"cell_type": "markdown",
"id": "d5709442",
"metadata": {},
"source": [
"### Setup Imports"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "2f75fe8b",
"metadata": {},
"outputs": [],
"source": [
"import boto3\n",
"from time import sleep\n",
"import subprocess\n",
"import sys\n",
"import os\n",
"import pandas as pd\n",
"import calendar\n",
"\n",
"sys.path.insert( 0, os.path.abspath(\"../../common\") )\n",
"\n",
"import json\n",
"import util"
]
},
{
"cell_type": "markdown",
"id": "ba564fea",
"metadata": {},
"source": [
"### Function to supressing printing account numbers"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "c2aad938",
"metadata": {},
"outputs": [],
"source": [
"import re\n",
"\n",
"def mask_arn(input_string):\n",
"\n",
" mask_regex = re.compile(':[0-9]{12}:')\n",
" mask = mask_regex.search(input_string)\n",
" \n",
" while mask:\n",
" input_string = input_string.replace(mask.group(),'X'*12)\n",
" mask = mask_regex.search(input_string) \n",
" \n",
" return input_string"
]
},
{
"cell_type": "markdown",
"id": "0c081a71",
"metadata": {},
"source": [
"### Create an instance of AWS SDK client for Amazon Forecast"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "abfc4705",
"metadata": {},
"outputs": [],
"source": [
"# Set your region accordingly, us-east-1 as shown\n",
"region = 'us-east-1'\n",
"session = boto3.Session(region_name=region) \n",
"forecast = session.client(service_name='forecast')\n",
"forecastquery = session.client(service_name='forecastquery')\n",
"\n",
"# Checking to make sure we can communicate with Amazon Forecast\n",
"assert forecast.list_forecasts()"
]
},
{
"cell_type": "markdown",
"id": "7d491c24",
"metadata": {},
"source": [
"### Setup IAM Role used by Amazon Forecast to access your data"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "fb6746b9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Creating Role ForecastNotebookRole-Basic...\n",
"The role ForecastNotebookRole-Basic already exists, skipping creation\n",
"Done.\n",
"Success! Created role = ForecastNotebookRole-Basic\n"
]
}
],
"source": [
"role_name = \"ForecastNotebookRole-Basic\"\n",
"print(f\"Creating Role {mask_arn(role_name)}...\")\n",
"role_arn = util.get_or_create_iam_role( role_name = role_name )\n",
"\n",
"# echo user inputs without account\n",
"print(f\"Success! Created role = {mask_arn(role_arn).split('/')[1]}\")"
]
},
{
"cell_type": "markdown",
"id": "fb6f85b0",
"metadata": {},
"source": [
"# Step 1: Upload sample data to S3\n",
"\n",
"The dataset has the following 3 columns:\n",
"- timestamp: Timetamp at which pick-ups are requested.\n",
"- item_id: Pick-up location ID.\n",
"- target_value: Number of pick-ups requested around the timestamp at the pick-up location.\n",
"\n",
"Note: As delivered, this uses the sample file in the data folder relative to this notebook. Please take care to ensure this file is available to your notebook."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "56d52a58",
"metadata": {},
"outputs": [],
"source": [
"bucket_name = input(\"\\nEnter S3 bucket name for uploading the data and hit ENTER key:\")\n",
"\n",
"s3 = boto3.resource('s3')\n",
"s3.meta.client.upload_file('./data/taxi_sample_data.csv', bucket_name, 'taxi_sample_data.csv')"
]
},
{
"cell_type": "markdown",
"id": "e00dfd33",
"metadata": {},
"source": [
"# Step 2: Create a dataset, import data and dataset group"
]
},
{
"cell_type": "markdown",
"id": "a3f30add",
"metadata": {},
"source": [
"### Create Dataset"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "1485d742",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Dataset ARN arn:aws:forecast:us-east-1XXXXXXXXXXXXdataset/TAXI_TIME_ALIGNMENT_BOUNDARY_DEMO is now ACTIVE.\n"
]
}
],
"source": [
"DATASET_FREQUENCY = \"H\"\n",
"TS_DATASET_NAME = \"TAXI_TIME_ALIGNMENT_BOUNDARY_DEMO\"\n",
"TS_SCHEMA = {\n",
" \"Attributes\":[\n",
" {\n",
" \"AttributeName\":\"timestamp\",\n",
" \"AttributeType\":\"timestamp\"\n",
" },\n",
" {\n",
" \"AttributeName\":\"item_id\",\n",
" \"AttributeType\":\"string\"\n",
" },\n",
" {\n",
" \"AttributeName\":\"target_value\",\n",
" \"AttributeType\":\"integer\"\n",
" }\n",
" ]\n",
"} \n",
"\n",
"create_dataset_response = forecast.create_dataset(Domain=\"CUSTOM\",\n",
" DatasetType='TARGET_TIME_SERIES',\n",
" DatasetName=TS_DATASET_NAME,\n",
" DataFrequency=DATASET_FREQUENCY,\n",
" Schema=TS_SCHEMA)\n",
"\n",
"ts_dataset_arn = create_dataset_response['DatasetArn']\n",
"describe_dataset_response = forecast.describe_dataset(DatasetArn=ts_dataset_arn)\n",
"\n",
"print(f\"Dataset ARN {mask_arn(ts_dataset_arn)} is now {describe_dataset_response['Status']}.\")"
]
},
{
"cell_type": "markdown",
"id": "26206f40",
"metadata": {},
"source": [
"### Import the initial seed data file"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "94857c19",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Waiting for Dataset Import Job with ARN arn:aws:forecast:us-east-1XXXXXXXXXXXXdataset-import-job/TAXI_TIME_ALIGNMENT_BOUNDARY_DEMO/taxi_sample_data to become ACTIVE.\n",
"\n",
"Current Status:\n",
"\n",
"CREATE_PENDING \n",
"CREATE_IN_PROGRESS .......\n",
"ACTIVE \n"
]
}
],
"source": [
"TS_IMPORT_JOB_NAME = 'taxi_sample_data'\n",
"TIMESTAMP_FORMAT = \"yyyy-MM-dd hh:mm:ss\"\n",
"ts_s3_path = f\"s3://{bucket_name}/{TS_IMPORT_JOB_NAME}.csv\"\n",
"TIMEZONE = \"EST\"\n",
"\n",
"#frequency of poll event from API to check status of tasks\n",
"sleep_duration=60\n",
"\n",
"\n",
"ts_dataset_import_job_response = \\\n",
" forecast.create_dataset_import_job(DatasetImportJobName=TS_IMPORT_JOB_NAME,\n",
" DatasetArn=ts_dataset_arn,\n",
" DataSource= {\n",
" \"S3Config\" : {\n",
" \"Path\": ts_s3_path,\n",
" \"RoleArn\": role_arn\n",
" } \n",
" },\n",
" TimestampFormat=TIMESTAMP_FORMAT,\n",
" TimeZone = TIMEZONE)\n",
"\n",
"ts_dataset_import_job_arn = ts_dataset_import_job_response['DatasetImportJobArn']\n",
"\n",
"print(f\"Waiting for Dataset Import Job with ARN {mask_arn(ts_dataset_import_job_arn)} to become ACTIVE.\\n\\nCurrent Status:\\n\")\n",
"\n",
"status = util.wait(lambda: forecast.describe_dataset_import_job(DatasetImportJobArn=ts_dataset_import_job_arn), sleep_duration)\n",
" "
]
},
{
"cell_type": "markdown",
"id": "af80283e",
"metadata": {},
"source": [
"### Create a dataset group"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "c5e7b474",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The DatasetGroup with ARN arn:aws:forecast:us-east-1XXXXXXXXXXXXdataset-group/TAXI_TIME_ALIGNMENT_BOUNDARY_DEMO is now ACTIVE.\n"
]
}
],
"source": [
"DATASET_GROUP_NAME = \"TAXI_TIME_ALIGNMENT_BOUNDARY_DEMO\"\n",
"DATASET_ARNS = [ts_dataset_arn]\n",
"\n",
"create_dataset_group_response = \\\n",
" forecast.create_dataset_group(Domain=\"CUSTOM\",\n",
" DatasetGroupName=DATASET_GROUP_NAME,\n",
" DatasetArns=DATASET_ARNS)\n",
"\n",
"dataset_group_arn = create_dataset_group_response['DatasetGroupArn']\n",
"describe_dataset_group_response = forecast.describe_dataset_group(DatasetGroupArn=dataset_group_arn)\n",
"\n",
"print(f\"The DatasetGroup with ARN {mask_arn(dataset_group_arn)} is now {describe_dataset_group_response['Status']}.\")"
]
},
{
"cell_type": "markdown",
"id": "027db930",
"metadata": {},
"source": [
"# Step 3: Create predictors\n",
"\n",
"Observe the new parameter in the create_auto_predictor() function TimeAlignmentBoundary. In this example, two predictors are created, each with a 3-week horizon. Note one predictor has DayOfWeek=Friday, the other has DayOfWeek=Sunday."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "446138b2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Waiting for Friday Predictor ARN arn:aws:forecast:us-east-1XXXXXXXXXXXXpredictor/TAXI_PREDICTOR_WEEK_FRIDAY_01G415VFPA7DRMEFTBWH4WM0M9 to become ACTIVE.\n",
"Waiting for Sunday Predictor ARN arn:aws:forecast:us-east-1XXXXXXXXXXXXpredictor/TAXI_PREDICTOR_WEEK_SUNDAY_01G415VFRXXYC865W93D3Y1CRZ to become ACTIVE.\n",
"CREATE_PENDING \n",
"CREATE_IN_PROGRESS ............................................\n",
"ACTIVE \n",
"CREATE_IN_PROGRESS .........\n",
"ACTIVE \n"
]
}
],
"source": [
"FORECAST_HORIZON = 3\n",
"FORECAST_FREQUENCY = \"W\"\n",
"\n",
"#Create a predictor with week starting Friday\n",
"create_auto_predictor_response = \\\n",
" forecast.create_auto_predictor(PredictorName = 'TAXI_PREDICTOR_WEEK_FRIDAY',\n",
" ForecastHorizon = FORECAST_HORIZON,\n",
" ForecastFrequency = FORECAST_FREQUENCY,\n",
" DataConfig = {\n",
" 'DatasetGroupArn': dataset_group_arn\n",
" },\n",
" TimeAlignmentBoundary={\n",
" \"DayOfWeek\":\"FRIDAY\"\n",
" },\n",
" ExplainPredictor = False)\n",
"\n",
"friday_predictor_arn = create_auto_predictor_response['PredictorArn']\n",
"print(f\"Waiting for Friday Predictor ARN {mask_arn(friday_predictor_arn)} to become ACTIVE.\")\n",
"\n",
"\n",
"\n",
"#Create a predictor with week starting Sunday\n",
"create_auto_predictor_response = \\\n",
" forecast.create_auto_predictor(PredictorName = 'TAXI_PREDICTOR_WEEK_SUNDAY',\n",
" ForecastHorizon = FORECAST_HORIZON,\n",
" ForecastFrequency = FORECAST_FREQUENCY,\n",
" DataConfig = {\n",
" 'DatasetGroupArn': dataset_group_arn\n",
" },\n",
" TimeAlignmentBoundary={\n",
" \"DayOfWeek\":\"SUNDAY\"\n",
" },\n",
" ExplainPredictor = False)\n",
"\n",
"sunday_predictor_arn = create_auto_predictor_response['PredictorArn']\n",
"print(f\"Waiting for Sunday Predictor ARN {mask_arn(sunday_predictor_arn)} to become ACTIVE.\\n\\n\")\n",
"\n",
"\n",
"#Wait on the predictors to complete and become active\n",
"status = util.wait(lambda: forecast.describe_auto_predictor(PredictorArn=friday_predictor_arn), sleep_duration)\n",
"status = util.wait(lambda: forecast.describe_auto_predictor(PredictorArn=sunday_predictor_arn), sleep_duration)\n"
]
},
{
"cell_type": "markdown",
"id": "fbfb7a95",
"metadata": {},
"source": [
"### Additional Examples"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "26b62677",
"metadata": {},
"outputs": [],
"source": [
"FORECAST_HORIZON = 3\n",
"\n",
"# Create a predictor that starts monthly frequencies on the 15th day of the month\n",
"FORECAST_FREQUENCY = \"M\"\n",
"\n",
"create_auto_predictor_response = \\\n",
" forecast.create_auto_predictor(PredictorName = PREDICTOR_NAME,\n",
" ForecastHorizon = FORECAST_HORIZON,\n",
" ForecastFrequency = FORECAST_FREQUENCY,\n",
" DataConfig = {\n",
" 'DatasetGroupArn': dataset_group_arn\n",
" },\n",
" TimeAlignmentBoundary={\n",
" \"DayOfMonth\": 15\n",
" },\n",
" ExplainPredictor = False)\n",
"\n",
"\n",
"# Create a predictor that starts each day at 9AM.\n",
"FORECAST_FREQUENCY = \"D\"\n",
"\n",
"create_auto_predictor_response = \\\n",
" forecast.create_auto_predictor(PredictorName = PREDICTOR_NAME,\n",
" ForecastHorizon = FORECAST_HORIZON,\n",
" ForecastFrequency = FORECAST_FREQUENCY,\n",
" DataConfig = {\n",
" 'DatasetGroupArn': dataset_group_arn\n",
" },\n",
" TimeAlignmentBoundary={\n",
" \"Hour\": 9\n",
" },\n",
" ExplainPredictor = False)"
]
},
{
"cell_type": "markdown",
"id": "f037a7c7",
"metadata": {},
"source": [
"# Step 4: Create forecasts\n",
"\n",
"Here, a forecast is created for each predictor. These will be used to serve requests in Step 5."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "1a688430",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Waiting for Friday Forecast with ARN arn:aws:forecast:us-east-1XXXXXXXXXXXXforecast/TAXI_FORECAST_WEEK_FRIDAY to become ACTIVE.\n",
"Waiting for Sunday Forecast with ARN arn:aws:forecast:us-east-1XXXXXXXXXXXXforecast/TAXI_FORECAST_WEEK_SUNDAY to become ACTIVE.\n",
"CREATE_PENDING \n",
"CREATE_IN_PROGRESS ..........\n",
"ACTIVE \n",
"CREATE_IN_PROGRESS ....\n",
"ACTIVE \n"
]
}
],
"source": [
"create_forecast_response = \\\n",
" forecast.create_forecast(ForecastName='TAXI_FORECAST_WEEK_FRIDAY',\n",
" PredictorArn=friday_predictor_arn)\n",
"\n",
"friday_forecast_arn = create_forecast_response['ForecastArn']\n",
"\n",
"\n",
"create_forecast_response = \\\n",
" forecast.create_forecast(ForecastName='TAXI_FORECAST_WEEK_SUNDAY',\n",
" PredictorArn=sunday_predictor_arn)\n",
"\n",
"sunday_forecast_arn = create_forecast_response['ForecastArn']\n",
"\n",
"\n",
"print(f\"Waiting for Friday Forecast with ARN {mask_arn(friday_forecast_arn)} to become ACTIVE.\")\n",
"print(f\"Waiting for Sunday Forecast with ARN {mask_arn(sunday_forecast_arn)} to become ACTIVE.\")\n",
"\n",
"status = util.wait(lambda: forecast.describe_forecast(ForecastArn=friday_forecast_arn), sleep_duration)\n",
"status = util.wait(lambda: forecast.describe_forecast(ForecastArn=sunday_forecast_arn), sleep_duration)\n"
]
},
{
"cell_type": "markdown",
"id": "20520aa2",
"metadata": {},
"source": [
"# Step 5: View forecasted data\n",
"\n",
"Below, the notebook shows how to use the Amazon Forecast query client to retrieve predictions through API for a named time-series. Below, queries against the Friday and Sunday forecast are performed.\n",
"\n",
"Note how forecasted data points from the Sunday forecast have Timestamps aligned to Sunday. Friday forecasts align to a Friday timestamp. Calendars are provided to visual purposes only -- ease of cross-reference."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "c3f70a6d",
"metadata": {},
"outputs": [],
"source": [
"#inpect values for a specific taxi route\n",
"item_id='201'"
]
},
{
"cell_type": "markdown",
"id": "ead5ed15",
"metadata": {},
"source": [
"### Sunday Forecasts"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "d1c6d3e6",
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" February 2019\n",
"Mo Tu We Th Fr Sa Su\n",
" 1 2 3\n",
" 4 5 6 7 8 9 10\n",
"11 12 13 14 15 16 17\n",
"18 19 20 21 22 23 24\n",
"25 26 27 28\n",
"\n",
"{'Timestamp': '2019-02-03T00:00:00', 'Value': 5.769572938646521}\n",
"{'Timestamp': '2019-02-10T00:00:00', 'Value': 5.977745991739137}\n",
"{'Timestamp': '2019-02-17T00:00:00', 'Value': 5.672434587428659}\n"
]
}
],
"source": [
"#Sunday Forecasts\n",
"\n",
"print(calendar.month(2019, 2))\n",
"\n",
"forecast_response = forecastquery.query_forecast(\n",
" ForecastArn=sunday_forecast_arn,\n",
" Filters={\"item_id\": item_id}\n",
")\n",
"\n",
"for i in forecast_response['Forecast']['Predictions']['p50']:\n",
" print(i)"
]
},
{
"cell_type": "markdown",
"id": "1e9f4cac",
"metadata": {},
"source": [
"### Friday Forecasts"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "91a90436",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" February 2019\n",
"Mo Tu We Th Fr Sa Su\n",
" 1 2 3\n",
" 4 5 6 7 8 9 10\n",
"11 12 13 14 15 16 17\n",
"18 19 20 21 22 23 24\n",
"25 26 27 28\n",
"\n",
"{'Timestamp': '2019-02-01T00:00:00', 'Value': 7.176211290014157}\n",
"{'Timestamp': '2019-02-08T00:00:00', 'Value': 7.440115660652784}\n",
"{'Timestamp': '2019-02-15T00:00:00', 'Value': 6.5957757171386255}\n"
]
}
],
"source": [
"#Friday Forecasts\n",
"\n",
"print(calendar.month(2019, 2))\n",
"\n",
"forecast_response = forecastquery.query_forecast(\n",
" ForecastArn=friday_forecast_arn,\n",
" Filters={\"item_id\": item_id}\n",
")\n",
"\n",
"for i in forecast_response['Forecast']['Predictions']['p50']:\n",
" print(i)"
]
},
{
"cell_type": "markdown",
"id": "992b041a",
"metadata": {},
"source": [
"# Step 6: Cleanup\n",
"\n",
"You will need to allow a few minutes for each of these steps to complete.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a51b653d",
"metadata": {},
"outputs": [],
"source": [
"forecast.delete_resource_tree(ResourceArn=dataset_group_arn)"
]
},
{
"cell_type": "markdown",
"id": "505de3fa",
"metadata": {},
"source": [
"Once the dataset group has been deleted (allow a few minutes), you may proceed. The following code will allow you to test and determine when the dataset group has been deleted. When you run this next cell, you may see your dataset group. Allow a couple minutes, and try again. Once your dataset is deleted, you may proceed to next step."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "685a6857",
"metadata": {},
"outputs": [],
"source": [
"forecast.list_dataset_groups()"
]
},
{
"cell_type": "markdown",
"id": "5ac8e709",
"metadata": {},
"source": [
"Delete dataset import jobs with TAXI_TIME_ALIGNMENT_BOUNDARY_DEMO in the job name."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ae721cf8",
"metadata": {},
"outputs": [],
"source": [
"response = forecast.list_dataset_import_jobs()\n",
"\n",
"for i in response['DatasetImportJobs']:\n",
"\n",
" try:\n",
" if i['DatasetImportJobArn'].index('TAXI_TIME_ALIGNMENT_BOUNDARY_DEMO'):\n",
" print('Deleting',i['DatasetImportJobName'])\n",
" forecast.delete_dataset_import_job(DatasetImportJobArn=i['DatasetImportJobArn'])\n",
" except:\n",
" pass"
]
},
{
"cell_type": "markdown",
"id": "0eaf5ece",
"metadata": {},
"source": [
"It will take a few minutes to delete the dataset import jobs. Once that is complete, the dataset can be deleted as follows in the next cell."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12148072",
"metadata": {},
"outputs": [],
"source": [
"forecast.delete_dataset(DatasetArn=ts_dataset_arn)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "conda_pytorch_p36",
"language": "python",
"name": "conda_pytorch_p36"
},
"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.6.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}