Использование scipy interpn с сеткой на n-мерном массиве - PullRequest
1 голос
/ 18 января 2020

Я пытаюсь перевести интерполяцию Matlab "interpn" большого массива 4D, но составы значительно расходятся между Matlab и Python. Есть хороший вопрос / ответ от нескольких лет go здесь , с которым я пытался работать. Я думаю, что я почти на месте, но, видимо, до сих пор не правильно сформулировал свой интерполятор сетки.

Я смоделировал мой пример кода после примера, приведенного в связанном ответе выше, как можно точнее, при использовании измерений. Я на самом деле работаю. Единственное изменение заключается в том, что я переключил роллаксис на ходу оси, так как первый устарел.

По сути, учитывая массив 4D skyrad0 (который зависит от четырех элементов, определенных в первом кодовом блоке) вместе с двумя константами и двумя массивами 1D, определенными в третьем блоке, я хочу получить интерполированный 2D-результат.

from scipy.interpolate import interpn
import numpy as np

# Define the data space in the 4D skyrad0 array
solzen = np.arange(0,70,10)     # 7
aod = np.arange(0,0.25,0.05)    # 5
index = np.arange(1,92477,1)    # 92476
wave = np.arange(350,1050,5)    # 140

# Simulated skyrad for the values above
skyrad0 = np.random.rand(
    solzen.size,aod.size,index.size,wave.size) # 7, 5, 92476, 140

# Data space for desired output values of skyrad 
# with interpolation between input data space
solzen0 = 30                    # 1
aod0 = 0.1                      # 1
index0 = index                  # 92476
wave0 = np.arange(350,1050,10)  # 70

# Matlab
# result = squeeze(interpn(solzen, aod, index, wave,
#                   skyrad0,
#                   solzen0, aod0, index0, wave0))

# Scipy
points = (solzen, aod, index, wave)             # 7, 5, 92476, 140
interp_mesh = np.array(
    np.meshgrid(solzen0, aod0, index0, wave0))  # 4, 1, 1, 92476, 70
interp_points = np.moveaxis(interp_mesh, 0, -1) # 1, 1, 92476, 70, 4
interp_points = interp_points.reshape(
    (interp_mesh.size // interp_mesh.shape[3], 
    interp_mesh.shape[3]))                      # 280, 92476

result = interpn(points, skyrad0, interp_points)

Я ожидаю "результат" массива 4D, который я могу numpy. Сжать в нужный мне 2D-ответ, но interpn выдает ошибку:

ValueError: The requested sample points xi have dimension 92476, but this RegularGridInterpolator has dimension 4

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

Бонусом будет, если кто-нибудь сможет выявить явные недостатки в моей формулировке. Мне нужно будет выполнить этот тип интерполяции тысячи раз для ряда различных структур - даже до 6D - поэтому эффективность важна.

Обновление Ответ, приведенный ниже, решил проблема очень элегантно. Однако, когда вычисления и массивы становятся более сложными, возникает другая проблема, а именно то, что кажется проблемой с элементами в массиве, которые не увеличиваются монотонно. Вот проблема, перефразированная в 6D:

# Data space in the 6D rad_boa array
azimuth = np.arange(0, 185, 5) # 37
senzen = np.arange(0, 185, 5) # 37
wave = np.arange(350,1050,5)    # 140
# wave = np.array([350, 360, 370, 380, 390, 410, 440, 470, 510, 550, 610, 670, 750, 865, 1040, 1240, 1640, 2250]) # 18
solzen = np.arange(0,65,5)     # 13
aod = np.arange(0,0.55,0.05)    # 11
wind = np.arange(0, 20, 5)      # 4

# Simulated rad_boa
rad_boa = np.random.rand(
    azimuth.size,senzen.size,wave.size,solzen.size,aod.size,wind.size,) # 37, 37, 140/18, 13, 11, 4

azimuth0 = 135              # 1
senzen0 = 140               # 1
wave0 = np.arange(350,1010,10) # 66
solzen0 = 30                # 1
aod0 = 0.1                  # 1
wind0 = 10                  # 1

da = xr.DataArray(name='Radiance_BOA',
                data=rad_boa,
                dims=['azimuth','senzen','wave','solzen','aod','wind'],
                coords=[azimuth,senzen,wave,solzen,aod,wind])

rad_inc_scaXR = da.loc[azimuth0,senzen0,wave0,solzen0,aod0,wind0].squeeze()

В существующем состоянии она работает, но если вы измените определение волны на закомментированную строку, она выдаст ошибку:

KeyError: "not all values found in index 'wave'"

Наконец, в ответ на комментарий ниже (и для повышения эффективности) я включил структуру файла HDF5 (созданного в Matlab), из которого фактически построен этот массив "rad_boa" 6D (в этом примере выше используется только смоделированный случайный массив). Фактическая база данных читается в Xarray следующим образом:

sdb = xr.open_dataset(db_path, group='sdb')

И полученный Xarray выглядит примерно так: Xarray VSC guide

1 Ответ

1 голос
/ 18 января 2020

Почему ошибка ValueError?

Прежде всего, scipy.interpolate.interpn требует, чтобы interp_points.shape[-1] было таким же, как число измерений в вашей задаче. Вот почему вы получаете ValueError из своего фрагмента кода - ваш interp_points имеет 92476 как n_dims, что конфликтует с фактическим количеством димсов (4).

Быстрое исправление

Вы можете исправить этот фрагмент кода, просто изменив порядок своих операций. Вы пытаетесь сжать слишком рано - если вы сжимаете после интерп:

points = (solzen, aod, index, wave)                 # 7, 5, 92476, 140
mg = np.meshgrid(solzen0, aod0, index0, wave0)      # 4, 1, 1, 92476, 70
interp_points = np.moveaxis(mg, 0, -1)              # 1, 1, 92476, 70, 4
result_presqueeze = interpn(points, 
                            skyrad0, interp_points) # 1, 1, 92476, 70
result = np.squeeze(result_presqueeze,
                    axis=(0,1))                     # 92476, 70

Я заменил interp_mesh на mg здесь и удалил np.array (это не требуется, поскольку np.meshgrid возвращает объект ndarray).

Комментарии по производительности

Я думаю, что ваш фрагмент кода в порядке, однако вы можете sh для использования xarray, если вы обрабатываете помеченные данные, как это:

  • чуть более читабельно, чем немаркированные numpy массивы
  • также может обрабатывать некоторые из фоновая работа с использованием dask (полезно, если вы просматриваете большие объемы данных в 6D)

Обновление : Ой! Это должно было быть .interp, а не .loc. Фрагмент кода ниже работал, потому что точки данных были фактически исходными точками данных. В качестве предупреждения для других:

from scipy.interpolate import interpn
import numpy as np
from xarray import DataArray

# Define the data space in the 4D skyrad0 array
solzen = np.arange(0,70,10)     # 7
aod = np.arange(0,0.25,0.05)    # 5
index = np.arange(1,92477,1)    # 92476
wave = np.arange(350,1050,5)    # 140

# Simulated skyrad for the values above
skyrad0 = np.random.rand(
    solzen.size,aod.size,index.size,wave.size) # 7, 5, 92476, 140

# Data space for desired output values of skyrad 
# with interpolation between input data space
solzen0 = 30                    # 1
aod0 = 0.1                      # 1
index0 = index                  # 92476
wave0 = np.arange(350,1050,10)  # 70

def slow():
    points = (solzen, aod, index, wave)                 # 7, 5, 92476, 140
    mg = np.meshgrid(solzen0, aod0, index0, wave0)      # 4, 1, 1, 92476, 70
    interp_points = np.moveaxis(mg, 0, -1)              # 1, 1, 92476, 70, 4
    result_presqueeze = interpn(points, 
                                skyrad0, interp_points) # 1, 1, 92476, 70
    result = np.squeeze(result_presqueeze,
                        axis=(0,1))                     # 92476, 70
    return result

# This function uses .loc instead of .interp!
"""
def fast():
    da = DataArray(name='skyrad0',
                   data=skyrad0,
                   dims=['solzen','aod','index','wave'],
                   coords=[solzen, aod, index, wave])

    result = da.loc[solzen0, aod0, index0, wave0].squeeze()

    return result
"""

Сделав несколько изменений в обновленном фрагменте кода, заданном OP:

import numpy as np
import xarray as xr
from scipy.interpolate import interpn

azimuth = np.arange(0, 185, 5) # 37
senzen = np.arange(0, 185, 5) # 37
#wave = np.arange(350,1050,5)    # 140
wave = np.asarray([350, 360, 370, 380, 390, 410, 440, 470, 510,
                   550, 610, 670, 750, 865, 1040, 1240, 1640, 2250]) # 18
solzen = np.arange(0,65,5)     # 13
aod = np.arange(0,0.55,0.05)    # 11
wind = np.arange(0, 20, 5)      # 4

coords = [azimuth, senzen, wave, solzen, aod, wind]

azimuth0 = 135              # 1
senzen0 = 140               # 1
wave0 = np.arange(350,1010,10) # 66
solzen0 = 30                # 1
aod0 = 0.1                  # 1
wind0 = 10                  # 1

interp_coords = [azimuth0, senzen0, wave0, solzen0, aod0, wind0]

# Simulated rad_boa
rad_boa = np.random.rand(
    *map(lambda x: x.size, coords)) # 37, 37, 140/18, 13, 11, 4

def slow():
    mg = np.meshgrid(*interp_coords)
    interp_points = np.moveaxis(mg, 0, -1)
    result_presqueeze = interpn(coords, 
                                rad_boa, interp_points)
    result = np.squeeze(result_presqueeze)
    return result

def fast():
    da = xr.DataArray(name='Radiance_BOA',
                    data=rad_boa,
                    dims=['azimuth','senzen','wave','solzen','aod','wind'],
                    coords=coords)

    interp_dict = dict(zip(da.dims, interp_coords))

    rad_inc_scaXR = da.interp(**interp_dict).squeeze()
    return rad_inc_scaXR

Это довольно быстро:

>>> %timeit slow()
2.09 ms ± 85.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit fast()
343 ms ± 6.77 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> np.array_equal(slow(),fast())
True

Подробнее о xarray интерполяции можно узнать здесь . Экземпляры набора данных имеют очень похожий синтаксис.

Также возможно изменить метод интерполяции по желанию (возможно, можно sh предоставить ключевой аргумент method='nearest' - .interp для дискретной задачи интерполяции) .

Более продвинутые вещи

Если вы хотите реализовать что-то более продвинутое, я бы порекомендовал, возможно, использовать одну из реализаций MARS (многомерных адаптивных сплайнов регрессии) , Он находится где-то между стандартной регрессией и интерполяцией и работает для многомерных случаев. В Python 3 ваша лучшая ставка - pyearth.

...