【问题标题】:The fastest way to create np.arrays for each item from list of tuples从元组列表中为每个项目创建 np.arrays 的最快方法
【发布时间】:2023-03-08 12:25:02
【问题描述】:

有一个元组列表l = [(x,y,z), (x,y,z), (x,y,z)] 这个想法是找到为每个 x-s、y-s、z-s 创建不同 np.array 的最快方法。需要帮助以找到最快的解决方案。为了进行速度比较,我使用下面附加的代码

import time

def myfast():
   code

n = 1000000
t0 = time.time()
for i in range(n): myfast()
t1 = time.time()

total_n = t1-t0

1.  np.array([i[0] for i in l])
    np.array([i[1] for i in l])
    np.array([i[2] for i in l])

输出:0.9980638027191162

2.  array_x = np.zeros((len(l), 1), dtype="float")
    array_y  = np.zeros((len(l), 1), dtype="float")
    array_z  = np.zeros((len(l), 1), dtype="float")

    for i, zxc in enumerate(l):
        array_x[i] = zxc[0]
        array_y[i] = zxc[1]
        array_z[i] = zxc[2]

输出 5.5509934425354

3. [np.array(x) for x in zip(*l)]

输出 2.5070037841796875

5. array_x, array_y, array_z = np.array(list(zip(*l)))

输出 2.725318431854248

【问题讨论】:

  • Printing tuples in a list的可能重复
  • @Chris 这是寻找最快解决方案的速度比较。不仅仅是可以在 stackoverflow 上找到的任何解决方案。 PS该帖子中的解决方案比我的第一个解决方案慢:)祝你有美好的一天
  • 是的,但是对于更长的列表,如果您想避免编写与元组中的项目一样多的列表推导式,zip 就是您想要的。顺便说一句,rusu 的解决方案没有给出你想要的结果......
  • @yatu 非常感谢您的解释,我明白了。但就我而言,没有那么多项目可以创建大量列表理解。
  • 第一种方法是不实用的,(例如,如果你的 len(l) = 1023131,你必须写很多代码

标签: python python-3.x list performance numpy


【解决方案1】:

你可以试试:

import numpy
array_x, array_y, array_z = numpy.array(list(zip(*l)))

或者只是:

numpy.array(list(zip(*l)))

还有更优雅的方式:

numpy.array(l).transpose()

【讨论】:

  • 我使用标准 timeit 模块添加了一个比较不同建议的答案。您使用.transpose() 的建议对于这个小输入数据来说是最快的。
  • 干得好,我认为大数据也是最快的(对 numpy 上的 ppl 工作盲目信任)
【解决方案2】:

也许我遗漏了一些东西,但为什么不直接将元组列表传递给np.array?说如果:

n = 100
l = [(0, 1, 2) for _ in range(n)]

arr = np.array(l)
x = arr[:, 0]
y = arr[:, 1]
z = arr[:, 2]

顺便说一句,我更喜欢使用以下时间码:

from timeit import default_timer as timer

t0 = timer()
do_heavy_calculation()
print("Time taken [sec]:", timer() - t0)

【讨论】:

  • 我使用标准timeit 模块添加了一个比较不同建议的答案。我同意你的观点,应该首选timeit 来衡量代码速度的时间。
【解决方案3】:

这里有一些非常好的选择,所以我总结了它们并比较了速度:

import numpy as np

def f1(input_data):
    array_x = np.array([elem[0] for elem in input_data])
    array_y = np.array([elem[1] for elem in input_data])
    array_z = np.array([elem[2] for elem in input_data])

    return array_x, array_y, array_z

def f2(input_data):
    array_x = np.zeros((len(input_data), ), dtype="float")
    array_y = np.zeros((len(input_data), ), dtype="float")
    array_z = np.zeros((len(input_data), ), dtype="float")

    for i, elem in enumerate(input_data):
        array_x[i] = elem[0]
        array_y[i] = elem[1]
        array_z[i] = elem[2]

    return array_x, array_y, array_z

def f3(input_data):
    return [np.array(elem) for elem in zip(*input_data)]

def f4(input_data):
    return np.array(list(zip(*input_data)))

def f5(input_data):
    return np.array(input_data).transpose()

def f6(input_data):
    array_all = np.array(input_data)
    array_x = array_all[:, 0]
    array_y = array_all[:, 1]
    array_z = array_all[:, 2]

    return array_x, array_y, array_z

首先我断言它们都返回相同的数据(使用np.array_equal()):

data = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
for array_list in zip(f1(data), f2(data), f3(data), f4(data), f5(data), f6(data)):
    # print()
    # for i, arr in enumerate(array_list):
    #     print('array from function', i+1)
    #     print(arr)
    for i, arr in enumerate(array_list[:-1]):
        assert np.array_equal(arr, array_list[i+1])

还有时间对比:

import timeit
for f in [f1, f2, f3, f4, f5, f6]:
    t = timeit.timeit('f(data)', 'from __main__ import data, f', number=100000)
    print('{:5s} {:10.4f} seconds'.format(f.__name__, t))

给出这些结果:

data = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]    # 3 tuples
timeit number=100000
f1        0.3184 seconds
f2        0.4013 seconds
f3        0.2826 seconds
f4        0.2091 seconds
f5        0.1732 seconds
f6        0.2159 seconds

data = [(1, 2, 3) for _ in range(10**6)]    # 1 millon tuples
timeit number=10
f1        2.2168 seconds
f2        2.8657 seconds
f3        2.0150 seconds
f4        1.9790 seconds
f5        2.6380 seconds
f6        2.6586 seconds

使f5() 成为短输入的最快选择,f4() 成为大输入的最快选择。


如果每个元组中的元素数量超过 3 个,那么只有 3 个函数适用于这种情况(其他函数被硬编码为每个元组中的 3 个元素):

data = [tuple(range(10**4)) for _ in range(10**3)]
timeit number=10
f3       11.8396 seconds
f4       13.4672 seconds
f5        4.6251 seconds

f5() 再次成为符合这些标准的最快选项。

【讨论】:

  • 我很确定,对于不同大小的输入,您会得到明显不同的结果。
  • @Ralf,干得好,非常感谢您的澄清。
  • 100 万个元组列表(timeit 重复 10x):f1 2.5675 秒 f2 2.8918 秒 f3 2.2974 秒 f4 2.2843 秒 f5 2.6894 秒 f6 2.6799 秒
  • @HåkonT。我将其添加到我的答案中;你是对的,f4 对于更大的输入会更快
  • 另外,这并不完全公平,因为结果略有不同。在某些情况下,您会收到 np.ndarray,而在其他情况下,您会收到 listtuple。目前尚不清楚这是否与 OP 相关,但肯定值得注意,因为这将加起来就是最终的时间安排。
【解决方案4】:

我相信这个答案的大部分(但不是全部)成分实际上存在于其他答案中,但到目前为止,在所有答案中,我还没有看到苹果与苹果之间的比较,因为有些方法没有返回np.ndarray 对象的列表,而是(在我看来很方便)单个np.ndarray()

尚不清楚您是否可以接受,因此我为此添加了适当的代码。 除此之外,性能可能会有所不同,因为在某些情况下您要添加额外的步骤,而对于其他一些情况,您可能不需要创建大对象(可能驻留在不同的内存页面中)。

最后,对于较小的输入 (3 x 10),np.ndarray()s 列表只是一些额外的负担,会显着增加时间。 对于较大的输入 (3 x 1000) 及以上,额外的计算不再重要,但涉及理解和避免创建大型 numpy 数组的方法可以变得与(甚至更快)一样快> than) 用于较小输入的最快方法。

此外,我提供的所有代码都适用于任意大小的元组/列表(当然,只要内部元组都具有相同的大小)。

(编辑:添加了对最终结果的评论)


测试的方法有:

import numpy as np


def to_arrays_zip(items):
    return np.array(list(zip(*items)))


def to_arrays_transpose(items):
    return np.array(items).transpose()


def to_arrays_zip_split(items):
    return [arr for arr in np.array(list(zip(*items)))]


def to_arrays_transpose_split(items):
    return [arr for arr in np.array(items).transpose()]


def to_arrays_comprehension(items):
    return [np.array([items[i][j] for i in range(len(items))]) for j in range(len(items[0]))]


def to_arrays_comprehension2(items):
    return [np.array([item[j] for item in items]) for j in range(len(items[0]))]

(这是一个方便检查结果是否相同的函数。)

def test_equal(items1, items2):
    return all(np.all(x == y) for x, y in zip(items1, items2))

对于小输入:

N = 3
M = 10
ll = [tuple(range(N)) for _ in range(M)]

print(to_arrays_comprehension2(ll))

print('Returning `np.ndarray()`')
%timeit to_arrays_zip(ll)
# 2.82 µs ± 28 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit to_arrays_transpose(ll)
# 3.18 µs ± 30 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

print('Returning a list')
%timeit to_arrays_zip_split(ll)
# 3.71 µs ± 47 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit to_arrays_transpose_split(ll)
# 3.97 µs ± 42.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit to_arrays_comprehension(ll)
# 5.91 µs ± 96.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit to_arrays_comprehension2(ll)
# 5.14 µs ± 109 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

讲台在哪里:

  1. to_arrays_zip_split()(非_split,如果您可以使用单个数组)
  2. to_arrays_zip_transpose_split()(非_split,如果您可以使用单个数组)
  3. to_arrays_comprehension2()

对于更大的输入:

N = 3
M = 1000
ll = [tuple(range(N)) for _ in range(M)]

print('Returning `np.ndarray()`')
%timeit to_arrays_zip(ll)
# 146 µs ± 2.3 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit to_arrays_transpose(ll)
# 222 µs ± 2.01 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

print('Returning a list')
%timeit to_arrays_zip_split(ll)
# 147 µs ± 1.68 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit to_arrays_transpose_split(ll)
# 221 µs ± 2.58 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit to_arrays_comprehension(ll)
# 261 µs ± 2.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit to_arrays_comprehension2(ll)
# 212 µs ± 1.68 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

讲台变成:

  1. to_arrays_zip_split()(无论您使用_split 还是非_split 变体,都没有太大区别)
  2. to_arrays_comprehension2()
  3. to_arrays_zip_transpose_split()(无论您使用_split 还是非_split 变体,都没有太大区别)

对于更大的输入:

N = 3
M = 1000000
ll = [tuple(range(N)) for _ in range(M)]

print('Returning `np.ndarray()`')
%timeit to_arrays_zip(ll)
# 215 ms ± 4.27 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit to_arrays_transpose(ll)
# 220 ms ± 4.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

print('Returning a list')
%timeit to_arrays_zip_split(ll)
# 218 ms ± 6.21 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit to_arrays_transpose_split(ll)
# 222 ms ± 3.48 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit to_arrays_comprehension(ll)
# 248 ms ± 3.55 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit to_arrays_comprehension2(ll)
# 186 ms ± 481 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

讲台变成:

  1. to_arrays_comprehension2()
  2. to_arrays_zip_split()(无论您使用_split 还是非_split 变体,都没有太大区别)
  3. to_arrays_zip_transpose_split()(无论您使用_split 还是非_split 变体,都没有太大区别)

_zip_transpose 变体非常接近。

(我还尝试使用 Numba 加快速度,但进展不顺利)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 2010-11-23
    相关资源
    最近更新 更多