As you've been learning, analysis of variance (commonly called ANOVA) is a group of statistical techniques that test the difference of means among three or more groups. It's a powerful tool for determining whether population means are different across groups and for answering a wide range of business questions.
In this activity, you are a data professional working with historical marketing promotion data. You will use the data to run a one-way ANOVA and a post hoc ANOVA test. Then, you will communicate your results to stakeholders. These experiences will help you make more confident recommendations in a professional setting.
In your dataset, each row corresponds to an independent marketing promotion, where your business uses TV, social media, radio, and influencer promotions to increase sales. You have previously provided insights about how different promotion types affect sales; now stakeholders want to know if sales are significantly different among various TV and influencer promotion types.
To address this request, a one-way ANOVA test will enable you to determine if there is a statistically significant difference in sales among groups. This includes:
Import pandas, pyplot from matplotlib, seaborn, api from statsmodels, ols from statsmodels.formula.api, and pairwise_tukeyhsd from statsmodels.stats.multicomp.
# Import libraries and packages.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
from statsmodels.formula.api import ols
from statsmodels.stats.multicomp import pairwise_tukeyhsd
Pandas
was used to load the dataset marketing_sales_data.csv
as data
, now display the first five rows. The variables in the dataset have been adjusted to suit the objectives of this lab. As shown in this cell, the dataset has been automatically loaded in for you. You do not need to download the .csv file, or provide more code, in order to access the dataset and proceed with this lab. Please continue with this activity by completing the following instructions.
# RUN THIS CELL TO IMPORT YOUR DATA.
### YOUR CODE HERE ###
data = pd.read_csv('marketing_sales_data.csv')
# Display the first five rows.
data.head()
TV | Radio | Social Media | Influencer | Sales | |
---|---|---|---|---|---|
0 | Low | 1.218354 | 1.270444 | Micro | 90.054222 |
1 | Medium | 14.949791 | 0.274451 | Macro | 222.741668 |
2 | Low | 10.377258 | 0.061984 | Mega | 102.774790 |
3 | High | 26.469274 | 7.070945 | Micro | 328.239378 |
4 | High | 36.876302 | 7.618605 | Mega | 351.807328 |
The features in the data are:
Question: Why is it useful to perform exploratory data analysis before constructing a linear regression model?
Potential reasons include:
First, use a boxplot to determine how Sales
vary based on the TV
promotion budget category.
# Create a boxplot with TV and Sales.
sns.boxplot(data=data, x='TV', y='Sales')
<matplotlib.axes._subplots.AxesSubplot at 0x7f1dbab77d10>
There is a function in the seaborn
library that creates a boxplot showing the distribution of a variable across multiple groups.
Use the boxplot()
function from seaborn
.
Use TV
as the x
argument, Sales
as the y
argument, and data
as the data
argument.
Question: Is there variation in Sales
based off the TV
promotion budget?
There is considerable variation in Sales
across the TV
groups. The significance of these differences can be tested with a one-way ANOVA.
Now, use a boxplot to determine how Sales
vary based on the Influencer
size category.
# Create a boxplot with Influencer and Sales.
sns.boxplot(data=data, x='Influencer', y='Sales')
<matplotlib.axes._subplots.AxesSubplot at 0x7f1db8ad6290>
Question: Is there variation in Sales
based off the Influencer
size?
There is some variation in Sales
across the Influencer
groups, but it may not be significant.
You may recall from prior labs that this dataset contains rows with missing values. To correct this, drop these rows. Then, confirm the data contains no missing values.
# Drop rows that contain missing data and update the DataFrame.
data = data.dropna(axis=0)
# Confirm the data contains no missing values.
data.isnull().sum()
TV 0 Radio 0 Social Media 0 Influencer 0 Sales 0 dtype: int64
There is a pandas
function that removes missing values.
The dropna()
function removes missing values from an object (e.g., DataFrame).
Verify the data is updated properly after the rows containing missing data are dropped.
Fit a linear regression model that predicts Sales
using one of the independent categorical variables in data
. Refer to your previous code for defining and fitting a linear regression model.
# Define the OLS formula.
formula = 'Sales ~ C(TV)'
# Create an OLS model.
OLS = ols(data=data, formula = formula)
# Fit the model.
model = OLS.fit()
# Save the results summary.
summary = model.summary()
# Display the model results.
summary
Dep. Variable: | Sales | R-squared: | 0.874 |
---|---|---|---|
Model: | OLS | Adj. R-squared: | 0.874 |
Method: | Least Squares | F-statistic: | 1971. |
Date: | Tue, 05 Sep 2023 | Prob (F-statistic): | 8.81e-256 |
Time: | 23:38:58 | Log-Likelihood: | -2778.9 |
No. Observations: | 569 | AIC: | 5564. |
Df Residuals: | 566 | BIC: | 5577. |
Df Model: | 2 | ||
Covariance Type: | nonrobust |
coef | std err | t | P>|t| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
Intercept | 300.5296 | 2.417 | 124.360 | 0.000 | 295.783 | 305.276 |
C(TV)[T.Low] | -208.8133 | 3.329 | -62.720 | 0.000 | -215.353 | -202.274 |
C(TV)[T.Medium] | -101.5061 | 3.325 | -30.526 | 0.000 | -108.038 | -94.975 |
Omnibus: | 450.714 | Durbin-Watson: | 2.002 |
---|---|---|---|
Prob(Omnibus): | 0.000 | Jarque-Bera (JB): | 35.763 |
Skew: | -0.044 | Prob(JB): | 1.71e-08 |
Kurtosis: | 1.775 | Cond. No. | 3.86 |
Refer to code you've written to fit linear regression models.
Use the ols()
function from statsmodels.formula.api
, which creates a model from a formula and DataFrame, to create an OLS model.
Use C()
around the variable name in the ols formula to indicate a variable is categorical.
Be sure the variable string names exactly match the column names in data
.
Question: Which categorical variable did you choose for the model? Why?
TV
was selected as the preceding analysis showed a strong relationship between the TV
promotion budget and the average Sales
.Influencer
was not selected because it did not show a strong relationship to Sales
in the analysis.Now, check the four linear regression assumptions are upheld for your model.
Question: Is the linearity assumption met?
Because your model does not have any continuous independent variables, the linearity assumption is not required.
The independent observation assumption states that each observation in the dataset is independent. As each marketing promotion (row) is independent from one another, the independence assumption is not violated.
Next, verify that the normality assumption is upheld for the model.
# Calculate the residuals.
res = model.resid
# Create a histogram with the residuals.
sns.histplot(res)
# Create a QQ plot of the residuals.
sm.qqplot(res, line='s')
plt.show()
Access the residuals from the fit model object.
Use model.resid
to get the residuals from a fit model called model
.
For the histogram, pass the residuals as the first argument in the seaborn
histplot()
function.
For the QQ-plot, pass the residuals as the first argument in the statsmodels
qqplot()
function.
Question: Is the normality assumption met?
There is reasonable concern that the normality assumption is not met when TV
is used as the independent variable predicting Sales
. The normal q-q forms an 'S' that deviates off the red diagonal line, which is not desired behavior.
However, for the purpose of the lab, continue assuming the normality assumption is met.
Now, verify the constant variance (homoscedasticity) assumption is met for this model.
# Create a scatter plot with the fitted values from the model and the residuals.
sns.scatterplot(model.fittedvalues, res).axhline(0, color='red')
# Add a line at y = 0 to visualize the variance of residuals above and below 0.
plt.show()
Access the fitted values from the model object fit earlier.
Use model.fittedvalues
to get the fitted values from the fit model called model
.
Call the scatterplot()
function from the seaborn
library and pass in the fitted values and residuals.
Add a line to a figure using the axline()
function.
Question: Is the constant variance (homoscedasticity) assumption met?
The variance where there are fitted values is similarly distributed, validating that the constant variance assumption is met.
First, display the OLS regression results.
# Display the model results summary.
summary
Dep. Variable: | Sales | R-squared: | 0.874 |
---|---|---|---|
Model: | OLS | Adj. R-squared: | 0.874 |
Method: | Least Squares | F-statistic: | 1971. |
Date: | Tue, 05 Sep 2023 | Prob (F-statistic): | 8.81e-256 |
Time: | 23:38:58 | Log-Likelihood: | -2778.9 |
No. Observations: | 569 | AIC: | 5564. |
Df Residuals: | 566 | BIC: | 5577. |
Df Model: | 2 | ||
Covariance Type: | nonrobust |
coef | std err | t | P>|t| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
Intercept | 300.5296 | 2.417 | 124.360 | 0.000 | 295.783 | 305.276 |
C(TV)[T.Low] | -208.8133 | 3.329 | -62.720 | 0.000 | -215.353 | -202.274 |
C(TV)[T.Medium] | -101.5061 | 3.325 | -30.526 | 0.000 | -108.038 | -94.975 |
Omnibus: | 450.714 | Durbin-Watson: | 2.002 |
---|---|---|---|
Prob(Omnibus): | 0.000 | Jarque-Bera (JB): | 35.763 |
Skew: | -0.044 | Prob(JB): | 1.71e-08 |
Kurtosis: | 1.775 | Cond. No. | 3.86 |
Question: What is your interpretation of the model's R-squared?
Using TV
as the independent variable results in a linear regression model with $R^{2} = 0.874$. In other words, the model explains $87.4\%$ of the variation in Sales
. This makes the model an effective predictor of Sales
.
Question: What is your intepretation of the coefficient estimates? Are the coefficients statistically significant?
The default TV
category for the model is High
, because there are coefficients for the other two TV
categories, Medium
and Low
. According to the model, Sales
with a Medium
or Low
TV
category are lower on average than Sales
with a High
TV
category. For example, the model predicts that a Low
TV
promotion would be 208.813 (in millions of dollars) lower in Sales
on average than a High
TV
promotion.
The p-value for all coefficients is $0.000$, meaning all coefficients are statistically significant at $p=0.05$. The 95% confidence intervals for each coefficient should be reported when presenting results to stakeholders. For instance, there is a $95\%$ chance the interval $[-215.353,-202.274]$ contains the true parameter of the slope of $\beta_{TVLow}$, which is the estimated difference in promotion sales when a Low
TV
promotion is chosen instead of a High
TV
promotion.
Question: Do you think your model could be improved? Why or why not? How?
Given how accurate TV
was as a predictor, the model could be improved with a more granular view of the TV
promotions, such as additional categories or the actual TV
promotion budgets. Further, additional variables, such as the location of the marketing campaign or the time of year, may increase model accuracy.
With the model fit, run a one-way ANOVA test to determine whether there is a statistically significant difference in Sales
among groups.
# Create an one-way ANOVA table for the fit model.
sm.stats.anova_lm(model, type=2)
df | sum_sq | mean_sq | F | PR(>F) | |
---|---|---|---|---|---|
C(TV) | 2.0 | 4.052692e+06 | 2.026346e+06 | 1971.455737 | 8.805550e-256 |
Residual | 566.0 | 5.817589e+05 | 1.027843e+03 | NaN | NaN |
Review what you've learned about how to perform a one-way ANOVA test.
There is a function in statsmodels.api
(i.e. sm
) that peforms an ANOVA test for a fit linear model.
Use the anova_lm()
function from sm.stats
. Specify the type of ANOVA test (for example, one-way or two-way), using the typ
parameter.
Question: What are the null and alternative hypotheses for the ANOVA test?
The null hypothesis is that there is no difference in Sales
based on the TV
promotion budget.
The alternative hypothesis is that there is a difference in Sales
based on the TV
promotion budget.
Question: What is your conclusion from the one-way ANOVA test?
The F-test statistic is 1971.46 and the p-value is $8.81 * 10^{-256}$ (i.e., very small). Because the p-value is less than 0.05, you would reject the null hypothesis that there is no difference in Sales
based on the TV
promotion budget.
Question: What did the ANOVA test tell you?
The results of the one-way ANOVA test indicate that you can reject the null hypothesis in favor of the alternative hypothesis. There is a statistically significant difference in Sales
among TV
groups.
If you have significant results from the one-way ANOVA test, you can apply ANOVA post hoc tests such as the Tukey’s HSD post hoc test.
Run the Tukey’s HSD post hoc test to compare if there is a significant difference between each pair of categories for TV.
# Perform the Tukey's HSD post hoc test.
tukey = pairwise_tukeyhsd(endog=data['Sales'], groups = data['TV'])
tukey.summary()
group1 | group2 | meandiff | p-adj | lower | upper | reject |
---|---|---|---|---|---|---|
High | Low | -208.8133 | 0.001 | -216.637 | -200.9896 | True |
High | Medium | -101.5061 | 0.001 | -109.3204 | -93.6918 | True |
Low | Medium | 107.3072 | 0.001 | 99.7063 | 114.908 | True |
Review what you've learned about how to perform a Tukey's HSD post hoc test.
Use the pairwise_tukeyhsd()
function from statsmodels.stats.multicomp
.
The endog
argument in pairwise_tukeyhsd
indicates which variable is being compared across groups (i.e., Sales
). The groups
argument in pairwise_tukeyhsd
tells the function which variable holds the group you’re interested in reviewing.
Question: What is your interpretation of the Tukey HSD test?
The first row, which compares the High
and Low
TV
groups, indicates that you can reject the null hypothesis that there is no significant difference between the Sales
of these two groups.
You can also reject the null hypotheses for the two other pairwise comparisons that compare High
to Medium
and Low
to Medium
.
Question: What did the post hoc tell you?
A post hoc test was conducted to determine which TV
groups are different and how many are different from each other. This provides more detail than the one-way ANOVA results, which can at most determine that at least one group is different. Further, using the Tukey HSD controls for the increasing probability of incorrectly rejecting a null hypothesis from peforming multiple tests.
The results were that Sales
is not the same between any pair of TV
groups.
What are some key takeaways that you learned during this lab?
What summary would you provide to stakeholders? Consider the statistical significance of key relationships and differences in distribution.
High TV promotion budgets result in significantly more sales than both medium and low TV promotion budgets. Medium TV promotion budgets result in significantly more sales than low TV promotion budgets.
Specifically, following are estimates for the difference between the mean sales resulting from different pairs of TV promotions, as determined by the Tukey's HSD test:
The linear regression model estimating Sales
from TV
had an R-squared of $0.871, making it a fairly accurate estimator. The model showed a statistically significant relationship between the TV
promotion budget and Sales
.
The results of the one-way ANOVA test indicate that the null hypothesis that there is no difference in Sales based on the TV promotion budget can be rejected. Through the ANOVA post hoc test, a significant difference between all pairs of TV promotions was found.
The difference in the distribution of sales across TV promotions was determined significant by both a one-way ANOVA test and a Tukey’s HSD test.
Congratulations! You've completed this lab. However, you may not notice a green check mark next to this item on Coursera's platform. Please continue your progress regardless of the check mark. Just click on the "save" icon at the top of this notebook to ensure your work has been logged.