{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Introducing What-If Analysis\n", "\n", "Now with [Amazon Forecast](https://aws.amazon.com/forecast/), you can seamlessly conduct what-if analyses up to 80% faster to analyze and quantify the potential impact of business levers on your demand forecasts. Forecast is a service that uses machine learning (ML) to generate accurate demand forecasts, without requiring any ML experience. Simulating scenarios through what-if analyses is a powerful business tool to navigate through the uncertainty of future events by capturing possible outcomes from hypothetical scenarios. It’s a common practice to assess the impact of business decisions on revenue or profitability, quantify the risk associated with market trends, evaluate how to organize logistics and workforce to meet customer demand, and much more.\n", "\n", "This notebooks demonstrates how to utillize the What-If feature by using newly available API calls. First, the notebook uses a sample, synthetic dataset to build a baseline forecast using the \"true\" product price. Next, the example leads you through the process of creating a what-if analysis that includes two price reduction simulations. Price reduction scenario 1 will offer a 10% discount; scenario 2 will offer a 20% discount. With the scenarios created, the notebook will show you to generate a forecast for each of these. Finally, the notebook shows you how to compare the baseline forecast against the \"discounted\" forecast. Using this generic technique, you can make a business decision -- choosing a price point that is most likely to maximize profit for your company.\n", "\n", "Workflow as follows:\n", "\n", "1. [Import libraries and setup AWS resources](#import_and_setup)\n", "2. [Upload demo CSVs to S3](#upload)\n", "3. [Create DatasetGroup and Datasets](#create_dsg_ds)\n", "4. [Import the target time series data and related time series](#import)\n", "5. [Create Predictor](#create_predictor)\n", "6. [Create Forecast to produce a baseline](#forecast)\n", "7. [Create What-If Analysis](#whatifanalysis)\n", "8. [Create What-If Forecast](#whatifforecast)\n", "9. [Export What-If Forecast](#forecastexport)\n", "10. [Download and Visualize](#download)\n", "11. [Resource Cleanup](#cleanup)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Import libraries and setup AWS resources \n", " \n", "Before proceeding, ensure you have upgraded boto3 as follows, so the latest API is available to you." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install boto3 --upgrade" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import sys\n", "import os\n", "import time\n", "import datetime\n", "import io\n", "import numpy as np\n", "\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "import boto3\n", "\n", "# importing forecast notebook utility from notebooks/common directory\n", "sys.path.insert( 0, os.path.abspath(\"../../common\") )\n", "import util\n", "import json" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Configure the S3 bucket name and region name for this lesson.\n", "\n", "- If you don't have an S3 bucket, create it first on S3.\n", "- Although we have set the region to us-west-2 as a default value below, you can choose any of the regions that the service is available in." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "bucket_name = 'input your existing S3 bucket name'\n", "region = 'us-west-2'" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "session = boto3.Session(region_name=region)\n", "s3 = session.client('s3')\n", "forecast = session.client(service_name='forecast') \n", "forecastquery = session.client(service_name='forecastquery')\n", "\n", "assert forecast.list_what_if_analyses()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "from sagemaker import get_execution_role\n", "role_arn = get_execution_role()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Upload to S3 \n", "Here, we will view the dataset locally, then upload the file to Amazon S3. Amazon Forecast consumes input data from S3.\n", " \n", "A sample [Target Time Series](https://github.com/aws-samples/amazon-forecast-samples/blob/main/library/content/TargetTimeSeries.md) (TTS) and [Related Time Series](https://github.com/aws-samples/amazon-forecast-samples/blob/main/library/content/RelatedTimeSeries.md) (RTS) file is provided. Please visit the links here to learn more about target and related time series.\n", "\n", "An important note with this dataset. The TTS ends on 2019-09-01, the RTS ends on 2019-12-01. When performing what-if scenarios, it's important to manipulate RTS variables beyond your known time horizon in TTS." ] }, { "cell_type": "code", "execution_count": 7, "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", "
item_idstore_idtimestampdemand
0item_001store_0012017-10-01305.824943
1item_001store_0012017-11-01276.942940
2item_001store_0012017-12-01277.608189
3item_001store_0012018-01-01275.940622
4item_001store_0012018-02-01272.387060
\n", "
" ], "text/plain": [ " item_id store_id timestamp demand\n", "0 item_001 store_001 2017-10-01 305.824943\n", "1 item_001 store_001 2017-11-01 276.942940\n", "2 item_001 store_001 2017-12-01 277.608189\n", "3 item_001 store_001 2018-01-01 275.940622\n", "4 item_001 store_001 2018-02-01 272.387060" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_tts = pd.read_csv('./data/consumer_electronics_TTS.csv', low_memory=False)\n", "df_tts.head(5)" ] }, { "cell_type": "code", "execution_count": 8, "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", "
item_idstore_idtimestampprice
0item_001store_0012017-10-01103.00000
1item_001store_0012017-11-01110.79548
2item_001store_0012017-12-01110.79548
3item_001store_0012018-01-01110.79548
4item_001store_0012018-02-01110.79548
\n", "
" ], "text/plain": [ " item_id store_id timestamp price\n", "0 item_001 store_001 2017-10-01 103.00000\n", "1 item_001 store_001 2017-11-01 110.79548\n", "2 item_001 store_001 2017-12-01 110.79548\n", "3 item_001 store_001 2018-01-01 110.79548\n", "4 item_001 store_001 2018-02-01 110.79548" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_rts = pd.read_csv('./data/consumer_electronics_RTS.csv', low_memory=False)\n", "df_rts.head(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Upload these files to Amazon S3" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "project = \"whatif_analysis\"\n", "\n", "key_tts = \"%s/consumer_electronics_TTS.csv\" % project\n", "key_rts = \"%s/consumer_electronics_RTS.csv\" % project\n", "\n", "s3.upload_file( Filename=\"./data/consumer_electronics_TTS.csv\", Bucket=bucket_name, Key=key_tts )\n", "s3.upload_file( Filename=\"./data/consumer_electronics_RTS.csv\", Bucket=bucket_name, Key=key_rts )\n", "\n", "s3_data_path_tts = \"s3://\" + bucket_name + \"/\" + key_tts\n", "s3_data_path_rts = \"s3://\" + bucket_name + \"/\" + key_rts" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Create DatasetGroup and Datasets
\n", " \n", "You can read more about Datasets and Dataset Groups [here](https://github.com/aws-samples/amazon-forecast-samples/blob/main/library/content/Datasets.md)." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "response = forecast.create_dataset_group(\n", " DatasetGroupName = project + \"_dsg\",\n", " Domain=\"CUSTOM\",\n", " )\n", "\n", "dataset_group_arn = response['DatasetGroupArn']" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "DATASET_FREQUENCY = \"M\"\n", "TIMESTAMP_FORMAT = \"yyyy-MM-dd\"\n", "\n", "schema ={\n", " \"Attributes\":[\n", " {\n", " \"AttributeName\":\"item_id\",\n", " \"AttributeType\":\"string\"\n", " },\n", " {\n", " \"AttributeName\":\"store_id\",\n", " \"AttributeType\":\"string\"\n", " },\n", " {\n", " \"AttributeName\":\"timestamp\",\n", " \"AttributeType\":\"timestamp\"\n", " },\n", " {\n", " \"AttributeName\":\"target_value\",\n", " \"AttributeType\":\"float\"\n", " },\n", " ]\n", "}\n", "\n", "response = forecast.create_dataset(\n", " Domain = \"CUSTOM\",\n", " DatasetType = 'TARGET_TIME_SERIES',\n", " DatasetName = project + \"_tts\",\n", " DataFrequency = DATASET_FREQUENCY, \n", " Schema = schema\n", ")\n", "\n", "tts_dataset_arn = response['DatasetArn']" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "schema ={\n", " \"Attributes\":[\n", " {\n", " \"AttributeName\":\"item_id\",\n", " \"AttributeType\":\"string\"\n", " },\n", " {\n", " \"AttributeName\":\"store_id\",\n", " \"AttributeType\":\"string\"\n", " },\n", " {\n", " \"AttributeName\":\"timestamp\",\n", " \"AttributeType\":\"timestamp\"\n", " },\n", " {\n", " \"AttributeName\":\"price\",\n", " \"AttributeType\":\"float\"\n", " },\n", " ]\n", "}\n", "\n", "response = forecast.create_dataset(\n", " Domain = \"CUSTOM\",\n", " DatasetType = 'RELATED_TIME_SERIES',\n", " DatasetName = project + \"_rts\",\n", " DataFrequency = DATASET_FREQUENCY, \n", " Schema = schema\n", ")\n", "\n", "rts_dataset_arn = response['DatasetArn']" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'ResponseMetadata': {'RequestId': '9826c838-871d-4339-8bd8-d45eb0905115',\n", " 'HTTPStatusCode': 200,\n", " 'HTTPHeaders': {'content-type': 'application/x-amz-json-1.1',\n", " 'date': 'Sat, 20 Aug 2022 21:58:44 GMT',\n", " 'x-amzn-requestid': '9826c838-871d-4339-8bd8-d45eb0905115',\n", " 'content-length': '2',\n", " 'connection': 'keep-alive'},\n", " 'RetryAttempts': 0}}" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "forecast.update_dataset_group( \n", " DatasetGroupArn = dataset_group_arn, \n", " DatasetArns = [\n", " tts_dataset_arn,\n", " rts_dataset_arn,\n", " ]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Import the target time series data and related time series \n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Start the import task for TTS" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "response = forecast.create_dataset_import_job(\n", " DatasetImportJobName = project + \"_tts_import\",\n", " DatasetArn = tts_dataset_arn,\n", " DataSource = {\n", " \"S3Config\" : {\n", " \"Path\" : s3_data_path_tts,\n", " \"RoleArn\" : role_arn\n", " }\n", " },\n", " TimestampFormat = TIMESTAMP_FORMAT\n", ")\n", "\n", "tts_dataset_import_job_arn = response['DatasetImportJobArn']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Start the import task for RTS" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "response = forecast.create_dataset_import_job(\n", " DatasetImportJobName = project + \"_rts_import\",\n", " DatasetArn = rts_dataset_arn,\n", " DataSource = {\n", " \"S3Config\" : {\n", " \"Path\" : s3_data_path_rts,\n", " \"RoleArn\" : role_arn\n", " }\n", " },\n", " TimestampFormat = TIMESTAMP_FORMAT\n", ")\n", "\n", "rts_dataset_import_job_arn = response['DatasetImportJobArn']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Poll and wait on the import tasks, running in parallel, to complete." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Waiting for TTS dataset import job to become ACTIVE.\n", "Status:\n", "CREATE_PENDING ..\n", "CREATE_IN_PROGRESS .........\n", "ACTIVE \n", "Waiting for RTS dataset import job to become ACTIVE.\n", "Status:\n", "CREATE_IN_PROGRESS ....................\n", "ACTIVE \n" ] } ], "source": [ "print(f\"Waiting for TTS dataset import job to become ACTIVE.\\nStatus:\")\n", "status = util.wait(lambda: forecast.describe_dataset_import_job( DatasetImportJobArn = tts_dataset_import_job_arn ))\n", "\n", "print(f\"Waiting for RTS dataset import job to become ACTIVE.\\nStatus:\")\n", "status = util.wait(lambda: forecast.describe_dataset_import_job( DatasetImportJobArn = rts_dataset_import_job_arn ))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Predictor \n", "\n", "Creating a Predictor based on the dataset contents we imported. We will re-use this Predictor to generate two variasions of forecasts. Please read about the [Auto Predictor](https://github.com/aws-samples/amazon-forecast-samples/blob/main/library/content/AutoPredictor.md) to learn more." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Waiting for Predictor to become ACTIVE. Depending on data size and predictor setting,it can take several hours to be ACTIVE.\n", "\n", "Current Status:\n", "CREATE_PENDING \n", "CREATE_IN_PROGRESS .............................................................................................................................................................................................................................................................................................................\n", "ACTIVE \n", "\n", "\n", "The Predictor is now ACTIVE.\n" ] } ], "source": [ "PREDICTOR_NAME = project + \"_predictor\"\n", "forecast_horizon = 3\n", "\n", "create_auto_predictor_response = \\\n", " forecast.create_auto_predictor(PredictorName = PREDICTOR_NAME,\n", " ForecastHorizon = forecast_horizon,\n", " ForecastFrequency = DATASET_FREQUENCY,\n", " ForecastDimensions = [\"store_id\"],\n", " DataConfig = {\n", " 'DatasetGroupArn': dataset_group_arn\n", " })\n", "\n", "predictor_arn = create_auto_predictor_response['PredictorArn']\n", "print(f\"Waiting for Predictor to become ACTIVE. Depending on data size and predictor setting,it can take several hours to be ACTIVE.\\n\\nCurrent Status:\")\n", "\n", "status = util.wait(lambda: forecast.describe_auto_predictor(PredictorArn=predictor_arn))\n", "\n", "describe_auto_predictor_response = forecast.describe_auto_predictor(PredictorArn=predictor_arn)\n", "print(f\"\\n\\nThe Predictor is now {describe_auto_predictor_response['Status']}.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Forecast from the true/baseline values in related time series dataset \n", "\n", "Here, we call create_forecast() API to produce future dated predictions from the imported data, using the trained predictor model from the prior step. This example produces predictions at the mean quantile, thus 0.50 is used. You can choose additional quantiles to meet other scenarios." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Waiting for Baseline Forecast to become ACTIVE.\n", "\n", "Current Status:\n", "CREATE_PENDING ..\n", "CREATE_IN_PROGRESS .......................................................................\n", "ACTIVE \n" ] } ], "source": [ "response = forecast.create_forecast(\n", " ForecastName = project + \"_forecast_baseline\",\n", " ForecastTypes=['0.50'],\n", " PredictorArn = predictor_arn\n", ")\n", "\n", "forecast_arn = response['ForecastArn']\n", "\n", "print(f\"Waiting for Baseline Forecast to become ACTIVE.\\n\\nCurrent Status:\")\n", "\n", "status = util.wait(lambda: forecast.describe_forecast( ForecastArn = forecast_arn ))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The next cell demonstrates how it's possible to get singleton responses from the query_forecast() API. This API can be used for lightweight testing, but it not meant for long term, at-scale use. The dataset under this API is available for 30 days. The preferred way to obtain predictions is below in this notebook via the create_what_if_forecast_export() shown, or through the baseline [create_forecast_export_job()](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/forecast.html#ForecastService.Client.create_forecast_export_job)." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'Forecast': {'Predictions': {'p50': [{'Timestamp': '2019-10-01T00:00:00',\n", " 'Value': 327.3666229248},\n", " {'Timestamp': '2019-11-01T00:00:00', 'Value': 327.0994415283078},\n", " {'Timestamp': '2019-12-01T00:00:00', 'Value': 320.0594177245906}]}},\n", " 'ResponseMetadata': {'RequestId': '3d9d1f1f-de54-488f-a5e0-ddc61e78b31c',\n", " 'HTTPStatusCode': 200,\n", " 'HTTPHeaders': {'content-type': 'application/x-amz-json-1.1',\n", " 'date': 'Sat, 20 Aug 2022 23:08:21 GMT',\n", " 'x-amzn-requestid': '3d9d1f1f-de54-488f-a5e0-ddc61e78b31c',\n", " 'content-length': '221',\n", " 'connection': 'keep-alive'},\n", " 'RetryAttempts': 0}}" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "response = forecastquery.query_forecast(\n", " ForecastArn = forecast_arn,\n", " Filters = { \"item_id\" : 'item_001', \"store_id\": \"store_001\" }\n", " )\n", "\n", "response" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With a baseline forecast now created, it becomes possible to setup alternate, what-if scenarios and generate corresponding what-if forecasts. This baseline can be compared against the alternates allowing the business to decide a path forward." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create a Named What-If Analysis \n", "\n", "A what-if analysis becomes a container for child what-if forecast(s). What-if forecasts are differ from the forecast above in that they produce outputs that contain the natural forecasted values and the scenario models in the same record set. This makes it easy to compare the \"change\" as a result of manipulating the features in the related time series set.\n", "\n", "Please observe in the cell below how the What-If Analysis has a name and is attached to an existing baseline forecast in the new create_what_if_analysis() API call." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Waiting for What-if Analysis to become ACTIVE.\n", "\n", "Current Status:\n", "CREATE_PENDING .\n", "ACTIVE \n" ] } ], "source": [ "WhatIfAnalysisName = \"PricePromotionAnalysis\"\n", "\n", "response = forecast.create_what_if_analysis(\n", " WhatIfAnalysisName=WhatIfAnalysisName,\n", " ForecastArn=forecast_arn,\n", ")\n", "\n", "WhatIfAnalysisArn = response['WhatIfAnalysisArn']\n", "\n", "print(f\"Waiting for What-if Analysis to become ACTIVE.\\n\\nCurrent Status:\")\n", "\n", "status = util.wait(lambda: forecast.describe_what_if_analysis( WhatIfAnalysisArn = WhatIfAnalysisArn ))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inside the What-If Analysis, Create a What-If Forecast \n", " \n", "In the next two cells we will create two What-If Forecasts that run in parallel. One of these examples discounts a products by 10%, the other offers a 20% discount. To emphasize the point, What-if forecasts are different than baseline forecasts in that they produce outputs that contain the baseline forecasted values and the scenario-models in the same record. \n", "\n", "Having the original baseline value and the what-if value adjacent makes it easy to measure the change as a result of your scenario. Here, you make a judgement call if the change is something your business wants to adopt." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scenario 1: Create a 10% off all products, for time period September 1, 2019 and greater." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "WhatIfForecastName='simulate_10_pct_off'\n", "\n", "response = forecast.create_what_if_forecast(\n", " WhatIfForecastName=WhatIfForecastName,\n", " WhatIfAnalysisArn=WhatIfAnalysisArn,\n", " TimeSeriesTransformations=[\n", " {\n", " \"Action\": {\n", " \"AttributeName\": \"price\",\n", " \"Operation\": \"MULTIPLY\",\n", " \"Value\": 0.90\n", " },\n", " \"TimeSeriesConditions\": [\n", " {\n", " \"AttributeName\": \"timestamp\",\n", " \"AttributeValue\": \"2019-09-01 00:00:00\",\n", " \"Condition\": \"GREATER_THAN\"\n", " }\n", " ]\n", " }\n", " ]\n", ")\n", "\n", "WhatIfForecastArn_10Pct = response['WhatIfForecastArn']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scenario 2: Create a 20% off product item_001, for time period September 1, 2019 and greater." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "WhatIfForecastName='simulate_20_pct_off'\n", "\n", "response = forecast.create_what_if_forecast(\n", " WhatIfForecastName=WhatIfForecastName,\n", " WhatIfAnalysisArn=WhatIfAnalysisArn,\n", " TimeSeriesTransformations=[\n", " {\n", " \"Action\": {\n", " \"AttributeName\": \"price\",\n", " \"Operation\": \"MULTIPLY\",\n", " \"Value\": 0.80\n", " },\n", " \"TimeSeriesConditions\": [\n", " {\n", " \"AttributeName\": \"timestamp\",\n", " \"AttributeValue\": \"2019-09-01 00:00:00\",\n", " \"Condition\": \"GREATER_THAN\"\n", " }\n", " ]\n", " }\n", " ]\n", ")\n", "\n", "WhatIfForecastArn_20Pct = response['WhatIfForecastArn']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Poll and wait for both of the what-if forecasts tasks to complete before moving forward." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Waiting for What-if Forecast to become ACTIVE.\n", "\n", "Current Status:\n", "CREATE_PENDING ..\n", "CREATE_IN_PROGRESS ..............................................................................................................................................................\n", "ACTIVE \n", "ACTIVE \n" ] } ], "source": [ "print(f\"Waiting for What-if Forecast to become ACTIVE.\\n\\nCurrent Status:\")\n", "\n", "status = util.wait(lambda: forecast.describe_what_if_forecast(WhatIfForecastArn=WhatIfForecastArn_10Pct ))\n", "\n", "status = util.wait(lambda: forecast.describe_what_if_forecast(WhatIfForecastArn=WhatIfForecastArn_20Pct ))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Export the What-If Forecast " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scenario 1 Export data to S3" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "scenario = 'simulate_10_pct_off'\n", "\n", "s3_data_path_export = \"s3://\" + bucket_name + \"/\" + project + \"/\" + scenario + \"/\"\n", "\n", "response = forecast.create_what_if_forecast_export(\n", " WhatIfForecastExportName=scenario,\n", " WhatIfForecastArns=[WhatIfForecastArn_10Pct],\n", " Destination={\n", " \"S3Config\": {\n", " \"Path\": s3_data_path_export,\n", " \"RoleArn\": role_arn,\n", " }\n", " }\n", ")\n", "\n", "WhatIfForecastExportArn_10Pct= response['WhatIfForecastExportArn']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scenario 2 Export data to S3" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "scenario = 'simulate_20_pct_off'\n", "\n", "s3_data_path_export = \"s3://\" + bucket_name + \"/\" + project + \"/\" + scenario + \"/\"\n", "\n", "response = forecast.create_what_if_forecast_export(\n", " WhatIfForecastExportName=scenario,\n", " WhatIfForecastArns=[WhatIfForecastArn_20Pct],\n", " Destination={\n", " \"S3Config\": {\n", " \"Path\": s3_data_path_export,\n", " \"RoleArn\": role_arn,\n", " }\n", " }\n", ")\n", "\n", "WhatIfForecastExportArn_20Pct= response['WhatIfForecastExportArn']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Poll for concurrent export jobs to complete" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Waiting for What-if Forecast Export to become ACTIVE.\n", "\n", "Current Status:\n", "CREATE_PENDING .\n", "CREATE_IN_PROGRESS .........................\n", "ACTIVE \n", "CREATE_IN_PROGRESS ................................................................\n", "ACTIVE \n" ] } ], "source": [ "print(f\"Waiting for What-if Forecast Export to become ACTIVE.\\n\\nCurrent Status:\")\n", "status = util.wait(lambda: forecast.describe_what_if_forecast_export(WhatIfForecastExportArn=WhatIfForecastExportArn_10Pct ))\n", "status = util.wait(lambda: forecast.describe_what_if_forecast_export(WhatIfForecastExportArn=WhatIfForecastExportArn_20Pct ))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Download and Visualize " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scenario 1:View tabular results of baseline forecast vs. 10% off forecast. Note the expected [increase] in demand after the 10% off discount. " ] }, { "cell_type": "code", "execution_count": 34, "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", "
Baseline_p50simulate_10_pct_off_p50
dateitem_idstore_id
2019-10-01T00:00:00Zitem_001store_001327.366623382.596756
2019-11-01T00:00:00Zitem_001store_001327.099442381.938004
2019-12-01T00:00:00Zitem_001store_001320.059418375.966370
\n", "
" ], "text/plain": [ " Baseline_p50 simulate_10_pct_off_p50\n", "date item_id store_id \n", "2019-10-01T00:00:00Z item_001 store_001 327.366623 382.596756\n", "2019-11-01T00:00:00Z item_001 store_001 327.099442 381.938004\n", "2019-12-01T00:00:00Z item_001 store_001 320.059418 375.966370" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scenario = 'simulate_10_pct_off'\n", "\n", "s3_key = project + \"/\" + scenario + \"/\"\n", "\n", "#get an arbirary list of 5 files from S3\n", "response = s3.list_objects_v2(\n", " Bucket=bucket_name,\n", " EncodingType='url',\n", " MaxKeys=100,\n", " Prefix=s3_key\n", ")\n", "\n", "df1 = []\n", "\n", "# if files are CSV, open, display and plot -- not for actual use, but as an example of art of possible\n", "for i in response['Contents']:\n", " \n", " if i['Key'].endswith('.csv'):\n", " \n", " obj = s3.get_object(Bucket=bucket_name, Key=i['Key'])\n", " csv = pd.read_csv(io.BytesIO(obj['Body'].read()))\n", " df1.append(csv)\n", "\n", "df1 = pd.concat(df1)\n", "df1 = df1[(df1['item_id']=='item_001') & (df1['store_id']=='store_001')]\n", "df1 = df1.set_index(keys=['date','item_id','store_id'])\n", "df1.head(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scenario 2:View tabular results of baseline forecast vs. 20% off forecast" ] }, { "cell_type": "code", "execution_count": 35, "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", "
simulate_20_pct_off_p50Baseline_p50
dateitem_idstore_id
2019-10-01T00:00:00Zitem_001store_001442.034897327.366623
2019-11-01T00:00:00Zitem_001store_001439.336487327.099442
2019-12-01T00:00:00Zitem_001store_001434.691040320.059418
\n", "
" ], "text/plain": [ " simulate_20_pct_off_p50 Baseline_p50\n", "date item_id store_id \n", "2019-10-01T00:00:00Z item_001 store_001 442.034897 327.366623\n", "2019-11-01T00:00:00Z item_001 store_001 439.336487 327.099442\n", "2019-12-01T00:00:00Z item_001 store_001 434.691040 320.059418" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scenario = 'simulate_20_pct_off'\n", "\n", "s3_key = project + \"/\" + scenario + \"/\"\n", "\n", "#get an arbirary list of 5 files from S3\n", "response = s3.list_objects_v2(\n", " Bucket=bucket_name,\n", " EncodingType='url',\n", " MaxKeys=100,\n", " Prefix=s3_key\n", ")\n", "\n", "df2 = []\n", "\n", "# if files are CSV, open, display and plot -- not for actual use, but as an example of art of possible\n", "for i in response['Contents']:\n", " \n", " if i['Key'].endswith('.csv'):\n", " \n", " obj = s3.get_object(Bucket=bucket_name, Key=i['Key'])\n", " csv = pd.read_csv(io.BytesIO(obj['Body'].read()))\n", " df2.append(csv)\n", "\n", "df2 = pd.concat(df2)\n", "df2 = df2[(df2['item_id']=='item_001') & (df2['store_id']=='store_001')]\n", "df2 = df2.set_index(keys=['date','item_id','store_id'])\n", "df2.head(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Join the two what-if dataframes and add a placeholder demand column" ] }, { "cell_type": "code", "execution_count": 36, "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", "
Baseline_p50simulate_10_pct_off_p50simulate_20_pct_off_p50demand
dateitem_idstore_id
2019-10-01T00:00:00Zitem_001store_001327.366623382.596756442.034897NaN
2019-11-01T00:00:00Zitem_001store_001327.099442381.938004439.336487NaN
2019-12-01T00:00:00Zitem_001store_001320.059418375.966370434.691040NaN
\n", "
" ], "text/plain": [ " Baseline_p50 \\\n", "date item_id store_id \n", "2019-10-01T00:00:00Z item_001 store_001 327.366623 \n", "2019-11-01T00:00:00Z item_001 store_001 327.099442 \n", "2019-12-01T00:00:00Z item_001 store_001 320.059418 \n", "\n", " simulate_10_pct_off_p50 \\\n", "date item_id store_id \n", "2019-10-01T00:00:00Z item_001 store_001 382.596756 \n", "2019-11-01T00:00:00Z item_001 store_001 381.938004 \n", "2019-12-01T00:00:00Z item_001 store_001 375.966370 \n", "\n", " simulate_20_pct_off_p50 demand \n", "date item_id store_id \n", "2019-10-01T00:00:00Z item_001 store_001 442.034897 NaN \n", "2019-11-01T00:00:00Z item_001 store_001 439.336487 NaN \n", "2019-12-01T00:00:00Z item_001 store_001 434.691040 NaN " ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df2.drop(columns='Baseline_p50', inplace=True)\n", "df_whatif = pd.concat([df1, df2], axis=1)\n", "df_whatif['demand'] = np.nan\n", "df_whatif.head(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Prepare a historical view of data, the true demand for a named item and store. This dataframe will be merged with the what-if dataframe, then plotted." ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages/pandas/core/frame.py:4308: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", " errors=errors,\n", "/home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages/ipykernel/__main__.py:4: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame.\n", "Try using .loc[row_indexer,col_indexer] = value instead\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n" ] } ], "source": [ "df_history = df_tts[(df_tts['item_id']=='item_001') & (df_tts['store_id']=='store_001')]\n", "\n", "df_history.rename(columns={'timestamp': 'date'}, inplace=True)\n", "df_history['date'] = df_history['date'] + 'T00:00:00Z'\n", "\n", "df_history = df_history.set_index(keys=['date','item_id','store_id'])\n", "\n", "df_history['Baseline_p50'] = np.nan\n", "df_history['simulate_20_pct_off_p50'] = np.nan\n", "df_history['simulate_10_pct_off_p50'] = np.nan" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Join the history dataframe with the predictions -- baseline and two what-if scenarios." ] }, { "cell_type": "code", "execution_count": 46, "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", "
dateitem_idstore_iddemandBaseline_p50simulate_20_pct_off_p50simulate_10_pct_off_p50
212019-07-01T00:00:00Zitem_001store_001315.759707NaNNaNNaN
222019-08-01T00:00:00Zitem_001store_001320.044658NaNNaNNaN
232019-09-01T00:00:00Zitem_001store_001325.455626NaNNaNNaN
242019-10-01T00:00:00Zitem_001store_001NaN327.366623442.034897382.596756
252019-11-01T00:00:00Zitem_001store_001NaN327.099442439.336487381.938004
262019-12-01T00:00:00Zitem_001store_001NaN320.059418434.691040375.966370
\n", "
" ], "text/plain": [ " date item_id store_id demand Baseline_p50 \\\n", "21 2019-07-01T00:00:00Z item_001 store_001 315.759707 NaN \n", "22 2019-08-01T00:00:00Z item_001 store_001 320.044658 NaN \n", "23 2019-09-01T00:00:00Z item_001 store_001 325.455626 NaN \n", "24 2019-10-01T00:00:00Z item_001 store_001 NaN 327.366623 \n", "25 2019-11-01T00:00:00Z item_001 store_001 NaN 327.099442 \n", "26 2019-12-01T00:00:00Z item_001 store_001 NaN 320.059418 \n", "\n", " simulate_20_pct_off_p50 simulate_10_pct_off_p50 \n", "21 NaN NaN \n", "22 NaN NaN \n", "23 NaN NaN \n", "24 442.034897 382.596756 \n", "25 439.336487 381.938004 \n", "26 434.691040 375.966370 " ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_history = df_history.append(df_whatif)\n", "df_history = df_history.reset_index()\n", "df_history.tail(6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Visualize\n", "\n", "\n", "In the graphic below, historic demand is in blue as 'demand'. With the price unchanged, the Baseline P50 estimate nearly matches the historic demand. In the green line, there is clear increase with a 10% off simulation as 'simulate_20_pct_off_p50'. Finally, the red line at top shows an even larger increase in demand at a 20% off price." ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3sAAAE+CAYAAAA0xwkVAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAABeEElEQVR4nO3deXzU1b3/8deZyb5BCAkkBAirbIEQAmhBRBG0VVFcihtqa92q19Z7qWJd21/xamt7LXWrdaNKq3WhqHVDBSuI0gTDIousSdhDAiFkn8z5/TGTIUCABDKZmfB+Ph7zmJnvfJfPjMcw7znf7znGWouIiIiIiIi0L45AFyAiIiIiIiKtT2FPRERERESkHVLYExERERERaYcU9kRERERERNohhT0REREREZF2SGFPRERERESkHfJ72DPGOI0x3xhj3vM+f9gYs80Yk++9/aDRuvcaYzYYY9YZY87zd20iIiIiIiLtVVgbHONnwBogodGy/7PWPt54JWPMIOBKYDCQBnxijOlvra0/2o47d+5sMzIyWr9iERERERGREJCXl7fHWpvc1Gt+DXvGmHTgAmAm8N/HWf1i4DVrbQ2w2RizARgFLDnaBhkZGeTm5rZWuSIiIiIiIiHFGFNwtNf8fRrnE8DdgPuw5XcYY1YYY140xiR6l3UDihqts9W7TERERERERFrIb2HPGHMhsNtam3fYS88AfYAsYAfw+4ZNmtiNbWK/Nxtjco0xucXFxa1YsYiIiIiISPvhz569McBkY8wW4DXgHGPMq9baXdbaemutG/gLnlM1wdOT173R9unA9sN3aq19zlqbY63NSU5u8tRUERERERGRU57frtmz1t4L3AtgjBkPTLfWXmuMSbXW7vCuNgVY5X38DvA3Y8wf8AzQ0g9Y2tLj1tXVsXXrVqqrq0/yHYi0TFRUFOnp6YSHhwe6FBERERGRNhmN83C/NcZk4TlFcwtwC4C19ltjzD+A1YALuP1YI3EezdatW4mPjycjIwNjmjozVKT1WWspKSlh69at9OrVK9DliIiIiIi0Tdiz1i4EFnofTzvGejPxjNx5wqqrqxX0pM0ZY0hKSkLXkYqIiIhIsPD7pOqBoKAngaB2JyIiIiLBpF2GvWDz8MMP8/jjjx9/RT/LyMhgz549gS5DRERERETagMKeiIiIiIhIO6Sw5yczZ87ktNNO49xzz2XdunUAbNy4kfPPP58RI0Zw5plnsnbtWgBuuOEGbrvtNs4++2x69+7N559/zo9//GMGDhzIDTfc4NvnbbfdRk5ODoMHD+ahhx7yLc/IyOChhx4iOzubzMxM335LSkqYNGkSw4cP55ZbbsHaI6YtFBERERE5YXtfe5398+dTtXIldbt3Y93uQJckjQRiNM52Ly8vj9dee41vvvkGl8tFdnY2I0aM4Oabb+bZZ5+lX79+fP311/z0pz/ls88+A2Dv3r189tlnvPPOO1x00UUsXryY559/npEjR5Kfn09WVhYzZ86kU6dO1NfXM2HCBFasWMHQoUMB6Ny5M8uWLePpp5/m8ccf5/nnn+dXv/oVY8eO5cEHH+Rf//oXzz33XCA/FhERERFpR9w1Nex8+OFDF4aFEZ6SQlhqKuFduhCW2pXwLl099109N2dSEsahPqe20K7D3q/e/ZbV2/e36j4HpSXw0EWDj7nOF198wZQpU4iJiQFg8uTJVFdX8+WXX3LFFVf41qupqfE9vuiiizDGkJmZSZcuXcjMzARg8ODBbNmyhaysLP7xj3/w3HPP4XK52LFjB6tXr/aFvUsvvRSAESNG8PbbbwPw73//2/f4ggsuIDExsZU+BRERERE51ZmICPot+RLXjh3U7dxF3c4duBrdV61aheuTT7C1tYduGB7uCYTe8BfWtYsCoZ+067AXSIePzOh2u+nYsSP5+flNrh8ZGQmAw+HwPW547nK52Lx5M48//jj/+c9/SExM5IYbbjhk4viGbZxOJy6X66h1iIiIiIi0BmMMYYmJhCUmEjVoUJPrWGup37sX186d1Hlvrh07qdvlua9auRLX/PnNC4RdU733XYno0QNnhw5t8C5DW7sOe8frgfOXcePGccMNNzBjxgxcLhfvvvsut9xyC7169eKNN97giiuuwFrLihUrGDZsWLP2uX//fmJjY+nQoQO7du3igw8+YPz48cetY86cOdx///188MEH7N27txXenYiIiIhI8xhjCOvUibBOnVo9EIanpRE5aCBRAwcSNXAQUYMGEtalizo7GmnXYS9QsrOzmTp1KllZWfTs2ZMzzzwTgDlz5nDbbbfxm9/8hrq6Oq688spmh71hw4YxfPhwBg8eTO/evRkzZsxxt3nooYe46qqryM7O5qyzzqJHjx4n9b5ERERERFpbSwJh3Y4duHbupGbTJmrWrKF69RoOfPoZeAcidHbqRNSAAUQNGkikNwRGZPQ8ZU8JNaE8QmNOTo7Nzc09ZNmaNWsYOHBggCqSU53an4iIiEjbqj9QQc1366hevYbqNaupXrOGmvUboK4OABMTQ9Rpp3l6AL0hMLJfPxwREQGuvHUYY/KstTlNvaaePRERERERCVnOuFhisrOJyc72LbO1tdRs3OgNgJ5b2T//yd6//c2zQng4kX36eE8B9YbAAQNwxsUF6F34h8KeiIiIiIi0KyYiwhfkGli3m7rCQk/484bAA//+N2Vz5/rWCe/Zw3P9nzcARg0cSFjnzoF4C61CYU9ERERERNo943AQkZFBREYGCd//PuC5FtC1u5jqNat91wBWr1pF+Ycf+rYLS072DQST9JObcMbFBuottJjCnoiIiIiInJKMMYR3SSG8SwrxjUa6r9+/n+o1aw8JgaX/ySX59tsDV+wJUNgTERERERFpxJmQQOzoUcSOHuVbZmtrMeHhAayq5U7NMUhFRERERERawITg6J0KeyIiIiIiIu2Qwp4fOJ1OsrKyGDZsGNnZ2Xz55Zetuv8bbriBN998E4Cf/OQnrF69ulX3fzQN7ysrK4vJkyf7lm/evJnRo0fTr18/pk6dSm1tbZvUIyIiIiIiR6ew5wfR0dHk5+ezfPly/vd//5d7773Xb8d6/vnnGTRokN/231jD+8rPz+edd97xLb/nnnu46667WL9+PYmJibzwwgttUo+IiIiIiBydwp6f7d+/n8TERAAOHDjAhAkTyM7OJjMzk3nz5gFQUVHBBRdcwLBhwxgyZAivv/46AHl5eZx11lmMGDGC8847jx07dhyx//Hjx5ObmwtAXFwc9913H8OGDeP0009n165dABQXF3PZZZcxcuRIRo4cyeLFi49a78MPP8y0adM455xz6NevH3/5y1+O+f6stXz22WdcfvnlAFx//fX885//bNmHJCIiIiIira59j8b5wQzYubJ199k1E77/6DFXqaqqIisri+rqanbs2MFnn30GQFRUFHPnziUhIYE9e/Zw+umnM3nyZD788EPS0tL417/+BUBZWRl1dXX813/9F/PmzSM5OZnXX3+d++67jxdffPGox62oqOD0009n5syZ3H333fzlL3/h/vvv52c/+xl33XUXY8eOpbCwkPPOO481a9YcdT8rVqzgq6++oqKiguHDh3PBBReQlpZGdXU1OTk5hIWFMWPGDC655BJKSkro2LEjYWGeppSens62bdta+qmKiIiIiEgra99hL0AaTncEWLJkCddddx2rVq3CWssvf/lL/v3vf+NwONi2bRu7du0iMzOT6dOnc88993DhhRdy5plnsmrVKlatWsXEiRMBqK+vJzU19ZjHjYiI4MILLwRgxIgRzJ8/H4BPPvnkkOv69u/fT3l5OfHx8U3u5+KLLyY6Opro6GjOPvtsli5dyiWXXEJhYSFpaWls2rSJc845h8zMTBISEo7Y3hjT4s9MRERERERaV/sOe8fpgWsLZ5xxBnv27KG4uJj333+f4uJi8vLyCA8PJyMjg+rqavr3709eXh7vv/8+9957L5MmTWLKlCkMHjyYJUuWNPtY4eHhvqDldDpxuVwAuN1ulixZQnR0dLP2c3hYa3ielpYGQO/evRk/fjzffPMNl112Gfv27cPlchEWFsbWrVt964mIiIiISODomj0/W7t2LfX19SQlJVFWVkZKSgrh4eEsWLCAgoICALZv305MTAzXXnst06dPZ9myZZx22mkUFxf7wl5dXR3ffvvtCdUwadIknnzySd/zhl7Ho5k3bx7V1dWUlJSwcOFCRo4cyd69e6mpqQFgz549LF68mEGDBmGM4eyzz/aNDjp79mwuvvjiE6pTRERERERaT/vu2QuQhmv2wDOAyezZs3E6nVxzzTVcdNFF5OTkkJWVxYABAwBYuXIlv/jFL3A4HISHh/PMM88QERHBm2++yZ133klZWRkul4uf//znDB48uMX1zJo1i9tvv52hQ4ficrkYN24czz777FHXHzVqFBdccAGFhYU88MADpKWl8eWXX3LLLbfgcDhwu93MmDHDNwroY489xpVXXsn999/P8OHDufHGG1v+oYmIiIiISKsy1tpA13DCcnJybMNIlA3WrFnDwIEDA1RR6Hv44YeJi4tj+vTpgS4lJKn9iYiIiEhbMsbkWWtzmnpNp3GKiIiIiIi0QzqN8xT10ksv8cc//vGQZWPGjOGpp54KUEUiIiIiItKaFPZOUT/60Y/40Y9+FOgyRERERETET3Qap4iIiASlsqq6QJcgIhLSFPZEREQk6Owur2bkbz5hXv62QJciIhKyFPZEREQk6GzYdYDaejf/yC0KdCkiIiFLYU9ERESCTmFpJQBfbixh9/7qAFcjIhKaFPbayE9+8hNWr17dKvvKyMhgz549x1znkUceOeH933fffXTv3p24uLhDltfU1DB16lT69u3L6NGj2bJlywkf43ALFy7kyy+/PKFti4uLGT16NMOHD+eLL77gjTfeYODAgZx99tkt3ldGRgaZmZlkZWWRk3NwupLS0lImTpxIv379mDhxInv37j2hWkVEpHkKSisxBqyF91bsCHQ5IiIhSWGvjTz//PMMGjSozY53MmHvoosuYunSpUcsf+GFF0hMTGTDhg3cdddd3HPPPSdT4iFOJux9+umnDBgwgG+++YYzzzyTF154gaeffpoFCxac0P4WLFhAfn4+ubm5vmWPPvooEyZMYP369UyYMIFHH330hPYtIiLNU1haSc9OMQxMTWDe8u2BLkdEJCS166kXHlv6GGtL17bqPgd0GsA9o44dcioqKvjhD3/I1q1bqa+v54EHHuCZZ57h8ccfJycnh7i4OG6//XY++eQTEhMTeeSRR7j77rspLCzkiSeeYPLkybz88svk5uby5JNPAnDhhRcyffp0xo8ff8ixLrnkEoqKiqiuruZnP/sZN998MzNmzKCqqoqsrCwGDx7MnDlzePXVV5k1axa1tbWMHj2ap59+GqfT2WT9p59+epPL582bx8MPPwzA5Zdfzh133IG1FmPMEesuXLiQBx98kKSkJNatW8e4ceN4+umncTgcfPjhh/zyl7+kvr6ezp0788ILL/Dss8/idDp59dVX+dOf/sSZZ555xD4LCgr48Y9/THFxMcnJybz00kuUlpZy9913+97vlClTWLRoEZs3b2by5Mn87ne/O2I/L7/8MnPnzqWmpobNmzdz9dVX89BDDx3rPynz5s1j4cKFAFx//fWMHz+exx577JjbiIjIiSsqraR7pxjG9O3Mox+spaCkgp5JsYEuS0QkpKhnzw8+/PBD0tLSWL58OatWreL8888/5PWKigrGjx9PXl4e8fHx3H///cyfP5+5c+fy4IMPtuhYL774Inl5eeTm5jJr1ixKSkp49NFHiY6OJj8/nzlz5rBmzRpef/11Fi9eTH5+Pk6nkzlz5rT4fW3bto3u3bsDEBYWRocOHSgpKTnq+kuXLuX3v/89K1euZOPGjbz99tsUFxdz00038dZbb7F8+XLeeOMNMjIyuPXWW7nrrrvIz89vMugB3HHHHVx33XWsWLGC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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "df_history.plot(x='date', y=['demand', 'Baseline_p50','simulate_10_pct_off_p50',\n", " 'simulate_20_pct_off_p50'], kind ='line', figsize=(15,5))\n", "plt.xlabel('time')\n", "plt.ylabel('demand')\n", "plt.show() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Resource cleanup \n", " This needs to be un-commented for clean-up" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Delete datasets and dataset group\n", "util.wait_till_delete(lambda: forecast.delete_resource_tree(ResourceArn = dataset_group_arn))\n", "util.wait_till_delete(lambda: forecast.delete_resource_tree(ResourceArn = rts_dataset_arn))\n", "util.wait_till_delete(lambda: forecast.delete_resource_tree(ResourceArn = tts_dataset_arn))\n", "\n", "util.wait_till_delete(lambda: forecast.delete_resource_tree(ResourceArn = WhatIfAnalysisArn))" ] } ], "metadata": { "kernelspec": { "display_name": "conda_python3", "language": "python", "name": "conda_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.6.13" } }, "nbformat": 4, "nbformat_minor": 2 }