如何更新数组中许多对象的属性的值

时间:2019-03-27 14:48:51

标签: python python-3.x numpy

我有一个类别为粒子p = [Particle, Particle, ...] N 个对象的小菜,其中每个对象都有一个属性posvel,每个都有一个数组{{1 }},所有的浮点数。我想知道更新数组中所有[x,y]pos属性值的最有效方法是什么。 我想对大量粒子进行以下更新,其中vel是2D数组, N 很多acc

[x,y]

我想知道是否存在一种更有效的方法来更改p元素的每个位置和速度属性,而不是使用逐个设置每个for i,body in enumerate(p): body.vel -= acc[i] * timestep body.pos += body.vel * timestep pos元素的值相应的加速元件。似乎必须有一种Python方式才能做到这一点。

我希望像vel这样的东西可以访问p[:].pos值的数组。

2 个答案:

答案 0 :(得分:0)

也许numpy.recarray将在这里为您提供帮助。使用recarray代替ndarray更可以满足您的要求。 recarray允许使用属性进行字段访问。一些示例代码在这里:

acc = numpy.array([1, 2, 3])
timestep = 1
paticle_num = len(acc)

arr = numpy.recarray(paticle_num, dtype=[('pos', float), ('vel', float)])
# initialize
arr.pos = numpy.arange(paticle_num)
arr.vel = numpy.arange(paticle_num)

# operate in the whole dimension easily, I think that is what you want.
arr.vel -= acc * timestep
arr.pos += arr.vel * timestep

为了简化问题,我只是将posvel视为浮点数,如果它们像[x, y]那样浮点数,就是这样。

答案 1 :(得分:0)

我建议使用pandas.DataFrame

我做了一些实现来比较速度。现在,您的选择几乎与使用带有Particle对象的简单Python列表相同。

import random
import timeit
import numpy as np
import pandas as pd

class Particle:
    def __init__(self, pos, vel):
        self.pos = pos
        self.vel = vel

    def __repr__(self):
        return 'Particle({:.2f} | {:.2f})'.format(self.pos, self.vel)

def f1(data, acc, time_step=2):
    for i, p in enumerate(data):
        p.vel -= acc[i] * time_step
        p.pos += p.vel * time_step

    return data

def f2(data, acc, time_step=2):
    df['vel'] -= acc * time_step
    df['pos'] += df['vel'] * time_step

    return data

if __name__ == '__main__':
    for n1 in (10**1, 10**3, 10**5):
        particle_list = [
            [random.random(), random.random()]
            for _ in range(n1)]
        acceleration_arr = np.random.random((n1, ))
        acceleration_arr_2 = acceleration_arr.reshape((n1, 1))

        # option 1
        particle_list_1 = [
            Particle(pos, vel)
            for pos, vel in particle_list]

        # option 2
        df = pd.DataFrame(
            data=particle_list,
            columns=['pos', 'vel'])

        # assure results are equal
        ret_1 = f1(particle_list_1, acceleration_arr)
        ret_2 = f2(df, acceleration_arr)
        # convert to lists
        ret_1 = [(p.pos, p.vel) for p in ret_1]
        ret_2 = [(p['pos'], p['vel']) for _, p in ret_2.iterrows()]
        # print('ret_1', ret_1)
        # print('ret_2', ret_2)
        assert ret_1 == ret_2

        # compare duration
        repetitions = 100
        t1 = timeit.timeit(
            'f1(particle_list_1, acceleration_arr)',
            'from __main__ import f1, acceleration_arr, particle_list_1',
            number=repetitions)
        t2 = timeit.timeit(
            'f2(df, acceleration_arr)',
            'from __main__ import f2, acceleration_arr, df',
            number=repetitions)
        print('n={:10d} | {:30s} {:.6f}'.format(n1, 'list with for-loop', t1))
        print('n={:10d} | {:30s} {:.6f}'.format(n1, 'pandas.DataFrame', t2))
        print()

运行此代码将得到以下输出:pandas版本随着数据大小的增长而快得多:

n=        10 | list with for-loop             0.001032
n=        10 | pandas.DataFrame               0.064379     # pandas is slower

n=      1000 | list with for-loop             0.106632
n=      1000 | pandas.DataFrame               0.067613     # pandas is faster

n=    100000 | list with for-loop             9.986003
n=    100000 | pandas.DataFrame               0.115627     # pandas is a lot faster

当然,我的示例实现与您描述的不完全相同,但是它表明有一种更快的方法可以实现您的预​​期目标。