在数据清洗,特别是处理从 Excel 或数据库导入的数据时,经常会遇到各种“空值”。Python 中的空值类型不止一种,判断和转换方法也各不相同。下面系统梳理常见空值的判断,以及在不同场景下的转换方式。


一、Python 中常见的空值类型

类型来源典型值特点
NonePython 内置None单例对象,表示“无”,常用于对象列或数据库 NULL
float('nan')Python 内置float('nan')浮点数,不等于自身(nan != nan)
numpy.nan (np.nan)NumPynp.nan同上,但类型为 np.float64
pandas.NaTPandaspd.NaT表示缺失的时间,兼容 datetime64 / timedelta64
pandas.NAPandas (≥1.0)pd.NA实验性标量,可用于可空整数、字符串、布尔类型,避免类型提升
空字符串 ''Python 内置''长度为 0 的字符串,不是严格意义上的缺失值,但在业务中常当作空值处理
空白字符串 ' 'Python 内置' '全是空白符,也常被当作空值
空容器 [], {}Python 内置空列表、字典等
缺失值占位符业务数据如 'NA', 'NULL', 'N/A', '-'需要识别并转换为真正的缺失值

二、单值判断:如何识别一个变量是否为空

1. None 的判断

x = None
x is None   # True
  • 注意:x == None 虽然可行,但不符合 PEP8,且对 numpy 数组会返回逐元素结果,推荐用 is。

2. float('nan') / np.nan 的判断

import math
import numpy as np
 
x = float('nan')
# 不能用 ==,因为 nan != nan
math.isnan(x)   # True
np.isnan(x)     # True (如果是 np.nan 或 float nan)
 
# 对于非浮点类型会报错,如 np.isnan(None) 会引发 TypeError
  • np.nan 和 float('nan') 在 math.isnan 和 np.isnan 下等价,但 np.isnan 可接受数组。

3. pd.NaT 的判断

import pandas as pd
x = pd.NaT
pd.isna(x)      # True
pd.isnull(x)    # True (isnull 是 isna 的别名)
# x is pd.NaT   # 也成立,但 pd.NaT 是单例,推荐使用 pd.isna

4. pd.NA 的判断

x = pd.NA
pd.isna(x)      # True

5. 空字符串 / 空白字符串 / 占位符的判断

s = ''
if s == '':
    # 空字符串
if s.strip() == '':
    # 空或只有空白
  • 对于 None 或 np.nan,直接 .strip() 会报错,需先判断。

通用判断函数对比

方法识别 None识别 np.nan识别 pd.NaT识别 pd.NA识别 ”备注
x is None✔️❌❌❌❌仅 None
math.isnan(x)❌ (报错)✔️❌ (报错)❌ (报错)❌仅 float nan
np.isnan(x)❌ (报错)✔️❌ (报错)❌ (报错)❌仅浮点 nan,可处理数组
pd.isna(x)✔️✔️✔️✔️❌推荐,覆盖最广
pd.isnull(x)✔️✔️✔️✔️❌与 isna 相同
# 4. 综合判断:None / 空字符串 / NaN / 空列表等
def is_empty(v):
    if v is None:
        return True
    if isinstance(v, float) and math.isnan(v):
        return True
    if isinstance(v, str) and v.strip() == "":
        return True
    if isinstance(v, (list, tuple, dict, set)) and len(v) == 0:
        return True
    return False

最佳实践:在数据清洗时,统一使用 pd.isna() / pd.isnull() 来判断各种缺失值标量。


三、批量判断:Pandas DataFrame / Series 中的空值

import pandas as pd
import numpy as np
 
df = pd.DataFrame({
    'A': [1, None, 3],
    'B': [np.nan, 'text', ''],
    'C': [pd.NaT, pd.Timestamp('2020-01-01'), pd.NaT],
    'D': [pd.NA, 2, pd.NA],
})

1. 检测所有缺失值

df.isna()    # 返回布尔 DataFrame,对 None/np.nan/NaT/NA 均为 True,对 '' 为 False
df.isnull()  # 完全等价

2. 检测非缺失值

df.notna()

3. 统计缺失值

df.isna().sum()        # 每列缺失值个数
df.isna().sum().sum()  # 总缺失值个数

4. 将空字符串也视为缺失值

# 替换所有空字符串为 NaN
df.replace('', np.nan, inplace=True)
# 或
df['B'] = df['B'].replace('', np.nan)
# 空白字符串同理
df.replace(r'^\s*$', np.nan, regex=True, inplace=True)

5. 判断某列是否存在缺失值

df['A'].hasnans   # True 如果列中包含 NaN 或 NaT (仅对数值/日期列有效)
# 通用
df['A'].isna().any()

四、空值的转换与清洗

1. 统一缺失值表示:将各种“空”变成标准的 NaN / None

一个常见场景:从 Excel 或 CSV 读入后,缺失值可能有 None、np.nan、'NA'、'NULL'、''、'-' 等。

# 定义需要替换的占位符列表
na_values = ['NA', 'NULL', 'N/A', '-', '']
 
# 读取时直接处理(以 read_csv 为例)
df = pd.read_csv('file.csv', na_values=na_values, keep_default_na=True)
 
# 或读取后替换
df.replace(na_values, np.nan, inplace=True)
 
# 同时去除字符串首尾空白后视为缺失
df = df.applymap(lambda x: np.nan if isinstance(x, str) and x.strip() == '' else x)

2. 填充缺失值 (fillna)

# 用常数填充
df['A'].fillna(0, inplace=True)
 
# 用均值/中位数填充
df['A'].fillna(df['A'].mean(), inplace=True)
 
# 用前一个值填充(向前填充)
df['B'].ffill()
 
# 用字典对不同列指定不同填充值
df.fillna({'A': 0, 'B': 'unknown', 'C': pd.Timestamp.now()}, inplace=True)
 
# 对于 pd.NA 兼容的可空类型,可直接填充 None 或 pd.NA
df['D'].fillna(0, inplace=True)  # pd.NA 也可以接受数值填充

3. 删除缺失值 (dropna)

# 删除任何包含缺失值的行
df.dropna(inplace=True)
 
# 仅当某列缺失才删除该行
df.dropna(subset=['A', 'C'], inplace=True)
 
# 删除全为空的行或列
df.dropna(how='all', inplace=True)
df.dropna(axis=1, how='all', inplace=True)
 
# 保留至少有 n 个非空值的行
df.dropna(thresh=2, inplace=True)

4. 类型兼容:让整型列支持缺失值

普通的 int64 列无法容纳 NaN,一旦出现缺失值就会被自动提升为 float64。解决方法:

# 使用 Pandas 的可空整数类型
df['A'] = df['A'].astype('Int64')   # 注意 I 大写,这是 pd.Int64Dtype()
# 现在缺失值显示为 <NA>,列为 Int64 类型,不再是 float

对于布尔类型、字符串类型也有类似的扩展类型:

  • boolean (可空布尔)
  • string (可空字符串,与 object 不同,存 pd.NA 而非 NaN)
df['B'] = df['B'].astype('string')

5. 写出数据时的空值转换

写入 Excel

# pandas 默认会将 NaN/NaT 写为空单元格,将 None 写为空单元格
df.to_excel('output.xlsx', na_rep='')  # 可以用 na_rep 指定替代文本
# 对于 Int64 等可空类型,<NA> 也会写为空单元格

写入数据库 (MySQL)

# 用 to_sql 时,NaN/NaT 自动映射为 SQL NULL
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://...')
df.to_sql('table_name', engine, if_exists='replace', index=False)
 
# 如果自己组装 SQL,需要将 None 作为 NULL 插入
import pymysql
conn = pymysql.connect(...)
cursor = conn.cursor()
# 将 np.nan / pd.NaT / pd.NA 转换为 None
value = None if pd.isna(value) else value
cursor.execute("INSERT INTO table (col) VALUES (%s)", (value,))

通用转换函数示例:

def to_sql_value(val):
    """将 Python 空值统一转换为 None,用于插入数据库"""
    if val is None:
        return None
    if isinstance(val, float) and math.isnan(val):
        return None
    if isinstance(val, pd.Timestamp) and pd.isna(val):
        return None
    if hasattr(val, '__module__') and 'pandas' in val.__module__:
        if pd.isna(val):
            return None
    return val

6. 其他转换技巧

  • 用 numpy.where 或 Series.where:
df['A'] = df['A'].where(df['A'].notna(), other=0)  # 缺失值替换为0
  • 布尔值列,将 NaN 填充为 False:
df['flag'] = df['flag'].fillna(False).astype(bool)
  • 使用 coalesce 逻辑选择第一个非空值:
# 相当于 SQL 的 COALESCE(A, B)
df['A'] = df['A'].combine_first(df['B'])
# 或
df['A'] = df['A'].fillna(df['B'])

五、注意事项总结

  1. np.nan 具有传染性:任何与 np.nan 的算术运算结果都是 np.nan。
  2. 整型列缺失值会自动提升为 float64,需要可空整数类型用 'Int64'。
  3. None 在某些情况下自动转为 np.nan:当 None 被放入 float64 或 datetime64 数组时。
  4. pd.isna('') 返回 False,需要显式处理空字符串。
  5. 判断不要用 == np.nan,始终用 math.isnan、np.isnan 或 pd.isna。
  6. 输出到数据库时,记得将 np.nan、pd.NaT 等转为 None,否则可能报错或写入非预期的值。

掌握这些判断和转换方法后,处理 Excel、数据库导入的数据中的空值就会非常得心应手。