The frame clause defines an intent of N rows, but at the start of a partition there aren't N rows yet, and AVG quietly averages whatever the frame contains. Nothing is wrong mechanically — the wrongness is in the chart legend that says '3-day average' over points that are 1-day averages. Three honest treatments: exclude the warm-up rows, flag them (a partial boolean via COUNT(*) OVER the same frame < N), or accept and say so.
SELECT d, AVG(n) OVER (ORDER BY d ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) AS avg3 FROM visits ORDER BY d;
SELECT d, AVG(n) OVER w AS avg3, (COUNT(*) OVER w < 3) AS partial FROM visits WINDOW w AS ( ORDER BY d ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) ORDER BY d;
The first rows of the 3-day average ran over partial windows — day 1 averaged one point, day 2 two — but the chart called every point a '3-day average'.
Exclude the warm-up rows, flag them with a partial column, or disclose in the label — the deliverable is the disclosure, not a different number.