Question Details

No question body available.

Tags

python numpy matplotlib plot visualization

Answers (2)

Accepted Answer Available
Accepted Answer
April 10, 2026 Score: 2 Rep: 549 Quality: High Completeness: 80%

You could make a broken axis using matplotlib. It's not exactly staightforward, especially when you need to break the axis multiple time. I also added a multicolored line plot (see this example), to highlight the treshholds.

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import itertools as itt

np.random.seed(42)

Simulate a gappy sensor time series:

~12 hours of recording split into ~11 segments

with ~30-40 min gaps (sensor offline periodically)

segments = [] tstart = 0 for i in range(11): duration = np.random.uniform(2000, 3000) npoints = int(duration / 0.1) t = np.linspace(tstart, tstart + duration, npoints)

base = 70 + 20 np.sin(2 np.pi t / 5000) noise = np.cumsum(np.random.randn(n_points)) 0.3 noise -= np.mean(noise) rate = base + noise + np.random.poisson(base) - base

nspikes = np.random.poisson(3) for in range(nspikes): spikepos = np.random.randint(0, npoints) spikeamp = np.random.exponential(100) spikewidth = np.random.uniform(5, 50) spike = spikeamp np.exp(-0.5 ((t - t[spikepos]) / spikewidth) 2) rate += spike

rate = np.maximum(rate, 0) segments.append((t / 3600, rate)) # store in hours

gap = np.random.uniform(1800, 2400) tstart += duration + gap

Thresholds for color-coding

allrates = np.concatenate([s[1] for s in segments]) med = np.median(allrates[allrates > 0]) thresh1 = med thresh2 = med 2

print(f"Total points: {len(all_rates)}") print(f"Segments: {len(segments)}") print(f"Median: {med:.1f}, 2x median: {thresh2:.1f}, Max: {np.max(all_rates):.1f}")

def color_for_rate(r, t1=thresh1, t2=thresh2): return list(np.where(r < t1, '#2166ac', np.where(r < t2, '#ff8c00', '#cc0000')).astype(str))

def bin_segment(t, r, bin_width_hrs=100/3600): """Bin a single continuous segment.""" if len(t) < 2: return np.array([]), np.array([]), np.array([]) t_bins, r_bins, e_bins = [], [], [] t_min, t_max = t[0], t[-1] edges = np.arange(t_min, t_max, bin_width_hrs) for j in range(len(edges) - 1): mask = (t >= edges[j]) & (t < edges[j+1]) if np.sum(mask) > 0: t_bins.append(np.mean(t[mask])) r_bins.append(np.mean(r[mask])) e_bins.append(np.std(r[mask]) / np.sqrt(np.sum(mask))) return np.array(t_bins), np.array(r_bins), np.array(e_bins)

def colored_line(x, y, c, ax, lc_kwargs): """ Plot a line with a color specified along the line by a third value.

It does this by creating a collection of line segments. Each line segment is made up of two straight lines each connecting the current (x, y) point to the midpoints of the lines connecting the current point with its two neighbors. This creates a smooth line with no gaps between the line segments.

Parameters ---------- x, y : array-like The horizontal and vertical coordinates of the data points. c : array-like The color values, which should be the same size as x and y. ax : Axes Axis object on which to plot the colored line. lc_kwargs Any additional arguments to pass to matplotlib.collections.LineCollection constructor. This should not include the array keyword argument because that is set to the color argument. If provided, it will be overridden.

Returns ------- matplotlib.collections.LineCollection The generated line collection representing the colored line. """ if "array" in lc_kwargs: warnings.warn('The provided "array" keyword argument will be overridden')

# Default the capstyle to butt so that the line segments smoothly line up default_kwargs = {"capstyle": "butt"} default_kwargs.update(lc_kwargs)

# Compute the midpoints of the line segments. Include the first and last points # twice so we don't need any special syntax later to handle them. x = np.asarray(x) y = np.asarray(y) x_midpts = np.hstack((x[0], 0.5 (x[1:] + x[:-1]), x[-1])) ymidpts = np.hstack((y[0], 0.5 * (y[1:] + y[:-1]), y[-1]))

# Determine the start, middle, and end coordinate pair of each line segment. # Use the reshape to add an extra dimension so each pair of points is in its # own list. Then concatenate them to create: # [ # [(x1start, y1start), (x1mid, y1mid), (x1end, y1end)], # [(x2start, y2start), (x2mid, y2mid), (x2end, y2end)], # ... # ] coordstart = np.columnstack((xmidpts[:-1], ymidpts[:-1]))[:, np.newaxis, :] coordmid = np.columnstack((x, y))[:, np.newaxis, :] coordend = np.columnstack((xmidpts[1:], ymidpts[1:]))[:, np.newaxis, :] segments = np.concatenate((coordstart, coordmid, coordend), axis=1)

lc = LineCollection(segments, colors=c, defaultkwargs)

return ax.addcollection(lc)

=====================================================================

6 visualization approaches

=====================================================================

fig, axes = plt.subplots(1, len(segments), sharey=True, figsize=(20, 3)) fig.subplotsadjust(wspace=0.1)

for ax, segment in zip(axes.flat, segments): # Display lines coloredline(segment[0], segment[1], colorforrate(segment[1]), ax)

# Set axis limits manually ax.setxlim(segment[0][0], segment[0][-1]) ax.setylim(bottom=0, top=500)

# Remove ticks that are too close to one of the breaks ax.setxticks( [val for val in ax.getxticks() if (segment[0][0] + 0.05 < val < segment[0][-1] - 0.05)] )

# Thresh lines ax.axhline(y=thresh1, color='#ff8c00', linestyle='--', lw=0.5) ax.axhline(y=thresh2, color='#cc0000', linestyle='--', lw=0.5)

Remove unused spines and ticks

for axleft, axright in itt.pairwise(axes.flat): axleft.spines.right.setvisible(False) axright.spines.left.setvisible(False)

axleft.tickparams(right=False) axright.tickparams(left=False)

axes[0].yaxis.tickleft() axes[-1].yaxis.tickright()

Create break lines

d = 2 # proportion of vertical to horizontal extent of the slanted line kwargs = dict( marker=[(-1, -d), (1, d)], markersize=12, linestyle="none", color='k', mec='k', mew=1, clipon=False )

for axleft, axright in itt.pairwise(axes.flat): axleft.plot([1, 1], [0, 1], transform=axleft.transAxes, kwargs) axright.plot([0, 0], [0, 1], transform=ax_right.transAxes, kwargs)

fig.savefig('example.png')

Output:

Figure with broken axis

EDIT: I looked into bining, but I don't think it's a good idea for your use-case, since it might erase small peaks. I'd say the raw data is probably best.

April 10, 2026 Score: 2 Rep: 21,707 Quality: Low Completeness: 40%

You're looking for the ruptures library. It formulates your Change Point Analysis problem as an optimization problem, minimizing a loss function, and identifies optimal timestamps to declare that the latent state variable(s) of the underlying process went through some change. Do take a little while to read the project's introductory paper, as it is quite illuminating.