
在数据处理和交换中,json(javascript object notation)是一种广泛使用的轻量级数据格式。然而,在从数据库、科学计算或数据分析工具(如pandas)导出数据时,我们经常会遇到两种特殊的值:nan(not a number,非数字)和null。尽管它们都表示“缺失”或“无效”的概念,但在语义和处理方式上却有着本质的区别:
我们的目标是精确地移除JSON数据中所有值为NaN的键值对,同时保留值为null(Python中的None)的键值对。例如,{"height": null}应该被保留,而{"weight": NaN}则应该被移除。
由于NaN != NaN的特性,我们不能简单地使用value == float('nan')来判断一个值是否为NaN。Python的math模块提供了一个专门用于此目的的函数:math.isnan()。
为了准确识别一个值是否为NaN,我们需要两个条件:
因此,判断一个值value是否为NaN的可靠条件是 isinstance(value, float) and math.isnan(value)。
立即学习“Python免费学习笔记(深入)”;
我们将创建一个辅助函数remove_nans,它接收一个字典对象,并返回一个移除了所有NaN键值对的新字典。这个函数将利用字典推导式和上述的NaN识别逻辑。
首先,假设我们有一个包含多个JSON对象的列表,其中一些对象包含NaN和null值:
import math
import json
# 模拟输入JSON数据
# 注意:在实际的JSON文件中,NaN通常会被json.loads()转换为float('nan')
# 或者在序列化时被json.dumps()转换为null。
# 这里我们直接构造Python对象来模拟解析后的数据。
data = [
{
"name": "John Doe",
"age": 30,
"height": None, # JSON null
"weight": float('nan'), # JSON NaN
"city": "New York"
},
{
"name": "Jim Hanks",
"age": float('nan'),
"height": float('nan'),
"weight": float('nan'),
"occupation": None
},
{
"id": 101,
"value": 123.45,
"status": "active"
}
]
print("原始数据示例:")
for item in data:
print(item)
print("-" * 30)
# 定义移除NaN的函数
def remove_nans(obj):
"""
从字典对象中移除所有值为NaN的键值对。
保留None(JSON null)值。
"""
# 使用字典推导式遍历所有键值对
# 条件:如果值不是浮点数NaN,则保留该键值对
return {
key: value
for key, value in obj.items()
if not (isinstance(value, float) and math.isnan(value))
}
# 应用函数到数据列表中的每个字典
processed_data = [remove_nans(row) for row in data]
print("处理后的数据示例:")
for item in processed_data:
print(item)代码解析:
为了展示一个更完整的流程,包括从JSON字符串加载数据和最终输出,我们可以结合json模块:
Easily find JSON paths within JSON objects using our intuitive Json Path Finder
30
import math
import json
# 模拟原始JSON字符串数据
# 注意:在JSON标准中,NaN不是一个合法的字面量。
# 通常,float('nan')在json.dumps时会被转换为null。
# 但如果JSON文件是从某些非标准源生成,可能包含字符串"NaN"。
# 本教程假设我们处理的是解析后Python对象中的float('nan')。
json_string = """
[
{
"name": "John Doe",
"age": 30,
"height": null,
"weight": NaN,
"city": "New York"
},
{
"name": "Jim Hanks",
"age": NaN,
"height": NaN,
"weight": NaN,
"occupation": null
},
{
"id": 101,
"value": 123.45,
"status": "active"
}
]
"""
# 为了让json.loads能够处理非标准的"NaN"字符串,需要自定义parse_constant
# 否则,如果json_string中直接是"NaN",json.loads会报错。
# 如果实际JSON文件中的NaN是"null",则不需要这一步。
# 这里我们假设数据源已经正确地将NaN转换为Python的float('nan')。
# 如果json_string中直接是NaN,需要这样处理:
# import re
# json_string_parsed = re.sub(r'NaN', 'null', json_string) # 或者其他处理
# data_from_json = json.loads(json_string_parsed)
# 更直接模拟问题中的情况,假设json.loads能够处理或我们直接构造了包含float('nan')的Python对象
# 实际的json.loads()默认会将NaN转换为null,除非自定义parser。
# 因此,为了匹配问题中“weight: NaN”在Python中被识别为float('nan')的场景,
# 我们直接使用前面构造的Python对象来演示。
data_from_json = [
{
"name": "John Doe",
"age": 30,
"height": None,
"weight": float('nan'),
"city": "New York"
},
{
"name": "Jim Hanks",
"age": float('nan'),
"height": float('nan'),
"weight": float('nan'),
"occupation": None
},
{
"id": 101,
"value": 123.45,
"status": "active"
}
]
print("--- 原始数据(Python对象形式)---")
print(json.dumps(data_from_json, indent=2, default=lambda x: str(x) if math.isnan(x) else x)) # 打印时将NaN转换为字符串显示
# 定义移除NaN的函数
def remove_nans(obj):
"""
从字典对象中移除所有值为NaN的键值对。
保留None(JSON null)值。
"""
return {
key: value
for key, value in obj.items()
if not (isinstance(value, float) and math.isnan(value))
}
# 应用函数到数据列表中的每个字典
processed_data = [remove_nans(row) for row in data_from_json]
print("\n--- 处理后的数据(Python对象形式)---")
print(json.dumps(processed_data, indent=2))
# 期望的JSON输出格式:
# { "name": "John Doe", "age": 30, "height": null, "city": "New York" }
# { "name": "Jim Hanks", "occupation": null }
# { "id": 101, "value": 123.45, "status": "active" }运行上述代码,你会看到weight、age和height中所有float('nan')值对应的键值对都被成功移除,而null(None)值则被保留。
输入数据格式的假设: 本教程的核心是处理Python对象中float('nan')形式的NaN。
处理嵌套结构: 上述remove_nans函数仅适用于单层字典。如果你的JSON数据包含嵌套的字典或列表,你需要一个递归函数来遍历所有层级。
def remove_nans_recursive(obj):
if isinstance(obj, dict):
return {
key: remove_nans_recursive(value)
for key, value in obj.items()
if not (isinstance(value, float) and math.isnan(value))
}
elif isinstance(obj, list):
return [remove_nans_recursive(elem) for elem in obj]
else:
# 对于非字典、非列表的叶子节点,直接返回其值
# 确保NaN浮点数不被保留
if isinstance(obj, float) and math.isnan(obj):
return None # 或者其他处理,这里为了兼容可以返回None,但通常应该在父级被过滤
return obj请注意,在递归版本中,如果一个叶子节点是NaN,它最终会被父级字典的过滤条件移除。如果递归到它本身,它会被if isinstance(obj, float) and math.isnan(obj): return None处理。更严谨的做法是让父级负责过滤,因此叶子节点可以直接返回obj。
# 改进后的递归版本,确保过滤逻辑在字典层级生效
def remove_nans_recursive_v2(obj):
if isinstance(obj, dict):
cleaned_dict = {}
for key, value in obj.items():
if not (isinstance(value, float) and math.isnan(value)):
cleaned_dict[key] = remove_nans_recursive_v2(value)
return cleaned_dict
elif isinstance(obj, list):
return [remove_nans_recursive_v2(elem) for elem in obj]
else:
return obj # 非字典、非列表的叶子节点直接返回性能考量: 对于非常大的JSON文件或数据流,一次性加载到内存并处理可能效率低下。在这种情况下,可以考虑使用迭代器或流式解析库(如ijson)来逐块处理数据。
None与NaN的区分: 再次强调,本教程的解决方案精确地区分了None和NaN。如果你也想移除null值,只需修改过滤条件,例如 if value is not None and not (isinstance(value, float) and math.isnan(value))。
通过本教程,我们学习了如何在Python中利用math.isnan()函数,结合类型检查,高效且精确地从JSON数据中移除NaN值。这种方法不仅能够处理常见的NaN场景,还能确保null值得到正确保留,从而满足严格的数据清洗要求。掌握这一技巧对于任何处理外部数据源并需要维护数据质量的Python开发者都至关重要。
以上就是Python数据清洗:高效移除JSON文件中的NaN值的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号