Об’єднати масиви Numpy без копіювання


76

У Numpy я можу об'єднати два масиви наскрізними за допомогою np.appendабо np.concatenate:

>>> X = np.array([[1,2,3]])
>>> Y = np.array([[-1,-2,-3],[4,5,6]])
>>> Z = np.append(X, Y, axis=0)
>>> Z
array([[ 1,  2,  3],
       [-1, -2, -3],
       [ 4,  5,  6]])

Але вони роблять копії своїх вхідних масивів:

>>> Z[0,:] = 0
>>> Z
array([[ 0,  0,  0],
       [-1, -2, -3],
       [ 4,  5,  6]])
>>> X
array([[1, 2, 3]])

Чи є спосіб об’єднати два масиви у подання , тобто без копіювання? Для цього потрібен np.ndarrayпідклас?


Чому ви хочете мати вигляд, а не копію?
Вінстон Еверт,

@WinstonEwert: У мене довгий список масивів, на яких я хочу виконати єдину глобальну нормалізацію.
Fred Foo

розуміння списку теж буде швидким.
кіборг

Це не відповідає на питання, що поганого в копіюванні всіх цих масивів? В основному, вас турбує вартість копіювання чи ви хочете змінити оригінальні масиви?
Вінстон Еверт,

1
@WinstonEwert: проблема копіювання - це проблема; інакше я міг би просто concatenateїх замінити та замінити оригінальні масиви поданнями на об'єднання. Здається, це те, що мені доведеться робити, однак.
Fred Foo

Відповіді:


78

Пам'ять, що належить масиву Numpy, повинна бути суміжною. Якщо ви виділили масиви окремо, вони випадковим чином розкидаються в пам'яті, і немає можливості представити їх як масив представлення Numpy.

Якщо ви заздалегідь знаєте, скільки вам потрібно масивів, ви можете замість цього розпочати з одного великого масиву, який ви виділите заздалегідь, і нехай кожен з малих масивів має вигляд великого масиву (наприклад, отриманий нарізанням).


12
Безслідне зауваження: пам'ять представлення не повинна бути суміжною, але, ймовірно, вона повинна бути упорядкована фіксованими кроками (що також не стосується списку масивів).
кіборг

Ви хочете сказати, що навіть підклас не буде працювати? Я знаю, що люди використовують ndarrayпідкласи для роботи з mmapмасивами, але, мабуть, відображення пам'яті також суміжні ...
Фред Фо

3
Так, підкласи також повинні відповідати моделі пам'яті Numpy. (Зауваження @ cyborgs вище також є правильним: підмасиви також можна замовити в пам'яті з фіксованими кроками, але також це можна отримати, лише домовившись про все заздалегідь.) Уважне читання цієї сторінки може пролити трохи світла.
pv.

1
Чи є причина, по якій ви пропонуєте нарізання, а не, наприклад numpy.empty?
lucidbrot

13

Просто ініціалізуйте масив, перш ніж заповнити його даними. Якщо ви хочете, ви можете виділити більше місця, ніж потрібно, і це не займе більше оперативної пам'яті через те, як працює Numpy.

A = np.zeros(R,C)
A[row] = [data]

Пам'ять використовується лише після введення даних у масив. Створення нового масиву з об'єднання двох ніколи не закінчиться на наборі даних будь-якого розміру, тобто наборі даних> 1 Гб або близько того.


2

Зовсім не елегантно, але ви можете наблизитись до того, що хочете, використовуючи кортеж для зберігання покажчиків на масиви. Зараз я не уявляю, як би я це використав у випадку, але я вже робив подібні речі раніше.

>>> X = np.array([[1,2,3]])
>>> Y = np.array([[-1,-2,-3],[4,5,6]])
>>> z = (X, Y)
>>> z[0][:] = 0
>>> z
(array([[0, 0, 0]]), array([[-1, -2, -3],
       [ 4,  5,  6]]))
>>> X
array([[0, 0, 0]])

Так, але це не дасть мені такої магії індексування NumPy, яку я хотів би мати. Все одно дякую.
Fred Foo

1

У мене була та ж проблема, і в кінцевому підсумку я зробив це навпаки, після нормального конкатенації (з копією) я перепризначив оригінальні масиви, щоб стати переглядами конкатенованого:

import numpy as np

def concat_no_copy(arrays):
    """ Concats the arrays and returns the concatenated array 
    in addition to the original arrays as views of the concatenated one.

    Parameters:
    -----------
    arrays: list
        the list of arrays to concatenate
    """
    con = np.concatenate(arrays)

    viewarrays = []
    for i, arr in enumerate(arrays):
        arrnew = con[sum(len(a) for a in arrays[:i]):
                     sum(len(a) for a in arrays[:i + 1])]
        viewarrays.append(arrnew)
        assert all(arr == arrnew)

    # return the view arrays, replace the old ones with these
    return con, viewarrays

Ви можете протестувати його наступним чином:

def test_concat_no_copy():
    arr1 = np.array([0, 1, 2, 3, 4])
    arr2 = np.array([5, 6, 7, 8, 9])
    arr3 = np.array([10, 11, 12, 13, 14])

    arraylist = [arr1, arr2, arr3]

    con, newarraylist = concat_no_copy(arraylist)

    assert all(con == np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 
                                11, 12, 13, 14]))

    for old, new in zip(arraylist, newarraylist):
        assert all(old == new)

0

Ви можете створити масив масивів, наприклад:

>>> from numpy import *
>>> a = array([1.0, 2.0, 3.0])
>>> b = array([4.0, 5.0])
>>> c = array([a, b])
>>> c
array([[ 1.  2.  3.], [ 4.  5.]], dtype=object)
>>> a[0] = 100.0
>>> a
array([ 100.,    2.,    3.])
>>> c
array([[ 100.    2.    3.], [ 4.  5.]], dtype=object)
>>> c[0][1] = 200.0
>>> a
array([ 100.,  200.,    3.])
>>> c
array([[ 100.  200.    3.], [ 4.  5.]], dtype=object)
>>> c *= 1000
>>> c
array([[ 100000.  200000.    3000.], [ 4000.  5000.]], dtype=object)
>>> a
array([ 100.,  200.,    3.])
>>> # Oops! Copies were made...

Проблема полягає в тому, що він створює копії під час трансляції (звучить як помилка).


0

Відповідь заснована на іншій моїй відповіді у Посиланні на рядки ndarray у ndarray

X = np.array([[1,2,3]])
Y = np.array([[-1,-2,-3],[4,5,6]])
Z = np.array([None, None, None])
Z[0] = X[0]
Z[1] = Y[0]
Z[2] = Y[1]

Z[0][0] = 5 # X would be changed as well

print(X)
Output: 
array([[5, 2, 3]])

# Let's make it a function!
def concat(X, Y, copy=True):
    """Return an array of references if copy=False""" 
    if copy is True:  # deep copy
        return np.append(X, Y, axis=0)
    len_x, len_y = len(X), len(Y)
    ret = np.array([None for _ in range(len_x + len_y)])
    for i in range(len_x):
        ret[i] = X[i]
    for j in range(len_y):
        ret[len_x + j] = Y[j] 
    return ret
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.