5.3 数据插值¶

Markdown by 董慧杰

1. Scipy插值函数¶

a) griddata()¶

示例1:

In [1]:
import numpy as np
from scipy.interpolate import griddata # 导入“scipy.interpolate”模块中的“griddata()”函数
import matplotlib.pyplot as plt
def crdat(): #生成随机经纬度数据
    np.random.seed(10)
    y = np.hstack((np.random.rand(500)*-90,np.random.rand(500)*90)) #随机生成1000个纬度数据
    x = np.random.rand(1000)*360 #随机生成1000个经度数据
    m, n = (1,2)
    A = 100*np.sin(n*y/180*np.pi)**2 
    dat = A*np.sin(m*x/180*np.pi)
    return x,y,dat
if __name__=='__main__':
   x,y,dat = crdat() 
   lon0 = np.arange(0,360,2.5) #生成间隔为2.5的经度格点
   lat0 = np.linspace(-90,90,73) #生成间隔为2.5的纬度格点
   lon,lat = np.meshgrid(lon0,lat0) #生成2.5X2.5的经纬度二维网格
   #画出生成的随机散点图a
   fig = plt.figure(figsize=(11,8.5))
   ax = fig.add_subplot(2,2,1)
   pl1=ax.scatter(x,y,c=dat,cmap='jet',vmax=100,vmin=-100,s=5)
   ax.set_title('(a) Random data',fontsize=15,loc='left')
   plt.colorbar(pl1, ax=ax).set_label('Dat Value')
   levels = list(np.arange(-100,101,20))#设置等值线间隔
   levels.remove(0)
   for i,m in enumerate(['nearest','linear','cubic']): #循环三种插值方式,并绘图
        datg = griddata((x,y),dat,(lon,lat),method=m) # 利用griddata函数将dat(x,y)插值到(lon,lat)网格点
        ax = fig.add_subplot(2,2,2+i)
        ax.contour(lon,lat,datg,levels=levels,colors='k')
        ax.set_title('('+chr(ord('b')+i)+ ')Interp. by '+"'"+m+"'",fontsize=15,loc='left')
No description has been provided for this image
b) interpolate.RBFInterpolator()¶

示例:

In [2]:
import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate
def crdat():
    np.random.seed(10)
    y = np.hstack((np.random.rand(500)*-90, np.random.rand(500)*90))
    x = np.random.rand(1000)*360
    m, n = (1, 2)
    A = 100*np.sin(n*y/180*np.pi)**2
    dat = A*np.sin(m*x/180*np.pi)
    return x, y, dat
if __name__ == '__main__':
    x, y, dat = crdat()
    xy = np.vstack((y, x)).T  
    print(xy.shape)
    lon0 = np.arange(0, 360, 2.5)  # 经度范围
    lat0 = np.arange(-90, 91, 2.5)  # 纬度范围
    lon, lat = np.meshgrid(lon0, lat0)  # 创建网格    
    lonlat = np.vstack((lat.flatten(), lon.flatten())).T  # 经纬度扁平化,组合为二维数组  
    print(lon.shape,lat.shape,lonlat.shape) 
    methods = ['multiquadric', 'inverse_quadratic', 'inverse_multiquadric', 'gaussian', 
               'linear', 'cubic', 'quintic', 'thin_plate_spline']  # 插值方法    
    levels = list(np.arange(-100, 101, 20))  
    levels.remove(0)    
    fig = plt.figure(figsize=(8.5, 11))    
    for i, m in enumerate(methods):
        ax = fig.add_subplot(4, 2, i+1)   
        # 初始化一个 RBF 插值器对象rbf:xy(1000,2)表示1000个数据点,2表示是2维;dat表示数据点的值;
        # kernel表示径向基函数类型; epsilon 控制插值平滑程度,越小越平滑(插值扫描区大);反之亦然
        rbf = interpolate.RBFInterpolator(xy, dat, kernel=m, epsilon=0.05)
        # 将lonlat网格点传入已经初始化的插值器 rbf 中,计算出这些网格点上对应的插值结果datg
        #注意:lonlat为(73X144,2),表示有73*144格点,2维数组
        datg = rbf(lonlat)   
        # 将结果重新调整为2维(73,144)形状,便于绘图
        datg = datg.reshape(lon.shape)
        ax.contour(lon, lat, datg, levels=levels, colors='k')
        ax.set_title('(' + chr(ord('a') + i) + ') Interp. by "' + m + '"', loc='left', fontsize=12)
    plt.tight_layout()
    plt.show()
(1000, 2)
(73, 144) (73, 144) (10512, 2)
No description has been provided for this image

2. Metpy插值函数¶

示例:

In [3]:
import numpy as np
from metpy.interpolate import interpolate_to_grid
import matplotlib.pyplot as plt
def crdat():
    np.random.seed(5)
    y = np.hstack((np.random.rand(500)*-90, np.random.rand(500)*90))  
    x = np.random.rand(1000)*360  
    m, n = (1, 2)
    A = 100 * np.sin(n * y / 180 * np.pi)**2  
    dat = A * np.sin(m * x / 180 * np.pi)  
    return x, y, dat
if __name__ == '__main__':
    x, y, dat = crdat()  
    levels = list(np.arange(-100, 101, 20))
    levels.remove(0)
    fig = plt.figure(figsize=(15, 8.5))
    methods = ['barnes', 'cressman', 'natural_neighbor']
    for i, m in enumerate(methods):
        ax = fig.add_subplot(2, 3, i + 1)
        #将dat(x,y)插值到2.5的全球经纬度网格。hres表示格距;boundary_coords表示4个字典形式表示的边界点位置;
        #minimum_neighbors表示至少利用周边几个点,用于“cressman”和“barnes”插值,默认值为3
        #“gamma”仅用于“barnes”插值的平滑,值越大平滑程度越大
        #search_radius”,默认值为散点间平均距离的5倍
        lon, lat, datg = interpolate_to_grid(x, y, dat,
                           interp_type=m,hres=2.5,  # 网格分辨率,2.5表示2.5度一个网格
                           boundary_coords={'west': 0, 'east': 357.5, 'south': -90, 'north': 90},
                           minimum_neighbors=3, gamma=5.)
        cs = ax.contour(lon, lat, datg, levels=levels, colors='k')
        ax.set_title(f'({chr(ord("a")+i)}) Interp. by {m}', loc='left', fontsize=15)
    plt.tight_layout() 
    plt.show()
No description has been provided for this image

注意:虽然Metpy 的计算均需要单位,但此代码不为任何变量提供单位,将所有变量的插值当作无量纲数进行处理,与动力诊断不同,这样做不会影响插值结果。

3. 高分辨率降为低分辨率¶

当某一数据的空间分辨率较高,需将其与低分辨率的网格数据进行比较时,除了前面介绍的插值方法,还可用scipy.stats.binned_statistic_2d()函数

示例:

In [4]:
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import binned_statistic_2d #Scipy 的“scipy.stats.binned_statistic_2d()”函数
from matplotlib.colors import LinearSegmentedColormap
def crdat():
    lon = np.arange(0, 360, 2.5)
    lat = np.arange(-90, 91, 2.5)
    x, y = np.meshgrid(lon, lat)
    m, n = (1, 2)
    A = 100 * np.sin(n * y / 180 * np.pi) ** 2
    dat = A * np.sin(m * x / 180 * np.pi)
    return lon, lat, x, y, dat

if __name__ == '__main__':
    lon, lat, x, y, dat = crdat()
    levels = list(np.arange(-100, 101, 20))
    x = x.flatten()
    y = y.flatten()
    z = dat.flatten()
    levels.remove(0)  
    lonn = np.arange(-2.5, 362.5, 5) #分辨率为5度
    latn = np.arange(-92.5, 97.5, 5) #分辨率为5度

    # 对二维数据(经纬度x,y和数据值dat)进行分箱统计,统计方法为'mean',
    # latn 和 lonn 分别是纬度和经度的边界值,表示经纬度网格的边界点,即“箱子边界”
    datg = binned_statistic_2d(y, x, z, statistic='mean', bins=(latn, lonn)) 
    #提取出分享统计结果
    datg=datg[0]
    #用网格中心经纬度表示位置新格点的经纬度
    lat1 = (latn[1:] + latn[:-1]) / 2  # 计算纬度网格的中点
    lon1 = (lonn[1:] + lonn[:-1]) / 2  # 计算经度网格的中点
    print(datg.shape)
    levels = list(np.arange(-100,101,20))
    levels.remove(0)
    fig = plt.figure(figsize=(11, 8.5))
    from matplotlib.colors import LinearSegmentedColormap
    colors = ['gray', 'w', 'gray']
    n_bin = 21
    cmap = LinearSegmentedColormap.from_list('gwg', colors, N=n_bin)
    for i, (x, y, z) in enumerate(zip([lon, lon1], [lat, lat1], [dat, datg])):
        ax = fig.add_subplot(2, 2, i + 1)
        c = ax.pcolor(x, y, z, vmin=-100, vmax=100, cmap=cmap)
        ax.contour(x, y, z, levels=levels, colors='k')
        ax.text(5, 72, f'({chr(ord("a") + i)})', fontsize=20, backgroundcolor='w')
    plt.colorbar(c, ax=ax)
    plt.show()
(37, 72)
No description has been provided for this image

注意:Xarray 提供的“interp()”函数也可实现对数据进行任意分辨率的插值。“interp()”函数的输入输出都是DataArray 或Dataset 结构的数据,不可用于Numpy 的Ndarray 数据 (请参考https://docs.xarray.dev/en/stable/generated/xarray.DataArray.interp.html)

课堂练习

1.综合练习题:使用不同插值方法对一维、二维数据进行插值 假设你有以下一维和二维数据,要求通过不同的插值方法,估算某些未知点的值。 你将使用三种插值方法:interp1d()、griddata() 和 Rbf。其中,interp1d() 用于一维插值,griddata() 用于二维网格数据插值,而 Rbf 用于径向基函数插值。

一维数据:

x = np.array([0, 1, 2, 3, 4, 5])
y = np.sin(x)

二维数据:

x = np.array([0, 1, 2, 3])
y = np.array([0, 1, 2, 3])
X, Y = np.meshgrid(x, y)
Z = np.sin(X) + np.cos(Y)

RBF插值点:

points = np.array([[0, 0], [1, 0], [0, 1], [1, 1]])
values = np.array([0, 1, 2, 3])
参考答案
import numpy as np
import scipy.interpolate as interp
import matplotlib.pyplot as plt

# Step 1: 1D 插值
x = np.array([0, 1, 2, 3, 4, 5])
y = np.sin(x)

# 使用 interp1d 进行线性插值
interp_func_linear = interp.interp1d(x, y, kind='linear')
y_linear = interp_func_linear(2.5)
print(f"1D Linear interpolation at x=2.5: {y_linear}")

# 使用 interp1d 进行立方插值
interp_func_cubic = interp.interp1d(x, y, kind='cubic')
y_cubic = interp_func_cubic(2.5)
print(f"1D Cubic interpolation at x=2.5: {y_cubic}")

# Step 2: 2D 插值(griddata)
x = np.array([0, 1, 2, 3])
y = np.array([0, 1, 2, 3])
X, Y = np.meshgrid(x, y)
Z = np.sin(X) + np.cos(Y)

# 最近邻插值
point = (1.5, 1.5)
Z_nearest = interp.griddata((X.ravel(), Y.ravel()), Z.ravel(), point, method='nearest')
print(f"2D Nearest-neighbor interpolation at point (1.5, 1.5): {Z_nearest}")

# 线性插值
Z_linear = interp.griddata((X.ravel(), Y.ravel()), Z.ravel(), point, method='linear')
print(f"2D Linear interpolation at point (1.5, 1.5): {Z_linear}")

# Step 3: RBF 插值
points = np.array([[0, 0], [1, 0], [0, 1], [1, 1]])
values = np.array([0, 1, 2, 3])

# 创建 RBF 插值器
rbf = interp.Rbf(points[:, 0], points[:, 1], values, function='multiquadric')
z_rbf = rbf(0.5, 0.5)
print(f"RBF interpolation at point (0.5, 0.5): {z_rbf}")

# 可视化 2D 网格数据插值结果
# Plot the grid
X, Y = np.meshgrid(x, y)
plt.contourf(X, Y, Z, cmap='viridis')
plt.colorbar(label='Z values')
plt.title('2D Grid Data (sin(X) + cos(Y))')
plt.show()

2.请将下面程序补充完整,实现如下功能:利用 MetPy 的插值函数将下图所示的气象站点数据(包括经度、纬度和气温值),插值到一个规则的全球经纬度网格中,并展示插值后的气温分布。 image.png

将如下程序空白处补充完整:

import numpy as np
from metpy.interpolate import interpolate_to_grid
import matplotlib.pyplot as plt

# 生成虚拟气象数据(经度、纬度、气温)
def generate_weather_data():
    # 随机生成100个气象站点的经纬度数据
    np.random.seed(42)
    lon = np.random.rand(100) * 360  # 经度 [0, 360)
    lat = np.random.rand(100) * 180 - 90  # 纬度 [-90, 90)
    
    # 生成与经纬度相关的气温数据:假设气温随纬度变化
    temperature = 30 * np.sin(np.radians(lat)) + 20 * np.random.rand(100)  # 假设气温随纬度变化
    
    return lon, lat, temperature

# 主程序
if __name__ == '__main__':
    # 获取气象数据
    lon, lat, temperature = generate_weather_data()

    # 设置插值网格的分辨率(这里是2度的网格)
    hres = 2.0  # 网格分辨率为2.0度
    levels = np.arange(-30, 50, 5)  # 气温的等温线级别,范围从-30到50,步长为5

    # 创建图形
    fig = plt.figure(figsize=(10, 8))

    # 请将此处补充完整,实现使用 MetPy  函数进行插值插值到全球经纬度网格(2.0度分辨率)

    

    # 请将此处补充完成,实现绘制插值后的等温线图



    # 显示图形
    plt.tight_layout()
    plt.show()
参考答案
import numpy as np
from metpy.interpolate import interpolate_to_grid
import matplotlib.pyplot as plt

# 生成虚拟气象数据(经度、纬度、气温)
def generate_weather_data():
    # 随机生成100个气象站点的经纬度数据
    np.random.seed(42)
    lon = np.random.rand(100) * 360  # 经度 [0, 360)
    lat = np.random.rand(100) * 180 - 90  # 纬度 [-90, 90)
    
    # 生成与经纬度相关的气温数据:假设气温随纬度变化
    temperature = 30 * np.sin(np.radians(lat)) + 20 * np.random.rand(100)  # 假设气温随纬度变化
    
    return lon, lat, temperature

# 主程序
if __name__ == '__main__':
    # 获取气象数据
    lon, lat, temperature = generate_weather_data()

    # 设置插值网格的分辨率(这里是2度的网格)
    hres = 2.0  # 网格分辨率为2.0度
    levels = np.arange(-30, 50, 5)  # 气温的等温线级别,范围从-30到50,步长为5

    # 创建图形
    fig = plt.figure(figsize=(15, 8))


    # 使用 MetPy 的 interpolate_to_grid 函数进行插值,插值到全球经纬度网格(2.0度分辨率)
    lon_grid, lat_grid, temp_grid = interpolate_to_grid(lon, lat, temperature,
                                                         interp_type='barnes',  # 使用Barnes插值方法
                                                         hres=hres,  # 设置网格分辨率
                                                         boundary_coords={'west': 0, 'east': 360, 'south': -90, 'north': 90},
                                                         minimum_neighbors=3, gamma=5.0)

    # 绘制插值后的等温线图
    ax2 = fig.add_subplot(122)
    cs = ax2.contourf(lon_grid, lat_grid, temp_grid, levels=levels, cmap='coolwarm')
    cbar2 = plt.colorbar(cs, ax=ax2)
    cbar2.set_label('Temperature (°C)')
    ax2.set_title('Interpolated Data (After Interpolation)', fontsize=16)
    ax2.set_xlabel('Longitude')
    ax2.set_ylabel('Latitude')

    # 显示图形
    plt.tight_layout()
    plt.show()
    ```