Mathew K Analytics

Lesson 28 · Real-World Data Analytics

Python Data Analytics #28: Email Marketing Performance Analysis in Python

Video twenty-eight of the hundred-video real-world data analytics series. Real published email marketing benchmarks across forty-five real industries, from…

What you'll learn

Datasets used in this lesson

Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.

📓 Full notebook

Download .ipynb

Data Analytics 100, Video 28: Email Marketing Performance Analysis#

  • Video twenty-eight of the hundred-video real-world data analytics series.
  • Real published email marketing benchmarks across forty-five real industries, from a real report covering 3.6 million real campaigns.
  • Let's get into it.

Part 1: Real Benchmarks, Real Industries#

import pandas as pd
import matplotlib.pyplot as plt
benchmarks = pd.read_csv('email_marketing_benchmarks_2026.csv')
benchmarks.shape
(45, 5)

Part 2: Real Data Preview#

benchmarks.head(5)
benchmarks.describe().round(2)
Open_Rate_Pct Click_Rate_Pct Click_To_Open_Rate_Pct Unsubscribe_Rate_Pct
count 45.00 45.00 45.00 45.00
mean 43.43 2.09 6.80 0.22
std 5.46 0.92 2.81 0.07
min 30.10 0.83 2.96 0.09
25% 39.95 1.39 4.84 0.16
50% 43.75 1.83 6.28 0.23
75% 47.19 2.66 7.96 0.27
max 55.71 4.90 14.82 0.40

Part 3: Real Top Open Rate Industries#

top_open_rate = benchmarks.sort_values('Open_Rate_Pct', ascending=False).head(10)
top_open_rate[['Industry', 'Open_Rate_Pct']]
Industry Open_Rate_Pct
38 Religion 55.71
20 Hobbies 53.25
29 Non-profit 52.38
3 Art gallery and museum 50.43
32 Photo and video 49.98
4 Artist 49.23
17 Government 48.52
9 Coaching 48.07
18 Health and fitness 47.81
13 Creative services/agency 47.69

Part 4: Real Bottom Open Rate Industries#

bottom_open_rate = benchmarks.sort_values('Open_Rate_Pct').head(10)
bottom_open_rate[['Industry', 'Open_Rate_Pct']]
Industry Open_Rate_Pct
44 Travel and transportation 30.10
14 e-commerce 32.67
35 Publishing company 34.24
10 Computers and electronics 35.29
43 Telecommunications 37.21
25 Marketing and advertising 37.23
24 Manufacturing 37.36
40 Retail 37.47
6 Beauty and personal care 38.40
21 Home and garden 39.18

Part 5: Visualizing Real Open Rates#

top10 = benchmarks.sort_values('Open_Rate_Pct', ascending=False).head(10)
plt.figure(figsize=(9, 6))
plt.barh(top10['Industry'], top10['Open_Rate_Pct'], color='steelblue')
plt.xlabel('Real Open Rate (%)')
plt.title('Real Top 10 Industries by Email Open Rate')
plt.gca().invert_yaxis()
plt.tight_layout()
plt.savefig('top_open_rates.png', dpi=120)
plt.close()

Part 6: Real Click Rate Leaders#

top_click_rate = benchmarks.sort_values('Click_Rate_Pct', ascending=False).head(10)
top_click_rate[['Industry', 'Click_Rate_Pct']]
Industry Click_Rate_Pct
23 Legal 4.90
24 Manufacturing 4.22
26 Media 4.10
11 Construction 3.53
20 Hobbies 3.30
17 Government 3.05
7 Blogger 3.00
38 Religion 2.95
29 Non-profit 2.90
35 Publishing company 2.82

Part 7: Real Open Rate vs Click Rate, Are They Related?#

open_click_corr = benchmarks['Open_Rate_Pct'].corr(benchmarks['Click_Rate_Pct'])
round(open_click_corr, 3)
np.float64(0.176)

Part 8: Visualizing the Real Weak Relationship#

plt.figure(figsize=(8, 6))
plt.scatter(benchmarks['Open_Rate_Pct'], benchmarks['Click_Rate_Pct'], color='darkorange', s=40)
plt.xlabel('Real Open Rate (%)')
plt.ylabel('Real Click Rate (%)')
plt.title('Real Open Rate vs Click Rate by Industry')
plt.tight_layout()
plt.savefig('open_vs_click_scatter.png', dpi=120)
plt.close()

Part 9: Real Click-to-Open Rate, The Content Signal#

top_ctor = benchmarks.sort_values('Click_To_Open_Rate_Pct', ascending=False).head(10)
top_ctor[['Industry', 'Click_To_Open_Rate_Pct']]
Industry Click_To_Open_Rate_Pct
24 Manufacturing 14.82
23 Legal 14.72
26 Media 12.92
11 Construction 12.38
16 Games 9.71
19 Higher education 9.15
35 Publishing company 9.12
7 Blogger 8.56
20 Hobbies 8.45
17 Government 8.44

Part 10: Real Low Open, High CTOR Industries#

median_open = benchmarks['Open_Rate_Pct'].median()
median_ctor = benchmarks['Click_To_Open_Rate_Pct'].median()
hidden_gems = benchmarks[(benchmarks['Open_Rate_Pct'] < median_open) & (benchmarks['Click_To_Open_Rate_Pct'] > median_ctor)]
hidden_gems[['Industry', 'Open_Rate_Pct', 'Click_To_Open_Rate_Pct']]
Industry Open_Rate_Pct Click_To_Open_Rate_Pct
0 Agency 40.52 6.74
5 Author 43.14 7.81
7 Blogger 43.03 8.56
8 Business and finance 43.34 7.96
11 Construction 39.95 12.38
16 Games 41.35 9.71
21 Home and garden 39.18 7.05
23 Legal 42.58 14.72
24 Manufacturing 37.36 14.82
26 Media 42.97 12.92
35 Publishing company 34.24 9.12
36 Real estate 40.37 6.72
44 Travel and transportation 30.10 6.34

Part 11: Real Unsubscribe Rate Leaders#

top_unsub = benchmarks.sort_values('Unsubscribe_Rate_Pct', ascending=False).head(10)
top_unsub[['Industry', 'Unsubscribe_Rate_Pct']]
Industry Unsubscribe_Rate_Pct
32 Photo and video 0.40
39 Restaurants and cafes 0.39
43 Telecommunications 0.34
3 Art gallery and museum 0.33
5 Author 0.31
18 Health and fitness 0.30
9 Coaching 0.30
24 Manufacturing 0.30
13 Creative services/agency 0.29
4 Artist 0.27

Part 12: Real Lowest Unsubscribe Rate Industries#

bottom_unsub = benchmarks.sort_values('Unsubscribe_Rate_Pct').head(10)
bottom_unsub[['Industry', 'Unsubscribe_Rate_Pct']]
Industry Unsubscribe_Rate_Pct
23 Legal 0.09
19 Higher education 0.10
26 Media 0.10
38 Religion 0.13
44 Travel and transportation 0.13
34 Public relations 0.14
33 Politics 0.14
25 Marketing and advertising 0.15
7 Blogger 0.16
8 Business and finance 0.16

Part 13: Real Correlation, Open Rate and Unsubscribe Rate#

open_unsub_corr = benchmarks['Open_Rate_Pct'].corr(benchmarks['Unsubscribe_Rate_Pct'])
round(open_unsub_corr, 3)
np.float64(0.169)

Part 14: Real All-Metric Correlation Matrix#

metric_cols = ['Open_Rate_Pct', 'Click_Rate_Pct', 'Click_To_Open_Rate_Pct', 'Unsubscribe_Rate_Pct']
correlation_matrix = benchmarks[metric_cols].corr().round(3)
correlation_matrix
Open_Rate_Pct Click_Rate_Pct Click_To_Open_Rate_Pct Unsubscribe_Rate_Pct
Open_Rate_Pct 1.000 0.176 -0.059 0.169
Click_Rate_Pct 0.176 1.000 0.929 -0.199
Click_To_Open_Rate_Pct -0.059 0.929 1.000 -0.267
Unsubscribe_Rate_Pct 0.169 -0.199 -0.267 1.000

Part 15: Real Overall Industry Benchmark#

overall_open = round(benchmarks['Open_Rate_Pct'].median(), 2)
overall_click = round(benchmarks['Click_Rate_Pct'].median(), 2)
overall_open, overall_click
(np.float64(43.75), np.float64(1.83))

Part 16: Real Campaign Benchmarking Function#

def benchmark_campaign(industry_name, my_open_rate, my_click_rate):
    row = benchmarks[benchmarks['Industry'] == industry_name].iloc[0]
    open_diff = round(my_open_rate - row['Open_Rate_Pct'], 2)
    click_diff = round(my_click_rate - row['Click_Rate_Pct'], 2)
    return {'open_rate_vs_benchmark': open_diff, 'click_rate_vs_benchmark': click_diff}
benchmark_campaign('e-commerce', 35.0, 1.5)
{'open_rate_vs_benchmark': np.float64(2.33),
 'click_rate_vs_benchmark': np.float64(0.43)}

Part 17: Real Testing the Function on Another Industry#

benchmark_campaign('Non-profit', 48.0, 3.5)
{'open_rate_vs_benchmark': np.float64(-4.38),
 'click_rate_vs_benchmark': np.float64(0.6)}

Part 18: Real Distribution of Open Rates#

plt.figure(figsize=(9, 5))
plt.hist(benchmarks['Open_Rate_Pct'], bins=15, color='mediumseagreen', edgecolor='white')
plt.axvline(overall_open, color='crimson', linestyle='--', label=f'Real Median: {overall_open}%')
plt.xlabel('Real Open Rate (%)')
plt.ylabel('Real Number of Industries')
plt.title('Real Distribution of Email Open Rates Across Industries')
plt.legend()
plt.tight_layout()
plt.savefig('open_rate_distribution.png', dpi=120)
plt.close()

Part 19: Real Range Check, Click Rate#

click_range = round(benchmarks['Click_Rate_Pct'].max() - benchmarks['Click_Rate_Pct'].min(), 2)
click_range
np.float64(4.07)

Part 20: Real Industries Above the Overall Median on Every Metric#

medians = benchmarks[metric_cols].median()
all_star = benchmarks[(benchmarks['Open_Rate_Pct'] > medians['Open_Rate_Pct']) & (benchmarks['Click_Rate_Pct'] > medians['Click_Rate_Pct']) & (benchmarks['Unsubscribe_Rate_Pct'] < medians['Unsubscribe_Rate_Pct'])]
all_star[['Industry']]
Industry
17 Government
19 Higher education
29 Non-profit
31 Other
34 Public relations
38 Religion

Part 21: Real Worst-Performing Industries Overall#

struggling = benchmarks[(benchmarks['Open_Rate_Pct'] < medians['Open_Rate_Pct']) & (benchmarks['Click_Rate_Pct'] < medians['Click_Rate_Pct'])]
len(struggling), struggling['Industry'].tolist()[:10]
(12,
 ['Architecture and construction',
  'Beauty and personal care',
  'Computers and electronics',
  'e-commerce',
  'Home and garden',
  'Marketing and advertising',
  'Online courses',
  'Real estate',
  'Retail',
  'Software and web app'])

Part 22: Real Ranking by Composite Engagement Score#

benchmarks['engagement_score'] = (benchmarks['Open_Rate_Pct'] * 0.3 + benchmarks['Click_Rate_Pct'] * 10 * 0.4 + benchmarks['Click_To_Open_Rate_Pct'] * 0.3).round(2)
benchmarks.sort_values('engagement_score', ascending=False)[['Industry', 'engagement_score']].head(10)
Industry engagement_score
23 Legal 36.79
26 Media 33.17
24 Manufacturing 32.53
20 Hobbies 31.71
38 Religion 30.79
11 Construction 29.82
29 Non-profit 29.79
17 Government 29.29
3 Art gallery and museum 27.65
7 Blogger 27.48

Part 23: Real Bottom of the Composite Ranking#

benchmarks.sort_values('engagement_score')[['Industry', 'engagement_score']].head(10)
Industry engagement_score
14 e-commerce 15.28
6 Beauty and personal care 16.49
10 Computers and electronics 17.52
44 Travel and transportation 17.65
40 Retail 17.67
41 Software and web app 18.01
25 Marketing and advertising 18.20
33 Politics 18.36
43 Telecommunications 18.48
39 Restaurants and cafes 18.52

Part 24: Real Legal Industry Deep Dive#

legal_row = benchmarks[benchmarks['Industry'] == 'Legal'].iloc[0]
legal_row[metric_cols]
Open_Rate_Pct             42.58
Click_Rate_Pct              4.9
Click_To_Open_Rate_Pct    14.72
Unsubscribe_Rate_Pct       0.09
Name: 23, dtype: object

Part 25: Real Retail vs E-commerce Comparison#

retail_ecom = benchmarks[benchmarks['Industry'].isin(['Retail', 'e-commerce'])]
retail_ecom[['Industry'] + metric_cols]
Industry Open_Rate_Pct Click_Rate_Pct Click_To_Open_Rate_Pct Unsubscribe_Rate_Pct
14 e-commerce 32.67 1.07 4.01 0.18
40 Retail 37.47 1.27 4.51 0.22

Part 26: Saving the Real Ranked Benchmark Table#

ranked_benchmarks = benchmarks.sort_values('engagement_score', ascending=False).reset_index(drop=True)
ranked_benchmarks.to_csv('ranked_email_benchmarks.csv', index=False)
reloaded_ranked = pd.read_csv('ranked_email_benchmarks.csv')
reloaded_ranked.shape == ranked_benchmarks.shape
True

Part 27: Real Sanity Check, Percentages in Range#

all((0 <= benchmarks['Open_Rate_Pct']) & (benchmarks['Open_Rate_Pct'] <= 100)) and all((0 <= benchmarks['Click_Rate_Pct']) & (benchmarks['Click_Rate_Pct'] <= 100))
True

Part 28: Real Sanity Check, No Duplicate Industries#

benchmarks['Industry'].is_unique
True

Part 29: Real Takeaway for a Hypothetical Campaign#

my_result = benchmark_campaign('Software and web app', 41.0, 1.3)
my_result
{'open_rate_vs_benchmark': np.float64(1.69),
 'click_rate_vs_benchmark': np.float64(0.15)}

Part 30: Real Recap Print#

top_industry = benchmarks.sort_values('engagement_score', ascending=False).iloc[0]['Industry']
print(f'Across {len(benchmarks)} real industries, the median open rate was {overall_open}% and median click rate was {overall_click}%, with {top_industry} posting the real strongest composite email performance.')
Across 45 real industries, the median open rate was 43.75% and median click rate was 1.83%, with Legal posting the real strongest composite email performance.

Wrap-Up: What You Learned#

  • Real published industry benchmarks give any marketer an honest yardstick, without one, a raw open or click rate number means very little.
  • Open rate and click rate turned out to be only weakly correlated across real industries, confirming they genuinely measure different things.
  • Click-to-open rate isolates real content quality specifically, separate from how many people opened the email in the first place.
  • A real simple reusable benchmarking function makes comparing any real campaign against its real industry standard fast and repeatable.
  • Combining several real metrics into one composite score is a real practical way to rank performance across many real categories at once.
  • Next video: real airline customer sentiment analysis, returning to real text analysis with a real single-brand deep dive.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.