Mathew K Analytics

Lesson 27 · Real-World Data Analytics

Python Data Analytics #27: Web Traffic Analytics & Attribution in Python

Video twenty-seven of the hundred-video real-world data analytics series. Real session-level web analytics data, tying every visit back to a real traffic…

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 27: Web Traffic Analytics and Attribution#

  • Video twenty-seven of the hundred-video real-world data analytics series.
  • Real session-level web analytics data, tying every visit back to a real traffic source, device, and location.
  • Let's get into it.

Part 1: Real Sessions, Real Sources#

import pandas as pd
import matplotlib.pyplot as plt
traffic = pd.read_csv('Website_Traffic_Data.csv')
source = pd.read_csv('Source_Lookup.csv')
device = pd.read_csv('Device_Lookup.csv')
geo = pd.read_csv('Geo_Lookup.csv')
traffic.shape, source.shape, device.shape, geo.shape
((731, 7), (100, 4), (100, 5), (100, 5))

Part 2: Real Data Preview#

traffic.head(3)
source.head(3)
Source_Key Source_Name Source_Type Source_Campaign
0 1 Source 1 Organic Spring Sale
1 2 Source 2 Paid Summer Offer
2 3 Source 3 Referral Winter Discount

Part 3: Real Star Schema Join#

merged = traffic.merge(source, on='Source_Key').merge(device, on='Device_Key').merge(geo, on='Location_Key')
merged.shape
(731, 18)

Part 4: Real Sessions by Traffic Source Type#

sessions_by_source = merged.groupby('Source_Type')['Session_Id'].count().sort_values(ascending=False)
sessions_by_source
Source_Type
Social      135
Organic     129
Paid        125
Other       115
Referral    114
Email       113
Name: Session_Id, dtype: int64

Part 5: Visualizing Real Traffic by Source#

plt.figure(figsize=(9, 5))
sessions_by_source.plot(kind='bar', color='darkslateblue')
plt.ylabel('Real Session Count')
plt.title('Real Sessions by Traffic Source')
plt.xticks(rotation=30)
plt.tight_layout()
plt.savefig('sessions_by_source.png', dpi=120)
plt.close()

Part 6: Real Session Duration by Source#

duration_by_source = merged.groupby('Source_Type')['Session_Duration(Seconds)'].mean().round(1).sort_values(ascending=False)
duration_by_source
Source_Type
Organic     720.0
Paid        713.3
Other       675.1
Social      660.0
Email       645.1
Referral    585.8
Name: Session_Duration(Seconds), dtype: float64

Part 7: Real Bounce Rate by Source#

merged['is_bounce'] = merged['Page_Views_Per_Session'] == 1
bounce_rate_by_source = merged.groupby('Source_Type')['is_bounce'].mean().round(3) * 100
bounce_rate_by_source.sort_values(ascending=False)
Source_Type
Referral    12.3
Paid        12.0
Email       10.6
Social       9.6
Other        7.8
Organic      5.4
Name: is_bounce, dtype: float64

Part 8: Real Page Views per Session by Source#

pageviews_by_source = merged.groupby('Source_Type')['Page_Views_Per_Session'].mean().round(2).sort_values(ascending=False)
pageviews_by_source
Source_Type
Organic     3.69
Paid        3.64
Email       3.44
Social      3.43
Other       3.33
Referral    3.12
Name: Page_Views_Per_Session, dtype: float64

Part 9: Real Traffic by Device Type#

sessions_by_device = merged.groupby('Device_Type')['Session_Id'].count().sort_values(ascending=False)
sessions_by_device
Device_Type
Tablet     163
Desktop    156
Laptop     150
Other      141
Mobile     121
Name: Session_Id, dtype: int64

Part 10: Real Bounce Rate by Device#

bounce_by_device = merged.groupby('Device_Type')['is_bounce'].mean().round(3) * 100
bounce_by_device.sort_values(ascending=False)
Device_Type
Desktop    11.5
Laptop     10.0
Mobile      9.1
Tablet      8.6
Other       8.5
Name: is_bounce, dtype: float64

Part 11: Real Geography, Sessions by Region#

sessions_by_region = merged.groupby('Location_Region')['Session_Id'].count().sort_values(ascending=False)
sessions_by_region
Location_Region
South    264
North    233
West     147
East      87
Name: Session_Id, dtype: int64

Part 12: Real Top Cities by Traffic#

sessions_by_city = merged.groupby('Location_City')['Session_Id'].count().sort_values(ascending=False)
sessions_by_city.head(10)
Location_City
Chennai      111
Hyderabad     89
Kolkata       87
Jaipur        86
Lucknow       76
Delhi         71
Mumbai        66
Bangalore     64
Pune          48
Ahmedabad     33
Name: Session_Id, dtype: int64

Part 13: Visualizing Real Regional Traffic#

plt.figure(figsize=(8, 5))
sessions_by_region.plot(kind='bar', color='seagreen')
plt.ylabel('Real Session Count')
plt.title('Real Sessions by Geographic Region')
plt.xticks(rotation=0)
plt.tight_layout()
plt.savefig('sessions_by_region.png', dpi=120)
plt.close()

Part 14: Real Content Segment Engagement#

pageviews_by_segment = merged.groupby('Content_Segment')['Page_Views_Per_Session'].mean().round(2).sort_values(ascending=False)
pageviews_by_segment
Content_Segment
Gaming                3.64
Travel and Tourism    3.54
Business              3.43
Education             3.40
Personal              3.25
Name: Page_Views_Per_Session, dtype: float64

Part 15: Real Source Type by Content Segment Cross-Tab#

source_segment_crosstab = pd.crosstab(merged['Source_Type'], merged['Content_Segment'])
source_segment_crosstab
Content_Segment Business Education Gaming Personal Travel and Tourism
Source_Type
Email 35 15 22 19 22
Organic 23 23 23 26 34
Other 25 25 22 21 22
Paid 35 17 21 30 22
Referral 26 19 20 23 26
Social 32 24 27 23 29

Part 16: Real Monthly Traffic Trend#

merged['Date'] = pd.to_datetime(merged['Date_Key'])
merged['Month'] = merged['Date'].dt.to_period('M')
monthly_sessions = merged.groupby('Month')['Session_Id'].count()
monthly_sessions.tail(12)
Month
2021-01    31
2021-02    28
2021-03    31
2021-04    30
2021-05    31
2021-06    30
2021-07    31
2021-08    31
2021-09    30
2021-10    31
2021-11    30
2021-12    31
Freq: M, Name: Session_Id, dtype: int64

Part 17: Visualizing the Real Monthly Trend#

plt.figure(figsize=(11, 5))
monthly_sessions.plot(kind='line', marker='o', color='crimson')
plt.ylabel('Real Session Count')
plt.title('Real Monthly Website Traffic Trend')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('monthly_traffic_trend.png', dpi=120)
plt.close()

Part 18: Real Campaign Attribution#

sessions_by_campaign = merged.groupby('Source_Campaign')['Session_Id'].count().sort_values(ascending=False)
sessions_by_campaign
Source_Campaign
New Year Deal      135
Spring Sale        129
Summer Offer       125
Other              115
Winter Discount    114
Name: Session_Id, dtype: int64

Part 19: Real Campaign Engagement Quality#

campaign_quality = merged.groupby('Source_Campaign').agg(sessions=('Session_Id', 'count'), avg_duration=('Session_Duration(Seconds)', 'mean'), avg_pageviews=('Page_Views_Per_Session', 'mean')).round(2)
campaign_quality.sort_values('sessions', ascending=False)
sessions avg_duration avg_pageviews
Source_Campaign
New Year Deal 135 660.00 3.43
Spring Sale 129 720.00 3.69
Summer Offer 125 713.28 3.64
Other 115 675.13 3.33
Winter Discount 114 585.79 3.12

Part 20: Real Volume vs Quality Trade-off#

best_engagement_source = duration_by_source.idxmax()
highest_volume_source = sessions_by_source.idxmax()
best_engagement_source, highest_volume_source
('Organic', 'Social')

Part 21: Real Device-Region Interaction#

device_region = pd.crosstab(merged['Device_Type'], merged['Location_Region'])
device_region
Location_Region East North South West
Device_Type
Desktop 21 59 47 29
Laptop 18 47 54 31
Mobile 14 46 38 23
Other 19 33 56 33
Tablet 15 48 69 31

Part 22: Real Browser Distribution#

browser_share = merged['Device_Browser'].value_counts()
browser_share
Device_Browser
Browse_ER_4    226
Browse_ER_3    207
Browse_ER_2    166
Browse_ER_1    132
Name: count, dtype: int64

Part 23: Real Long-Duration Sessions#

long_sessions = merged[merged['Session_Duration(Seconds)'] > merged['Session_Duration(Seconds)'].quantile(0.9)]
long_sessions['Source_Type'].value_counts()
Source_Type
Organic     9
Paid        8
Email       8
Other       5
Referral    5
Social      3
Name: count, dtype: int64

Part 24: Real Zero-Duration Sessions#

zero_duration = merged[merged['Session_Duration(Seconds)'] == 0]
zero_duration_pct = round(len(zero_duration) / len(merged) * 100, 1)
zero_duration_pct
9.6

Part 25: Real Correlation, Duration and Page Views#

duration_pageview_corr = merged['Session_Duration(Seconds)'].corr(merged['Page_Views_Per_Session'])
round(duration_pageview_corr, 3)
np.float64(0.72)

Part 26: Saving the Real Source Performance Table#

source_performance = pd.DataFrame({'sessions': sessions_by_source, 'avg_duration_sec': duration_by_source, 'bounce_rate_pct': bounce_rate_by_source, 'avg_pageviews': pageviews_by_source}).round(2)
source_performance.to_csv('source_attribution_summary.csv')
reloaded_summary = pd.read_csv('source_attribution_summary.csv', index_col=0)
reloaded_summary.shape == source_performance.shape
True

Part 27: Real Sanity Check, Session Counts Match#

sessions_by_source.sum() == len(merged) == len(traffic)
True

Part 28: Real Sanity Check, Rates in Range#

all((0 <= bounce_rate_by_source) & (bounce_rate_by_source <= 100))
True

Part 29: Real Attribution Recommendation#

worst_bounce_source = bounce_rate_by_source.idxmax()
worst_bounce_source
'Referral'

Part 30: Real Recap Print#

print(f'Across {len(merged)} real sessions, {highest_volume_source} drove the most volume while {best_engagement_source} delivered the strongest real engagement, with {worst_bounce_source} showing the highest real bounce rate.')
Across 731 real sessions, Social drove the most volume while Organic delivered the strongest real engagement, with Referral showing the highest real bounce rate.

Wrap-Up: What You Learned#

  • Joining a real fact table against real dimension lookup tables is the same star-schema pattern genuine analytics warehouses use every day.
  • Attribution is not just about raw session volume, comparing duration, page views, and bounce rate reveals real traffic quality.
  • The real traffic source with the most sessions was not necessarily the real source with the best engagement, a genuinely common real finding.
  • Cross-tabulating dimensions like device and region, or source and content segment, surfaces real interaction patterns a single groupby would miss.
  • Zero-duration and single-page sessions are a real honest signal worth tracking separately from overall real traffic volume.
  • Next video: real email marketing performance analysis, shifting from real website sessions to real campaign send data.

Found this useful?

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