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.
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.
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 found | Evidence | The fix (and its chapter) |
|---|---|---|
| Duplicate rows | 8 exact-copy rows | Deduplicate · Finding & Removing Duplicates and Inconsistencies |
| Inconsistent categories | genre typed 19 ways for 8 genres; mode 8 ways for 2 | Standardize categories · Finding & Removing Duplicates and Inconsistencies |
| Missing values | tempo (18), key (12), popularity (73) | Handle missing data · Handling Missing Data |
| Missing in disguise | tempo recorded as 0 BPM (impossible) in 23 rows | Recode then impute · Handling Missing Data |
| Impossible numbers | duration_ms up to 18,000,000 (5 hours) | Outlier treatment · Detecting & Treating Outliers |
| Clashing scales | features span 0–1, −40–0, and the hundreds of thousands | Scaling · Feature Engineering |
| More messy categories | audio_quality (ordinal: low/standard/high) and mood (nominal) in mixed casing | Clean & encode by type · Finding & Removing Duplicates and Inconsistencies & Feature Engineering |
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.
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:
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.
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.
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.
| Column | What is missing | Decision | Why |
|---|---|---|---|
| tempo | 18 NaN + 23 zeros (impossible) = 41 | Recode 0 → NaN, then median-impute | A measurement gap; the median resists extreme tempos |
| key | 12 missing musical keys | Mode-impute | A nominal category; fill the most common value |
| popularity | 73 missing | Drop those rows | It is the outcome we want to explain; never invent a target |
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.
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.
Here is the whole journey, from raw file to analysis-ready table:
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.
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:
| Measurement | tempo (BPM) | popularity (0–100) | What it tells us |
|---|---|---|---|
| Mean / median / mode (see Measures of Central Tendency) | 118.4 / 117.6 / 118 | 28.7 / 26.0 / 22 | Both centers cluster tightly; mean > median hints at mild right skew |
| Std dev / IQR (see Measures of Dispersion & Measures of Position) | 26.7 / 32.7 | 16.7 / 24.0 | Absolute 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.1 | 16.0 / 40.0 | The middle 50% of songs sits here |
| Skewness (see Shape of a Distribution) | +0.65 | +0.55 | Both lean mildly right |
| Kurtosis (see Shape of a Distribution) | +3.77 | −0.37 | Tempo 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.
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.
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:
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 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_qualitybecame 1/2/3 (order preserved); nominalgenreandmoodbecame 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.
Practice Challenges
Five challenges on the Spotify data, following the case study beat by beat. Try them in Python before checking the solutions.
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.
drop_duplicates(), then .str.strip() + a replace map.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?
replace(0, np.nan) then fillna(median); the median resists extremes.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.
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?
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?
np.log, then StandardScaler; scaling a skewed column keeps the skew.A fully-worked solutions notebook answers all five on the real data, in the same visual style. Try them yourself first, then compare.
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.