Outliers and Ranges
Clip Values
Cap each value to a [lo, hi] range. Values below lo become lo, values above
hi become hi, and values already in range are unchanged. By hand, apply a
three-way branch in a loop. With pandas, Series.clip(lo, hi) performs the
same capping in one call.
By hand
For each value, check if v < lo → append lo, elif v > hi → append hi,
else → append v unchanged. The trace shows all three branches in action:
under-range inputs (3 and 2) hit the first branch, over-range inputs (42
and 28) hit the second, and in-range inputs fall through to else.
naive.py
Replay: real traced execution (multi-file project)
values = [3, 15, 7, 42, 11, 2, 28, 9]
lo = 5
hi = 25
result = []
for v in values:
if v < lo:
result.append(lo)
elif v > hi:
result.append(hi)
else:
result.append(v)
print('RESULT:', result)
values ← [3, 15, 7, 42, 11, 2, 28, 9]
1values = [3, 15, 7, 42, 11, 2, 28, 9]2lo = 5values this step[3, 15, 7, 42, 11, 2, 28, 9]valueslo ← 5
1values = [3, 15, 7, 42, 11, 2, 28, 9]2lo = 53hi = 25values this step5lohi ← 25
2lo = 53hi = 254result = []values this step25hiresult ← []
3hi = 254result = []5for v in values:values this step[]resultv ← 3
4result = []5for v in values:6 if v < lo:values this step3vif v < lo:
5for v in values:6 if v < lo:7 result.append(lo)result ← [5]
6if v < lo:7 result.append(lo)8elif v > hi:values this step[] → [5]resultv ← 15
4result = []5for v in values:6 if v < lo:values this step3 → 15vif v < lo:
5for v in values:6 if v < lo:7 result.append(lo)elif v > hi:
7 result.append(lo)8elif v > hi:9 result.append(hi)result ← [5, 15]
10 else:11 result.append(v)12print('RESULT:', result)values this step[5] → [5, 15]resultv ← 7
4result = []5for v in values:6 if v < lo:values this step15 → 7vif v < lo:
5for v in values:6 if v < lo:7 result.append(lo)elif v > hi:
7 result.append(lo)8elif v > hi:9 result.append(hi)result ← [5, 15, 7]
10 else:11 result.append(v)12print('RESULT:', result)values this step[5, 15] → [5, 15, 7]resultv ← 42
4result = []5for v in values:6 if v < lo:values this step7 → 42vif v < lo:
5for v in values:6 if v < lo:7 result.append(lo)elif v > hi:
7 result.append(lo)8elif v > hi:9 result.append(hi)result ← [5, 15, 7, 25]
8elif v > hi:9 result.append(hi)10else:values this step[5, 15, 7] → [5, 15, 7, 25]resultv ← 11
4result = []5for v in values:6 if v < lo:values this step42 → 11vif v < lo:
5for v in values:6 if v < lo:7 result.append(lo)elif v > hi:
7 result.append(lo)8elif v > hi:9 result.append(hi)result ← [5, 15, 7, 25, 11]
10 else:11 result.append(v)12print('RESULT:', result)values this step[5, 15, 7, 25] → [5, 15, 7, 25, 11]resultv ← 2
4result = []5for v in values:6 if v < lo:values this step11 → 2vif v < lo:
5for v in values:6 if v < lo:7 result.append(lo)result ← [5, 15, 7, 25, 11, 5]
6if v < lo:7 result.append(lo)8elif v > hi:values this step[5, 15, 7, 25, 11] → [5, 15, 7, 25, 11, 5]resultv ← 28
4result = []5for v in values:6 if v < lo:values this step2 → 28vif v < lo:
5for v in values:6 if v < lo:7 result.append(lo)elif v > hi:
7 result.append(lo)8elif v > hi:9 result.append(hi)result ← [5, 15, 7, 25, 11, 5, 25]
8elif v > hi:9 result.append(hi)10else:values this step[5, 15, 7, 25, 11, 5] → [5, 15, 7, 25, 11, 5, 25]resultv ← 9
4result = []5for v in values:6 if v < lo:values this step28 → 9vif v < lo:
5for v in values:6 if v < lo:7 result.append(lo)elif v > hi:
7 result.append(lo)8elif v > hi:9 result.append(hi)result ← [5, 15, 7, 25, 11, 5, 25, 9]
10 else:11 result.append(v)12print('RESULT:', result)values this step[5, 15, 7, 25, 11, 5, 25] → [5, 15, 7, 25, 11, 5, 25, 9]resultfor v in values:
4result = []5for v in values:6 if v < lo:stdout ← RESULT: [5, 15, 7, 25, 11, 5, 25, 9]
11 result.append(v)12print('RESULT:', result)values this stepRESULT: [5, 15, 7, 25, 11, 5, 25, 9]stdout
With pandas
Series.clip(lo, hi) caps every element to the given bounds in one pass.
The snapshot shows values before and after so the replacements are
immediately visible.
library.py
import pandas as pd
from dalib.display import set_display
set_display()
values = [3, 15, 7, 42, 11, 2, 28, 9]
lo = 5
hi = 25
df = pd.DataFrame({'x': values})
clipped = df['x'].clip(lo, hi)
result = clipped.tolist()
print('index:', clipped.index.tolist())
print('dtype:', clipped.dtype)
print('values before:', df['x'].tolist())
print('values after:', clipped.tolist())
print('RESULT:', result)
index: [0, 1, 2, 3, 4, 5, 6, 7]
dtype: int64
values before: [3, 15, 7, 42, 11, 2, 28, 9]
values after: [5, 15, 7, 25, 11, 5, 25, 9]
RESULT: [5, 15, 7, 25, 11, 5, 25, 9]
Implementation notes
clippreserves the dtype when all values and bounds are the same numeric type; integer input with integer bounds returns int64.- Clipping to a fixed range is sometimes called Winsorizing — replacing extremes with boundary values rather than discarding them.
- Pass only one bound to clip on one side:
s.clip(lower=lo)ors.clip(upper=hi). - Cross-reference:
where-replace(numpy chapter) for conditional replacement usingnp.where.