pyarrow.lib.ArrowInvalid: ('无法将类型为Y的X进行转换:在推断Arrow数据类型时未识别Python值类型')

24

使用pyarrow将包含Player对象的pandas.DataFrame转换为pyarrow.Table的代码如下:

import pandas as pd
import pyarrow as pa

class Player:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def __repr__(self):
        return f'<{self.name} ({self.age})>'

data = [
    Player('Jack', 21, 'm'),
    Player('Ryan', 18, 'm'),
    Player('Jane', 35, 'f'),
]
df = pd.DataFrame(data, columns=['player'])
print(pa.Table.from_pandas(df))

我们得到了错误:

pyarrow.lib.ArrowInvalid: ('Could not convert <Jack (21)> with type Player: did not recognize Python value type when inferring an Arrow data type', 'Conversion failed for column 0 with type object')

使用时遇到相同的错误

df.to_parquet('players.pq')

是否可以使用 pyarrow 并回退到使用 pickle 来序列化这些Python对象?还是有更好的解决方案?pyarrow.Table 最终将使用 Parquet.write_table() 写入磁盘。

  • 使用 Python 3.8.0,pandas 0.25.3,pyarrow 0.13.0。
  • pandas.DataFrame.to_parquet() 不支持多级索引,因此最好使用 pq.write_table(pa.Table.from_dataframe(pandas.DataFrame)) 的解决方案。

谢谢!


你能否在Apache Arrow上开一个JIRA问题?我们不会在StackOverflow上与用户或开发人员互动。https://github.com/apache/arrow/blob/master/CONTRIBUTING.md - Wes McKinney
1
你最终解决了这个问题吗? - rocksNwaves
4个回答

4

我的建议是将数据插入到已序列化的DataFrame中。

最佳方案 - 使用dataclass (python >=3.7)

通过装饰器将Player类定义为dataclass,并让序列化自动完成(转换为JSON格式)。

import pandas as pd
from dataclasses import dataclass

@dataclass
class PlayerV2:
    name:str
    age:int
    gender:str

    def __repr__(self):
        return f'<{self.name} ({self.age})>'


dataV2 = [
    PlayerV2(name='Jack', age=21, gender='m'),
    PlayerV2(name='Ryan', age=18, gender='m'),
    PlayerV2(name='Jane', age=35, gender='f'),
]

# The serialization is done natively to JSON
df_v2 = pd.DataFrame(data, columns=['player'])
print(df_v2)

# Can still get the objects's attributes by deserializeing the record
json.loads(df_v2["player"][0])['name']

手动序列化对象(Python < 3.7)

在Player类中定义一个序列化函数,在创建Dataframe之前对每个实例进行序列化。

import pandas as pd
import json

class Player:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def __repr__(self):
        return f'<{self.name} ({self.age})>'
    
    # The serialization function for JSON, if for some reason you really need pickle you can use it instead
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__)

# Serialize the objects before inserting it into the DataFrame
data = [
    Player('Jack', 21, 'm').toJSON(),
    Player('Ryan', 18, 'm').toJSON(),
    Player('Jane', 35, 'f').toJSON(),
]
df = pd.DataFrame(data, columns=['player'])

# You can see all the data inserted as a serialized json into the column player
print(df)

# Can still get the objects's attributes by deserializeing the record
json.loads(df["player"][0])['name']

0
在我的理解中,由于repr存在问题,所以出现了“类型”问题。尝试这种方法(它有效):
class Player:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def other(self):
        return f'<{self.name} ({self.age})>'

data = [
    Player('Jack', 21, 'm').other(),
    Player('Ryan', 18, 'm').other(),
    Player('Jane', 35, 'f').other(),
]
df = pd.DataFrame(data, columns=['player'])
print(df)
        player
0  <Jack (21)>
1  <Ryan (18)>
2  <Jane (35)>

print(pa.Table.from_pandas(df))

pyarrow.Table
player: string

0

不确定parquet支持格式<string(int)>。 但它适用于字典,列表。

对于一个Python类, 通过调用object.dict来获取对象的字典表示。

例如, 以下代码可以正常工作

from dataclasses import dataclass
import pandas as pd
import pyarrow as pa

@dataclass
class Player:
  name: str
  age: int
  gender: str

players = [
  {"name": "player1", "age": 12, "gender": "f"},
  {"name": "player2", "age": 22, "gender": "m"},
  {"name": "player3", "age": 18, "gender": "m"}
]
df = pd.DataFrame()
df["players"] = [Player(**r).__dict__ for r in players]

pa.Table.from_pandas(df)

您的答案可以通过添加更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认您的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

0
另一个选项是使用自定义的 Dtype 扩展 pandas。Pandas 提供了相当多的文档,介绍如何创建扩展 Dtype,您可以查看基类以获取更多详细信息,并查看现有扩展以获取示例。

话虽如此,这有点复杂,如果你只是想解决“无法转换”错误并将数据打印或保存到Parquet中,我建议采用其他答案中提到的某种形式的预序列化,或者在你的类上实现__str__,然后将列类型转换为str。顺便说一句,既然你将使用__str__来实现其预期目的,你可以改进你的__repr__,返回一个看起来像一个有效的Python表达式,可以用来重新创建具有相同值的对象(在适当的环境下)的字符串。把它们全部组合起来,就会得到以下内容:

import pandas as pd
import pyarrow as pa

class Player:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def __repr__(self):
        return f'Player("{self.name}", {self.age}, "{self.gender}")'

    def __str__(self):
        return f'<{self.name} ({self.age})>'


data = [
    Player('Jack', 21, 'm'),
    Player('Ryan', 18, 'm'),
    Player('Jane', 35, 'f'),
]
df = pd.DataFrame(data, columns=['player'])
for col in [c for c in df.select_dtypes(include=['object']).columns]:
    df[col] = df[col].astype('str')

print(pa.Table.from_pandas(df))
df.to_parquet('players.pq')
print([repr(d) for d in data])

这将输出结果:
pyarrow.Table
player: string
----
player: [["<Jack (21)>","<Ryan (18)>","<Jane (35)>"]]
# No output from to_parquet b/c there was no error
['Player("Jack", 21, "m")', 'Player("Ryan", 18, "m")', 'Player("Jane", 35, "f")']

当然,如果您想保留原始类型的原始`DataFrame`,则需要在副本上更改这些列类型,而不是在原始数据上更改。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接