Использование numpy для создания массива со строками, извлеченными из другого 2D-массива в виде блоков 2x2

Предположим, у меня есть следующий 2D-массив:

x = np.array([[10,20,30,40], [50,60,70,80],[90,100,110,120]])  
print(x)

array([[ 10,  20,  30,  40],
       [ 50,  60,  70,  80],
       [ 90, 100, 110, 120]])

Я хотел бы построить новый массив y, где каждая строка имеет значения блока 2x2 из x в порядке по часовой стрелке:

print(y)
array([[ 10,  20,  60,  50],
       [ 20,  30,  70,  60],
       [ 30,  40,  80,  70],
       [ 50,  60,  100, 90],
       [ 60,  70,  110, 100],
       [ 70,  80,  120, 110]])

Я мог бы добиться этого, используя циклы Python for следующим образом:

n_rows, n_cols = x.shape
y = []
for i in range(n_rows-1): 
     for j in range(n_cols-1): 
         row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]] 
         y.append(row) 
y = np.array(y)

Интересно, есть ли более быстрый способ, использующий функции Numpy и избегающий использования циклов Python.

Если я правильно понимаю, вы пытаетесь реализовать какую-то операцию, подобную свертке, где каждая строка в y имеет значения из блока 2x2 в x, верно?

joanis 09.04.2022 19:13

@joanis: я хочу извлечь каждый блок 2x2 и поместить его в ряд.

OK-Validation 09.04.2022 19:15

@джоанис. Вы немного утрируете: здесь нет суммирования, а извлечение происходит по часовой стрелке, а не просто вразброс.

Mad Physicist 09.04.2022 19:46

@MadPhysicist Вы правы, скользящее окно - правильный инструмент, это не имеет ничего общего с извилинами. Мне нравится твой ответ. Я просто не нашел вопрос очень ясным в первую очередь.

joanis 09.04.2022 20:03

Там я просто отредактировал вопрос, чтобы прояснить связь между x и y.

joanis 09.04.2022 20:06

@джоанис. Проза вообще сбивает с толку. Обычно я смотрю на эталонную реализацию, предоставленную OP, и она совершенно недвусмысленна.

Mad Physicist 09.04.2022 20:11

@MadPhysicist Я согласен только наполовину. Да, код однозначен, но немного хорошо написанной прозы поможет вам понять код гораздо быстрее.

joanis 09.04.2022 20:14
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения текстовых сообщений может быть настолько сложным или простым, насколько вы его сделаете. Как и в любом ML-проекте, вы можете выбрать...
7 лайфхаков для начинающих Python-программистов
7 лайфхаков для начинающих Python-программистов
В этой статье мы расскажем о хитростях и советах по Python, которые должны быть известны разработчику Python.
Установка Apache Cassandra на Mac OS
Установка Apache Cassandra на Mac OS
Это краткое руководство по установке Apache Cassandra.
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
В одном из недавних постов я рассказал о том, как я использую навыки количественных исследований, которые я совершенствую в рамках программы TPQ...
Создание персонального файлового хранилища
Создание персонального файлового хранилища
Вы когда-нибудь хотели поделиться с кем-то файлом, но он содержал конфиденциальную информацию? Многие думают, что электронная почта безопасна, но это...
Создание приборной панели для анализа данных на GCP - часть I
Создание приборной панели для анализа данных на GCP - часть I
Недавно я столкнулся с интересной бизнес-задачей - визуализацией сбоев в цепочке поставок лекарств, которую могут просматривать врачи и...
5
7
48
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете кэшировать свой код, так как цикл в основном повторяет одну и ту же матрицу снова и снова (если вы хотите сохранить свой код с циклом). Я сделал сравнение скорости вашего кода до и после кэширования.

# Before caching
def loop_before_cache():
    n_rows, n_cols = x.shape
    y = []
    for i in range(n_rows-1): 
        for j in range(n_cols-1): 
            row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]] 
            y.append(row) 
    return np.array(y)


%timeit loop_before_cache()
11.6 µs ± 318 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

А теперь с кэшированием

# After caching
from functools import lru_cache

@lru_cache()
def loop_after_cache():
    n_rows, n_cols = x.shape
    y = []
    for i in range(n_rows-1): 
        for j in range(n_cols-1): 
            row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]] 
            y.append(row) 
    return np.array(y)

%timeit loop_after_cache()
83.6 ns ± 2.42 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Дополнительный

Я добавил смоделированные данные с массивом (1000,5000) с помощью range, чтобы показать эффективность кэширования.

x = np.array([i for i in range(1,5000001)])
x = np.reshape(x, (1000,5000))

# Before caching
def loop_before_cache():
    n_rows, n_cols = x.shape
    y = []
    for i in range(n_rows-1): 
        for j in range(n_cols-1): 
            row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]] 
            y.append(row) 
    return np.array(y)

%timeit loop_before_cache()
8.58 s ± 113 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# After caching
@lru_cache(maxsize = 256)
def loop_after_cache():
    n_rows, n_cols = x.shape
    y = []
    for i in range(n_rows-1): 
        for j in range(n_cols-1): 
            row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]] 
            y.append(row) 
    return np.array(y)

%timeit loop_after_cache()
82.2 ns ± 5.58 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Ницца! Тем не менее, я все еще хочу увидеть ответ, который, если возможно, использует функции numpy.

OK-Validation 09.04.2022 19:13

Я надеюсь, что смогу помочь с этим, но извините, я не очень хорош в этом numpy. Что ж, вы тоже можете использовать это кеширование после того, как появится лучший пост с решением numpy: D

Kevin Choon Liang Yew 09.04.2022 19:16

Теперь попробуйте это на массиве 100x500 или около того. Сравнительные тесты на крошечных наборах данных довольно бесполезны, поскольку в них преобладают накладные расходы, особенно в таких случаях, как numpy, который требует огромного объема подготовительной работы, прежде чем данные будут переданы фактической функции C.

Mad Physicist 09.04.2022 19:51

Спасибо за предложение, я включил обновление в ответ

Kevin Choon Liang Yew 10.04.2022 03:25
Ответ принят как подходящий

Сначала создайте sliding_window_view в x с прямоугольниками 2x2, которые вы хотите видеть:

b = np.lib.stride_tricks.sliding_window_view(x, (2, 2))

Каждый из самых внутренних массивов 2x2 содержит развернутую версию того, что вы хотите, но с перевернутой второй частью массива. Пока мы не копировали никаких данных. Теперь сделайте копию, распутав последнее измерение. Изменение формы всегда будет создавать здесь копию, потому что b очень несмежное:

c = b.reshape(*b.shape[:2], 4)

Поменяйте местами последние два столбца:

c[..., 2:] = c[..., -1:1:-1]

Теперь распутайте ведущие размеры:

y = c.reshape(-1, c.shape[-1])

Если у вас есть версия numpy старше 1.20, вы можете заменить определение b на

b = np.lib.stride_tricks.as_strided(x, shape=(x.shape[0] - 1, x.shape[1] - 1, 2, 2), strides=x.strides * 2)

Другие вопросы по теме