Pandas NA vs np.nan: Understanding Missing Value Differences in DataFrames
pd.NA is a dtype-agnostic missing value singleton introduced in pandas for nullable extension arrays, while np.nan is a float-specific IEEE-754 sentinel that only works with floating-point dtypes.
When working with missing data in the pandas-dev/pandas repository, understanding the distinction between these two sentinels is critical for correct data manipulation and type preservation. While both represent missing values, they differ fundamentally in their implementation, supported dtypes, and behavioral semantics across logical and arithmetic operations.
What Is pd.NA? The Pandas Missing Value Sentinel
pd.NA represents a missing value marker designed specifically for pandas' nullable extension dtypes. Unlike traditional missing value representations, it is not tied to any specific numeric type.
Implementation in pandas/_libs/missing.c
The core implementation resides in pandas/_libs/missing.c, where NAType is defined as a C-extension singleton type. This object is exposed to Python as pandas.NA through the top-level pandas/__init__.py import chain. The implementation ensures pd.NA is immutable and hashable, allowing it to function as a dictionary key when necessary.
Integration with Nullable Extension Dtypes
Each nullable extension dtype implements an na_value property returning libmissing.NA, defined in pandas/core/dtypes/base.py. When you create a Series with dtype Int64, boolean, or string, the underlying array implementations in pandas/core/arrays/integer.py, pandas/core/arrays/boolean.py, and pandas/core/arrays/string_.py store pd.NA in a separate mask array. This design keeps the data buffer in its native format (integer, boolean, etc.) while tracking missingness separately.
What Is np.nan? The NumPy Float Sentinel
np.nan (Not a Number) originates from NumPy's C implementation in numpy/core/_numpyinternal.c and represents an IEEE-754 floating-point value. It is specifically a float64 value, making it fundamentally different from pd.NA's type-agnostic design.
Because np.nan is a float, it only works naturally with pandas float dtypes. When placed in non-float columns, pandas coerces the entire column to object dtype, significantly impacting performance and memory usage.
Key Differences Between pandas NA and np.nan
Understanding the behavioral differences ensures you choose the appropriate sentinel for your data pipeline.
Type System and dtype Support
pd.NA works exclusively with pandas nullable extension dtypes (Int64, UInt64, Float64, boolean, string). These dtypes preserve the underlying data type while supporting missing values.
np.nan only functions correctly with float dtypes. Attempting to use np.nan in integer or boolean Series forces conversion to object dtype, losing type safety and vectorization benefits.
Equality and Comparison Behavior
The equality semantics differ fundamentally:
import pandas as pd
import numpy as np
# pd.NA equality
print(pd.NA == pd.NA) # <NA> (returns NA, not True)
# np.nan equality
print(np.nan == np.nan) # False
pd.NA propagates through comparisons, returning pd.NA when the result is indeterminate. np.nan follows IEEE-754 rules where it never equals itself, requiring np.isnan() for detection.
Logical and Arithmetic Propagation
pd.NA propagates through logical, arithmetic, and comparison operations with well-defined semantics. For example, True & pd.NA returns pd.NA because the result depends on the unknown value.
np.nan propagates in arithmetic (1 + np.nan → np.nan) but raises TypeError in logical operations on boolean arrays because np.nan is not a boolean value.
Detection with pd.isna() and pd.notna()
The utility functions in pandas/core/dtypes/missing.py handle both sentinels:
# Both are detected as missing
print(pd.isna(pd.NA)) # True
print(pd.isna(np.nan)) # True
# Works on Series with mixed markers
s = pd.Series([1, pd.NA, np.nan], dtype="object")
print(pd.isna(s)) # [False, True, True]
Practical Code Examples
Creating Series with Different Missing Value Types
import pandas as pd
import numpy as np
# Nullable integer dtype uses pd.NA
s_int = pd.Series([1, pd.NA, 3], dtype="Int64")
print(s_int)
# 0 1
# 1 <NA>
# 2 3
# Standard float dtype uses np.nan
s_float = pd.Series([1.0, np.nan, 3.0])
print(s_float)
# 0 1.0
# 1 NaN
# 2 3.0
# Nullable boolean dtype
s_bool = pd.Series([True, pd.NA, False], dtype="boolean")
print(s_bool)
# 0 True
# 1 <NA>
# 2 False
Logical Operations and Propagation
# pd.NA propagates through logical AND
s_bool = pd.Series([True, False, True], dtype="boolean")
result = s_bool & pd.NA
print(result)
# 0 <NA>
# 1 False
# 2 <NA>
# Reduction operations respect NA
print(s_bool.sum()) # 2 (skipna=True by default)
print(s_bool.sum(skipna=False)) # <NA>
Mixing Markers (Not Recommended)
# Creating object dtype Series by mixing markers
mixed = pd.Series([1, pd.NA, np.nan], dtype="object")
print(mixed.dtype) # object
# Both detected as missing
print(pd.isna(mixed))
# 0 False
# 1 True
# 2 True
When to Use pd.NA vs np.nan
Choose pd.NA when:
- Working with nullable extension dtypes (
Int64,boolean,string,Float64) - You need consistent propagation through logical operations (
&,|,^) - Type preservation is critical (avoiding
objectdtype) - You want unified missing value semantics across integer, boolean, and string data
Choose np.nan when:
- Working with pure NumPy arrays or float-only pandas Series
- Memory efficiency is paramount and you only need float support
- You require IEEE-754 compliant floating-point behavior
- Backward compatibility with legacy code that expects float
nanbehavior
Summary
-
pd.NAis a dtype-agnostic singleton (NAType) implemented inpandas/_libs/missing.cdesigned for nullable extension dtypes, propagating consistently through logical and arithmetic operations. -
np.nanis a float64 IEEE-754 sentinel from NumPy limited to float dtypes, following standard floating-point rules where it never equals itself. -
Detection: Use
pd.isna()andpd.notna()frompandas/core/dtypes/missing.pyto identify both markers reliably. -
Performance:
pd.NAenables vectorized operations on nullable integers and booleans withoutobjectdtype overhead, whilenp.nanoffers raw speed for float-only workflows.
Frequently Asked Questions
Can I mix pd.NA and np.nan in the same Series?
Yes, but doing so forces the Series to use object dtype, which eliminates performance benefits and type safety. When mixing occurs, pd.isna() correctly identifies both as missing values, but arithmetic and logical operations lose vectorization. For optimal performance, stick to one sentinel type per Series and use nullable extension dtypes with pd.NA.
Why does np.nan == np.nan return False?
This behavior follows the IEEE-754 floating-point standard, which specifies that NaN (Not a Number) is not equal to any value, including itself. This design prevents false positives in numerical computations where undefined results should not appear equal. To test for np.nan, always use np.isnan() or pd.isna() rather than equality operators.
Does pd.NA work with NumPy arrays?
pd.NA does not integrate natively with NumPy arrays because NumPy lacks native support for nullable integer or boolean dtypes. When you attempt to place pd.NA in a standard NumPy array, NumPy typically raises an error or forces object dtype conversion. Use pd.NA specifically with pandas Series and DataFrames that employ nullable extension dtypes (Int64, boolean, etc.).
Which is better for performance: pd.NA or np.nan?
For float-only data, np.nan offers slightly better raw performance because it avoids the extension array dispatch overhead. However, for integer, boolean, or string data, pd.NA is significantly faster because it allows vectorized operations through nullable extension dtypes without converting to slow object dtype. The pd.NA approach also reduces memory usage for non-float data by maintaining compact native buffers with separate boolean masks.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →