У меня есть массив NxM, а также произвольный список наборов индексов столбцов, которые я хотел бы использовать для разрезания массива. Например, массив 3x3
my_arr = np.array([[1, 2, 3], [1, 2, 3], [1, 2, 3]])
и наборы индексов
my_idxs = [[0, 1], [2]]
Я хотел бы использовать пары индексов, чтобы выбрать соответствующие столбцы из массива и получить длину векторов (по строкам), используя np.linalg.norm()
. Я хотел бы сделать это для всех пар индексов. Учитывая вышеупомянутый массив и список наборов индексов, это должно дать:
[[2.23606797749979, 3],
[2.23606797749979, 3],
[2.23606797749979, 3]]
Когда все наборы имеют одинаковое количество индексов (например, используя my_idxs = [[0, 1], [1, 2]]
, я могу просто использовать np.linalg.norm(my_arr[:, my_idxs], axis=1)
:
[[2.23606797749979, 3.605551275463989],
[2.23606797749979, 3.605551275463989],
[2.23606797749979, 3.605551275463989]]
Однако когда это не так (как в случае с my_idxs = [[0, 1], [2]]
), переменная длина списка индексов приводит к ошибке при разрезании, поскольку массив наборов индексов будет иметь неправильную форму. Есть ли способ реализовать однострочный вариант, не прибегая к перебирать список наборов индексов и обрабатывать каждый из них отдельно?
Каковы размеры в реальном случае? Сколько комплектов? Помните, что с numpy
несколько итераций сложной задачи могут оказаться самыми быстрыми. Петли не так уж и плохи.
Существуют случаи использования my_idxs
, где это можно сделать с помощью ufunc.reduceat()
, но для этого требуется, чтобы все множества были смежными и монотонными (т. е. [[1,3], [2]]
невозможно). Является ли my_idxs
действительно произвольным или соответствует этим требованиям?
@hpaulj В реалистичном случае массивы даже не были бы такими большими; подумайте, ~ 100 x 8. Наборов будет всего несколько, поскольку вы можете составить только определенное количество комбинаций в пределах 8 доступных столбцов. Несколько итераций на самом деле могут быть совсем неплохими, и, возможно, их даже нельзя избежать. Однако они будут работать в среде моделирования, которая запускается примерно 230 раз в секунду, поэтому я решил попробовать и посмотреть, есть ли какие-либо эффективные способы сделать это. Однако вы абсолютно правы, циклы не всегда плохи по своей сути и могут быть лучшим решением в этом случае.
@danielF Наборы указаны в настройках среды, в которой выполняется моделирование, и они также будут непересекающимися. Я думаю, что слово «произвольный» здесь действительно не самое подходящее, поскольку они заранее указаны в настройках. С моей стороны это неудачный выбор слов. Однако я не думаю, что могу во всех случаях гарантировать, что наборы будут соответствовать этим требованиям. Полагаю, мне также хотелось бы, чтобы это было как можно более обобщаемым на будущее.
Можешь попробовать:
my_arr = np.array([[1, 2, 3], [1, 2, 3], [1, 2, 3]])
my_idxs = [[0, 1], [2]]
out = np.c_[*[np.linalg.norm(my_arr[:, i], axis=1) for i in my_idxs]]
print(out)
Распечатки:
[[2.23606798 3. ]
[2.23606798 3. ]
[2.23606798 3. ]]
Одна линия, но все равно петля. Но такой цикл может быть необходим.
Я согласен. Однако это решение выглядит наиболее элегантным! Делает свою работу, спасибо! Поскольку я использую версию 3.10.7, оператор распаковки *
пока нельзя использовать в индексе (3.11+). Вместо этого я запускаю его без него и транспонирую результат, который дает тот же результат.
Вы хотите вычислить построчные нормы векторов, сформированных путем выбора столбцов из массива NumPy, используя списки индексов различной длины. Вы стремитесь достичь этого эффективно, желательно без использования явных циклов над наборами индексов.
Решение: Вы можете использовать понимание списка, чтобы решить проблему, связанную с наборами индексов различной длины. Хотя это не единственная операция нарезки (что невозможно из-за неправильной формы), это лаконичный подход, использующий возможности операций NumPy.
Вот как это можно реализовать:
import numpy as np
# Define your array
my_arr = np.array([[1, 2, 3], [1, 2, 3], [1, 2, 3]])
# Define your list of index sets
my_idxs = [[0, 1], [2]]
# Compute the norm for each set of indices using a list comprehension
result = np.array([np.linalg.norm(my_arr[:, idx], axis=1) for idx in my_idxs]).T
# Print the result
print(result)
Выход:
[[2.23606798 3. ]
[2.23606798 3. ]
[2.23606798 3. ]]
Объяснение:
Понимание списка: циклически перебирает каждый набор индексов в my_idxs. Для каждого набора он выбирает соответствующие столбцы из my_arr и вычисляет норму по строкам (ось = 1).
Транспонирование (T): Результатом понимания списка является список, в котором каждый элемент представляет собой массив, представляющий нормы, рассчитанные для каждого набора индексов. np.array(...) преобразует этот список в 2D-массив NumPy. Затем применяется транспонирование для правильного выравнивания выходных данных, чтобы каждая строка соответствовала исходным строкам my_arr, а каждый столбец представлял результаты нормы для каждого набора индексов.
Этот подход эффективно удовлетворяет вашим требованиям по вычислению векторных норм на основе подмножеств столбцов массива, даже если подмножества различаются по длине. Он максимально использует возможности NumPy, чтобы избежать явного снижения производительности, связанного с циклическим перебором строк массива.
Заключение: Хотя прямое разделение индексов различной длины на одну операцию невозможно из-за возникающих в результате неоднородных размеров, предоставленное решение достигает вашей цели с помощью компактной и эффективной строки кода Python.
Ваш andwer по сути такой же, как и предыдущий, просто используется другой способ объединения норм. И более длинное объяснение. Никто всерьез не предлагал перебирать строки (не понимаю, чем это поможет). Пока число наборов индексов относительно небольшое, итерация наборов не будет дорогостоящей.
@hpaulj Похоже, это ответ, сгенерированный ИИ.
Вот ответ без цикла – вроде. Основная идея состоит в том, чтобы заменить списки индексов my_idxs
эквивалентными «логическими масками» my_masks
, где содержащиеся индексы отмечены 1
, а другие - 0
. Затем можно рассчитать норму после взвешивания по маскам. Таким образом, решение может выглядеть следующим образом:
import numpy as np
my_arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# Replace index lists with boolean masks: [0, 1] → [1, 1, 0], [2] → [0, 0, 1]
my_masks = [[1, 1, 0], [0, 0, 1]]
result = np.linalg.norm(my_arr[:, np.newaxis, :] * my_masks, axis=-1)
print(result)
# >>> [[ 2.23606798 3. ]
# [ 6.40312424 6. ]
# [10.63014581 9. ]]
Обратите внимание, что я заменил ваши значения в my_arr
разными значениями для каждой строки, чтобы подтвердить, что подход действительно работает так, как ожидалось. Более того, я совершенно уверен, что эквивалентное решение можно реализовать с использованием маскированных массивов.
В любом случае, вот в чем загвоздка: я не нашел подхода, который бы не использовал цикл for для преобразования ваших списков индексов в мои маски. Так что, в каком-то смысле, я просто перемещаю проблему. Однако, в зависимости от того, как вы в первую очередь определяете свои списки индексов, использование масок вместо них все равно может быть решением, которое стоит рассмотреть.
В частности, что не сработало при создании маски: сначала ответы на этот вопрос (stackoverflow.com/questions/53631460) выглядели многообещающе; однако проблема в нашем случае заключается (опять же, как отмечено в вопросе) в том, что списки индексов не все имеют одинаковую длину.
Чтобы быть уверенным, что то, что вы ищете, понятно, я бы предложил добавить ожидаемый результат более общего случая и цикл для его вычисления.