Что означает «()» для применения numpy вдоль оси и чем он отличается от 0

Я пытался получить хорошее представление о том, как numpy применяется вдоль оси. Ниже приведен код из документации numpy (https://numpy.org/doc/stable/reference/generated/numpy.apply_along_axis.html)

import numpy as np

def my_func(a):
    """Average first and last element of a 1-D array"""
    return (a[0] + a[-1]) * 0.5

b = np.array([[1,2,3], [4,5,6], [7,8,9]])
print(np.apply_along_axis(my_func, 0, b))
#array([4., 5., 6.])
print(np.apply_along_axis(my_func, 1, b))
#array([2.,  5.,  8.])

Согласно веб-странице, приведенный выше код имеет аналогичную функциональность с кодом ниже, который я взял с веб-страницы и изменил его (поигрался с ним), чтобы понять его:

arr = np.array([[1,2,3], [4,5,6], [7,8,9]])
axis = 0

def my_func(a):
    """Average first and last element of a 1-D array"""
    print(a, a[0], a[-1])
    return (a[0] + a[-1]) * 0.5

out = np.empty(arr.shape[axis+1:])
Ni, Nk = arr.shape[:axis], arr.shape[axis+1:]
print(Ni)
for ii in np.ndindex(Ni):
    for kk in np.ndindex(Nk):
        f = my_func(arr[ii + np.s_[:,] + kk])
        Nj = f.shape
        for jj in np.ndindex(Nj):
            out[ii + jj + kk] = f[jj]

#The code below may help in understanding what I was trying to figure out.
#print(np.shape(np.asarray(1)))
#x = np.int32(1)
#print(x, type(x), x.shape)

Из документации numpy я понимаю, что скаляры и массивы в numpy имеют одинаковые атрибуты и методы. Я пытаюсь понять разницу между «()» и 0. Я понимаю, что () — это кортеж. См. ниже.

Пример:

В приведенном ниже коде первый цикл for не выполняет итерацию, а второй цикл for выполняет итерацию один раз. Я пытаюсь понять, почему.

import numpy as np

for i in np.ndindex(0):
  print(i) #does not run.

for i in np.ndindex(()):
  print(i) #runs once

Вкратце: учитывая приведенный выше контекст, в чем разница между () и 0?

Возможно, ваш вопрос нуждается в уточнении. Они разные, потому что вы ожидаете, что np.index(0) и np.index((0,)) (просто кортеж с одинаковым 0) вернут один и тот же результат. Поскольку (0,) != () имеет некоторый смысл, что результат также отличается - почему содержимое np.ndindex(()) представляет собой один пустой кортеж, я не знаю.

Grismar 06.09.2024 01:15

@Grismar, () — это форма «скаляра», массива из одного элемента 0d. Вы можете проиндексировать это с помощью x[()].

hpaulj 06.09.2024 02:53

Спасибо @hpaulj - я также считаю ценным дополнением к принятому ответу то, что пустой кортеж является одновременно формой и (единственным) допустимым индексом для такого числового скаляра.

Grismar 06.09.2024 03:47
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
3
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

ндиндекс

Один возвращает пустой список, другой — список с одним кортежем:

In [39]: list(np.ndindex(0))
Out[39]: []

In [40]: list(np.ndindex(()))
Out[40]: [()]

Согласно документации ndindex, он возвращает кортежи в соответствии с формой входного аргумента:

At each iteration a tuple
of indices is returned, the last dimension is 
iterated over first.

Это может быть яснее всего с такими формами, как

In [55]: list(np.ndindex((2,3)))
Out[55]: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]

In [56]: list(np.ndindex((2,)))
Out[56]: [(0,), (1,)]

Это наиболее полезно при переборе массива фигур n-d. Но обычно мы вообще стараемся избегать итераций (даже с apply_along_axis).

Если массив имеет 0 элементов, его форма равна (0,), и перебор if ничего не дает. Если массив является «скалярным», форма равна (), и итерация по нему происходит один раз.

In [89]: x=np.empty(0)
    ...: for i in np.ndindex(x.shape):
    ...:     print(i, x[i])
    ...:     

In [90]: x=np.array(2)
    ...: for i in np.ndindex(x.shape):
    ...:     print(i, x[i])
    ...:     
() 2

In [91]: x=np.array([[1,2]])
    ...: for i in np.ndindex(x.shape):
    ...:     print(i, x[i])
    ...:     
(0, 0) 1
(0, 1) 2

2d применить

Ваш пример применения эквивалентен

In [59]: (arr[0,:]+arr[-1,:])/2
Out[59]: array([4., 5., 6.])

In [60]: (arr[:,0]+arr[:,-1])/2
Out[60]: array([2., 5., 8.])

В расширенном apply... использование ndindex имеет больше смысла при работе с 3 или более измерениями. Имея всего 2 (3,3), это не особо нужно.

In [61]: axis=0
In [62]: Ni, Nk = arr.shape[:axis], arr.shape[axis+1:]
In [63]: Ni,Nk
Out[63]: ((), (3,))
In [64]: arr.shape
Out[64]: (3, 3)

Для этого двухмерного массива расширенный код оценивается как:

In [67]: res=np.empty(3)
    ...: for i in range(3):
    ...:     res[i] = (arr[0,i]+arr[-1,i])/2
    ...: res
Out[67]: array([4., 5., 6.])

То есть first,last avg применяется к срезам на 2-й оси или, другими словами, ко всем измерениям, кроме axis==0.

3D массив

It might be more interesting with a 3d array

In [77]: def myfunc(a1d):
    ...:     print(a1d) 
    ...:     return (a1d[0]+a1d[-1])/2
    ...:     

In [78]: arr = np.arange(24).reshape(2,3,4)

In [79]: arr
Out[79]: 
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])

Итерация по всем осям, кроме оси 0, делает:

In [80]: np.apply_along_axis(myfunc,0,arr)
[ 0 12]
[ 1 13]
[ 2 14]
[ 3 15]
...
[11 23]
Out[80]: 
array([[ 6.,  7.,  8.,  9.],
       [10., 11., 12., 13.],
       [14., 15., 16., 17.]])

при итерации по всем осям, кроме последней:

In [82]: np.apply_along_axis(myfunc,2,arr)
[0 1 2 3]
[4 5 6 7]
[ 8  9 10 11]
[12 13 14 15]
[16 17 18 19]
[20 21 22 23]
Out[82]: 
array([[ 1.5,  5.5,  9.5],
       [13.5, 17.5, 21.5]])

Здесь он отправляет в функцию массивы размера 4 и делает это 6 раз, массив в форме (2,3).

Это то же самое, что индексировать последнюю ось:

In [87]: (arr[:,:,0]+arr[:,:,-1])/2
Out[87]: 
array([[ 1.5,  5.5,  9.5],
       [13.5, 17.5, 21.5]])

и для оси = 1 получите результат (2,4):

In [88]: (arr[:,0,:]+arr[:,-1,:])/2
Out[88]: 
array([[ 4.,  5.,  6.,  7.],
       [16., 17., 18., 19.]])

Здесь (arr[:,0,:]+arr[:,-1,:]) примерно эквивалентно использованию ndindex и f = my_func(arr[ii + np.s_[:,] + kk]), способу индексации «середины» нескольких осей.

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

apply... код написан на Python и доступен для чтения по ссылке [source]. Он упрощает итерацию по всей оси X, транспонируя эту ось до конца, упрощая indndindex), и выполняет итерацию с помощью:

for ind in inds:
    buff[ind] = asanyarray( 
       func1d(inarr_view[ind], *args, **kwargs))

https://github.com/numpy/numpy/blob/v2.1.0/numpy/lib/_shape_base_impl.py#L278-L419

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

Вкратце: учитывая приведенный выше контекст, в чем разница между () и 0?

Первый представляет собой нульмерный массив с одним элементом. Второй представляет собой одномерный массив с нулевыми элементами.

Нульмерный массив всегда состоит из одного элемента.

Пример:

>>> array = np.array(42)
>>> array
array(42)

Нульмерные массивы имеют форму ().

>>> array.shape
()

Индексация в нульмерный массив дает скаляр.

>>> array[()]
42

Нульмерные массивы похожи на скаляры, поскольку оба они могут иметь только один элемент. Однако в некоторых тонкостях они действуют по-разному. Различия между нульмерными массивами и скалярами выходят за рамки этой статьи.

Одномерные массивы, в отличие от нульмерных, могут содержать любое количество элементов. Например, этот одномерный массив содержит ноль элементов:

>>> array = np.array([])
>>> array
array([], dtype=float64)

Он имеет форму (0,).

>>> array.shape
(0,)

(Когда вы указываете форму от 0 до np.nditer(), она неявно преобразуется в (0,). Это та же форма, что и в вашем примере.)

Если вы перебираете каждый массив с помощью for i in np.nditer(array.shape):, цикл по массиву с одним элементом будет выполняться один раз. Цикл по массиву с нулевыми элементами будет выполняться ноль раз.

Я думаю, что это очень хорошее описание того, чем 0 и () отличаются, но, возможно, было бы полезно добавить к объяснению, что итерация по всем индексам приведет к пустому списку для одномерного массива с нулевыми элементами (нечего перебирать). пока) и список только с пустым кортежем для нульмерного массива, поскольку есть единственный элемент с индексом ()?

Grismar 06.09.2024 01:38

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