从numpy数组行中制作python对象的最快方法

时间:2018-11-08 08:47:54

标签: python-3.x pandas numpy oop vectorization

我需要从numpy数组(或熊猫数据框)中列出对象列表。每行都包含对象的所有属性值(请参见示例)。

import numpy as np

class Dog:

def __init__(self, weight, height, width, girth):
    self.weight = weight
    self.height = height
    self.width = width
    self.girth = girth


dogs = np.array([[5, 100, 50, 80], [4, 80, 30, 70], [7, 120, 60, 90], [2, 50, 30, 50]])

# list comprehension with idexes
dog_list = [Dog(dogs[i][0], dogs[i][1], dogs[i][2], dogs[i][3]) for i in range(len(dogs))]

我的真实数据当然要大得多(多达5列的一百万行),因此逐行进行迭代并查找正确的索引会花费很多时间。有没有一种方法可以对此进行矢量化,或者通常使其更高效/更快?我尝试自己寻找方法,但至少在我的专业水平上,我找不到任何可翻译的内容。

但是保留行的顺序非常重要,因此,如果行不通,我想我将不得不忍受缓慢的操作。

干杯!

编辑-关于np.vectorize的问题:

这是我实际代码的一部分,以及一些实际数据:

将numpy导入为np

class Particle:
    TrackID = 0
    def __init__(self, uniq_ident, intensity, sigma, chi2, past_nn_ident, past_distance, aligned_x, aligned_y, NeNA):
        self.uniq_ident = uniq_ident
        self.intensity = intensity
        self.sigma = sigma
        self.chi2 = chi2
        self.past_nn_ident = past_nn_ident
        self.past_distance = past_distance
        self.aligned_y = aligned_y
        self.aligned_x = aligned_x
        self.NeNA = NeNA
        self.new_track_length = 1
        self.quality_pass = True  
        self.re_seeder(self.NeNA)


def re_seeder(self, NeNA):

    if np.isnan(self.past_nn_ident):  
        self.newseed = True            
        self.new_track_id = Particle.TrackID
        print(self.new_track_id)
        Particle.TrackID += 1

    else:
        self.newseed = False
        self.new_track_id = None

data = np.array([[0.00000000e+00, 2.98863746e+03, 2.11794100e+02, 1.02241467e+04, np.NaN,np.NaN, 9.00081968e+02, 2.52456745e+04, 1.50000000e+01],
       [1.00000000e+00, 2.80583577e+03, 4.66145720e+02, 6.05642671e+03, np.NaN, np.NaN, 8.27249728e+02, 2.26365501e+04, 1.50000000e+01],
       [2.00000000e+00, 5.28702810e+02, 3.30889610e+02, 5.10632793e+03, np.NaN, np.NaN, 6.03337243e+03, 6.52702811e+04, 1.50000000e+01],
       [3.00000000e+00, 3.56128350e+02, 1.38663730e+02, 3.37923885e+03, np.NaN, np.NaN, 6.43263261e+03, 6.14788766e+04, 1.50000000e+01],
       [4.00000000e+00, 9.10148200e+01, 8.30057400e+01, 4.31205993e+03, np.NaN, np.NaN, 7.63955009e+03, 6.08925862e+04, 1.50000000e+01]])

Particle.TrackID = 0
particles = np.vectorize(Particle)(*data.transpose())

l = [p.new_track_id for p in particles]

对此感到奇怪的是,ree_seeder函数“ print(self.new_track_id)”中的print语句将打印0、1、2、3、4、5。

如果我然后取出粒子对象,并从它们的new_track_id属性“ l = [p中的p的p.new_track_id]”中列出来,则值为1、2、3、4、5。

所以在某个地方,第一个对象不知何故丢失,重写或其他我不理解的东西。

3 个答案:

答案 0 :(得分:2)

只要坚持构建Python对象,您就不会获得很大的效率/速度提升。拥有这么多项目,将数据保留在numpy数组中将可以为您提供更好的服务。如果您希望更好的属性访问,可以将数组转换为记录数组(recarray),这将允许您在仍保持命名的情况下命名列(如weightheight等)将数据保存在numpy数组中。

dog_t = np.dtype([
    ('weight', int),
    ('height', int),
    ('width', int),
    ('girth', int)
])

dogs = np.array([
    (5, 100, 50, 80),
    (4, 80, 30, 70),
    (7, 120, 60, 90),
    (2, 50, 30, 50),
], dtype=dog_t)

dogs_recarray = dogs.view(np.recarray)

print(dogs_recarray.weight)
print(dogs_recarray[2].height)

如果需要,您还可以混合和匹配数据类型(例如,如果某些列是整数,而另一些则是浮点数)。请注意,在使用此代码时,dogs数组中的项需要在元组中指定(使用()),而不是在列表中指定才能正确应用数据类型。

答案 1 :(得分:0)

Multiprocessing可能值得一看。

from multiprocessing import Pool dog_list = []

将对象追加到列表的功能:

def append_dog(i): dog_list.append(Dog(*dogs[i]))

让多个工作人员并行追加到此列表:

number_of_workers = 4 pool = Pool(processes=number_of_workers) pool.map_async(append_dog, range(len(dogs)))

或更短的版本:

from multiprocessing import Pool
number_of_workers = 4
pool = Pool(processes=number_of_workers)
pool.map_async(lambda i: dog_list.append(Dog(*dogs[i])), range(len(dogs)))

答案 2 :(得分:0)

使用一个简单的类:

class Foo():
    _id = 0
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
        self.id = self._id
        Foo._id += 1
    def __repr__(self):
        return '<Foo %s>'%self.id


In [23]: arr = np.arange(12).reshape(4,3)

直观的列表理解:

In [24]: [Foo(*xyz) for xyz in arr]
Out[24]: [<Foo 0>, <Foo 1>, <Foo 2>, <Foo 3>]

默认使用vectorize

In [26]: np.vectorize(Foo)(*arr.T)
Out[26]: array([<Foo 5>, <Foo 6>, <Foo 7>, <Foo 8>], dtype=object)

请注意,Foo 4已被跳过。 vectorize执行试算以确定返回的dtype(此处为object)。 (这给其他用户带来了问题。)我们可以通过指定otypes来解决此问题。还有一个cache参数可能有用,但是我没有玩过。

In [27]: np.vectorize(Foo,otypes=[object])(*arr.T)
Out[27]: array([<Foo 9>, <Foo 10>, <Foo 11>, <Foo 12>], dtype=object)

内部vectorize使用frompyfunc,在这种情况下,效果也一样,并且根据我的经验,速度更快:

In [28]: np.frompyfunc(Foo, 3,1)(*arr.T)
Out[28]: array([<Foo 13>, <Foo 14>, <Foo 15>, <Foo 16>], dtype=object)

通常vectorize/frompyfunc将“标量”值传递给函数,从而迭代2d数组的整个元素。但是使用*arr.T是传递行的一种聪明方法-实际上是一维元组数组。

In [31]: list(zip(*arr.T)) 
Out[31]: [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)]

一些比较时间:

In [32]: Foo._id=0
In [33]: timeit [Foo(*xyz) for xyz in arr]
14.2 µs ± 17.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [34]: Foo._id=0
In [35]: timeit np.vectorize(Foo,otypes=[object])(*arr.T)
44.9 µs ± 108 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [36]: Foo._id=0
In [37]: timeit np.frompyfunc(Foo, 3,1)(*arr.T)
15.6 µs ± 18.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

这与我过去的时间一致。 vectorize很慢。 frompyfunc在列表理解方面具有竞争力,有时甚至快2倍。将列表解析包装在数组中会减慢其速度,例如np.array([Foo(*xyz)...])

以及您的原始列表理解:

In [40]: timeit [Foo(arr[i][0],arr[i][1],arr[i][2]) for i in range(len(arr))]
10.1 µs ± 80 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

那甚至更快!因此,如果您的目标是列表而不是数组,那么我看不出使用numpy工具的意义。

当然,在一个小例子中,这些计时需要谨慎对待。