Skip to content

Instantly share code, notes, and snippets.

@azimut
Last active October 30, 2020 22:18
Show Gist options
  • Save azimut/8647bd57e57edffdde03 to your computer and use it in GitHub Desktop.
Save azimut/8647bd57e57edffdde03 to your computer and use it in GitHub Desktop.
Create an animated heat map .gif from your lastfm scrobbler history

Lastfm Heat Map gif

Imgur

Description

Use as input file the output of lastfm-export.py. If you don't want to download one you could use mine here.

heatmaps.py generate the heat maps images on the current directory.
heatmap-makegif.sh creates the final gif and these directories smalldir/outputgif/ also it does all the gif compression for you.

Usage

# ls exported_tracks.txt
exported_tracks.txt
# python2 heatmaps.py
# bash heatmap-makegif.sh
# display smalldir/outputgif/beat_o3.gif
#!/bin/bash
hash convert || { echo 'Error: convert was not found on PATH, please install ImageMagick'; exit 1; }
hash gifsicle || { echo 'Warning: gifsicle not found on PATH, you might want to install it to obtain a optmized gif file.'; }
set -x
rm -rf smalldir
mkdir smalldir/
for i in ./beat*; do
convert $i -resize %40 smalldir/${i##*/}
done
cd smalldir && {
mkdir outputgif
# "fix" bug on the python script that generates wrong heat maps...by deleting the latest ones
ls beat* | sort -n -k1.5,1.7n | tail -n 6 | xargs -n1 rm -v
convert \
-crop -20-20 \
+repage \
-loop 0 \
-delay 25 \
-layers OptimizeTransparency \
-ordered-dither o8x8,14 \
+map \
$(ls beat* | sort -n -k1.5,1.7n) \
outputgif/beat.gif
cd outputgif && {
gifsicle -O3 < beat.gif > beat_o3.gif
}
}
#!/usr/bin/env python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# df = pd.read_csv('./exported_tracks.new.txt.gz', compression='gzip',
df = pd.read_csv('./exported_tracks.txt',
sep='\t',
header=None,
names=['date','tr_name','ar_name','al_name',
'tr_duration','tr_listeners',
'tr_playcount',
'tr_t0','tr_t1','tr_t2','tr_t3','tr_t4',
'tr_mbid','ar_yf','ar_yt',
'ar_ctry','ar_listeners','ar_playcount',
'ar_t0','ar_t1','ar_t2','ar_t3','ar_t4',
'ar_mbid',
'al_releasedate','al_playcount',
'al_listeners','al_mbid'],
parse_dates=False)
df.index = pd.to_datetime(df.pop('date'), unit='s')
df.tr_duration = df.tr_duration.apply(lambda x: x/60/1000)
df['al_releasedate'] = df.al_releasedate.apply( lambda x: np.nan if x == ' ' else x)
df['al_releasedate'] = pd.to_datetime(df.al_releasedate, format=' %d %b %Y, %H:%M')
b = df.groupby(pd.TimeGrouper('d')).ar_t0.apply(lambda x: x.value_counts().head(30)).unstack(level=1).fillna(0)\
.add(df.groupby(pd.TimeGrouper('d')).ar_t1.apply(lambda x: x.value_counts().head(30)).unstack(level=1).fillna(0), fill_value=0)\
.add(df.groupby(pd.TimeGrouper('d')).ar_t2.apply(lambda x: x.value_counts().head(30)).unstack(level=1).fillna(0), fill_value=0)\
.add(df.groupby(pd.TimeGrouper('d')).ar_t3.apply(lambda x: x.value_counts().head(30)).unstack(level=1).fillna(0), fill_value=0)\
.add(df.groupby(pd.TimeGrouper('d')).ar_t4.apply(lambda x: x.value_counts().head(30)).unstack(level=1).fillna(0), fill_value=0)
b_t = b.T
b_t['svalue'] = b_t.sum(axis=1).values
b_t_plot = b_t.sort_index(by='svalue', ascending=True).drop('svalue',axis=1)[-30:]#.sort_index()
current_plot = 0
cols_max = len(b_t_plot.columns)
for slice_value_tip in xrange(0,len(b_t_plot.columns),5):
slice_value_top = slice_value_tip + 30
df_temp = b_t_plot.iloc[:,slice_value_tip:slice_value_top]
print slice_value_tip, slice_value_top, cols_max
fig, ax = plt.subplots()
heatmap = ax.pcolor(df_temp,
cmap=plt.cm.YlGn,
alpha=0.8,
vmin=0,
vmax= b_t_plot.max().max() / 4)
fig = plt.gcf()
fig.set_size_inches(7,7)
# turn off the frame
ax.set_frame_on(False)
# put the major ticks at the middle of each cell
ax.set_yticks(np.arange(df_temp.shape[0])+0.1, minor=False)
ax.set_xticks(np.arange(df_temp.shape[1])+0.5, minor=False)
#ax.set_xticks(minor=False)
# want a more natural, table-like display
ax.invert_yaxis()
ax.xaxis.tick_top()
# Set the labels
# note I could have used nba_sort.columns but made "labels" instead
ax.set_xticklabels(df_temp.columns.map(lambda x: x.year), minor=False, size='small')
ax.set_yticklabels(df_temp.index, minor=False, va='top',size='small')
ax.grid(False)
# rotate the
plt.xticks(rotation=90)
# Turn off all the ticks
ax = plt.gca()
for t in ax.xaxis.get_major_ticks():
t.tick1On = False
t.tick2On = False
for t in ax.yaxis.get_major_ticks():
t.tick1On = False
t.tick2On = False
fig.savefig('./beat' + str(current_plot) + '.png')
current_plot = current_plot + 1
del df_temp
plt.clf()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment