Displays the autocorrelation function (ACF) and partial autocorrelation function (PACF) of a time series as vertical stem/bar plots arranged in two vertically stacked subplots. Each lag is represented by a vertical line from zero to the correlation value, with horizontal dashed lines indicating 95% confidence bounds. These plots are essential for identifying the order of AR and MA components in ARIMA modeling and for diagnosing residual independence.

// anyplot.ai
// acf-pacf: Autocorrelation and Partial Autocorrelation (ACF/PACF) Plot
// Library: d3 7.9.0 | JavaScript 22.22.3
// Quality: 90/100 | Created: 2026-06-10
//# anyplot-orientation: landscape
const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;
// Deterministic LCG (no seeded RNG in browser)
let _seed = 42;
function lcg() {
_seed = (Math.imul(_seed, 1664525) + 1013904223) >>> 0;
return _seed / 4294967296;
}
function randn() {
return Math.sqrt(-2 * Math.log(Math.max(lcg(), 1e-12))) * Math.cos(2 * Math.PI * lcg());
}
// AR(2) series: x_t = 0.7·x_{t-1} − 0.3·x_{t-2} + ε_t
// Stationary; PACF cuts off after lag 2 — canonical AR-order diagnostic
const N = 200;
const series = [randn(), 0.7 * randn() + randn()];
for (let i = 2; i < N; i++) {
series.push(0.7 * series[i - 1] - 0.3 * series[i - 2] + randn());
}
// ACF: r(k) = Cov(x_t, x_{t-k}) / Var(x)
function computeACF(x, maxLag) {
const n = x.length;
const mean = x.reduce((s, v) => s + v, 0) / n;
const variance = x.reduce((s, v) => s + (v - mean) ** 2, 0) / n;
return Array.from({ length: maxLag + 1 }, (_, k) => {
if (k === 0) return 1.0;
let cov = 0;
for (let i = k; i < n; i++) cov += (x[i] - mean) * (x[i - k] - mean);
return cov / (n * variance);
});
}
// PACF via Durbin-Levinson: phi_{k,k} is partial autocorrelation at lag k
function computePACF(acf, maxLag) {
const phi = Array.from({ length: maxLag + 1 }, () => new Array(maxLag + 1).fill(0));
const pacf = new Array(maxLag + 1).fill(0);
pacf[0] = 1.0;
for (let k = 1; k <= maxLag; k++) {
let num = acf[k], den = 1.0;
for (let j = 1; j < k; j++) {
num -= phi[k - 1][j] * acf[k - j];
den -= phi[k - 1][j] * acf[j];
}
phi[k][k] = Math.abs(den) > 1e-10 ? num / den : 0;
for (let j = 1; j < k; j++) {
phi[k][j] = phi[k - 1][j] - phi[k][k] * phi[k - 1][k - j];
}
pacf[k] = phi[k][k];
}
return pacf;
}
const maxLag = 30;
const acfVals = computeACF(series, maxLag);
const pacfVals = computePACF(acfVals, maxLag);
const confBand = 1.96 / Math.sqrt(N);
// Layout
const margin = { top: 70, right: 60, bottom: 70, left: 80 };
const iw = width - margin.left - margin.right;
const panelGap = 40;
const panelH = (height - margin.top - margin.bottom - panelGap) / 2;
const svg = d3
.select("#container")
.append("svg")
.attr("width", width)
.attr("height", height);
const root = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
// Draw an ACF or PACF panel
function drawPanel(g, data, yLabel, color, yTop, showXLabels) {
const vals = data.map((d) => d.val);
const vMin = Math.min(d3.min(vals), -confBand) - 0.08;
const vMax = Math.max(d3.max(vals), confBand, yLabel === "ACF" ? 1.0 : 0) + 0.08;
const xScale = d3.scaleLinear().domain([-0.5, maxLag + 0.5]).range([0, iw]);
const yScale = d3.scaleLinear().domain([vMin, vMax]).nice().range([panelH, 0]);
const pg = g.append("g").attr("transform", `translate(0,${yTop})`);
// Horizontal gridlines
for (const tick of yScale.ticks(5)) {
pg.append("line")
.attr("x1", 0)
.attr("x2", iw)
.attr("y1", yScale(tick))
.attr("y2", yScale(tick))
.attr("stroke", t.grid)
.attr("stroke-width", 1);
}
// Zero baseline
pg.append("line")
.attr("x1", 0)
.attr("x2", iw)
.attr("y1", yScale(0))
.attr("y2", yScale(0))
.attr("stroke", t.inkSoft)
.attr("stroke-width", 1.5)
.attr("opacity", 0.5);
// Confidence band fill (amber, very subtle)
pg.append("rect")
.attr("x", 0)
.attr("width", iw)
.attr("y", yScale(confBand))
.attr("height", yScale(-confBand) - yScale(confBand))
.attr("fill", "#DDCC77")
.attr("opacity", 0.08);
// Confidence band dashed boundary lines
for (const cb of [confBand, -confBand]) {
pg.append("line")
.attr("x1", 0)
.attr("x2", iw)
.attr("y1", yScale(cb))
.attr("y2", yScale(cb))
.attr("stroke", "#DDCC77")
.attr("stroke-width", 1.5)
.attr("stroke-dasharray", "8,5");
}
// Stem lines
pg.selectAll(".stem")
.data(data)
.join("line")
.attr("class", "stem")
.attr("x1", (d) => xScale(d.lag))
.attr("x2", (d) => xScale(d.lag))
.attr("y1", yScale(0))
.attr("y2", (d) => yScale(d.val))
.attr("stroke", color)
.attr("stroke-width", 2.5);
// Tip markers
pg.selectAll(".dot")
.data(data)
.join("circle")
.attr("class", "dot")
.attr("cx", (d) => xScale(d.lag))
.attr("cy", (d) => yScale(d.val))
.attr("r", 4.5)
.attr("fill", color)
.attr("stroke", t.pageBg)
.attr("stroke-width", 1);
// Y axis
const yAx = pg.append("g").call(d3.axisLeft(yScale).ticks(5).tickSize(5));
yAx.selectAll("text").attr("fill", t.inkSoft).style("font-size", "13px");
yAx.selectAll(".tick line").attr("stroke", t.inkSoft);
yAx.select(".domain").attr("stroke", t.inkSoft);
// X axis
const xAx = pg
.append("g")
.attr("transform", `translate(0,${panelH})`)
.call(d3.axisBottom(xScale).ticks(10).tickSize(5));
if (showXLabels) {
xAx.selectAll("text").attr("fill", t.inkSoft).style("font-size", "13px");
} else {
xAx.selectAll("text").remove();
}
xAx.selectAll(".tick line").attr("stroke", t.inkSoft);
xAx.select(".domain").attr("stroke", t.inkSoft);
// Y-axis label (rotated)
pg.append("text")
.attr("transform", `translate(${-60},${panelH / 2}) rotate(-90)`)
.attr("text-anchor", "middle")
.attr("fill", t.ink)
.style("font-size", "15px")
.style("font-weight", "600")
.text(yLabel);
}
// ACF panel: lags 0 … maxLag (lag 0 = 1.0)
drawPanel(
root,
acfVals.map((v, k) => ({ lag: k, val: v })),
"ACF",
t.palette[0],
0,
false
);
// PACF panel: lags 1 … maxLag (skip lag 0)
drawPanel(
root,
pacfVals.slice(1).map((v, i) => ({ lag: i + 1, val: v })),
"PACF",
t.palette[2],
panelH + panelGap,
true
);
// X-axis label
svg
.append("text")
.attr("x", margin.left + iw / 2)
.attr("y", height - 22)
.attr("text-anchor", "middle")
.attr("fill", t.inkSoft)
.style("font-size", "14px")
.text("Lag");
// Title
svg
.append("text")
.attr("x", width / 2)
.attr("y", 44)
.attr("text-anchor", "middle")
.attr("fill", t.ink)
.style("font-size", "22px")
.style("font-weight", "600")
.text("Monthly Retail Sales · acf-pacf · javascript · d3 · anyplot.ai");
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/acf-pacf/d3/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.
{
"spec_id": "acf-pacf",
"language": "javascript",
"library": "d3",
"page": "https://anyplot.ai/acf-pacf/javascript/d3",
"hub": "https://anyplot.ai/acf-pacf",
"code_json": "https://api.anyplot.ai/specs/acf-pacf/d3/code",
"spec_json": "https://api.anyplot.ai/specs/acf-pacf",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-dark.png",
"interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-light.html",
"interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/acf-pacf/javascript/d3/plot-dark.html",
"quality_score": 90.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Autocorrelation and Partial Autocorrelation (ACF/PACF) Plot on anyplot.ai.