Masked Arrays (np.ma)
Reviewed & published by Brayan K
A masked array pairs your data with a boolean mask that flags invalid or missing values, so NumPy automatically excludes them from means, sums, and other reductions without you deleting or overwriting anything.
Part of the free NumPy course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You'll learn to build masked arrays, hide sentinel values like −999, run reductions that skip the masked entries, and convert back to a plain array with filled() — plus when a mask beats a NaN.
What You'll Learn in This Lesson
- Creating a masked array with np.ma.masked_array
- Masking sentinel values with masked_equal / masked_greater
- Running reductions that ignore masked elements
- Recovering data with filled() and compressed()
- Working across an axis with masked 2D arrays
- When masked arrays beat NaN-based workflows
1 Building a Masked Array
Real-world data is full of placeholders: a sensor that failed records -999, a survey leaves a blank as -1. A masked array lets you mark those entries as invalid while keeping the original numbers intact. NumPy prints masked elements as -- and skips them in calculations. The fastest way to mask a sentinel is np.ma.masked_equal(data, value).
import numpy as np
data = np.array([10, 20, -999, 40, -999, 60])
# Mark every -999 as invalid
masked = np.ma.masked_equal(data, -999)
print(masked) # [10 20 -- 40 -- 60]
# Reductions automatically skip the masked values
print(masked.mean()) # 32.5
print(masked.sum()) # 130
# ✅ Expected output:
# [10 20 -- 40 -- 60]
# 32.5
# 130You can also supply the mask directly with np.ma.masked_array(data, mask=[...]) when you already know which positions are bad. .compressed() returns just the valid values as a plain 1D array.
import numpy as np
# True positions are masked (hidden)
a = np.ma.masked_array([1, 2, 3, 4], mask=[False, True, False, True])
print(a) # [1 -- 3 --]
print(a.mask) # [False True False True]
# Drop the masked entries entirely
print(a.compressed()) # [1 3]
# ✅ Expected output:
# [1 -- 3 --]
# [False True False True]
# [1 3]2 Masked Reductions
Once values are masked, every reduction — mean, sum, max, min, std — ignores them. np.ma also gives you helpers that build the mask from a condition: masked_greater, masked_less, masked_outside, and masked_invalid (for NaN/inf). .count() tells you how many valid values remain.
import numpy as np
# 999.0 is a stuck-sensor error code
temps = np.array([22.5, 23.1, 999.0, 21.8, 999.0, 22.0])
clean = np.ma.masked_greater(temps, 100)
print(clean.mean()) # 22.35
print(clean.max()) # 23.1
print(clean.min()) # 21.8
print(clean.count()) # 4 (valid readings)
# ✅ Expected output:
# 22.35
# 23.1
# 21.8
# 43 filled() and Masked vs NaN
When you are done, filled(value) substitutes a chosen value for every masked element and hands back an ordinary array — useful for exporting or plotting. This is where masked arrays beat NaN: a plain arr.mean() on data containing np.nan returns nan (the NaN "poisons" the result), but a masked array's mean() just works. Masks also handle integer arrays, which cannot hold NaN at all.
import numpy as np
mm = np.ma.masked_array([1, 2, 3, 4], mask=[False, True, True, False])
# Replace masked entries to get a normal array back
print(mm.filled(0)) # [1 0 0 4]
print(mm.filled(-1)) # [ 1 -1 -1 4]
print(mm.mean()) # 2.5 (only 1 and 4 count)
# Compare with a NaN array
arr_nan = np.array([1.0, 2.0, np.nan, 4.0])
print(arr_nan.mean()) # nan (poisoned!)
print(np.ma.masked_invalid(arr_nan).mean()) # 2.3333333333333335
# ✅ Expected output:
# [1 0 0 4]
# [ 1 -1 -1 4]
# 2.5
# nan
# 2.3333333333333335🎯 YOUR TURN: Fill in the Blanks
Replace each ___ so the program masks the -1 placeholders, averages the rest, then fills the gaps with 0.
import numpy as np
readings = np.array([5, 8, -1, 12, -1, 20])
# Mask every -1 sentinel
masked = np.ma.___(readings, -1) # fill in: masked_equal
print(masked.___()) # fill in: mean -> 11.25
print(masked.___(0)) # fill in: filled -> [ 5 8 0 12 0 20]
# Expected output:
# 11.25
# [ 5 8 0 12 0 20]Expected output: 11.25 then [ 5 8 0 12 0 20]. (Answers: masked_equal, mean, filled.)
⚠️ Common Errors & Quick Reference
❌ Using np.mean(masked) instead of masked.mean()
Top-level np.mean may not respect the mask the way you expect.
✅ Fix: call the method on the masked array — masked.mean() — or use np.ma.mean(masked), both of which honor the mask.
❌ Forgetting that the data is still there
masked.data still contains the original sentinel values; only the mask hides them.
✅ Fix: use filled(value) to overwrite masked entries or compressed() to drop them before exporting.
| Task | Code |
|---|---|
| Mask a sentinel value | np.ma.masked_equal(a, -999) |
| Mask values above a limit | np.ma.masked_greater(a, 100) |
| Mask NaN / inf | np.ma.masked_invalid(a) |
| Replace masked with a value | m.filled(0) |
| Keep only valid values | m.compressed() |
🎯 Mini Challenge: Column Means with Missing Sales
A sales table uses -1 for days a store was closed. Mask them, then compute each column's mean ignoring the closures, and fill any all-masked result with 0.
import numpy as np
sales = np.array([[100, 200, -1],
[-1, 150, 300],
[120, -1, 250]])
m = np.ma.masked_equal(sales, -1)
# Mean of each column, ignoring the -1 closures
print(m.mean(axis=0).filled(0)) # [110. 175. 275.]
print(m.mean(axis=1)) # [150.0 225.0 185.0]
print(m.sum()) # 1120
# ✅ Expected output:
# [110. 175. 275.]
# [150.0 225.0 185.0]
# 1120❓ Frequently Asked Questions
Lesson complete — masked arrays unlocked!
You can now flag invalid data with np.ma.masked_array and helpers like masked_equal, run reductions that skip masked elements, recover plain arrays with filled() and compressed(), and explain why a mask sidesteps the NaN-poisoning problem.
Practice quiz
What does a masked array carry alongside its data?
- A boolean mask marking invalid elements
- A second copy of the data
- A list of column names
- A timestamp
Answer: A boolean mask marking invalid elements. A masked array pairs the data with a boolean mask flagging invalid entries.
Which function masks every occurrence of a sentinel value like -999?
- np.ma.compressed
- np.ma.masked_equal
- np.ma.filled
- np.ma.average
Answer: np.ma.masked_equal. np.ma.masked_equal(data, -999) masks each element equal to the sentinel.
How does NumPy print a masked element?
- NaN
- 0
- --
- null
Answer: --. Masked elements are displayed as -- in the array output.
For np.ma.masked_equal([10,20,-999,40,-999,60], -999), what is .mean()?
- 32.5
- 0.0
- -999.0
- 21.5
Answer: 32.5. Masking the two -999 values leaves 10,20,40,60 with a mean of 32.5.
What does .compressed() return from a masked array?
- The mask only
- The fill value
- Only the valid values as a plain 1D array
- A 2D copy
Answer: Only the valid values as a plain 1D array. .compressed() drops the masked entries and returns the valid values.
What does filled(0) do to a masked array?
- Deletes the array
- Replaces masked elements with 0 and returns a plain array
- Masks every zero
- Adds zeros to the end
Answer: Replaces masked elements with 0 and returns a plain array. filled(value) substitutes the value for masked entries, returning an ndarray.
Why does a mask beat NaN for an integer array?
- Integers store NaN fine
- NaN is faster
- Integer arrays cannot hold NaN, but a mask works on any dtype
- Masks use less code always
Answer: Integer arrays cannot hold NaN, but a mask works on any dtype. NaN is a float-only value; a mask works with any dtype including integers.
Which helper masks NaN or inf values in an array?
- np.ma.masked_invalid
- np.ma.masked_equal
- np.ma.filled
- np.ma.count
Answer: np.ma.masked_invalid. np.ma.masked_invalid masks NaN and inf entries automatically.
What does .count() report on a masked array?
- The number of masked elements
- The fill value
- The array size in bytes
- The number of valid (unmasked) values
Answer: The number of valid (unmasked) values. .count() returns how many valid, unmasked values remain.
Why does a plain arr.mean() return nan when arr contains np.nan?
- NaN poisons the reduction
- mean is undefined
- It rounds down
- It ignores NaN by default
Answer: NaN poisons the reduction. A single NaN poisons a normal reduction, so the result is nan; a masked mean avoids this.
Continue this course
- Previous: Checkpoint: Numerical Computing
- Next: Fourier Transforms (np.fft) — Analyse frequency content of signals with the FFT and rfft
- Quick reference: NumPy cheat sheet
Frequently asked questions
What is a masked array in NumPy?
A masked array is an array that carries a parallel boolean mask marking certain elements as invalid or missing. The data stays in place, but any element whose mask is True is excluded from calculations. You create one with np.ma.masked_array(data, mask=...) or helpers like np.ma.masked_equal, and reductions such as mean() and sum() automatically skip the masked values.
What is the difference between a masked array and using NaN?
NaN is a special floating-point value, so it only works with float arrays and it poisons ordinary reductions: arr.mean() on data containing NaN returns NaN. A masked array works with any dtype including integers, keeps a separate mask instead of overwriting the data, and its built-in reductions ignore the masked entries automatically without you reaching for nanmean. Masked arrays also remember which values were invalid, so you can recover them later.
How do I get a plain array back from a masked array?
Call filled(value) to replace every masked element with a fill value and return a regular ndarray, for example masked.filled(0). Use compressed() to drop the masked elements entirely and return only the valid 1D values. The .data attribute exposes the underlying raw values including the ones under the mask, and .mask gives the boolean mask itself.