Python cómo rellenar la matriz numpy con ceros

96

Quiero saber cómo puedo rellenar una matriz numpy 2D con ceros usando python 2.6.6 con la versión numpy 1.5.0. ¡Lo siento! Pero estas son mis limitaciones. Por lo tanto, no puedo usar np.pad. Por ejemplo, quiero rellenar acon ceros para que su forma coincida b. La razón por la que quiero hacer esto es para poder hacer:

b-a

tal que

>>> 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]])

La única forma en que puedo pensar en hacer esto es agregando, sin embargo, esto parece bastante feo. ¿Existe una solución más limpia que posiblemente esté usando b.shape?

Editar, gracias a la respuesta de MSeiferts. Tuve que limpiarlo un poco, y esto es lo que obtuve:

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
fuente

Respuestas:

155

Muy simple, crea una matriz que contiene ceros usando la forma de referencia:

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

y luego inserte la matriz donde la necesite:

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

y listo lo has acolchado:

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.]])

También puede hacerlo un poco más general si define dónde debe insertarse el elemento superior izquierdo

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.]])

pero luego tenga cuidado de no tener compensaciones mayores de lo permitido. Por x_offset = 2ejemplo, esto fallará.


Si tiene un número arbitrario de dimensiones, puede definir una lista de cortes para insertar la matriz original. Me pareció interesante jugar un poco y creé una función de relleno que puede rellenar (con compensación) una matriz de forma arbitraria siempre que la matriz y la referencia tengan el mismo número de dimensiones y las compensaciones no sean demasiado grandes.

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

Y algunos casos de prueba:

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
fuente
Solo para resumir el caso que necesitaba: si se inserta en el origen, dimensiones arbitrarias:padded = np.zeros(b.shape) padded[tuple(slice(0,n) for n in a.shape)] = a
shaneb
162

NumPy 1.7.0 (cuando numpy.padse agregó) es bastante antiguo ahora (se lanzó en 2013), así que aunque la pregunta pedía una forma sin usar esa función, pensé que podría ser útil saber cómo se podría lograr usando numpy.pad.

En realidad, es bastante simple:

>>> 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.]])

En este caso utilicé que 0es el valor predeterminado para mode='constant'. Pero también podría especificarse pasándolo explícitamente:

>>> 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.]])

En caso de que el segundo argumento ( [(0, 1), (0, 1)]) parezca confuso: cada elemento de la lista (en este caso tupla) corresponde a una dimensión y el elemento en el mismo representa el relleno antes (primer elemento) y después (segundo elemento). Entonces:

[(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.

En este caso, el relleno para el primer y segundo eje es idéntico, por lo que también se podría pasar en la 2-tupla:

>>> 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.]])

En caso de que el relleno antes y después sea idéntico, se podría incluso omitir la tupla (aunque no es aplicable en este caso):

>>> 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.]])

O si el relleno antes y después es idéntico pero diferente para el eje, también puede omitir el segundo argumento en las tuplas internas:

>>> 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.]])

Sin embargo, tiendo a preferir siempre usar el explícito, porque es demasiado fácil cometer errores (cuando las expectativas de NumPys difieren de sus intenciones):

>>> 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.]])

¡Aquí NumPy cree que querías rellenar todos los ejes con 1 elemento antes y 2 elementos después de cada eje! Incluso si tenía la intención de rellenar con 1 elemento en el eje 1 y 2 elementos para el eje 2.

Usé listas de tuplas para el relleno, tenga en cuenta que esto es solo "mi convención", también podría usar listas de listas o tuplas de tuplas, o incluso tuplas de matrices. NumPy solo verifica la longitud del argumento (o si no tiene una longitud) y la longitud de cada elemento (o si tiene una longitud).

MSeifert
fuente
4
Eso está muy bien explicado. Mucho mejor que la documentación original. Gracias.
M.Innat
mode='constant'es el valor predeterminado sensato, por lo que el relleno con ceros se puede lograr sin la necesidad de ninguna palabra clave opcional, lo que lleva a un código un poco más legible.
divenex
¿Cómo puedo agregar relleno solo a la tercera dimensión de una matriz numpy 3D?
Ramsha Siddiqui
@RamshaSiddiqui puede usar 0 para las dimensiones que no deben rellenarse.
MSeifert
9

Entiendo que su principal problema es que necesita calcular, d=b-apero sus matrices tienen diferentes tamaños. No es necesario un acolchado intermedioc

Puede resolver esto sin relleno:

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

Salida:

[[ 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
fuente
Es cierto, para su caso específico, no necesariamente necesita rellenar, pero esa es una de las pocas operaciones aritméticas en las que el relleno y su enfoque son equivalentes. Sin embargo, ¡buena respuesta!
MSeifert
1
No solo eso. Esto también podría ser más eficiente en memoria que el relleno con ceros.
norok2
0

En caso de que necesite agregar una cerca de 1 a una matriz:

>>> 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
fuente
0

Sé que llego un poco tarde a esto, pero en caso de que desee realizar un relleno relativo (también conocido como relleno de borde), así es como puede implementarlo. Tenga en cuenta que la primera instancia de asignación da como resultado un relleno de ceros, por lo que puede usar esto tanto para el relleno de ceros como para el relleno relativo (aquí es donde copia los valores de los bordes de la matriz original en la matriz de relleno).

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

Análisis de complejidad:

La solución óptima para esto es el método de almohadilla de numpy. Después de promediar 5 ejecuciones, np.pad con relleno relativo solo es 8%mejor que la función definida anteriormente. Esto muestra que este es un método bastante óptimo para el relleno relativo y de relleno de ceros.


#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
fuente