A Q-Q (Quantile-Quantile) plot compares the distribution of a dataset against a theoretical distribution (typically normal) or another dataset. Points are plotted by matching sample quantiles to theoretical quantiles, with a diagonal reference line indicating perfect distribution match. Deviations from the line reveal distribution characteristics such as skewness, heavy tails, and outliers.

""" anyplot.ai
qq-basic: Basic Q-Q Plot
Library: plotly 6.9.0 | Python 3.13.14
Quality: 90/100 | Updated: 2026-07-24
"""
import os
import numpy as np
import plotly.graph_objects as go
# Theme tokens (see prompts/default-style-guide.md "Background" + "Theme-adaptive Chrome")
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
BRAND = "#009E73" # Imprint palette position 1 — Q-Q scatter points
REF_COLOR = INK # Imprint semantic anchor "neutral" — reference line (baseline role)
BAND_COLOR = INK_MUTED # Imprint semantic anchor "muted" — confidence envelope
# Data - sample with slight positive skew to demonstrate Q-Q plot interpretation
np.random.seed(42)
sample = np.concatenate(
[
np.random.normal(50, 10, 80), # Main normal distribution
np.random.normal(75, 5, 20), # Slight right tail for interest
]
)
sample = np.sort(sample)
# Standardize sample for comparison with standard normal
sample_standardized = (sample - np.mean(sample)) / np.std(sample)
# Theoretical quantiles via Blom's plotting positions + Winitzki erfinv approximation
n = len(sample)
probabilities = (np.arange(1, n + 1) - 0.375) / (n + 0.25)
q = 2 * probabilities - 1 # map to [-1, 1] for erfinv
a = 0.147
ln1q2 = np.log(1 - q**2)
b = 2 / (np.pi * a) + ln1q2 / 2
theoretical_quantiles = np.sign(q) * np.sqrt(2) * np.sqrt(np.sqrt(b**2 - ln1q2 / a) - b)
# 95% confidence envelope from the asymptotic variance of normal order statistics
normal_density = np.exp(-(theoretical_quantiles**2) / 2) / np.sqrt(2 * np.pi)
band_half_width = 1.96 * np.sqrt(probabilities * (1 - probabilities) / n) / normal_density
# Reference line (y=x for standardized data)
margin = 0.3
line_min = min(theoretical_quantiles.min(), sample_standardized.min()) - margin
line_max = max(theoretical_quantiles.max(), sample_standardized.max()) + margin
# Plot
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=np.concatenate([theoretical_quantiles, theoretical_quantiles[::-1]]),
y=np.concatenate([theoretical_quantiles + band_half_width, (theoretical_quantiles - band_half_width)[::-1]]),
fill="toself",
fillcolor=BAND_COLOR,
opacity=0.15,
line={"width": 0},
name="95% Confidence Band",
hovertemplate="Theoretical: %{x:.3f}<br>Band Edge: %{y:.3f}<extra></extra>",
legendrank=3,
)
)
fig.add_trace(
go.Scatter(
x=[line_min, line_max],
y=[line_min, line_max],
mode="lines",
line={"color": REF_COLOR, "width": 2.5, "dash": "dash"},
name="Reference (y=x)",
hoverinfo="skip",
legendrank=2,
)
)
fig.add_trace(
go.Scatter(
x=theoretical_quantiles,
y=sample_standardized,
mode="markers",
marker={"size": 10, "color": BRAND, "opacity": 0.85},
name="Sample Quantiles",
hovertemplate="Theoretical: %{x:.3f}<br>Sample: %{y:.3f}<extra></extra>",
legendrank=1,
)
)
# Annotate the largest tail deviation from the reference line (|theoretical| > 1.5)
tail_mask = np.abs(theoretical_quantiles) > 1.5
deviation = np.abs(sample_standardized - theoretical_quantiles)
tail_idx = np.where(tail_mask)[0][np.argmax(deviation[tail_mask])]
fig.add_annotation(
x=theoretical_quantiles[tail_idx],
y=sample_standardized[tail_idx],
text="Right-tail deviation",
showarrow=True,
arrowhead=2,
arrowcolor=INK_SOFT,
ax=-50,
ay=-30,
font={"size": 10, "color": INK_SOFT},
)
# Style
title_text = "qq-basic · python · plotly · anyplot.ai"
fig.update_layout(
autosize=False,
width=800,
height=450,
paper_bgcolor=PAGE_BG,
plot_bgcolor=PAGE_BG,
title={"text": title_text, "font": {"size": 16, "color": INK}},
xaxis={
"title": {"text": "Theoretical Quantiles", "font": {"size": 12, "color": INK}},
"tickfont": {"size": 10, "color": INK_SOFT},
"gridcolor": GRID,
"gridwidth": 1,
"zeroline": False,
"showline": True,
"linewidth": 1.5,
"linecolor": INK_SOFT,
"mirror": False,
},
yaxis={
"title": {"text": "Sample Quantiles", "font": {"size": 12, "color": INK}},
"tickfont": {"size": 10, "color": INK_SOFT},
"gridcolor": GRID,
"gridwidth": 1,
"zeroline": False,
"showline": True,
"linewidth": 1.5,
"linecolor": INK_SOFT,
"mirror": False,
},
legend={
"bgcolor": ELEVATED_BG,
"bordercolor": INK_SOFT,
"borderwidth": 1,
"font": {"size": 10, "color": INK_SOFT},
"x": 0.02,
"y": 0.98,
},
margin={"l": 80, "r": 40, "t": 80, "b": 60},
)
# Save
fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4)
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")
Part of Basic Q-Q Plot on anyplot.ai.