Load the data


In [1]:
import tcxparser
import pandas as pd
from datetime import datetime
from os import listdir
from os.path import join

In [2]:
def get_hr_data(tcx_path):
    tcx = tcxparser.TCXParser(tcx_path)
    hr_values = tcx.hr_values()
    time_values = [datetime.strptime(t, '%Y-%m-%dT%H:%M:%S.000Z') for t in tcx.time_values()]
    elapsed = [(tv - time_values[0]).seconds for tv in time_values]
    return pd.DataFrame(data={'elapsed': elapsed, 'hr': hr_values})

def load_data(dir_path):
    tcx_filenames = [f for f in listdir(dir_path) if f.endswith('.tcx')]
    data = [get_hr_data(join(dir_path, f)) for f in tcx_filenames]
    dates = [datetime.strptime(f.split('_')[1], '%Y%m%d') for f in tcx_filenames]
    return data, dates

In [3]:
activities, activity_dates = load_data('data')

What does the data look like?


In [4]:
first_activity = activities[0]
first_activity.head()


Out[4]:
elapsed hr
0 0 95
1 1 94
2 7 91
3 9 88
4 21 91

In [5]:
first_activity.tail()


Out[5]:
elapsed hr
482 3587 121
483 3589 121
484 3590 122
485 3591 122
486 3598 120

...and visualise the data with Bokeh


In [6]:
import bokeh
from bokeh.io import output_notebook
from bokeh.plotting import figure, show
from bokeh.palettes import Blues9, Spectral9, RdYlGn5
from bokeh.models import BoxAnnotation
from bokeh.models import Range1d

In [7]:
output_notebook()


Loading BokehJS ...

What does the first activity look like?


In [8]:
p = figure(plot_width=800, tools='box_zoom,resize,reset,hover')

p.line(first_activity.elapsed, first_activity.hr, line_width=2)

show(p)


Out[8]:

<Bokeh Notebook handle for In[8]>


In [9]:
WALKING = Spectral9[0]
JOGGING = Spectral9[1]
RUNNING = Spectral9[6]

p = figure(plot_width=800, tools='box_zoom,resize,reset,hover')

p.line(first_activity.elapsed, first_activity.hr, line_width=2)

p.add_layout(BoxAnnotation(left=0, right=300, fill_alpha=0.1, line_color=WALKING, fill_color=WALKING))
p.add_layout(BoxAnnotation(left=300, right=600, fill_alpha=0.1, line_color=JOGGING, fill_color=JOGGING))
p.add_layout(BoxAnnotation(left=600, right=3000, fill_alpha=0.1, line_color=RUNNING, fill_color=RUNNING))
p.add_layout(BoxAnnotation(left=3000, right=3300, fill_alpha=0.1, line_color=JOGGING, fill_color=JOGGING))
p.add_layout(BoxAnnotation(left=3300, right=3600, fill_alpha=0.1, line_color=WALKING, fill_color=WALKING))

show(p)


Out[9]:

<Bokeh Notebook handle for In[9]>


In [10]:
TOOLS = 'box_zoom,resize,reset,hover'
WIDTH=800
BAND_OPACITY=0.1

WALKING = Spectral9[0]
JOGGING = Spectral9[1]
RUNNING = Spectral9[6]

def plot_activity(activity):
    p = figure(plot_width=WIDTH, tools=TOOLS)
    p.line(activity.elapsed, activity.hr, line_width=2)
    return p

def add_intensity_band(p, left, right, colour):
    band = BoxAnnotation(left=left, right=right, fill_alpha=BAND_OPACITY, line_color=colour, fill_color=colour)
    p.add_layout(band)

def add_intensity_bands(p):
    add_intensity_band(p, 0, 300, WALKING)
    add_intensity_band(p, 300, 600, JOGGING)
    add_intensity_band(p, 600, 3000, RUNNING)
    add_intensity_band(p, 3000, 3300, JOGGING)
    add_intensity_band(p, 3300, 3600, WALKING)

Now all activities


In [11]:
p = figure(plot_width=WIDTH, tools=TOOLS)

for activity in activities:
    p.line(activity.elapsed, activity.hr, line_width=2)

show(p)


Out[11]:

<Bokeh Notebook handle for In[11]>

What's going on? How do they vary over time?


In [12]:
p = figure(plot_width=WIDTH, tools=TOOLS)

colours = list(Blues9); colours.reverse()

for activity in activities:
    line_colour = colours.pop()
    p.line(activity.elapsed, activity.hr, line_width=2, line_color=line_colour)

show(p)


Out[12]:

<Bokeh Notebook handle for In[12]>


In [13]:
X_LABEL = 'Elapsed Time (s)'
Y_LABEL = 'Heart Rate (BPM)'

def plot_activities_colourful(activities):
    p = figure(plot_width=WIDTH, tools=TOOLS, x_axis_label=X_LABEL, y_axis_label=Y_LABEL)
    colours = list(Blues9); colours.reverse()
    for activity in activities:
        line_colour = colours.pop()
        p.line(activity.elapsed, activity.hr, line_width=2, line_color=line_colour)
    return p

def focus(p, start, end, hr_min, hr_max):
    p.x_range = Range1d(start, end)
    p.y_range = Range1d(hr_min, hr_max)

All together now!


In [14]:
p = plot_activities_colourful(activities)
add_intensity_bands(p)
focus(p, 0, 3600, 70, 170)
show(p)


Out[14]:

<Bokeh Notebook handle for In[14]>

Zooming in on the first transition


In [15]:
p = plot_activities_colourful(activities)
add_intensity_bands(p)

p.title = 'Walking to Jogging'
focus(p, 240, 600, 80, 140)

show(p)


Out[15]:

<Bokeh Notebook handle for In[15]>


In [16]:
p = plot_activities_colourful(activities)
add_intensity_bands(p)

p.title = 'Jogging to Running'
focus(p, 540, 900, 115, 155)

show(p)


Out[16]:

<Bokeh Notebook handle for In[16]>


In [17]:
p = plot_activities_colourful(activities)
add_intensity_bands(p)

p.title = 'Running to Jogging'
focus(p, 2940, 3300, 135, 165)

show(p)