🎵

Hit Songs Choice Engine

Pick a reference song to find the songs a listener who loves it would value most.

CE: quadratic consumer surplus with logistic (or chart-points NNLS) shadow prices · VSM: cosine similarity

Algorithm Information

CE — Consumer Surplus Formula

CS(cand | ref) = Σcontinuous b·q_R·q_C − ½·b·q_C²
             + Σ0/1 flags ½·b·q_R·q_C   (quadratic option: b·q_R·q_C − ½·b·q_C²)
             + b_era·e_R·e_C − ½·b_era·e_C²   (e = (year − 1960) / 50)

All features are scaled to [0, 1] (max-normalised; loudness min-max because decibels have no natural zero). The X factor is shown on every card but, as in the other explorers without a price, it is not added to the ranking score.

Adapting a logistic regression to the CE

Shadow prices. The CE needs one non-negative weight per feature measuring how much a unit of that feature is worth. A logistic slope on a [0, 1] feature is exactly that, in log-odds of being a year-end top-100 hit rather than dollars. Rankings do not depend on the overall scale of the weights, so log-odds units work as well as prices.

Negative slopes → reflection. Where a slope is negative (e.g. energy, once loudness and danceability are held fixed), the feature is replaced by its complement q′ = 1 − q ("calm", "sung not rapped", "clean", "not country"). The refit has an identical log-likelihood and exactly flipped signs, so every β ≥ 0 and every q′ stays in [0, 1]. This keeps self-rank-1 without zeroing features, which a β ≥ 0 constrained fit would have done to nine of them.

Artist history switch. The artist's previous chart entries and top-10 hits are strong predictors, but they describe the artist, not the song. With the switch off (default) the engine uses weights from models refitted on song features alone, and "hit potential" is that song-only model's fitted probability. With it on the engine uses the full model: the two track-record features join the CE score as continuous terms (entries reflected to 1 − q, since more prior entries with the same top-10 count lowers the odds), so candidates whose artist has a similar track record gain utility and dissimilar ones lose it. Hit potential then includes track record. VSM adds the same two dimensions when the switch is on. Within-year AUC (in-sample): song only 0.647, with artist 0.668; out of sample the top decile's hit rate is 42.8% vs 46.1%.

Chart-year switch. The logit model includes a fixed effect for each chart year, because the share of charting songs that make the year-end 100 varies with how crowded the chart was: about 18% in the 1960s and 2010, 28–30% in the 1990s–2000s. With the switch on (default), hit potential is the model's fitted probability for the song's own year. With it off, each song's year effect is swapped for the average one, so hit potential compares songs on their features alone, across decades. The CE ranking never uses the year effect (it sits in the X factor, which is displayed but not ranked), so only hit potential and its column sort change. The chart-points model is already measured relative to each chart year and is unaffected.

Flag scoring switch. The original CE gives a shared 0/1 flag a bonus of ½·b·q_R·q_C and nothing else. A dyadic analysis of all 319 million ordered pairs (logit weights, song features only, era off) found this makes hubs: songs that carry every hit-favoured flag at once (pop, rock, easy listening and dance tags; minor key; 4/4; clean; no soul or country tag) collect bonuses from any reference that shares any of them. One song appeared in 224 other songs' top 10s. The top 1% of songs held 10% of all top-10 slots, and 13% of songs never appeared in any. Switched on, flags use the same quadratic form as continuous features, b·q_R·q_C − ½·b·q_C²: +½b when both songs have the flag, −½b when only the candidate does, 0 otherwise. Hubs disappear (the top 1% hold 2.7% of slots, as under VSM). CE and VSM top 10s share 4.8 songs instead of 1.7. The CE's tilt toward hits among its top-10 suggestions (30% vs a 24% base rate) goes away, showing it came from the hubs. Self-rank-1 holds under both forms.

Indifference. A price-based engine converts the CS gap to dollars. Here CS is in log-odds, so the gap converts to an odds multiplier: e(CS_R − CS_C) is how many times better the candidate's odds would need to be to compensate a listener of the reference for the feature mismatch.

Alternative: NNLS on chart points

DV = log(total Hot 100 points, 101 − weekly position summed over the run), measured relative to the song's chart-year mean. Same features and the same reflection rule, then non-negative least squares with a free intercept. X = intercept + track-record terms + residual, so X is a proper over/under-performance reading. The gap multiplier is in chart points. Adj R² is only 0.046: audio features explain little of how long a song charts, so treat this as a comparison, not the headline.

Era

Era enters as one more continuous CE feature, e = (year − 1960) / 50, so it lies in [0, 1]. Min-max scaling is used because dividing years by their maximum would squeeze 1960–2010 into 0.975–1. Like every continuous term, the quadratic peaks when e_C = e_R: the loss relative to the reference is ½·b_era·(e_R − e_C)². A candidate gets the most utility from being in the reference's own year, and the penalty grows with the square of the distance.

b_era cannot be estimated from either regression, because both measure success within a chart year. The year effect absorbs era completely. So b_era is a listener-preference setting, like the constant-β engines, and is set by the penalty for a 10-year gap: b_era = ln(k) / (½·0.2²). Quadratic means 20 years costs k⁴ and 30 years k⁹. Feature gaps between a song and its nearest neighbours are small (about 0.01–0.02 in log-odds), so even k = 1.02 pulls the top 20 from a median of about 6 years away to about 2. The default is "Light", so sound still drives the ranking. VSM does not use era.

VSM — Vector Space Model

VSM(ref, cand) = (v_ref · v_cand) / (‖v_ref‖ · ‖v_cand‖)

Cosine similarity on the unreflected [0, 1] feature vector (10 audio features + 10 flags, plus the 2 track-record features when artist history is on), unweighted. It is the same under both weight sets.

Weights

FeatureTypeLogit, songLogit, + artistNNLS, songNNLS, + artist
Danceabilitycontinuous1.0900.9860.3150.227
Energycontinuous1.300′1.285′0.582′0.591′
Loudnesscontinuous1.3251.2920.5580.625
Speechiness (rap/spoken)continuous0.733′0.813′0.620′0.630′
Acousticnesscontinuous0.109′0.074′0.083′0.076′
Instrumentalnesscontinuous0.418′0.490′0.322′0.347′
Sounds livecontinuous0.363′0.359′0.016′0.012′
Happy-soundingcontinuous0.035′0.011′0.1120.116
Tempocontinuous0.2530.2050.2210.197
Song lengthcontinuous0.6620.6870.2050.132
4/4 time0/1 match0.2420.2310.2420.224
Major key0/1 match0.041′0.039′0.0250.026
Explicit lyrics0/1 match0.105′0.078′0.083′0.058′
Rock0/1 match0.1390.1230.0970.054
Pop0/1 match0.4050.3560.2030.152
Soul / R&B0/1 match0.070′0.0100.0460.072
Hip hop0/1 match0.3370.2860.2290.195
Country0/1 match0.471′0.338′0.001′0.123
Dance / disco0/1 match0.3340.2860.1700.128
Easy listening0/1 match0.2140.2920.2350.212
Artist's prior chart entriescontinuous—2.392′—1.260′
Artist's prior top-10 hitscontinuous—2.054—1.506
′ = applied to 1 − q (reflected). "Song" columns are refitted without track record, so their song weights are not conditioned on it.

Invariants

I1 β ≥ 0: logit ✓ · NNLS ✓
I2 q ∈ [0,1]: ✓
I3 self-rank-1: 0 violations in 300–500 random references for each of the four weight sets (Python); first 20 re-checked in the browser with and without the strongest era weight and under both flag forms
I4 X sum-check: logit ✓ · NNLS ✓
CS(q_R,q_R) − CS(q_R,q_C) = ½·b·(q_R − q_C)² ≥ 0

Data

Billboard Hot 100 weekly charts 1960–2010, Billboard year-end top 100 lists, Spotify audio features and artist genre tags. Songs without audio features or genre tags are excluded (17,858 of 19,265 remain). Logit: year fixed effects, standard errors clustered by artist. A song's year is the year it earned most of its chart points.