xarray指南:插值

目录

本文翻译自 xarray 官方文档 Interpolating data 的部分内容。

首先导入需要使用到的库。

import numpy as np
import pandas as pd
import xarray as xr
import matplotlib.pyplot as plt

xarray 提供了灵活的插值程序,与索引有相似的接口。

注意:interp 需要安装 scipy

标量和一维插值

插值 DataArray 和标签索引 DataArray 的工作方式类似。

da = xr.DataArray(
    np.sin(0.3 * np.arange(12).reshape(4, 3)),
    [
        ("time", np.arange(4)),
        ("space", [0.1, 0.2, 0.3]),
    ]
)
da
<xarray.DataArray (time: 4, space: 3)>
array([[ 0.        ,  0.29552021,  0.56464247],
       [ 0.78332691,  0.93203909,  0.99749499],
       [ 0.97384763,  0.86320937,  0.67546318],
       [ 0.42737988,  0.14112001, -0.15774569]])
Coordinates:
  * time     (time) int64 0 1 2 3
  * space    (space) float64 0.1 0.2 0.3

标签查找

da.sel(time=3)
<xarray.DataArray (space: 3)>
array([ 0.42737988,  0.14112001, -0.15774569])
Coordinates:
    time     int64 3
  * space    (space) float64 0.1 0.2 0.3

插值

da.interp(time=2.5)
<xarray.DataArray (space: 3)>
array([0.70061376, 0.50216469, 0.25885874])
Coordinates:
  * space    (space) float64 0.1 0.2 0.3
    time     float64 2.5

注意:未设置 method 参数的标签查找仅支持已存在的坐标值。

与索引类似,interp 也接受数组形式的参数,返回数组形式的插值结果

标签查找

da.sel(time=[2, 3])
<xarray.DataArray (time: 2, space: 3)>
array([[ 0.97384763,  0.86320937,  0.67546318],
       [ 0.42737988,  0.14112001, -0.15774569]])
Coordinates:
  * time     (time) int64 2 3
  * space    (space) float64 0.1 0.2 0.3

插值

da.interp(time=[2.5, 3.5])
<xarray.DataArray (time: 2, space: 3)>
array([[0.70061376, 0.50216469, 0.25885874],
       [       nan,        nan,        nan]])
Coordinates:
  * space    (space) float64 0.1 0.2 0.3
  * time     (time) float64 2.5 3.5

对带有 numpy.datetime64 坐标的数据插值,可以传递字符串

da_dt64 = xr.DataArray(
    [1, 3],
    [
        ("time", pd.date_range("1/1/2000", "1/3/2000", periods=2))
    ]
)
da_dt64
<xarray.DataArray (time: 2)>
array([1, 3])
Coordinates:
  * time     (time) datetime64[ns] 2000-01-01 2000-01-03
da_dt64.interp(
    time="2000-01-02"
)
<xarray.DataArray ()>
array(2.)
Coordinates:
    time     datetime64[ns] 2000-01-02

通过指定所需的时间段,可以将插值后的数据合并到原始 DataArray 中。

da_dt64.interp(
    time=pd.date_range(
        "1/1/2000",
        "1/3/2000",
        periods=3
    )
)
<xarray.DataArray (time: 3)>
array([1., 2., 3.])
Coordinates:
  * time     (time) datetime64[ns] 2000-01-01 2000-01-02 2000-01-03

也可以对使用 CFTimeIndex 索引的数据进行插值。

注意

当前,我们的插值仅适用于常规网格。 因此,与 sel() 类似,只有沿维度的一维坐标才可以用于插值的原始坐标。

多维插值

与 sel() 类似,interp() 接收多个坐标。 这种情况下,将进行多维插值。

标签查找

da.sel(
    time=2, 
    space=0.1,
)
<xarray.DataArray ()>
array(0.97384763)
Coordinates:
    time     int64 2
    space    float64 0.1

插值

da.interp(
    time=2.5, 
    space=0.15
)
<xarray.DataArray ()>
array(0.60138922)
Coordinates:
    time     float64 2.5
    space    float64 0.15

同样接收数组形式的参数

标签查找

da.sel(
    time=[2, 3],
    space=[0.1, 0.2]
)
<xarray.DataArray (time: 2, space: 2)>
array([[0.97384763, 0.86320937],
       [0.42737988, 0.14112001]])
Coordinates:
  * time     (time) int64 2 3
  * space    (space) float64 0.1 0.2

插值

da.interp(
    time=[1.5, 2.5],
    space=[0.15, 0.25],
)
<xarray.DataArray (time: 2, space: 2)>
array([[0.88810575, 0.86705165],
       [0.60138922, 0.38051172]])
Coordinates:
  * time     (time) float64 1.5 2.5
  * space    (space) float64 0.15 0.25

interp_like() 方法是一个有用的快捷方式。 此方法将 xarray 对象插值到另一个 xarray 对象的坐标上。 例如,如果我们想要计算两个坐标略有不同的 DataArray (da 和 other)之间的差异。

other = xr.DataArray(
    np.sin(0.4 * np.arange(9).reshape(3, 3)),
    [
        ("time", [0.9, 1.9, 2.9]),
        ("space", [0.15, 0.25, 0.35]),
    ]
)
other
<xarray.DataArray (time: 3, space: 3)>
array([[ 0.        ,  0.38941834,  0.71735609],
       [ 0.93203909,  0.9995736 ,  0.90929743],
       [ 0.67546318,  0.33498815, -0.05837414]])
Coordinates:
  * time     (time) float64 0.9 1.9 2.9
  * space    (space) float64 0.15 0.25 0.35

最好先对 da 进行插值,使其保持在 other 坐标上,然后再减去它。 interp_like() 可用于这种情况。

# 沿 oeter 的坐标插值 da
interpolated = da.interp_like(other)
interpolated
<xarray.DataArray (time: 3, space: 3)>
array([[0.78669071, 0.91129847,        nan],
       [0.91244395, 0.78887935,        nan],
       [0.3476778 , 0.06945207,        nan]])
Coordinates:
  * time     (time) float64 0.9 1.9 2.9
  * space    (space) float64 0.15 0.25 0.35

现在可以安全地计算 other - interpolated 差异了。

other - interpolated
<xarray.DataArray (time: 3, space: 3)>
array([[-0.78669071, -0.52188012,         nan],
       [ 0.01959514,  0.21069425,         nan],
       [ 0.32778538,  0.26553608,         nan]])
Coordinates:
  * time     (time) float64 0.9 1.9 2.9
  * space    (space) float64 0.15 0.25 0.35

插值方法

我们使用 scipy.interpolate.interp1d 对一维数据插值,使用 scipy.interpolate.interpn() 对多维数据插值。

可以通过可选的 method 参数指定插值方法。

da = xr.DataArray(
    np.sin(np.linspace(0, 2 * np.pi, 10)),
    dims="x",
    coords={
        "x": np.linspace(0, 1, 10),
    }
)
da
<xarray.DataArray (x: 10)>
array([ 0.00000000e+00,  6.42787610e-01,  9.84807753e-01,  8.66025404e-01,
        3.42020143e-01, -3.42020143e-01, -8.66025404e-01, -9.84807753e-01,
       -6.42787610e-01, -2.44929360e-16])
Coordinates:
  * x        (x) float64 0.0 0.1111 0.2222 0.3333 ... 0.6667 0.7778 0.8889 1.0
da.plot.line('o', label="original")

da.interp(
    x=np.linspace(0, 1, 100)
).plot.line(
    label='linear (default)'
)

da.interp(
    x=np.linspace(0, 1, 100), 
    method='cubic'
).plot.line(
    label='cubic'
)

plt.legend()

附加的关键词参数 kwargs 会传递给 scipy 函数。

原始坐标范围外的点填充 0

da.interp(
    x=np.linspace(-0.5, 1.5, 10), 
    kwargs={
        'fill_value': 0.0
    }
)
<xarray.DataArray (x: 10)>
array([ 0.        ,  0.        ,  0.        ,  0.81379768,  0.60402277,
       -0.60402277, -0.81379768,  0.        ,  0.        ,  0.        ])
Coordinates:
  * x        (x) float64 -0.5 -0.2778 -0.05556 0.1667 ... 0.8333 1.056 1.278 1.5

外推

da.interp(
    x=np.linspace(-0.5, 1.5, 10), 
    kwargs={
        'fill_value': 'extrapolate'
    }
)
<xarray.DataArray (x: 10)>
array([-2.89254424, -1.60696902, -0.3213938 ,  0.81379768,  0.60402277,
       -0.60402277, -0.81379768,  0.3213938 ,  1.60696902,  2.89254424])
Coordinates:
  * x        (x) float64 -0.5 -0.2778 -0.05556 0.1667 ... 0.8333 1.056 1.278 1.5

高级插值

interp() 与 sel() 类似,接收 DataArray,提供更高级的插值。 根据传递给 interp() 的新坐标维度,确定返回结果的维度。

例如,如果要沿特定维度插值二维数组,如下图所示,则可以传递两个具有共同维度的一维 DataArray 作为新坐标。

例如:

da = xr.DataArray(
    np.sin(0.3 * np.arange(20).reshape(5, 4)),
    [
        ('x', np.arange(5)),
        ('y', [0.1, 0.2, 0.3, 0.4])
    ]
)
da
<xarray.DataArray (x: 5, y: 4)>
array([[ 0.        ,  0.29552021,  0.56464247,  0.78332691],
       [ 0.93203909,  0.99749499,  0.97384763,  0.86320937],
       [ 0.67546318,  0.42737988,  0.14112001, -0.15774569],
       [-0.44252044, -0.68776616, -0.87157577, -0.97753012],
       [-0.99616461, -0.92581468, -0.77276449, -0.55068554]])
Coordinates:
  * x        (x) int64 0 1 2 3 4
  * y        (y) float64 0.1 0.2 0.3 0.4

高级索引

x = xr.DataArray([0, 2, 4], dims='z')
y = xr.DataArray([0.1, 0.2, 0.3], dims='z')

da.sel(x=x, y=y)
<xarray.DataArray (z: 3)>
array([ 0.        ,  0.42737988, -0.77276449])
Coordinates:
    x        (z) int64 0 2 4
    y        (z) float64 0.1 0.2 0.3
Dimensions without coordinates: z

高级插值

x = xr.DataArray([0.5, 1.5, 2.5], dims='z')
y = xr.DataArray([0.15, 0.25, 0.35], dims='z')
da.interp(x=x, y=y)
<xarray.DataArray (z: 3)>
array([ 0.55626357,  0.63496063, -0.46643289])
Coordinates:
    x        (z) float64 0.5 1.5 2.5
    y        (z) float64 0.15 0.25 0.35
Dimensions without coordinates: z

原始坐标 (x, y) = ((0.5, 0.15), (1.5, 0.25), (2.5, 0.35)) 的值通过二维插值获得,并映射到新的维度 z。

如果想要为新的维度 z 添加坐标,可以为 DataArray 提供坐标。

x = xr.DataArray(
    [0.5, 1.5, 2.5], 
    dims='z', 
    coords={
        'z': ['a', 'b','c']
    }
)

y = xr.DataArray(
    [0.15, 0.25, 0.35], 
    dims='z',
    coords={
        'z': ['a', 'b','c']
    }
)

da.interp(x=x, y=y)
<xarray.DataArray (z: 3)>
array([ 0.55626357,  0.63496063, -0.46643289])
Coordinates:
    x        (z) float64 0.5 1.5 2.5
    y        (z) float64 0.15 0.25 0.35
  * z        (z) <U1 'a' 'b' 'c'

插值带 NaN 值的数组

我们的 interp() 处理带 NaN 值的数组时使用与 scipy.interpolate.interp1d 和 scipy.interpolate.interpn 一样的方法。linear 和 nearest 方法返回包含 NaN 的数组,而其他方法返回所有值都为 NaN 的数组,例如 cubic 和 quadratic。

da = xr.DataArray(
    [0, 2, np.nan, 3, 3.25],
    dims="x",
    coords={"x": range(5)}
)
da
<xarray.DataArray (x: 5)>
array([0.  , 2.  ,  nan, 3.  , 3.25])
Coordinates:
  * x        (x) int64 0 1 2 3 4
da.interp(
    x=[0.5, 1.5, 2.5]
)
<xarray.DataArray (x: 3)>
array([ 1., nan, nan])
Coordinates:
  * x        (x) float64 0.5 1.5 2.5
da.interp(
    x=[0.5, 1.5, 2.5],
    method="cubic",
)
<xarray.DataArray (x: 3)>
array([nan, nan, nan])
Coordinates:
  * x        (x) float64 0.5 1.5 2.5

为了避免这种情况,您可以通过 dropna() 删除 NaN,然后进行插值

dropped = da.dropna("x")
dropped
<xarray.DataArray (x: 4)>
array([0.  , 2.  , 3.  , 3.25])
Coordinates:
  * x        (x) int64 0 1 3 4
dropped.interp(
    x=[0.5, 1.5, 2.5],
    method="cubic"
)
<xarray.DataArray (x: 3)>
array([1.19010417, 2.5078125 , 2.9296875 ])
Coordinates:
  * x        (x) float64 0.5 1.5 2.5

如果 NaN 在多维数组中随机分布,使用 dropna() 删掉所有包含超过一个 NaN 值的列可能会丢失大量信息。 在这种情况下,可以使用 interpolate_na() 填充 NaN,与 pandas.Series.interpolate() 类似。

filled = da.interpolate_na(dim='x')
filled
<xarray.DataArray (x: 5)>
array([0.  , 2.  , 2.5 , 3.  , 3.25])
Coordinates:
  * x        (x) int64 0 1 2 3 4
filled.interp(
    x=[0.5, 1.5, 2.5], 
    method='cubic',
)
<xarray.DataArray (x: 3)>
array([1.30859375, 2.31640625, 2.73828125])
Coordinates:
  * x        (x) float64 0.5 1.5 2.5

关于 interpolate_na() 的更多细节,请查看 Missing values。

示例

让我们看一下 interp() 在实际数据上的效果。

ds = xr.open_dataset('air_temperature.nc').isel(time=0)
ds
<xarray.Dataset>
Dimensions:  (lat: 25, lon: 53)
Coordinates:
  * lat      (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0
  * lon      (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0
    time     datetime64[ns] 2013-01-01
Data variables:
    air      (lat, lon) float32 ...
Attributes:
    Conventions:  COARDS
    title:        4x daily NMC reanalysis (1948)
    description:  Data is from NMC initialized reanalysis\n(4x/day).  These a...
    platform:     Model
    references:   http://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanaly...
fig, axes = plt.subplots(
    ncols=2, 
    figsize=(10, 4)
)

# 原始数据
ds.air.plot(ax=axes[0])
axes[0].set_title('Raw data')

# 插值数据
new_lon = np.linspace(
    ds.lon[0],
    ds.lon[-1], 
    ds.dims['lon'] * 4
)
new_lat = np.linspace(
    ds.lat[0], 
    ds.lat[-1], 
    ds.dims['lat'] * 4
)
dsi = ds.interp(
    lat=new_lat, 
    lon=new_lon
)

dsi.air.plot(ax=axes[1])
axes[1].set_title('Interpolated data')
plt.show()

我们的高级插值可用于将数据重新映射到新坐标 考虑二维平面上的新坐标 x 和 z。 重新映射如下面示例所示

# 新坐标
x = np.linspace(240, 300, 100)
z = np.linspace(20, 70, 100)

# 新旧坐标之间的关系
lat = xr.DataArray(
    z, 
    dims=['z'], 
    coords={'z': z}
)
lon = xr.DataArray(
    (x[:, np.newaxis]-270)/np.cos(z*np.pi/180)+270,
    dims=['x', 'z'], 
    coords={'x': x, 'z': z}
)

fig, axes = plt.subplots(ncols=2, figsize=(10, 4))
ds.air.plot(ax=axes[0])

# 在原始坐标中绘制新坐标
for idx in [0, 33, 66, 99]:
     axes[0].plot(
         lon.isel(x=idx), 
         lat, 
         '--k'
     )
for idx in [0, 33, 66, 99]:
     axes[0].plot(
         *xr.broadcast(
            lon.isel(z=idx), 
            lat.isel(z=idx)
         ), 
         '--k'
     )
        
axes[0].set_title('Raw data')

dsi = ds.interp(lon=lon, lat=lat)
dsi.air.plot(ax=axes[1])
axes[1].set_title('Remapped data')

plt.show()

参考

http://xarray.pydata.org/en/stable/interpolation.html