A/B Testing Marketing Promotions

Question asked: Which promotion was the most effective?

Scenario:

A fast food chain plans to add a new item to its menu. However, they are still undecided between three possible marketing campaigns for promoting the new product. In order to determine which promotion has the greatest impact on the sales of the new item, the new item is introduced at locations in several randomly selected markets. A different promotion is used at each location, and the weekly sales of the new item are recorded for the first four weeks.

The description of the data set: Our data set consists of 548 entries including:

  • MarketId: an inhouse tag used to describe market types, we won't be using it
  • AgeOfStores: Age of store in years (1–28). The mean age of a store is 8.5 years.
  • LocationID: Unique identifier for store location. Each location is identified by a number. The total number of stores is 137.
  • Promotion: One of three promotions that were tested (1, 2, 3). We don’t really know the specifics of each promotion.
  • Sales in Thousands: Sales amount for a specific LocationID, Promotion and week. The mean amount of sales are 53.5 thousand dollars.
  • Market size: there are three types of market size: small, medium and large.
  • Week: One of four weeks when the promotions were run (1–4).
In [1]:
import pandas as pd
import matplotlib.pyplot as plt

# Uncomment this line if using this notebook locally
#df = pd.read_csv('./data/marketing/WA_Fn-UseC_-Marketing-Campaign-Eff-UseC_-FastF.csv')

file_name = "https://raw.githubusercontent.com/rajeevratan84/datascienceforbusiness/master/WA_Fn-UseC_-Marketing-Campaign-Eff-UseC_-FastF.csv"
df = pd.read_csv(file_name)

df.head(10)
Out[1]:
MarketID MarketSize LocationID AgeOfStore Promotion week SalesInThousands
0 1 Medium 1 4 3 1 33.73
1 1 Medium 1 4 3 2 35.67
2 1 Medium 1 4 3 3 29.03
3 1 Medium 1 4 3 4 39.25
4 1 Medium 2 5 2 1 27.81
5 1 Medium 2 5 2 2 34.67
6 1 Medium 2 5 2 3 27.98
7 1 Medium 2 5 2 4 27.72
8 1 Medium 3 12 1 1 44.54
9 1 Medium 3 12 1 2 37.94
In [2]:
print ("Rows     : " , df.shape[0])
print ("Columns  : " , df.shape[1])
print ("\nFeatures : \n", df.columns.tolist())
print ("\nMissing values :  ", df.isnull().sum().values.sum())
print ("\nUnique values :  \n", df.nunique())
Rows     :  548
Columns  :  7

Features : 
 ['MarketID', 'MarketSize', 'LocationID', 'AgeOfStore', 'Promotion', 'week', 'SalesInThousands']

Missing values :   0

Unique values :  
 MarketID             10
MarketSize            3
LocationID          137
AgeOfStore           25
Promotion             3
week                  4
SalesInThousands    517
dtype: int64
In [3]:
df.describe()
Out[3]:
MarketID LocationID AgeOfStore Promotion week SalesInThousands
count 548.000000 548.000000 548.000000 548.000000 548.000000 548.000000
mean 5.715328 479.656934 8.503650 2.029197 2.500000 53.466204
std 2.877001 287.973679 6.638345 0.810729 1.119055 16.755216
min 1.000000 1.000000 1.000000 1.000000 1.000000 17.340000
25% 3.000000 216.000000 4.000000 1.000000 1.750000 42.545000
50% 6.000000 504.000000 7.000000 2.000000 2.500000 50.200000
75% 8.000000 708.000000 12.000000 3.000000 3.250000 60.477500
max 10.000000 920.000000 28.000000 3.000000 4.000000 99.650000

EDA and Visualizations

In [4]:
# Visualisation of our sales and marketing data

# Using ggplot's style
plt.style.use('ggplot')
ax = df.groupby('Promotion').sum()['SalesInThousands'].plot.pie(figsize=(8,8),
                                                                autopct='%1.0f%%',
                                                                shadow=True,
                                                                explode = (0, 0.1, 0))
ax.set_ylabel('')
ax.set_title('Sales Distribution Across the 3 Different Promotions')

plt.show()
In [5]:
# Now let's view the promotions for each market size
df.groupby(['Promotion', 'MarketSize']).count()['MarketID']
Out[5]:
Promotion  MarketSize
1          Large          56
           Medium         96
           Small          20
2          Large          64
           Medium        108
           Small          16
3          Large          48
           Medium        116
           Small          24
Name: MarketID, dtype: int64
In [6]:
# Using unstack
df.groupby(['Promotion', 'MarketSize']).count()['MarketID'].unstack('MarketSize')
Out[6]:
MarketSize Large Medium Small
Promotion
1 56 96 20
2 64 108 16
3 48 116 24
In [7]:
# Put this into a plot
ax = df.groupby(['Promotion', 'MarketSize']).count()['MarketID'].unstack('MarketSize').plot(
    kind='bar',
    figsize=(12,10),
    grid=True)

ax.set_ylabel('count')
ax.set_title('breakdowns of market sizes across different promotions')

plt.show()
In [8]:
# Put this into a different plot
ax = df.groupby(['Promotion', 'MarketSize']).count()['MarketID'].unstack('MarketSize').plot(
    kind='bar',
    figsize=(12,10),
    grid=True,
    stacked=True)

ax.set_ylabel('count')
ax.set_title('breakdowns of market sizes across different promotions')

plt.show()
In [9]:
#Plot visualisations to view the distribution of store ages
ax = df.groupby('AgeOfStore').count()['MarketID'].plot(
    kind='bar', 
    figsize=(12,7),
    grid=True)

ax.set_xlabel('age')
ax.set_ylabel('count')
ax.set_title('Overall Distributions Store Ages')

plt.show()
In [10]:
# Group by Age of Store and Promotion to get counts
df.groupby(['AgeOfStore', 'Promotion']).count()['MarketID']
Out[10]:
AgeOfStore  Promotion
1           1            24
            2            36
            3            20
2           1             8
            2             8
            3             4
3           1            16
            2            12
            3             4
4           1            16
            2            12
            3            16
5           1             8
            2            12
            3            24
6           1            20
            2             4
            3            12
7           1             4
            2            24
            3            12
8           1            12
            2             8
            3            20
9           1             8
            2            12
            3             8
10          2            16
            3             8
11          1             4
            3            12
12          1            12
            2             4
            3             8
13          1            12
            2             8
14          2             8
            3             4
15          1             4
            2             4
17          3             4
18          1             8
19          1             4
            2             8
            3             8
20          3             4
22          1             4
            3             8
23          2             4
            3             4
24          1             4
            3             8
25          2             4
27          1             4
28          2             4
Name: MarketID, dtype: int64
In [11]:
# Visaulize this summary
ax = df.groupby(['AgeOfStore', 'Promotion']).count()['MarketID'].unstack('Promotion').iloc[::-1].plot(
    kind='barh', 
    figsize=(14,18),
    grid=True)

ax.set_ylabel('age')
ax.set_xlabel('count')
ax.set_title('overall distributions of age of store')

plt.show()
In [12]:
df.groupby('Promotion').describe()['AgeOfStore']
Out[12]:
count mean std min 25% 50% 75% max
Promotion
1 172.0 8.279070 6.636160 1.0 3.0 6.0 12.0 27.0
2 188.0 7.978723 6.597648 1.0 3.0 7.0 10.0 28.0
3 188.0 9.234043 6.651646 1.0 5.0 8.0 12.0 24.0

This table makes it easy to understand the overall store age distribution from our summary stats.

All test groups have similar age profiles and the average store ages is ~8 to 9 years old for theese 3 groups.

The majority of the stores are 10–12 years old or even younger.

We can see that the store profiles are similar to each other.

This indicates that our sample groups are well controlled and the A/B testing results will be meaningful and trustworthy.

Performing A/B Testing

In [13]:
means = df.groupby('Promotion').mean()['SalesInThousands']
stds = df.groupby('Promotion').std()['SalesInThousands']
ns = df.groupby('Promotion').count()['SalesInThousands']
print(means)
print(stds)
print(ns)
Promotion
1    58.099012
2    47.329415
3    55.364468
Name: SalesInThousands, dtype: float64
Promotion
1    16.553782
2    15.108955
3    16.766231
Name: SalesInThousands, dtype: float64
Promotion
1    172
2    188
3    188
Name: SalesInThousands, dtype: int64

T-Value

The t-value measures the degree of difference relative to the variation in our data groups. Large t-values indicate a higher degree of difference between the grups.

P-Value

P-value measures the probability that the results would occur by random chance. Therefore the smaller the p-value is, the more statistically significant difference there will be between the two groups

Comparing Promotion 1 vs Promotion 2 in an A/B Test

In [14]:
# Computing the t and p values using scipy 
from scipy import stats

t, p = stats.ttest_ind(df.loc[df['Promotion'] == 1, 'SalesInThousands'].values,
                       df.loc[df['Promotion'] == 2, 'SalesInThousands'].values, 
                       equal_var=False)
print("t-value = " +str(t))
print("p-value = " +str(p))
t-value = 6.42752867090748
p-value = 4.2903687179871785e-10

Analysis of P and t-values

Our P-Value is close to 0 which suggests that there is good evidence to REJECT the Null Hypothesis. Meaning the there is a statistical difference between the two groups. Our threshold rejectings the Null is usually less than 0.05.

Our t-test shows that the marketing performances for these two groups (1 and 2) are significantly different and that promotion group 1 outperforms promotion group 2.

Comparing Promotion 1 vs Promotion 3 in an A/B Test

However, if we run a t-test between the promotion group 1 and promotion group 3, we see different results:

In [15]:
t, p = stats.ttest_ind(
    df.loc[df['Promotion'] == 1, 'SalesInThousands'].values, 
    df.loc[df['Promotion'] == 3, 'SalesInThousands'].values, 
    equal_var=False)

print("t-value = " +str(t))
print("p-value = " +str(p))
t-value = 1.5560224307758634
p-value = 0.12059147742229478

Analysis of P and t-values

We note that the average sales from promotion group 1 (58.1) is higher than those from promotion group 3 (55.36).

But, running a t-test between these two groups, gives us a t-value of 1.556 and a p-value of 0.121.

The computed p-value is a lot higher than 0.05, past the threshold for statistical significance.

Our t-test shows that the marketing performances for these two groups (1 and 3) are not significantly different.

In [ ]: