Machine Learning: Как да анализираме музиката, която слушаме?

Идеята за тази статия дойде благодарение на моето любопитство към музиката, която слушам в Spotify. Исках да разбера колко често пускам определени песни, кои артисти и жанрове като цяло слушам най-много и дали наистина са тези, които аз си мисля, че са.

С практическа задача ще ви покажа как можете да анализирате музиката, която слушате в Spotify, с помощта на езика за програмиране Python и неговите библиотеки за анализ на данни.

Извадката, която ще използваме, съдържа 1846 произволно избрани песни, които съм слушала в Spotify за периода 28 юни 2015 – 26 януари 2021 година.

Ще се стремим да намерим отговор на следните въпроси:

  • Предимно каква музика е слушана и кога?
  • Има ли връзка между броя слушания и популярността на песните?
  • Кои са най-слушаните жанрове, албуми, артисти и песни?
  • Има ли зависимост между отделните числови характеристики?

Преглед на данните

Данните, с които ще работим, са предварително изчистени. Съдържат 15 числови променливи и 10 категорийни.

Можете да изтеглите файла с първоначалната обработка от тук.

5 случайно избрани реда от данните изглеждат по следния начин:

artisttitlealbumgenreyearaddedlast_listenedlistensbpmnrgydncedBlivevalduracousspchpopadd_dayadd_monthadd_yearll_dayll_monthll_yearll_time
793Ben PlattTemporary LoveSing to Me Insteadhollywood20192019-12-022019-11-28 21:07:0030895163-812142195135521220192811201921:07:00
493Loving CaliberWe're In This Together NowWhen We Were Youngerscandipop20172020-04-162020-04-19 13:12:0041442249-151216233783401642020194202013:12:00
554Isak DanielsonHold My HandYoursindie cafe pop20182020-03-292020-03-29 16:15:00101223266-101323201404512932020293202016:15:00
1506Hayley KiyokoCliffs EdgeThis Side of Paradise - EPdance pop20152016-08-222019-07-22 14:21:0022806069-7106521305512282016227201914:21:00
1343TEEN TOPRockingTEEN TOP CLASS ADDITIONk-pop20132016-08-252018-06-27 07:36:00341289870-31179193210302582016276201807:36:00

Предимно каква музика е слушана и кога?

За създаване на визуализациите ще използваме библиотеката Plotly на Python.

1/ Преглед на разпределения на числови характеристики

Ако видим как са разпределени данните, ще можем да добием представа за това какви песни предимно са слушани. За всяка от числовите характеристики без тези, свързани с дати като година, месец, дни и т.н., ще създадем хистограма.

# Запазване на числовите характеристики в нова променлива
numerical = df.select_dtypes('int64')

# Взимане само на стойностите от колоните
numerical = numerical.columns.values

# Създаване на една обща фигура, на която да са всичките 11 хистограми
fig = make_subplots(rows=11, cols=1, x_title='Values', y_title='Count', subplot_titles=numerical)

# Изграждане на хистограмите
for i, col in zip(range(1,12), numerical):
    fig.add_trace(
        go.Histogram(x=df[col],
                     name=col),
        row=i, col=1
    )

# Настройки на хистограмите   
fig.update_layout(bargap = 0.01,
                  width = 800,
                  height =1600,
                  title_text='Histograms of numerical variables')

# Показване на визуализацията
fig.show()

Първите 3 променливи са брой слушания (listens), удари в минута (bpm) и енергия (nrgy), показваща колко е забързана и шумна дадена песен. От хистограмите става ясно, че в повечето случаи една песен е слушана предимно до 4 пъти. Има и такива моменти, в които една и съща песен е пускана над 300+ пъти. Повечето песни са с около 100-104 удара в минута и са по-енергични.

Най-вече се слушат такива, на които може да се танцува, с от -6 до -4 dB сила на звука. Също така много от тях не са от концерти на живо, а са студио версии на песните.

Валентността (val) е доста равномерна. Това е метрика, която е със стойности от 0 до 100 и показва до колко е позитивна определена песен. Когато стойността е по-близо до 0 е тъжна, а ако е весела, е със стойност близо до 100. Има 13 песни, които са много тъжни и 21, които са много весели. Повечето стойности са в границите на 30-40 валентност и 60-67 – по около 120-150 песни. Предимно са с продължителност (dur) около 2 минути и доста от тях не са акустични.

В много от песните няма реч и предимно се слушат такива с популярност около 40 по скалата на Spotify, която е между 0 и 100. Най-популярните песни са със стойности на тази променлива близо до 100, а по-малко популярните са близо до 0.

2/ Средна популярност на песните

Ще изградим 2 линейни диаграми, показващи средната популярност на песните през отделните години. На едната визуализация са използвани годините, когато последно са слушани песните, а на другата годините, в които те са добавени в любими.

# Агрегиране на данните
df_popularity_year = df.groupby(['ll_year'], as_index=False)['pop'].mean()
df_popularity_added = df.groupby(['add_year'], as_index=False)['pop'].mean()

# Създаване на обща фигура за 2-те линейни диаграми
fig = make_subplots(rows=2, cols=1)

# Изграждане на визуализациите
fig.add_trace(go.Scatter(x=df_popularity_year['ll_year'],
                         y=df_popularity_year['pop'],
                         name='Last Listened',
                         hovertemplate=
                                    '<br>Last Listened: %{x} '+
                                    '<br>Popularity: %{y}'),
              row=1, col=1
             )
fig.add_trace(go.Scatter(x=df_popularity_added['add_year'],
                         y=df_popularity_added['pop'],
                         name='Year Added',
                         hovertemplate= '<br>Year Added: %{x} '+
                                        '<br>Popularity: %{y}'),
             row=2, col=1)

# Настройки на визуализациите
fig.update_layout(height=600, width=800, title_text='Line charts comparison of popularity by year added and year last listened')

# Добавяне на заглавия
fig['layout']['xaxis']['title']='Year Last Listened'
fig['layout']['xaxis2']['title']='Year Added'
fig['layout']['yaxis']['title']='Popularity'
fig['layout']['yaxis2']['title']='Popularity'

# Показване на визуализацията
fig.show()

И на двете визуализации средната популярност е най-висока през 2018-та година. Според последно слушане на песните, тя е 46, а според година на добавяне на песните в любими е 53.

3/ Брой слушания през различните години

Следващите линейни диаграми, които ще изградим, ще представим колко пъти са слушани песните по година на последно слушане, година на добавяне и когато е издадена песента.

# Агрегиране на данните
df_yl = df.groupby(['year'], as_index=False)[['listens']].sum()
df_ayl = df.groupby(['add_year'], as_index=False)[['listens']].sum()
df_lly = df.groupby(['ll_year'], as_index=False)[['listens']].sum()

# Създаване на обща фигура за 3-те визуализации
fig = make_subplots(rows=3, cols=1, vertical_spacing = 0.2)

# Изграждане на линейните диаграми
fig.add_trace(go.Scatter(x=df_yl['year'],
                         y=df_yl['listens'],
                         name='Year Released',
                         hovertemplate=
                                    '<br>Year Released: %{x} '+
                                    '<br>Number of listens: %{y}',
                         mode='lines+markers'),
              row=1, col=1
             )
fig.add_trace(go.Scatter(x=df_ayl['add_year'],
                         y=df_ayl['listens'],
                         name='Year Added',
                         hovertemplate= '<br>Year Added: %{x} '+
                                        '<br>Listens: %{y}'),
             row=2, col=1)
fig.add_trace(go.Scatter(x=df_lly['ll_year'],
                         y=df_lly['listens'],
                         name='Last Listened',
                         hovertemplate= '<br>Last Listened: %{x} '+
                                        '<br>Listens: %{y}'),
             row=3, col=1)

# Настройки на визуализациите
fig.update_layout(height=600, width=800, title_text='Line charts comparison of number of listens by years')

# Добавяне на заглавия
fig['layout']['xaxis']['title']='Year Released'
fig['layout']['xaxis2']['title']='Year Added'
fig['layout']['xaxis3']['title']='Year Last Listened'
fig['layout']['yaxis']['title']='Listens'
fig['layout']['yaxis2']['title']='Listens'
fig['layout']['yaxis3']['title']='Listens'

# Показване на визуализацията
fig.show()

Най-много са слушани и добавяни песни в любими през 2020-та година, като те са най-вече от 2017-та и 2018-та година.

С групирана стълбовидна диаграма можем да видим по месеци за 2018-та, 2019-та и 2020-та година как са разпределени слушанията.

# Агрегиране на данните
df_ym = df[df['ll_year'].isin(['2018','2019','2020'])].groupby(['ll_year','ll_month'], as_index=False)[['listens']].sum()

# Промяна на типа на стойностите в колоната с година на последно слушане
df_ym['ll_year'] = df_ym['ll_year'].map(str)

# Изграждане на стълбовидна диаграма
fig = px.bar(df_ym,
             x='ll_month',
             y='listens',
             color='ll_year',
             title='Listens by year and month',
             color_discrete_sequence=px.colors.qualitative.Dark2
            )

# Настройки на визуализацията
fig.update_layout(barmode='group', xaxis={'tickmode': 'array',
                                          'tickvals': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
                                          'ticktext':['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']})

# Показване на визуализацията
fig.show()

На групираната стълбовидна диаграма по година на последно слушане на песните отново се вижда, че през 2020-та година броят на слушанията е най-голям.

Можем да ги филтрираме и по месеци и дни само за 2020-та година.

# Агрегиране на данните
df_md = df[df['ll_year'] == '2020'].groupby(['ll_month','ll_day'], as_index=False)['listens'].sum()

# Изграждане на стълбовидна диаграма
fig = go.Figure()
fig.add_trace(go.Bar(x=df_md[df_md['ll_month'] == 1].ll_day,
                     y=df_md[df_md['ll_month'] == 1].listens,
                     name='',
                     hovertemplate= '<br>Day: %{x} '+
                                     '<br>Listens: %{y}'))

# Създаване на празен списък за бутоните
buttons = []

# Създаване на променливи за месеците
month_nums = df_md['ll_month'].unique()
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

# Създаване на падащия списък с месеците
for i in month_nums:
    buttons.append(dict(method='restyle',
                        label= months[i-1],
                        visible=True,
                        args=[{'x':[df_md[df_md['ll_month'] == i].ll_day],
                               'y':[df_md[df_md['ll_month'] == i].listens],
                               'type':'bar'}, [0]]))

# Настройки на визуализацията
fig.update_layout(showlegend=False,
                  updatemenus=[ dict(buttons=buttons,
                                direction='down',
                                showactive=True)],
                  annotations=[ dict(text='<b> Month </b>',
                                     x=-0.13, xref='paper',
                                     y=1.06, yref='paper',
                                     align='left', showarrow=False)],
                  xaxis={'dtick':1},
                  height=600,
                  width=800,
                  title_text='Number of Listens for each day filtered by Month in 2020')

# Добавяне на заглавия
fig['layout']['xaxis']['title']='Day'
fig['layout']['yaxis']['title']='Number of Listens'

# Показване на визуализацията
fig.show()

Предимно към края на всеки месец има най-много слушания.

Има ли връзка между броя слушания и популярността на песните?

Необходимо е да филтрираме данните само по песни, които са с популярност над 80, след което можем да създадем една диаграма на разсейването, за да видим дали има връзка между броя слушания и популярността на песните.

# Филтриране на данните по песни с популярност (pop) > 80
popular_songs = df[df['pop'] > 80]

# Създаване на диаграма на разсейването
fig = go.Figure(go.Scatter(x=popular_songs['listens'],
                           y=popular_songs['pop'],
                           mode='markers',
                           name='',
                           text=popular_songs['artist'] + ' - ' + popular_songs['title'], 
                           hovertemplate=
                                    '<br>Listens: %{x}'+
                                    '<br>Popularity: %{y}'+
                                    '<br>Song: %{text}'))

# Добавяне на заглавия
fig['layout']['xaxis']['title'] = 'Listens'
fig['layout']['yaxis']['title'] = 'Popularity'

# Показване на визуализацията
fig.show()

Можем да кажем, че слушането на песните може би не зависи толкова от популярността им, тъй като броя на слушания за тях не е толкова голям. Има само една песен, която е популярна и е слушана 438 пъти. Останалите са най-вече с между 0 и 50 слушания.

Кои са най-слушаните песни, албуми, артисти и жанрове?

За да отговорим на този въпрос, ще създадем няколко стълбовидни диаграми.

Първо ще визуализираме 10-те най-слушани песни и ще ги подредим в низходящ ред.

# Намиране на 10-те най-слушани песни
top_10_listened = df.nlargest(10, 'listens')

# Създаване на стълбовидна диаграма
fig = go.Figure([go.Bar(x=top_10_listened['listens'],
                        y=top_10_listened['title'],
                        orientation='h',
                        text=top_10_listened['artist'],
                        name='',
                        hovertemplate=
                                    '<br><b>Song:</b> %{text} - %{y}' +
                                    '<br><b>Listens:</b> %{x}')])

# Настройки на визуализацията
fig.update_layout(yaxis={'categoryorder':'total ascending'}, title='Top 10 Songs by Number of Listens' )

# Добавяне на заглавия
fig['layout']['xaxis']['title'] = 'Title'
fig['layout']['yaxis']['title'] = 'Number of Listens'

# Показване на визуализацията
fig.show()

На първо място в данните е pop песента Lewis Capaldi – Someone You Loved с 545 слушания, а на 10-то е k-pop песента Blackpink – Forever Young с 311 слушания. Тук прави впечатление и песента от диаграмата на разсейването с популярните песни, която създадохме по-рано – Billie Eilish – ocean eyes. Тя е четвъртата най-слушана песен.

На следващата стълбовидна диаграма ще видим артистите, чиито песни са пускани най-често.

# Агрегиране на данните
df_artists = df.groupby('artist', as_index=False)['listens'].sum()

# Намиране на 10-те най-слушани артисти
top_artists = df_artists.nlargest(10, 'listens')

# Създаване на стълбовидна диаграма
fig = px.bar(top_artists, x='artist', y='listens', title='Top 10 Artists by Number of Listens')

# Показване на визуализацията
fig.show()

С най-много слушания е групата Chase Atlantic – 2655. Заедно с тях, песни на още 4 артисти – Sabrina Carpenter, Jessica, Dermot Kennedy и Blackpink са в 10-те най-слушани.

На следващата стълбовидна диаграма ще видим албумите, които са слушани най-много.

# Агрегиране на данните
df_albs = df.groupby(['artist','album'], as_index=False)['listens'].sum()

# Намиране на 10-те най-слушани албума
top_10_albums = df_albs.nlargest(10, 'listens')

# Създаване на стълбовидна диаграма
fig = go.Figure([go.Bar(x=top_10_albums['listens'],
                        y=top_10_albums['album'],
                        orientation='h',
                        text=top_10_albums['artist'],
                        name='',
                        hovertemplate=
                                    '<br><b>Artist:</b> %{text}' +
                                    '<br><b>Listens:</b> %{x}')])

# Настройки на визуализацията
fig.update_layout(yaxis={'categoryorder':'total ascending'}, title='Top 10 Albums by Number of Listens' )

# Добавяне на заглавия
fig['layout']['xaxis']['title'] = 'Album'
fig['layout']['yaxis']['title'] = 'Number of Listens'

# Показване на визуализацията
fig.show()

На първо място е албумът I met you when I was 18. (the playlist) на Lauv. Артистът е на 3-то място в данните по брой слушания. Почти всички албуми, които са представени на тази визуализация, са на артисти, които са в 10-те най-слушани.

Нека сега да видим кои са 5-те най-слушани жанрове.

# Агрегиране на данните
df_genres = df.groupby('genre', as_index=False)['listens'].sum()

# Намиране на 5-те най-слушани жанрове
top_genres = df_genres.nlargest(5, 'listens')

# Филтриране на данните по 5-те най-слушани жанрове и добавяне на индекс
grouped_ga = df[df['genre'].isin(top_genres['genre'])].set_index(['genre','artist','title'])

# Групиране на данните
grouped_ga = grouped_ga.sort_values('listens', ascending=False).groupby(level=0)[['listens']].head(1).sort_index()

# Нулиране на индекса
grouped_ga.reset_index(inplace=True)

# Създаване на стълбовидна диаграма
fig = go.Figure([go.Bar(x=grouped_ga['listens'],
                        y=grouped_ga['genre'],
                        text= grouped_ga['artist'] + ' - ' + grouped_ga['title'],
                        orientation='h',
                        name='',
                        hovertemplate=
                                    '<br>Listens: %{x} '+
                                    '<br>Genre: %{y}' + 
                                    '<br>Top Song: %{text}')])

# Настройки на визуализацията
fig.update_layout(yaxis={'categoryorder':'total ascending'}, title='Top 5 Genres by Number of Listens' )

# Добавяне на заглавия
fig['layout']['xaxis']['title'] = 'Genre'
fig['layout']['yaxis']['title'] = 'Number of Listens'

# Показване на фигурата
fig.show()

С най-много слушания е жанра k-pop, а топ песен от него е Jessica – Wonderland. Тя е на 2-ро място в диаграмата с най-слушани песни.

Има ли зависимост между отделните числови характеристики?

С корелационна матрица можем да видим дали има връзка между числовите променливи в извадката.

# Намиране на корелацията
corr = df.corr()

# Изграждане на визуализацията
fig = go.Figure()
fig.add_trace(go.Heatmap(z=corr.values,
                  x=corr.index.values,
                  y=corr.columns.values,
                  colorscale='Reds',
                  name='',
                  hovertemplate= '<br>x: %{x}'+
                            '<br>y: %{y}'
                            '<br>Correlation: %{z:.2f}'))

# Добавяне на заглавие
fig['layout']['title'] = 'Correlation matrix'

# Показване на визуализацията
fig.show()

От визуализацията става ясно, че сила на звука (dB) и енергия (nrgy) имат положителна корелация от 0.8, а акустичност (acous) и енергия (nrgy) имат отрицателна корелация от -0.7

Връзката между тези променливи се вижда и на диаграмите по-надолу.

# Изграждане на визуализацията
fig = px.scatter(df,
                 x='dB',
                 y='nrgy',
                 color='ll_year',
                 size='listens',
                 facet_col='ll_year',
                 facet_col_wrap=4,
                 height=600,
                 width=900,
                 hover_data=['ll_year','artist', 'title', 'dB', 'nrgy','listens'])

# Показване на визуализацията
fig.show()
# Изграждане на визуализацията
fig = px.scatter(df,
                 x='acous',
                 y='nrgy',
                 color='ll_year',
                 size='listens',
                 facet_col='ll_year',
                 facet_col_wrap=4,
                 height=600,
                 width=900,
                 hover_data=['ll_year','artist', 'title', 'acous', 'nrgy','listens'])

# Показване на визуализацията
fig.show()

Размера на балончетата се определя от броя слушания на песните. Тези, които са пускани най-много пъти, веднага се отличават. Видяхме ги и в 10-те най-слушани песни.

Извод

Благодарение на направения анализ, можем да кажем, че са слушани най-много поп песни, които в повечето случаи са бързи, весели и може да се танцува на тях. Има определени артисти и песни, които се отличават в почти всички визуализации, защото са слушани много голям брой пъти. За някои от тях определено останах изненадана.

Следваща стъпка, която може да се предприеме от тук нататък, е да се изгради модел за машинно обучение. Може да се направи например клъстерен анализ, които да групира сходните песни и така да се създават плейлисти или можем да генерираме асоциативни правила, за да видим кои песни след кои най-често се слушат. Това би било полезно ако искаме да правим предложения за песни.

Цялостния пример можете да изтеглите от тук.

Ако искате да научите за проучвателния анализ на данни, можете да прочетете в следната статия: Machine Learning: Какво включва проучвателният анализ на данни?

Искате да научите повече за Python?

Включете се в курса по програмиране с Python.

Научете повече

Автор: Десислава Христова