How to plot a time series without calculating a time data column in Python

Viewed 439

I have a dataframe that consists of only one column of data (3.000.000 samples) taken over a period of 10 hours. One can assume that the sample frequency is constant.

Is it possible to create a time series line plot without having to create another column with some sort of timestamp/datetime?

I know that:

-Y axis should be the value of the sample

-X axis should be the time. The origin equals 0 and the last mark of the axis is 10 h.

All the examples I saw assume that there is a column with a timestamp but I guess it should not be necessary to calculate it if I know the period (0 h - 10 h) and assume a constant frequency for the data/samples.

2 Answers

If the data are evenly spaced, we can simply plot against the index and just change the label of the ticks using the FuncFormatter class provided by matplotlib, thereby avoiding extensive calculations to determine the time for each data point:

from matplotlib import pyplot as plt
from matplotlib.ticker import LinearLocator, FuncFormatter
from datetime import datetime as dt

#fake data generation
import numpy as np
np.random.seed(123)
n=3000
arr = np.random.random(n)
last_index = len(arr)-1
#time in h
total_time = 10 

#plot the array against the index
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(arr)
ax.set_xlim(0, last_index)
ax.tick_params(axis="x", labelrotation=90)

#formatter function to convert the index value into a time string
def ind2date(x, pos):
    s = dt.fromtimestamp(total_time * 3600 * x / last_index)
    return s.strftime("%H:%M:%S.%f")

#generates 5 evenly distributed ticks, even if we change the view by zooming in
ax.xaxis.set_major_locator(LinearLocator(5))
ax.xaxis.set_major_formatter(FuncFormatter(ind2date))

plt.tight_layout()
plt.show()

Sample output before... enter image description here ...and after zooming enter image description here

This can be adapted to other time ranges - at the moment, you cannot zoom out to negative time values below zero.

If I understood you right what you want is to have a x-axis displayed as a time instead of just a number. This can be done by assigning a corresponding time to each data point. The problem is that matplotlib only works with datetime objects. What you can do is to assign corresponding a datetime for each data point you have and set year, month and day to any value, like datetime(2021, 1, 1, 10, 12, 56, 547). Also, since data period in your sample is smaller than one second, you need to provide microseconds parameter as well. The trick is to set a format of x-axis as %H:%M:%S.%f and simply ignore year-month-day information. The tick labels may be too long, therefore it would be nice to rotate them to avoid overlap. Below is a sample of a code to do that:

import matplotlib as mpl
from random import random
import matplotlib.pyplot as plt
from datetime import datetime

import matplotlib.dates as mdates


# This is needed to avoid overflow error
#   OverflowError: Exceeded cell block limit (set 'agg.path.chunksize' rcparam)
mpl.rcParams['agg.path.chunksize'] = 10000


def get_hms(value_number, total_hours, total_samples):
    # Relative value in terms of hours
    val = value_number * total_hours / (total_samples - 1)

    # Get the number of hours:
    h = int(val)

    # Reminder in minutes
    rem = (val - h) * 60

    # Get the number of minutes
    m = int(rem)

    # Reminder in seconds
    rem = (rem - m) * 60

    # Get number of seconds
    s = int(rem)

    # Reminder in microseconds
    mcs = int((rem - s) * 1000000)

    # Must return datetime object because matplotlib doesn't
    # like time objects. The first three arguments can be any
    # without reasonable time frame.
    # I just set them to be Jan 1, 2021
    return datetime(2021, 1, 1, h, m, s, mcs)


# Total number of data points
n = 3000000

# Time length in hours
th = 10

# Generate fake data where for each y-value there is corresponding x-value
# which is a datetime object
x_data = [get_hms(i, th, n) for i in range(n)]
y_data = [random() for _ in range(n)]

# Expect to see
#   2020-01-01 00:00:00    2020-01-01 00:00:00.012000   2020-01-01 10:00:00
print(x_data[0], x_data[1], x_data[-1])

# Specify boundaries for your exes. Need this because
# x-ticks are too long and may not fit in the area under the chart
ax_left, ax_bottom, ax_width, ax_height = 0.1, 0.2, 0.8, 0.8

# Create a figure
fig = plt.figure(figsize=(13, 7))

# Add axes
ax = plt.axes([ax_left, ax_bottom, ax_width, ax_height])

# Let matplotlib know that x-axis is a date axis
ax.xaxis_date()

# Plot the data
ax.plot(x_data, y_data, '-')

# Now, since we are not interested in Year, Month, and Day, let's ignore them
# and display only Hour (%H), Minute (%M), second (%S) and microseconds (%f)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S.%f'))

# X tick labels will be very long and may overlap if displayed horizontally
# Therefore, rotate them by 90 degrees to make them vertical
ax.tick_params(axis='x', rotation=90)

plt.show()

If you zoom the resulting image you can see that the granularity of x-axis labels is below 1 second: enter image description here

Related