Search code examples
pythonnumpymatplotlibscipyinterpolation

Interpolating a time series with interp1d using only numpy


I want to plot a time series with numpy and matplotlib, using markers for the exact points, and interpolation. Basically this (data is dummy, but functionality is the same, note that distance between time-points may vary):

import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt

T = [
    np.datetime64('2020-01-01T00:00:00.000000000'),
    np.datetime64('2020-01-02T00:00:00.000000000'),
    np.datetime64('2020-01-03T00:00:00.000000000'),
    np.datetime64('2020-01-05T00:00:00.000000000'),
    np.datetime64('2020-01-06T00:00:00.000000000'),
    np.datetime64('2020-01-09T00:00:00.000000000'),
    np.datetime64('2020-01-13T00:00:00.000000000'),
]
Z = [543, 234, 435, 765, 564, 235, 345]

fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot()
ax.plot(T, Z, 'o-')

enter image description here

However, the interpolation done here is just connecting the points. I want to include spline interpolation and other kinds using scipy's interp1d. So, I tried replacing the last line with the following:

ax.plot(T,Z, 'o')
ax.plot(T,interp1d(T, Z)(T), '-')

and I get the following error:

UFuncTypeError: ufunc 'true_divide' cannot use operands with types dtype('float64') and dtype('<m8[ns]')

Reading this answer, I read that during interpolation I should divide T by np.timedelta64(1, 's'), like this:

ax.plot(T,Z, 'o')
ax.plot(T,interp1d(T/np.timedelta64(1, 's'))(T), '-')

however, I get an even weirder error:

ufunc 'true_divide' cannot use operands with types dtype('<M8[ns]') and dtype('<m8[s]')

What should I do?


Solution

  • The data type of any element in T is np.datetime64 and not np.timedelta64.

    Thus, convert the dtype of all elements of T to np.timedelta64 by creating a numpy array with datatype m:

    T = np.array(
        np.datetime64('2020-01-01T00:00:00.000000000'),
        np.datetime64('2020-01-02T00:00:00.000000000'),
        np.datetime64('2020-01-03T00:00:00.000000000'),
        np.datetime64('2020-01-05T00:00:00.000000000'),
        np.datetime64('2020-01-06T00:00:00.000000000'),
        np.datetime64('2020-01-09T00:00:00.000000000'),
        np.datetime64('2020-01-13T00:00:00.000000000'),
        dtype='m')
    

    Then, as the documentation suggests, we have to pass x and y that are convertible to float like values to scipy.interpolate.interp1d to get a interpolation function. We'll use a method suggested in this answer to do that:

    # Get an interpolation function f
    f = scipy.interpolation.interp1d(x=T/np.timedelta64(1, 's'), y=Z)
    

    Finally, we can use the interpolated function as follows for plotting:

    ax.plot(T, f(T/np.timedelta64(1, 's'), '-')
    

    Combining everything, we get the following output:

    enter image description here

    The code that can reproduce the image:

    import numpy as np
    from scipy.interpolate import interp1d
    import matplotlib.pyplot as plt
    
    T = np.array([
        np.datetime64('2020-01-01T00:00:00.000000000'),
        np.datetime64('2020-01-02T00:00:00.000000000'),
        np.datetime64('2020-01-03T00:00:00.000000000'),
        np.datetime64('2020-01-05T00:00:00.000000000'),
        np.datetime64('2020-01-06T00:00:00.000000000'),
        np.datetime64('2020-01-09T00:00:00.000000000'),
        np.datetime64('2020-01-13T00:00:00.000000000'),
    ], dtype='m')
    
    Z = [543, 234, 435, 765, 564, 235, 345]
    
    fig = plt.figure(figsize=(8,6))
    ax = fig.add_subplot()
    ax.plot(T, Z, 'o')
    
    f = interp1d(x=T/np.timedelta64(1, 's'), y=Z)
    
    ax.plot(T, f(T/np.timedelta64(1, 's')), '-')
    plt.show()