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

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
# 130

You 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
# 4

3 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.

TaskCode
Mask a sentinel valuenp.ma.masked_equal(a, -999)
Mask values above a limitnp.ma.masked_greater(a, 100)
Mask NaN / infnp.ma.masked_invalid(a)
Replace masked with a valuem.filled(0)
Keep only valid valuesm.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

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.