Python, wie man ein numpy-Array mit Nullen auffüllt

99

Ich möchte wissen, wie ich mit Python 2.6.6 mit Numpy Version 1.5.0 ein 2D-Numpy-Array mit Nullen auffüllen kann. Es tut uns leid! Aber das sind meine Grenzen. Daher kann ich nicht verwenden np.pad. Zum Beispiel möchte ich amit Nullen auffüllen, damit die Form übereinstimmt b. Der Grund, warum ich das tun möchte, ist, dass ich Folgendes tun kann:

b-a

so dass

>>> a
array([[ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.]])
>>> b
array([[ 3.,  3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.,  3.]])
>>> c
array([[1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 0],
       [0, 0, 0, 0, 0, 0]])

Der einzige Weg, den ich mir vorstellen kann, ist das Anhängen, aber das scheint ziemlich hässlich. Gibt es möglicherweise eine sauberere Lösung b.shape?

Bearbeiten, danke an MSeiferts Antwort. Ich musste es ein bisschen aufräumen, und das habe ich bekommen:

def pad(array, reference_shape, offsets):
    """
    array: Array to be padded
    reference_shape: tuple of size of ndarray to create
    offsets: list of offsets (number of elements must be equal to the dimension of the array)
    will throw a ValueError if offsets is too big and the reference_shape cannot handle the offsets
    """

    # Create an array of zeros with the reference shape
    result = np.zeros(reference_shape)
    # Create a list of slices from offset to offset + shape in each dimension
    insertHere = [slice(offsets[dim], offsets[dim] + array.shape[dim]) for dim in range(array.ndim)]
    # Insert the array in the result at the specified offsets
    result[insertHere] = array
    return result
user2015487
quelle

Antworten:

160

Ganz einfach, Sie erstellen ein Array mit Nullen unter Verwendung der Referenzform:

result = np.zeros(b.shape)
# actually you can also use result = np.zeros_like(b) 
# but that also copies the dtype not only the shape

und fügen Sie dann das Array dort ein, wo Sie es benötigen:

result[:a.shape[0],:a.shape[1]] = a

und voila du hast es gepolstert:

print(result)
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

Sie können es auch etwas allgemeiner gestalten, wenn Sie definieren, wo Ihr oberes linkes Element eingefügt werden soll

result = np.zeros_like(b)
x_offset = 1  # 0 would be what you wanted
y_offset = 1  # 0 in your case
result[x_offset:a.shape[0]+x_offset,y_offset:a.shape[1]+y_offset] = a
result

array([[ 0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.],
       [ 0.,  1.,  1.,  1.,  1.,  1.],
       [ 0.,  1.,  1.,  1.,  1.,  1.]])

Achten Sie dann jedoch darauf, dass Sie keine größeren Offsets als zulässig haben. Zum x_offset = 2Beispiel wird dies fehlschlagen.


Wenn Sie eine beliebige Anzahl von Dimensionen haben, können Sie eine Liste von Slices definieren, um das ursprüngliche Array einzufügen. Ich fand es interessant, ein bisschen herumzuspielen, und habe eine Auffüllfunktion erstellt, die ein arbitär geformtes Array (mit Versatz) auffüllen kann, solange das Array und die Referenz die gleiche Anzahl von Dimensionen haben und die Offsets nicht zu groß sind.

def pad(array, reference, offsets):
    """
    array: Array to be padded
    reference: Reference array with the desired shape
    offsets: list of offsets (number of elements must be equal to the dimension of the array)
    """
    # Create an array of zeros with the reference shape
    result = np.zeros(reference.shape)
    # Create a list of slices from offset to offset + shape in each dimension
    insertHere = [slice(offset[dim], offset[dim] + array.shape[dim]) for dim in range(a.ndim)]
    # Insert the array in the result at the specified offsets
    result[insertHere] = a
    return result

Und einige Testfälle:

import numpy as np

# 1 Dimension
a = np.ones(2)
b = np.ones(5)
offset = [3]
pad(a, b, offset)

# 3 Dimensions

a = np.ones((3,3,3))
b = np.ones((5,4,3))
offset = [1,0,0]
pad(a, b, offset)
MSeifert
quelle
padded = np.zeros(b.shape) padded[tuple(slice(0,n) for n in a.shape)] = a
Um den
167

NumPy 1.7.0 (als numpy.pades hinzugefügt wurde) ist jetzt ziemlich alt (es wurde 2013 veröffentlicht). Obwohl die Frage nach einem Weg ohne diese Funktion gestellt wurde, hielt ich es für nützlich zu wissen, wie dies mit erreicht werden kann numpy.pad.

Es ist eigentlich ziemlich einfach:

>>> import numpy as np
>>> a = np.array([[ 1.,  1.,  1.,  1.,  1.],
...               [ 1.,  1.,  1.,  1.,  1.],
...               [ 1.,  1.,  1.,  1.,  1.]])
>>> np.pad(a, [(0, 1), (0, 1)], mode='constant')
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

In diesem Fall habe ich verwendet, dass dies 0der Standardwert für ist mode='constant'. Es kann aber auch durch explizite Übergabe angegeben werden:

>>> np.pad(a, [(0, 1), (0, 1)], mode='constant', constant_values=0)
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

Nur für den Fall, dass das zweite Argument ( [(0, 1), (0, 1)]) verwirrend erscheint: Jedes Listenelement (in diesem Fall Tupel) entspricht einer Dimension, und das darin enthaltene Element repräsentiert die Auffüllung vor (erstes Element) und nach (zweites Element). Damit:

[(0, 1), (0, 1)]
         ^^^^^^------ padding for second dimension
 ^^^^^^-------------- padding for first dimension

  ^------------------ no padding at the beginning of the first axis
     ^--------------- pad with one "value" at the end of the first axis.

In diesem Fall ist die Polsterung für die erste und zweite Achse identisch, so dass man auch einfach das 2-Tupel übergeben kann:

>>> np.pad(a, (0, 1), mode='constant')
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

Falls die Auffüllung vorher und nachher identisch ist, kann man sogar das Tupel weglassen (in diesem Fall jedoch nicht zutreffend):

>>> np.pad(a, 1, mode='constant')
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.]])

Oder wenn die Auffüllung vorher und nachher identisch, aber für die Achse unterschiedlich ist, können Sie auch das zweite Argument in den inneren Tupeln weglassen:

>>> np.pad(a, [(1, ), (2, )], mode='constant')
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]])

Ich bevorzuge es jedoch, immer die explizite zu verwenden, da es einfach zu einfach ist, Fehler zu machen (wenn die Erwartungen von NumPys von Ihren Absichten abweichen):

>>> np.pad(a, [1, 2], mode='constant')
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]])

Hier glaubt NumPy, Sie wollten alle Achsen mit 1 Element vor und 2 Elementen nach jeder Achse auffüllen! Auch wenn Sie beabsichtigten, mit 1 Element in Achse 1 und 2 Elementen für Achse 2 aufzufüllen.

Ich habe Listen mit Tupeln für das Auffüllen verwendet. Beachten Sie, dass dies nur "meine Konvention" ist. Sie können auch Listen mit Listen oder Tupeln mit Tupeln oder sogar Tupel mit Arrays verwenden. NumPy überprüft nur die Länge des Arguments (oder wenn es keine Länge hat) und die Länge jedes Elements (oder wenn es eine Länge hat)!

MSeifert
quelle
5
Das ist wirklich gut erklärt. Weitaus besser als die Originaldokumentation. Vielen Dank.
M.Innat
mode='constant'ist die sinnvolle Standardeinstellung, sodass das Auffüllen mit Nullen ohne ein optionales Schlüsselwort erreicht werden kann, was zu etwas besser lesbarem Code führt.
Divenex
Wie kann ich nur die dritte Dimension eines 3D-Numpy-Arrays auffüllen?
Ramsha Siddiqui
@RamshaSiddiqui Sie können 0s für die Dimensionen verwenden, die nicht aufgefüllt werden sollen.
MSeifert
9

Ich verstehe, dass Ihr Hauptproblem darin besteht, dass Sie berechnen müssen, d=b-aaber Ihre Arrays unterschiedliche Größen haben. Es ist kein gepolstertes Zwischenprodukt erforderlichc

Sie können dies ohne Polsterung lösen:

import numpy as np

a = np.array([[ 1.,  1.,  1.,  1.,  1.],
              [ 1.,  1.,  1.,  1.,  1.],
              [ 1.,  1.,  1.,  1.,  1.]])

b = np.array([[ 3.,  3.,  3.,  3.,  3.,  3.],
              [ 3.,  3.,  3.,  3.,  3.,  3.],
              [ 3.,  3.,  3.,  3.,  3.,  3.],
              [ 3.,  3.,  3.,  3.,  3.,  3.]])

d = b.copy()
d[:a.shape[0],:a.shape[1]] -=  a

print d

Ausgabe:

[[ 2.  2.  2.  2.  2.  3.]
 [ 2.  2.  2.  2.  2.  3.]
 [ 2.  2.  2.  2.  2.  3.]
 [ 3.  3.  3.  3.  3.  3.]]
Juan Leni
quelle
Für seinen speziellen Fall muss er zwar nicht unbedingt auffüllen, aber dies ist eine der wenigen arithmetischen Operationen, bei denen das Auffüllen und Ihr Ansatz gleichwertig sind. Trotzdem schöne Antwort!
MSeifert
1
Nicht nur das. Dies könnte auch speichereffizienter sein als das Auffüllen mit Nullen.
Norok2
0

Falls Sie einem Array einen Zaun von 1s hinzufügen müssen:

>>> mat = np.zeros((4,4), np.int32)
>>> mat
array([[0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0]])
>>> mat[0,:] = mat[:,0] = mat[:,-1] =  mat[-1,:] = 1
>>> mat
array([[1, 1, 1, 1],
       [1, 0, 0, 1],
       [1, 0, 0, 1],
       [1, 1, 1, 1]])
MSeifert
quelle
0

Ich weiß, dass ich etwas spät dran bin, aber falls Sie eine relative Auffüllung (auch als Kantenauffüllung bezeichnet) durchführen möchten, können Sie dies wie folgt implementieren. Beachten Sie, dass die allererste Instanz der Zuweisung zu einem Null-Auffüllen führt. Sie können dies also sowohl für das Null-Auffüllen als auch für das relative Auffüllen verwenden (hier kopieren Sie die Kantenwerte des ursprünglichen Arrays in das aufgefüllte Array).

def replicate_padding(arr):
    """Perform replicate padding on a numpy array."""
    new_pad_shape = tuple(np.array(arr.shape) + 2) # 2 indicates the width + height to change, a (512, 512) image --> (514, 514) padded image.
    padded_array = np.zeros(new_pad_shape) #create an array of zeros with new dimensions
    
    # perform replication
    padded_array[1:-1,1:-1] = arr        # result will be zero-pad
    padded_array[0,1:-1] = arr[0]        # perform edge pad for top row
    padded_array[-1, 1:-1] = arr[-1]     # edge pad for bottom row
    padded_array.T[0, 1:-1] = arr.T[0]   # edge pad for first column
    padded_array.T[-1, 1:-1] = arr.T[-1] # edge pad for last column
    
    #at this point, all values except for the 4 corners should have been replicated
    padded_array[0][0] = arr[0][0]     # top left corner
    padded_array[-1][0] = arr[-1][0]   # bottom left corner
    padded_array[0][-1] = arr[0][-1]   # top right corner 
    padded_array[-1][-1] = arr[-1][-1] # bottom right corner

    return padded_array

Komplexitätsanalyse:

Die optimale Lösung hierfür ist die Numpy-Pad-Methode. Nach der Mittelung für 5 Läufe ist np.pad mit relativer Auffüllung nur 8%besser als die oben definierte Funktion. Dies zeigt, dass dies eine ziemlich optimale Methode für relative und Null-Polsterung ist.


#My method, replicate_padding
start = time.time()
padded = replicate_padding(input_image)
end = time.time()
delta0 = end - start

#np.pad with edge padding
start = time.time()
padded = np.pad(input_image, 1, mode='edge')
end = time.time()
delta = end - start


print(delta0) # np Output: 0.0008790493011474609 
print(delta)  # My Output: 0.0008130073547363281
print(100*((delta0-delta)/delta)) # Percent difference: 8.12316715542522%
Qasim Wani
quelle