Pass np.clip an upper bound that sits below its lower bound and every value comes back as the bound you called the maximum. Nothing raises, the array keeps its length and its dtype, and the only way to catch it is to read the numbers. The function compares each element against two edges and returns whichever edge it crossed, which also explains why it leaves NaN alone and why an integer array stays integer.
What np.clip does
np.clip takes an array and two edges, then returns a new array where nothing sits outside those edges. Values below the lower edge become the lower edge, values above the upper edge become the upper edge, and everything between them is copied through unchanged.
import numpy as np
a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
x = np.clip(a, 4, 8)
print("a:", a)
print("x:", x)
print("dtype:", x.dtype)
a: [0 1 2 3 4 5 6 7 8 9]
x: [4 4 4 4 4 5 6 7 8 8]
dtype: int64
The output is the same length as the input and holds the same dtype, because clipping changes values rather than the container. That is what makes it safe to drop into an existing pipeline without reshaping anything downstream.
Reach for it whenever a value has to stay inside a physical or logical range, such as a voltage ceiling or a sensor reading that cannot fall below zero.
One pass over the array replaces a Python loop that would otherwise compare every element twice. The edges can also arrive as arrays, which is where the loop stops being a reasonable substitute at all.
The signature and the two keyword spellings
The positional form takes the array, then the lower edge, then the upper edge. The keyword forms come in two spellings, and both work in current NumPy.
import inspect
import numpy as np
print("numpy", np.__version__)
print("clip" + str(inspect.signature(np.clip)))
numpy 2.5.3
clip(a, a_min=, a_max=, out=None, *, min=, max=, **kwargs)
Everything after the asterisk is keyword-only, so min and max cannot be passed positionally while a_min and a_max accept either form.
| Name | Position | Notes |
|---|---|---|
| a | 1st | The array or scalar to clip. |
| a_min | 2nd or keyword | Lower edge. None leaves the bottom unbounded. |
| a_max | 3rd or keyword | Upper edge. None leaves the top unbounded. |
| out | 4th or keyword | Destination array. Must have a compatible dtype. |
| min, max | keyword only | Current spelling of the same two edges. |
Passing None for one edge clips in one direction only, which is the shortest way to write a floor or a ceiling.
import numpy as np
a = np.array([-5, 0, 5, 15])
print("upper only:", np.clip(a, None, 10))
print("lower only:", np.clip(a, 0, None))
upper only: [-5 0 5 10]
lower only: [ 0 0 5 15]
Clipping a one-dimensional array
A one-dimensional array behaves the way the description promises, and the interesting case is what happens when the two edges are the wrong way round.
import numpy as np
a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
print("a :", a)
print("clip(a, 3, 6) :", np.clip(a, 3, 6))
print("clip(a, 8, 1) :", np.clip(a, 8, 1))
With the edges in the right order the array keeps 4 and 5 and flattens everything else onto the nearest edge. With 8 as the lower edge and 1 as the upper edge every element satisfies both comparisons at the same time, so all nine values collapse onto 1.
That second result is easy to produce accidentally when the two edges come from variables and the variable order gets swapped.
Reading the output against the input is the fastest check. If every element in the result is identical, the two edges crossed somewhere before the call.
Clipping two-dimensional arrays
Higher dimensions need no extra argument. The same two edges apply to every element, and the shape comes back untouched.
import numpy as np
a = np.array([[1, -5, 12],
[7, 20, -3]])
print(np.clip(a, 0, 10))
print("shape preserved:", np.clip(a, 0, 10).shape)
[[ 1 0 10]
[ 7 10 0]]
shape preserved: (2, 3)
Every entry is compared against 0 and 10 independently, so negative values climb to 0 and anything above 10 drops to 10 wherever it sits in the grid.
There is no axis argument to set, because clipping is not a reduction. Nothing is combined across rows or columns, and the shape never changes.
Using array bounds instead of scalars
The edges do not have to be single numbers. Array-like bounds are broadcast against the input, which lets each position carry its own limit.
import numpy as np
a = np.arange(10)
lower = [3, 4, 1, 1, 1, 4, 4, 4, 4, 4]
x = np.clip(a, lower, 6)
print("a :", a)
print("lower:", lower)
print("x :", x)
a : [0 1 2 3 4 5 6 7 8 9]
lower: [3, 4, 1, 1, 1, 4, 4, 4, 4, 4]
x : [3 4 2 3 4 5 6 6 6 6]
Index 2 shows the effect clearly. Its lower edge is 1, so the value 2 survives there even though a scalar lower edge of 3 would have raised it.
A bound array with a shape that lines up against a column broadcasts down the rows, so a matrix can carry a separate range for each column.
import numpy as np
a = np.array([[1, 2, 3],
[4, 5, 6]])
print(np.clip(a, [2, 2, 2], [4, 4, 4]))
| Bound shape | Effect |
|---|---|
| Scalar | The same edge applies to every element. |
| Same shape as the input | Each element gets its own lower and upper edge. |
| One value per column | Each column keeps its own range and the bounds repeat down the rows. |
When the minimum is greater than the maximum
Reversed edges are not an error case, and the documented behavior is worth remembering. Every element ends up equal to the upper edge, because no value can satisfy both comparisons at once.
import numpy as np
a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
print("clip(a, 8, 1):", np.clip(a, 8, 1))
print("clip(a, 5, 5):", np.clip(a, 5, 5))
clip(a, 8, 1): [1 1 1 1 1 1 1 1 1]
clip(a, 5, 5): [5 5 5 5 5 5 5 5 5]
Equal edges collapse the array to a single constant, and reversed edges collapse it to the upper one. Neither case warns, so an assertion on the edge order is cheaper than debugging the output later.
This shows up when a scale factor is applied to both edges and turns out to be negative. A signed multiplier flips the order silently, and the array comes back as a constant that looks like a legitimate result.
Clipping in place and what happens to NaN
Passing the input as out writes the clipped values back into the same buffer and returns it, so no second array is allocated.
import numpy as np
a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
x = np.clip(a, 3, 6, out=a)
print("x:", x)
print("a:", a)
print("x is a:", x is a)
x: [3 3 3 3 4 5 6 6 6]
a: [3 3 3 3 4 5 6 6 6]
x is a: True
The returned array is the same object as the input, so anything still holding the original values now sees the clipped ones. That saves a full allocation and couples the two names together.
NaN is the other behavior that surprises people, because it is neither below the lower edge nor above the upper edge.
import numpy as np
a = np.array([1.0, np.nan, 12.0, -4.0])
b = np.clip(a, 0, 10)
print("input :", a)
print("clipped :", b)
print("in range :", bool((b >= 0).all() and (b <= 10).all()))
print("finite entries :", int(np.isfinite(b).sum()), "of", b.size)

The two out-of-range values are repaired while the NaN travels through unchanged. Any comparison against NaN is False, so the range check reports a failure and the finite count comes back one short.
Clean the NaN out before clipping rather than after, using a mask or np.nan_to_num. Clipping is not a validation step, and it will not tell you that a value was missing.
Common questions about np.clip
The cases below are the ones that produce a plausible-looking array instead of an error.
Does np.clip modify the original array?
Only when you pass the input as out. Without out, np.clip allocates a new array and leaves the input untouched. With out=a the returned array is the same object as a, so every existing reference to a now sees the clipped values.
What happens when a_min is greater than a_max?
Every element becomes a_max, because no value can be both at least a_min and at most a_max. NumPy does not warn or raise, so assert the edge order when the bounds come from variables.
Does np.clip remove NaN values?
It does not. NaN compares False against both edges, so it passes through unchanged. Filter the array with np.isfinite or replace the missing entries with np.nan_to_num before clipping.
Can the bounds be arrays instead of single numbers?
Yes. The bounds broadcast against the input, so a bound array of the same length gives every element its own limit and a column-shaped bound array gives each column its own range.
Is np.clip the same as np.minimum and np.maximum?
For a simple range the results are identical, and np.minimum(np.maximum(a, lo), hi) returns the same array as np.clip(a, lo, hi). np.clip is the shorter form and it also supports the out and None-edge arguments directly.

