{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib widget" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, timedelta\n", "import calendar\n", "from glob import glob\n", "import json\n", "import pathlib\n", "\n", "import numpy as np\n", "import pandas as pd\n", "\n", "import matplotlib.pyplot as plt\n", "import seaborn as sns\n", "\n", "from ipywidgets import Button, FileUpload, GridspecLayout" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.ioff()\n", "\n", "fig = plt.figure(figsize=(9, 3))\n", "fig.set_tight_layout(dict(pad=0))\n", "\n", "fig.canvas.toolbar_visible = False\n", "fig.canvas.header_visible = False\n", "fig.canvas.footer_visible = False\n", "\n", "fig.patch.set_facecolor('white')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def update_figure(path):\n", " global ax\n", " global fig\n", "\n", " data = []\n", "\n", " for file in sorted(glob(str(pathlib.Path(path) / 'StreamingHistory*.json'))):\n", " with open(file) as fobj:\n", " data.extend(json.load(fobj))\n", "\n", " streaming_history = pd.DataFrame(data)\n", " streaming_history['endTime'] = pd.to_datetime(streaming_history['endTime'])\n", " streaming_history[\"date\"] = streaming_history[\"endTime\"].dt.floor('d')\n", "\n", " by_date = streaming_history.groupby(\"date\")[[\"trackName\"]].count()\n", " by_date = by_date.sort_index()\n", "\n", " by_date[\"weekday\"] = by_date.index.weekday\n", " by_date[\"week\"] = by_date.index.week\n", "\n", " week = 0\n", " prev_week = by_date.iloc[0][\"week\"]\n", " continuous_week = np.zeros(len(by_date)).astype(int)\n", " sunday_dates = []\n", " for i, (_, row) in enumerate(by_date.iterrows()):\n", " if row[\"week\"] != prev_week:\n", " week += 1\n", " prev_week = row[\"week\"]\n", " continuous_week[i] = week\n", " by_date[\"continuous_week\"] = continuous_week\n", "\n", " songs = np.full((7, continuous_week.max()+1), np.nan)\n", "\n", " for index, row in by_date.iterrows():\n", " songs[row[\"weekday\"]][row[\"continuous_week\"]] = row[\"trackName\"]\n", "\n", " min_date = streaming_history[\"endTime\"].min()\n", " first_monday = min_date - timedelta(min_date.weekday())\n", " mons = [first_monday + timedelta(weeks=wk) for wk in range(continuous_week.max())]\n", " x_labels = [calendar.month_abbr[mons[0].month]]\n", " x_labels.extend([\n", " calendar.month_abbr[mons[i].month] if mons[i-1].month != mons[i].month else \"\" \n", " for i in range(1, len(mons))])\n", "\n", " y_labels = [\"Mon\", \"\", \"Wed\", \"\", \"Fri\", \"\", \"Sun\"]\n", "\n", " songs = np.nan_to_num(songs)\n", "\n", " plt.clf()\n", "\n", " ax = plt.subplot()\n", "\n", " ax.xaxis.tick_top()\n", " ax.tick_params(axis='both', which='both', length=0, labelsize=7)\n", " ax.set_facecolor(\"white\")\n", "\n", " ax.set_title('{:,} songs listened in the last year'.format(int(np.sum(songs))), loc='left')\n", "\n", " sns.heatmap(songs, linewidths=2, linecolor='white', square=True,\n", " cmap=\"Greens\", cbar=True, cbar_kws=dict(orientation='horizontal', shrink=0.4),\n", " vmin=0, vmax=np.max(songs), ax=ax)\n", "\n", " ax.set_yticklabels(y_labels, rotation=0)\n", " ax.set_xticklabels(x_labels, ha=\"left\")\n", "\n", " fig.canvas.draw()\n", " fig.canvas.flush_events()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Your year on Spotify!\n", "\n", "This work was inspired by [Más visualización con tus datos de Spotify con Python](https://tacosdedatos.com/mas-data-viz-con-spotify-python), also available [in english](https://dev.to/fferegrino/plotting-your-spotify-data-2km8).\n", "\n", "How does it work?\n", "\n", "First request your data on Spotify following this link: https://www.spotify.com/account/privacy\n", "\n", "Then come back to this dashboard and upload all the `StreamingHistory*.json` files (don't worry, nothing is saved permanently onto the server, at least not if you see this application from https://voila-gallery.org) and click on the `Show` button!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layout = GridspecLayout(20, 3, height='600px')\n", "\n", "def on_sample_data_click(event):\n", " update_figure('sample_data')\n", "\n", "def on_upload(change):\n", " value = change['new']\n", "\n", " for filename in value:\n", " with open(filename, 'wb') as outfile:\n", " outfile.write(value[filename]['content'])\n", "\n", "def show(event):\n", " update_figure('.')\n", "\n", "load_sample_data = Button(description='Show sample data', layout=dict(width='auto', height='auto'))\n", "load_sample_data.on_click(on_sample_data_click)\n", "\n", "upload_data = FileUpload(description='Upload your data:', layout=dict(width='auto', height='auto'))\n", "upload_data.observe(on_upload, 'value')\n", "\n", "plot = Button(description='Show', button_style='success', layout=dict(width='auto', height='auto'))\n", "plot.on_click(show)\n", "\n", "\n", "layout[0, 0] = load_sample_data\n", "layout[1, 0] = upload_data\n", "layout[1, 1] = plot\n", "\n", "layout[3:, :] = fig.canvas\n", "\n", "layout" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.2" } }, "nbformat": 4, "nbformat_minor": 4 }