interpolación inversa de rayos x (en coordenadas, no en datos)

8

Tengo el siguiente DataArray

arr = xr.DataArray([[0.33, 0.25],[0.55, 0.60],[0.85, 0.71],[0.92,0.85],[1.50,0.96],[2.5,1.1]],[('x',[0.25,0.5,0.75,1.0,1.25,1.5]),('y',[1,2])])

Esto da el siguiente resultado

<xarray.DataArray (x: 6, y: 2)>
array([[0.33, 0.25],
       [0.55, 0.6 ],
       [0.85, 0.71],
       [0.92, 0.85],
       [1.5 , 0.96],
       [2.5 , 1.1 ]])
Coordinates:
  * x        (x) float64 0.25 0.5 0.75 1.0 1.25 1.5
  * y        (y) int32 1 2

o ordenados a continuación con x y salida (z) uno al lado del otro para mayor comodidad.

x         z (y=1)   z(y=2)
0.25      0.33      0.25
0.50      0.55      0.60
0.75      0.85      0.71
1.00      0.92      0.85
1.25      1.50      0.96
1.50      2.50      1.10

Los datos que tengo son el resultado de varios valores de entrada. Uno de ellos es el valor x. Hay varias otras dimensiones (como y) para otros valores de entrada. Quiero saber cuándo mi valor de salida (z) está creciendo a más de 1.00, manteniendo las otras dimensiones fijas y variando el valor de x. En el ejemplo bidimensional anterior, me gustaría obtener la respuesta [1.03 1.32]. Porque un valor de 1.03 para x me dará 1.00 para z cuando y = 1 y un valor de 1.32 para x me dará 1.00 para z cuando y = 2.

editar: Dado que la salida z crecerá con el aumento de x, solo hay un punto donde z tendrá 1.0 como salida.

¿Hay alguna manera eficiente de lograr esto con xarray? Mi tabla actual es mucho más grande y tiene 4 entradas (dimensiones).

¡Gracias por cualquier ayuda!

Hoogendijk
fuente

Respuestas:

4

xarray tiene una función muy útil para esto: xr.interpque hará una interpolación lineal por partes de un xarray.

En su caso, puede usarlo para obtener una interpolación por partes de los puntos (x, y1) y (x, y1). Una vez hecho esto, lo único que queda por hacer es obtener el valor de su xmatriz interpolada que está asociada al valor de cierre de su matriz interpolada.y1/y2/.. matriz al número objetivo (1.00 en su ejemplo).

Así es como podría verse esto:

y_dims = [0, 1,] 
target_value = 1.0
# create a 'high resolution` version of your data array:
arr_itp = arr.interp(x=np.linspace(arr.x.min(), arr.x.max(), 10000))
for y in y_dims:
    # get the index of closest data
    x_closest = np.abs(arr_itp.isel(y=y) - target_value).argmin()
    print(arr_itp.isel(y=y, x=x_closest))

>>> <xarray.DataArray ()>
>>> array(0.99993199)
>>> Coordinates:
>>>     y        int64 1
>>>     x        float64 1.034
>>> <xarray.DataArray ()>
>>> array(1.00003)
>>> Coordinates:
>>>     y        int64 2
>>>     x        float64 1.321


Si bien esto funciona, no es una forma realmente eficiente de abordar el problema y aquí hay 2 razones por las que no:

  1. El uso de xr.interp realiza una interpolación por partes de todo el DataArray. Sin embargo, solo necesitamos la interpolación entre los dos puntos más cercanos a su valor objetivo.
  2. Aquí, una interpolación es una línea recta entre 2 puntos. Pero si conocemos una coordenada de un punto en esa línea (y = 1.00) entonces simplemente podemos calcular la otra coordenada resolviendo la ecuación lineal de la línea recta y el problema se resuelve en unas pocas operaciones aritméticas.

Teniendo en cuenta estas razones, podemos desarrollar una solución más eficiente para su problema:

# solution of linear function between two points (2. reason)
def lin_itp(p1,p2,tv):
    """Get x coord of point on line

    Determine the x coord. of a point (x, target_value) on the line
    through the points p1, p2.

    Approach:
      - parametrize x, y between p1 and p2: 
          x = p1[0] + t*(p2[0]-p1[0])
          y = p1[1] + t*(p2[1]-p1[1])
      - set y = tv and resolve 2nd eqt for t
          t = (tv - p1[1]) / (p2[1] - p1[1])
      - replace t in 1st eqt with solution for t
          x = p1[0] + (tv - p1[1])*(p2[0] - p1[0])/(p2[1] - p1[1])
    """
    return float(p1[0] + (tv - p1[1])*(p2[0] - p1[0])/(p2[1] - p1[1])) 

# target value:
t_v = 1.0
for y in [0, 1]:
    arr_sd = arr.isel(y=y)
    # get index for the value closest to the target value (but smaller)
    s_udim = int(xr.where(arr_sd - t_v <=0, arr_sd, arr_sd.min()).argmax())
    # I'm explicitly defining the two points here
    ps_itp = arr_sd[s_udim:s_udim+2]
    p1, p2 = (ps_itp.x[0], ps_itp[0]), (ps_itp.x[1], ps_itp[1])
    print(lin_itp(p1,p2,t_v))

>>> 1.0344827586206897
>>> 1.3214285714285714

jojo
fuente
1
Cometiste un error cuando dices: "arr_sd = arr.isel (y = 0)" te refieres a "arr_sd = arr.isel (y = y)"
Hoogendijk
@Hoogendijk tienes razón, gracias. No vi eso. Espero que la respuesta haya sido útil. :)
jojo
Sí, fue útil, pero aún así decidí ver si podía mejorarlo y eliminar la necesidad de un bucle for.
Hoogendijk
0

El problema que tuve con la respuesta de jojo es que es difícil expandirlo en muchas dimensiones y mantener la estructura de rayos. Por lo tanto, decidí profundizar en esto. Utilicé algunas ideas del código de jojo para responder a continuación.

Hago dos matrices, una con la condición de que los valores son más pequeños de lo que busco y otra con la condición de que deben ser más grandes. Cambio el segundo en la dirección x por menos 1. Ahora los combino en una fórmula de interpolación lineal normal. Las dos matrices solo tienen valores superpuestos en el 'borde' de la condición. Si no se desplaza por -1, ningún valor se superpondría. En la línea final sumo sobre la dirección x y como todos los demás valores lo son NaN, extraigo el valor correcto y elimino la dirección x del DataArray en el proceso.

def interpolate_dimension_x(arr, target_value, step):
    M0 = arr.where(arr - target_value <= 0)
    M1 = arr.where(arr - target_value > 0).shift(x=-1)

    work_mat = M0.x + step * (target_value - M0) / (M1 - M0)

    return work_mat.sum(dim='x')
interpolate_dimension_x(arr, 1, 0.25)

>>> <xarray.DataArray (y: 2)>
array([1.034483, 1.321429])
Coordinates:
  * y        (y) int32 1 2

Tengo algunos inconvenientes con mi código. El código solo funciona si M0 y M1 encuentran un valor que cumpla la condición. De lo contrario, todos los valores de esa fila se establecerán en NaN. Para evitar problemas con M0, decidí que los valores de x comenzaran en 0 ya que mi valor objetivo siempre es mayor que 0. Para evitar problemas con M1, elijo mis valores de x lo suficientemente grandes para que sepa que mis valores están ahí . Naturalmente, estas no son soluciones ideales y pueden romper el código. Si tengo un poco más de experiencia con xarray y python, podría volver a escribir. En resumen, tengo los siguientes elementos que me gustaría resolver:

  • ¿Cómo extrapolar valores fuera del rango x? Actualmente solo estoy asegurando que mi rango x sea lo suficientemente grande como para que las respuestas caigan dentro de él.
  • ¿Cómo hacer que el código sea robusto para un tamaño de pasos variable?
  • Cómo hacer el código para que mi dimensión se pueda elegir dinámicamente (ahora solo funciona para 'x')
  • Cualquier optimización es apreciada.
Hoogendijk
fuente