Spotify APIを使って曲のデータ分析をしてみる

## 注意事項（2025年4月追記）

Spotify APIの仕様変更によって、本投稿で紹介しているソースコードは動作できなくなっています。

## 背景

音楽ストリーミングサービス「Spotify」にAPIがあることを知り、折角データが取れるのならデータ分析をしてみよう！と思い立ちやってみました。

（データ分析に関しては完全初心者なので、お試しでやってみたという記事です）

最終的には、Spotifyの自動レコメンド機能を自分で作ってみたいと考えています。。。

今回は**プレイリストからデータ取得→相関分析**まで行います。

### 内部パラメータについて

Spotify APIについて調べていくと、どうやら楽曲毎に以下のようなパラメータが付与されていることを知りました。


| パラメータ         | 説明                                                         |
| ------------------ | ------------------------------------------------------------ |
| `acousticness`     | 曲がアコースティックである確率を0.0から1.0で表したもの。1.0は曲がアコースティックである確率が高いことを示す。 |
| `danceability`     | 曲がダンスに適しているかを0.0から1.0で表したもの。この値はテンポ、リズムの安定性、ビートの強さなどから計算され、1.0は曲がダンスに非常に適していることを示す。 |
| `energy`           | 曲がエネルギッシュである程度を0.0から1.0で表したもの。この値は活動的な音、速いテンポ、騒々しさなどから計算され、1.0は曲が非常にエネルギッシュであることを示す。 |
| `instrumentalness` | 曲がインストゥルメンタルである確率を0.0から1.0で表したもの。1.0は曲がインストゥルメンタル（つまり、ボーカルなし）である確率が高いことを示す。 |
| `liveness`         | 曲がライブで演奏されている確率を0.0から1.0で表したもの。1.0は曲がライブで演奏されている確率が高いことを示す。 |
| `loudness`         | 曲の全体的な音の大きさをデシベルで表したもの。この値は曲全体にわたって平均化され、値は約-60から0dbまでの範囲である。 |
| `speechiness`      | 曲に話し言葉が含まれている確率を0.0から1.0で表したもの。1.0は曲が話し言葉で構成されている確率が高いことを示す。 |
| `valence`          | 曲が陽性の感情（例えば、幸せ、楽観的、陽気）を伝える確率を0.0から1.0で表したもの。1.0は曲が陽性の感情を強く伝えることを示す。 |
| `tempo`            | 曲の全体的な推定テンポをBPM（ビート・パー・ミニット）で表したもの。 |
| `key`              | 曲の全体的なキー（音階）を示す整数。この値はピッチクラス表記法によるもので、0 = C, 1 = C#/Db, 2 = D, と続く。 |
| `mode`             | 曲のモード（主音がメジャーかマイナーか）を示す整数。1はメジャー、0はマイナーを示す。 |
| `time_signature`   | 曲の拍子記号（1小節あたりの拍数）を示す整数。                |

かなりの量の属性情報があり、Spotifyのレコメンド機能はこのような値を使って実装されているのでは？と思いました。（このパラメータを利用すれば、自分でレコメンド機能を作成できそう…）

### 今回やること

内部パラメータの分析を行う

## 環境

使用言語はPython3.9、エディタはVSCodeを利用しています。

また、ライブラリとして以下のものを導入しています。

- Spotipy
- numpy
- pandas
- matplotlib
- seaborn



## 事前準備

1. Spotifyのアカウントを作成します。

2. [Spotify for Developer](https://developer.spotify.com/)にログインします。

3. 右上のアカウントアイコンから、ダッシュボードを開きます。

4. Create Appからアプリを作成します。

   アプリ名と説明は任意、Redirect URIsはリダイレクト先のURLを設定してください。

5. 作成したアプリをダッシュボードから選択し、Settingボタンから設定画面を開きます。

   クライアントIDとクライアントシークレットが表示されるので、メモしておきましょう。

6. `spotipy`ライブラリを利用するので、pip等でインストールします。

## データを取得する

とりあえず動かしてみます。

#### インポート

```python
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials #ここ以降にいろいろ追加
```

APIレスポンスの中身が気になったのでほかに`json`とか`pprint`とか入れてました。

#### 必須情報

```python
my_id = '発行したCLIANT ID'
my_secret = '発行したCLIANT SECRET'
```

Spotify for Developersから取ってきたものをつっこみます。

#### 準備

ユーザー情報が必要ないメソッドを使う場合は、以下のコードで`sp`からメソッドを呼び出せます。

```python
ccm = SpotifyClientCredentials(client_id = my_id, client_secret = my_secret)
sp = spotipy.Spotify(client_credentials_manager = ccm)
```

ユーザー情報が必要な場合はOAuthで認証画面を表示する必要があります。以下のコードで実装できます。

```python
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=my_id,
                                               client_secret=my_secret,
                                               redirect_uri="http://localhost:3000/",
                                               scope="user-read-recently-played"))
```

スコープに欲しい情報の要素入れて、リダイレクト先で承認するフローを踏む必要があります。

`redirect_uri`には事前準備で設定したリダイレクトURIを、`scope`には利用したいメソッドで必要なものを入れます。



#### プレイリストから楽曲データを取得する関数

```python
# プレイリストから曲を取得
def get_to_playlist(playlist_id):
    playlist = sp.playlist(playlist_id)
    track_ids = []
    for item in playlist['tracks']['items']:
        track = item['track']
        if not track['id'] in track_ids:
            track_ids.append(track['id'])
        else:
            for item in playlist['tracks']['items']:
                track = item['track']
                if not track['id'] in track_ids:
                    track_ids.append(track['id'])
    return track_ids
```

プレイリストのIDを引数に、プレイリスト内の楽曲IDを配列に格納し返却します。
`sp.playlist`にプレイリストIDを渡すと、プレイリストの情報がJSONで返ってくるので上手いこと抽出します。


#### 楽曲の内部パラメータを取得する

`audio_features`で内部パラメータを取得できます。

```python
music_id = "楽曲のID"
result = sp.audio_features(music_id)
pprint.pprint(result)
```

こんな感じの情報が返ってきます。（サカナクション「忘れられないの」の場合）

```python
[{'acousticness': 0.35,
  'analysis_url': 'https://api.spotify.com/v1/audio-analysis/7a3LbQFgp7NCuNcGlTgSsN',
  'danceability': 0.642,
  'duration_ms': 238000,
  'energy': 0.645,
  'id': '7a3LbQFgp7NCuNcGlTgSsN',
  'instrumentalness': 0.00349,
  'key': 6,
  'liveness': 0.191,
  'loudness': -7.358,
  'mode': 0,
  'speechiness': 0.0375,
  'tempo': 172.1,
  'time_signature': 4,
  'track_href': 'https://api.spotify.com/v1/tracks/7a3LbQFgp7NCuNcGlTgSsN',
  'type': 'audio_features',
  'uri': 'spotify:track:7a3LbQFgp7NCuNcGlTgSsN',
  'valence': 0.917}]
```

関数にします。`sp.track`で楽曲の基本データを、`sp.audio_features`で内部パラメータを取得できます。
曲名、アルバム名、アーティスト名、尺と内部パラメータを気合で抽出し、配列にして返します。

```python
def getTrackFeatures(id):
    meta = sp.track(id)
    features = sp.audio_features(id)

    name = meta['name']
    album = meta['album']['name']
    artist = meta['album']['artists'][0]['name']
    length = meta['duration_ms']
    popularity = meta['popularity']
    key = features[0]['key']
    mode = features[0]['mode']
    danceability = features[0]['danceability']
    acousticness = features[0]['acousticness']
    energy = features[0]['energy']
    instrumentalness = features[0]['instrumentalness']
    liveness = features[0]['liveness']
    loudness = features[0]['loudness']
    speechiness = features[0]['speechiness']
    tempo = features[0]['tempo']
    time_signature = features[0]['time_signature']
    valence = features[0]['valence']

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

#### CSVファイルにデータを出力

データを扱いやすくするために一度CSV形式で出力します。
引数から得られた楽曲IDのリストから、楽曲の内部パラメータを取得する関数`getTrackFeatures`を使って1曲ずつデータを取得します。
最後はpandasを使ってデータフレーム化し、`to_csv`でcsvに変換します。

```python
def id_to_csv(track_ids):
    tracks = []
    for track_id in track_ids:
        track = getTrackFeatures(track_id)
        tracks.append(track)

    df = pd.DataFrame(tracks, columns=['name', 'album', 'artist', 'release_date', 'length', 'popularity', 'key', 'mode', 'danceability',
                      'acousticness', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'time_signature', 'valence'])
    df.head()

    df.to_csv('myplaylist.csv', encoding='utf-8', index=False)
    print("CSVファイルが作成されました。")

    return df
```

#### 動作確認

プレイリストIDを用意し、プレイリストから楽曲データを取得します。
そして楽曲データをCSVファイルに変換します。
今回使用したプレイリストは、私のSpotifyアカウントの自動レコメンドされたプレイリストです。
特徴としては、JPOPが多い、全50曲、インスト曲は少ないことが挙げられます。

```python
if __name__ == '__main__':
    ids = get_to_playlist("37i9dQZF1E39zhZzaOT7fU")
    id_to_csv(ids)
```
動かしてみると、CSVファイルが作成されています。
```
> py my_top_song.py     
CSVファイルが作成されました。
```
ExcelでCSVファイルを見てみると、ちゃんとデータ取得できているぽいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3535910/44e1f13e-b370-86d9-a77e-8803b32f3de6.png)

## データを分析する

先ほど取得したCSVファイルを使ってデータを分析してみます。
パラメータ同士の関連性がレコメンド機能の実装に役立つかも？と思い、とりあえず**相関分析**からやってみることにしました。

以下のサイトを参考にしました。

https://analysis-navi.com/?p=2751

pandasの`corr`メソッドで相関係数を算出し、データを可視化するライブラリ`seaborn`を用いてヒートマップで表示します。

```python:data_analytics.py
import numpy as numpy
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

df_score = pd.read_csv(
    'top50_japan_data.csv', 
    index_col= "name",
    usecols=["name", "length", "popularity", "key", "mode", "danceability",
             "acousticness", "energy", "instrumentalness", "liveness",
             "loudness", "speechiness", "tempo", "valence"
    ])

df_corr = df_score.corr()

sns.set_context(context="talk")
fig = plt.subplots(figsize=(8, 8))
sns.heatmap(df_corr, annot=True, fmt='.1f', cmap='bwr', vmin=-1, vmax=1)
plt.show()
```

こちらを動かしてみると…
下のようなヒートマップを作成できました！

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3535910/f3b8cd51-f0eb-56f9-d381-bea9c5bbfa18.png)

正の相関があれば赤く、相関が無ければ白、負の相関があれば青くなっています。
画像を見てみると、いろいろなことが分かるようになりました。

まずは正の相関から。
valence(曲の陽性度)とdanceability(ダンスへの適応度)が0.7、energy(エネルギッシュさ)とloudness(音の大きさ)が0.6と強い相関を示しています。
ダンスに適してる曲は基本的に明るい・楽しい曲ですし、エネルギッシュな曲は音が大きいことも納得できます。

次に負の相関を見てみます。
danceability(ダンスへの適応度)とloudness(音の大きさ)、danceability(ダンスへの適応度)とtempo(BPM)が-0.5と強い負の相関を示しています。
ダンスに適している曲は音が小さく、テンポが遅い傾向があるということですね。
音が小さいのはあまり納得できないですが、テンポが速すぎる曲は踊ることに適していないのは確かです。

Spotify公式のヒットチャート(日本TOP50)でもヒートマップを作成してみます。

![Figure2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3535910/b5b1b1d3-6140-b401-650e-59245de93eb7.png)

正の相関について見てみると、energy(エネルギッシュさ)とloudness(音の大きさ)が0.8と非常に強い相関を示しています。
また、energy(エネルギッシュさ)とliveness(ライブ感)、speechiness(話し言葉の多さ)が0.5と強い相関を示しています。
最近の人気曲の傾向として、エネルギッシュな曲ほど音が大きく、ライブ感があり、話し言葉が多いことが分かります。

負の相関について見てみると、liveness(ライブ感)とdanceability(ダンスへの適応度)が-0.5と強い負の相関を示しています。
また、相関係数が-0.4の組み合わせは以下の通りでした。

- length(曲の長さ)とdanceability(ダンスへの適応度)
- length(曲の長さ)とvalence(曲の陽性度)
- energy(エネルギッシュさ)とacousticness(アコースティック度)
- tempo(BPM)とdanceability(ダンスへの適応度)

## まとめ

Spotifyのプレイリストからデータを取得し、パラメータの相関分析を行うことが出来ました。
相関分析の結果としては、プレイリスト単位での傾向は知ることが出来ました。しかし、自分の趣味のプレイリストとヒットチャートの結果を見て分かる通り、分析対象によってかなり結果が変わってきます。
相関分析でパラメータの類似性が分かればいいな～と思っていたのですが、そもそものデータの傾向が分析結果に大きく影響するようです。
「**音楽のジャンルがバラバラ**」「**分析対象のデータ数が足りない**」ことが思った通りに行かなかった原因でしょうか。。。

次は、ジャンル別で規模を大きくしたデータセットを作成し、もう一度データを分析してみようと思います。

つづく