{"id":1452,"date":"2025-01-27T07:04:45","date_gmt":"2025-01-27T07:04:45","guid":{"rendered":"https:\/\/mailitics.com\/index.php\/2025\/01\/27\/optimising-budgets-with-marketing-mix-models-in-python-d14f50622453\/"},"modified":"2025-01-27T07:04:45","modified_gmt":"2025-01-27T07:04:45","slug":"optimising-budgets-with-marketing-mix-models-in-python-d14f50622453","status":"publish","type":"post","link":"https:\/\/mailitics.com\/index.php\/2025\/01\/27\/optimising-budgets-with-marketing-mix-models-in-python-d14f50622453\/","title":{"rendered":"Optimising Budgets With Marketing Mix Models In Python"},"content":{"rendered":"<p>    Optimising Budgets With Marketing Mix Models In Python<br \/>\n \t<BR><br \/>\n<BR><\/BR><br \/>\n    <!-- no image --><br \/>\n \t<BR><br \/>\n<BR><\/BR><\/p>\n<div>\n<h4>Part 3 of a hands-on guide to help you master MMM in\u00a0pymc<\/h4>\n<figure><img decoding=\"async\" alt=\"\" src=\"https:\/\/cdn-images-1.medium.com\/max\/1024\/0*M0er8jWDM4n3JMu1\"><figcaption>Photo by <a href=\"https:\/\/unsplash.com\/@towfiqu999999?utm_source=medium&amp;utm_medium=referral\">Towfiqu barbhuiya<\/a> on\u00a0<a href=\"https:\/\/unsplash.com\/?utm_source=medium&amp;utm_medium=referral\">Unsplash<\/a><\/figcaption><\/figure>\n<h3>What is this series\u00a0about?<\/h3>\n<p>Welcome to part 3 of my series on marketing mix modelling (MMM), a hands-on guide to help you master MMM. Throughout this series, we\u2019ll cover key topics such as model training, validation, calibration and budget optimisation, all using the powerful <strong>pymc-marketing<\/strong> python package. Whether you\u2019re new to MMM or looking to sharpen your skills, this series will equip you with practical tools and insights to improve your marketing strategies.<\/p>\n<p>If you missed part 2 check it out\u00a0here:<\/p>\n<p><a href=\"https:\/\/towardsdatascience.com\/calibrating-marketing-mix-models-in-python-49dce1a5b33d\">Calibrating Marketing Mix Models In Python<\/a><\/p>\n<h3>Introduction<\/h3>\n<p>In the third instalment of the series we are going to cover how we can start to get business value from our marketing mix models by covering the following areas:<\/p>\n<ul>\n<li>Why do organisations want to optimise their marketing budgets?<\/li>\n<li>How can we use the outputs of our marketing mix model to optimise\u00a0budgets?<\/li>\n<li>A python walkthrough demonstrating how to optimise budgets using <strong>pymc-marketing<\/strong>.<\/li>\n<\/ul>\n<p>The full notebook can be found\u00a0here:<\/p>\n<p><a href=\"https:\/\/github.com\/raz1470\/pymc_marketing\/blob\/main\/notebooks\/3.%20optimising%20budgets%20with%20marketing%20mix%20models%20(MMM)%20in%20python.ipynb\">pymc_marketing\/notebooks\/3. optimising budgets with marketing mix models (MMM) in python.ipynb at main \u00b7 raz1470\/pymc_marketing<\/a><\/p>\n<h3>1.0 Why do organisations want to optimise their marketing budgets?<\/h3>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2Aan01PT9XJ3whZ20uTPRVYw.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>This famous quote (from John Wanamaker I think?!) illustrates both the challenge and opportunity in marketing. While modern analytics have come a long way, the challenge remains relevant: understanding which parts of your marketing budget deliver\u00a0value.<\/p>\n<p>Marketing channels can vary significantly in terms of their performance and ROI due to several\u00a0factors:<\/p>\n<ul>\n<li>\n<strong>Audience Reach and Engagement\u200a\u2014\u200a<\/strong>Some channels are more effective at reaching specific prospects aligned to your target audience.<\/li>\n<li>\n<strong>Cost of Acquisition\u200a\u2014\u200a<\/strong>The cost of reaching prospects differs between channels.<\/li>\n<li>\n<strong>Channel Saturation\u200a\u2014\u200a<\/strong>Overuse of a marketing channel can lead to diminishing returns.<\/li>\n<\/ul>\n<p>This variability creates the opportunity to ask critical questions that can transform your marketing strategy:<\/p>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/890\/1%2AfL-1tkKEOF0d9ruuVYvFlQ.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>Effective budget optimisation is a critical component of modern marketing strategies. By leveraging the outputs of MMM, businesses can make informed decisions about where to allocate their resources for maximum impact. MMM provides insights into how various channels contribute to overall sales, allowing us to identify opportunities for improvement and optimisation. In the following sections, we will explore how we can translate MMM outputs into actionable budget allocation strategies.<\/p>\n<h4>2.1 Response\u00a0curves<\/h4>\n<p>A response curve can translate the outputs of MMM into a comprehensive form, showing how sales responds to spend for each marketing channel.<\/p>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2AaHZIss7-EI2APxGjZfKQpQ.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>Response curves alone are very powerful, allowing us to run what-if scenarios. Using the response curve above as an example, we could estimate how the sales contribution from social changes as we spend more. We can also visually see where diminishing returns starts to take effect. But what if we want to try and answer more complex what-if scenarios like optimising channel level budgets given a fixed overall budget? This is where linear programming comes in\u200a\u2014\u200aLet\u2019s explore this in the next\u00a0section!<\/p>\n<h4>2.2 Linear programming<\/h4>\n<p>Linear programming is an optimisation method which can be used to find the optimal solution of a linear function given some constraints. It\u2019s a very versatile tool from the operations research area but doesn\u2019t often get the recognition it deserves. It is used to solve scheduling, transportation and resource allocation problems. We are going to explore how we can use it to optimise marketing budgets.<\/p>\n<p>Let\u2019s try and understand linear programming with a simple budget optimisation problem:<\/p>\n<ul>\n<li>\n<strong>Decision variables (x):<\/strong> These are the unknown quantities which we want to estimate optimal values for e.g. The marketing spend on each\u00a0channel.<\/li>\n<li>\n<strong>Objective function (Z): <\/strong>The linear equation we are trying to minimise or maximise e.g. Maximising the sum of the sales contribution from each\u00a0channel.<\/li>\n<li>\n<strong>Constraints: <\/strong>Some restrictions on the decision variables, usually represented by linear inequalities e.g. Total marketing budget is equal to \u00a350m, Channel level budgets between \u00a35m and\u00a0\u00a315m.<\/li>\n<\/ul>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2ArxnfcxRfGKezMdbtH5aXqw.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>The intersection of all constraints forms a feasible region, which is the set of all possible solutions that satisfy the given constraints. The goal of linear programming is to find the point within the feasible region that optimises the objective function.<\/p>\n<p>Given the saturation transformation we apply to each marketing channel, optimising channel level budgets is actually a non-linear programming problem. Sequential Least Squares Programming (SLSQP) is an algorithm used for solving non-linear programming problems. It allows for both equality and inequality constraints making it a sensible choice for our use\u00a0case.<\/p>\n<ul>\n<li>\n<strong>Equality constraints<\/strong> e.g. Total marketing budget is equal to\u00a0\u00a350m<\/li>\n<li>\n<strong>Inequality constraints<\/strong> e.g. Channel level budgets between \u00a35m and\u00a0\u00a315m<\/li>\n<\/ul>\n<p>SciPy have a great implementation of\u00a0SLSQP:<\/p>\n<p><a href=\"https:\/\/docs.scipy.org\/doc\/scipy\/tutorial\/optimize.html\">Optimization (scipy.optimize) &#8211; SciPy v1.14.1 Manual<\/a><\/p>\n<p>The example below illustrates how we could use\u00a0it:<\/p>\n<pre>from scipy.optimize import minimize<br><br>result = minimize(<br>    fun=objective_function,  # Define your ROI function here<br>    x0=initial_guess,        # Initial guesses for spends<br>    bounds=bounds,           # Channel-level budget constraints<br>    constraints=constraints, # Equality and inequality constraints<br>    method='SLSQP'<br>)<br>print(result)<\/pre>\n<p>Writing budget optimisation code from scratch is a complex but very rewarding exercise. Fortunately, the <strong>pymc-marketing<\/strong> team has done the heavy lifting, providing a robust framework for running budget optimisation scenarios. In the next section, we\u2019ll explore how their package can streamline the budget allocation process and make it more accessible to analysts.<\/p>\n<h3>3.0 Python walkthrough<\/h3>\n<p>Now we understand how we can use the output of MMM to optimise budgets, let\u2019s see how much value we can drive using our model from the last article! In this walkthrough we will\u00a0cover:<\/p>\n<ul>\n<li>Simulating data<\/li>\n<li>Training the\u00a0model<\/li>\n<li>Validating the\u00a0model<\/li>\n<li>Response curves<\/li>\n<li>Budget optimisation<\/li>\n<\/ul>\n<h4>3.1 Simulating data<\/h4>\n<p>We are going to re-use the data-generating process from the first article. If you want a reminder on the data-generating process, take a look at the first article where we did a detailed walkthrough:<\/p>\n<p><a href=\"https:\/\/towardsdatascience.com\/mastering-marketing-mix-modelling-in-python-7bbfe31360f9\">Mastering Marketing Mix Modelling In Python<\/a><\/p>\n<pre>np.random.seed(10)<br><br># Set parameters for data generator<br>start_date = \"2021-01-01\"<br>periods = 52 * 3<br>channels = [\"tv\", \"social\", \"search\"]<br>adstock_alphas = [0.50, 0.25, 0.05]<br>saturation_lamdas = [1.5, 2.5, 3.5]<br>betas = [350, 150, 50]<br>spend_scalars = [10, 15, 20]<br><br>df = dg.data_generator(start_date, periods, channels, spend_scalars, adstock_alphas, saturation_lamdas, betas)<br><br># Scale betas using maximum sales value - this is so it is comparable to the fitted beta from pymc (pymc does feature and target scaling using MaxAbsScaler from sklearn)<br>betas_scaled = [<br>    ((df[\"tv_sales\"] \/ df[\"sales\"].max()) \/ df[\"tv_saturated\"]).mean(),<br>    ((df[\"social_sales\"] \/ df[\"sales\"].max()) \/ df[\"social_saturated\"]).mean(),<br>    ((df[\"search_sales\"] \/ df[\"sales\"].max()) \/ df[\"search_saturated\"]).mean()<br>]<br><br># Calculate contributions<br>contributions = np.asarray([<br>    round((df[\"tv_sales\"].sum() \/ df[\"sales\"].sum()), 2),<br>    round((df[\"social_sales\"].sum() \/ df[\"sales\"].sum()), 2),<br>    round((df[\"search_sales\"].sum() \/ df[\"sales\"].sum()), 2),<br>    round((df[\"demand\"].sum() \/ df[\"sales\"].sum()), 2)<br>])<br><br>df[[\"date\", \"demand\", \"demand_proxy\", \"tv_spend_raw\", \"social_spend_raw\", \"search_spend_raw\", \"sales\"]]<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2AjdEZvrZPdwxf8g-IocIqOQ.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<h4>3.2 Training the\u00a0model<\/h4>\n<p>We are now going to re-train the model from the first article. We will prepare the training data in the same way as last time\u00a0by:<\/p>\n<ul>\n<li>Splitting data into features and\u00a0target.<\/li>\n<li>Creating indices for train and out-of-time slices.<\/li>\n<\/ul>\n<p>However, as the focus of this article is not on model calibration, we are going to include demand as a control variable rather than demand_proxy. This means the model will be very well calibrated\u200a\u2014\u200aAlthough this isn\u2019t very realistic, it will give us some good results to illustrate how we can optimise\u00a0budgets.<\/p>\n<pre># set date column<br>date_col = \"date\"<br><br># set outcome column<br>y_col = \"sales\"<br><br># set marketing variables<br>channel_cols = [\"tv_spend_raw\",<br>                \"social_spend_raw\",<br>                \"search_spend_raw\"]<br><br># set control variables<br>control_cols = [\"demand\"]<br><br># create arrays<br>X = df[[date_col] + channel_cols + control_cols]<br>y = df[y_col]<br><br># set test (out-of-sample) length<br>test_len = 8<br><br># create train and test indexs<br>train_idx = slice(0, len(df) - test_len)<br>out_of_time_idx = slice(len(df) - test_len, len(df))<br><br>mmm_default = MMM(<br>    adstock=GeometricAdstock(l_max=8),<br>    saturation=LogisticSaturation(),<br>    date_column=date_col,<br>    channel_columns=channel_cols,<br>    control_columns=control_cols,<br>)<br><br>fit_kwargs = {<br>    \"tune\": 1_000,<br>    \"chains\": 4,<br>    \"draws\": 1_000,<br>    \"target_accept\": 0.9,<br>}<br><br>mmm_default.fit(X[train_idx], y[train_idx], **fit_kwargs)<\/pre>\n<h4>3.3 Validating the\u00a0model<\/h4>\n<p>Before we get into the optimisation, lets check our model fits well. First we check the true contributions:<\/p>\n<pre>channels = np.array([\"tv\", \"social\", \"search\", \"demand\"])<br><br>true_contributions = pd.DataFrame({'Channels': channels, 'Contributions': contributions})<br>true_contributions= true_contributions.sort_values(by='Contributions', ascending=False).reset_index(drop=True)<br>true_contributions = true_contributions.style.bar(subset=['Contributions'], color='lightblue')<br><br>true_contributions<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/708\/1%2AbDpuhXOwF89CyRBH_jzOxQ.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>As expected, our model aligns very closely to the true contributions:<\/p>\n<pre>mmm_default.plot_waterfall_components_decomposition(figsize=(10,6));<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2A2-y8-FiSlJJVXjo1GBFfZQ.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<h4>3.4 Response\u00a0curves<\/h4>\n<p>Before we get into the budget optimisation, let\u2019s take a look at the response curves. There are two ways to look at response curves in the <strong>pymc-marketing<\/strong> package:<\/p>\n<ol>\n<li>Direct response\u00a0curves<\/li>\n<li>Cost share response\u00a0curves<\/li>\n<\/ol>\n<p>Let\u2019s start with the direct response curves. In the direct response curves we simply create a scatter plot of weekly spend against weekly contribution for each\u00a0channel.<\/p>\n<p>Below we plot the direct response\u00a0curves:<\/p>\n<pre>fig = mmm_default.plot_direct_contribution_curves(show_fit=True, xlim_max=1.2)<br>[ax.set(xlabel=\"spend\") for ax in fig.axes];<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2AHGX5jsMHePGkdFhwRYFGlA.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>The cost share response curves are an alternative way of comparing the effectiveness of channels. When \u03b4 = 1.0, the channel spend remains at the same level as the training data. When \u03b4 = 1.2, the channel spend is increased by\u00a020%.<\/p>\n<p>Below we plot the cost share response\u00a0curves:<\/p>\n<pre>mmm_default.plot_channel_contributions_grid(start=0, stop=1.5, num=12, figsize=(15, 7));<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2AFTELRKHVhj6oMkwVBdqVcQ.png?ssl=1\"><figcaption>user generated image<\/figcaption><\/figure>\n<p>We can also change the x-axis to show absolute spend\u00a0values:<\/p>\n<pre>mmm_default.plot_channel_contributions_grid(start=0, stop=1.5, num=12, absolute_xrange=True, figsize=(15, 7));<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2AWTrndwxF5te0bDrfIMLl9g.png?ssl=1\"><figcaption>user generated image<\/figcaption><\/figure>\n<p>The response curves are great tools to help think about planning future marketing budgets at a channel level. Next lets put them to action and run some budget optimisation scenarios!<\/p>\n<h4>3.5 Budget optimisation<\/h4>\n<p>To begin with let\u2019s set a couple of parameters:<\/p>\n<ul>\n<li>\n<strong>perc_change:<\/strong> This is used to set the constraint around min and max spend on each channel. This constraint helps us keep the scenario realistic and means we don\u2019t extrapolate response curves too far outside of what the model has seen in training.<\/li>\n<li>\n<strong>budget_len:<\/strong> This is the length of the budget scenario in\u00a0weeks.<\/li>\n<\/ul>\n<p>We will start by using the desired length of the budget scenario to select the most recent period of\u00a0data.<\/p>\n<pre>perc_change = 0.20<br>budget_len = 12<br>budget_idx = slice(len(df) - test_len, len(df))<br>recent_period = X[budget_idx][channel_cols]<br><br>recent_period<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2ApJ9q5py9EP8yKx2Yf6gZRg.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>We then use this recent period to set overall budget constraints and channel constraints at a weekly\u00a0level:<\/p>\n<pre># set overall budget constraint (to the nearest \u00a31k)<br>budget = round(recent_period.sum(axis=0).sum() \/ budget_len, -3)<br><br># record the current budget split by channel<br>current_budget_split = round(recent_period.mean() \/ recent_period.mean().sum(), 2)<br><br># set channel level constraints<br>lower_bounds = round(recent_period.min(axis=0) * (1 - perc_change))<br>upper_bounds = round(recent_period.max(axis=0) * (1 + perc_change))<br><br>budget_bounds = {<br>    channel: [lower_bounds[channel], upper_bounds[channel]]<br>    for channel in channel_cols<br>}<br><br>print(f'Overall budget constraint: {budget}')<br>print('Channel constraints:')<br>for channel, bounds in budget_bounds.items():<br>    print(f'  {channel}: Lower Bound = {bounds[0]}, Upper Bound = {bounds[1]}')<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1024\/1%2AQPyAoPX8KHGFPfJpUW_L8w.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>Now it\u2019s time to run our scenario! We feed in the relevant data and parameters and get back the optimal spend. We compare it to taking the total budget and splitting it by the current budget split proportions (which we have called actual\u00a0spend).<\/p>\n<pre>model_granularity = \"weekly\"<br><br># run scenario<br>allocation_strategy, optimization_result = mmm_default.optimize_budget(<br>    budget=budget,<br>    num_periods=budget_len,<br>    budget_bounds=budget_bounds,<br>    minimize_kwargs={<br>        \"method\": \"SLSQP\",<br>        \"options\": {\"ftol\": 1e-9, \"maxiter\": 5_000},<br>    },<br>)<br><br>response = mmm_default.sample_response_distribution(<br>    allocation_strategy=allocation_strategy,<br>    time_granularity=model_granularity,<br>    num_periods=budget_len,<br>    noise_level=0.05,<br>)<br><br># extract optimal spend<br>opt_spend = pd.Series(allocation_strategy, index=recent_period.mean().index).to_frame(name=\"opt_spend\")<br>opt_spend[\"avg_spend\"] = budget * current_budget_split<br><br># plot actual vs optimal spend<br>fig, ax = plt.subplots(figsize=(9, 4))<br>opt_spend.plot(kind='barh', ax=ax, color=['blue', 'orange'])<br><br>plt.xlabel(\"Spend\")<br>plt.ylabel(\"Channel\")<br>plt.title(\"Actual vs Optimal Spend by Channel\")<br>plt.legend([\"Optimal Spend\", \"Actual Spend\"])<br>plt.legend([\"Optimal Spend\", \"Actual Spend\"], loc='lower right', bbox_to_anchor=(1.5, 0.0))<br><br>plt.show()<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/1000\/1%2AJwT2svozTj_lP3CvVF-Ryw.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>We can see the suggestion is to move budget from digital channels to TV. But what is the impact on\u00a0sales?<\/p>\n<p>To calculate the contribution of the optimal spend we need to feed in the new spend value per channel plus any other variables in the model. We only have demand, so we feed in the mean value from the recent period for this. We will also calculate the contribution of the average spend in the same\u00a0way.<\/p>\n<pre># create dataframe with optimal spend<br>last_date = mmm_default.X[\"date\"].max()<br>new_dates = pd.date_range(start=last_date, periods=1 + budget_len, freq=\"W-MON\")[1:]<br>budget_scenario_opt = pd.DataFrame({\"date\": new_dates,})<br>budget_scenario_opt[\"tv_spend_raw\"] = opt_spend[\"opt_spend\"][\"tv_spend_raw\"]<br>budget_scenario_opt[\"social_spend_raw\"] = opt_spend[\"opt_spend\"][\"social_spend_raw\"]<br>budget_scenario_opt[\"search_spend_raw\"] = opt_spend[\"opt_spend\"][\"search_spend_raw\"]<br>budget_scenario_opt[\"demand\"] = X[budget_idx][control_cols].mean()[0]<br><br># calculate overall contribution<br>scenario_contrib_opt = mmm_default.sample_posterior_predictive(<br>    X_pred=budget_scenario_opt, extend_idata=False<br>)<br><br>opt_contrib = scenario_contrib_opt.mean(dim=\"sample\").sum()[\"y\"].values<br><br># create dataframe with avg spend<br>last_date = mmm_default.X[\"date\"].max()<br>new_dates = pd.date_range(start=last_date, periods=1 + budget_len, freq=\"W-MON\")[1:]<br>budget_scenario_avg = pd.DataFrame({\"date\": new_dates,})<br>budget_scenario_avg[\"tv_spend_raw\"] = opt_spend[\"avg_spend\"][\"tv_spend_raw\"]<br>budget_scenario_avg[\"social_spend_raw\"] = opt_spend[\"avg_spend\"][\"social_spend_raw\"]<br>budget_scenario_avg[\"search_spend_raw\"] = opt_spend[\"avg_spend\"][\"search_spend_raw\"]<br>budget_scenario_avg[\"demand\"] = X[budget_idx][control_cols].mean()[0]<br><br># calculate overall contribution<br>scenario_contrib_avg  = mmm_default.sample_posterior_predictive(<br>    X_pred=budget_scenario_avg , extend_idata=False<br>)<br><br>avg_contrib = scenario_contrib_avg.mean(dim=\"sample\").sum()[\"y\"].values<br><br># calculate % increase in sales<br>print(f'% increase in sales: {round((opt_contrib \/ avg_contrib) - 1, 2)}')<\/pre>\n<figure><img data-recalc-dims=\"1\" decoding=\"async\" alt=\"\" src=\"https:\/\/i0.wp.com\/cdn-images-1.medium.com\/max\/592\/1%2AgO1njT_VaNoVM4_3mE1jIQ.png?ssl=1\"><figcaption>User generated image<\/figcaption><\/figure>\n<p>The optimal spend gives us a 6% increase in sales! That\u2019s impressive especially given we have fixed the overall\u00a0budget!<\/p>\n<h3>Closing thoughts<\/h3>\n<p>Today we have seen how powerful budget optimisation can be. It can help organisations with monthly\/quarterly\/yearly budget planning and forecasting. As always the key to making good recommendations comes back to having a robust, well calibrated model.<\/p>\n<p>I hope you enjoyed the third instalment! That\u2019s it for this series on mastering MMM. However, stay tuned if you want to learn about the complex topic of measuring long-term brand building\u00a0effects!<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/medium.com\/_\/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=d14f50622453\" width=\"1\" height=\"1\" alt=\"\"><\/p>\n<hr>\n<p><a href=\"https:\/\/towardsdatascience.com\/optimising-budgets-with-marketing-mix-models-in-python-d14f50622453\">Optimising Budgets With Marketing Mix Models In Python<\/a> was originally published in <a href=\"https:\/\/towardsdatascience.com\/\">Towards Data Science<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>\n<\/div>\n<p> \t<BR><br \/>\n <BR><\/BR><br \/>\n    Ryan O&#8217;Sullivan<br \/>\n \t<BR><br \/>\n<BR><\/BR><br \/>\n<a href=\"https:\/\/medium.com\/m\/global-identity-2?redirectUrl=https%3A%2F%2Ftowardsdatascience.com%2Foptimising-budgets-with-marketing-mix-models-in-python-d14f50622453\">Go to original source<\/a><br \/>\n \t<BR><br \/>\n <BR><\/BR><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Optimising Budgets With Marketing Mix Models In Python Part 3 of a hands-on guide to help you master MMM in\u00a0pymc Photo by Towfiqu barbhuiya on\u00a0Unsplash What is this series\u00a0about? Welcome to part 3 of my series on marketing mix modelling (MMM), a hands-on guide to help you master MMM. Throughout this series, we\u2019ll cover key [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[62,210,83,176,1480,160],"tags":[1481,1444,1482],"class_list":["post-1452","post","type-post","status-publish","format-standard","hentry","category-aimldsaimlds","category-causal-inference","category-data-science","category-marketing","category-marketing-mix-modeling","category-programming","tag-budgets","tag-marketing","tag-mix"],"_links":{"self":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/posts\/1452"}],"collection":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/comments?post=1452"}],"version-history":[{"count":0,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/posts\/1452\/revisions"}],"wp:attachment":[{"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/media?parent=1452"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/categories?post=1452"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mailitics.com\/index.php\/wp-json\/wp\/v2\/tags?post=1452"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}