# Pandas NA vs np.nan: Understanding Missing Value Differences in DataFrames

> Understand the key differences between pandas pd.NA and numpy np.nan for handling missing values in DataFrames. Learn which to use for your data.

- Repository: [pandas/pandas](https://github.com/pandas-dev/pandas)
- Tags: deep-dive
- Published: 2026-02-16

---

**`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`](https://github.com/pandas-dev/pandas/blob/main/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`](https://github.com/pandas-dev/pandas/blob/main/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`](https://github.com/pandas-dev/pandas/blob/main/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`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/arrays/integer.py), [`pandas/core/arrays/boolean.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/arrays/boolean.py), and [`pandas/core/arrays/string_.py`](https://github.com/pandas-dev/pandas/blob/main/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`](https://github.com/pandas-dev/pandas/blob/main/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:

```python
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`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/dtypes/missing.py) handle both sentinels:

```python

# 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

```python
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

```python

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

```python

# 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 `object` dtype)
- 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 `nan` behavior

## Summary

- **`pd.NA`** is a **dtype-agnostic singleton** (`NAType`) implemented in [`pandas/_libs/missing.c`](https://github.com/pandas-dev/pandas/blob/main/pandas/_libs/missing.c) designed for nullable extension dtypes, propagating consistently through logical and arithmetic operations.

- **`np.nan`** is 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()` and `pd.notna()` from [`pandas/core/dtypes/missing.py`](https://github.com/pandas-dev/pandas/blob/main/pandas/core/dtypes/missing.py) to identify both markers reliably.

- **Performance**: `pd.NA` enables vectorized operations on nullable integers and booleans without `object` dtype overhead, while `np.nan` offers 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.