pandas.to_parquet pyarrow.lib.ArrowInvalid: 无法转换时间差

0 投票
1 回答
67 浏览
提问于 2025-04-13 16:23

我有一个很大的多重索引数据框,格式是长格式,里面只有一列“值”。有些“值”的类型是 pd.Timedelta

当我尝试用 pd.to_parquet 把这个数据框保存为 parquet 文件时,出现了一个错误:

pyarrow.lib.ArrowInvalid: ("无法将 Timedelta('2903 days 00:00:00') 转换为 double 类型", '转换失败,值列的类型为对象')

如果我把错误信息中的特定值转换成 numpy,我得到的是:

array([Timedelta('2903 days 00:00:00')], dtype=object)

我做了一个简单的例子,想看看是否可以把 pd.Timedelta 转换成 parquet。下面的代码运行得很好:

import pandas as pd

idx = pd.MultiIndex.from_tuples(
    [("A", "x"), ("A", "y"), ("B", "x"), ("B", "y")],
    names=["idx1", "idx2"])
data = {"value": [
    pd.Timedelta(days=10),
    2.5,
    pd.Timedelta(days=20),
    5
]}

df = pd.DataFrame(data=data, index=idx)
df.to_parquet("test.parquet")

x = pd.read_parquet("test.parquet")

df.iloc[0, :].to_numpy() 得到的类型和我真实数据框中的完全一样:array([Timedelta('10 days 00:00:00')], dtype=object)

我在想,我的原始数据框和这个简单例子之间可能有什么不同呢?

1 个回答

0

你看到的这个错误信息 'Conversion failed for column value with type object',说明你的这一列数据里混合了浮点数和时间差(time delta),正如你所预期的那样。

问题出在,pyarrow在处理一个很大的数据表时,会根据pandas数据表的前几行来推测数据类型。在你的情况下,这些前几行很可能都是浮点数。下面这段代码(和你之前的例子类似,只是稍微改动了一下)会重现你的错误:

import pandas as pd

float_vals = [2.5 for i in range(10000)]
float_index_val = [("float", i) for i in range(10000)]
float_vals.extend([("A", "x"), ("A", "y"), ("B", "x"), ("B", "y")])
float_vals.extend([
    pd.Timedelta(days=2903),
    2.5,
    pd.Timedelta(days=20),
    5
])

idx = pd.MultiIndex.from_tuples(
    float_vals,
    names=["idx1", "idx2"])
data = {"value": float_vals}

df = pd.DataFrame(data=data, index=idx)
df.to_parquet("test.parquet")

x = pd.read_parquet("test.parquet")

这里的问题是,parquet格式的列不能同时包含多种类型。在你的例子中,如果你加载保存的parquet文件,你会发现所有数据都被转换成了时间差。例如,使用 x = pd.read_parquet("test.parquet") 然后 x.iloc[1, :].to_numpy() 会得到 array([2], dtype='timedelta64[us]') 这样的结果。

如果浮点数值并不表示时间差,你应该按照评论的建议,先把数据进行“解透视”(unpivot)。如果浮点数值确实表示时间差,你可以在保存之前进行转换,或者在保存时指定一个数据结构(schema)。

df.value = pd.to_timedelta(df.value)
df.to_parquet("test.parquet")

或者

df.to_parquet("test.parquet", schema=pa.schema([("value", pa.duration("s"))]))

撰写回答