2010-01-21 75 views
28

Tôi tạo ra một danh sách các mảng có chiều rộng một chiều trong một vòng lặp và sau đó chuyển đổi danh sách này thành một mảng numpy 2d. Tôi sẽ preallocated một mảng numpy 2d nếu tôi biết số lượng các mặt hàng trước thời hạn, nhưng tôi không, do đó tôi đặt tất cả mọi thứ trong một danh sách.Cách Pythonic để tạo một mảng numpy từ danh sách các mảng có nhiều mảng

Các giả lên là dưới đây:

>>> list_of_arrays = map(lambda x: x*ones(2), range(5)) 
>>> list_of_arrays 
[array([ 0., 0.]), array([ 1., 1.]), array([ 2., 2.]), array([ 3., 3.]), array([ 4., 4.])] 
>>> arr = array(list_of_arrays) 
>>> arr 
array([[ 0., 0.], 
     [ 1., 1.], 
     [ 2., 2.], 
     [ 3., 3.], 
     [ 4., 4.]]) 

Câu hỏi của tôi là như sau:

Có cách nào tốt hơn (performancewise) để đi về nhiệm vụ thu thập dữ liệu bằng số tuần tự (trong tôi trường hợp mảng numpy) hơn đặt chúng trong một danh sách và sau đó làm một numpy.array ra khỏi nó (Tôi đang tạo một obj mới và sao chép dữ liệu)? Có cấu trúc dữ liệu ma trận "có thể mở rộng" có sẵn trong một mô-đun được thử nghiệm tốt không?

Một kích thước đặc trưng của ma trận 2ngày của tôi sẽ là giữa 100x10 và 5000x10 nổi

EDIT: Trong ví dụ này tôi đang sử dụng bản đồ, nhưng trong ứng dụng thực tế của tôi Tôi có một vòng lặp for

Trả lời

14

Giả sử bạn biết rằng mảng cuối cùng arr sẽ không bao giờ lớn hơn 5000x10. Sau đó, bạn có thể phân bổ trước một mảng có kích thước tối đa, điền dữ liệu đó là bạn đi qua vòng lặp và sau đó sử dụng arr.resize để cắt kích thước đó xuống kích thước được phát hiện sau khi thoát khỏi vòng lặp.

Các thử nghiệm dưới đây cho thấy làm như vậy sẽ nhanh hơn một chút so với việc xây dựng các danh sách python trung gian bất kể kích thước cuối cùng của mảng là gì.

Ngoài ra, arr.resize không cấp phát bộ nhớ không sử dụng, do đó dấu chân bộ nhớ cuối cùng (mặc dù có thể không phải là trung gian) nhỏ hơn số được sử dụng bởi python_lists_to_array.

này cho thấy numpy_all_the_way là nhanh hơn:

% python -mtimeit -s"import test" "test.numpy_all_the_way(100)" 
100 loops, best of 3: 1.78 msec per loop 
% python -mtimeit -s"import test" "test.numpy_all_the_way(1000)" 
100 loops, best of 3: 18.1 msec per loop 
% python -mtimeit -s"import test" "test.numpy_all_the_way(5000)" 
10 loops, best of 3: 90.4 msec per loop 

% python -mtimeit -s"import test" "test.python_lists_to_array(100)" 
1000 loops, best of 3: 1.97 msec per loop 
% python -mtimeit -s"import test" "test.python_lists_to_array(1000)" 
10 loops, best of 3: 20.3 msec per loop 
% python -mtimeit -s"import test" "test.python_lists_to_array(5000)" 
10 loops, best of 3: 101 msec per loop 

Điều này cho thấy numpy_all_the_way sử dụng ít bộ nhớ:

% test.py 
Initial memory usage: 19788 
After python_lists_to_array: 20976 
After numpy_all_the_way: 20348 

test.py:

#!/usr/bin/env python 
import numpy as np 
import os 

def memory_usage(): 
    pid=os.getpid() 
    return next(line for line in open('/proc/%s/status'%pid).read().splitlines() 
      if line.startswith('VmSize')).split()[-2] 

N,M=5000,10 

def python_lists_to_array(k): 
    list_of_arrays = map(lambda x: x*np.ones(M), range(k)) 
    arr = np.array(list_of_arrays) 
    return arr 

def numpy_all_the_way(k): 
    arr=np.empty((N,M)) 
    for x in range(k): 
     arr[x]=x*np.ones(M) 
    arr.resize((k,M)) 
    return arr 

if __name__=='__main__': 
    print('Initial memory usage: %s'%memory_usage()) 
    arr=python_lists_to_array(5000) 
    print('After python_lists_to_array: %s'%memory_usage())  
    arr=numpy_all_the_way(5000) 
    print('After numpy_all_the_way: %s'%memory_usage())  
2

gì bạn đang làm là cách tiêu chuẩn. Một tài sản của mảng numpy là họ cần bộ nhớ tiếp giáp. Khả năng duy nhất của "lỗ hổng" mà tôi có thể nghĩ là có thể với thành viên strides của PyArrayObject, nhưng điều đó không ảnh hưởng đến cuộc thảo luận ở đây. Kể từ khi mảng numpy có bộ nhớ tiếp giáp và được "preallocated", thêm một hàng/cột mới có nghĩa là phân bổ bộ nhớ mới, sao chép dữ liệu, và sau đó giải phóng bộ nhớ cũ. Nếu bạn làm điều đó rất nhiều, nó không phải là rất hiệu quả.

Một trường hợp mà ai đó có thể không muốn tạo danh sách và sau đó chuyển đổi nó thành mảng có nhiều dấu chấm cuối cùng là khi danh sách chứa nhiều số: một dãy số có nhiều không gian hơn nhiều so với danh sách Python gốc của các số (kể từ khi danh sách Python gốc lưu trữ các đối tượng Python). Đối với kích thước mảng điển hình của bạn, tôi không nghĩ rằng đó là một vấn đề.

Khi bạn tạo mảng cuối cùng từ danh sách mảng, bạn sao chép tất cả dữ liệu sang vị trí mới cho mảng mới (2-d trong ví dụ của bạn). Điều này vẫn còn hiệu quả hơn nhiều so với có một mảng numpy và làm next = numpy.vstack((next, new_row)) mỗi khi bạn nhận được dữ liệu mới. vstack() sẽ sao chép tất cả dữ liệu cho mọi "hàng".

Đã có một số thread on numpy-discussion mailing list một số thời gian trước đây đã thảo luận về khả năng thêm loại mảng gọn gàng mới cho phép mở rộng/thêm hiệu quả. Có vẻ như đã có sự quan tâm đáng kể trong thời gian này, mặc dù tôi không biết có điều gì đó xảy ra hay không. Bạn có thể muốn xem chuỗi đó.

Tôi sẽ nói rằng những gì bạn đang làm là rất Pythonic, và hiệu quả, vì vậy trừ khi bạn thực sự cần một cái gì đó khác (hiệu quả hơn không gian, có thể?), Bạn sẽ không sao. Đó là cách tôi tạo các mảng cứng nhắc của mình khi tôi không biết số phần tử trong mảng trong phần đầu.

+0

@Alok --- cảm ơn cho câu trả lời chu đáo . Thời gian trong câu trả lời từ ~ unubuntu thể hiện sự lo lắng về hiệu quả 5%. Điều này gần như chắc chắn là một sai lầm cho đến khi bạn nhận được đến mức bạn hoàn toàn phải có 5% đó. – telliott99

2

Tôi sẽ thêm phiên bản riêng của tôi ~ unutbu của câu trả lời. Tương tự như numpy_all_the cách, nhưng bạn tự động thay đổi kích thước nếu bạn có lỗi chỉ mục. Tôi nghĩ rằng nó sẽ nhanh hơn một chút đối với các tập dữ liệu nhỏ, nhưng nó chậm hơn một chút - việc kiểm tra giới hạn làm chậm quá nhiều thứ.

initial_guess = 1000 

def my_numpy_all_the_way(k): 
    arr=np.empty((initial_guess,M)) 
    for x,row in enumerate(make_test_data(k)): 
     try: 
      arr[x]=row 
     except IndexError: 
      arr.resize((arr.shape[0]*2, arr.shape[1])) 
      arr[x]=row 
    arr.resize((k,M)) 
    return arr 
11

Cách thuận tiện, sử dụng numpy.concatenate. Tôi tin rằng nó cũng nhanh hơn, so với câu trả lời @ unutbu của:

In [32]: import numpy as np 

In [33]: list_of_arrays = list(map(lambda x: x * np.ones(2), range(5))) 

In [34]: list_of_arrays 
Out[34]: 
[array([ 0., 0.]), 
array([ 1., 1.]), 
array([ 2., 2.]), 
array([ 3., 3.]), 
array([ 4., 4.])] 

In [37]: shape = list(list_of_arrays[0].shape) 

In [38]: shape 
Out[38]: [2] 

In [39]: shape[:0] = [len(list_of_arrays)] 

In [40]: shape 
Out[40]: [5, 2] 

In [41]: arr = np.concatenate(list_of_arrays).reshape(shape) 

In [42]: arr 
Out[42]: 
array([[ 0., 0.], 
     [ 1., 1.], 
     [ 2., 2.], 
     [ 3., 3.], 
     [ 4., 4.]]) 
0

Thậm chí đơn giản hơn @Gill Bates' câu trả lời, đây là một mã một dòng:

np.stack(list_of_arrays, axis=0) 
Các vấn đề liên quan