Skip to main content
NumPy · lesson 6 of 13
NumPy beginner Lesson 6 of 13

NumPy Universal Functions (ufuncs)

Use NumPy ufuncs, axis aggregations, statistics, sorting, top-k and set operations with real output, including NaN handling and the removed np.in1d.

By SkillByExample · Updated

Elementwise Math

A ufunc applies the same operation to every element and returns an array of the same shape.

import numpy as np

arr = np.array([1.0, 4.0, 9.0, 16.0])
print(np.sqrt(arr))
print(np.exp([0.0, 1.0, 2.0]).round(3))
print(np.log10([1.0, 10.0, 1000.0]))
print(np.clip([-2.0, 0.5, 1.5], 0.0, 1.0))
print(np.maximum([1, 5, 3], [4, 2, 6]))
$ python math_ufuncs.py
[1. 2. 3. 4.]
[1.    2.718 7.389]
[0. 1. 3.]
[0.  0.5 1. ]
[4 5 6]

Plain Python lists are accepted too — np.exp([0.0, 1.0, 2.0]) converts the list to an array first. np.maximum is a two-input ufunc: it compares the arrays position by position, unlike max(), which would return a single value.

Division by Zero and Invalid Values

import warnings
import numpy as np

a = np.array([1.0, 0.0, -1.0])
with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    print(a / 0)
    print(np.log(a))

for w in caught:
    print(f"{w.category.__name__}: {w.message}")
$ python warnings_demo.py
[ inf  nan -inf]
[  0. -inf  nan]
RuntimeWarning: divide by zero encountered in divide
RuntimeWarning: invalid value encountered in divide
RuntimeWarning: divide by zero encountered in log
RuntimeWarning: invalid value encountered in log

NumPy does not raise on these: it returns inf, -inf or nan and issues a RuntimeWarning. Normally Python prints each warning to stderr with the script’s path and line number; here catch_warnings(record=True) collects them so they can be printed after the results. 1/0 is inf, 0/0 is nan, and log(-1) is nan. The program keeps running, so silent NaNs can spread through a pipeline — check with np.isnan or np.isfinite.

Writing into an Existing Array with out=

import numpy as np

x = np.array([1.0, 2.0, 3.0])
result = np.empty(3)
np.multiply(x, 10, out=result)
np.sqrt(result, out=result)
print(result)
$ python out_param.py
[3.16227766 4.47213595 5.47722558]

Both calls wrote into result instead of allocating new arrays, and the second computed a square root in place. In tight loops over large arrays this avoids repeated allocations.

Aggregations and axis

import numpy as np

sales = np.array([[10, 20, 30],
                  [40, 50, 60]])

print(sales.sum())
print(sales.sum(axis=0))
print(sales.sum(axis=1))
print(sales.mean(axis=0, keepdims=True).shape)
print(np.cumsum([1, 2, 3, 4]))
print(np.argmax([88, 45, 91, 67]))
$ python aggregations.py
210
[50 70 90]
[ 60 150]
(1, 3)
[ 1  3  6 10]
2

axis=0 collapsed the two rows into one total per column; axis=1 collapsed the three columns into one total per row. keepdims=True keeps the collapsed axis with length 1 — shape (1, 3) — so the result still broadcasts against the original. argmax returns a position, not a value.

NaN-Aware Aggregations

import numpy as np

readings = np.array([21.5, np.nan, 23.0, 22.5])
print(readings.mean())
print(np.nanmean(readings))
print(np.isnan(readings).sum(), "missing")
$ python nan_aware.py
nan
22.333333333333332
1 missing

One missing value turned the plain mean into nan. np.nanmean ignores NaN entries and averages the remaining three; there are matching nansum, nanstd, nanmax and so on.

Statistics

import numpy as np

rng = np.random.default_rng(42)
heights = rng.normal(loc=170, scale=10, size=1000)
print(f"mean {heights.mean():.2f}  median {np.median(heights):.2f}  std {heights.std():.2f}")
q1, q3 = np.percentile(heights, [25, 75])
print(f"IQR {q3 - q1:.2f}")

x = rng.standard_normal(100)
y = 0.8 * x + 0.5 * rng.standard_normal(100)
print(f"corr {np.corrcoef(x, y)[0, 1]:.3f}")
$ python stats.py
mean 169.71  median 170.06  std 9.89
IQR 12.86
corr 0.780

For normally distributed data the mean and median sit close together, and the IQR comes out near the 1.35 standard deviations theory predicts (12.86 / 9.89 ≈ 1.30 for this sample). np.corrcoef returns a 2 × 2 matrix; the off-diagonal [0, 1] entry is the correlation between x and y, strongly positive by construction.

Sorting and Top-k

import numpy as np

arr = np.array([3, 1, 4, 1, 5, 9, 2, 6])
print(np.sort(arr))
print(np.argsort(arr))

top3 = np.argsort(arr)[-3:][::-1]
print(top3, arr[top3])

print(np.sort(np.array([[3, 1, 2], [6, 4, 5]]), axis=1))
$ python sorting.py
[1 1 2 3 4 5 6 9]
[1 3 6 0 2 4 7 5]
[5 7 4] [9 6 5]
[[1 2 3]
 [4 5 6]]

argsort gives the positions that would sort the array. Its last three entries point at the three largest values in ascending order, so [::-1] reverses them to largest-first. Note the two 1s: their order in the argsort output follows the default quicksort, which is not guaranteed stable — pass kind="stable" when ties must keep their original order.

Unique Values and Set Operations

import numpy as np

arr = np.array([1, 2, 3, 2, 1, 3, 3])
values, counts = np.unique(arr, return_counts=True)
print(values, counts)

a = np.array([1, 2, 3, 4, 5])
b = np.array([3, 4, 5, 6, 7])
print(np.intersect1d(a, b), np.union1d(a, b), np.setdiff1d(a, b))
print(np.isin(a, b))
print(np.any(a > 4), np.all(a > 3))
$ python sets.py
[1 2 3] [2 2 3]
[3 4 5] [1 2 3 4 5 6 7] [1 2]
[False False  True  True  True]
True False

np.unique returns sorted distinct values and, with return_counts=True, how often each appears. np.isin is the membership test — the older np.in1d name no longer exists in NumPy 2.5, as the next example shows.

The Removed np.in1d

import numpy as np

try:
    print(np.in1d([1, 2, 3], [2]))
except AttributeError as e:
    print("AttributeError:", e)
print(np.isin([1, 2, 3], [2]))
$ python in1d_removed.py
AttributeError: module 'numpy' has no attribute 'in1d'
[False  True False]

Code written for NumPy 1.x that calls np.in1d crashes on current NumPy. np.isin is the drop-in replacement.

Practice

  1. Compute the per-row maximum and its column position for [[4, 9, 2], [7, 1, 8]].
  2. Standardize each column of [[1., 10.], [2., 20.], [3., 30.]] to mean 0 and std 1 using keepdims=True.
  3. Find the two smallest values of [8, 3, 9, 1, 7] with their original positions.
  4. Average [4.0, nan, 6.0, nan, 5.0], ignoring the missing values.
Solution 1
import numpy as np

m = np.array([[4, 9, 2], [7, 1, 8]])
print(m.max(axis=1), m.argmax(axis=1))
$ python practice_rowmax.py
[9 8] [1 2]

axis=1 collapses each row, so both calls return one entry per row.

Solution 2
import numpy as np

X = np.array([[1.0, 10.0], [2.0, 20.0], [3.0, 30.0]])
Z = (X - X.mean(axis=0, keepdims=True)) / X.std(axis=0, keepdims=True)
print(Z.round(3))
print(Z.mean(axis=0), Z.std(axis=0))
$ python practice_standardize.py
[[-1.225 -1.225]
 [ 0.     0.   ]
 [ 1.225  1.225]]
[0. 0.] [1. 1.]

Each column is standardized independently, so both come out identical even though the second column is ten times larger.

Solution 3
import numpy as np

arr = np.array([8, 3, 9, 1, 7])
idx = np.argsort(arr)[:2]
print(idx, arr[idx])
$ python practice_bottom2.py
[3 1] [1 3]

The first two argsort positions are the two smallest values, already in ascending order.

Solution 4
import numpy as np

print(np.nanmean(np.array([4.0, np.nan, 6.0, np.nan, 5.0])))
$ python practice_nanmean.py
5.0

The two NaNs are skipped, leaving the mean of 4, 6 and 5.

Frequently Asked Questions

What is a ufunc?
A ufunc, or universal function, is a compiled function that operates element by element on NumPy arrays. The arithmetic operators and functions such as np.sqrt, np.exp and np.maximum are all ufuncs. They support broadcasting, dtype casting, and an out= argument that writes results into an existing array instead of allocating a new one.
What does axis= mean in aggregation functions?
axis names the dimension that gets collapsed. On a 2-D array, axis=0 collapses the rows and returns one value per column, while axis=1 collapses the columns and returns one value per row. Leaving axis out reduces the whole array to a single value.
Why does np.mean return nan for my data?
Any arithmetic involving NaN produces NaN, so a single missing value makes sum, mean and std return nan. Use the NaN-aware versions np.nansum, np.nanmean and np.nanstd, which skip NaN entries, or drop them first with arr[~np.isnan(arr)].
What replaced np.in1d in NumPy 2?
np.in1d was deprecated in NumPy 2.0 and has since been removed, so calling it raises AttributeError. Use np.isin(element, test_elements), which does the same membership test and also preserves the shape of a multi-dimensional input.
How do I get the top k values of a NumPy array?
np.argsort(arr)[-k:][::-1] returns the indices of the k largest values in descending order, and indexing arr with them gives the values. For large arrays where only the top k matter, np.argpartition(arr, -k)[-k:] is faster because it avoids a full sort, but its result is not ordered.