自分の音楽趣味をデータ分析する

JXA で遊んでいたら iTunes で登録している曲一覧データがとれたので、集計してみた。

参考： [知らないうちにMacがシステム標準でJavaScriptで操作できるようになってた \(JXA\) \- Qiita](https://qiita.com/zakuroishikuro/items/1b02378bf9e940602d87)

## iTunes の曲リストを取得する
JXA で曲リストを取得するにはこの6行だけでいける。

``` js:getAllTracks.js
function run() {
   const itunes = Application('iTunes');
   const music = itunes.playlists[1];
   const tracks = music.tracks.properties();
   return JSON.stringify(tracks);
}
```

`Application('iTunes')` で iTunes.app にアクセスするためのオブジェクトを取得して、2番目のプレイリスト (index でいうと `1` 番目) からトラック一覧を取得する。

なぜ2番目かというというのは以下のコマンドを実行してみればわかるが、

``` console
$ osascript -l JavaScript -e "Application('iTunes').playlists.name()"
ライブラリ, ミュージック, ミュージックビデオ, ムービー, テレビ番組, Podcast, オーディオブック, ...
```

iTunes のプレイリストは `ライブラリ, ミュージック, ミュージックビデオ, ムービー, ...` と続いていて、後半にユーザ定義のプレイリストが並んでいる。

そのためすべての音楽データのプレイリスト (`ミュージック`) を取得したい場合は2番目のプレイリストにアクセスすればよい。

この全曲のリストを JSON として吐き出す。

``` console
$ osascript -l JavaScript getAllTracks.js > tracks.json
```

データを JSON として吐き出すことができたので、あとは煮るなり焼くなり好きなようにできる。

## 曲リストを Python で読み込む
おもむろに Jupyter Notebook を開いて、吐き出した JSON を Pandas DataFrame として読み込む。

``` python
import pandas as pd

with open('tracks.json', 'r') as f:
    df = pd.read_json(f).set_index('id')

df.sample(5)
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16369/d8c4806c-1b57-1987-4e5f-56f2b5388024.png)

たくさん (60種類以上) のカラムがあるが、それぞれのカラムの意味は `スクリプトエディタ.app` を起動して `ウィンドウ` > `ライブラリ` > `iTunes` から `Track` クラスのドキュメントを探せば書いてある。(英語)

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16369/4b2eae5d-75f5-509a-2b0b-f5af87811219.png)

曲名やアルバム名などの情報だけではなく、iTunes に追加した日時や再生回数などの利用情報も取得できる。

## 集計する
### 色々な数をカウント
``` python
print('曲数：', len(df))
print('アルバム数：', len(df.album.unique()))
print('アーティスト数：', len(df.albumArtist.unique()))
print('総再生回数：', df.playedCount.sum())
print('総再生時間：', (df.time * df.playedCount).sum())
```

> 曲数： 7848
> アルバム数： 818
> アーティスト数： 239
> 総再生回数： 39770
> 総再生時間： 119 days 02:15:05

今までに iTunes で聞いた音楽の総合計時間は約3ヶ月らしい。自分が iTunes に移行したのが8年前なので、この8年間のうち約 3% の時間を音楽を聴きながら過ごしていることがわかる。

### 曲ごとの再生回数のヒストグラム
``` python
ax = df.playedCount[df.playedCount <= 40].hist(bins=40)
ax.set_xlabel('再生回数')
ax.set_ylabel('曲数')
plt.show()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16369/52a67bc4-4413-d220-7993-358bfe5ae0e0.png)

一度も再生したことない曲が2000曲以上ある。
ごめんて。

### 曲ごと再生回数 TOP 10
``` python
df.groupby('name') \
    .apply(lambda df: pd.Series([df.albumArtist.iloc[0], sum(df.playedCount)], index=['artist', 'playedCount'])) \
    .sort_values('playedCount', ascending=False) \
    .head(5) \
    .reset_index()
```

|  | name | artist | playedCount |
|--:|:--|:--|--:|
| 0 | ホワイトノイズ | ラグジュアリー オルゴール | 369 |
| 1 | future nova -album edit- | School Food Punishment | 137 |
| 2 | 君の知らない物語 | supercell | 131 |
| 3 | 恋の煙 | チャットモンチー | 126 |
| 4 | ソラニン | ASIAN KUNG-FU GENERATION | 126 |
| 5 | サヨナラ絶望人生 | 空想委員会 | 101 |
| 6 | 真夜中遊園地 | チャットモンチー | 97 |
| 7 | ハロウシンパシー | シナリオアート | 96 |
| 8 | 少年少女 | amazarashi | 95 |
| 9 | Cherish | 大塚愛 | 94 |

「赤ちゃんはホワイトノイズを流しておくとよく寝る」説を信じて一時期ずっと夜中にホワイトノイズを流していたら再生回数トップになってしまった。ちなみにあまり寝てくれなかった。

### アーティストごと再生回数 TOP 10
``` python
df.groupby('albumArtist')['playedCount'].sum() \
    .sort_values(ascending=False).head(10).to_frame().reset_index()
```

| albumArtist | playedCount |
|--:|:--|
| 0 | amazarashi | 3409 |
| 1 | ASIAN KUNG-FU GENERATION | 3229 |
| 2 | 米津玄師 | 1521 |
| 3 | VOCALOID | 1493 |
| 4 | 空想委員会 | 1398 |
| 5 | RADWIMPS | 1236 |
| 6 | サカナクション | 1098 |
| 7 | ヒトリエ | 943 |
| 8 | GOOD ON THE REEL | 810 |
| 9 | 凛として時雨 | 705 |

趣味がよく出ている感じがする。
「アジカンはいいぞ」と言おうとしたが amazarashi の方が上になってしまった。

### お気に入り曲の割合
``` python
sum(df['loved']) / len(df)
```

> 0.18450560652395515

iTunes には「ラブ」という機能があり、気に入った曲にはこのフラグをつけるようにしているのだけど、集計してみると5曲に1曲くらいはお気に入りフラグが立っている。自分ちょろい。

### 年ごとの開拓した曲数
``` python
ax = df.dateAdded.dt.year.value_counts().sort_index().plot.bar()
ax.set_xlabel('年')
ax.set_ylabel('曲数')
ax.set_ylim(0, 1500)
ax.set_title('年ごとの開拓した曲数')
plt.show()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16369/0df49fb6-d765-815f-e587-ac5f3237e66a.png)

(2011年は iTunes に移行した年なのでノーカン)

毎年安定して 500 曲くらい新しい曲を開拓していることがわかる。

### 今年ハマっているアーティスト
``` python
df[df.dateAdded > '2019'].groupby('albumArtist')['playedCount'].sum() \
    .sort_values(ascending=False).head(10).to_frame().reset_index()
```

|  | albumArtist | playedCount |
|--:|:--|--:|
| 0 | ヨルシカ | 133 |
| 1 | RADWIMPS | 87 |
| 2 | ヒトリエ | 63 |
| 3 | ねごと | 60 |
| 4 | サカナクション | 47 |
| 5 | BUMP OF CHICKEN | 39 |
| 6 | Base Ball Bear | 38 |
| 7 | 相対性理論 | 34 |
| 8 | ASIAN KUNG-FU GENERATION | 33 |
| 9 | 岡崎体育 | 31 |

今年は圧倒的にヨルシカにハマっている。
ヨルシカはいいぞ。

### 歌詞でワードクラウド
残念なことに iTunes の曲データにはデフォルトでは歌詞が含まれておらず、iTunes 上で歌詞を表示して歌詞をダウンロードしたことがある曲の歌詞データしか利用できない。

自分の曲データの中で歌詞が含まれていたものは 235 曲と少なかったものの、一応これでワードクラウドを作ってみた。

コードは長いので省略。(ググればいくらでもやり方はでてくる)
雑に MeCab で形態素解析して、ストップワードは NLTK (英語) + SlothLib (日本語) + 手作業でいくつか追加したものを使って、あとは [wordcloud](https://github.com/amueller/word_cloud) に投げた。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16369/741b22b6-92fc-a433-f8ad-d278fc17d4f8.png)

歌詞を投げただけあってだいぶエモっぽいワードが並んでいる。

思ったより普通な感じだったのでちょっと残念 (？)

## まとめ
JXA を使うと iTunes からデータを引っこ抜けるので便利。

集計してみると「意外とこのアーティストよく聴いてるんだな」みたいなのに気付けたりして面白いので各位やってみるといいです。