Как визуализировать плотный, неоднородный временной ряд с пиками в Python?Python

Программы на Python
Anonymous
Как визуализировать плотный, неоднородный временной ряд с пиками в Python?

Сообщение Anonymous »

У меня есть временной ряд датчика со следующими свойствами:
  • ~250 000 точек данных при высокой частоте выборки в течение ~12 часов
  • ~11 непрерывных сегментов, разделенных промежутками в 30–40 минут (датчик периодически отключается)
  • Сигнал имеет медиану ~70, но острые пики достигают 500+
  • Мне нужно обозначить точки по интенсивности цветом (ниже медианы, медиана 1–2x, медиана выше 2x) и показать пороговые линии
Вот минимальный воспроизводимый пример создания аналогичных данных:

Код: Выделить всё

"""
Minimal reproducible example: visualizing a dense, gappy time series with spikes.
Shows 6 different approaches for comparison.
"""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

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 = []
t_start = 0
for i in range(11):
duration = np.random.uniform(2000, 3000)
n_points = int(duration / 0.1)
t = np.linspace(t_start, t_start + duration, n_points)

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

n_spikes = np.random.poisson(3)
for _ in range(n_spikes):
spike_pos = np.random.randint(0, n_points)
spike_amp = np.random.exponential(100)
spike_width = np.random.uniform(5, 50)
spike = spike_amp * np.exp(-0.5 * ((t - t[spike_pos]) / spike_width) ** 2)
rate += spike

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

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

# Thresholds for color-coding
all_rates = np.concatenate([s[1] for s in segments])
med = np.median(all_rates[all_rates > 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 np.where(r < t1, '#2166ac', np.where(r < t2, '#ff8c00', '#cc0000'))

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)

# =====================================================================
# 6 visualization approaches
# =====================================================================
fig, axes = plt.subplots(6, 1, figsize=(20, 30), sharex=True)

# --- 1. Naive ax.plot (BAD: connects across gaps) ---
ax = axes[0]
t_all = np.concatenate([s[0] for s in segments])
r_all = np.concatenate([s[1] for s in segments])
ax.plot(t_all, r_all, 'k-', linewidth=0.3, alpha=0.5)
ax.set_ylabel("Intensity")
ax.set_title("1. ax.plot() — connects across gaps (bad)", fontsize=14, fontweight='bold')
ax.set_ylim(0, thresh2 * 3)

# --- 2. Per-segment line plot (fixes gaps, but dense) ---
ax = axes[1]
for t_seg, r_seg in segments:
ax.plot(t_seg, r_seg, 'k-', linewidth=0.2, alpha=0.4)
ax.set_ylabel("Intensity")
ax.set_title("2. Per-segment ax.plot() — gaps correct, but too dense to read", fontsize=14, fontweight='bold')
ax.set_ylim(0, thresh2 * 3)

# --- 3.  Errorbar points with 100s binning ---
ax = axes[2]
for t_seg, r_seg in segments:
tb, rb, eb = bin_segment(t_seg, r_seg)
if len(tb) > 0:
colors = color_for_rate(rb)
for c in ['#2166ac', '#ff8c00', '#cc0000']:
mask = colors == c
if np.any(mask):
ax.errorbar(tb[mask], rb[mask], yerr=eb[mask], fmt='.', color=c,
markersize=4, elinewidth=0.5, capsize=0)
ax.axhline(thresh1, color='#2166ac', ls='--', lw=1.5, alpha=0.5)
ax.axhline(thresh2, color='#cc0000', ls='--', lw=1.5, alpha=0.5)
ax.set_ylabel("Intensity")
ax.set_title("3. Errorbar + 100s binning — readable but spikes smoothed out", fontsize=14, fontweight='bold')
ax.set_ylim(0, thresh2 * 3)

# --- 4. Per-segment colored fill_between ---
ax = axes[3]
for t_seg, r_seg in segments:
tb, rb, _ = bin_segment(t_seg, r_seg, bin_width_hrs=30/3600)
if len(tb) < 2:
continue
ax.fill_between(tb, 0, rb, where=rb < thresh1, color='#2166ac', alpha=0.5, linewidth=0)
ax.fill_between(tb, 0, rb, where=(rb >= thresh1) & (rb < thresh2), color='#ff8c00', alpha=0.5, linewidth=0)
ax.fill_between(tb, 0, rb, where=rb >= thresh2, color='#cc0000', alpha=0.6, linewidth=0)
ax.plot(tb, rb, 'k-', linewidth=0.4, alpha=0.5)
ax.axhline(thresh1, color='#2166ac', ls='--', lw=1.5, alpha=0.5)
ax.axhline(thresh2, color='#cc0000', ls='--', lw=1.5, alpha=0.5)
ax.set_ylabel("Intensity")
ax.set_title("4. Per-segment fill_between (30s bins) — shows structure but messy at transitions", fontsize=14, fontweight='bold')
ax.set_ylim(0, thresh2 * 3)

# --- 5. Vertical bars (stem plot) per segment ---
ax = axes[4]
for t_seg, r_seg in segments:
tb, rb, _ = bin_segment(t_seg, r_seg, bin_width_hrs=30/3600)
if len(tb) == 0:
continue
colors = color_for_rate(rb)
bar_width = 30 / 3600 * 0.9
ax.bar(tb, rb, width=bar_width, color=colors, linewidth=0, alpha=0.7)
ax.axhline(thresh1, color='#2166ac', ls='--', lw=1.5, alpha=0.5)
ax.axhline(thresh2, color='#cc0000', ls='--', lw=1.5, alpha=0.5)
ax.set_ylabel("Intensity")
ax.set_title("5. Vertical bars (30s bins) — gaps natural, spikes visible, but busy", fontsize=14, fontweight='bold')
ax.set_ylim(0, thresh2 * 3)

# --- 6. Two-layer: faint raw + bold smoothed ---
ax = axes[5]
for t_seg, r_seg in segments:
# Faint raw data (thin line, very transparent)
ax.plot(t_seg, r_seg, color='gray', linewidth=0.1, alpha=0.15, rasterized=True)
# Bold 100s binned overlay
tb, rb, _ = bin_segment(t_seg, r_seg)
if len(tb) > 1:
colors = color_for_rate(rb)
for j in range(len(tb) - 1):
ax.plot([tb[j], tb[j+1]], [rb[j], rb[j+1]],
color=colors[j], linewidth=2, solid_capstyle='round')
ax.axhline(thresh1, color='#2166ac', ls='--', lw=1.5, alpha=0.5)
ax.axhline(thresh2, color='#cc0000', ls='--', lw=1.5, alpha=0.5)
ax.set_ylabel("Intensity")
ax.set_title("6. Two-layer: faint raw + bold 100s trend — shows both detail and structure", fontsize=14, fontweight='bold')
ax.set_ylim(0, thresh2 * 3)

axes[-1].set_xlabel("Time (hours)", fontsize=14)

for ax in axes:
ax.tick_params(labelsize=11)
ax.grid(axis='y', alpha=0.1)

plt.tight_layout()
fig.savefig("example.png", dpi=150, bbox_inches='tight', facecolor='white')
plt.close()
print("Saved: example.png")
Что я пробовал:
  • Код: Выделить всё

    ax.plot()
    — соединяет точки через пробелы длинными диагональными линиями. Выглядит ужасно.
  • Код: Выделить всё

    ax.scatter()
    с понижающей дискретизацией LTTB — точки слишком малы для чтения или слишком велики и перекрываются. Пробелы неоднозначны.
  • Код: Выделить всё

    ax.errorbar(fmt='.')
    с группированием по 100 секунд на сегмент — наиболее близко к тому, что мне нужно, но кажется загроможденным. Биннинг скрывает структуру пиков.
  • Цветная заливка с помощьюwhere= — создает артефакты при переходе цвета.
Чего я хочу:
  • Пробелы должны быть отчетливо видны как пустое пространство (не подключено)
  • Всплески должны быть визуально заметными (не усредняться путем группирования)
  • Общая тенденция должна быть читаема с первого взгляда
  • Цветовая кодировка по уровню интенсивности должна быть четкой
  • Подходит для Публикационный рисунок (чистый, не загроможденный)
Рисунок имеет широкий формат (~ 4400 пикселей по горизонтали) и содержит ~ 11 сегментов.
Какой подход к визуализации лучше всего подходит для этого типа данных? Открыт для любой библиотеки Python — matplotlib,plotly, bokeh, seaborn или чего-то еще. Также открыты для нестандартных подходов (полосы тепловых карт, графики плотности, пошаговые графики с учетом сегментов и т. д.).
Изображение

Вернуться в «Python»