2022年5月29日日曜日

Spotify再生回数トップ50の曲を分類して可視化する

Spotify APIで再生回数トップ50の曲データを取得するでは、Spotify APIで再生回数トツプ50の曲一覧を取得した。Spotify APIでは、曲名やアーティスト名などの情報のほかに、曲の特徴を取得できる。曲の特徴とは、ダンスに適した曲か、エネルギッシュさなどといったもので、これらの情報を利用して曲の分類と可視化をしてみる。


環境

WSL2(Ubuntu20.04)とJupyter。

$ lsb_release -dr
Description:    Ubuntu 20.04.4 LTS
Release:        20.04
$ python3 -V
Python 3.8.10
$ jupyter --version
jupyter core     : 4.7.1
jupyter-notebook : 6.2.0
qtconsole        : 5.0.2
ipython          : 7.20.0
ipykernel        : 5.4.3
jupyter client   : 6.1.12
jupyter lab      : 3.0.9
nbconvert        : 6.0.7
ipywidgets       : 7.6.3
nbformat         : 5.1.2
traitlets        : 5.0.5

Spotify APIで再生回数トップ50の曲データを取得するの手順でspotipyをインストールしておく。

さらに、曲を特徴でクラスタリングして可視化するためのPythonライブラリをインストールする。

$ pip3 install scikit-learn pandas matplotlib seaborn


spotipyで曲の特徴を取得

APIで取得できる特徴の説明はGet Track's Audio Featuresにある。今回はacousticness(アコースティック度)、danceability(ダンスに適した曲か)、energy(エネルギッシュさ)、instrumentalness(ボーカルがない度合い)、liveness(ライブ感)、loudness(曲全体のボリューム)、speechiness(話し言葉の度合い)、valence(曲のポジティブ度)を使用する。

今回もSpotify APIで再生回数トップ50の曲データを取得すると同様にspotipyを使う。曲の特徴を取得するには、spotifyのaudio_featuresメソッドに引数としてトラックIDのリストを渡す。取得した特徴はPandasのDataFrameにしておく。
import pandas as pd
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials

# language='ja'としないとアーティスト名が英語表記になる
spotify = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials(), language='ja')

def get_tracks(playlist_id):
    results = spotify.playlist_items(
        playlist_id,
        fields=None,
        limit=50,
        offset=0,
        market='JP',
        additional_types=('track', 'episode'),
    )
    items = results['items']

    tracks = []
    for item in items:
        data = {}
        track = item['track']
        data['track_id'] = track['id']
        data['track_name'] = track['name']
        data['artists'] = [ar['name'] for ar in track['artists']]
        tracks.append(data)

    return tracks

def get_track_features(track_ids):
    results = spotify.audio_features(track_ids)
    # idと分類に使用する特徴
    features = ['id', 'danceability', 'energy', 'loudness', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']
    track_features = []
    for re in results:
        track_features.append(
            {key: value for key, value in re.items() if key in features}
        )
    return track_features

# プレイリストの曲一覧取得
# トップ50 日本
# URLの最後の22文字がプレイリストID
playlist_id = '37i9dQZEVXbKXQ4mDTEBXq'
tracks = get_tracks(playlist_id)
track_info = {tr['track_id']: f'{tr["track_name"]} - {"|".join(tr["artists"])}' for tr in tracks}

# 曲の特徴を取得
track_ids = [tr['track_id'] for tr in tracks]
track_features = get_track_features(track_ids)
df_feature = pd.DataFrame([tf for tf in track_features])
df_feature.set_index('id', inplace=True)
df_feature.info()

次のようにDataFrameには50曲分の特徴が格納される。
Index: 50 entries, 4IfrM44LofE9bSs6TDZS49 to 3oQaOjaIYPsnJbGNzXcIID
Data columns (total 9 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   danceability      50 non-null     float64
 1   energy            50 non-null     float64
 2   loudness          50 non-null     float64
 3   speechiness       50 non-null     float64
 4   acousticness      50 non-null     float64
 5   instrumentalness  50 non-null     float64
 6   liveness          50 non-null     float64
 7   valence           50 non-null     float64
 8   tempo             50 non-null     float64


クラスタリングによる分類と可視化

まずはK平均法でクラスタリングする。クラスター数は今回はとりあえず5とする。そして、その結果をもとにクラスターごとの曲を表示する。

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

# language='ja'としないとアーティスト名が英語表記になる
spotify = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials(), language='ja')

def get_tracks(playlist_id):
    results = spotify.playlist_items(
        playlist_id,
        fields=None,
        limit=50,
        offset=0,
        market='JP',
        additional_types=('track', 'episode'),
    )
    items = results['items']

    tracks = []
    for item in items:
        data = {}
        track = item['track']
        data['track_id'] = track['id']
        data['track_name'] = track['name']
        data['artists'] = [ar['name'] for ar in track['artists']]
        tracks.append(data)

    return tracks

def get_track_features(track_ids):
    results = spotify.audio_features(track_ids)
    # idと分類に使用する特徴
    features = ['id', 'danceability', 'energy', 'loudness', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']
    track_features = []
    for re in results:
        track_features.append(
            {key: value for key, value in re.items() if key in features}
        )
    return track_features

def kcluster(df):
    X = StandardScaler().fit_transform(df)
    # とりあえずクラスター数5
    n_clusters = 5
    clusterer = KMeans(init='k-means++', n_clusters=n_clusters, random_state=0)
    kmeans = clusterer.fit(X)
    return kmeans.labels_

# プレイリストの曲一覧取得
# トップ50 日本
# URLの最後の22文字がプレイリストID
playlist_id = '37i9dQZEVXbKXQ4mDTEBXq'
tracks = get_tracks(playlist_id)
track_info = {tr['track_id']: f'{tr["track_name"]} - {"|".join(tr["artists"])}' for tr in tracks}

# 曲の特徴を取得
track_ids = [tr['track_id'] for tr in tracks]
track_features = get_track_features(track_ids)
df_feature = pd.DataFrame([tf for tf in track_features])
df_feature.set_index('id', inplace=True)
df_feature.info()

# K平均法でクラスタリング
df_feature['cluster'] = kcluster(df_feature.values)

# クラスターごとの曲名を表示
with pd.option_context('display.max_rows', None, 'display.max_columns', None):
    for cluster, rows in df_feature.groupby('cluster'):
        print(f'\ncluster {cluster}:')
        for id in rows.index:
            print(track_info[id])

結果は以下の通り。

cluster 0:
ミックスナッツ - Official髭男dism
喜劇 - 星野 源
Habit - SEKAI NO OWARI
FEARLESS - LE SSERAFIM
Dynamite - BTS
Butter - BTS
群青 - YOASOBI
一途 - King Gnu
常緑 - 大橋ちっぽけ
怪物 - YOASOBI
WA DA DA - Kep1er
The Feels - TWICE
Bye-Good-Bye - BE:FIRST
ELEVEN - IVE

cluster 1:
ベテルギウス - 優里
なんでもないよ、 - マカロニえんぴつ
水平線 - back number
残響散歌 - Aimer
STAY (with Justin Bieber) - The Kid LAROI|ジャスティン・ビーバー
Cry Baby - Official髭男dism
猫 - DISH//
I LOVE... - Official髭男dism
おもかげ (produced by Vaundy) - milet|Aimer|幾田りら

cluster 2:
M八七 - 米津玄師
ドライフラワー - 優里
カメレオン - King Gnu
シャッター - 優里
勿忘 - Awesome City Club
逆夢 - King Gnu
点描の唄 - Mrs. GREEN APPLE|井上苑子
魔法の絨毯 - 川崎 鷹也
YOKAZE - 変態紳士クラブ
ハート - あいみょん
Stand by me, Stand by you. - 平井 大

cluster 3:
W / X / Y - Tani Yuuki
シンデレラボーイ - Saucy Dog
CITRUS - Da-iCE
恋風邪にのせて - Vaundy
きらり - 藤井 風
LOVE DIVE - IVE
Permission to Dance - BTS
Mela! - 緑黄色社会
夜に駆ける - YOASOBI
三原色 - YOASOBI
115万キロのフィルム - Official髭男dism
ヨワネハキ - MAISONdes|和ぬか|asmi
Pretender - Official髭男dism
踊 - Ado
BOY - King Gnu

cluster 4:
踊り子 - Vaundy

さらに、特徴ごとにクラスター別の箱ひげ図を作成する。上記コードに以下を追記。

# 特徴別の箱ひげ図を作成
fig, ax =plt.subplots(len(df_feature.columns) - 1, 1, figsize=(12, 40), facecolor=(1, 1, 1))
for i, col_name in enumerate(df_feature):
    if col_name != 'cluster':
        sns.boxplot(x=df_feature[col_name], y=df_feature['cluster'], width=0.5, orient='h', ax=ax[i])
        sns.swarmplot(x=df_feature[col_name], y=df_feature['cluster'], color="r", orient='h', dodge=True, ax=ax[i])
plt.tight_layout()
plt.show()

結果は以下のような箱ひげ図が作成される。クラスター4に1曲だけ属する「踊り子」は、ほかの曲に比べてacousticness(アコースティック度)とinstrumentalness(ボーカルがない度合い)が高いことがわかる。



0 件のコメント:

コメントを投稿