Compute a 3-element sliding-window average over an ordered numeric sequence. By hand, the first two positions cannot form a full window so they get None; positions 2 onward average the current element and the two before it. With pandas, Series.rolling(3).mean() handles the window and fills the incomplete prefix with NaN.

By hand

Walk range(len(readings)). Indices 0 and 1 have fewer than 3 elements available, so append None. From index 2 onward, slice the 3-element window readings[i - 2: i + 1] into w, compute round(sum(w) / 3, 2), and append. The trace shows w refreshing each iteration alongside the growing result.

naive.py
Replay: real traced execution (multi-file project)
readings = [6, 9, 12, 9, 15, 9]
result = []
for i in range(len(readings)):
    if i < 2:
        result.append(None)
    else:
        w = readings[i - 2: i + 1]
        result.append(round(sum(w) / 3, 2))
print('RESULT:', result)
  1. readings ← [6, 9, 12, 9, 15, 9]

    1readings = [6, 9, 12, 9, 15, 9]2result = []
    values this step[6, 9, 12, 9, 15, 9]readings
  2. result ← []

    1readings = [6, 9, 12, 9, 15, 9]2result = []3for i in range(len(readings)):
    values this step[]result
  3. i ← 0

    2result = []3for i in range(len(readings)):4    if i < 2:
    values this step0i
  4. if i < 2:

    3for i in range(len(readings)):4    if i < 2:5        result.append(None)
  5. result ← [None]

    4if i < 2:5    result.append(None)6else:
    values this step[] [None]result
  6. i ← 1

    2result = []3for i in range(len(readings)):4    if i < 2:
    values this step0 1i
  7. if i < 2:

    3for i in range(len(readings)):4    if i < 2:5        result.append(None)
  8. result ← [None, None]

    4if i < 2:5    result.append(None)6else:
    values this step[None] [None, None]result
  9. i ← 2

    2result = []3for i in range(len(readings)):4    if i < 2:
    values this step1 2i
  10. if i < 2:

    3for i in range(len(readings)):4    if i < 2:5        result.append(None)
  11. w ← [6, 9, 12]

    6else:7    w = readings[i - 2: i + 1]8    result.append(round(sum(w) / 3, 2))
    values this step[6, 9, 12]w
  12. result ← [None, None, 9.0]

    7        w = readings[i - 2: i + 1]8        result.append(round(sum(w) / 3, 2))9print('RESULT:', result)
    values this step[None, None] [None, None, 9.0]result
  13. i ← 3

    2result = []3for i in range(len(readings)):4    if i < 2:
    values this step2 3i
  14. if i < 2:

    3for i in range(len(readings)):4    if i < 2:5        result.append(None)
  15. w ← [9, 12, 9]

    6else:7    w = readings[i - 2: i + 1]8    result.append(round(sum(w) / 3, 2))
    values this step[6, 9, 12] [9, 12, 9]w
  16. result ← [None, None, 9.0, 10.0]

    7        w = readings[i - 2: i + 1]8        result.append(round(sum(w) / 3, 2))9print('RESULT:', result)
    values this step[None, None, 9.0] [None, None, 9.0, 10.0]result
  17. i ← 4

    2result = []3for i in range(len(readings)):4    if i < 2:
    values this step3 4i
  18. if i < 2:

    3for i in range(len(readings)):4    if i < 2:5        result.append(None)
  19. w ← [12, 9, 15]

    6else:7    w = readings[i - 2: i + 1]8    result.append(round(sum(w) / 3, 2))
    values this step[9, 12, 9] [12, 9, 15]w
  20. result ← [None, None, 9.0, 10.0, 12.0]

    7        w = readings[i - 2: i + 1]8        result.append(round(sum(w) / 3, 2))9print('RESULT:', result)
    values this step[None, None, 9.0, 10.0] [None, None, 9.0, 10.0, 12.0]result
  21. i ← 5

    2result = []3for i in range(len(readings)):4    if i < 2:
    values this step4 5i
  22. if i < 2:

    3for i in range(len(readings)):4    if i < 2:5        result.append(None)
  23. w ← [9, 15, 9]

    6else:7    w = readings[i - 2: i + 1]8    result.append(round(sum(w) / 3, 2))
    values this step[12, 9, 15] [9, 15, 9]w
  24. result ← [None, None, 9.0, 10.0, 12.0, 11.0]

    7        w = readings[i - 2: i + 1]8        result.append(round(sum(w) / 3, 2))9print('RESULT:', result)
    values this step[None, None, 9.0, 10.0, 12.0] [None, None, 9.0, 10.0, 12.0, 11.0]result
  25. for i in range(len(readings)):

    2result = []3for i in range(len(readings)):4    if i < 2:
  26. stdout ← RESULT: [None, None, 9.0, 10.0, 12.0, 11.0]

    8        result.append(round(sum(w) / 3, 2))9print('RESULT:', result)
    values this stepRESULT: [None, None, 9.0, 10.0, 12.0, 11.0]stdout

With pandas

df['x'].rolling(3).mean() computes the mean of each 3-row window in one call. The first two rows have no complete window, so they are NaN and the column is float64. The snapshot shows the raw output; result converts NaN → None and rounds the finite values for comparison with the naive half.

library.py
import math
import pandas as pd
from dalib.display import set_display
set_display()

readings = [6, 9, 12, 9, 15, 9]
df = pd.DataFrame({'x': readings})
r = df['x'].rolling(3).mean()
result = [round(v, 2) if not math.isnan(v) else None for v in r.tolist()]
print('index:', r.index.tolist())
print('dtype:', r.dtype)
print('values raw:', r.tolist())
print('RESULT:', result)
index: [0, 1, 2, 3, 4, 5]
dtype: float64
values raw: [nan, nan, 9.0, 10.0, 12.0, 11.0]
RESULT: [None, None, 9.0, 10.0, 12.0, 11.0]

Implementation notes

  • The min_periods parameter controls how many non-NaN values are required to compute a result. rolling(3, min_periods=1) fills the prefix instead of returning NaN.
  • rolling also supports sum(), min(), max(), std(), and apply(fn) for custom window functions.
  • The float64 upcast and NaN prefix mirror diff() — use math.isnan or pd.isna when normalising for comparison.
  • Cross-reference: diff-previous-row (this chapter) for a single-step lag instead of an aggregated window.
  • Cross-reference: running-total (python-data-basics) for the cumulative-sum approach — accumulating all prior values rather than a fixed-size window.