Contents/ Part V · Putting It All Together/ Chapter 27

Case Study: Spotify Track Features

Our first full case study. One file of 915 songs, audio features like danceability, energy, loudness, and tempo, taken from its raw, messy state all the way to an analysis-ready table, with the reason for every cleaning decision spelled out as we go.

⏱️ ~15 min read
🐍 Notebook included
📊 Chapter 27

Time to put the whole toolkit to work. This chapter follows the five-beat routine from the Review & How to Read These Case Studies chapter, first contact, describe, visualize, prepare, recap, on a dataset of Spotify track features. The data is fun and intuitive, and it is messy in exactly the ways that teach the most.

🎧
The dataset

spotify_tracks.csv holds 915 songs. Each row is a track with audio features Spotify computes automatically: danceability, energy, loudness, tempo, valence (how happy it sounds), duration_ms, plus genre, key, mode, and a popularity score. It is a realistic teaching file: duplicates, missing values, inconsistent text, impossible numbers, and features on wildly different scales, all waiting for us.

1

First Contact: Audit the Raw File

Before describing or cleaning anything, we look. The first-contact audit (shape, dtypes, duplicates, missingness, and how the categories were actually typed) turns a mystery file into a concrete to-do list.

What the audit foundEvidenceThe fix (and its chapter)
Duplicate rows8 exact-copy rowsDeduplicate · Finding & Removing Duplicates and Inconsistencies
Inconsistent categoriesgenre typed 19 ways for 8 genres; mode 8 ways for 2Standardize categories · Finding & Removing Duplicates and Inconsistencies
Missing valuestempo (18), key (12), popularity (73)Handle missing data · Handling Missing Data
Missing in disguisetempo recorded as 0 BPM (impossible) in 23 rowsRecode then impute · Handling Missing Data
Impossible numbersduration_ms up to 18,000,000 (5 hours)Outlier treatment · Detecting & Treating Outliers
Clashing scalesfeatures span 0–1, −40–0, and the hundreds of thousandsScaling · Feature Engineering
More messy categoriesaudio_quality (ordinal: low/standard/high) and mood (nominal) in mixed casingClean & encode by type · Finding & Removing Duplicates and Inconsistencies & Feature Engineering
🧭
Look before you leap

Notice we have not changed a single value yet. The entire point of first contact is to understand the damage before touching it, so each later step is deliberate. A 0 BPM tempo and an 18,000,000 ms duration would both quietly wreck any average we computed; spotting them now means they never get the chance.

2

Describe & Visualize: What the Data Says

With the problems cataloged, we explore. Two things jump out immediately, one about the scales of the features, and one about how they relate.

Every feature lives on a different scale

Descriptive statistics show the audio features do not share a number range at all: danceability is a decimal near 1, loudness is negative decibels, tempo runs to the hundreds, and duration_ms is in the hundreds of thousands. On one shared (logarithmic) axis the gap is impossible to miss:

Seven features, seven different number scales (log axis) 1 10 100 1k 10k 100k 1M danceability 0.08 – 0.95 energy 0.02 – 0.98 valence 0.01 – 1.00 loudness (dB) −40 – 0 popularity 0 – 81 tempo (BPM) 0 – 299 duration (ms) ~600,000 duration dwarfs danceability by ~6 orders of magnitude, so without scaling it would dominate every distance and gradient.

One strong relationship, and a surprise

A correlation heatmap of the seven numeric features is almost entirely pale, the features are mostly independent, except for one hot cell: energy and loudness correlate at r ≈ 0.94. The surprise is the popularity row: it is near-zero against every audio feature, what makes a song popular here is not its sound.

Audio-feature correlations (one hot pair: energy & loudness) dnc eng lou tmp val dur pop dnc eng lou tmp val dur pop 1.00 0.04 0.02 0.05 0.01 0.03 -0.01 0.04 1.00 0.94 0.10 0.10 0.03 -0.01 0.02 0.94 1.00 0.11 0.10 0.04 -0.01 0.05 0.10 0.11 1.00 0.02 0.04 -0.06 0.01 0.10 0.10 0.02 1.00 -0.01 0.05 0.03 0.03 0.04 0.04 -0.01 1.00 -0.03 -0.01 -0.01 -0.01 -0.06 0.05 -0.03 1.00 eng ↔ lou = 0.94 near-duplicate pair: multicollinearity pop row ≈ 0: sound doesn't explain popularity dnc danceability · eng energy · lou loudness · tmp tempo · val valence · dur duration · pop popularity
🔎
Exploration produces findings, not just clean data

The heatmap earns its keep twice. The energy/loudness pair is a cleaning flag (we may drop or combine one before modeling). The flat popularity row is a genuine insight: a folk belief that "louder, higher-energy songs are more popular" simply is not supported in this data. Both came from one picture, which is the whole reason we visualize before we model.

3

Cleaning: Duplicates, Categories, Missing Values

Now we fix the to-do list, in order, and we say why at each step.

Duplicates first (see Finding & Removing Duplicates and Inconsistencies). Eight rows are exact copies of other rows. Left in, they would double-count those songs in every average and every chart, so drop_duplicates() removes them: 915 → 907 rows.

Standardize the categories (see Finding & Removing Duplicates and Inconsistencies). The genre column was typed nineteen ways ("Pop", "pop ", "POP", "EDM" for electronic, and so on). Until those collapse to the eight real genres, any group-by treats them as different. Trimming whitespace and mapping the variants fixes it; mode shrinks from eight spellings to two (Major / Minor) the same way.

Missing values, each on its merits (see Handling Missing Data). This is the step that rewards thought: the same problem has three different right answers depending on the column.

ColumnWhat is missingDecisionWhy
tempo18 NaN + 23 zeros (impossible) = 41Recode 0 → NaN, then median-imputeA measurement gap; the median resists extreme tempos
key12 missing musical keysMode-imputeA nominal category; fill the most common value
popularity73 missingDrop those rowsIt is the outcome we want to explain; never invent a target
🎯
The decision that matters most here

Imputing a missing feature is routine; imputing a missing target is dangerous. Because popularity is the thing we ultimately want to understand or predict, filling it with a guess would bake our assumptions into the answer. Dropping those 73 rows (about 8%) is the honest choice. Same missing-data problem, opposite treatment, because the column plays a different role.

4

Preparing to Model: Outliers, Transform, Scale & Encode

Treat the outliers (see Detecting & Treating Outliers). A box plot of duration_ms shows a few tracks running to impossible lengths, one at 18,000,000 ms, five hours. The IQR rule flags a couple dozen long songs, but most are genuine (extended mixes, classical works). Only three are physically impossible, so we remove just those errors and keep the real long songs. An outlier rule detects; a human decides.

Transform the skew (see Transformations). Even after the errors are gone, duration has a long right tail (skew ≈ +0.93). A natural-log transform straightens it to near-symmetric (skew ≈ +0.04), which steadies any model that prefers well-behaved inputs.

Scale, then encode by type (see Feature Engineering). Recall the log-axis chart: the features are on utterly different scales. Standardizing rescales each to mean 0 and SD 1 so none dominates by sheer magnitude. Then the categories are encoded according to whether they have an order. The file carries two extra category columns we cleaned alongside genre: audio_quality (low / standard / high) and mood (happy / sad / chill), both typed with messy casing. The first is ordinal, so it becomes integer codes that keep the order; the second is nominal, so it becomes one-hot columns, exactly like genre.

Encode by type: ordinal keeps order, one-hot does not invent one audio_quality · ORDINAL → 1, 2, 3 "Low", "low " "Standard","std" "HIGH","high " low = 1 standard = 2 high = 3 one column; 1 < 2 < 3 keeps the real order mood · NOMINAL → one-hot (1/0) happysadchill "Happy" 100 "sad " 010 "chill" 001 one column per category; no false order implied Both are cleaned first (trim, lowercase, fix "std"→standard); then the encoding depends only on whether order is real. Label-encoding mood (happy=0, sad=1, chill=2) would be wrong, it would invent an order that does not exist.

Here is the whole journey, from raw file to analysis-ready table:

From raw file to analysis-ready: every row accounted for 915raw rows 907deduped 834target kept 831errors gone 831×19analysis-ready −8 dupesCh 19 −73 no targetCh 20 −3 errorsCh 21 log · scale · encodeCh 22 & 24 831 clean songs, 6 scaled + popularity + audio_quality (ordinal) + 8 genre + 3 mood columns = 19 model-ready columns.
🤖
Order matters

We transformed before scaling, and scaled after the outliers were handled. That order is not cosmetic: scaling a skewed, outlier-ridden column just bakes the skew and the outliers into the scaled values. Clean, then transform, then scale, then encode. Each step assumes the previous one is done.

5

Exploring the Clean Data

Cleaning was never the goal; understanding is. Now that the duplicates, missing values, and errors are gone, the describing toolkit from Describing Data and the charts from Visualizing Data finally tell the truth. This is the payoff beat: a last, exploratory look at what the data actually says.

Measurements: the numbers behind two features

The full descriptive summary of tempo and popularity, computed on the clean data, pulls together everything from Chapters 8 to 11:

Measurementtempo (BPM)popularity (0–100)What it tells us
Mean / median / mode (see Measures of Central Tendency)118.4 / 117.6 / 11828.7 / 26.0 / 22Both centers cluster tightly; mean > median hints at mild right skew
Std dev / IQR (see Measures of Dispersion & Measures of Position)26.7 / 32.716.7 / 24.0Absolute spread of each variable
Coeff. of variation (see Measures of Dispersion)23%58%Popularity is far more variable relative to its mean
Quartiles Q1 / Q3 (see Measures of Position)101.4 / 134.116.0 / 40.0The middle 50% of songs sits here
Skewness (see Shape of a Distribution)+0.65+0.55Both lean mildly right
Kurtosis (see Shape of a Distribution)+3.77−0.37Tempo is sharply peaked (leptokurtic); popularity is flatter

The standout is the coefficient of variation: at 58% versus 23%, songs differ far more in popularity than in tempo once you account for their different averages, the kind of comparison a raw standard deviation cannot make across two different units (see Measures of Dispersion). And tempo's high kurtosis says most songs huddle around 118 BPM with a few far-off tails, a shape no single average reveals.

A bar chart for the categories (see Charts for Categorical Data)

With genres finally standardized, a simple bar chart of counts is trustworthy: the eight genres are reasonably balanced, with pop, rock, and hip-hop the most common.

Genre distribution after cleaning (831 songs) 167pop 150rock 144hip-hop 117electronic 79country 74r&b 58jazz 42classical

Grouped box plots reveal hidden structure (Chapters 15 & 16)

The most useful exploratory chart compares a numeric variable across a category. Box plots of energy by genre split cleanly into two clusters, an upbeat group (pop, rock, electronic) and a mellow group (jazz, country, r&b, hip-hop, classical), structure that was not safe to read until the data was clean.

Energy by genre: two natural clusters 00.250.50.751.0 energy high-energy mellow rock electronic pop jazz country r&b hip-hop classical box = middle 50% (IQR), line = median, whiskers = range; pop/rock/electronic sit a clear notch higher.
🎭
A folk belief, tested and rejected

People often say songs in a major key "sound happier." We can check it directly: grouping valence (Spotify's happiness score) by mode gives 0.522 for Major versus 0.516 for Minor, a gap of about 0.006, essentially nothing. In this dataset the folklore does not hold. Being able to test a claim instead of repeating it is exactly what exploratory analysis buys you (see Multivariate & Specialized Visuals).

The last visual check: all the prep work, at a glance

Before declaring victory, one final summary. Every problem the audit found, and what we did about it, on a single scorecard:

Prep-work scorecard: raw file → analysis-ready Duplicates · 8 exact rows removed Ch 19 Categories · genre 19 → 8, mode 8 → 2 standardized Ch 19 Missing · tempo (41) median-imputed, key (12) mode-imputed Ch 20 Target · 73 rows missing popularity dropped (never imputed) Ch 20 Outliers · 3 impossible durations removed, genuine long songs kept Ch 21 Skew & scale · duration logged (+0.93 → +0.04), 6 features standardized Ch 22, 24 Encode by type · audio_quality ordinal (1/2/3); genre & mood one-hot (11 columns) Ch 24 915 × 15 raw → 831 × 19 analysis-ready · every decision logged
🔁
Why explore again at the end?

EDA is iterative. The describing and visualizing we did on the raw file flagged the problems; doing it again on the clean file confirms the fixes held and surfaces the real findings, the energy clusters, the popularity variability, the debunked folklore. The loop closes only when the clean data tells a consistent, trustworthy story.

🐍

Run the whole case study in Python

The companion notebook is the full walkthrough end to end: the first-contact audit, descriptive statistics, the popularity histogram, the correlation heatmap and the energy-versus-loudness scatter, then every cleaning step (dedup, category standardization, the three missing-value treatments, outlier removal, the log transform, scaling, and one-hot encoding), then a final exploration beat on the clean data, a descriptive measurements table, the genre bar chart, the major-vs-minor folklore check, and the energy-by-genre box plots, finishing at the analysis-ready table. Every cell explains its decision.

📓 View Notebook (code & outputs) ▶ Open in Colab ⬇ View / Download on GitHub

View opens the rendered notebook instantly (no setup). Open in Colab runs & edits it live in your browser. To run locally, install numpy, pandas, scikit-learn, scipy, matplotlib and launch jupyter notebook.

🎓 Key Takeaways

  • Audit first: shape, duplicates, missingness, and how categories were typed turned a messy file into a clear to-do list.
  • Visualize to find, not just to check: the heatmap exposed an energy/loudness near-duplicate (r ≈ 0.94) and that audio features barely explain popularity.
  • Missing data has no single fix: median-impute a feature gap, mode-impute a category, but drop rows missing the target.
  • Outliers need judgment: remove the 3 impossible durations, keep the genuine long songs.
  • Order the prep: clean → transform skew → scale → encode took 915 raw rows to an 831 × 19 analysis-ready table.
  • Encode by type: the ordinal audio_quality became 1/2/3 (order preserved); nominal genre and mood became one-hot 1/0 columns (no false order).
  • Explore again on clean data: measurements (CV, kurtosis) and grouped box plots exposed genre energy-clusters and debunked the major-key folklore, insight you can only trust once the data is fixed.
6

Practice Challenges

Five challenges on the Spotify data, following the case study beat by beat. Try them in Python before checking the solutions.

1

Dedup & standardize categories

Remove exact duplicate rows, then collapse the many genre spellings into the real genres. Report the before/after label count and the genre value counts.

Hint: drop_duplicates(), then .str.strip() + a replace map.
2

Fix missing-as-zero tempo

Recode a tempo of 0 to NaN, count the now-missing values, and median-impute them. Why the median rather than the mean?

Hint: replace(0, np.nan) then fillna(median); the median resists extremes.
3

Errors vs genuine extremes

Apply the IQR rule to duration_ms. How many rows does it flag, and which are real data errors? Remove only the errors and justify keeping the rest.

Hint: fence = Q3 + 1.5·IQR; only the > 10-minute values are impossible.
4

Find the multicollinear pair

Compute the correlation matrix of the audio features and identify the most strongly correlated pair. What would you do about it before fitting a linear model?

Hint: largest off-diagonal |r| is energy & loudness (≈ 0.94); drop or combine one.
5

Transform, then scale

Log-transform duration (report skew before and after), then standardize the feature set so each column has mean 0 and SD 1. Why transform before scaling?

Hint: np.log, then StandardScaler; scaling a skewed column keeps the skew.
Check your work

A fully-worked solutions notebook answers all five on the real data, in the same visual style. Try them yourself first, then compare.

📓 View Solutions ▶ Open Solutions in Colab ⬇ View / Download on GitHub
7

Quiz: Test Yourself

Eight questions on the Spotify case study. Answer them, hit Check Answers, and keep refining until you score 100%. Your progress is saved, so you can hop back to the chapter and return anytime.