Polars长结构体列表解析异常:仅前25项字段可正常解析
我遇到了一个疑似类型推断的问题:当处理包含4种不同数据类型字段的结构体列表时,Polars仅解析列表前25项中出现过值的字段。
我先通过json.loads将JSON数据转换为字典后加载到DataFrame,数据存在大量嵌套结构。问题出在一个结构体列表类型的列上,加载时已传入预定义Schema,该字段的Schema定义如下:
{ "attributes": pl.List(pl.Struct({ "stringValue": pl.String, "boolValue": pl.Boolean, "intValue": pl.Int64, "doubleValue": pl.Float64 }))}
列表中的每个条目仅包含一种类型的值。例如,若字符串和布尔类型的值出现在前25项中,后续条目里的这两类值能被正确解析;但如果整数类型的值出现在第25项之后,会被解析为None。
请问这是预期行为吗?是否可以配置类型推断的样本数量?有没有解决方案?
我尝试过多种Schema定义、调整infer_schema_length参数、切换使用read_json和pl.DataFrame加载方式。调整测试数据中列表项的顺序可以解决问题,但我无法控制原数据中列表项的顺序。
更新:示例代码
import polars as pl data_schema = { "attributes": pl.List(pl.Struct({ "key": pl.String, "value": pl.Struct({ "stringValue": pl.String, "doubleValue": pl.Float64, "boolValue": pl.Boolean, "intValue": pl.Int64, }), })), } data = [ { "attributes": [ {"key": "string1", "value": {"stringValue": "sv1"}}, {"key": "string2", "value": {"stringValue": "sv2"}}, {"key": "string3", "value": {"stringValue": "sv3"}}, {"key": "string4", "value": {"stringValue": "sv4"}}, {"key": "string5", "value": {"stringValue": "sv5"}}, {"key": "string6", "value": {"stringValue": "sv6"}}, {"key": "string7", "value": {"stringValue": "sv7"}}, {"key": "string8", "value": {"stringValue": "sv8"}}, {"key": "string9", "value": {"stringValue": "sv9"}}, {"key": "int1", "value": {"intValue": 1}}, {"key": "int2", "value": {"intValue": 2}}, {"key": "int3", "value": {"intValue": 3}}, {"key": "string10", "value": {"stringValue": "sv10"}}, {"key": "string11", "value": {"stringValue": "sv11"}}, {"key": "string12", "value": {"stringValue": "sv12"}}, {"key": "string13", "value": {"stringValue": "sv13"}}, {"key": "string14", "value": {"stringValue": "sv14"}}, {"key": "string15", "value": {"stringValue": "sv15"}}, {"key": "string16", "value": {"stringValue": "sv16"}}, {"key": "string17", "value": {"stringValue": "sv17"}}, {"key": "string18", "value": {"stringValue": "sv18"}}, {"key": "string19", "value": {"stringValue": "sv19"}}, {"key": "string20", "value": {"stringValue": "sv20"}}, {"key": "string21", "value": {"stringValue": "sv21"}}, {"key": "string22", "value": {"stringValue": "sv22"}}, {"key": "string23", "value": {"stringValue": "sv23"}}, {"key": "string24", "value": {"stringValue": "sv24"}}, {"key": "string25", "value": {"stringValue": "sv25"}}, {"key": "int4", "value": {"intValue": 4}}, {"key": "int5", "value": {"intValue": 5}}, {"key": "bool1", "value": {"boolValue": True}}, {"key": "bool2", "value": {"boolValue": False}}, {"key": "bool3", "value": {"boolValue": True}}, {"key": "double1", "value": {"doubleValue": 0.1}}, {"key": "double2", "value": {"doubleValue": 2.3}}, ] } ] df = pl.DataFrame(data, schema=data_schema) df.rows()
输出结果
[([{'key': 'string1', 'value': {'stringValue': 'sv1', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string2', 'value': {'stringValue': 'sv2', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string3', 'value': {'stringValue': 'sv3', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string4', 'value': {'stringValue': 'sv4', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string5', 'value': {'stringValue': 'sv5', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string6', 'value': {'stringValue': 'sv6', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string7', 'value': {'stringValue': 'sv7', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string8', 'value': {'stringValue': 'sv8', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string9', 'value': {'stringValue': 'sv9', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'int1', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': 1}}, {'key': 'int2', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': 2}}, {'key': 'int3', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': 3}}, {'key': 'string10', 'value': {'stringValue': 'sv10', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string11', 'value': {'stringValue': 'sv11', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string12', 'value': {'stringValue': 'sv12', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string13', 'value': {'stringValue': 'sv13', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string14', 'value': {'stringValue': 'sv14', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string15', 'value': {'stringValue': 'sv15', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string16', 'value': {'stringValue': 'sv16', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string17', 'value': {'stringValue': 'sv17', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string18', 'value': {'stringValue': 'sv18', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string19', 'value': {'stringValue': 'sv19', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string20', 'value': {'stringValue': 'sv20', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string21', 'value': {'stringValue': 'sv21', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string22', 'value': {'stringValue': 'sv22', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string23', 'value': {'stringValue': 'sv23', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string24', 'value': {'stringValue': 'sv24', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'string25', 'value': {'stringValue': 'sv25', 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'int4', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': 4}}, {'key': 'int5', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': 5}}, {'key': 'bool1', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'bool2', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'bool3', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'double1', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': None}}, {'key': 'double2', 'value': {'stringValue': None, 'doubleValue': None, 'boolValue': None, 'intValue': None}}],)]
这个问题是Polars在处理嵌套结构体列表时的隐性类型推断逻辑导致的:即使传入了顶层Schema,Polars在解析列表内的结构体时,仍会默认采样前25个元素来推断结构体的实际字段存在性,忽略Schema中定义的后续字段。
解决方法:
使用
pl.Struct的strict=True参数
在定义结构体Schema时,添加strict=True强制Polars严格遵循Schema中的所有字段定义,不再进行采样推断:data_schema = { "attributes": pl.List(pl.Struct({ "key": pl.String, "value": pl.Struct({ "stringValue": pl.String, "doubleValue": pl.Float64, "boolValue": pl.Boolean, "intValue": pl.Int64, }, strict=True), # 启用严格模式 })), }这样Polars会严格按照Schema解析所有字段,无论它们出现在列表的哪个位置。
直接使用
read_json并指定Schema
跳过json.loads转字典的步骤,直接用pl.read_json加载原始JSON文件,并传入带strict=True的Schema:df = pl.read_json("your_data.json", schema=data_schema)这种方式能避免字典转译过程中的信息丢失,同时严格遵循Schema定义。
调整全局采样长度(不推荐)
可以通过设置环境变量POLARS_INFER_SCHEMA_LENGTH来修改默认采样数量,比如设置为-1表示采样全部数据:import os os.environ["POLARS_INFER_SCHEMA_LENGTH"] = "-1"但这种方法会影响所有Polars操作的性能,尤其是大数据量时,因此优先推荐前两种方法。
内容的提问来源于stack exchange,提问作者Jeffrey Vermeire

