将Pandas数据帧写入Feather/Parquet时列表转numpy数组的问题求助
Pandas写入Feather/Parquet时list列自动转为numpy.ndarray的问题及解决办法
问题场景
用pd.to_feather()或pd.to_parquet()存储包含list类型列的Pandas数据帧,读取后原本的list列会被转成numpy.ndarray类型,导致依赖原生list的代码运行失败。改用pickle存储虽然能保留原类型,但文件体积直接涨到原来的4倍。当前使用版本:pandas 1.4.4、pyarrow 9.0.0。
可以用这段代码复现问题:
import pandas as pd # 构造带list列的DataFrame df = pd.DataFrame({'list_col': [[1, 2, 3], [4, 5, 6]]}) print(f"原始元素类型: {type(df['list_col'].iloc[0])}") # 输出 <class 'list'> # 写入Feather再读取 df.to_feather('demo.feather') df_read = pd.read_feather('demo.feather') print(f"读取后元素类型: {type(df_read['list_col'].iloc[0])}") # 输出 <class 'numpy.ndarray'>
原因
这是因为Feather/Parquet依赖pyarrow引擎序列化数据,会把Python list转成Arrow的list类型;而在pandas 1.4.x这类老版本中,读取Arrow list类型时会默认转成numpy.ndarray,而且这个转换过程没有任何警告,很容易踩坑。
解决办法
1. 读取后手动转成list
既保留Feather/Parquet的体积优势,又能恢复原类型,适合不想升级版本的场景:
# 单列转换 df_read['list_col'] = df_read['list_col'].apply(lambda x: x.tolist()) # 多列批量转换 list_columns = ['list_col1', 'list_col2'] # 替换成你的列名 df_read[list_columns] = df_read[list_columns].applymap(lambda x: x.tolist())
2. 升级pandas版本
pandas 2.0及以上版本对Arrow类型的处理做了优化,默认会把Arrow list映射为pd.ListDtype,后续可以更方便地转成原生list,而且不会再无警告地转成numpy.ndarray。升级后能从根源避免这个问题。
3. 直接用pyarrow API控制序列化(进阶)
如果需要更精细的类型控制,可以绕过pandas的IO方法,直接用pyarrow操作:
import pyarrow as pa import pyarrow.feather as feather # 写入数据 table = pa.Table.from_pandas(df) feather.write_feather(table, 'demo.feather') # 读取时指定类型映射,直接转成原生list table_read = feather.read_feather('demo.feather') df_read = table_read.to_pandas(types_mapper=lambda dtype: dtype.to_pylist() if pa.types.is_list(dtype) else dtype)
关于pickle的补充
pickle虽然能完美保留原生类型,但缺点很明显:文件大、只能在Python环境用、不安全(不可信的pickle文件可能执行恶意代码),所以优先推荐上面的方法。
内容的提问来源于stack exchange,提问作者matrs
相关产品推荐
相关产品推荐

