Poincaré Plot
A scatter diagram in which each value of a time series is plotted against the next value, exposing short- and long-term variability through the shape of the resulting cloud. The clinical workhorse of heart rate variability analysis.
// 01 — The chart
What it looks like
A Poincaré plot of RR intervals showing a tight elliptical cloud along the line of identity, indicating healthy heart rate variability. The dashed ellipse marks the SD1 / SD2 cloud envelope.
// 02 — Definition
What is a Poincaré plot?
A Poincaré plot (also called a return map or lag plot) is a scatter diagram in which each data point x(n) in a time series is plotted against the next value x(n+1). The x-axis shows the current value, and the y-axis shows the immediately following value. This simple transformation converts a one-dimensional time series into a two-dimensional picture that reveals the relationship between consecutive measurements.
In cardiology, where the technique is most widely used, the time series consists of RR intervals (the time between successive heartbeats). The resulting scatter cloud’s shape, size, and orientation encode clinically meaningful information about heart rate variability (HRV).
The cloud’s width perpendicular to the line of identity (the 45° diagonal where x = y) measures short-term variability (SD1), while its length along the diagonal measures long-term variability (SD2). Together, they provide a compact, intuitive summary of autonomic nervous system function.
Beyond cardiology, Poincaré plots are used in nonlinear-dynamics work to visualize the qualitative behavior of dynamical systems — detection of stationarity, regime changes, and weak periodicities. In every case, the geometry around the identity line is what carries the meaning.
Origin: Named after Henri Poincaré, the French mathematician who pioneered the qualitative study of dynamical systems. The specific application to heart rate variability was popularized in the 1990s by Tulppo, Huikuri, and colleagues studying autonomic cardiac control.
// 03 — When to use
When a Poincaré plot is the right call
Reach for a Poincaré plot whenever the question is about how the next sample relates to the current one. In cardiology that is the canonical short-term HRV question; in dynamics it is a quick visual stationarity check.
- Analyzing heart rate variability from RR intervals
- Studying short- and long-term variability of any time series
- Investigating the qualitative dynamics of a measured process
- Looking for regime changes or non-stationarity
- Comparing variability across patients, sessions, or experimental conditions
- Quantifying autonomic balance through SD1 / SD2 ratios
- You want a single picture that summarizes both noise and trend
// 04 — When not to use
When a Poincaré plot is the wrong call
The chart only makes sense when the data is genuinely a time series with a stable lag. Outside that, simpler charts almost always communicate better.
- Your data is not ordered in time
- Sampling intervals are highly irregular and you can’t justify pairing samples
- The series is too short (under ~50 points) for the cloud to develop
- A simple line chart over time would tell the same story to a less specialized audience
- You need to see absolute timing or frequency content — use a spectrogram instead
- There are too many ectopic beats / artifacts and the cloud is dominated by noise
- You’re trying to communicate to a non-numerate audience
// 05 — Data requirements
What your data needs to look like
Before building the chart, your dataset needs to fit a specific shape. Use this checklist to confirm yours does.
Shape
One row per sample, ordered in time, with a numeric value column. The chart is built from the lag-1 pairing of consecutive values.
Minimum rows
50 samples. Below that, the cloud is too sparse to see structure.
Maximum rows
No effective upper bound. Long Holter recordings produce hundreds of thousands of pairs without trouble.
The sample index or time at which each value was recorded. Used to lag the series correctly; ordering is what matters, not the absolute timestamp.
The measurement at each time step. In cardiology this is the RR interval (ms); in dynamics it is any state variable measured at regular intervals.
| t | rr_ms |
|---|---|
| 1 | 868 |
| 2 | 871 |
| 3 | 880 |
| 4 | 892 |
| 5 | 884 |
| 6 | 879 |
Tip: in cardiology you almost always want to filter ectopic and artifact beats before plotting. Tools like Kubios, RHRV’s FilterNIHR(), and Python’s hrv-analysis all do this. Document the filter; aggressive cleaning deflates SD1.
// 06 — Anatomy
Parts of a Poincaré plot
Five elements make up every Poincaré plot: two equal-scale axes, the line of identity, the point cloud, and the SD1 / SD2 ellipse drawn over it.
// 07 — Step-by-step
Step-by-step: how to build a good Poincaré plot
Eight steps that work regardless of tool. The artifact filter and the SD1 / SD2 ellipse are the two non-trivial moves; the rest is mechanical once the data is in shape.
- 1
Confirm the series is genuinely a time series
Order matters. If the rows are unordered or the time gaps are irregular, the lag transform is meaningless. Sort by time and either verify constant sampling or document the gap distribution before plotting. - 2
Choose the lag
The default lag is 1 — plot x(n) against x(n+1). Larger lags expose periodicity (lag = period gives a tight diagonal). Stick with lag 1 for the canonical ‘Poincaré plot’ unless you have a reason to deviate. - 3
Filter or annotate non-stationary segments
Drift, regime changes, and ectopic beats produce off-diagonal clusters that swamp the variability story. Either filter them or color them differently so readers can see what’s signal vs artifact. - 4
Draw the line of identity
Add a 45-degree dashed line where x = y. Without it, the cloud has no anchor and SD1 / SD2 mean nothing geometrically. - 5
Compute SD1 and SD2
SD1 = std(x − y) / sqrt(2) and SD2 = std(x + y) / sqrt(2). They project the point cloud onto the perpendicular and parallel directions of the identity line. - 6
Overlay the SD1 / SD2 ellipse
Draw an ellipse centered at the cloud mean with semi-axes equal to SD1 (perpendicular to identity) and SD2 (along identity), rotated 45 degrees. This is the classical Poincaré ellipse. - 7
Use partial transparency for overplotting
Even moderate-sized series produce overplotting. Use alpha around 0.3–0.5 so density becomes visible without losing the underlying scatter. - 8
Title the takeaway
Replace “Poincaré plot of RR intervals” with “Tight elliptical cloud, SD1 = 38 ms, SD2 = 92 ms — healthy heart rate variability” so the chart announces the conclusion.
// 08 — Real-world examples
Where you’ll see Poincaré plots used
Cardiology is the headline application but Poincaré geometry shows up wherever a single time series needs a quick visual summary of short-term variability.
Cardiology: HRV from a Holter recording
A 24-hour Holter monitor yields ~100,000 RR intervals. The Poincaré plot summarizes them in one figure; SD1 / SD2 are reported alongside time- and frequency-domain HRV metrics in a clinical report.
CardiologySleep medicine: autonomic state during sleep stages
Sleep researchers split a recording into REM / non-REM segments and produce one Poincaré plot per segment, color-coded. The contrast in cloud size visualizes the autonomic state shifts directly.
SleepDynamics: stationarity check for a sensor signal
Engineering teams use Poincaré plots as a quick visual stationarity check on vibration / acoustic sensor data. Off-diagonal smearing is the immediate red flag for drift or fault onset.
EngineeringSports science: training load monitoring
Athletes record morning HRV over weeks. Poincaré plots stacked by week reveal recovery patterns, with a shrinking SD1 indicating accumulated fatigue.
Sports science// 09 — Variations
Variants of the Poincaré plot
The basic chart has a few common variants — lag choice, density encoding, and segment overlay — each suited to a slightly different question.
Lag-k Poincaré plot
Plot x(n) against x(n+k) for k > 1. Tight diagonal alignment at a specific k reveals periodicity at period k.
Density Poincaré plot
Replace points with a 2D density / hexbin to handle very long recordings. Reveals the same SD1 / SD2 envelope without overplotting.
Segmented Poincaré plot
Color-code points by experimental segment (rest, exercise, recovery) so the contrast in cloud shape is immediately visible.
3D Poincaré plot
Plots x(n), x(n+1), x(n+2) on three axes for higher-order embedding. Used in nonlinear dynamics rather than clinical HRV.
// 10 — Comparisons
How it compares
Poincaré plots sit at the intersection of scatter plots and time-series visualization. The most useful comparisons are with general scatter plots, the broader lag-plot family, and recurrence plots from nonlinear dynamics.
Poincaré plot vs scatter plot
A scatter plot pairs two different variables; a Poincaré plot pairs one variable with itself, lagged. The chart geometries look identical but the interpretation is completely different — Poincaré reveals temporal structure that a scatter cannot.
Poincaré plot
Lag-1 scatter of one variable against itself. Reveals short- and long-term variability of a time series.
- Single variable, lagged by 1
- Identity line is the reference
- SD1 / SD2 quantify variability
Scatter plot
Two different variables plotted against each other. Reveals correlation and bivariate structure, not temporal structure.
- Two variables, no lag
- No special reference line
- Correlation, not variability
Poincaré plot vs lag plot
A Poincaré plot is a lag plot at lag 1, with the line of identity and the SD1 / SD2 ellipse drawn explicitly. A general lag plot is a family of charts at lags 1, 2, 3, … used for periodicity discovery.
Poincaré plot
Lag = 1 specifically, with identity line and SD1 / SD2 ellipse. Used for variability and stationarity assessment.
- Lag = 1 only
- Identity line and ellipse drawn
- Cardiology and dynamics convention
Lag plot (general)
Family of lag-k plots used to discover periodicity and autocorrelation structure across multiple lags.
- Variable lag (1, 2, 3, ...)
- Used for periodicity scanning
- Often shown as a small-multiples grid
Poincaré plot vs recurrence plot
Both come from nonlinear-dynamics tradition, but they answer different questions. Poincaré asks ‘how similar is each value to the next?’; recurrence asks ‘when does this state recur in time?’ The visuals look nothing alike.
Poincaré plot
Lag-1 scatter showing short-term variability. Cloud-shaped, anchored on the identity line.
- Lag-1 scatter
- Compact cloud near identity
- Local variability summary
Recurrence plot
Time × time matrix marking pairs of timestamps with similar state. Looks like a checkerboard / textile pattern.
- Time × time grid
- Reveals periodicity globally
- Used for chaos and signal classification
// 11 — Common mistakes
Common Poincaré plot mistakes
The chart’s simplicity is deceptive. Most failures come from forgetting that order matters, that artifacts dominate, or that the geometry depends on equal axis scales.
Forgetting equal axis scales
If the x and y axes have different scales, the line of identity is no longer 45 degrees and SD1 / SD2 stop projecting onto perpendicular and parallel directions.
Skipping artifact correction
Even one ectopic beat creates two dramatic off-diagonal points that swamp the cloud. Always filter and document the filter.
Using mismatched axis ranges across patients
When comparing recordings, lock both axes to the same range. Otherwise the visual cloud size lies.
Omitting SD1 / SD2 numbers
Most readers can’t back out variability from the visual alone. Print the numbers in the title or as annotations.
Using a non-1 lag without saying so
If you compute the chart at lag = 2 or higher, label it explicitly. The default convention is lag = 1.
Treating the chart as a recurrence plot
Poincaré and recurrence plots are different charts answering different questions. Don’t use the names interchangeably.
// 12 — Accessibility
Accessibility checklist
Run through this list before publishing. The chart should still communicate its message to readers using assistive technology, color-blind users, keyboard navigation, and reduced-motion settings.
- ✓
Color contrast for points and reference lines
WCAG 1.4.3The point cloud should reach 3:1 contrast against the chart background; the line of identity, ellipse outline, and any annotations should reach 4.5:1 so they remain visible at thumbnail size. - ✓
Don’t encode meaning by color alone
WCAG 1.4.1When you split the cloud (e.g., normal vs. ectopic beats), use both color and a marker shape so color-vision-deficient readers can still distinguish the groups. - ✓
Provide a meaningful text alternative
WCAG 1.1.1An accessible name should describe the takeaway: “Tight elliptical cloud along the identity line; SD1 = 38 ms, SD2 = 92 ms, indicating healthy short- and long-term heart rate variability.” - ✓
Expose the underlying time series
WCAG 1.3.1Offer a downloadable CSV of the original RR intervals plus the computed SD1 / SD2. The Poincaré representation is essentially impossible to experience without sight. - ✓
Label SD1 and SD2 explicitly
WCAG 3.3.2Caption the chart with both the values and the units (typically ms). Numbers without labels are meaningless to anyone unfamiliar with HRV conventions. - ✓
Respect prefers-reduced-motion
WCAG 2.3.3Avoid animated unfolding of the cloud unless the animation is gated behind a prefers-reduced-motion: no-preference media query. - ✓
Resizable and zoomable
WCAG 1.4.4Use a responsive viewBox so the chart remains legible at 200% browser zoom. Make sure the ellipse outline and identity line stay visible at every container width.
// 13 — Best practices
Design and craft tips
A short list of dos and don’ts that consistently separate publication-quality Poincaré plots from the demo-grade ones produced by default in most scripting environments.
Always draw the identity line
Use a lag other than 1 without saying so
Report SD1 and SD2 numerically
Hide artifact beats silently
Use partial transparency for the points
Compare two Poincaré plots with different axes
Annotate regime changes
Confuse Poincaré plot with recurrence plot
// 15 — Tool instructions
How to build it in your tool of choice
Recipes for the libraries and clinical tools that ship a Poincaré implementation. RHRV and Kubios are the closest things to canonical references.
Python (Matplotlib)
Code — ~5 min- 01Install Matplotlib and NumPy with pip install matplotlib numpy.
- 02Load the time series into a one-dimensional NumPy array.
- 03Lag by one: x = series[:-1] and y = series[1:].
- 04Scatter with plt.scatter(x, y, alpha=0.4, s=12).
- 05Add the identity line: ax.plot([lo, hi], [lo, hi], '--', alpha=0.4).
- 06Compute SD1 / SD2 and overlay an ellipse with matplotlib.patches.Ellipse, rotated 45 degrees, with width=2*SD2 and height=2*SD1.
If you only need the metrics and the visual is secondary, hrv-analysis (PyPI) computes SD1 / SD2 directly and ships a Poincaré helper.
R (RHRV)
Code — ~6 min- 01Install RHRV and ggplot2 with install.packages(c('RHRV', 'ggplot2')).
- 02Build the HRV data structure: hrv <- CreateHRVData(); LoadBeatRR(hrv, RR=rr_intervals_seconds); BuildNIHR(hrv).
- 03Filter artifacts: hrv <- FilterNIHR(hrv) to remove ectopic and impossible beats.
- 04Run the non-linear analysis pipeline: hrv <- CreateNonLinearAnalysis(hrv).
- 05Call PoincarePlot(hrv, indexNonLinearAnalysis=1) to compute SD1 / SD2 and draw the plot.
- 06Re-render with ggplot2 if you want finer control over color, theme, and annotation.
RHRV is the reference R package for HRV; PoincarePlot() is the canonical implementation, including the SD1 / SD2 ellipse overlay.
JavaScript (D3)
Code — ~8 min- 01Install with npm i d3 or include via CDN.
- 02Build pairs = series.slice(0, -1).map((v, i) => [v, series[i + 1]]).
- 03Set up x and y scales with d3.scaleLinear() over the same domain so the identity line remains a true 45-degree diagonal.
- 04Render circles for each pair with low fill-opacity to handle overplotting.
- 05Draw the identity line as an SVG line element from [domain.min, domain.min] to [domain.max, domain.max].
- 06Compute SD1 = std(x[i] - y[i]) / sqrt(2) and SD2 = std(x[i] + y[i]) / sqrt(2); render the ellipse rotated 45 degrees centered on (mean, mean).
Observable Plot can do the scatter and the rule-line in two marks, but it has no built-in helper for the rotated SD1 / SD2 ellipse.
MATLAB
Code — ~5 min- 01Place the time series in a column vector rr.
- 02Build x = rr(1:end-1); y = rr(2:end).
- 03Use scatter(x, y, 12, 'filled', 'MarkerFaceAlpha', 0.4).
- 04Hold on; plot the identity line with plot([lo hi], [lo hi], '--').
- 05Compute SD1 = std(x - y) / sqrt(2) and SD2 = std(x + y) / sqrt(2).
- 06Overlay the ellipse with rectangle('Position', [mu_x-SD2, mu_y-SD1, 2*SD2, 2*SD1], 'Curvature', [1 1]) inside an hgtransform rotated 45 degrees.
MATLAB’s Signal Processing Toolbox doesn’t ship a poincare() function out of the box; the recipe above is the standard manual implementation.
Kubios HRV
GUI — ~3 min- 01Open Kubios HRV and import your raw recording (ECG, .edf, or pre-extracted RR series).
- 02Mark the analysis period and use the artifact-correction tool to filter ectopic beats.
- 03Switch to the ‘Non-linear’ results tab; Kubios automatically draws the Poincaré plot and reports SD1 / SD2 / SD1/SD2 ratio.
- 04Use the ‘Sample report’ export to embed the figure in a PDF that includes the time-domain and frequency-domain summaries alongside.
- 05Double-check that the artifact-correction strength is documented in your methods — strong correction can deflate SD1.
- 06Save the analysis session so the same recording can be reopened with the same correction settings later.
Kubios is the de-facto clinical HRV tool; the Standard edition is free for academic use and produces journal-ready Poincaré figures with no scripting.
// 16 — Code examples
Working code in the most common stacks
Three runnable snippets that produce equivalent Poincaré plots in Python, R, and JavaScript. Each computes SD1 and SD2 alongside the visual.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
rng = np.random.default_rng(42)
n = 600
# Simulated RR intervals (ms): mean ~880 with realistic short-term variability.
rr = 880 + np.cumsum(rng.normal(0, 6, n)) # slow drift
rr += rng.normal(0, 18, n) # short-term variability
x = rr[:-1]
y = rr[1:]
mu_x, mu_y = x.mean(), y.mean()
sd1 = np.std(x - y, ddof=1) / np.sqrt(2)
sd2 = np.std(x + y, ddof=1) / np.sqrt(2)
fig, ax = plt.subplots(figsize=(6.5, 6.5))
ax.scatter(x, y, s=12, alpha=0.4, color="#c94a2e")
lo, hi = rr.min() - 30, rr.max() + 30
ax.plot([lo, hi], [lo, hi], "--", color="#6b6b67", alpha=0.5, lw=1)
ellipse = Ellipse(
(mu_x, mu_y),
width=2 * sd2, height=2 * sd1,
angle=45,
fill=False, edgecolor="#1a1a18", lw=1.2, linestyle=":",
)
ax.add_patch(ellipse)
ax.set_xlim(lo, hi); ax.set_ylim(lo, hi); ax.set_aspect("equal")
ax.set_xlabel("RR(n) [ms]")
ax.set_ylabel("RR(n+1) [ms]")
ax.set_title(
f"Healthy HRV: SD1 = {sd1:.0f} ms, SD2 = {sd2:.0f} ms, "
f"ratio = {sd1 / sd2:.2f}",
loc="left",
)
ax.spines[["top", "right"]].set_visible(False)
plt.tight_layout()
plt.savefig("poincare.png", dpi=200, bbox_inches="tight")
plt.show()
// 17 — FAQs
Frequently asked questions
What is a Poincaré plot?+
A Poincaré plot (also called a return map or lag-1 plot) is a scatter diagram in which each data point x(n) in a time series is plotted against the next value x(n+1). The simple transformation converts a one-dimensional time series into a two-dimensional picture that reveals the relationship between consecutive measurements.
When should you use a Poincaré plot?+
Use a Poincaré plot when analyzing time-series data for short- and long-term variability, especially in cardiology (RR intervals), nonlinear dynamics, or any domain where short-term predictability of the next sample matters. It excels at exposing periodicity, drift, and abrupt regime changes.
When should you avoid a Poincaré plot?+
Avoid a Poincaré plot when your data is not a time series, when there is no clear lag relationship to interpret, when the series is too short (under ~50 points) for the cloud to develop, or when a simple line chart over time would tell the same story to a less specialized audience.
What are SD1 and SD2?+
SD1 is the standard deviation of the projection of the points onto the line perpendicular to the line of identity (y = x). It quantifies short-term variability — beat-to-beat fluctuation in cardiology. SD2 is the standard deviation of the projection onto the line of identity itself; it quantifies long-term variability.
What is the line of identity?+
The line of identity is the 45-degree diagonal where x(n) equals x(n+1). Points falling exactly on this line indicate consecutive samples that did not change. The width of the cloud perpendicular to this line measures short-term variability.
Is a Poincaré plot suitable for dashboards?+
Yes — Poincaré plots work well in clinical or operational dashboards as long as the panel is large enough to show the cloud, the SD1 / SD2 ellipse is overlaid for quick interpretation, and the time period summarized is clearly stated in the title.
What category of chart is a Poincaré plot?+
Poincaré Plot belongs to the Scientific family of charts. Charts in that family are designed to answer the same kind of question — how a system’s state evolves over time — so they often work as alternatives when one doesn’t quite fit your data.
How do you read a Poincaré plot?+
Start with the line of identity, then look at the cloud’s shape and orientation. A tight cloud along the diagonal means consecutive values are similar; a wider cloud means consecutive values vary more. Elongation along the diagonal is long-term variability (SD2); width perpendicular to it is short-term variability (SD1).
What’s the best library for building Poincaré plots?+
For Python, matplotlib’s scatter() with a manual lag transform is the standard. For R, the RHRV package builds Poincaré plots and computes SD1 / SD2 automatically. For the web, D3 with a simple lag-shift on the data array does the job in a dozen lines.
// 18 — References
References and further reading
Primary sources, official library documentation, and the international HRV standards cited throughout this guide.
- The classical paper deriving SD1 and SD2 as projections onto the identity line and discussing what Poincaré geometry actually captures.https://ieeexplore.ieee.org/document/959330
- One of the foundational HRV papers introducing SD1 / SD2 in a clinical exercise-physiology context.https://journals.physiology.org/doi/10.1152/ajpheart.1996.271.1.H244
- Wikipedia — Poincaré plotReferenceEncyclopedia entry covering construction, SD1 / SD2, and applications across cardiology, signal processing, and dynamical systems.https://en.wikipedia.org/wiki/Poincar%C3%A9_plot
- Official documentation for the R package most widely used to compute Poincaré metrics and render the chart.https://rhrv.r-forge.r-project.org/
- Documentation for the Kubios HRV analysis software, the de-facto clinical tool that ships Poincaré plots out of the box.https://www.kubios.com/
- International HRV standards document. Defines the conventions Poincaré plots adhere to in clinical practice.https://academic.oup.com/eurheartj/article/17/3/354/485572
- The open repository of physiological signals (including RR-interval datasets) used to teach and test Poincaré plot pipelines.https://www.physionet.org/
- WAI — Complex Images: Charts and GraphsAccessibilityWeb Accessibility Initiative guidance on text alternatives, long descriptions, and data tables for complex scientific charts.https://www.w3.org/WAI/tutorials/images/complex/
- Wikipedia — Henri PoincaréReferenceBackground on Henri Poincaré and his foundational work on dynamical systems, after whom the chart is named.https://en.wikipedia.org/wiki/Henri_Poincar%C3%A9