Final Project: Mental Health and Music Survey Analysis Notebook¶
Final Project by Anjelica Martinez
Research Question¶
As someone with a background in psychology, this dataset immediately caught my attention. I was curious whether the music people listen to and how they feel about its effect on them might be related to the mental health challenges they report experiencing. I wanted to explore this relationship using real survey data rather than relying on assumptions about music's role in mental wellbeing.
Is there an association between music listening habits and perceived effects, and self-reported anxiety, depression, insomnia, and OCD levels among survey respondents?
To explore this question, I broke it down into four sub-questions:
- How do the four mental health scales (Anxiety, Depression, Insomnia, and OCD) relate to each other?
- Does favorite genre relate to anxiety and depression levels?
- How do hours per day and BPM preference relate to anxiety and depression?
- Do people who feel music "Worsens" their mental state report higher Anxiety, Depression, Insomnia, and OCD than those who feel it "Improves" their mental state, or report "No effect"?
Import Libraries and Data¶
# Import libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.formula.api as smf
# Load the cleaned dataset (produced in the data wrangling notebook)
df = pd.read_csv('data/mental_health_music_survey.csv', index_col=0)
# Quick check that the data loaded correctly and looks as expected
df.head()
| Age | Primary streaming service | Hours per day | While working | Instrumentalist | Composer | Fav genre | Exploratory | Foreign languages | BPM | ... | Anxiety | Depression | Insomnia | OCD | Music effects | While working_bool | Instrumentalist_bool | Composer_bool | Exploratory_bool | Foreign languages_bool | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 18.0 | Spotify | 3.0 | Yes | Yes | Yes | Latin | Yes | Yes | 156.0 | ... | 3 | 0 | 1 | 0 | NaN | True | True | True | True | True |
| 1 | 63.0 | Pandora | 1.5 | Yes | No | No | Rock | Yes | No | 119.0 | ... | 7 | 2 | 2 | 1 | NaN | True | False | False | True | False |
| 2 | 18.0 | Spotify | 4.0 | No | No | No | Video game music | No | Yes | 132.0 | ... | 7 | 7 | 10 | 2 | No effect | False | False | False | False | True |
| 3 | 61.0 | YouTube Music | 2.5 | Yes | No | Yes | Jazz | Yes | Yes | 84.0 | ... | 9 | 7 | 3 | 3 | Improve | True | False | True | True | True |
| 4 | 18.0 | Spotify | 4.0 | Yes | No | No | R&B | Yes | No | 107.0 | ... | 7 | 2 | 5 | 9 | Improve | True | False | False | True | False |
5 rows × 36 columns
About the Data¶
This dataset contains 736 survey responses across 36 columns, covering self-reported music listening habits (favorite genre, listening frequency by genre, hours per day, preferred tempo, streaming service) and four self-reported mental health scales (Anxiety, Depression, Insomnia, and OCD), each rated from 0 to 10. The data comes from a publicly available Kaggle dataset (Music & Mental Health Survey Results) and was collected through a voluntary, informally shared online survey.
# Glimpse at the structure of the dataframe
df.info()
<class 'pandas.core.frame.DataFrame'> Index: 736 entries, 0 to 735 Data columns (total 36 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Age 735 non-null float64 1 Primary streaming service 735 non-null object 2 Hours per day 736 non-null float64 3 While working 733 non-null object 4 Instrumentalist 732 non-null object 5 Composer 735 non-null object 6 Fav genre 736 non-null object 7 Exploratory 736 non-null object 8 Foreign languages 732 non-null object 9 BPM 621 non-null float64 10 Frequency [Classical] 679 non-null object 11 Frequency [Country] 712 non-null object 12 Frequency [EDM] 684 non-null object 13 Frequency [Folk] 683 non-null object 14 Frequency [Gospel] 727 non-null object 15 Frequency [Hip hop] 647 non-null object 16 Frequency [Jazz] 700 non-null object 17 Frequency [K pop] 685 non-null object 18 Frequency [Latin] 706 non-null object 19 Frequency [Lofi] 661 non-null object 20 Frequency [Metal] 672 non-null object 21 Frequency [Pop] 566 non-null object 22 Frequency [R&B] 650 non-null object 23 Frequency [Rap] 629 non-null object 24 Frequency [Rock] 572 non-null object 25 Frequency [Video game music] 657 non-null object 26 Anxiety 736 non-null int64 27 Depression 736 non-null int64 28 Insomnia 736 non-null int64 29 OCD 736 non-null int64 30 Music effects 728 non-null object 31 While working_bool 733 non-null object 32 Instrumentalist_bool 732 non-null object 33 Composer_bool 735 non-null object 34 Exploratory_bool 736 non-null bool 35 Foreign languages_bool 732 non-null object dtypes: bool(1), float64(3), int64(4), object(28) memory usage: 207.7+ KB
Summary Statistics¶
Survey respondent ages ranged from 10 to 89 years old, with an average age of 25 and a standard deviation of 12 years. Daily listening time ranged from 0 to 24 hours, with an average of 4 hours and a standard deviation of 3 hours. Preferred listening tempo (BPM) ranged from 40 to 220 beats per minute, with an average of 124 BPM and a standard deviation of 32*.
All four mental health scales ranged from 0 (not severe) to 10 (severe). Respondents reported an average Anxiety score of 6 (SD = 3), an average Depression score of 5 (SD = 3), an average Insomnia score of 4 (SD = 3), and an average OCD score of 3 (SD = 3).
*One respondent did not report their age, but their response was not excluded from the dataset, since their remaining answers still provided useful data for analysis.
**The BPM field was left blank by 115 respondents.
# Reviewing the summary statistics for all continuous variable columns
df.describe()
| Age | Hours per day | BPM | Anxiety | Depression | Insomnia | OCD | |
|---|---|---|---|---|---|---|---|
| count | 735.000000 | 736.000000 | 621.000000 | 736.000000 | 736.000000 | 736.000000 | 736.000000 |
| mean | 25.206803 | 3.572758 | 123.758454 | 5.836957 | 4.794837 | 3.737772 | 2.635870 |
| std | 12.054970 | 3.028199 | 32.028258 | 2.792710 | 3.029564 | 3.088797 | 2.840047 |
| min | 10.000000 | 0.000000 | 40.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 18.000000 | 2.000000 | 100.000000 | 4.000000 | 2.000000 | 1.000000 | 0.000000 |
| 50% | 21.000000 | 3.000000 | 120.000000 | 6.000000 | 5.000000 | 3.000000 | 2.000000 |
| 75% | 28.000000 | 5.000000 | 144.000000 | 8.000000 | 7.000000 | 6.000000 | 5.000000 |
| max | 89.000000 | 24.000000 | 220.000000 | 10.000000 | 10.000000 | 10.000000 | 10.000000 |
Histogram Distributions for Age and BPM¶
Participant Ages
Participants ranged in age from 10 to 89, with an average age of 25. The distribution is right-skewed, meaning most respondents were in their 20s, with a smaller number of older participants stretching out the tail. This is probably just a reflection of who this survey actually reached, younger adults are more likely to use music streaming apps and to come across an informally shared online survey like this one, which probably explains why the sample doesn't really match the general population's age spread.
# Distribution histogram of participant ages
# Using 5-year bins since age is heavily right-skewed (mostly younger respondents,
# with a long thin tail of older ones)
sns.set_theme(style="white")
sns.histplot(data=df, x='Age', bins=range(10, 95, 5), color='#21908CFF')
plt.suptitle("What Does the Age Distribution of Respondents Look Like?", fontweight='bold')
plt.title("The majority of respondents were in their 20s, with a long tail of older respondents", pad=10, size=10)
plt.xlabel("Respondent Age (years)", size=10)
plt.ylabel("Number of Survey Respondents", size=10)
plt.show()
Popular Beats Per Minute Tempo (BPM)
Most people in this survey prefer listening to music somewhere between 100 and 150 beats per minute, with 120-125 BPM being the most common choice. A smaller group prefers much faster music, up to around 220 BPM. This roughly matches what you'd expect, since Pop and Rock (the two most popular favorite genres in this sample, see below) typically fall in that same tempo range. Before making this chart, eight clearly invalid BPM entries were removed (including one respondent who somehow entered almost a billion beats per minute), so this chart reflects the cleaned data.
# Distribution histogram of BPM (Beats per Minute)
# Uses the cleaned BPM column (outliers like the ~1 billion entry were already removed)
sns.set_theme(style="white")
sns.histplot(data=df, x='BPM', color='#21908CFF')
plt.suptitle("What Tempo Do People Prefer to Listen To? Most Cluster Around 100–150 BPM", fontweight='bold', size=13)
plt.title("A smaller number of respondents reported much faster preferred tempos", pad=10, size=10)
plt.xlabel("Music Beats Per Minute (BPM)", size=10, labelpad=10)
plt.ylabel("Number of Respondents", size=10)
plt.show()
Analysis¶
How Do Mental Health Scales Relate to Each Other and to Listening Habits?¶
The correlation heatmap shows that the four mental health scales are much more related to each other than to either listening variable. Anxiety and Depression had the strongest connection in the whole dataset (r = 0.52), followed by moderate relationships between Depression and Insomnia (r = 0.38), Anxiety and Insomnia (r = 0.29), and Anxiety and OCD (r = 0.35). Hours per day and BPM, on the other hand, barely related to any of the four scales; every single one of those correlations was under 0.14. So whatever's driving differences in anxiety, depression, insomnia, and OCD in this sample, it doesn't seem to be how much music someone listens to or how fast their preferred tempo is.
To check this more formally, I ran a linear regression predicting Anxiety from Hours per day and BPM (n = 621). Neither one came out significant (Hours per day: p = 0.199; BPM: p = 0.170), and together they explained less than 1% of the variation in Anxiety scores (R² = 0.006). The model overall didn't reach significance either (F-statistic p = 0.169). Basically, this just confirms what the heatmap already hinted at, neither how long someone listens nor their preferred tempo seems to matter much for anxiety.
I ran the same regression for Depression. This time it actually came out significant overall (F-statistic p = 0.0155). Hours per day turned out to be a real predictor (p = 0.009): each extra hour of daily listening was linked to about a 0.10-point bump in Depression score, holding BPM steady. BPM still wasn't significant (p = 0.220), same as before. But the actual size of this effect is small. Together, Hours per day and BPM only explained 1.3% of the variation in Depression scores (R² = 0.013). So there's a small, real connection between listening time and depression here, but it's nowhere close to being the main thing driving differences in people's depression scores.
Worth pointing out: this is correlational, not causal. I can't tell from this data whether listening more actually contributes to higher depression, or whether people who are more depressed just happen to listen to more music as a way of coping. Both are equally plausible, and this analysis can't tell them apart.
So overall, of the two listening variables I looked at, Hours per day shows a small but real link to Depression, while BPM doesn't seem to matter much for either Anxiety or Depression in this sample.
# Subset to the 6 numeric variables relevant to the research question:
# the four mental health scales plus the two listening habit variables
df_subset = df[['Anxiety', 'Depression', 'Insomnia', 'OCD', 'Hours per day', 'BPM']]
# Correlation heatmap to see how mental health scales relate to each other
# and to listening habits, answering sub-question 1
plt.figure(figsize=(8,6))
corr_matrix = df_subset.corr()
sns.heatmap(corr_matrix, annot=True, cmap="RdBu", vmin=-1, vmax=1)
plt.suptitle("How Do Mental Health Scales Relate to Each Other and to Listening Habits?",
fontweight='bold',
size=13,
y=.97)
plt.title("Anxiety and Depression are most strongly related; listening habits show little connection to any of them",
pad=11,
size=10,
x= 0.6)
plt.show()
Regression: Anxiety predicted by Hours per day and BPM
# Linear regression testing whether Hours per day and BPM predict Anxiety
# (sub-question 3: do listening habits relate to mental health scores?)
model = smf.ols(formula='Anxiety ~ Q("Hours per day") + BPM', data=df)
results = model.fit()
results.summary()
| Dep. Variable: | Anxiety | R-squared: | 0.006 |
|---|---|---|---|
| Model: | OLS | Adj. R-squared: | 0.003 |
| Method: | Least Squares | F-statistic: | 1.784 |
| Date: | Fri, 26 Jun 2026 | Prob (F-statistic): | 0.169 |
| Time: | 18:55:14 | Log-Likelihood: | -1510.2 |
| No. Observations: | 621 | AIC: | 3026. |
| Df Residuals: | 618 | BIC: | 3040. |
| Df Model: | 2 | ||
| Covariance Type: | nonrobust |
| coef | std err | t | P>|t| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| Intercept | 5.0939 | 0.461 | 11.047 | 0.000 | 4.188 | 5.999 |
| Q("Hours per day") | 0.0460 | 0.036 | 1.286 | 0.199 | -0.024 | 0.116 |
| BPM | 0.0048 | 0.003 | 1.373 | 0.170 | -0.002 | 0.012 |
| Omnibus: | 50.474 | Durbin-Watson: | 1.903 |
|---|---|---|---|
| Prob(Omnibus): | 0.000 | Jarque-Bera (JB): | 32.144 |
| Skew: | -0.429 | Prob(JB): | 1.05e-07 |
| Kurtosis: | 2.287 | Cond. No. | 533. |
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
Regression: Depression predicted by Hours per day and BPM
# Linear regression testing whether Hours per day and BPM predict Depression
# (sub-question 3, second outcome variable)
# Q() wraps "Hours per day" since the formula syntax can't handle column names with spaces
model = smf.ols(formula='Depression ~ Q("Hours per day") + BPM', data=df)
results = model.fit()
results.summary()
| Dep. Variable: | Depression | R-squared: | 0.013 |
|---|---|---|---|
| Model: | OLS | Adj. R-squared: | 0.010 |
| Method: | Least Squares | F-statistic: | 4.195 |
| Date: | Fri, 26 Jun 2026 | Prob (F-statistic): | 0.0155 |
| Time: | 18:55:14 | Log-Likelihood: | -1558.9 |
| No. Observations: | 621 | AIC: | 3124. |
| Df Residuals: | 618 | BIC: | 3137. |
| Df Model: | 2 | ||
| Covariance Type: | nonrobust |
| coef | std err | t | P>|t| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| Intercept | 3.9127 | 0.499 | 7.845 | 0.000 | 2.933 | 4.892 |
| Q("Hours per day") | 0.1012 | 0.039 | 2.614 | 0.009 | 0.025 | 0.177 |
| BPM | 0.0046 | 0.004 | 1.228 | 0.220 | -0.003 | 0.012 |
| Omnibus: | 164.610 | Durbin-Watson: | 1.973 |
|---|---|---|---|
| Prob(Omnibus): | 0.000 | Jarque-Bera (JB): | 29.689 |
| Skew: | -0.110 | Prob(JB): | 3.57e-07 |
| Kurtosis: | 1.952 | Cond. No. | 533. |
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
What Genres Do Respondents Prefer Most?¶
Rock came out as the most popular favorite genre by far, with 188 people picking it, followed by Pop (114), Metal (88), Classical (53), and Video Game Music (44). Together, these five genres cover most of the sample, while the other 11 genres in the dataset were each picked by much fewer people. Since some of those smaller genres had really small sample sizes (some with fewer than 10 respondents), I stuck to just these top 5 for the genre-based comparisons later on (the listening frequency chart and the genre-by-mental-health boxplots), since those numbers are big enough to actually mean something.
# Music genre color dictionary for consistent color mapping
# Ensures each genre gets the same color across multiple charts
# (seaborn's default palette assigns colors by position, which caused
# mismatched colors between the bar chart and boxplot)
genre_colors = {
'Rock': '#4b4279',
'Pop': '#3c6682',
'Metal': '#2f837f',
'Classical': '#45a778',
'Video game music': '#8fc456'
}
# Count how many respondents picked each genre as their favorite, keep top 5
top5_genre_counts = df['Fav genre'].value_counts().head(5).reset_index()
# Bar chart of the 5 most popular favorite genres
# Uses genre_colors dictionary so colors stay consistent with the boxplot below
plt.figure(figsize=(8,6))
sns.barplot(data=top5_genre_counts, x='Fav genre', y='count', palette=genre_colors, hue='Fav genre', legend=False)
plt.suptitle("What Genres Do Respondents Prefer Most? Rock Is the Clear Favorite", fontweight='bold', size=12, y=.96)
plt.title("Pop and Metal follow, while Classical and Video Game Music are far less common favorites", pad=10, size=10)
plt.xlabel("Favorite Genre", size=10, labelpad=10)
plt.ylabel("Number of Respondents", size=10)
plt.show()
Does Favorite Genre Relate to Anxiety Levels?¶
Looking at Anxiety scores across the top 5 genres, Rock fans reported the highest typical anxiety (median = 7), while Classical fans came in lowest (median = 6, though Classical also had the widest spread of responses by far). Pop, Metal, and Video Game Music all landed on the same median (6), even though their distributions looked a bit different in spread and shape.
These differences are real, but pretty small, every genre's median falls somewhere in a narrow 6-to-7 range, and the boxes for all five genres overlap a lot. So while favorite genre might have some connection to anxiety level in this sample, it's a weak one. You couldn't reliably guess someone's anxiety level just from knowing their favorite genre.
And like everything else here, this is correlational and self-reported, so I can't say which way it goes. It's just as possible that people with different anxiety levels are drawn to different music, as it is that the music itself is shaping their anxiety. This data just can't tell us which.
# Restrict to the top 5 favorite genres (established above) since smaller
# genres have too few respondents for a reliable comparison
top5_genre = ['Rock', 'Pop', 'Metal', 'Classical', 'Video game music']
filtered_df = df[df['Fav genre'].isin(top5_genre)]
# Boxplot of Anxiety by favorite genre (sub-question 2)
# Uses genre_colors so colors match the genre popularity bar chart above
plt.figure(figsize=(10,6))
sns.boxplot(filtered_df, x='Fav genre', y='Anxiety',
palette=genre_colors, hue='Fav genre',
legend=False, order=top5_genre)
plt.suptitle("Does Favorite Genre Relate to Anxiety Levels? Rock Fans Report the Highest Typical Anxiety",
fontweight='bold',
size=12,
y=.97)
plt.title("Classical fans show the lowest median anxiety, but with the widest range of responses",
pad=14,
size=11)
plt.xlabel("Favorite Genre",
size=10,
labelpad=10,
fontweight='bold')
plt.ylabel("Self-Reported Anxiety Level (0 = none, 10 = severe)",
size=10,
fontweight='bold')
plt.show()
Do People Who Feel Music "Worsens" Their Mental State Report Higher Distress?¶
People who said music "Worsens" their mental state reported noticeably higher scores across all four mental health measures than those who said it "Improves" their state or has "No effect." This showed up most clearly with Depression: the "Worsen" group averaged 7.2, compared to 4.9 for "Improve" and 4.4 for "No effect." The same pattern held for Anxiety (6.8 vs. 6.0 vs. 5.1), Insomnia (4.5 vs. 3.8 vs. 3.7), and OCD (3.1 vs. 2.7 vs. 2.4).
What's interesting is that "Improve" and "No effect" were pretty close to each other across the board, while "Worsen" stood apart from both. So the real divide here might not be "does music help vs. not help", it's more like "does music actively make things worse."
That said, this one needs a caveat: the "Worsen" group was tiny, only 17 people, compared to 542 for "Improve" and 169 for "No effect." With a group that small, a few people with unusually high scores could easily skew the average. The wide error bars on the chart are basically showing that uncertainty.
And like everything else in this analysis, I can't say which way the relationship actually goes. It's just as plausible that people who are already struggling more tend to feel like music is making things worse, as it is that music itself is contributing to that. Even so, out of everything I looked at in this project, this is the clearest and most consistent pattern I found.
# Reshape the four mental health scales into long format, keeping Music effects
# attached to each row, so they can all be plotted together grouped by Scale
mental_scales = ['Anxiety', 'Depression', 'Insomnia', 'OCD']
mental_long_df = df.melt(id_vars='Music effects',
value_vars=mental_scales,
var_name='Scale',
value_name='Score')
# Grouped bar chart of average score per mental health scale, split by Music effects
# (sub-question 4) — seaborn calculates the means and confidence intervals automatically
sns.barplot(data = mental_long_df, x='Scale', y='Score', hue='Music effects', palette='Reds')
plt.suptitle("""Do People Who Feel Music "Worsens" Their Mental State Report Higher Distress? Yes, Especially for Depression""",
fontweight='bold',
size=11)
plt.title("""The "Worsen" group's wide error bars reflect its small sample size (n = 17)""",
pad=10,
size=11)
plt.xlabel("Mental Health Scale", size=12, labelpad=10)
plt.ylabel("Average Self-Reported Score (0–10)", size=12)
plt.show()
Conclusion¶
I set out to see whether music listening habits, and how people feel about music's effect on them, are connected to self-reported anxiety, depression, insomnia, and OCD. A few clear patterns showed up.
First, the four mental health scales were much more related to each other than to anything about people's listening habits. Anxiety and Depression had the strongest connection (r = 0.52), and the other pairs were also moderately related. This makes sense since these four things probably overlap a lot, even though respondents rated them separately.
Second, the actual mechanics of listening (how long, how fast) barely mattered. BPM didn't predict Anxiety or Depression at all. Hours per day didn't predict Anxiety either, but it did show a small, real connection to Depression. Each extra hour of listening per day was linked to about a 0.10-point bump in Depression score. That said, it's a tiny effect; it only explains about 1% of why people's Depression scores differ.
Third, favorite genre had a modest link to Anxiety. Rock fans reported the highest typical anxiety, Classical fans the lowest, but honestly, all five genres overlapped a lot, so this isn't a huge difference.
The most interesting finding by far was about how people feel about music's effect on them. People who said music "Worsens" their mental state reported noticeably higher scores across all four mental health measures, especially Depression. That group was small (only 17 people), so I'd take it with a grain of salt, but the pattern held up consistently across every scale, which makes it the strongest result in this whole project.
Limitations¶
This is correlational, not causal. I can't say which direction any of these relationships actually go. It's just as likely that people who are already struggling turn to music more, or feel like it's worsening things, as it is that music itself is driving these outcomes. The sample also skewed pretty young (median age 21), people opted in themselves, and everything is self-reported, so I wouldn't generalize this too far beyond this specific group. A few genres and the "Worsen" group especially had small sample sizes, which adds more uncertainty there. And there wasn't really a meaningful time variable in this dataset, so a time-based chart wasn't possible.
Overall, it seems like how someone feels about music's role in their life might matter more than how or how much they actually listen. To really dig into whether music is shaping mental health, or the other way around, you'd need a bigger, more representative sample and ideally data that follows people over time.