将numpy类型转换为Python

53

我有一个由pandas生成的字典列表,格式如下。我想将它转换为JSON格式。

list_val = [{1.0: 685}, {2.0: 8}]
output = json.dumps(list_val)

然而,json.dumps会抛出一个错误:TypeError: 685不可序列化为JSON。

我猜测这是从numpy到python的类型转换问题(?)。

然而,当我使用np.int32(v)来转换数组中每个字典的值v时,仍然会出现错误。

编辑:这是完整代码

            new = df[df[label] == label_new] 
            ks_dict = json.loads(content)
            ks_list = ks_dict['variables']
            freq_counts = []

            for ks_var in ks_list:

                    freq_var = dict()
                    freq_var["name"] = ks_var["name"]
                    ks_series = new[ks_var["name"]]
                    temp_df = ks_series.value_counts().to_dict()
                    freq_var["new"] = [{u: np.int32(v)} for (u, v) in temp_df.iteritems()]            
                    freq_counts.append(freq_var)

           out = json.dumps(freq_counts)

你的代码在我的电脑上运行良好...(Python 3.4.2) - [{"1.0": 685}, {"2.0": 8}] - MattDMo
所以 list_val 是一个 numpy 数组? - Padraic Cunningham
是的,它是从DataFrame生成的。我会在帖子中更新完整的代码。 - ubh
那么...你在freq_var中为什么要放置np.int32(v)而不是v(或者int(v);我不确定v是什么)? - abarnert
另外,当你将来遇到类似的问题时,请先查看每个对象的reprtype,而不仅仅是打印它们的str。 (并将结果包含在您的问题中。)知道您有一个np.float32或其他类型会比猜测可能存在某种类型转换问题要容易得多。 - abarnert
我使用了int32,以为这会解决类型错误。但是,当我将np.int32(v)更改为np.int(v)时,它起作用了。 - ubh
7个回答

128

看起来你是正确的:

>>> import numpy
>>> import json
>>> json.dumps(numpy.int32(685))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/json/__init__.py", line 243, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib/python2.7/json/encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python2.7/json/encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: 685 is not JSON serializable

不幸的是,numpy数字的__repr__未提供任何关于它们的类型的提示。它们伪装成int时可能并不是(惊叹声)。最终,看起来像json在告诉你一个int不能被序列化,但实际上,它告诉你这个特定的np.int32(或者你实际拥有的任何类型)不能被序列化。(没有什么真正的惊喜--没有np.int32是可序列化的)。这也是为什么在传递给json.dumps之前,你不可避免地要打印一下字典,发现它只有整数。

最简单的解决方法可能是编写自己的序列化器1

class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, numpy.integer):
            return int(obj)
        elif isinstance(obj, numpy.floating):
            return float(obj)
        elif isinstance(obj, numpy.ndarray):
            return obj.tolist()
        else:
            return super(MyEncoder, self).default(obj)
您可以像这样使用它:

json.dumps(numpy.float32(1.2), cls=MyEncoder)
json.dumps(numpy.arange(12), cls=MyEncoder)
json.dumps({'a': numpy.int32(42)}, cls=MyEncoder)

1或者你可以只编写默认函数,并将其作为 json.dumpsdefault 关键字参数传递。在这种情况下,您将使用 raise TypeError 替换最后一行,但是...呃,类更具可扩展性 :-)


4
如果想真正玩得开心,可以尝试使用 np.float64np.bool 进行操作,因为它们实际上是 floatbool 的子类。如果你仔细想一想,就会明白为什么这两种类型是子类,而其他数字类型却不是,但在你理解之前,可能需要进行一些有趣的调试... - abarnert
@abarnert -- np.float64是显而易见的(毕竟它只是C语言中的double,这也是Python用于float的类型),但np.bool就不那么明显了。我认为它本可以是np.int32的子类...看一下np.int64__mro__,我认为在Python2.x上至少可以工作。 - mgilson
1
或者您可以将numpy类型转换为本地类型。请参阅https://dev59.com/0mox5IYBdhLWcg3wFAU1#11389998,了解如何执行此操作的详细信息。 - Mack
1
或者您可以使用json_tricks库,它默认情况下会执行此操作(声明:我是主要贡献者)。 - Mark
不幸的是,JSON 规范不支持复杂值......所以我认为任何将复杂值转换为 JSON 的代码都会有点 hacky。或者至少我的尝试听起来是 hacky 的。 - Trevor Boyd Smith

4

您还可以将数组转换为Python列表(使用tolist方法),然后将列表转换为JSON。


2
在更简单的情况下,当你只需要将numpy数值转换时,最简单的方法是:
json.dumps(a, default=float)

2
您可以使用我们的ujson分支来处理NumPy int64。 caiyunapp / ultrajson:用Python绑定和NumPy绑定编写的C语言超快速JSON解码器和编码器。最初的回答。
pip install nujson

那么

>>> import numpy as np
>>> import nujson as ujson
>>> a = {"a": np.int64(100)}
>>> ujson.dumps(a)
'{"a":100}'
>>> a["b"] = np.float64(10.9)
>>> ujson.dumps(a)
'{"a":100,"b":10.9}'
>>> a["c"] = np.str_("12")
>>> ujson.dumps(a)
'{"a":100,"b":10.9,"c":"12"}'
>>> a["d"] = np.array(list(range(10)))
>>> ujson.dumps(a)
'{"a":100,"b":10.9,"c":"12","d":[0,1,2,3,4,5,6,7,8,9]}'
>>> a["e"] = np.repeat(3.9, 4)
>>> ujson.dumps(a)
'{"a":100,"b":10.9,"c":"12","d":[0,1,2,3,4,5,6,7,8,9],"e":[3.9,3.9,3.9,3.9]}'

1
如果您将数据留在任何pandas对象中,该库会在Series、DataFrame和所有其他更高维度的表亲上提供一个to_json函数。
请参见Series.to_json()

1
这样做不行,因为Series.to_json()仍然无法处理numpy.ndarrays。 - nivniv
1
经过长时间的头痛和抵制,为了一个看似简单的问题创建自定义函数或类,这对我很有效!!我喜欢它因为它保持事情简单! - vk1011

1
在某些情况下,简单的json.dump(eval(str(a)), your_file)可以解决问题。

这是一个可怕的hack,但对于我遇到的某些隐藏在周围的float32实例的复杂数据结构来说,它要简洁得多。 - starbeamrainbowlabs

0
如果您有包含多个numpy对象(如ndarray或float32对象)的字典,您可以使用.tolist()手动将ndarray转换为列表。
import numpy as np
import json

a = np.empty([2, 2], dtype=np.float32)
json.dumps(a.tolist()) # this should work

或者使用.item()保存一个float32对象。

import numpy as np
import json

a = np.float32(1)
json.dumps(a.item()) # this should work

但是,如果您有一个包含多个嵌套在列表中的字典的复杂字典,并且这些字典进一步嵌套了numpy对象,那么在代码中导航和手动更新每个变量会变得繁琐,您可能不想这样做。相反,您可以定义一个NumpyEncoder类,在json.dumps()期间为您处理此问题reference

class NumpyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, np.float32):
            return obj.item()
        if isinstance(obj, np.ndarray):
            return obj.tolist()
        return json.JSONEncoder.default(self, obj)

with open('output.json', 'w') as outfile: 
    json.dump(json_dict, outfile, sort_keys=True, indent=4, cls=NumpyEncoder) # indent and sort_keys are just for cleaner output

这对我来说完美地运作了,甚至允许我们在保存到JSON时处理任何其他数据类型的例子,例如在保存时格式化小数位。

class NumpyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, float):
            return "{:.2f}".format(obj)
        return json.JSONEncoder.default(self, obj)

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