The science behind the data N°1 — Clock representation of 24-hour cyclic data

Photo by Phil Desforges on Unsplash

Time is by nature cyclical, whether we consider it in seconds, minutes, hours, days, or months. Therefore, choosing the right representation to display time data could be quite challenging. In this article, we will present a time representation that aims to easily spot and explain repetitive patterns, such as sleep parameters. In particular, we will explore in detail the case of daily cyclical data and the use of a clock representation.

What are daily cyclical data?

Daily cyclical data — or 24-hour time data — are time points going from midnight to almost midnight (note: this precision is important, and will be discussed later). They are often expressed in a HH-MM-SS format (Hours-Minutes-Seconds.). We mainly record those data through daily questionnaires, such as sleeping routine, or eating habits.

Choosing the right representation, a.k.a. the Clock

When considering daily time data, two representations are possible, either a linear timeline or a circular one.

However, the cycle representation provides more insights and advantages than the linear one.

First, it allows to easily flag patterns. As the time measures are plotted and overlap each other, patterns are visually identifiable when present. In our clock figure, we can see that people who sleep after 1 a.m., wake up after 9 a.m.

24-hour clock representation of sleep information.

Then, it prevents a major issue encountered with linear representation, which is the midnight day switch (midnight to almost midnight format) that results in biased distance comparisons (a deeper explanation can be found in the next section).

Finally, on a daily basis, a clock circle representation is an international standard and looks familiar and easily understandable. Thus, choosing this representation ensures that it is understood by a very large audience.

Things are getting tricky now, since a preliminary-step is required in order to convert the daily format data into a trigonometric format to achieve a clock representation.

Why a trigonometric conversion?

We decided to opt for a trigonometric data conversion since those data naturally fits into a circle representation, and they can be expressed on a clock. More importantly, this trigonometric conversion triggers the day switch, as explained in the following example.

First, imagine you want to compute the distance between two different persons' bedtime 🛏. The first indicates a bedtime at “23:59:59” and the second one “00:00:00”. From a data perspective, a day goes from midnight to 23:59:59, so the difference between the bedtime of the two users is 23 hours 59 minutes and 59 seconds, not 1 second as we might think! This doesn’t fit the reality unless we consider the same comparison between the trigonometric values, “23:59:59” becomes cos = 0.9999 and sin = 0.0001 and “00:00:00” becomes cos = 1 and sin = 0. The differences are now minimal and closer to the expectation.

To complete the trigonometric transformation, those simple few steps can be followed:

1. Convert time “HH:MM:SS” in seconds.

2. Divide the obtained time by the total duration of a day in seconds which is 86400.

3. Multiply it by 2π.

4. Apply cos and sin to the values.

5. You’re done !

For more convenience, here is a code snippet in python of the detailed trigonometric transformation.

import pandas as pd
import math
def extract_time_angle_positions(data: pd.Series) -> pd.Series:
""" Transform time responses to angles between 0 and 2pi."""
return 2 * math.pi / 86_400 * data
def extract_time_positions(data: pd.Series) -> pd.DataFrame:
return pd.DataFrame(
{
'res_cos': data.apply(math.cos),
'res_sin': data.apply(math.sin)
}
)

Clock representation, or how to do a nice visualization.

Last step will be to draw the clock 🕔 !

The one in this article has been achieved using plotly library. If you are not familiar with it, Plotly is a visualization library available for python and R languages, which allows interactive data plotting construction. However, the following steps could be followed in any other programming language.

1. Draw a circle that will shape the clock of radius 2.

2. Add the hours from 0 to 24 clockwise along the circle.

3. Transform current cyclic counterclockwise data into clockwise values.

4. Shift the data to align midnight with the uppermost point of the circle.

In our case, we also :

5. Add a triangular shape, which represents the amount of time spent between two time features of interest, i.e., sleep time and wake-up time.

6. You’re done again!!

For more convenience, just after is given a code snippet in python of the detailed clock visualization.

Now you know everything about 24-hour cyclic data representation developed, in our case, for a clinical study regarding depression and sleep disorders.

Hope you find this article useful, if so leave a comment and see you for another one !

import numpy as np
import plotly.graph_objects as go
def plot_sleep_analysis(data):
fig = go.Figure()
sleep_sin = data['sleep_sin']
sleep_cos = data['sleep_cos']
wakeup_sin = data['wakeup_sin']
wakeup_cos = data['wakeup_cos']
# Transform clockwise + shift to midnight data
sleep_angle = np.arctan2(sleep_sin, sleep_cos)
fig.add_trace(
go.Scatter(
x=[-np.cos(sleep_angle + np.pi / 2)],
y=[np.sin(sleep_angle + np.pi / 2)],
mode='markers',
marker=dict(size=10),
)
)

wakeup_angle = np.arctan2(wakeup_sin, wakeup_cos)
fig.add_trace(
go.Scatter(
x=[-np.cos(wakeup_angle + np.pi / 2)],
y=[np.sin(wakeup_angle + np.pi / 2)],
mode='markers',
marker=dict(symbol='cross', size=10),
hovertemplate='cos: %{x} ' + ' sin: %{y} ',
)
)
# Fit the Clock into perfect circle size illustration
fig.update_xaxes(range=[-1.2, 1.2], visible=False)
fig.update_yaxes(range=[-1.2, 1.2], visible=False)
fig.update_layout(
autosize=False,
width=700,
height=600
)
# Add the 0 to 24 hours around a circle shape
fig.add_shape(
type="circle",
x0=-1, y0=-1, x1=1, y1=1,
line_color="lightgrey",
opacity=0.4,
layer='below'
)
pos = np.linspace(
np.pi / 2,
np.pi / 2 - 2 * np.pi,
24,
endpoint=False
)
for val in range(24):
x_val = np.cos(pos[val])
y_val = np.sin(pos[val])
if abs(y_val) == 1:
x_shift = 0
else:
x_shift = np.sign(x_val) * 10
if abs(x_val) == 1:
y_shift = 0
else:
y_shift = np.sign(y_val) * 10
fig.add_annotation(
x=x_val,
y=y_val,
text=val,
showarrow=False,
xshift=x_shift,
yshift=y_shift
)
fig.show()

--

--

Angéline Plaud, PhD in computer science
Ad Scientiam Stories

I’m a data scientist in Ad Scientiam, building innovative smartphone solutions certified medical devices, providing a better understanding of pathologies.