Mathew K Analytics

Lesson 5 · Social Media Content Analytics

Setting Up Python and Jupyter for Social Media Analytics

This lesson helps you configure Python and Jupyter to analyze real-world social media datasets. Social media analytics reveals what content works, who…

⬇ 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

Setting Up Python and Jupyter for Social Media Analytics#

  • This lesson helps you configure Python and Jupyter to analyze real-world social media datasets.
  • Social media analytics reveals what content works, who engages, and when trends emerge.
  • Learning these techniques empowers content creators and businesses to make better data-driven decisions.
  • By the end, you will use Python to load, explore, and interpret key engagement metrics from YouTube and other platforms.
  • You will move from setup through real analytics examples to actionable insights.
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

Key Social Media Analytics Concepts#

  • Social media datasets capture posts, videos, comments, and user engagement as rows of structured data.
  • Common engagement metrics include: views, likes, comments, shares, watch time, and click-through rate (CTR).
  • High views do not always mean high engagement; engagement ratios matter most for understanding quality.
  • CTR (Click-Through Rate) measures how often people click on content after seeing it.
  • Beginners often confuse reach (views) with impact (likes or comments per view).
  • Missing data and inconsistent metric definitions can lead to misleading analysis.
# Example 1: Load the YouTube Trending Videos dataset
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 = fetch_yt_trending()
    print('Live trending data:', df.shape)
except Exception as e:
    print(f'Falling back to synthetic: {e}')
    np.random.seed(42)
    n = 1000
    df = 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.shape)

print(df.head(3))
Falling back to synthetic: <HttpError 403 when requesting https://youtube.googleapis.com/youtube/v3/videos?part=snippet%2Cstatistics&chart=mostPopular&regionCode=US&maxResults=50&key=YOUR_GOOGLE_API_KEY&alt=json returned "The request cannot be completed because you have exceeded your <a href="/youtube/v3/getting-started#quota">quota</a>.". Details: "[{'message': 'The request cannot be completed because you have exceeded your <a href="/youtube/v3/getting-started#quota">quota</a>.', 'domain': 'youtube.quota', 'reason': 'quotaExceeded'}]">
Synthetic fallback: (1000, 8)
  video_id trending_date          title channel_title  category_id    views  \
0     vid0    2023-01-01  Video Title 0      ChannelC           10  3958242   
1     vid1    2023-01-02  Video Title 1      ChannelA           10  1227060   
2     vid2    2023-01-03  Video Title 2      ChannelC           24  1041519   

    likes  comment_count  
0  144402          18611  
1  160181           4977  
2   33966          18763  
# Example 2: Load a synthetic Social Media Content dataset
np.random.seed(42)
n_posts = 500
views = np.random.randint(100, 100000, n_posts)
df_posts = 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_posts.shape)
print(df_posts.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 3: Load a synthetic YouTube Analytics dataset
np.random.seed(42)
n_videos = 300
views_yta = np.random.randint(100, 500000, n_videos)
df_ytanal = pd.DataFrame({
    'video_id': range(1, n_videos+1),
    'publish_date': pd.date_range('2022-01-01', periods=n_videos, freq='D'),
    'views': views_yta,
    'watch_time': np.random.randint(1000, 500000, n_videos),
    'likes': (views_yta * np.random.uniform(0.01, 0.08, n_videos)).astype(int),
    'comments': (views_yta * np.random.uniform(0.001, 0.02, n_videos)).astype(int),
    'ctr': np.round(np.random.uniform(2, 10, n_videos), 2)
})

print(df_ytanal.shape)
print(df_ytanal.head(3))
(300, 7)
   video_id publish_date   views  watch_time  likes  comments   ctr
0         1   2022-01-01  122058      158381   1890      1975  4.14
1         2   2022-01-02  146967      481671   1730      1900  6.99
2         3   2022-01-03  132032      195806  10217       337  5.28
# Example 4: Load a simple social media time series dataset
np.random.seed(42)
n_days = 365
dates = pd.date_range('2023-01-01', periods=n_days, freq='D')
views_ts = np.random.randint(1000, 50000, n_days)
likes_ts = (views_ts * np.random.uniform(0.03, 0.12, n_days)).astype(int)
df_timeseries = pd.DataFrame({
    'date': dates,
    'views': views_ts,
    'likes': likes_ts,
    'engagement_rate': np.round(likes_ts / views_ts * 100, 2)
})

print(df_timeseries.shape)
print(df_timeseries.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 5: Load social media comments data for sentiment and text analysis
np.random.seed(42)
comments_data = [
    {'post_id': 1, 'platform': 'YouTube',   'comment': 'Great video, learned a lot!'},
    {'post_id': 1, 'platform': 'YouTube',   'comment': 'Very clear explanation, thank you.'},
    {'post_id': 2, 'platform': 'Instagram', 'comment': 'Amazing content as always.'},
    {'post_id': 2, 'platform': 'Instagram', 'comment': 'Not what I expected, a bit confusing.'},
    {'post_id': 3, 'platform': 'TikTok',    'comment': 'This is so helpful!'},
    {'post_id': 3, 'platform': 'TikTok',    'comment': 'Could you make a follow-up video?'},
    {'post_id': 4, 'platform': 'YouTube',   'comment': 'Terrible content, waste of time.'},
    {'post_id': 4, 'platform': 'YouTube',   'comment': 'I disagree with this approach.'},
    {'post_id': 5, 'platform': 'Instagram', 'comment': 'Loved it, sharing with my friends.'},
    {'post_id': 5, 'platform': 'TikTok',    'comment': 'The best tutorial I have seen.'},
]
df_comments = pd.DataFrame(comments_data)
df_comments['timestamp'] = pd.date_range('2023-01-01', periods=len(df_comments), freq='3h')

print(df_comments.shape)
print(df_comments.head(3))
(10, 4)
   post_id   platform                             comment           timestamp
0        1    YouTube         Great video, learned a lot! 2023-01-01 00:00:00
1        1    YouTube  Very clear explanation, thank you. 2023-01-01 03:00:00
2        2  Instagram          Amazing content as always. 2023-01-01 06:00:00
# Example 6: Basic calculation  Engagement rate on posts dataset
df_posts['engagement_rate'] = ((df_posts['likes'] + df_posts['comments'] + df_posts['shares']) / df_posts['views'] * 100).round(2)
print(df_posts[['platform','views','likes','comments','shares','engagement_rate']].head(5))
    platform  views  likes  comments  shares  engagement_rate
0  Instagram  15895   2356       116     253            17.14
1  Instagram    960     94        16      26            14.17
2  Instagram  76920   3910      2879    1185            10.37
3    YouTube  54986   1827       488    1637             7.19
4  Instagram   6365    253       261     163            10.64
# Example 7: Identify top-performing videos by views and engagement
top_views = df.sort_values('views', ascending=False).head(3)
top_engagement = (df.assign(engagement_rate=(df['likes']+df['comment_count'])/df['views']*100)
                 .sort_values('engagement_rate', ascending=False).head(3))
print('Top Videos by Views:')
print(top_views[['video_id','title','views','likes','comment_count']])
print('\nTop Videos by Engagement Rate:')
print(top_engagement[['video_id','title','views','likes','comment_count','engagement_rate']])
Top Videos by Views:
    video_id            title    views   likes  comment_count
307   vid307  Video Title 307  4975452   94575          10818
795   vid795  Video Title 795  4973401   54231          21072
60     vid60   Video Title 60  4966289  196849          13006

Top Videos by Engagement Rate:
    video_id            title  views   likes  comment_count  engagement_rate
675   vid675  Video Title 675  39725  199875          27974       573.565765
155   vid155  Video Title 155  38625  110255          23746       346.928155
821   vid821  Video Title 821  29675   93674           1639       321.189553
# Example 8: Summarize median metrics by content platform
by_platform = df_posts.groupby('platform').agg({'views':'median','likes':'median','comments':'median','shares':'median','engagement_rate':'median'})
print(by_platform)
             views   likes  comments  shares  engagement_rate
platform                                                     
Instagram  52934.0  3905.5    1026.5   568.5            12.79
TikTok     53021.0  3611.0     965.0   616.0            12.90
YouTube    52090.0  3251.0     958.0   632.0            12.01
# Example 9: Visualize trends  Daily engagement time series
import matplotlib.pyplot as plt
plt.figure(figsize=(11,5))
plt.plot(df_timeseries['date'], df_timeseries['engagement_rate'], label='Engagement Rate (%)')
plt.xlabel('Date')
plt.ylabel('Engagement Rate (%)')
plt.title('Daily Content Engagement Rate Over Time')
plt.legend()
plt.tight_layout()
plt.show()
No description has been provided for this image
# Example 10: Analyze CTR and watch time for video effectiveness
df_ytanal['engagement_index'] = (df_ytanal['likes'] + df_ytanal['comments']) / df_ytanal['views'] * df_ytanal['ctr']
top_effective = df_ytanal.sort_values('engagement_index', ascending=False).head(5)
print(top_effective[['video_id','views','watch_time','likes','comments','ctr','engagement_index']])
     video_id   views  watch_time  likes  comments   ctr  engagement_index
100       101  202383      301504  15483      2492  9.90          0.879286
177       178  158438      132373  12125      1549  9.93          0.857009
101       102  196869      214090  15068      1960  9.55          0.826018
225       226  357503       30592  24837      6571  8.81          0.773992
116       117   24400       39467   1760       393  8.76          0.772962
# Example 11: Detect viral spikes in content performance
viral_dates = df_timeseries[df_timeseries['engagement_rate'] > df_timeseries['engagement_rate'].quantile(0.99)]
print('Dates with viral engagement spikes:')
print(viral_dates[['date','engagement_rate']])
Dates with viral engagement spikes:
          date  engagement_rate
223 2023-08-12            11.93
279 2023-10-07            11.99
280 2023-10-08            11.96
# Example 12: Find the best posting times for likes and shares
df_posts['hour'] = df_posts['date'].dt.hour
by_hour = df_posts.groupby('hour').agg({'likes':'mean','shares':'mean'}).sort_values('likes', ascending=False)
print(by_hour.head())
         likes   shares
hour                   
12    4664.176  837.176
18    4428.880  770.104
0     4394.032  793.808
6     4055.440  719.096
# Example 13: Analyze comment text for positive and negative sentiment clues
pos_keywords = ['great','clear','amazing','helpful','loved','best']
neg_keywords = ['confusing','terrible','disagree','waste']
df_comments['sentiment'] = df_comments['comment'].str.lower().apply(lambda x: 'positive' if any(word in x for word in pos_keywords) else ('negative' if any(word in x for word in neg_keywords) else 'neutral'))
print(df_comments[['comment','sentiment']])
                                 comment sentiment
0            Great video, learned a lot!  positive
1     Very clear explanation, thank you.  positive
2             Amazing content as always.  positive
3  Not what I expected, a bit confusing.  negative
4                    This is so helpful!  positive
5      Could you make a follow-up video?   neutral
6       Terrible content, waste of time.  negative
7         I disagree with this approach.  negative
8     Loved it, sharing with my friends.  positive
9         The best tutorial I have seen.  positive
# Example 14: Advanced  Detect inconsistent engagement metric definitions
posts_zero_views = df_posts[df_posts['views'] == 0]
if not posts_zero_views.empty:
    print('Warning: Some posts have zero views, which would make engagement rate unreliable!')
    print(posts_zero_views[['post_id','platform']])
# Example 15: Advanced  Check for missing values before grouping or aggregating
missing_engagement = df_posts[['likes','comments','shares']].isnull().sum()
print('Missing values in engagement columns:')
print(missing_engagement)
if missing_engagement.any():
    df_posts[['likes','comments','shares']] = df_posts[['likes','comments','shares']].fillna(0)
    print('Filled missing engagement values with zeros.')
Missing values in engagement columns:
likes       0
comments    0
shares      0
dtype: int64
# Example 16: Best practice  Benchmark content performance by percentile
p80 = df['views'].quantile(0.80)
benchmark80 = df[df['views'] >= p80]
print(f'There are {len(benchmark80)} trending videos above the 80th percentile of views.')
There are 200 trending videos above the 80th percentile of views.
# Example 17: Best practice  Segment audience by post platform for optimization
audience_segments = df_posts.groupby('platform')['views'].agg(['count', 'mean', 'median', 'max'])
print(audience_segments)
           count          mean   median    max
platform                                      
Instagram    162  52754.950617  52934.0  99399
TikTok       153  50206.411765  53021.0  99813
YouTube      185  49390.124324  52090.0  98198
# Example 18: Best practice  Analyze trend growth using rolling averages
df_timeseries['views_7d_avg'] = df_timeseries['views'].rolling(7).mean()
print(df_timeseries[['date','views','views_7d_avg']].tail(10))
          date  views  views_7d_avg
355 2023-12-22  34828  37334.142857
356 2023-12-23  19711  33795.142857
357 2023-12-24   4420  31790.428571
358 2023-12-25  32216  31076.714286
359 2023-12-26   1301  24901.857143
360 2023-12-27  46236  24621.000000
361 2023-12-28   1699  20058.714286
362 2023-12-29   1190  15253.285714
363 2023-12-30  11492  14079.142857
364 2023-12-31  36743  18696.714286
# Example 19: Best practice  Consistent metric definitions for benchmarking and comparison
def compute_engagement_rate(row):
    if row['views'] < 1:
        return np.nan
    return (row['likes'] + row.get('comments',0) + row.get('shares',0)) / row['views'] * 100

df_posts['engagement_rate'] = df_posts.apply(compute_engagement_rate, axis=1).round(2)
print(df_posts[['platform','views','likes','comments','shares','engagement_rate']].head(3))
    platform  views  likes  comments  shares  engagement_rate
0  Instagram  15895   2356       116     253            17.14
1  Instagram    960     94        16      26            14.17
2  Instagram  76920   3910      2879    1185            10.37

Mini Challenge: From Data to Content Strategy#

  • Use the YouTube Trending Videos dataset to answer: What are the top content categories by engagement rate?
  • Make one recommendation for content strategy based on trends you observe.
  • Your answer should help a creator or brand prioritize their next videos.
# End-to-end: Find top content categories for video engagement
df['engagement_rate'] = ((df['likes'] + df['comment_count']) / df['views'] * 100).round(2)
cat_summary = df.groupby('category_id')['engagement_rate'].mean().sort_values(ascending=False)
print('Top content categories by mean engagement rate:')
print(cat_summary.head())
best_cat = cat_summary.idxmax()
print(f'Strategy: Focus next content on category {best_cat} for the highest engagement potential.')
Top content categories by mean engagement rate:
category_id
24    15.585389
10    13.590347
2     12.109281
1     10.455706
28    10.417647
Name: engagement_rate, dtype: float64
Strategy: Focus next content on category 24 for the highest engagement potential.
 

Found this useful?

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