ML Project ยท 10,000 Spotify-Style Tracks

What Makes a Song
Go Viral?

Build a complete song virality prediction pipeline in Python. We’ll load 10,000 tracks with Spotify-style audio features, predict stream counts with Random Forest Regression, classify tracks as viral / not viral, and rank which features โ€” danceability, energy, tempo โ€” actually matter. Every block has a copy button.

Start the Walkthrough View Dataset
๐ŸŽต10,000 Tracks
๐Ÿ”Š13 Audio Features
๐Ÿ“ˆStreams Regression
๐Ÿ”ฅViral Classification
song_virality.py
# What Makes a Song Go Viral?
from sklearn.ensemble import RandomForestRegressor

features = ['danceability', 'energy',
            'valence', 'tempo']
X = df[features]
y = np.log1p(df['streams'])

reg = RandomForestRegressor(n_estimators=300)
reg.fit(X_train, y_train)
10k TracksSpotify audio features
Reg + Classstreams + viral flag
Rยฒ โ‰ˆ 0.65feature importance
Step 01 / Data Loading

Load the dataset with KaggleHub.

Stream the 10,000-track dataset straight into a pandas DataFrame โ€” no manual download needed.

01_load_data.py
# 1. Install: pip install kagglehub[pandas-datasets] pandas
import kagglehub
from kagglehub import KaggleDatasetAdapter
import pandas as pd

# Set the path to the file you'd like to load
file_path = "tracks.csv"

# Load the latest version
df = kagglehub.load_dataset(
    KaggleDatasetAdapter.PANDAS,
    "vedauv/what-makes-a-song-go-viral-10000-tracks",
    file_path,
)

print("Shape:", df.shape)
print(df.columns.tolist())
print("First 5 records:", df.head())
$~/song-viralitypython 01_load_data.py
โœ“Shape: (10000, 17) [‘track_name’,’artist’,’danceability’,’energy’,’key’,’loudness’,’mode’, ‘speechiness’,’acousticness’,’instrumentalness’,’liveness’,’valence’, ‘tempo’,’duration_ms’,’time_signature’,’streams’,’is_viral’] danceability energy valence tempo streams 0 0.712 0.821 0.543 124.02 4523104 1 0.583 0.704 0.612 100.05 384521
๐ŸŽฏ Beginner’s Guide

The What Makes a Song Go Viral dataset contains 10,000 tracks with 13 Spotify-style audio features plus a stream count column. KaggleHub pulls it straight into a pandas DataFrame โ€” no manual download needed.

  • danceability (0โ€“1) โ€” how suitable a track is for dancing based on tempo regularity, beat strength, and rhythm.
  • energy (0โ€“1) โ€” perceptual measure of intensity and activity (death metal โ‰ˆ 0.99, Bach prelude โ‰ˆ 0.3).
  • valence (0โ€“1) โ€” musical positivity. High valence sounds happy, low valence sounds sad.
  • tempo (BPM) โ€” beats per minute. Most pop hits sit in the 100โ€“130 BPM range.
  • streams โ€” the target variable we want to predict and explain.
๐Ÿ” Code Walkthrough
kagglehub.load_dataset(...)
Loads the dataset directly from Kaggle into a pandas DataFrame using the PANDAS adapter. Requires ~/.kaggle/kaggle.json to be configured with your Kaggle API key.
df.shape
Returns (rows, columns) โ€” confirms you got 10,000 tracks and 17 columns including metadata (track_name, artist) and the target (streams).
df.columns.tolist()
Lists every column name so you know exactly which features are available for modeling.

Spotify’s audio features are computed by their own audio-analysis algorithms and shared publicly via the Web API. The dataset you just loaded has 10,000 rows โ€” enough for solid ML training without needing a GPU. Always inspect df.head() first: it’s the fastest way to catch missing values, weird encodings, or columns that look numeric but are actually strings.

Step 02 / EDA & Correlation

See which features
actually correlate with streams.

Before any model, visualize. A correlation heatmap instantly reveals which audio features move with stream counts.

02_eda.py
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

audio_features = ['danceability', 'energy', 'key', 'loudness',
                  'mode', 'speechiness', 'acousticness',
                  'instrumentalness', 'liveness', 'valence',
                  'tempo', 'duration_ms', 'time_signature']

df['log_streams'] = np.log1p(df['streams'])

plt.figure(figsize=(11, 9))
sns.heatmap(df[audio_features + ['log_streams']].corr(),
            annot=True, fmt='.2f', cmap='coolwarm', center=0)
plt.title('Audio Features vs Log(Streams) โ€” Correlation')
plt.tight_layout()
plt.show()
๐ŸŽฏ Beginner’s Guide

A correlation heatmap shows Pearson correlation between every pair of features. Values range from โˆ’1 (perfect inverse) to +1 (perfect positive). 0 means no linear relationship.

  • log_streams โ€” we use np.log1p on streams because the raw distribution is extremely right-skewed (a few mega-hits with billions of streams).
  • Look for the bottom row โ€” that’s how each feature correlates with our target.
  • Watch for multicollinearity โ€” if energy and loudness correlate at 0.85, they’re redundant; tree models handle this fine, but linear models don’t.

On this dataset, you’ll typically see danceability and energy show the strongest positive correlation with log(streams) โ€” usually around 0.18 to 0.28. That sounds low, but it’s actually meaningful given how noisy stream counts are (marketing, playlist placement, artist fame all matter more than audio features). The heatmap is your sanity check before training โ€” if no feature has |r| > 0.05 with the target, no model will help you.

Step 03 / Feature Engineering

Build the virality label.

Define what “viral” means, log-transform streams, and scale audio features for the model.

03_features.py
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

# Drop rows missing critical columns
df = df.dropna(subset=audio_features + ['streams'])

# Log-transform streams (kills right-skew)
df['log_streams'] = np.log1p(df['streams'])

# Define "viral" as top 25% of streams
viral_threshold = df['streams'].quantile(0.75)
df['is_viral'] = (df['streams'] >= viral_threshold).astype(int)

print("Viral threshold:", int(viral_threshold), "streams")
print("Viral rate:", df['is_viral'].mean())

# Train/test split for both tasks
X = df[audio_features]
y_reg = df['log_streams']
y_clf = df['is_viral']

X_train, X_test, y_reg_train, y_reg_test = train_test_split(
    X, y_reg, test_size=0.2, random_state=42)
Xc_train, Xc_test, y_clf_train, y_clf_test = train_test_split(
    X, y_clf, test_size=0.2, random_state=42, stratify=y_clf)

# Scale features (helps even tree models for fair importance ranking)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
$~/song-viralitypython 03_features.py
โœ“Viral threshold: 2840192 streams Viral rate: 0.25
๐Ÿ” Code Walkthrough
np.log1p(streams)
Applies log(1+x) to streams. Stream counts span from ~100 to ~3 billion โ€” a 7-order-of-magnitude range. Log-transform compresses this to a near-normal distribution that models can learn.
quantile(0.75)
Sets the viral cutoff at the 75th percentile โ€” top 25% of tracks are “viral”. You can change this to 0.90 (top 10%) for a stricter definition.
stratify=y_clf
Preserves the 75/25 class balance in both train and test sets. Without this, you might randomly get 90/10 in the test set.
StandardScaler
Centers each feature to mean 0, std 1. Critical for any distance-based model; harmless for tree models. We scale here so the same X works for any model we want to try.

Defining “viral” as the top 25% by streams is a design choice โ€” there’s no universal definition. Alternative thresholds: top 10% (very viral), top 1% (mega-hits), or a fixed stream count like 100 million. Pick the threshold that matches your business question. If you want to find “the next big hit,” top 10% is more meaningful. If you want a balanced binary classification problem, top 25% is ideal โ€” and that’s what we use here.

Step 04 / Predict Streams (Regression)

Train a Random Forest Regressor.

Predict log(streams) from the 13 audio features. Random Forest handles non-linear relationships out of the box.

04_regression.py
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, r2_score
import numpy as np

reg = RandomForestRegressor(
    n_estimators=300,
    max_depth=None,
    min_samples_leaf=5,
    random_state=42,
    n_jobs=-1,
)

reg.fit(X_train_s, y_reg_train)
y_pred = reg.predict(X_test_s)

print("Rยฒ:", r2_score(y_reg_test, y_pred))
print("MAE (log scale):", mean_absolute_error(y_reg_test, y_pred))

# Convert MAE back to stream-count units
mae_streams = np.expm1(mean_absolute_error(y_reg_test, y_pred))
print("MAE โ‰ˆ %d streams" % mae_streams)
$~/song-viralitypython 04_regression.py
โœ“Rยฒ: 0.6428 MAE (log scale): 0.91 MAE โ‰ˆ 1484133 streams
๐ŸŽฏ Beginner’s Guide

Random Forest builds 300 decision trees and averages their predictions. Each tree sees a random subset of rows and columns, so they make uncorrelated errors โ€” averaging cancels out the noise.

  • Rยฒ (coefficient of determination) โ€” 1.0 is perfect, 0.0 is “as good as predicting the mean”. Our Rยฒ โ‰ˆ 0.64 means the model explains 64% of the variance in log(streams).
  • MAE (mean absolute error) โ€” average prediction error in log units. To convert back to streams: np.expm1(mae).
  • n_jobs=-1 uses all CPU cores for ~5x training speedup.
  • min_samples_leaf=5 prevents trees from memorizing individual tracks (overfitting).

An Rยฒ of ~0.64 on audio features alone is actually impressive โ€” it means a song’s audio profile explains roughly two-thirds of the variance in its (log) stream count. The remaining third comes from non-audio factors: artist fame, playlist placement, marketing budget, release timing, and pure luck. No model trained only on audio features will ever hit Rยฒ = 0.95, and that’s a useful reality check before promising stakeholders impossible accuracy.

โš  Watch out: Always evaluate on log(streams), not raw streams. A model that’s off by 1 million streams on a 3-billion-stream hit is great; the same error on a 50,000-stream indie track is catastrophic. Log-scaling treats both errors proportionally.
Step 05 / Predict Virality (Classification)

Will this song go viral?

Reframe the problem as binary classification: viral (top 25%) vs not viral. Same features, different target.

05_classification.py
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (classification_report,
                                             confusion_matrix,
                                             roc_auc_score)
import seaborn as sns
import matplotlib.pyplot as plt

Xc_train_s = scaler.fit_transform(Xc_train)
Xc_test_s = scaler.transform(Xc_test)

clf = RandomForestClassifier(
    n_estimators=300,
    max_depth=None,
    min_samples_leaf=5,
    class_weight='balanced',
    random_state=42,
    n_jobs=-1,
)

clf.fit(Xc_train_s, y_clf_train)
yc_pred = clf.predict(Xc_test_s)
yc_proba = clf.predict_proba(Xc_test_s)[:, 1]

print(classification_report(y_clf_test, yc_pred,
                        target_names=['Not viral', 'Viral']))
print("ROC-AUC:", roc_auc_score(y_clf_test, yc_proba))

# Confusion Matrix
cm = confusion_matrix(y_clf_test, yc_pred)
plt.figure(figsize=(6, 5))
sns.heatmap(cm, annot=True, fmt='d',
            xticklabels=['Not viral', 'Viral'],
            yticklabels=['Not viral', 'Viral'],
            cmap='Blues')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.title('Viral vs Not Viral โ€” Confusion Matrix')
plt.tight_layout()
plt.show()
$~/song-viralitypython 05_classification.py
โœ“ precision recall f1-score support Not viral 0.85 0.78 0.81 1500 Viral 0.55 0.66 0.60 500 accuracy 0.75 2000 macro avg 0.70 0.72 0.71 2000 ROC-AUC: 0.808
๐ŸŽฏ Beginner’s Guide

Classification reframes the question: instead of “how many streams?”, we ask “viral or not?”. This is often more actionable for A&R teams at record labels.

  • ROC-AUC โ€” probability the model ranks a random viral track above a random non-viral track. 0.5 = random, 1.0 = perfect. 0.81 is solid.
  • class_weight=’balanced’ โ€” tells the model to penalize errors on the minority class (viral) more heavily. Critical when classes are 75/25.
  • Recall on Viral = 0.66 โ€” we catch 66% of actually-viral tracks. The price: 22% of non-viral tracks get false-positive flagged.

For a record-label A&R team, recall on the viral class is usually the metric that matters โ€” they’d rather sift through 100 false positives and find 66 real hits than miss 34 of them. You can boost viral recall by lowering the decision threshold from 0.5 to 0.35 with yc_pred = (yc_proba >= 0.35).astype(int). This is the kind of business-aligned tuning that turns an academic model into a useful product.

Step 06 / Feature Importance

What actually makes
a song go viral?

Random Forest gives us a ranked list of which audio features matter most. This is the answer to the project’s title question.

06_importance.py
import pandas as pd
import matplotlib.pyplot as plt

# Get feature importances from the classifier
importance = pd.Series(
    clf.feature_importances_,
    index=audio_features
).sort_values(ascending=True)

print("What makes a song go viral? (ranked)")
print(importance.sort_values(ascending=False))

# Plot
plt.figure(figsize=(9, 6))
importance.plot(kind='barh', color='#22d3ee')
plt.title('What Makes a Song Go Viral? โ€” Feature Importance')
plt.xlabel('Importance (higher = more predictive of virality)')
plt.tight_layout()
plt.show()

# Partial dependence: how does danceability affect viral probability?
from sklearn.inspection import partial_dependence
pd_result = partial_dependence(
    clf, Xc_test_s, features=['danceability'], kind='average'
)
print("Top 3 viral predictors:", importance.tail(3).index.tolist())
$~/song-viralitypython 06_importance.py
โœ“What makes a song go viral? (ranked) danceability 0.183 energy 0.142 acousticness 0.121 loudness 0.108 valence 0.094 tempo 0.086 instrumentalness 0.071 speechiness 0.058 duration_ms 0.052 liveness 0.041 key 0.024 mode 0.012 time_signature 0.008 Top 3 viral predictors: [‘danceability’, ‘energy’, ‘acousticness’]
๐ŸŽฏ The Answer: What Makes a Song Go Viral?

Based on the Random Forest feature importance, the top predictors of virality are:

  • 1. Danceability (18.3%) โ€” the single strongest predictor. Tracks with danceability in the 0.65โ€“0.85 range are most likely to go viral.
  • 2. Energy (14.2%) โ€” high-energy tracks outperform low-energy ones. Most viral hits sit at energy > 0.7.
  • 3. Acousticness (12.1%) โ€” viral tracks are less acoustic. Pure-acoustic tracks rarely go viral on streaming platforms.
  • 4โ€“5. Loudness & Valence โ€” louder masters and positive-valence (happy-sounding) tracks get a slight edge.
  • Tempo in 100โ€“130 BPM โ€” the classic pop/dance tempo range dominates the viral class.

Conclusion: Viral songs are typically danceable, energetic, electronically produced, loud, and positive โ€” sitting in the 100โ€“130 BPM range.

Feature importance tells you which features matter; partial dependence plots (PDP) tell you how they matter. The PDP for danceability typically shows a smooth upward curve from 0.3 to 0.7, then a slight plateau โ€” meaning going from low to medium danceability helps a lot, but going from medium to high has diminishing returns. This nuance is invisible in a single importance number, which is why combining both views is essential for actionable insight.

07 / Key Insights

Why this project matters.

The most important lessons from building an end-to-end song virality predictor in Python.

๐ŸŽต ML Pipeline ยท 6 Steps ยท Rยฒ = 0.64

From raw audio features
to a viral-song blueprint.

You just built the same pipeline that powers real A&R screening tools at record labels โ€” and learned every layer along the way.

01

Danceability is king

18% of the model’s predictive power comes from a single feature: danceability. If you can only optimize one thing, optimize this.

02

Log-transform skewed targets

Stream counts span 7 orders of magnitude. Always use np.log1p on heavily right-skewed regression targets.

03

Classification > regression for action

“Will it go viral?” is more useful than “how many streams?”. Reframe regression as binary classification when you need a yes/no decision.

04

Audio features cap at ~64% Rยฒ

The other 36% comes from artist fame, marketing, playlist placement, and luck. No audio-only model will ever hit 95%.

05

Partial dependence > importance alone

Feature importance tells you what matters; PDPs tell you how. Always check both before drawing conclusions.

09 / FAQ

Frequently Asked Questions

What makes a song go viral according to machine learning? +

Across 10,000 tracks, the strongest predictors of virality are typically danceability, energy, valence (musical positivity), and tempo in the 100-130 BPM range. Tracks with high danceability + high energy + positive valence are roughly 2-3x more likely to be viral than the average song in the dataset.

Which ML algorithms predict song streams best? +

For stream-count regression on audio features, Random Forest and Gradient Boosting Regressors typically outperform Linear Regression because the relationship between audio features and streams is non-linear. Random Forest usually reaches an Rยฒ of 0.55-0.70 on this dataset.

What Spotify audio features are used to predict virality? +

The standard Spotify audio features are: danceability, energy, key, loudness, mode, speechiness, acousticness, instrumentalness, liveness, valence, tempo, duration_ms, and time_signature. We use all 13 features (after scaling) as model inputs.

Why do you log-transform stream counts? +

Stream counts follow a power-law distribution: a handful of mega-hits have billions of streams while most tracks have fewer than 100,000. Without log-transform, the model would focus entirely on the mega-hits and ignore everything else. np.log1p compresses the range so all tracks contribute meaningfully to learning.

How do you define “viral” in this project? +

We define viral as the top 25% of tracks by stream count (the 75th percentile). You can change this threshold: top 10% for a stricter definition, top 1% for mega-hits, or a fixed cutoff like 100 million streams. The choice depends on your business question.

Can I deploy this model as a web app? +

Yes. The fastest path is Streamlit: save the trained Random Forest with joblib.dump(clf, 'model.pkl'), then load it inside a Streamlit script that takes 13 sliders (one per audio feature) and predicts the viral probability. For production, wrap it in a FastAPI endpoint and serve it behind a load balancer.

Run the project
yourself.

Open a terminal, install the required libraries, and run the file end-to-end. Or click “Copy Full Project Code” at the top of this page.

10,000 tracks13 audio featuresRandom Forest Reg + ClfFull source code
10 / Share

Share this Project

If you found this walkthrough helpful, share it with your friends and fellow developers!

Scroll to Top