Characterizing time series using recurrence plot

In this article, I will show how to use Recurrence Plots to describe different types of time series. We will look at various simulated time series with 500 data points. We can visually characterize a time series by visualizing its recurrence plot and comparing it to other known recurrence plots of different time series.

Recursive graph

Recurrence Plots (RP) is a method for visualizing and analyzing time series or dynamic systems. It converts time series into graphical representations in order to analyze recurring patterns and structures in time series. Recurrence Plots are useful especially when there are periodicity, recurring events, or correlation structures in the time series data.

The basic principle of Recurrence Plots is to measure the similarity between points in a time series. If the distance between two time points is less than a given threshold, a point is plotted in the Recurrence Plot indicating that there is recurrence between the two time points. These points form an image on a two-dimensional plane.

 import numpy as np
 import matplotlib.pyplot as plt
 
 def recurrence_plot(data, threshold=0.1):
     """
     Generate a recurrence plot from a time series.
 
     :param data: Time series data
     :param threshold: Threshold to determine recurrence
     :return: Recurrence plot
     """
     # Calculate the distance matrix
     N = len(data)
     distance_matrix = np.zeros((N, N))
     for i in range(N):
         for j in range(N):
             distance_matrix[i, j] = np.abs(data[i] - data[j])
 
     # Create the recurrence plot
     recurrence_plot = np.where(distance_matrix <= threshold, 1, 0)
 
     return recurrence_plot

The code above creates a binary distance matrix that takes the value of time series i and j to be 1 if their values are within 0.1 (threshold), and 0 otherwise. The resulting matrix can be viewed as an image.

White noise

Next we will visualize white noise. First, we need to create a series of simulated white noise:

 # Set a seed for reproducibility
 np.random.seed(0)
 
 # Generate 500 data points of white noise
 white_noise = np.random.normal(size=500)
 
 # Plot the white noise time series
 plt.figure(figsize=(10, 6))
 plt.plot(white_noise, label='White Noise')
 plt.title('White Noise Time Series')
 plt.xlabel('Time')
 plt.ylabel('Value')
 plt.legend()
 plt.grid(True)
 plt.show()

Recursion plots provide an interesting visualization of this white noise. For any kind of white noise, the plot looks the same:

 # Generate and plot the recurrence plot
 recurrence = recurrence_plot(white_noise, threshold=0.1)
 
 plt.figure(figsize=(8, 8))
 plt.imshow(recurrence, cmap='binary', origin='lower')
 plt.title('Recurrence Plot')
 plt.xlabel('Time')
 plt.ylabel('Time')
 plt.colorbar(label='Recurrence')
 plt.show()

A noisy process can be visualized. You can see that the diagonal lines in the picture are always black.

Random walk

Next let’s see what a Random Walk looks like:

 # Generate 500 data points of a random walk
 steps = np.random.choice([-1, 1], size=500) # Generate random steps: -1 or 1
 random_walk = np.cumsum(steps) # Cumulative sum to generate the random walk
 
 # Plot the random walk time series
 plt.figure(figsize=(10, 6))
 plt.plot(random_walk, label='Random Walk')
 plt.title('Random Walk Time Series')
 plt.xlabel('Time')
 plt.ylabel('Value')
 plt.legend()
 plt.grid(True)
 plt.show()

 # Generate and plot the recurrence plot
 recurrence = recurrence_plot(random_walk, threshold=0.1)
 
 plt.figure(figsize=(8, 8))
 plt.imshow(recurrence, cmap='binary', origin='lower')
 plt.title('Recurrence Plot')
 plt.xlabel('Time')
 plt.ylabel('Time')
 plt.colorbar(label='Recurrence')
 plt.show()

SARIMA

Simulated data of SARIMA(4,1,4)(1,0,0,12)

 from statsmodels.tsa.statespace.sarimax import SARIMAX
 
 #Define SARIMA parameters
 p, d, q = 4, 1, 4 # Non-seasonal order
 P, D, Q, s = 1, 0, 0, 12 # Seasonal order
 
 # Simulate data
 model = SARIMAX(np.random.randn(100), order=(p, d, q), seasonal_order=(P, D, Q, s), trend='ct')
 fit = model.fit(disp=False) # Fit the model to random data to get parameters
 simulated_data = fit.simulate(nsimulations=500)
 
 # Plot the simulated time series
 plt.figure(figsize=(10, 6))
 plt.plot(simulated_data, label=f'SARIMA({p},{d},{q})({P},{D},{Q},{s})')
 plt.title('Simulated Time Series from SARIMA Model')
 plt.xlabel('Time')
 plt.ylabel('Value')
 plt.legend()
 plt.grid(True)
 plt.show()

 recurrence = recurrence_plot(simulated_data, threshold=0.1)
 
 plt.figure(figsize=(8, 8))
 plt.imshow(recurrence, cmap='binary', origin='lower')
 plt.title('Recurrence Plot')
 plt.xlabel('Time')
 plt.ylabel('Time')
 plt.colorbar(label='Recurrence')
 plt.show()

Chaotic data

 def logistic_map(x, r):
     """Logistic map function."""
     return r * x * (1 - x)
 
 #Initialize parameters
 N = 500 #Number of data points
 r = 3.9 # Parameter r, set to a value that causes chaotic behavior
 x0 = np.random.rand() # Initial value
 
 # Generate chaotic time series data
 chaotic_data = [x0]
 for _ in range(1, N):
     x_next = logistic_map(chaotic_data[-1], r)
     chaotic_data.append(x_next)
 
 # Plot the chaotic time series
 plt.figure(figsize=(10, 6))
 plt.plot(chaotic_data, label=f'Logistic Map (r={r})')
 plt.title('Chaotic Time Series')
 plt.xlabel('Time')
 plt.ylabel('Value')
 plt.legend()
 plt.grid(True)
 plt.show()

 recurrence = recurrence_plot(chaotic_data, threshold=0.1)
 
 plt.figure(figsize=(8, 8))
 plt.imshow(recurrence, cmap='binary', origin='lower')
 plt.title('Recurrence Plot')
 plt.xlabel('Time')
 plt.ylabel('Time')
 plt.colorbar(label='Recurrence')
 plt.show()

S&P 500 Index

As a final example, let’s look at real S&P 500 data from October 28, 2013, to October 27, 2023:

 import pandas as pd
 
 df = pd.read_csv('standard_and_poors_500_idx.csv', parse_dates=True)
 df['Date'] = pd.to_datetime(df['Date'])
 df.set_index('Date', inplace = True)
 df.drop(columns = ['Open', 'High', 'Low'], inplace = True)
 
 df.plot()
 plt.title('S &P 500 Index - 10/28/2013 to 10/27/2023')
 plt.ylabel('S &P 500 Index')
 plt.xlabel('Date');

 recurrence = recurrence_plot(df['Close/Last'], threshold=10)
 
 plt.figure(figsize=(8, 8))
 plt.imshow(recurrence, cmap='binary', origin='lower')
 plt.title('Recurrence Plot')
 plt.xlabel('Time')
 plt.ylabel('Time')
 plt.colorbar(label='Recurrence')
 plt.show()

Choosing an appropriate similarity threshold is a key step in recursive graph analysis. A smaller threshold will result in more repeating patterns, while a larger threshold will result in fewer repeating patterns. The choice of threshold usually needs to be adjusted based on the characteristics of the data and the analysis goals.

Here we had to adjust the threshold, and we ended up with a value of 10, which allowed for greater contrast. The recurrence graph above looks a lot like a mix of a random walk recurrence graph and random chaotic data.

Summary

In this article, we introduced recursive graphs and how to create them using Python. Recursion graphs give us a way to visually represent time series graphs. Recurrence plots are a powerful tool for revealing structure and patterns in time series, especially those with periodic, repetitive, or complex structure. Through visualization and feature extraction, researchers can better understand time series data and perform further analysis.

Various features can be extracted from the recurrence graph for further analysis. These features can include the distribution of repeating points, Lempel-Ziv complexity, longest diagonal length, etc.

Recursive graphs are widely used in many fields, including time series analysis, vibration analysis, seismology, ecology, financial analysis, biomedicine, etc. It can be used to detect periodicity, abnormal events, phase synchronization, etc.

https://avoid.overfit.cn/post/6b385fd6e8d64f2cb62d9caafd05389b

Author: Sam Erickson