Как можно векторизовать эту функцию?

#python #numpy

#python #numpy

Вопрос:

У меня есть массив NumPy со следующими свойствами:

  • форма: (9986080, 2)
  • dtype: np.float32

У меня есть метод, который перебирает диапазон массива, выполняет операцию, а затем вводит результат в новый массив:

 def foo(arr):
    new_arr = np.empty(arr.size, dtype=np.uint64)
    for i in range(arr.size):
        x, y = arr[i]
        e, n = ''
        if x < 0:
            e = '1'
        else:
            w = '2'
        if y > 0:
            n = '3'
        else:
            s = '4'
        new_arr[i] = int(f'{abs(x)}{e}{abs(y){n}'.replace('.', ''))
  

Комментарии:

1. Какова цель этого кода? Я вижу, что она буквально делает, но я не уверен, почему вы это делаете. Знание того, что она должна делать, может помочь найти лучший ответ.

2. @Iguananaut Он принимает значения X Y, которые представляют центральные точки произвольных фигур, определяет квадрант, в котором находятся их широты и долготы, и создает из этого уникальный идентификатор, окончательно преобразуя его в int . Это преобразование необходимо для сохранения в хранилище в формате файла позже.

3. Хорошо, это имеет смысл. Мне любопытно, хотя, что это за формат файла? У меня недостаточно информации, чтобы сказать, хорошо ли мотивирована эта схема, но она кажется ужасно непрозрачной, если вы спросите меня.

4. К сожалению, @Iguananaut проприетарный. Тем не менее, я пытаюсь векторизовать это или найти способ применить это по всему массиву, а не повторять диапазон.

5. Каковы домены x и y, хотя. Если я не ошибаюсь, это не возвращает уникальное значение для любых произвольных x и y .

Ответ №1:

Я согласен с комментарием Iguananaut о том, что эта структура данных кажется немного странной. Моя самая большая проблема с этим заключается в том, что действительно сложно попытаться векторизовать объединение целых чисел в строку, а затем повторно преобразовать это в целое число. Тем не менее, это, безусловно, поможет ускорить работу функции:

 def foo(arr):
    x_values = arr[:,0]
    y_values = arr[:,1]
    ones = np.ones(arr.shape[0], dtype=np.uint64)
    e = np.char.array(np.where(x_values < 0, ones, ones * 2))
    n = np.char.array(np.where(y_values < 0, ones * 3, ones * 4))
    x_values = np.char.array(np.absolute(x_values))
    y_values = np.char.array(np.absolute(y_values))
    x_values = np.char.replace(x_values, '.', '')
    y_values = np.char.replace(y_values, '.', '')
    new_arr = np.char.add(np.char.add(x_values, e), np.char.add(y_values, n))
    return new_arr.astype(np.uint64)
  

Здесь значения x и y входного массива сначала разделяются. Затем мы используем векторизованное вычисление, чтобы определить, где e и n должно быть 1 или 2, 3 или 4. В последней строке используется стандартное понимание списка для выполнения бита слияния строк, который по-прежнему нежелательно медленный для сверхбольших массивов, но быстрее, чем обычный цикл for. Также векторизация предыдущих вычислений должна значительно ускорить работу функции.

Редактировать: я ошибался раньше. У Numpy действительно есть хороший способ обработки конкатенации строк с использованием метода np.char.add(). Для этого требуется преобразование x_values и y_values в массивы символов Numpy с использованием np.char.array() . Также по какой-то причине np.char.add() метод принимает только два массива в качестве входных данных, поэтому необходимо сначала объединить x_values и e и y_values и n и, а затем объединить эти результаты. Тем не менее, это векторизирует вычисления и должно быть довольно быстрым. Код все еще немного неуклюж из-за довольно странной операции, которую вы выполняете, но я думаю, что это поможет вам значительно ускорить работу функции.

Комментарии:

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

2. Изучил этот вопрос и нашел лучший способ сделать это. Смотрите мое обновление выше. Я думаю, что это довольно эффективно выполняет то, что вы хотели сделать.

Ответ №2:

Вы можете использовать np.apply_along_axis . Когда вы передаете эту функцию другой функции, которая принимает строку (или столбец) в качестве аргумента, она делает то, что вы хотите сделать.

В вашем случае вы можете переписать функцию, как показано ниже:

 def foo(row):
        x, y = row
        e, n = ''
        if x < 0:
            e = '1'
        else:
            w = '2'
        if y > 0:
            n = '3'
        else:
            s = '4'
        return int(f'{abs(x)}{e}{abs(y){n}'.replace('.', ''))


# Where you want to you use it.
new_arr = np.apply_along_axis(foo, 1, n)