การปรับปรุงประสิทธิภาพในการแปลงอาร์เรย์ numpy
ด้วยnumpyอาร์เรย์ 1D สามอาร์เรย์ฉันต้องการแปลงเป็นดังนี้:
import numpy as np
Xd = np.asarray([0, 0, 1, 1, 0.5])
Yd = np.asarray([0, 0, 0, 2.5, 2.5])
Zd = np.asarray([0, 1.5, 1.5, 1.5, 1.5])
points = np.stack([Xd, Yd, Zd], axis=1).reshape(-1, 1, 3)
segments = np.concatenate([points[:-1], points[1:]], axis = 1)
print(segments.shape)
print(segments)
เอาท์พุต:
(4, 2, 3)
[[[0. 0. 0. ]
[0. 0. 1.5]]
[[0. 0. 1.5]
[1. 0. 1.5]]
[[1. 0. 1.5]
[1. 2.5 1.5]]
[[1. 2.5 1.5]
[0.5 2.5 1.5]]]
มีวิธีปรับปรุงประสิทธิภาพของการเปลี่ยนแปลงนี้หรือไม่?
พื้นหลัง
การเปลี่ยนแปลงครั้งนี้มีความจำเป็นต้องใช้XYZพิกัดกับmatplotlib Line3DCollectionจนถึงตอนนี้ฉันเห็นเพียงรูปแบบต่างๆของโค้ดด้านบน แต่ด้วยพิกัดหรือข้อมูลที่ถูกแก้ไขหลายพันรายการเพื่อความละเอียดที่ดีขึ้นจึงจำเป็นต้องมีแนวทางที่ดีที่สุด
สรุป
ขอบคุณ@Mercuryสรุปได้ว่าสำหรับอาร์เรย์ที่สั้นกว่า (ความยาว <1k) คำตอบของ @Miguelทำงานได้ดีกว่า แต่วิธีการของ @mathfux จะปรับขนาดได้ดีขึ้นเมื่ออาร์เรย์ยาวขึ้น
คำตอบ
ดูเหมือนว่าคุณกำลังพยายามม้วนหน้าต่างรูปร่าง(2, 3)ในอาร์เรย์ 2 มิติ สิ่งนี้คล้ายกับการแปลงภาพซึ่งสามารถทำได้ด้วยnp.lib.stride_tricksวิธีที่มีประสิทธิภาพมาก
a = np.transpose([Xd, Yd, Zd])
window = (2, 3)
view_shape = (len(a) - window[0] + 1,) + window # (4,2,3) if len(a) == 5
sub_matrix = np.lib.stride_tricks.as_strided(a, shape = view_shape, strides = (a.itemsize,) + a.strides)
>>> sub_matrix
array([[[0. , 0. , 0. ],
[0. , 0. , 1.5]],
[[0. , 0. , 1.5],
[1. , 0. , 1.5]],
[[1. , 0. , 1.5],
[1. , 2.5, 1.5]],
[[1. , 2.5, 1.5],
[0.5, 2.5, 1.5]]])
โปรดทราบว่าnp.lib.stride_tricksมีประสิทธิภาพมากเมื่อเทียบกับวิธีอื่น ๆ
ตามคำแนะนำทั่วไปเมื่อคุณต้องการความเร็วโดยทั่วไปคุณควรพยายามหลีกเลี่ยงการสแต็กและเชื่อมต่อกันเนื่องจากมักหมายถึงการคัดลอกข้อมูลเดียวกันในรอบทวีคูณ
อย่างไรก็ตามนี่คือวิธีที่ฉันจะทำรหัสยาวกว่าเล็กน้อย แต่ไม่ได้ผลเกินความจำเป็น
n = len(Xd)
segments = np.empty((n-1, 2, 3))
segments[:,0,0] = Xd[:-1]
segments[:,1,0] = Xd[1:]
segments[:,0,1] = Yd[:-1]
segments[:,1,1] = Yd[1:]
segments[:,0,2] = Zd[:-1]
segments[:,1,2] = Zd[1:]
[แก้ไข] - ต่อไปนี้สร้างขึ้นเพื่อวิทยาศาสตร์ / ความสนุกสนานอย่าทำซ้ำ e
ดังนั้นฉันจึงพยายามดูว่าฉันสามารถบีบประสิทธิภาพได้มากขึ้นอีกเล็กน้อยจากคำตอบของ@mathfuxหรือไม่และฉันก็ออกมาพร้อมกับรหัสที่น่าเกลียดนี้:
a = np.empty(3*n)
a[:n] = Xd
a[n:n+n] = Yd
a[n+n:] = Zd
interface = dict(a.__array_interface__)
interface['shape'] = (n-1, 2, 3)
interface['strides'] = (a.itemsize, a.itemsize, n*a.itemsize)
segments= np.array(np.lib.stride_tricks.DummyArray(interface, base=a), copy=False)
บนเครื่องของฉันมันเร็วกว่าอย่างวัดได้ (สูงสุด ~ 30% ขึ้นอยู่กับขนาดของอินพุต) กำไรส่วนหนึ่งเกิดจากการสร้างaและข้ามการตรวจสอบของas_strided
ต่อไปนี้คือการทดสอบเวลาในอาร์เรย์ขนาดใหญ่ซึ่งทำให้ความแตกต่างชัดเจนขึ้น
import numpy as np
from timeit import timeit
# original
def f1(x, y, z):
points = np.stack([x, y, z], axis=1).reshape(-1, 1, 3)
return np.concatenate([points[:-1], points[1:]], axis = 1)
# preallocating and then assigning
def f2(x, y, z):
segments = np.empty((len(x)-1, 2, 3))
segments[:,0,0] = x[:-1]
segments[:,1,0] = x[1:]
segments[:,0,1] = y[:-1]
segments[:,1,1] = y[1:]
segments[:,0,2] = z[:-1]
segments[:,1,2] = z[1:]
return segments
# stacking, but in one go
def f3(x, y, z):
segments = np.stack([x[:-1], y[:-1], z[:-1], x[1:], y[1:],z[1:]], axis=1)
return segments.reshape(-1, 2, 3)
# list comparison
def f4(x, y, z):
z_ = [i for i in zip(x,y,z)]
return [[[z_[i]],[z_[i+1]]] for i in range(len(z_)-1)]
#np.lib.stride_tricks approach
def f5(x, y, z):
a = np.transpose([x, y, z])
window = (2, 3)
view_shape = (len(a) - window[0] + 1,) + window # (4,2,3) if len(a) == 5
return np.lib.stride_tricks.as_strided(a, shape = view_shape, strides = (a.itemsize,) + a.strides)
ntime = 5000 #number of test runs
nxd = 500 #array length
Xd = np.random.randn(nxd)
Yd = np.random.randn(nxd)
Zd = np.random.randn(nxd)
print(timeit(lambda: f1(Xd, Yd, Zd), number=ntime))
#0.11369249999999999
print(timeit(lambda: f2(Xd, Yd, Zd), number=ntime))
#0.0480651
print(timeit(lambda: f3(Xd, Yd, Zd), number=ntime))
#0.10202380000000003
print(timeit(lambda: f4(Xd, Yd, Zd), number=ntime))
#1.8407391
print(timeit(lambda: f5(Xd, Yd, Zd), number=ntime))
#0.09132560000000023
ntime = 50 #number of test runs
nxd = 500000 #array length
Xd = np.random.randn(nxd)
Yd = np.random.randn(nxd)
Zd = np.random.randn(nxd)
print(timeit(lambda: f1(Xd, Yd, Zd), number=ntime))
#1.7519548999999999
print(timeit(lambda: f2(Xd, Yd, Zd), number=ntime))
#1.504727
print(timeit(lambda: f3(Xd, Yd, Zd), number=ntime))
#1.5010566
print(timeit(lambda: f4(Xd, Yd, Zd), number=ntime))
#22.6208157
print(timeit(lambda: f5(Xd, Yd, Zd), number=ntime))
#0.46465339999999955
อย่างที่คุณเห็นวิธีของ @ Miguel เป็นวิธีที่จะไป: การจัดสรรอาร์เรย์ล่วงหน้าแล้วกำหนดเป็นวิธีที่มีประสิทธิภาพที่สุดในการดำเนินการนี้ แม้ว่าคุณจะวางซ้อนกันอย่างชาญฉลาดเช่นใน f3 () แต่ก็ยังช้ากว่า f2 () แต่ไม่มีสิ่งใดที่จะเอาชนะ f5 () เมื่อความยาวอาร์เรย์เพิ่มขึ้นอย่างมาก
ฉันพบว่าสิ่งนี้เร็วกว่ารหัสของ @ มิเกล
z = [i for i in zip(Xd,Yd,Zd)]
segments = [[[z[i]],[z[i+1]]] for i in range(len(z)-1)]