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…
- CourseReal-World Data Analytics
- Lesson27 of 100
- FormatJupyter notebook · 30 code cells
- Data4 datasets
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.
- Website_Traffic_Data.csv20.6 KB
- Source_Lookup.csv3.1 KB
- Device_Lookup.csv4.2 KB
- Geo_Lookup.csv3.4 KB
📓 Full notebook
Download .ipynbData 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
Part 2: Real Data Preview#
traffic.head(3)
source.head(3)
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
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
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
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)
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
Part 9: Real Traffic by Device Type#
sessions_by_device = merged.groupby('Device_Type')['Session_Id'].count().sort_values(ascending=False)
sessions_by_device
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)
Part 11: Real Geography, Sessions by Region#
sessions_by_region = merged.groupby('Location_Region')['Session_Id'].count().sort_values(ascending=False)
sessions_by_region
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)
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
Part 15: Real Source Type by Content Segment Cross-Tab#
source_segment_crosstab = pd.crosstab(merged['Source_Type'], merged['Content_Segment'])
source_segment_crosstab
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)
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
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)
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
Part 21: Real Device-Region Interaction#
device_region = pd.crosstab(merged['Device_Type'], merged['Location_Region'])
device_region
Part 22: Real Browser Distribution#
browser_share = merged['Device_Browser'].value_counts()
browser_share
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()
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
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)
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
Part 27: Real Sanity Check, Session Counts Match#
sessions_by_source.sum() == len(merged) == len(traffic)
Part 28: Real Sanity Check, Rates in Range#
all((0 <= bounce_rate_by_source) & (bounce_rate_by_source <= 100))
Part 29: Real Attribution Recommendation#
worst_bounce_source = bounce_rate_by_source.idxmax()
worst_bounce_source
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.')
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.



