Изменять размер с усреднением или повторять массив numd 2d
Я пытаюсь переопределить в python функцию IDL:
http://star.pst.qub.ac.uk/idl/REBIN.html
который уменьшает с помощью целочисленного коэффициента массив 2d путем усреднения.
Например:
>>> a=np.arange(24).reshape((4,6))
>>> a
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]])
Я хотел бы изменить его до (2,3), взяв среднее значение соответствующих выборок, ожидаемый результат:
>>> b = rebin(a, (2, 3))
>>> b
array([[ 3.5, 5.5, 7.5],
[ 15.5, 17.5, 19.5]])
то есть. b[0,0] = np.mean(a[:2,:2]), b[0,1] = np.mean(a[:2,2:4])
и т.д.
Я считаю, что я должен переделать в 4-мерный массив, а затем взять среднее значение на правильном фрагменте, но не смог вычислить алгоритм. У вас есть намек?
Ответы
Ответ 1
Вот пример, основанный на ответ, который вы связали (для ясности):
>>> import numpy as np
>>> a = np.arange(24).reshape((4,6))
>>> a
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]])
>>> a.reshape((2,a.shape[0]//2,3,-1)).mean(axis=3).mean(1)
array([[ 3.5, 5.5, 7.5],
[ 15.5, 17.5, 19.5]])
Как функция:
def rebin(a, shape):
sh = shape[0],a.shape[0]//shape[0],shape[1],a.shape[1]//shape[1]
return a.reshape(sh).mean(-1).mean(1)
Ответ 2
J.F. У Себастьяна есть отличный ответ для 2D-биннинга. Вот версия его функции "rebin", которая работает для N измерений:
def bin_ndarray(ndarray, new_shape, operation='sum'):
"""
Bins an ndarray in all axes based on the target shape, by summing or
averaging.
Number of output dimensions must match number of input dimensions and
new axes must divide old ones.
Example
-------
>>> m = np.arange(0,100,1).reshape((10,10))
>>> n = bin_ndarray(m, new_shape=(5,5), operation='sum')
>>> print(n)
[[ 22 30 38 46 54]
[102 110 118 126 134]
[182 190 198 206 214]
[262 270 278 286 294]
[342 350 358 366 374]]
"""
operation = operation.lower()
if not operation in ['sum', 'mean']:
raise ValueError("Operation not supported.")
if ndarray.ndim != len(new_shape):
raise ValueError("Shape mismatch: {} -> {}".format(ndarray.shape,
new_shape))
compression_pairs = [(d, c//d) for d,c in zip(new_shape,
ndarray.shape)]
flattened = [l for p in compression_pairs for l in p]
ndarray = ndarray.reshape(flattened)
for i in range(len(new_shape)):
op = getattr(ndarray, operation)
ndarray = op(-1*(i+1))
return ndarray
Ответ 3
Здесь вы можете сделать то, что вы просите, используя матричное умножение, которое не требует новых размеров массива для разделения старого.
Сначала мы создаем матрицу компрессора строк и матрицу компрессора столбцов (я уверен, что есть более чистый способ сделать это, возможно, даже используя только операции numpy):
def get_row_compressor(old_dimension, new_dimension):
dim_compressor = np.zeros((new_dimension, old_dimension))
bin_size = float(old_dimension) / new_dimension
next_bin_break = bin_size
which_row = 0
which_column = 0
while which_row < dim_compressor.shape[0] and which_column < dim_compressor.shape[1]:
if round(next_bin_break - which_column, 10) >= 1:
dim_compressor[which_row, which_column] = 1
which_column += 1
elif next_bin_break == which_column:
which_row += 1
next_bin_break += bin_size
else:
partial_credit = next_bin_break - which_column
dim_compressor[which_row, which_column] = partial_credit
which_row += 1
dim_compressor[which_row, which_column] = 1 - partial_credit
which_column += 1
next_bin_break += bin_size
dim_compressor /= bin_size
return dim_compressor
def get_column_compressor(old_dimension, new_dimension):
return get_row_compressor(old_dimension, new_dimension).transpose()
... так, например, get_row_compressor(5, 3)
дает вам:
[[ 0.6 0.4 0. 0. 0. ]
[ 0. 0.2 0.6 0.2 0. ]
[ 0. 0. 0. 0.4 0.6]]
и get_column_compressor(3, 2)
дает вам:
[[ 0.66666667 0. ]
[ 0.33333333 0.33333333]
[ 0. 0.66666667]]
Затем просто допробуйте компрессором строк и послепроцессорным компрессором колонки, чтобы получить сжатую матрицу:
def compress_and_average(array, new_shape):
# Note: new shape should be smaller in both dimensions than old shape
return np.mat(get_row_compressor(array.shape[0], new_shape[0])) * \
np.mat(array) * \
np.mat(get_column_compressor(array.shape[1], new_shape[1]))
Используя этот метод,
compress_and_average(np.array([[50, 7, 2, 0, 1],
[0, 0, 2, 8, 4],
[4, 1, 1, 0, 0]]), (2, 3))
дает:
[[ 21.86666667 2.66666667 2.26666667]
[ 1.86666667 1.46666667 1.86666667]]
Ответ 4
Я пытался уменьшить масштаб растра - возьмите растровый размер размером примерно от 6000 до 2000 и превратите его в произвольный размер меньшего растра, который усреднил значения правильно по сравнению с предыдущими размерами бункеров. Я нашел решение с помощью SciPy, но тогда я не смог заставить SciPy установить на используемую мной услугу совместного доступа, поэтому я просто написал эту функцию. Вероятно, есть более эффективные способы сделать это, что не связано с циклом по строкам и столбцам, но похоже, что это работает.
Приятная часть этого заключается в том, что старое количество строк и столбцов не обязательно должно делиться на новые числа строк и столбцов.
def resize_array(a, new_rows, new_cols):
'''
This function takes an 2D numpy array a and produces a smaller array
of size new_rows, new_cols. new_rows and new_cols must be less than
or equal to the number of rows and columns in a.
'''
rows = len(a)
cols = len(a[0])
yscale = float(rows) / new_rows
xscale = float(cols) / new_cols
# first average across the cols to shorten rows
new_a = np.zeros((rows, new_cols))
for j in range(new_cols):
# get the indices of the original array we are going to average across
the_x_range = (j*xscale, (j+1)*xscale)
firstx = int(the_x_range[0])
lastx = int(the_x_range[1])
# figure out the portion of the first and last index that overlap
# with the new index, and thus the portion of those cells that
# we need to include in our average
x0_scale = 1 - (the_x_range[0]-int(the_x_range[0]))
xEnd_scale = (the_x_range[1]-int(the_x_range[1]))
# scale_line is a 1d array that corresponds to the portion of each old
# index in the_x_range that should be included in the new average
scale_line = np.ones((lastx-firstx+1))
scale_line[0] = x0_scale
scale_line[-1] = xEnd_scale
# Make sure you don't screw up and include an index that is too large
# for the array. This isn't great, as there could be some floating
# point errors that mess up this comparison.
if scale_line[-1] == 0:
scale_line = scale_line[:-1]
lastx = lastx - 1
# Now it linear algebra time. Take the dot product of a slice of
# the original array and the scale_line
new_a[:,j] = np.dot(a[:,firstx:lastx+1], scale_line)/scale_line.sum()
# Then average across the rows to shorten the cols. Same method as above.
# It is probably possible to simplify this code, as this is more or less
# the same procedure as the block of code above, but transposed.
# Here I'm reusing the variable a. Sorry if that confusing.
a = np.zeros((new_rows, new_cols))
for i in range(new_rows):
the_y_range = (i*yscale, (i+1)*yscale)
firsty = int(the_y_range[0])
lasty = int(the_y_range[1])
y0_scale = 1 - (the_y_range[0]-int(the_y_range[0]))
yEnd_scale = (the_y_range[1]-int(the_y_range[1]))
scale_line = np.ones((lasty-firsty+1))
scale_line[0] = y0_scale
scale_line[-1] = yEnd_scale
if scale_line[-1] == 0:
scale_line = scale_line[:-1]
lasty = lasty - 1
a[i:,] = np.dot(scale_line, new_a[firsty:lasty+1,])/scale_line.sum()
return a