Это можно сделать, предварительно выделив полный результирующий массив и заполнив строки и столбцы старым массивом, даже в нескольких измерениях, и размеры не должны совпадать с размером:
def insert_at(arr, output_size, indices):
"""
Insert zeros at specific indices over whole dimensions, e.g. rows and/or columns and/or channels.
You need to specify indices for each dimension, or leave a dimension untouched by specifying
`...` for it. The following assertion should hold:
`assert len(output_size) == len(indices) == len(arr.shape)`
:param arr: The array to insert zeros into
:param output_size: The size of the array after insertion is completed
:param indices: The indices where zeros should be inserted, per dimension. For each dimension, you can
specify: - an int
- a tuple of ints
- a generator yielding ints (such as `range`)
- Ellipsis (=...)
:return: An array of shape `output_size` with the content of arr and zeros inserted at the given indices.
"""
# assert len(output_size) == len(indices) == len(arr.shape)
result = np.zeros(output_size)
existing_indices = [np.setdiff1d(np.arange(axis_size), axis_indices,assume_unique=True)
for axis_size, axis_indices in zip(output_size, indices)]
result[np.ix_(*existing_indices)] = arr
return result
Для вашего случая использования вы можете использовать его так:
def fill_by_label(arr, labels):
# If this is your only use-case, you can make it more efficient
# By not computing the missing indices first, just to compute
# The existing indices again
missing_idxs = np.setdiff1d(np.arange(len(labels)), x)
return insert_at(arr, output_size=(len(labels), len(labels)),
indices=(missing_idxs, missing_idxs))
x = np.array([[3, 0, 3],
[0, 2, 0],
[2, 3, 3]])
labels = ["a", "b", "c", "d", "e"]
missing_idxs = np.setdiff1d(np.arange(len(labels)), x)
print(fill_by_label(x, labels))
>> [[3. 0. 0. 3. 0.]
[0. 0. 0. 0. 0.]
[0. 0. 2. 0. 0.]
[2. 0. 3. 3. 0.]
[0. 0. 0. 0. 0.]]
Но это очень гибко.Вы можете использовать его для заполнения нулями:
def zero_pad(arr):
out_size = np.array(arr.shape) + 2
indices = (0, out_size[0] - 1), (0, out_size[1] - 1)
return insert_at(arr, output_size=out_size,
indices=indices)
print(zero_pad(x))
>> [[0. 0. 0. 0. 0.]
[0. 3. 0. 3. 0.]
[0. 0. 2. 0. 0.]
[0. 2. 3. 3. 0.]
[0. 0. 0. 0. 0.]]
Он также работает с неквадратичными входами и выходами:
x = np.ones((3, 4))
print(insert_at(x, (4, 5), (2, 3)))
>>[[1. 1. 1. 0. 1.]
[1. 1. 1. 0. 1.]
[0. 0. 0. 0. 0.]
[1. 1. 1. 0. 1.]]
С различным количеством вставок на измерение:
x = np.ones((3, 4))
print(insert_at(x, (4, 6), (1, (2, 4))))
>> [[1. 1. 0. 1. 0. 1.]
[0. 0. 0. 0. 0. 0.]
[1. 1. 0. 1. 0. 1.]
[1. 1. 0. 1. 0. 1.]]
Вы можете использовать range
(или другие генераторы) вместо перечисления каждого индекса:
x = np.ones((3, 4))
print(insert_at(x, (4, 6), (1, range(2, 4))))
>>[[1. 1. 0. 0. 1. 1.]
[0. 0. 0. 0. 0. 0.]
[1. 1. 0. 0. 1. 1.]
[1. 1. 0. 0. 1. 1.]]
Работает с произвольными измерениями (если вы указали индексы для каждого измерения) 1:
x = np.ones((2, 2, 2))
print(insert_at(x, (3, 3, 3), (0, 0, 0)))
>>>[[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
[[0. 0. 0.]
[0. 1. 1.]
[0. 1. 1.]]
[[0. 0. 0.]
[0. 1. 1.]
[0. 1. 1.]]]
Вы можете использовать Ellipsis
(= ...
), чтобы указать, что вы не хотите изменять размер 1,2 :
x = np.ones((2, 2))
print(insert_at(x, (2, 4), (..., (0, 1))))
>>[[0. 0. 1. 1.]
[0. 0. 1. 1.]]
1 : Вы можете автоматически определить это на основе arr.shape
и output_size
и при необходимости заполнить его ...
, но я оставлю это на ваше усмотрение, если вынужно это.Если бы вы захотели, вы, возможно, могли бы вместо этого избавиться от параметра output_size
, но тогда он усложняется при передаче в генераторы.
2 : это несколько отличается от обычного numpy...
семантика, так как вам нужно указать ...
для каждого измерения, которое вы хотите сохранить, т. Е. Следующее NOT работает:
x = np.ones((2, 2, 2))
print(insert_at(x, (2, 2, 3), (..., 0)))
Для синхронизации,Я выполнил вставку 10 строк и столбцов в массив 90x90 100000 раз, это результат:
x = np.random.random(size=(90, 90))
indices = np.arange(10) * 10
def measure_time_fast():
insert_at(x, (100, 100), (indices, indices))
def measure_time_slow():
insert_rows_columns_at_slow(x, indices)
if __name__ == '__main__':
import timeit
for speed in ("fast", "slow"):
times = timeit.repeat(f"measure_time_{speed}()", setup=f"from __main__ import measure_time_{speed}", repeat=10, number=10000)
print(f"Min: {np.min(times) / 10000}, Max: {np.max(times) / 10000}, Mean: {np.mean(times) / 10000} seconds per call")
Для быстрой версии:
Мин: 7.336409069976071e-05, Макс .: 7.7440657400075e-05, Среднее: 7.520040466995852e-05 секунд на вызов
Это примерно 75 микросекунд.
Для вашей медленной версии:
Мин: 0,00028272533010022016, Макс: 0,0002923079213000165, Среднее: 0,00028581595062998535 секунд на вызов
Это около 300 микросекунд.Разница будет тем больше, чем больше будут массивы.Например, для вставки 100 строк и столбцов в массив 900x900 это результаты (выполняются только 1000 раз):
Быстрая версия:
Мин .: 0.00022916630539984907, Макс .: 0.0022916630539984908, Среднее: 0,0022916630539984908 секунд на звонок
Медленная версия:
Мин: 0,013766934227399906, Макс .: 0,13766934227399907, Среднее: 0,13766934227399907 секунд на звонок