Mathew K Analytics

Lesson 40 · Social Media Content Analytics

Designing Data-Driven Content Strategies

In this lesson, we will solve real-world social media content analytics problems. The challenge is to design content strategies using real data, not just…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Designing Data-Driven Content Strategies#

  • In this lesson, we will solve real-world social media content analytics problems.
  • The challenge is to design content strategies using real data, not just gut feeling.
  • Data-driven strategies help creators and businesses grow their audience and engagement.
  • You will learn how to extract insights from key metrics like views, likes, and comments.
  • We will explore viral trends, audience preferences, and content optimization techniques.
  • By the end, you will be able to recommend actionable improvements based on analytics.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Core Social Media Analytics Concepts#

  • Social media datasets often represent posts, videos, or user interactions.
  • Key metrics include views (total times seen), likes, comments, shares, watch time, and CTR.
  • Metrics help us understand what content resonates and performs well.
  • Engagement rate is a common way to compare performance across posts.
  • Beginner mistake: Comparing raw numbers without considering content type or audience size.
  • Watch out for missing data, misleading ratios, and over-interpreting outliers.
# Example 1: Load and preview the Social Media Content Dataset
np.random.seed(42)
n_posts = 500
views = np.random.randint(100, 100000, n_posts)
df_content = pd.DataFrame({
    'post_id': range(1, n_posts+1),
    'platform': np.random.choice(['YouTube','Instagram','TikTok'], n_posts),
    'date': pd.date_range('2023-01-01', periods=n_posts, freq='6h'),
    'views': views,
    'likes': (views * np.random.uniform(0.02, 0.15, n_posts)).astype(int),
    'comments': (views * np.random.uniform(0.001, 0.05, n_posts)).astype(int),
    'shares': (views * np.random.uniform(0.001, 0.03, n_posts)).astype(int)
})
print(df_content.shape)
df_content.head(3)
(500, 7)
post_id platform date views likes comments shares
0 1 Instagram 2023-01-01 00:00:00 15895 2356 116 253
1 2 Instagram 2023-01-01 06:00:00 960 94 16 26
2 3 Instagram 2023-01-01 12:00:00 76920 3910 2879 1185
# Example 2: Load and preview daily content trends (time series)
np.random.seed(42)
n_days = 365
dates = pd.date_range('2023-01-01', periods=n_days, freq='D')
views_daily = np.random.randint(1000, 50000, n_days)
likes_daily = (views_daily * np.random.uniform(0.03, 0.12, n_days)).astype(int)
df_time = pd.DataFrame({
    'date': dates,
    'views': views_daily,
    'likes': likes_daily,
    'engagement_rate': np.round(likes_daily / views_daily * 100, 2)
})
print(df_time.shape)
df_time.head(3)
(365, 4)
date views likes engagement_rate
0 2023-01-01 16795 1790 10.66
1 2023-01-02 1860 108 5.81
2 2023-01-03 39158 1772 4.53
# Example 3: Calculate engagement rate per platform
df_content['engagement_rate'] = (df_content['likes'] + df_content['comments'] + df_content['shares']) / df_content['views'] * 100
platform_engagement = df_content.groupby('platform')['engagement_rate'].mean().round(2)
print(platform_engagement)
platform
Instagram    13.04
TikTok       12.69
YouTube      12.16
Name: engagement_rate, dtype: float64
# Example 4: Identify top 5 posts by engagement rate
top_posts = df_content.sort_values('engagement_rate', ascending=False).head(5)
print(top_posts[['post_id', 'platform', 'views', 'engagement_rate']])
     post_id   platform  views  engagement_rate
351      352  Instagram  74643        21.781011
10        11  Instagram  16123        21.205731
383      384     TikTok  36731        20.925104
423      424     TikTok  53021        20.882292
87        88     TikTok  82898        20.698931
# Example 5: Time series trend analysis for engagement rate
import matplotlib.pyplot as plt
plt.figure(figsize=(10,5))
plt.plot(df_time['date'], df_time['engagement_rate'], label='Engagement Rate (%)')
plt.xlabel('Date')
plt.ylabel('Engagement Rate (%)')
plt.title('Daily Engagement Rate Over Time')
plt.legend()
plt.tight_layout()
plt.show()
No description has been provided for this image
# Example 6: Aggregate engagement by month
df_time['month'] = df_time['date'].dt.to_period('M')
monthly_engagement = df_time.groupby('month')['engagement_rate'].mean()
print(monthly_engagement.round(2))
month
2023-01    8.54
2023-02    6.85
2023-03    6.98
2023-04    7.18
2023-05    7.97
2023-06    7.63
2023-07    7.17
2023-08    8.02
2023-09    8.53
2023-10    8.25
2023-11    7.41
2023-12    6.10
Freq: M, Name: engagement_rate, dtype: float64
# Example 7: Load YouTube Trending Videos Dataset (synthetic fallback if needed)
import os, pickle
from pathlib import Path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']

def get_yt_service():
    api_key = os.environ.get('YOUTUBE_API_KEY')
    if api_key:
        return build('youtube', 'v3', developerKey=api_key)
    if Path('client_secret.json').exists():
        creds = None
        if Path('token_ro.pickle').exists():
            with open('token_ro.pickle', 'rb') as f:
                creds = pickle.load(f)
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file('client_secret.json', SCOPES)
                creds = flow.run_local_server(port=0)
            with open('token_ro.pickle', 'wb') as f:
                pickle.dump(creds, f)
        return build('youtube', 'v3', credentials=creds)
    raise EnvironmentError('Set YOUTUBE_API_KEY or provide client_secret.json')

def fetch_yt_trending(max_results=200, region='US'):
    youtube = get_yt_service()
    records, token = [], None
    while len(records) < max_results:
        resp = youtube.videos().list(
            part='snippet,statistics',
            chart='mostPopular',
            regionCode=region,
            maxResults=min(50, max_results - len(records)),
            pageToken=token
        ).execute()
        for item in resp.get('items', []):
            s = item['snippet']; st = item.get('statistics', {})
            records.append({
                'video_id':      item['id'],
                'trending_date': pd.Timestamp.today().date(),
                'title':         s.get('title', ''),
                'channel_title': s.get('channelTitle', ''),
                'category_id':   s.get('categoryId', ''),
                'views':         int(st.get('viewCount', 0)),
                'likes':         int(st.get('likeCount', 0)),
                'comment_count': int(st.get('commentCount', 0)),
            })
        token = resp.get('nextPageToken')
        if not token: break
    return pd.DataFrame(records)

try:
    df_trending = fetch_yt_trending()
    print('Live trending data:', df_trending.shape)
except Exception as e:
    print(f'Falling back to synthetic: {e}')
    np.random.seed(42)
    n = 1000
    df_trending = pd.DataFrame({
        'video_id':      [f'vid{i}' for i in range(n)],
        'trending_date': pd.date_range('2023-01-01', periods=n, freq='D'),
        'title':         [f'Video Title {i}' for i in range(n)],
        'channel_title': np.random.choice(['ChannelA','ChannelB','ChannelC'], n),
        'category_id':   np.random.choice([1,2,10,22,24,28], n),
        'views':         np.random.randint(10000, 5000000, n),
        'likes':         np.random.randint(100, 200000, n),
        'comment_count': np.random.randint(10, 50000, n),
    })
    print('Synthetic fallback:', df_trending.shape)

df_trending.head(3)
Live trending data: (199, 8)
video_id trending_date title channel_title category_id views likes comment_count
0 82-jTNka3uc 2026-06-02 Ariana Grande - hate that i made you love me (... ArianaGrandeVevo 10 1714835 352682 24711
1 3oB9AxspVow 2026-06-02 The End of Oak Street | Official Trailer Warner Bros. 1 913752 27092 2304
2 Vagb9BqdX8g 2026-06-02 We Almost Lost EVERYTHING Gambling SMii7Yplus 20 1340056 65433 1718
# Example 8: Calculate like-to-view ratio for trending videos
df_trending['like_view_ratio'] = (df_trending['likes'] / df_trending['views'] * 100).round(2)
print(df_trending[['title','likes','views','like_view_ratio']].head(5))
                                               title   likes    views  \
0  Ariana Grande - hate that i made you love me (...  352682  1714835   
1           The End of Oak Street | Official Trailer   27092   913752   
2                 We Almost Lost EVERYTHING Gambling   65433  1340056   
3                       MEOVV(미야오) - ‘DDI RO RI’ M/V       0  1091961   
4  No Peace Amongst the Stars | Warhammer 40,000 ...  142347  1894927   

   like_view_ratio  
0            20.57  
1             2.96  
2             4.88  
3             0.00  
4             7.51  
# Example 9: Find the top trending categories
top_cats = df_trending.groupby('category_id')['views'].sum().sort_values(ascending=False).head(3)
print('Top trending YouTube categories by total views:')
print(top_cats)
Top trending YouTube categories by total views:
category_id
20    38806709
10    10097770
24     6520632
Name: views, dtype: int64
# Example 10: Identify most commented trending video
most_commented = df_trending.sort_values('comment_count', ascending=False).iloc[0]
print(f"Most commented trending video: {most_commented['title']} (Comments: {most_commented['comment_count']})")
Most commented trending video: TREASURE - ‘IF I’ M/V (Comments: 39089)
# Example 11: Check for missing values in trending dataset (error handling)
missing = df_trending.isnull().sum()
print(missing[missing > 0])
Series([], dtype: int64)
# Example 12: Handle missing engagement by zero-filling
df_trending.fillna({'likes': 0, 'comment_count': 0}, inplace=True)
print(df_trending[['likes','comment_count']].isnull().sum())
likes            0
comment_count    0
dtype: int64
# Example 13: Correcting wrong metric aggregation (sum vs mean)
wrong_sum = df_content.groupby('platform')['engagement_rate'].sum()
right_mean = df_content.groupby('platform')['engagement_rate'].mean()
print('Wrong (sum):')
print(wrong_sum)
print('Correct (mean):')
print(right_mean)
Wrong (sum):
platform
Instagram    2113.179174
TikTok       1941.683993
YouTube      2250.156922
Name: engagement_rate, dtype: float64
Correct (mean):
platform
Instagram    13.044316
TikTok       12.690745
YouTube      12.163010
Name: engagement_rate, dtype: float64
# Example 14: Misinterpreting engagement ratios (catching divide-by-zero)
df_content.loc[df_content['views'] == 0, 'engagement_rate'] = np.nan
n_zeros = df_content['engagement_rate'].isnull().sum()
print(f'Number of posts with undefined engagement rate due to zero views: {n_zeros}')
Number of posts with undefined engagement rate due to zero views: 0
# Example 15: Wrong vs right grouping logic for YouTube categories
cat_mean_engagement = df_trending.groupby('category_id')['like_view_ratio'].mean().sort_values(ascending=False).head(3)
cat_wrong_agg = df_trending.groupby('category_id')[['likes','views']].sum()
cat_wrong_agg['ratio'] = (cat_wrong_agg['likes'] / cat_wrong_agg['views'] * 100).round(2)
wrong_top = cat_wrong_agg['ratio'].sort_values(ascending=False).head(3)
print(f'Right way (mean of individual ratios):\n{cat_mean_engagement}')
print(f'Wrong way (ratio of totals):\n{wrong_top}')
Right way (mean of individual ratios):
category_id
10    6.695667
17    6.610000
20    5.415734
Name: like_view_ratio, dtype: float64
Wrong way (ratio of totals):
category_id
10    6.62
17    6.61
23    5.05
Name: ratio, dtype: float64

Best Practices and Analytics Patterns#

  • Always check for missing or unexpected values before analysis.
  • Compare platforms or categories using normalized rates, not raw totals.
  • Track engagement, growth, and audience trends over time for context.
  • Group and segment by content type, time, or audience characteristics.
  • Use visualizations to spot trends, outliers, and patterns at a glance.
  • Document how metrics are defined for consistent results.
# Example 16: Benchmark content performance vs category average
chosen_category = df_trending['category_id'].iloc[0]
cat_avg = df_trending[df_trending['category_id'] == chosen_category]['like_view_ratio'].mean()
first_video_ratio = df_trending.iloc[0]['like_view_ratio']
print(f"First trending video like/view ratio: {first_video_ratio:.2f}%")
print(f"Category average like/view ratio: {cat_avg:.2f}%")
First trending video like/view ratio: 20.57%
Category average like/view ratio: 6.70%
# Example 17: Segment audience by content type (platform)
segment = df_content.groupby('platform')[['views','likes','comments','shares']].mean().round(0)
print('Average metrics per platform:')
print(segment)
Average metrics per platform:
             views   likes  comments  shares
platform                                    
Instagram  52755.0  4703.0    1335.0   812.0
TikTok     50206.0  4491.0    1269.0   784.0
YouTube    49390.0  4021.0    1335.0   749.0
# Example 18: Detecting viral content patterns
viral_threshold = df_content['engagement_rate'].mean() + 2 * df_content['engagement_rate'].std()
viral_posts = df_content[df_content['engagement_rate'] > viral_threshold]
print(f'There are {len(viral_posts)} viral posts based on engagement rate > 2 standard deviations above the mean.')
viral_posts[['post_id','platform','engagement_rate']].head()
There are 4 viral posts based on engagement rate > 2 standard deviations above the mean.
post_id platform engagement_rate
10 11 Instagram 21.205731
351 352 Instagram 21.781011
383 384 TikTok 20.925104
423 424 TikTok 20.882292
# Example 19: Visualize distribution of engagement rate
plt.hist(df_content['engagement_rate'].dropna(), bins=30, color='skyblue', edgecolor='black')
plt.xlabel('Engagement Rate (%)')
plt.ylabel('Number of Posts')
plt.title('Distribution of Engagement Rate Across All Social Posts')
plt.show()
No description has been provided for this image
# Example 20: Analyze growth trend in audience engagement over time
df_time['7d_avg'] = df_time['engagement_rate'].rolling(window=7).mean()
plt.figure(figsize=(10,5))
plt.plot(df_time['date'], df_time['7d_avg'], label='7-day Moving Average')
plt.xlabel('Date')
plt.ylabel('Engagement Rate (%)')
plt.title('Weekly Moving Average of Engagement Rate')
plt.legend()
plt.show()
No description has been provided for this image
# Example 21: End-to-end strategy: Find best posting time for highest engagement
df_content['hour'] = df_content['date'].dt.hour
hourly_engage = df_content.groupby('hour')['engagement_rate'].mean()
best_hour = hourly_engage.idxmax()
best_rate = hourly_engage.max()
print(f'Best posting hour (average engagement): {best_hour}:00 with {best_rate:.2f}% engagement rate')
Best posting hour (average engagement): 12:00 with 13.24% engagement rate
# Example 22: End-to-end strategy: Recommend category to focus on
best_cat_id = cat_mean_engagement.idxmax()
print(f'Recommend focusing on category {best_cat_id} for the highest average YouTube engagement!')
Recommend focusing on category 10 for the highest average YouTube engagement!

Lesson Review and Next Steps#

  • You now know how to identify top-performing content, trends, and pitfalls.
  • Data-driven strategies help you optimize for engagement and growth.
  • Combine these insights for recommendations tailored to your goals.
  • Experiment, analyze, and iteratelet the data guide your content strategy.
  • For more tutorials, subscribe to our YouTube channel!

Found this useful?

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