Я пытался получить хорошее представление о том, как 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?
@Grismar, ()
— это форма «скаляра», массива из одного элемента 0d. Вы можете проиндексировать это с помощью x[()]
.
Спасибо @hpaulj - я также считаю ценным дополнением к принятому ответу то, что пустой кортеж является одновременно формой и (единственным) допустимым индексом для такого числового скаляра.
Один возвращает пустой список, другой — список с одним кортежем:
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
Ваш пример применения эквивалентен
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
.
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, транспонируя эту ось до конца, упрощая ind
(с ndindex
), и выполняет итерацию с помощью:
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
и ()
отличаются, но, возможно, было бы полезно добавить к объяснению, что итерация по всем индексам приведет к пустому списку для одномерного массива с нулевыми элементами (нечего перебирать). пока) и список только с пустым кортежем для нульмерного массива, поскольку есть единственный элемент с индексом ()
?
Возможно, ваш вопрос нуждается в уточнении. Они разные, потому что вы ожидаете, что
np.index(0)
иnp.index((0,))
(просто кортеж с одинаковым 0) вернут один и тот же результат. Поскольку(0,) != ()
имеет некоторый смысл, что результат также отличается - почему содержимоеnp.ndindex(())
представляет собой один пустой кортеж, я не знаю.