{ "cells": [ { "cell_type": "markdown", "id": "02c22456", "metadata": {}, "source": [ "# Logging Visualizations with MLflow\n", "\n", "In this part of the guide, we emphasize the **importance of logging visualizations with MLflow**. Retaining visualizations alongside trained models enhances model interpretability, auditing, and provenance, ensuring a robust and transparent ML model development workflow.\n", "\n", "#### What Are We Doing?\n", "- **Storing Visual Artifacts:** We are logging various plots as visual artifacts in MLflow, ensuring that they are always accessible and aligned with the corresponding model and run data.\n", "- **Enhancing Model Interpretability:** These visualizations aid in understanding and explaining model behavior, contributing to improved model transparency and accountability.\n", "\n", "#### How Does It Apply to MLflow?\n", "- **Integrated Visualization Logging:** MLflow seamlessly integrates facilities for logging and accessing visual artifacts, enhancing the ease and efficiency of handling visual context and insights.\n", "- **Convenient Access:** Logged figures are displayable within the Runs view pane in the MLflow UI, ensuring quick and easy access for analysis and review.\n", "\n", "#### Caution\n", "While MLflow offers simplicity and convenience for logging visualizations, it's crucial to ensure the consistency and relevance of the visual artifacts with the corresponding model data, maintaining the integrity and comprehensiveness of the model information.\n", "\n", "#### Why Is Consistent Logging Important?\n", "- **Auditing and Provenance:** Consistent and comprehensive logging of visualizations is pivotal for auditing purposes, ensuring that every model is accompanied by relevant visual insights for thorough analysis and review.\n", "- **Enhanced Model Understanding:** Proper visual context enhances the understanding of model behavior, aiding in effective model evaluation, and validation.\n", "\n", "In conclusion, MLflow's capabilities for visualization logging play an invaluable role in ensuring a comprehensive, transparent, and efficient ML model development workflow, reinforcing model interpretability, auditing, and provenance.\n" ] }, { "cell_type": "markdown", "id": "63e20328", "metadata": {}, "source": [ "### Generating Synthetic Apple Sales Data\n", "\n", "In this next section, we dive into **generating synthetic data for apple sales demand prediction** using the `generate_apple_sales_data_with_promo_adjustment` function. This function simulates a variety of features relevant to apple sales, providing a rich dataset for exploration and modeling.\n", "\n", "#### What Are We Doing?\n", "- **Simulating Realistic Data:** Generating a dataset with features like date, average temperature, rainfall, weekend flag, and more, simulating realistic scenarios for apple sales.\n", "- **Incorporating Various Effects:** The function incorporates effects like promotional adjustments, seasonality, and competitor pricing, contributing to the 'demand' target variable.\n", "\n", "#### How Does It Apply to Data Generation?\n", "- **Comprehensive Dataset:** The synthetic dataset provides a comprehensive set of features and interactions, ideal for exploring diverse aspects and dimensions for demand prediction.\n", "- **Freedom and Flexibility:** The synthetic nature allows for unconstrained exploration and analysis, devoid of real-world data sensitivities and constraints.\n", "\n", "#### Caution\n", "While synthetic data offers numerous advantages for exploration and learning, it's crucial to acknowledge its limitations in capturing real-world complexities and nuances.\n", "\n", "#### Why Is Acknowledging Limitations Important?\n", "- **Real-World Complexities:** Synthetic data may not capture all the intricate patterns and anomalies present in real-world data, potentially leading to over-simplified models and insights.\n", "- **Transferability to Real-World Scenarios:** Ensuring that insights and models derived from synthetic data are transferable to real-world scenarios requires careful consideration and validation.\n", "\n", "In conclusion, the `generate_apple_sales_data_with_promo_adjustment` function offers a robust tool for generating a comprehensive synthetic dataset for apple sales demand prediction, facilitating extensive exploration, and analysis while acknowledging the limitations of synthetic data.\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "7b060ff8", "metadata": {}, "outputs": [], "source": [ "import math\n", "import pathlib\n", "from datetime import datetime, timedelta\n", "\n", "import matplotlib.pylab as plt\n", "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "from scipy import stats\n", "from sklearn.linear_model import Ridge\n", "from sklearn.metrics import (\n", " mean_absolute_error,\n", " mean_squared_error,\n", " mean_squared_log_error,\n", " median_absolute_error,\n", " r2_score,\n", ")\n", "from sklearn.model_selection import train_test_split\n", "\n", "import mlflow\n", "\n", "\n", "def generate_apple_sales_data_with_promo_adjustment(\n", " base_demand: int = 1000,\n", " n_rows: int = 5000,\n", " competitor_price_effect: float = -50.0,\n", "):\n", " \"\"\"\n", " Generates a synthetic dataset for predicting apple sales demand with multiple\n", " influencing factors.\n", "\n", " This function creates a pandas DataFrame with features relevant to apple sales.\n", " The features include date, average_temperature, rainfall, weekend flag, holiday flag,\n", " promotional flag, price_per_kg, competitor's price, marketing intensity, stock availability,\n", " and the previous day's demand. The target variable, 'demand', is generated based on a\n", " combination of these features with some added noise.\n", "\n", " Args:\n", " base_demand (int, optional): Base demand for apples. Defaults to 1000.\n", " n_rows (int, optional): Number of rows (days) of data to generate. Defaults to 5000.\n", " competitor_price_effect (float, optional): Effect of competitor's price being lower\n", " on our sales. Defaults to -50.\n", "\n", " Returns:\n", " pd.DataFrame: DataFrame with features and target variable for apple sales prediction.\n", "\n", " Example:\n", " >>> df = generate_apple_sales_data_with_promo_adjustment(base_demand=1200, n_rows=6000)\n", " >>> df.head()\n", " \"\"\"\n", "\n", " # Set seed for reproducibility\n", " np.random.seed(9999)\n", "\n", " # Create date range\n", " dates = [datetime.now() - timedelta(days=i) for i in range(n_rows)]\n", " dates.reverse()\n", "\n", " # Generate features\n", " df = pd.DataFrame({\n", " \"date\": dates,\n", " \"average_temperature\": np.random.uniform(10, 35, n_rows),\n", " \"rainfall\": np.random.exponential(5, n_rows),\n", " \"weekend\": [(date.weekday() >= 5) * 1 for date in dates],\n", " \"holiday\": np.random.choice([0, 1], n_rows, p=[0.97, 0.03]),\n", " \"price_per_kg\": np.random.uniform(0.5, 3, n_rows),\n", " \"month\": [date.month for date in dates],\n", " })\n", "\n", " # Introduce inflation over time (years)\n", " df[\"inflation_multiplier\"] = 1 + (df[\"date\"].dt.year - df[\"date\"].dt.year.min()) * 0.03\n", "\n", " # Incorporate seasonality due to apple harvests\n", " df[\"harvest_effect\"] = np.sin(2 * np.pi * (df[\"month\"] - 3) / 12) + np.sin(\n", " 2 * np.pi * (df[\"month\"] - 9) / 12\n", " )\n", "\n", " # Modify the price_per_kg based on harvest effect\n", " df[\"price_per_kg\"] = df[\"price_per_kg\"] - df[\"harvest_effect\"] * 0.5\n", "\n", " # Adjust promo periods to coincide with periods lagging peak harvest by 1 month\n", " peak_months = [4, 10] # months following the peak availability\n", " df[\"promo\"] = np.where(\n", " df[\"month\"].isin(peak_months),\n", " 1,\n", " np.random.choice([0, 1], n_rows, p=[0.85, 0.15]),\n", " )\n", "\n", " # Generate target variable based on features\n", " base_price_effect = -df[\"price_per_kg\"] * 50\n", " seasonality_effect = df[\"harvest_effect\"] * 50\n", " promo_effect = df[\"promo\"] * 200\n", "\n", " df[\"demand\"] = (\n", " base_demand\n", " + base_price_effect\n", " + seasonality_effect\n", " + promo_effect\n", " + df[\"weekend\"] * 300\n", " + np.random.normal(0, 50, n_rows)\n", " ) * df[\"inflation_multiplier\"] # adding random noise\n", "\n", " # Add previous day's demand\n", " df[\"previous_days_demand\"] = df[\"demand\"].shift(1)\n", " df[\"previous_days_demand\"].fillna(method=\"bfill\", inplace=True) # fill the first row\n", "\n", " # Introduce competitor pricing\n", " df[\"competitor_price_per_kg\"] = np.random.uniform(0.5, 3, n_rows)\n", " df[\"competitor_price_effect\"] = (\n", " df[\"competitor_price_per_kg\"] < df[\"price_per_kg\"]\n", " ) * competitor_price_effect\n", "\n", " # Stock availability based on past sales price (3 days lag with logarithmic decay)\n", " log_decay = -np.log(df[\"price_per_kg\"].shift(3) + 1) + 2\n", " df[\"stock_available\"] = np.clip(log_decay, 0.7, 1)\n", "\n", " # Marketing intensity based on stock availability\n", " # Identify where stock is above threshold\n", " high_stock_indices = df[df[\"stock_available\"] > 0.95].index\n", "\n", " # For each high stock day, increase marketing intensity for the next week\n", " for idx in high_stock_indices:\n", " df.loc[idx : min(idx + 7, n_rows - 1), \"marketing_intensity\"] = np.random.uniform(0.7, 1)\n", "\n", " # If the marketing_intensity column already has values, this will preserve them;\n", " # if not, it sets default values\n", " fill_values = pd.Series(np.random.uniform(0, 0.5, n_rows), index=df.index)\n", " df[\"marketing_intensity\"].fillna(fill_values, inplace=True)\n", "\n", " # Adjust demand with new factors\n", " df[\"demand\"] = df[\"demand\"] + df[\"competitor_price_effect\"] + df[\"marketing_intensity\"]\n", "\n", " # Drop temporary columns\n", " df.drop(\n", " columns=[\n", " \"inflation_multiplier\",\n", " \"harvest_effect\",\n", " \"month\",\n", " \"competitor_price_effect\",\n", " \"stock_available\",\n", " ],\n", " inplace=True,\n", " )\n", "\n", " return df" ] }, { "cell_type": "markdown", "id": "883a47ee", "metadata": {}, "source": [ "### Generating Apple Sales Data\n", "\n", "In this cell, we call the `generate_apple_sales_data_with_promo_adjustment` function to generate a dataset of apple sales. \n", "\n", "#### Parameters Used:\n", "- `base_demand`: Set to 1000, representing the baseline demand for apples.\n", "- `n_rows`: Set to 10,000, determining the number of rows or data points in the generated dataset.\n", "- `competitor_price_effect`: Set to -25.0, representing the impact on our sales when the competitor's price is lower.\n", "\n", "By running this cell, we obtain a dataset `my_data`, which holds the synthetic apple sales data with the aforementioned configurations. This dataset will be used for further exploration and analysis in subsequent steps of this notebook.\n", "\n", "You can see the data in the cell after the generation cell." ] }, { "cell_type": "code", "execution_count": 3, "id": "e687b85c", "metadata": {}, "outputs": [], "source": [ "my_data = generate_apple_sales_data_with_promo_adjustment(\n", " base_demand=1000, n_rows=10_000, competitor_price_effect=-25.0\n", ")" ] }, { "cell_type": "code", "execution_count": 4, "id": "d585dbbe", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
| \n", " | date | \n", "average_temperature | \n", "rainfall | \n", "weekend | \n", "holiday | \n", "price_per_kg | \n", "promo | \n", "demand | \n", "previous_days_demand | \n", "competitor_price_per_kg | \n", "marketing_intensity | \n", "
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | \n", "1996-05-11 13:10:40.689999 | \n", "30.584727 | \n", "1.831006 | \n", "1 | \n", "0 | \n", "1.578387 | \n", "1 | \n", "1301.647352 | \n", "1326.324266 | \n", "0.755725 | \n", "0.323086 | \n", "
| 1 | \n", "1996-05-12 13:10:40.689999 | \n", "15.465069 | \n", "0.761303 | \n", "1 | \n", "0 | \n", "1.965125 | \n", "0 | \n", "1143.972638 | \n", "1326.324266 | \n", "0.913934 | \n", "0.030371 | \n", "
| 2 | \n", "1996-05-13 13:10:40.689998 | \n", "10.786525 | \n", "1.427338 | \n", "0 | \n", "0 | \n", "1.497623 | \n", "0 | \n", "890.319248 | \n", "1168.942267 | \n", "2.879262 | \n", "0.354226 | \n", "
| 3 | \n", "1996-05-14 13:10:40.689997 | \n", "23.648154 | \n", "3.737435 | \n", "0 | \n", "0 | \n", "1.952936 | \n", "0 | \n", "811.206168 | \n", "889.965021 | \n", "0.826015 | \n", "0.953000 | \n", "
| 4 | \n", "1996-05-15 13:10:40.689997 | \n", "13.861391 | \n", "5.598549 | \n", "0 | \n", "0 | \n", "2.059993 | \n", "0 | \n", "822.279469 | \n", "835.253168 | \n", "1.130145 | \n", "0.953000 | \n", "
| ... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
| 9995 | \n", "2023-09-22 13:10:40.682895 | \n", "23.358868 | \n", "7.061220 | \n", "0 | \n", "0 | \n", "1.556829 | \n", "1 | \n", "1981.195884 | \n", "2089.644454 | \n", "0.560507 | \n", "0.889971 | \n", "
| 9996 | \n", "2023-09-23 13:10:40.682895 | \n", "14.859048 | \n", "0.868655 | \n", "1 | \n", "0 | \n", "1.632918 | \n", "0 | \n", "2180.698138 | \n", "2005.305913 | \n", "2.460766 | \n", "0.884467 | \n", "
| 9997 | \n", "2023-09-24 13:10:40.682894 | \n", "17.941035 | \n", "13.739986 | \n", "1 | \n", "0 | \n", "0.827723 | \n", "1 | \n", "2675.093671 | \n", "2179.813671 | \n", "1.321922 | \n", "0.884467 | \n", "
| 9998 | \n", "2023-09-25 13:10:40.682893 | \n", "14.533862 | \n", "1.610512 | \n", "0 | \n", "0 | \n", "0.589172 | \n", "0 | \n", "1703.287285 | \n", "2674.209204 | \n", "2.604095 | \n", "0.812706 | \n", "
| 9999 | \n", "2023-09-26 13:10:40.682889 | \n", "13.048549 | \n", "5.287508 | \n", "0 | \n", "0 | \n", "1.794122 | \n", "1 | \n", "1971.029266 | \n", "1702.474579 | \n", "1.261635 | \n", "0.750458 | \n", "
10000 rows × 11 columns
\n", "