42
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQL+Markdownだけでデータ可視化できるOSS Evidenceを使ってPerfumeの楽曲分析をしてみる

Posted at

BIをコード管理したくないですか?私はしたいです。
BI as Codeを謳うOSSがあるようなので、Get Startedしてみます。

環境構築

公式ドキュメントを見ると、VSCodeのExtensionを入れて開発することを推奨しているようです。

2.Open the Command Palette (Ctrl/Cmd + Shift + P) and enter Evidence: New Evidence Project
3.Click Start Evidence in the bottom status bar

拡張機能のインストール後、2クリックでローカルサーバーが起動しサンプルページが立ち上がりました。

外部データを投入する

せっかくなのでサンプルデータ以外のデータを投入してみます。
初期のサンプルデータは、DuckDBのテーブルとして配布されているようです。

ローカル環境として手軽そうなので、同じようにDuckDBを使ってみましょう。

無料で取得できる面白そうなデータとして、SpotifyのAPIから楽曲データを引っ張ってきます。

Spotify APIはOAuth2.0認証方式が使えるため、データ基盤支援SaaSのTROCCO®を使うことで画面上から(プログラミング無しで)データ取得処理を作成できます。無料プランでも利用可能です。

OAuth2.0の接続設定方法はこちら

いくつかのAPIを叩いて、必要そうなデータをparquet形式でS3に貯めていきます。Spotify APIの仕様にあわせ、以下のようなER図を想定します。

スクリーンショット 2024-10-25 20.11.44.png

今回はPerfumeの全アルバムと、それに紐づく全曲のデータを収集しました。

S3上のデータをローカルのDuckDBに保存します。

duckdb spotify.duckdb
--
CREATE SECRET (
    TYPE S3,
    KEY_ID 'xxx',
    SECRET 'xxx',
    REGION 'xxx'
);

CREATE TABLE albums AS SELECT * FROM read_parquet("s3://{バケット名}/{path}/*.parquet");

sources/ 以下のディレクトリにあるサンプルをコピーして、connection.yaml ファイルと接続用SQLのファイルを配置します。データマートのロジックもここに入れてしまいましょう。

sources/spotify/mart_tracks.sql
SELECT
  t.*,
  f.tempo,
  f.danceability,
  f.duration_ms,
  (f.duration_ms / 1000)::INTEGER AS duration_s,
  a.name AS album_name,
  a.jacket_url,
  a.release_date AS release_datetime,
  a.release_date::DATE AS release_date,
  td.popularity,
  dense_rank() OVER (ORDER BY a.release_date) AS album_number
FROM tracks t
LEFT JOIN audio_features f
  ON t.id=f.id
LEFT JOIN albums a
  ON t.album_id=a.id
LEFT JOIN track_details td
  ON t.id=td.id

可視化する

pages/index.md のサンプルを参考に、Markdownのページを書いてみます。
全楽曲のテンポ(BPM: Beats Per Minute)と人気度を散布図としてプロットしてみました。

index.md
---
title: Perfume's Insights
---
## Audio Features

```sql tracks
  SELECT * FROM spotify.mart_tracks
```

### Popularity x Tempo(BPM)

<ScatterPlot 
  data={tracks}
  x=tempo
  y=popularity
  series=name
/>

シンプルなマークダウンとSQLの記述で、以下のようなグラフを表現できます。

スクリーンショット 2024-10-29 16.25.05.png

縦軸に人気度を取ったのは深い意味はなく、いい感じに分散させたかっただけです。
ダンスミュージックなのでBPM 120〜150 あたりに集中しているのは予想通りですが、
ある程度キリのいい数値で縦が揃っているのが印象的です。

110, 120, 130, 135, 140, 145 あたりにプロットが集中していますが、
最も曲数が多いのは128でした。
これは何か意図的なものか?と思いググってみると、以下のような記事がヒットしました。

「機械で作ってる感」「折角だから」と意図的に128を使用していることがわかります。

逆に、キリのよい数値ではない楽曲は少数なので目立ちます。

スクリーンショット 2024-10-29 17.47.28.png

例えばBPM117の「微かなカオリ」ですが、歌詞の内容としては「うっすら芽生えた淡い恋心を隠しているけど、本当は気づいてほしい」みたいな微妙な気持ちを歌っているので、敢えてキリのいい数値を避けた、というのがあるのかもしれません。(これは裏付けがないので妄想です。)

また、アルバム発売順に曲の長さやテンポの平均を取って並べてみました。
サブスク等の影響により楽曲の短尺化が進んでいるというような一般論がありますが、Perfumeでも概ねそのような傾向が見えます。
また、テンポについても音楽シーン全体でスロー化しているらしいという言説がありますが、こちらも同じような傾向が見て取れました。

詳しく掘り下げたい方は、ぜひ上記手順でデータ取得して遊んでみてください。

子ページのテンプレートを作る

Template Pages機能を使うと、一覧→詳細 のような子ページを簡単に実装できます。
リンクに含まれるパラメータは変数として参照できます。

${params.album} の部分で、アルバムの識別子を参照してフィルタ条件に含めています。

[album].md
```sql tracks
  SELECT * FROM spotify.mart_tracks
    WHERE album_id = '${params.album}'
    ORDER BY track_number
```

<DataTable data={tracks} rows=all>
  <Column id=track_number title=# />
  <Column id=name />
  <Column id=duration_s contentType=colorscale />
  <Column id=tempo contentType=colorscale />
  <Column id=popularity contentType=colorscale />
  <Column id=danceability contentType=colorscale />
</DataTable>

スクリーンショット 2024-10-29 17.04.29.png

公開する

npm run build で静的ページが出力されるので、S3 + CloudFrontなどで簡単に公開が可能です。

また、公開ページであればホスティングサービスのEvidence Cloudを使うと、Gitリポジトリ連携設定をするのみで無料で公開が可能です。

今回作成したサンプルページは以下で公開しました。

まとめ

SQLとMarkdownのみの記述でシンプルなデータ可視化を実現することができました。
GUI操作がないため変更履歴を管理しやすく、実装者に求める技術的ハードルを極力抑えながらデータ活用を推進することができそうです。

まだ日本語の情報が少ないツールですが、非常に手軽で使い勝手よく、今後に期待したいOSSでした。

42
33
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?