How to plot time at night with an axis ranging from 20 (8 pm) to 12 (am) python

Viewed 155

I am trying to plot the time of the day I went to bed each day.

I was wondering if there exists an elegant solution to have my y-axis (time of the day) go from 20 h (or 10 PM) to 12 h or (12 PM)

Essentially, my ticks would be [20, 21, 22, 23, 00, 01, 02, ..., 09, 10, 11, 12]

Here is a part of my database :

Date Start End
2019-09-21 23:26:01 05:40:01
2019-09-22 22:45:42 05:58:04
2019-09-23 22:56:40 05:55:23
2019-09-24 22:35:47 06:05:07
2019-09-25 00:23:13 06:03:08

Here is what I'd like to do :

enter image description here

And here's what I tried :

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import dates as mdates

data = pd.read_csv("sleepdata.csv", sep=";")

plt.plot(mdates.date2num(data.Date), mdates.datestr2num(data.Start))

Output

1 Answers

I suppose you have data stored in a csv file which can be read with pandas:

df = pd.read_csv(r'data/data.csv')
         Date     Start       End
0  2019-09-21  23:26:01  05:40:01
1  2019-09-22  22:45:42  05:58:04
2  2019-09-23  22:56:40  05:55:23
3  2019-09-24  22:35:47  06:05:07
4  2019-09-25  00:23:13  06:03:08

First of all you have to make some changes to the dataframe.
On y axis you want time from two different days: the evening of the day when you start sleeping and the morning of the next day. However 'Start' and 'End' columns don't contain info regarding date, only time.
Moreover, you want to plot time of different days. So, if I derive the date from 'Date' column, on y axis you will have data from 2019-09-21 20:00:00 to 2019-09-25 12:00:00 (or the last date in your data) and you won't be able to compare time only, because each time belongs to different day. So you have to impute a date to 'Start' and 'End' columns and you have to chose always the same date, in order to be able to compare time only, regardless of the date. This is the reason why I impute the dates 2000-01-01 or 2000-01-02 to 'Start' and 'End' columns through start and end functions.
Then you have to convert 'Date' column type from str to datetime.
At this point you have this dataframe:

        Date               Start                 End
0 2019-09-21 2000-01-01 23:26:01 2000-01-02 05:40:01
1 2019-09-22 2000-01-01 22:45:42 2000-01-02 05:58:04
2 2019-09-23 2000-01-01 22:56:40 2000-01-02 05:55:23
3 2019-09-24 2000-01-01 22:35:47 2000-01-02 06:05:07
4 2019-09-25 2000-01-02 00:23:13 2000-01-02 06:03:08

Pay attention to the date of 'Start' and 'End' columns: if 'Start' hour is later than midday, they you have gone to bed the 1st day, on 2000-01-01, otherwise the next day, on 2000-01-02. In any case you always wake up on the 2nd day, on 2000-01-02.
Now you can plot the dataframe day by day. I use a df.iterrows loop, which isn't the best, I am open to suggestion to improve the plotting process.
Finally you have to adjust x and y axis format and labels.

Complete Code

import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
import matplotlib.dates as md


df = pd.read_csv(r'data/data.csv')


def start(x):
    H, M, S = list(map(int, x['Start'].split(':')))
    Y, m, d = 2000, 1, 1
    if H > 12:
        return datetime(year = Y, month = m, day = d, hour = H, minute = M, second = S)
    else:
        return datetime(year = Y, month = m, day = d, hour = H, minute = M, second = S) + timedelta(days = 1)

def end(x):
    H, M, S = list(map(int, x['End'].split(':')))
    Y, m, d = 2000, 1, 1
    return datetime(year = Y, month = m, day = d, hour = H, minute = M, second = S) + timedelta(days = 1)


df['Start'] = df.apply(start, axis = 1)
df['End'] = df.apply(end, axis = 1)
df['Date'] = pd.to_datetime(df['Date'], format = '%Y-%m-%d')


fig, ax = plt.subplots()

for i, row in df.iterrows():
    ax.plot([row['Date'], row['Date']], [row['Start'], row['End']], linewidth = 10, color = 'blue')

ax.xaxis.set_major_locator(md.DayLocator(interval = 1))
ax.xaxis.set_major_formatter(md.DateFormatter('%Y-%m-%d'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation = 90)


ax.yaxis.set_major_locator(md.HourLocator(interval = 1))
ax.yaxis.set_major_formatter(md.DateFormatter('%H:%M'))
ax.set_ylim(datetime(year = 2000, month = 1, day = 1, hour = 20, minute = 0, second = 0),
            datetime(year = 2000, month = 1, day = 2, hour = 12, minute = 0, second = 0))

plt.show()

enter image description here

Related