Использование списка списков индексов для разрезания столбцов и получения длины вектора по строкам

У меня есть массив 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]]), переменная длина списка индексов приводит к ошибке при разрезании, поскольку массив наборов индексов будет иметь неправильную форму. Есть ли способ реализовать однострочный вариант, не прибегая к перебирать список наборов индексов и обрабатывать каждый из них отдельно?

Чтобы быть уверенным, что то, что вы ищете, понятно, я бы предложил добавить ожидаемый результат более общего случая и цикл для его вычисления.

Matt Haberland 17.04.2024 16:49

Каковы размеры в реальном случае? Сколько комплектов? Помните, что с numpy несколько итераций сложной задачи могут оказаться самыми быстрыми. Петли не так уж и плохи.

hpaulj 17.04.2024 19:52

Существуют случаи использования my_idxs, где это можно сделать с помощью ufunc.reduceat(), но для этого требуется, чтобы все множества были смежными и монотонными (т. е. [[1,3], [2]] невозможно). Является ли my_idxs действительно произвольным или соответствует этим требованиям?

Daniel F 18.04.2024 09:22

@hpaulj В реалистичном случае массивы даже не были бы такими большими; подумайте, ~ 100 x 8. Наборов будет всего несколько, поскольку вы можете составить только определенное количество комбинаций в пределах 8 доступных столбцов. Несколько итераций на самом деле могут быть совсем неплохими, и, возможно, их даже нельзя избежать. Однако они будут работать в среде моделирования, которая запускается примерно 230 раз в секунду, поэтому я решил попробовать и посмотреть, есть ли какие-либо эффективные способы сделать это. Однако вы абсолютно правы, циклы не всегда плохи по своей сути и могут быть лучшим решением в этом случае.

Snoekoog 18.04.2024 12:34

@danielF Наборы указаны в настройках среды, в которой выполняется моделирование, и они также будут непересекающимися. Я думаю, что слово «произвольный» здесь действительно не самое подходящее, поскольку они заранее указаны в настройках. С моей стороны это неудачный выбор слов. Однако я не думаю, что могу во всех случаях гарантировать, что наборы будут соответствовать этим требованиям. Полагаю, мне также хотелось бы, чтобы это было как можно более обобщаемым на будущее.

Snoekoog 18.04.2024 12:41
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
5
91
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Можешь попробовать:

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

Одна линия, но все равно петля. Но такой цикл может быть необходим.

hpaulj 17.04.2024 16:52

Я согласен. Однако это решение выглядит наиболее элегантным! Делает свою работу, спасибо! Поскольку я использую версию 3.10.7, оператор распаковки * пока нельзя использовать в индексе (3.11+). Вместо этого я запускаю его без него и транспонирую результат, который дает тот же результат.

Snoekoog 18.04.2024 12:48

Вы хотите вычислить построчные нормы векторов, сформированных путем выбора столбцов из массива 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 17.04.2024 19:49

@hpaulj Похоже, это ответ, сгенерированный ИИ.

simon 18.04.2024 09:50

Вот ответ без цикла – вроде. Основная идея состоит в том, чтобы заменить списки индексов 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) выглядели многообещающе; однако проблема в нашем случае заключается (опять же, как отмечено в вопросе) в том, что списки индексов не все имеют одинаковую длину.

simon 18.04.2024 12:40

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