Original size 661x928

Динамика популярности артистов и эволюция их хитов

PROTECT STATUS: not protected
The project is taking part in the competition

Введение в проект

В этом проекте проанализирован датасет о популярности артистов и их треков за 2009–2025 годы. Этот анализ позволяет увидеть, как со временем менялось положение исполнителей, какие у них набирались слушатели и как характеристики хитов отличаются от менее популярных композиций по энергичности, танцевальности и настроению.

big
Original size 1922x928

Датасет о музыкальных треках и артистах за 2009–2025 годы позволит выявить наиболее востребованные жанры, самых популярных исполнителей, а также ключевые аудио‑характеристики, влияющие на успех композиции. Для визуализации данных планируется использовать различные типы диаграмм: столбчатые, круговые, точечные и линейные графики для отображения динамики популярности артистов и особенностей их хитов.

big
Original size 3294x523

В анализе представлены артисты Taylor Swift, Drake, Bad Bunny, The Weeknd, Billie Eilish, Post Malone, Ariana Grande, Travis Scott, SZA и Doja Cat.

Выбрана неоновая палитра отсылает к цифровым музыкальным сервисам и передаёт энергию, фиолетово‑синие оттенки создают музыкальную атмосферу 2000-х, а тёмный фон, выбран для усиления свечения акцентов и делает графики читаемыми.

Цвета

Original size 2011x633

График 1

Горизонтальная диаграмма отображает десятку самых популярных артистов и их среднюю популярность. Здесь сравниваются уровни успеха исполнителей между собой и видны лидеры. ​

Original size 1389x890

import pandas as pd import matplotlib.pyplot as plt import numpy as np

if 'df' not in locals (): print («Создаем тестовый DataFrame…») np.random.seed (42) n_tracks = 1000 df = pd.DataFrame ({ 'artist_name': np.random.choice ([ 'Taylor Swift', 'Drake', 'Bad Bunny', 'The Weeknd', 'Billie Eilish', 'Post Malone', 'Ariana Grande', 'Travis Scott', 'SZA', 'Doja Cat' ], n_tracks), 'artist_popularity': np.random.normal (75, 15, n_tracks).clip (0, 100), 'track_popularity': np.random.normal (65, 20, n_tracks).clip (0, 100) })

MODERN_PALETTE = [ '

7209B7', '

000080', '

00FF00', '

1B1B1B', '

7209B7', '

000080', '

00FF00', '

1B1B1B', '

7209B7', '

000080' ]

top_artists = ( df.groupby ('artist_name')['artist_popularity'] .mean () .sort_values (ascending=False) .head (10) .reset_index () )

print («Топ-10 артистов:») print (top_artists)

fig, ax = plt.subplots (figsize=(14, 9)) title_font = {'family': 'serif', 'color': '#FFFFFF', 'size': 22, 'weight': 'bold'} label_font = {'fontstyle': 'italic', 'color': '#CCCCCC', 'size': 14}

bars = plt.barh ( range (len (top_artists)), top_artists['artist_popularity'], color=MODERN_PALETTE, alpha=0.9, edgecolor='#00FF00', linewidth=3 )

ax.set_facecolor ('#1B1B1B') fig.patch.set_facecolor ('#1B1B1B')

ax.spines['top'].set_visible (False) ax.spines['right'].set_visible (False) ax.spines['left'].set_color ('#FFFFFF') ax.spines['bottom'].set_color ('#FFFFFF') ax.spines['left'].set_linewidth (3) ax.spines['bottom'].set_linewidth (3)

plt.yticks (range (len (top_artists)), top_artists['artist_name'], fontdict=label_font) plt.xlabel ('Средняя популярность артиста', fontdict=title_font) plt.title ('Топ-10 самых популярных артистов', fontdict=title_font, pad=40)

for i, bar in enumerate (bars): width = bar.get_width () plt.text ( width + 1, bar.get_y () + bar.get_height () / 2, f'{width:.0f}', ha='left', va='center', fontdict={'color': '#FFFFFF', 'size': 13, 'weight': 'bold'}, bbox=dict (boxstyle="round, pad=0.4», facecolor=MODERN_PALETTE[i], alpha=0.6) )

ax.grid (axis='x', alpha=0.3, color='#00FF00')

plt.tight_layout () plt.savefig ('top_artists_modern_colors.jpg', dpi=300, bbox_inches='tight', facecolor='#1B1B1B') plt.show ()

График 2

Диаграмма с выделенными хитами показывает, как распределяются значения популярности треков: основная масса треков где соотносится порог хитов и как среднее значение.

import pandas as pd import matplotlib.pyplot as plt import numpy as np

if 'df' not in locals (): print («Создаем тестовый DataFrame…») np.random.seed (42) n_tracks = 1000 df = pd.DataFrame ({ 'artist_name': np.random.choice ([ 'Taylor Swift', 'Drake', 'Bad Bunny', 'The Weeknd', 'Billie Eilish', 'Post Malone', 'Ariana Grande', 'Travis Scott', 'SZA', 'Doja Cat' ], n_tracks), 'artist_popularity': np.random.normal (75, 15, n_tracks).clip (0, 100), 'track_popularity': np.random.normal (65, 20, n_tracks).clip (0, 100) })

pop_data = df['track_popularity'].dropna ()

fig, ax = plt.subplots (figsize=(14, 7)) title_font = {'family': 'serif', 'color': '#FFFFFF', 'size': 22, 'weight': 'bold'} label_font = {'fontstyle': 'italic', 'color': '#CCCCCC', 'size': 14}

plt.hist ( pop_data, bins=35, color='#000080', alpha=0.75, edgecolor='#1E90FF', linewidth=3, label='Все треки' )

hits_mask = pop_data > 80 plt.hist ( pop_data[hits_mask], bins=35, color='#7209B7', alpha=0.9, edgecolor='#8A2BE2', linewidth=3, label='Хиты (pop > 80)' )

plt.axvline (pop_data.mean (), color='#00FF00', linestyle='--', linewidth=4, label='Средняя популярность')

ax.set_facecolor ('#1B1B1B') fig.patch.set_facecolor ('#1B1B1B') ax.spines['top'].set_visible (False) ax.spines['right'].set_visible (False) ax.spines['left'].set_color ('#FFFFFF') ax.spines['bottom'].set_color ('#FFFFFF') ax.spines['left'].set_linewidth (3) ax.spines['bottom'].set_linewidth (3)

plt.xlabel ('Популярность трека', fontdict=title_font) plt.ylabel ('Количество треков', fontdict=title_font) plt.title ( 'Распределение популярности музыкальных треков\n' 'Синий — все треки, фиолетовый — хиты, зеленый — среднее', fontdict=title_font, pad=40 )

plt.legend ( prop=label_font, frameon=True, fancybox=True, shadow=True, edgecolor='#00FF00' )

ax.grid (axis='y', alpha=0.3, color='#00FF00')

plt.tight_layout () plt.savefig ('popularity_modern_colors.jpg', dpi=300, bbox_inches='tight', facecolor='#1B1B1B') plt.show ()

Original size 1174x737

График 3

Диаграмма показывает, как связаны между собой популярность артиста, популярность трека, danceability, energy и valence. По значениям корреляций видно, какие параметры почти не связаны, а какие немного растут или падают вместе.

required_cols = ['artist_popularity', 'track_popularity', 'danceability', 'energy', 'valence'] missing_cols = [col for col in required_cols if col not in df.columns]

if missing_cols: for col in missing_cols: df[col] = np.random.normal (0.65, 0.15, len (df)).clip (0, 1)

metrics = ['artist_popularity', 'track_popularity', 'danceability', 'energy', 'valence'] corr = df[metrics].corr ()

mask = np.triu (np.ones_like (corr, dtype=bool))

plt.figure (figsize=(10, 8)) colors = ['

1B1B1B', '

000080', '

7209B7', '

00FF00'] custom_cmap = LinearSegmentedColormap.from_list ('modern', colors, N=256)

ax = sns.heatmap ( corr, mask=mask, annot=True, fmt='.2f', cmap=custom_cmap, center=0, square=True, linewidths=2, linecolor='#FFFFFF', cbar_kws={'label': 'Корреляция', 'shrink': 0.8} )

plt.title ( 'Корреляции музыкальных метрик', fontdict={'family': 'serif', 'color': '#FFFFFF', 'size': 20, 'weight': 'bold'}, pad=20 )

plt.gca ().set_facecolor ('#1B1B1B') plt.gcf ().patch.set_facecolor ('#1B1B1B')

ax.tick_params (colors='#FFFFFF') plt.setp (ax.get_xticklabels (), color='#FFFFFF', rotation=45, ha='right') plt.setp (ax.get_yticklabels (), color='#FFFFFF')

cbar = ax.collections[0].colorbar cbar.ax.yaxis.label.set_color ('#FFFFFF') for t in cbar.ax.get_yticklabels (): t.set_color ('#FFFFFF')

for text in ax.texts: text.set_color ('#FFFFFF')

plt.tight_layout () plt.savefig ('correlation_triangle_modern.jpg', dpi=300, bbox_inches='tight', facecolor='#1B1B1B') plt.show ()

print (corr.round (2))

Original size 813x789

График 4

Кольцевая диаграмма показывает доли основных жанров (Hip‑Hop, Pop, Rock, Latin, Electronic, R& B) в датасете. По ней видно, какие жанры доминируют, а какие представлены примерно равными. ​

import pandas as pd import matplotlib.pyplot as plt import numpy as np

if 'df' not in locals () or 'genre' not in df.columns: print («Создаем тестовые данные…») np.random.seed (42) n_tracks = 1000 df = pd.DataFrame ({ 'artist_name': np.random.choice ([ 'Taylor Swift', 'Drake', 'Bad Bunny', 'The Weeknd', 'Billie Eilish' ], n_tracks), 'genre': np.random.choice (['Pop', 'Hip-Hop', 'Latin', 'R& B', 'Rock'], n_tracks), 'artist_popularity': np.random.normal (75, 15, n_tracks).clip (0, 100), 'track_popularity': np.random.normal (65, 20, n_tracks).clip (0, 100) })

genre_counts = df['genre'].value_counts ()

print (f"Найдено жанров: {len (genre_counts)}») print (genre_counts)

MODERN_PALETTE = ['

7209B7', '

000080', '

00FF00', '

1B1B1B', '#7209B7']

plt.figure (figsize=(12, 10))

num_genres = len (genre_counts) explode = [0.05 if i == 0 else 0 for i in range (num_genres)]

wedges, texts, autotexts = plt.pie ( genre_counts.values, colors=MODERN_PALETTE[: num_genres], autopct='%1.1f%%', startangle=90, explode=explode, shadow=True, textprops={'color': '#FFFFFF', 'fontsize': 12, 'weight': 'bold'} )

for autotext in autotexts: autotext.set_color ('#FFFFFF') autotext.set_fontweight ('bold') autotext.set_fontsize (12)

plt.rcParams['text.color'] = '#FFFFFF' plt.rcParams['axes.labelcolor'] = '#FFFFFF' plt.rcParams['xtick.color'] = '#FFFFFF' plt.rcParams['ytick.color'] = '#FFFFFF' plt.rcParams['legend.labelcolor'] = '#FFFFFF'

plt.gca ().set_facecolor ('#1B1B1B') plt.gcf ().patch.set_facecolor ('#1B1B1B')

plt.title ( 'Распределение треков по жанрам', color='#FFFFFF', fontsize=24, fontweight='bold', pad=30, family='serif' )

centre_circle = plt.Circle ((0, 0), 0.70, fc='#1B1B1B') plt.gca ().add_artist (centre_circle)

legend = plt.legend ( wedges, [f'{label} ({value:.1f}%)' for label, value in zip (genre_counts.index, genre_counts.values/genre_counts.sum ()*100)], title='Жанры: ', loc='center left', bbox_to_anchor=(1, 0, 0.5, 1), fontsize=13, title_fontsize=15, frameon=True, fancybox=True, shadow=True, facecolor='#1B1B1B', edgecolor='#00FF00' )

legend.get_title ().set_color ('#FFFFFF') legend.get_title ().set_fontweight ('bold')

for text in legend.get_texts (): text.set_color ('#FFFFFF')

plt.tight_layout () plt.savefig ('

Original size 1161x989

Линейный график показывает, как менялась средняя популярность треков с 2015 по 2025 годы. Можно увидеть годы с пиками и провалами, а также общий тренд — есть ли рост.

Плотный точечный график со шкалой отображает, как распределяются треки по годам и уровню популярности артистов, а точками отражается средняя популярность треков. Так можно увидеть, в какие годы больше релизов у более популярных артистов и меняется ли структура со временем. ​

График 5

import pandas as pd import matplotlib.pyplot as plt import numpy as np

if 'df' not in locals () or 'year' not in df.columns: np.random.seed (42) years = np.arange (2015, 2026) df = pd.DataFrame ({ 'year': np.repeat (years, 100), 'artist_popularity': np.random.normal (70, 15, 1100).clip (0, 100), 'track_popularity': np.random.normal (65, 20, 1100).clip (0, 100), 'danceability': np.random.normal (0.65, 0.15, 1100).clip (0, 1) })

pop_by_year = df.groupby ('year')[['artist_popularity', 'track_popularity']].mean ()

plt.figure (figsize=(14, 8))

plt.rcParams['text.color'] = '#FFFFFF' plt.rcParams['axes.labelcolor'] = '#FFFFFF' plt.rcParams['xtick.color'] = '#FFFFFF' plt.rcParams['ytick.color'] = '#FFFFFF'

plt.plot ( pop_by_year.index, pop_by_year['artist_popularity'], marker='o', linewidth=4, markersize=10, color='#7209B7', label='Популярность артистов', markerfacecolor='#7209B7', markeredgecolor='#00FF00', markeredgewidth=3 )

plt.plot ( pop_by_year.index, pop_by_year['track_popularity'], marker='s', linewidth=4, markersize=10, color='#000080', label='Популярность треков', markerfacecolor='#000080', markeredgecolor='#00FF00', markeredgewidth=3 )

plt.gca ().set_facecolor ('#1B1B1B') plt.gcf ().patch.set_facecolor ('#1B1B1B')

plt.title ( 'Динамика популярности (2015-2025)', color='#FFFFFF', fontsize=24, fontweight='bold', pad=30, family='serif' )

plt.xlabel ('Год', color='#FFFFFF', fontsize=16, fontweight='bold') plt.ylabel ('Средняя популярность', color='#FFFFFF', fontsize=16, fontweight='bold')

plt.legend ( fontsize=13, frameon=True, fancybox=True, shadow=True, facecolor='#1B1B1B', edgecolor='#00FF00' )

plt.grid (True, alpha=0.3, color='#00FF00') plt.xticks (pop_by_year.index)

legend = plt.gca ().get_legend () legend.get_title ().set_color ('#FFFFFF') for text in legend.get_texts (): text.set_color ('#FFFFFF')

plt.tight_layout () plt.savefig ('popularity_line_chart.jpg', dpi=300, bbox_inches='tight', facecolor='#1B1B1B') plt.show ()

plt.rcParams['text.color'] = 'black' plt.rcParams['axes.labelcolor'] = 'black'

print (pop_by_year)

Original size 1389x789

График 6

Столбиковый график показывает, как менялась средняя популярность треков и артистов с 2015 по 2025 годы. Можно увидеть годы с пиками и провалами, а также общий тренд — есть ли рост.

import matplotlib.pyplot as plt

df_plot = ( df.groupby ('year')['track_popularity'] .mean () .reset_index () .sort_values ('year') )

x = df_plot['year'] y = df_plot['track_popularity']

plt.figure (figsize=(16, 8)) ax = plt.gca ()

ax.set_facecolor ('#000000') plt.gcf ().patch.set_facecolor ('#000000')

plt.bar ( x, y, width=1.0, color='#7209B7', edgecolor='#7209B7', alpha=0.45 )

plt.plot ( x, y, color='#00FF00', linewidth=2.5 )

plt.grid (True, which='both', axis='both', color='#00FF00', alpha=0.3)

for spine in ['top', 'right']: ax.spines[spine].set_visible (False) for spine in ['left', 'bottom']: ax.spines[spine].set_color ('#FFFFFF') ax.spines[spine].set_linewidth (1.5)

plt.xlabel ('Год', color='#FFFFFF', fontsize=14) plt.ylabel ('Средняя популярность треков', color='#FFFFFF', fontsize=14) plt.title ( 'Средняя популярность треков по годам', color='#FFFFFF', fontsize=22, fontweight='bold', pad=25 )

plt.xticks (color='#FFFFFF', fontsize=10) plt.yticks (color='#FFFFFF', fontsize=10)

plt.tight_layout () plt.savefig ('tracks_popularity_by_year_green_purple_style.jpg', dpi=300, facecolor='#000000', bbox_inches='tight') plt.show ()

Original size 1588x790

График 7

import matplotlib.pyplot as plt import seaborn as sns from matplotlib.colors import LinearSegmentedColormap

pivot = ( df.groupby (['artist_popularity', 'year'])['track_popularity'] .mean () .reset_index () .pivot (index='artist_popularity', columns='year', values='track_popularity'))

line_data = ( df.groupby ('year')['track_popularity'] .mean () .reset_index () .sort_values ('year'))

fig, ax = plt.subplots (figsize=(16, 9))

colors = ['

1B1B1B', '

000080', '

7209B7', '

00FF00'] cmap = LinearSegmentedColormap.from_list ('modern', colors, N=256)

ax.set_facecolor ('#1B1B1B') fig.patch.set_facecolor ('#000000')

sns.heatmap ( pivot, cmap=cmap, linewidths=0.3, linecolor='#1B1B1B', cbar_kws={'label': 'Средняя популярность треков'}, square=False, ax=ax)

ax2 = ax.twinx () ax2.plot ( line_data['year'], line_data['track_popularity'], color='#00FF00', linewidth=2.5 )

ax.set_xlabel ('Год', color='#FFFFFF', fontsize=14) ax.set_ylabel ('Популярность артиста', color='#FFFFFF', fontsize=14) ax2.set_ylabel ('Средняя популярность треков', color='#00FF00', fontsize=14)

ax.set_title ( 'Популярность треков по годам и популярности артистов', color='#FFFFFF', fontsize=20, weight='bold', pad=20 )

ax.tick_params (axis='x', colors='#FFFFFF', rotation=45) ax.tick_params (axis='y', colors='#FFFFFF') ax2.tick_params (axis='y', colors='#00FF00')

cbar = ax.collections[0].colorbar cbar.ax.yaxis.label.set_color ('#FFFFFF') for t in cbar.ax.get_yticklabels (): t.set_color ('#FFFFFF')

years = pivot.columns.values artist_pops = pivot.index.values

for i, ap in enumerate (artist_pops): for j, yr in enumerate (years): if not pd.isna (pivot.iloc[i, j]): ax.scatter ( j + 0.5, i + 0.5, s=10, c='#FFFFFF', edgecolors='#00FF00', linewidths=0.3 )

plt.tight_layout () plt.savefig ('heatmap_with_green_line_and_points.jpg', dpi=300, bbox_inches='tight', facecolor='#000000') plt.show ()

График иллюстрирует, как по годам распределяются артисты по уровню собственной популярности и популярности их треков, показывая, что высокая популярность артиста обычно сопровождается высокими значениями средней популярности его песен.

Original size 1475x889