ビートメイカーがPythonで人気チャートの傾向を徹底分析

Python

初めに

こんにちは、主にFL StudioというDAWソフトを使用してヒップホップのビートメイクについて情報を発信しています。PHONON(フォノン)といいます。今回はプログラミング言語PythonとSpotify APIを使用してSpotifyのSNS人気チャートバイラルヒッツ22カ国分を使用して人気曲を徹底的に分析したいと思います。

きっかけ

音楽好きから自分で音楽を作ってみたいと思い、会社員と並行して楽曲制作を行なっています。多くの音楽家はお仕事と平行して、日々制作に励んでいるのではと思います。

日々の業務に追われ、限りある時間の中で効率よく制作するにはどうすれば良いか長年考えながら制作に取り組んできました。そんな時に書店で生産性、業務効率化ならPython!のような書籍をみて自身の業務や制作に活かせないかと考え、Aidemyさんでデータ分析コースを3ヶ月受講しました。今回の記事はそのポートフォリオでもあります。

以前記事にした、教育訓練給付制度でスキルのワイド化でも取り上げた教育訓練給付制度を利用すれば受講料の自己負担もかなり軽減してくれます。

とんでもなく長い記事になると思うので、私が今までプロデュースした楽曲をSpotifyのプレイリストにまとめたのでよければ聴きながらこの記事を読んでいただけたら嬉しいです。笑

それでは実際のコーディングの方を見ていきましょう。

SpotifyのAPIを利用してチャートのデータを取得

とても長くなるのでSpotify APIを使用するために必要な登録とプレイリストIDの取得方法は以下の記事を参考にさせていただきました。実行環境はjupyrer labでPythonは3.8です。

PythonでSpotify APIを使ってみる ~全ての音楽愛好家のためのSpotify API ep 1~

PythonでSpotify APIを使ってみる ~全ての音楽愛好家のためのSpotify API ep 1~
Spotify APIを使ってみましょう。Spotifyのバックにある膨大なデータにアクセスしてみよう。
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
import pandas as pd
import time

まずは必要なライブラリをインポートします。

client_id = 'Spotify Webデベロッパーから取得'
client_secret = 'Spotify Webデベロッパーから取得'

client_credentials_manager = spotipy.oauth2.SpotifyClientCredentials(client_id, client_secret)
spotify = spotipy.Spotify(client_credentials_manager=client_credentials_manager,language='ja')

APIを使用するためのログイン情報を入力します。ここはあまり深く考えず、コピペでいきます。

# idを取得
def getTrackIDs(user, playlist_id):
    ids = []
    playlist = spotify.user_playlist(user, playlist_id)
    for item in playlist['tracks']['items']:
        track = item['track']
        ids.append(track['id'])
    return ids

# Spotifyのユーザー名と、プレイリストのIDを入力
ids = getTrackIDs('あなたのユーザー名','取得したいプレイリストのID')

idsという空のリストにプレイリスト内の楽曲毎のIDを追加していきます。

# メタデータを取得
def getTrackFeatures(id):
    meta = spotify.track(id)
    features = spotify.audio_features(id)
    
    
    # meta
    name = meta['name']
    album = meta['album']['name']
    artist = meta['album']['artists'][0]['name']
    release_date = meta['album']['release_date']
    length = meta['duration_ms']
    popularity = meta['popularity']
    
    # features
    acousticness = features[0]['acousticness']
    key = features[0]['key']
    danceability = features[0]['danceability']
    energy = features[0]['energy']
    instrumentalness = features[0]['instrumentalness']
    mode = features[0]['mode']
    liveness = features[0]['liveness']
    loudness = features[0]['loudness']
    speechiness = features[0]['speechiness']
    bpm = features[0]['tempo']
    time_signature = features[0]['time_signature']
    valence = features[0]['valence']

    track = [name, album, artist, release_date, length, mode, popularity, danceability, acousticness, key, energy, instrumentalness, liveness, loudness, speechiness, bpm, time_signature, valence]
    return track


# loop over track ids
tracks = []
for i in range(len(ids)):
    time.sleep(.5)
    track = getTrackFeatures(ids[i])
    tracks.append(track)

取得したidから楽曲のアーティスト名などのメタデータ、楽曲の特徴量を取得して空のリストtracksにappendしていきます。ここが非常に分かりづらいのですが、for文ではなくids[0]のようにして一曲単位で確認すると比較的どんな情報が内包されているか分かりやすいです。各特徴量はこんな感じになっています。

name
曲名
album
アルバム名
artist
アーティスト名
release_date
リリース日
length
曲の長さ
millsecondsで入る
popularity
人気度
danceability
ダンス度(テンポ、リズムの一定感、ビートの強さなどから算出)
0.0-1.0
1.0がダンス度が高いことを示す
acousticness
アコースティック度
0.0-1.0
1.0がアコースティック度高いことを示す
energy
エネルギー
fast, loud, noisyであれば1に近づく
0.0-1.0
instrumentalness
インスト感
0.0-1.0
0.5以上でインスト
mode
曲調を示す。
メジャーが1,
マイナーが0
liveness
ライブさ
0.0-1.0
レコーディングにオーディエンスが含まれているかで判断されている
0.8以上でライブトラックの可能性大
loudness
音の大きさ
-60 〜 0 db
speechiness
スピーチ度
トークショー、オーディオブック、ポエムなどは1に近くなる
0.33以下で音楽
valence
曲のポジティブ度
0.0-1.0
1がポジティブ(happy, cheerful, euphoric)
0がネガティブ (sad, depressed, angry)
bpm
曲のテンポ
BPMの数字が入る
time_signature
拍子

特徴量だけでたくさんあるのですが、このくらい細かく楽曲の情報を知ることができます。

# 追加したジャンルのリスト
genres_list = []
for i in range(len(ids)):
    track_id = spotify.track(ids[i])
    track_id = track_id['album']['artists'][0]['id']
    results = spotify.artist_related_artists(track_id)
    try:
        genres = results['artists'][0]['genres'][0]
        genres_list.append(genres)
    except:
        genres_list.append('None')

# 追加した視聴用のリスト
preview_url_list = []
for i in range(len(ids)):
    track_id = spotify.track(ids[i])
    preview_url = track_id['preview_url']
    preview_url_list.append(preview_url)

ジャンルや視聴用のURLがあったので後から追加したものです。ジャンルに関してもSpotifyは例えばHip Hopでも一つの楽曲にEmo Rap, Chicago Rap, Sad Rapなど細かく細分化されています。なのでresults[‘artists’][0][‘genres’][0]としています。逆にジャンルがない楽曲もありエラーが出るのでtry, exceptでNoneと入力するようにしました。

視聴用URLは実際に自分の耳で確認用で追加しました、

df = pd.DataFrame(tracks, columns = ['name', 'album', 'artist', 'release_date', 'length', 'mode', 'popularity', 'danceability', 'acousticness', 'key','energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'time_signature', 'valence'])
df['genres'] = genres_list
df['preview_urls'] = preview_url_list
# 国コードを追加
df["country"] = "South Africa"

pandasでデータフレームにします。後から追加した’genres’, ‘preview_urls’も加えて、国コードも追加します。

df.to_csv("spotify_バイラルヒッツSouth Africa.csv", sep = ',')

CSVに書き出します。これを22ヵ国分のidで出力していきます。これで22カ国分のデータが揃いました。

ここまでをまとめたものがこちらです。

import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
import pandas as pd
import time


client_id = 'Spotify Webデベロッパーから取得'
client_secret = 'Spotify Webデベロッパーから取得'

client_credentials_manager = spotipy.oauth2.SpotifyClientCredentials(client_id, client_secret)
spotify = spotipy.Spotify(client_credentials_manager=client_credentials_manager,language='ja')

# idを取得
def getTrackIDs(user, playlist_id):
    ids = []
    playlist = spotify.user_playlist(user, playlist_id)
    for item in playlist['tracks']['items']:
        track = item['track']
        ids.append(track['id'])
    return ids

# Spotifyのユーザー名と、プレイリストのIDを入力
ids = getTrackIDs('あなたのユーザー名','取得したいプレイリストのID')

# メタデータを取得
def getTrackFeatures(id):
    meta = spotify.track(id)
    features = spotify.audio_features(id)
    
    
    # meta
    name = meta['name']
    album = meta['album']['name']
    artist = meta['album']['artists'][0]['name']
    release_date = meta['album']['release_date']
    length = meta['duration_ms']
    popularity = meta['popularity']
    
    # features
    acousticness = features[0]['acousticness']
    key = features[0]['key']
    danceability = features[0]['danceability']
    energy = features[0]['energy']
    instrumentalness = features[0]['instrumentalness']
    mode = features[0]['mode']
    liveness = features[0]['liveness']
    loudness = features[0]['loudness']
    speechiness = features[0]['speechiness']
    bpm = features[0]['tempo']
    time_signature = features[0]['time_signature']
    valence = features[0]['valence']

    track = [name, album, artist, release_date, length, mode, popularity, danceability, acousticness, key, energy, instrumentalness, liveness, loudness, speechiness, bpm, time_signature, valence]
    return track


# loop over track ids
tracks = []
for i in range(len(ids)):
    time.sleep(.5)
    track = getTrackFeatures(ids[i])
    tracks.append(track)
    
# 追加したジャンルのリスト
genres_list = []
for i in range(len(ids)):
    track_id = spotify.track(ids[i])
    track_id = track_id['album']['artists'][0]['id']
    results = spotify.artist_related_artists(track_id)
    try:
        genres = results['artists'][0]['genres'][0]
        genres_list.append(genres)
    except:
        genres_list.append('None')

# 追加した視聴用のリスト
preview_url_list = []
for i in range(len(ids)):
    track_id = spotify.track(ids[i])
    preview_url = track_id['preview_url']
    preview_url_list.append(preview_url)

# create dataset
df = pd.DataFrame(tracks, columns = ['name', 'album', 'artist', 'release_date', 'length', 'mode', 'popularity', 'danceability', 'acousticness', 'key','energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'time_signature', 'valence'])
df['genres'] = genres_list
df['preview_urls'] = preview_url_list
# 国コードを追加
df["country"] = "South Africa"

# csvにして保存
df.to_csv("spotify_バイラルヒッツSouth Africa.csv", sep = ',')

22ヵ国分のデータを結合する

import pandas as pd
import glob

結合するのに必要なライブラリをインポートします。

filepaths = glob.glob('*.csv') 

data_list = []
for file in filepaths:
    data_list.append(pd.read_csv(file))

filepathsにあるチャートデータを空のdata_listへ追加していきます。

df = pd.concat(data_list,axis=0,sort=True)

pd,concatで縦方向に結合します。dfの内容を確認してみると

df.head()

結合されているのが確認できます。ただUnnamed: 0というcolumnは使用しないので

df = df.drop('Unnamed: 0', axis=1)

dropしてから

df.to_csv('Viral_playlists.csv')

CSVファイルに書き出して、結合は完成です。

まとめたものがこちらになります。

import pandas as pd
import glob


filepaths = glob.glob('バイラルヒットチャート_v2/*.csv') 

data_list = []
for file in filepaths:
    data_list.append(pd.read_csv(file))

df = pd.concat(data_list,axis=0,sort=True)
print(df.head())

df = df.drop('Unnamed: 0', axis=1)

df.to_csv('Viral_playlists.csv')

各ジャンル毎のグループによる分析

ここから分析に入っていきます。まずは

import os
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn import preprocessing
from glob import glob

必要なライブラリをインポートします。

df = pd.read_csv('Viral_playlist.csv')

2で書き出したCSVを読み込みます。

# genresと特長量のリストを作成
extra_cols = ['genres','length', 'mode', 'popularity', 'danceability', 'acousticness', 'key', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'time_signature', 'valence']

ジャンルで分析するので’genres’と各特徴量をまとめたリストを作成します。

# genresをgroupbyでまとめてインデックスへ.meanで平均を算出し新しいdfへ
df_genres_feature = df[extra_cols].groupby('genres').mean()

読み込んだdfのextra_cols内のカラム’genres’をgroupbyで同じ値を持つデータをまとめて、meanで特徴量を平均化したデータフレームをdf_genres_featureとします。’

# 各列は、平均0 、標準偏差1に標準化するためにインスタンス化
sc = preprocessing.StandardScaler()

コメントアウトにも書いていますが平均0 、標準偏差1に標準化するためにpreprocessing.StandardScaler()をインスタンス化します。

# 正規化したdf_genres_feature.valuesをarr_genres_featureへ
arr_genres_feature = sc.fit_transform(df_genres_feature.values)

df_genres_featureのvalues、値を標準化しarr_genres_featureというインスタンスを作成。

cls = KMeans(n_clusters=8,random_state=0)

ジャンルが細分化され過ぎていて、近い属性を持つものをクラスタリングしたいのでKMeansでn_clusters=8、8個のグループを作成するためインスタンス化する。

result = cls.fit(arr_genres_feature)

先程標準化したarr_genres_featureをクラスタリング。

pred = cls.predict(arr_genres_feature)

predにarr_genres_featureが属しているクラスタ番号を返す。

df_genres_feature['pred'] = pred

meanで特徴量を平均化したデータフレームをdf_genres_featureにクラスタ番号のカラムを作成します。

次に主成分分析を行いたいので

# 主成分分析PCA,2次元に圧縮する。
pca = PCA(n_components=2)

PCAをインスタンス化、n_components=2で2次元に圧縮します。

# 主成分分析を行う
pca_corr = pca.fit_transform(arr_genres_feature)

標準化したarr_genres_featureを主成分分析します。

# 転置する
pca.components_.T

転置します。ここでは表示しませんが169行、2列のデータとなっていることを確認できました。ジャンルの関係性を可視化したいのでプロットしていきます。

# プロットする
plt.figure(figsize = (30,16)) # 図のサイズ
# 散布図を作るpca_corrをスライスしてx,yに分ける。色分けはpredで予測した値。cmapは色のカラーマップみたいなもの
plt.scatter(pca_corr[:,0],pca_corr[:,1],c = pred, cmap = 'tab10')
# pca_corr.shape[0]は169行
for i in range(pca_corr.shape[0]):
    # annotateは注釈を入れれるし細かく設定できるようだ
    plt.annotate(df_genres_feature.index.values[i],(pca_corr[i,0],pca_corr[i,1]))
plt.show()

こういった結果となりました。

やはりジャンルが細分化され過ぎていて分かりずらいですね。なのでここからはクラスタリングしたジャンルを分析していきます。

df_genre_cls = pd.merge(df, df_genres_feature['pred'],left_on='genres', right_index=True, how='inner')

mergeをして内部結合したdf_genre_clsを作りました。これを利用して各特徴量の関係性をヒートマップで確認してみます。

# 'object'を削除する
_df = df_genre_cls.select_dtypes(exclude='object')

# ヒートマップで確認。
colormap = plt.cm.RdBu
plt.figure(figsize=(14,12))
plt.title('Correlation', y=1.05, size=15)
sns.heatmap(_df.astype(float).corr(), linewidths=0.1,cmap=colormap, vmax=1.0, square=True, annot=True)
plt.show()

ここからわかることは

エネルギーとアコースティック度が関係性が低い

エネルギーとラウドネスは関係性が高い。

ダンス度とポジティブ度は関係性が高い。

ということがわかります。各特徴量の説明は1の方にあります。関係性が高いものが同じジャンルグループに属してそうですね。

それでは各ジャンルグループの各特徴量を確認します。seabornで確認します。

# popularity = 人気度
ax = sns.boxplot(x="pred", y="popularity", data = df_genre_cls)   
ax.set_title('popularity by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('popularity')     
plt.show()

# valence = 曲のポジティブ度 0.0-1.0
ax = sns.boxplot(x="pred", y="valence", data = df_genre_cls)   
ax.set_title('valence by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('valence')     
plt.show()

# danceability = ダンス度(テンポ、リズムの一定感、ビートの強さなどから算出)
ax = sns.boxplot(x="pred", y="danceability", data = df_genre_cls)   
ax.set_title('Dancing by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('danceability')     
plt.show()

# liveness = ライブ感はあまりない
ax = sns.boxplot(x="pred", y="liveness", data = df_genre_cls)   
ax.set_title('liveness by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('liveness')     
plt.show()

# loudness = 音の大きさ -60 〜 0 db
ax = sns.boxplot(x="pred", y="loudness", data = df_genre_cls)   
ax.set_title('loudness by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('loudness')     
plt.show()

# energy = fast, loud, noisyであれば1に近づく
ax = sns.boxplot(x="pred", y="energy", data = df_genre_cls)   
ax.set_title('energy by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('energy')     
plt.show()


# 以下の方法で90%点を外れた値を90%点に置き換えることができますのでこちらを試してみてください。
p90 = df_genre_cls["instrumentalness"].quantile(0.90)
df_genre_cls["instrumentalness"] = df_genre_cls["instrumentalness"].clip(0,p90)
# print(df_genre_cls['instrumentalness'])

# instrumentalness = インスト感
ax = sns.barplot(x="pred", y="instrumentalness", data = df_genre_cls)  
ax.set_title('instrumentalness by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('instrumentalness')     
plt.show()

# tempo = BPM
ax = sns.boxplot(x="pred", y="tempo", data = df_genre_cls)  
ax.set_title('tempo by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('tempo')     
plt.show()

# keyが6から7が中心値ということはFからG
ax = sns.boxplot(x="pred", y="key", data = df_genre_cls)  
ax.set_title('key by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('key')     
plt.show()

これを踏まえてジャンルグループの楽曲を視聴しながら分析しました。

ジャンルグループ0

明るいダンスミュージックが多く軽快な曲が多い、ライプ感が一番高い。

ジャンルグループ1

しっとり目のR&Bが多く楽曲人気度が一番低いグループ。

ジャンルグループ2

レゲエだったりレゲエをネタにしたドリル、ラテン調の曲が多い。楽曲のポジティブ度が一番高くダンス度が最も高い。

ジャンルグループ3

エネルギッシュでラウドな曲が一番多くグループ2に次いでポジティブ度が高い。

ジャンルグループ4

バラード調の曲が多い。人気度が一番高い楽曲が多く曲のポジティブ度が最も低い。もちろんバラード多めなのでダンス度が最も低い。テンポが一番高い、バラードなのでハーフでとっているからだと思われるBPM100で制作したい場合BPM200にしたりする。そっちの方が昨今のハイハット刻みやすいですよね。

ジャンルグループ5

カントリーミュージックやアコースティックな曲が多い。グループ4同様にダンス度は低い。

ジャンルグループ6

平均値寄りの楽曲の多い。他のグループより圧倒的に楽曲数が多い。人気チャートでも特に人気の曲が集中している可能性がある。

ジャンルグループ7

ボーカルのないダンスミュージックが多いグループ。エネルギッシュがグループ3同様に高くインスト度が群を抜いて高い、やはりダンスミュージックだからか。EDMであったりハウス、テクノ好きはこのグループでしょう。

ジャンルグループでの傾向はこのような感じになりました。ジャンルグループの分析に使ったコードのまとめはこちらになります。

import os
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn import preprocessing
from glob import glob
from IPython.core.display import display


df = pd.read_csv('Viral_playlists.csv')

# genresと特長量のリストを作成
extra_cols = ['genres','length', 'mode', 'popularity', 'danceability', 'acousticness', 'key', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'time_signature', 'valence']

# genresをgroupbyでまとめてインデックスへ.meanで平均を算出し新しいdfへ
df_genres_feature = df[extra_cols].groupby('genres').mean()

# KMeansは重点を置き分割していってグループ分けしてるってイメージ
cls = KMeans(n_clusters=8,random_state=0)

# 各列は、平均0 、標準偏差1に標準化・正規化するためにインスタンス化
sc = preprocessing.StandardScaler()

# df_genres_feature.valuesを標準化・正規化、学習実施
sc.fit(df_genres_feature.values)

# 正規化したdf_genres_feature.valuesをarr_genres_featureへ
arr_genres_feature = sc.transform(df_genres_feature.values)

result = cls.fit(arr_genres_feature)

pred = cls.predict(arr_genres_feature)

# df_genres_featureのカラムにpred
df_genres_feature['pred'] = pred

# 主成分分析PCA,2次元に圧縮する。
pca = PCA(n_components=2)

pca.fit(arr_genres_feature)

pca_corr = pca.transform(arr_genres_feature)

# 転置する
pca.components_.T

# プロットする
plt.figure(figsize = (30,16)) # 図のサイズ
# 散布図を作るpca_corrをスライスしてx,yに分ける。色分けはpredで予測した値。cmapは色のカラーマップみたいなもの
plt.scatter(pca_corr[:,0],pca_corr[:,1],c = pred, cmap = 'tab10')
# pca_corr.shape[0]は169行
for i in range(pca_corr.shape[0]):
    # annotateは注釈を入れれるし細かく設定できるようだ
    plt.annotate(df_genres_feature.index.values[i],(pca_corr[i,0],pca_corr[i,1]))
plt.show()

df_genre_cls = pd.merge(df, df_genres_feature['pred'],left_on='genres', right_index=True, how='inner')

# 'object'を削除する
_df = df_genre_cls.select_dtypes(exclude='object')

# ヒートマップで確認。
colormap = plt.cm.RdBu
plt.figure(figsize=(14,12))
plt.title('Correlation', y=1.05, size=15)
sns.heatmap(_df.astype(float).corr(), linewidths=0.1,cmap=colormap, vmax=1.0, square=True, annot=True)
plt.show()

# popularity = 人気度
ax = sns.boxplot(x="pred", y="popularity", data = df_genre_cls)   
ax.set_title('popularity by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('popularity')     
plt.show()

# valence = 曲のポジティブ度 0.0-1.0
ax = sns.boxplot(x="pred", y="valence", data = df_genre_cls)   
ax.set_title('valence by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('valence')     
plt.show()

# danceability = ダンス度(テンポ、リズムの一定感、ビートの強さなどから算出)
ax = sns.boxplot(x="pred", y="danceability", data = df_genre_cls)   
ax.set_title('Dancing by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('danceability')     
plt.show()

# liveness = ライブ感はあまりない
ax = sns.boxplot(x="pred", y="liveness", data = df_genre_cls)   
ax.set_title('liveness by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('liveness')     
plt.show()

# loudness = 音の大きさ -60 〜 0 db
ax = sns.boxplot(x="pred", y="loudness", data = df_genre_cls)   
ax.set_title('loudness by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('loudness')     
plt.show()

# energy = fast, loud, noisyであれば1に近づく
ax = sns.boxplot(x="pred", y="energy", data = df_genre_cls)   
ax.set_title('energy by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('energy')     
plt.show()


# 以下の方法で90%点を外れた値を90%点に置き換えることができますのでこちらを試してみてください。
p90 = df_genre_cls["instrumentalness"].quantile(0.90)
df_genre_cls["instrumentalness"] = df_genre_cls["instrumentalness"].clip(0,p90)
# print(df_genre_cls['instrumentalness'])

# instrumentalness = インスト感
ax = sns.barplot(x="pred", y="instrumentalness", data = df_genre_cls)  
ax.set_title('instrumentalness by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('instrumentalness')     
plt.show()

# tempo = BPM
ax = sns.boxplot(x="pred", y="tempo", data = df_genre_cls)  
ax.set_title('tempo by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('tempo')     
plt.show()

# keyが6から7が中心値ということはFからG
ax = sns.boxplot(x="pred", y="key", data = df_genre_cls)  
ax.set_title('key by pred')
ax.set_xlabel('pred')         
ax.set_ylabel('key')     
plt.show()

次は国同士での関係性も見ていきます。

国別での分析

国別ではジャンル分析のコードを流用しているので以下にまとめて記載しておきます。国別で使用するCSVはジャンルグループ毎で使用したものを書き出してdf_genre_clsとしています。

import os
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn import preprocessing
from glob import glob
from IPython.core.display import display


df = pd.read_csv('df_genre_cls.csv')

# genresと特長量のリストを作成
extra_cols = ['country','length', 'mode', 'popularity', 'danceability', 'acousticness', 'key', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'time_signature', 'valence']
df_country_feature = df[extra_cols].groupby('country').mean()

# 各列は、平均0 、標準偏差1に標準化・正規化するためにインスタンス化
sc = preprocessing.StandardScaler()

# df_genres_feature.valuesを標準化・正規化、学習実施
arr_country_feature = sc.fit_transform(df_country_feature.values)

distortions = []

for i  in range(1,11):                # 1~10クラスタまで一気に計算 
    km = KMeans(n_clusters=i,
                init='k-means++',     # k-means++法によりクラスタ中心を選択
                n_init=10,
                max_iter=300,
                random_state=0)
    km.fit(arr_country_feature)                         # クラスタリングの計算を実行
    distortions.append(km.inertia_)   # km.fitするとkm.inertia_が得られる

plt.plot(range(1,11),distortions,marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.show()

cls = KMeans(n_clusters=6,random_state=0)

result = cls.fit(arr_country_feature)
c
ountry_pred = cls.predict(arr_country_feature)

df_country_feature['country_pred'] = country_pred

# 主成分分析PCA,2次元に圧縮する。
pca = PCA(n_components=2)

pca_corr = pca.fit_transform(arr_country_feature)

# 転置する
pca.components_.T

# プロットする
plt.figure(figsize = (30,16)) # 図のサイズ
# 散布図を作るpca_corrをスライスしてx,yに分ける。色分けはpredで予測した値。cmapは色のカラーマップみたいなもの
plt.scatter(pca_corr[:,0],pca_corr[:,1],c =country_pred, cmap = 'tab10')
# pca_corr.shape[0]は169行
for i in range(pca_corr.shape[0]):
    # annotateは注釈を入れれるし細かく設定できるようだ
    plt.annotate(df_country_feature.index.values[i],(pca_corr[i,0],pca_corr[i,1]))
plt.show()

日本、ブラジル、インドなどかなり離れたところにいる国は独自性があるのがわかる。

df_country_cls = pd.merge(df, df_country_feature['country_pred'],left_on='country', right_index=True, how='inner')

# popularity = 人気度
ax = sns.boxplot(x="country_pred", y="popularity", data = df_country_cls)   
ax.set_title('popularity by country_pred')
ax.set_xlabel('country_pred')         
ax.set_ylabel('popularity')     
plt.show()

# valence = 曲のポジティブ度 0.0-1.0
ax = sns.boxplot(x="country_pred", y="valence", data = df_country_cls)   
ax.set_title('valence by country_pred')
ax.set_xlabel('country_pred')         
ax.set_ylabel('valence')     
plt.show()

# danceability = ダンス度(テンポ、リズムの一定感、ビートの強さなどから算出)
ax = sns.boxplot(x="country_pred", y="danceability", data = df_country_cls)   
ax.set_title('Dancing by country_pred')
ax.set_xlabel('country_pred')         
ax.set_ylabel('danceability')     
plt.show()

# liveness = ライブ感はあまりない
ax = sns.boxplot(x="country_pred", y="liveness", data = df_country_cls)   
ax.set_title('liveness by country_pred')
ax.set_xlabel('country_pred')         
ax.set_ylabel('liveness')     
plt.show()

# loudness = 音の大きさ -60 〜 0 db
ax = sns.boxplot(x="country_pred", y="loudness", data = df_country_cls)   
ax.set_title('loudness by country_pred')
ax.set_xlabel('country_pred')         
ax.set_ylabel('loudness')     
plt.show()

# energy = fast, loud, noisyであれば1に近づく
ax = sns.boxplot(x="country_pred", y="energy", data = df_country_cls)   
ax.set_title('energy by country_pred')
ax.set_xlabel('country_pred')         
ax.set_ylabel('energy')     
plt.show()


# 以下の方法で90%点を外れた値を90%点に置き換えることができますのでこちらを試してみてください。
p90 = df_country_cls["instrumentalness"].quantile(0.90)
df_country_cls["instrumentalness"] = df_country_cls["instrumentalness"].clip(0,p90)
# print(df_country_cls['instrumentalness'])

# instrumentalness = インスト感
ax = sns.barplot(x="country_pred", y="instrumentalness", data = df_country_cls)  
ax.set_title('instrumentalness by country_pred')
ax.set_xlabel('country_pred')         
ax.set_ylabel('instrumentalness')     
plt.show()

# tempo = BPM
ax = sns.boxplot(x="country_pred", y="tempo", data = df_country_cls)  
ax.set_title('tempo by country_pred')
ax.set_xlabel('country_pred')         
ax.set_ylabel('tempo')     
plt.show()

# keyが6から7が中心値ということはFからG
ax = sns.boxplot(x="country_pred", y="key", data = df_country_cls)  
ax.set_title('key by country_pred')
ax.set_xlabel('country_pred')         
ax.set_ylabel('key')     
plt.show()

国毎のグループだと楽曲の特徴量での差異はあまり見られなかったため、ジャンルグループを使用して国別グループを見てみる。

sns.set(font_scale = 1)
ax = sns.countplot(x="pred", data = df_country_cls.query('country_pred == 0'))  
ax.set_title('genres by Grope 0')
ax.set_xlabel('genres')              
plt.xticks(rotation=90)
plt.show()

sns.set(font_scale = 1)
ax = sns.countplot(x="pred", data = df_country_cls.query('country_pred == 1'))  
ax.set_title('genres by Grope 1')
ax.set_xlabel('genres')              
plt.xticks(rotation=90)
plt.show()

sns.set(font_scale = 1)
ax = sns.countplot(x="pred", data = df_country_cls.query('country_pred == 2'))  
ax.set_title('genres by Grope 2')
ax.set_xlabel('genres')              
plt.xticks(rotation=90)
plt.show()

sns.set(font_scale = 1)
ax = sns.countplot(x="pred", data = df_country_cls.query('country_pred == 3'))  
ax.set_title('genres by Grope 3')
ax.set_xlabel('genres')              
plt.xticks(rotation=90)
plt.show()

sns.set(font_scale = 1)
ax = sns.countplot(x="pred", data = df_country_cls.query('country_pred == 4'))  
ax.set_title('genres by Grope 4')
ax.set_xlabel('genres')              
plt.xticks(rotation=90)
plt.show()

sns.set(font_scale = 1)
ax = sns.countplot(x="pred", data = df_country_cls.query('country_pred == 5'))  
ax.set_title('genres by Grope 5')
ax.set_xlabel('genres')              
plt.xticks(rotation=90)
plt.show()

ジャンルグループ6がどの国でも多いのがわかります。なのでジャンルグループ6を抜いたら、国毎のグループの特徴を捉えやすいのではと考え分析してみました。

国毎グループ0(South Africa, Vietnam)  

ジャンルグループ1も多くジャンルグループ1はR&Bが多い、しっとり目で人気度が一番低いジャンルグループでジャンルグループ3のデータがなくエネルギッシュでラウドな曲があまり好まれない傾向があるグループだとわかる

国毎グループ1(Greece, Israel, Italy, Switzerland, Ukraine)

ジャンルグループ2,3,5が同じくらい入っている。レゲエだったりレゲエをネタにしたドリル、ラテン調の曲が多い楽曲のポジティブ度が一番高くダンス度が最も高い。ジャンルグループ5はカントリーミュージックやアコースティックな曲が多い。

国毎グループ2(Chine, Japan, Korea, Taiwan)

ジャンルグループ3も多くエネルギッシュでラウドな曲が一番多い。国別グループで見てみてもポジティブ度が他の国に比べ高い。我々日本を含む近隣の国々はエネルギッシュでラウドらしい。

国毎グループ3(AU, Argentina, Canada, Ecuador, Singapore, UK, US)

ジャンルグループ6が占める割合が最も多いグループでそれ以外のジャンルが少ないが、その中で少しだけ多かったのがグループ5でカントリーミュージックやアコースティックな曲が多い。

国毎グループ4(Arab, India)

グループ5も多くカントリーミュージックやアコースティックな曲が多い。国別で見ると最もインスト度が高くボーカルが入っていない楽曲が多い傾向がわかる。テンポも国グループ平均より僅かに下回っておりゆったりした曲の方が好まれる。データだけでなんとなくインドっぽいなという印象。

国毎グループ5(BR, Germany)

ジャンルグループ2も多くレゲエだったりレゲエをネタにしたドリル、ラテン調の曲が多い。楽曲のポジティブ度が一番高くダンス度が最も高い。国別全体でみてもダンス度が高い傾向。ブラジルにいったことはないですが、ダンス度は高そうだなという印象があるものの同じグループにドイツも加わるのは意外でした。ジャンルグループ1のしっとり目のR&Bが入っていないためそういった楽曲があまり好まれない傾向がある。

分析結果まとめ

ジャンルでの人気曲の特徴と国毎での人気曲の特徴を知ることができた。元々人気曲を集めたチャートの中でもどの国でも人気があるジャンルグループ6の楽曲をさらに掘り下げて分析していけば、もっと深く人気の楽曲がどういった傾向があるかわかりそう。

自身の楽曲制作でも活かせるようなデータを知ることができました。とりあえず制作する際にはキーをF、BPMを140あたりで始めてみようと思います。笑

今後の活用方法

楽曲制作という点では、このSpotifyのデータ分析を軸に新しいプロジェクトの指針になりそうです。またこういう雰囲気の楽曲でというオーダーに対しても、その楽曲の傾向、キーやコード進行、BPMに加えて、最近の人気の楽曲はこうゆう傾向があるなといったところも盛り込みながら制作するのにも大いに役立つと感じました。

また会社員的側面でいくと、職場のDX活動、業務効率化やデータ分析においてこの経験が生きてきています。

おわりに

今までプログラミング言語というものを全く知らなかった私ですが、Pythonを学んだことによりまた別の知見を得ることができたと感じました。Aidemyさんのチューターさんのフォローアップのおかげでなんとかここまで分析できたので、今後も学習を継続して自身の理解をさらに深めていきたいと思います。

とても長くSEO対策ツールは可読性NG出まくってますが以上です。

コメント

タイトルとURLをコピーしました