Xarray - 计算
本文主要介绍 Xarray 相关的计算,包括:基础计算、分组计算、重采样计算、滑动计算以及多项式回归
1 | import numpy as np |
基础计算
基本计算
加减乘除 和 numpy 函数
xarray 的 DataArray 和 DataSet 对象可以无缝地使用计算操作符(如+, -, *, /)和 numpy 数组函数。
下面我们将海温数据的 摄氏温度 改写为 开尔文温度 为例说明上述问题。
可以发现再进行计算操作后,数据集的维度和坐标都没有发生变化。
1 | sst_kelvin = ds.sst+270 |
下面我们来尝试一下用更为复杂的函数进行计算。
1 | np.log(sst_kelvin ** 2).coords |
apply_ufunc 函数的使用
上面可以调用 np.log(ds) 并使其在 xarray 中“正常工作”是非常幸运的,但是 并非所有的库都能直接在 xarray 中正常工作 。
xr.apply_ufunc(function, vars)
1 | xr.apply_ufunc(np.log, sst_kelvin**2).coords |
apply_ufunc 函数功能强大,有很多可选参数以便进行复杂操作
更多可查阅:http://xarray.pydata.org/en/latest/generated/xarray.apply_ufunc.html
降维计算: mean, sum, min, max, std, ...
1 | sst = ds.sst |
根据 axis 降维
xarray.mean( axis )
在numpy中,如果要进行对某一维度以某种方式(譬如取最大、小值,平均值)进行降维,可通过指定axis参数实现。比如:
1 | arr = np.arange(20).reshape(5,4) |
对于xarray对象,如sst:如果要对时间方向上以平均的方法进行降维,与numpy中的方法类似,可写为
1 | # 对第0维度(维度time)以平均的方法进行降维 |
1 | sst.mean(axis = 0).plot() |
那么如果要对经纬方向上同时进行降维应当如何写呢?只需要将同时降维的维度号用小括号包含在内即可。
1 | # 对第1、2维度(维度lat、lon)以平均的方法进行降维 |
通过对经纬方向的降维,我们现在得到了一个时间序列,这个时间序列描述了全球平均表面海温(SST)的变化。
1 | sst.mean(axis=(1, 2)).plot() |
可以注意到每一年中全球平均海温存在明显的季节振荡(变化),这个季节振荡的去除可利用 resample 方法得到,后续将会详细介绍这个功能的使用方法。
根据 dim 降维
xarray.mean( dim )
除了使用类似于numpy中的对数轴的降维方法,也可以使用xarray中独特、便捷的方法。这种方法无需记忆数轴所对应的维度名称。
若要求解多年SST的平均场,可以通过对时间维取平均降维实现。这可以定义dim这个参数实现。
1 | sst.mean(dim="time").coords |
当然除了mean方法,其他的numpy标准降维方法也是可以使用的,如min(取最小值), max(取最大值), sum(求和), std(求解标准差)等。
控制缺失值 skipna
上述这些操作会自动跳过缺失值,这对于某些数据的处理是非常有利的,比如SST仅在海洋上有值,但陆地上没有值,利用.mean进行计算时会自动忽略缺失值。
为进一步说明,下面举一个例子
1 | xr.DataArray([1, 2, np.nan, 3]).mean() |
如果要考虑缺失值的计算(虽然通常没有此类需求),则需在括号中添加参数skipna=False.
1 | xr.DataArray([1, 2, np.nan, 3]).mean(skipna=False) |
练习 - 1
在经度和纬度上均取变量sst的平均值。绘制一个简单的时间序列图:
1 | # 在这里写你的代码 |
广播
广播(Broadcasting)是指具有不同维度数组的对齐。
基于数组形状的 Numpy 广播规则有时可能难以理解和记住,
Xarray相较Numpy提供了按维度名称(而非数组形状)进行广播的方法,免去了记忆的困难。
为说明广播在计算中的作用,下面将创建一个与纬度有关的权重因子。这个权重因子常用于描述规则经纬网格上数据的面积权重系数。
下述的代码创建了一个权重系数,ds.lat可以获取ds的纬度数组(离散角度值)。因为后续的np.cos()只能接受弧度进行计算,这儿利用np.deg2rad函数将角度转换为弧度,再利用np.cos()计算余弦值。
总而言之,本质上权重系数即为各个纬度上的余弦值。
1 | weights = np.cos(np.deg2rad(ds.lat)) |
此时,权重因子仅有一个维度。如果我们将这个权重因子与SST相乘会发生什么呢?
应当注意这个相乘不是矩阵相乘,而是对应位置的元素彼此相乘。
1 | (ds.sst * weights).coords |
如果要广播的数组共享一个维度名称,但坐标维度不同。
在这种情况下,广播将会使用xarray的默认对齐设置(即取两者变量索引的交集)进行对齐(包括使用NaN填充缺失值)。
如果这不是想要的结果,最好在广播之前显式调用 align 并指定参数使得两个数组得以对齐。
赋权降维
DataArray.weighted( weights ) , Dataset.weighted( weights )
xarray目前支持DataArray和Dataset对象,对于这两个对象的赋权降维可采用 DataArray.weighted() 和 Dataset.weighted() 方法。
目前支持带权重的以平均(mean)和求和(sum)方法降维。
为说明赋权降维,下面先创建一个关于降水数据的DataArray和一个权重的DataArray.
1 | prec = xr.DataArray( |
1 | weights = xr.DataArray( |
接下来对prec以weights为权重创建权重对象
1 | weighted_prec = prec.weighted(weights) |
计算加权和:50x31 + 10x28 + 0.9x31
1 | weighted_prec.sum() |
等价于:
1 | (prec*weights).sum() |
计算加权平均:( 50x31 + 10x28 + 0.9x31 ) / (31+28+31)
1 | weighted_prec.mean(dim = "month") |
等价于:
1 | (prec*weights).sum() / weights.sum() |
源数据存在 缺失值和某些特殊计算的情况
如果原数据存在缺失值np.nan时,赋权降维将得到正确的结果。
1 | data = xr.DataArray([np.nan, 2, 4]) |
而利用 (data * weights).sum() / weights.sum() 公式进行计算,得到的结果是不正确的
不正确的原因在于某一点没有值,但其权重却参与了计算,会使得整体值减小。
1 | (data*weights).sum()/weights.sum() |
实际案例:平均SST的计算
1 | # 数据导入 |
尝试一下以下方法对带权重的空间平均SST进行计算,就如下述代码所示
没有考虑缺厕值问题,导致结果偏小
1 | sst_mean = ((ds.sst * weights).sum(dim="lat") / weights.sum(dim="lat")).mean(dim="lon") |
就一般而言,多维数组上的赋权降维是复杂的。为了使操作更简单,xarray提供了一种赋权降维的机制。
这个机制通过创建一个特殊的中间DataArrayWeighted对象来实现这个目的,从而能够对数组使用各类的降维操作。
下述代码对DataArray对象给予了.weighted()方法,括号内填入了权重。
运行的结果创建了一个关于DataArray的权重对象(DataArrayWeighted),并且这个权重是伴随着维度lat(纬度)变化的。
接着对DataArray这个权重对象同时在在维度lon, lat上取平均。这时我们便获得了一个正确的全球平均SST.
1 | sst_weighted = ds.sst.weighted(weights) |
Groupby 分组
Groupby是Pandas包中比较重要的一种聚合方法
xarray借鉴了Pandas包中groupby功能,在xarray的DataArrays和Datasets上实现分割(split)、应用(apply) 和 组合(combine)
为提供一个实际案例,下面考虑某个格点上的SST时间序列。
1 | ds.sst.sel(lon=300, lat=50).plot() |
根据时间分组
创建时间变量的 待索引对象
1 | ds.time.dt |
利用.dt.month提取各个时间的月份数据
1 | ds.time.dt.month.shape #提取月份 |
利用.dt.year提取各个时间的年份数据
1 | ds.time.dt.year.shape # 提取年 |
根据时间索引,创建 groupby对象
ds.groupby( ds.time.dt.month )
ds.groupby( "time.month" )
类似于Pandas包中的groupby的思想,我们利用ds.groupby()函数将月份作为键(唯一值)来对原数据进行分离。
本质是即把各年的某个月的数据放到了一组。
1 | gb = ds.groupby(ds.time.dt.month) # 根据月份进行分组 |
如果说时间参数变量已经包含在原数据集中(这也是通常出现的情况),可以使用xarray中更为简洁的方法,即"time.month". 这与ds.time.dt.month实现的操作是一致的。
1 | gb = ds.groupby("time.month") |
1 | ds.groupby("time.year") |
1 | ds.groupby("time.season") |
迭代访问 groupby对象
经过上面的分组操作后,原数据已经拆分成12个组(groups),放置在变量gb中。
对于这12个组,可通过循环进行遍历。迭代器返回各个组的键(组名)和值(与该组相对应的实际数据集)。
1 | for group_name, group_ds in gb: |
逐个访问 groupby对象
除了可以使用循环的方法对各个分组进行遍历,也可直接使用python中 列表 的访问方法访问各个分组。
通过对list对象的第一个分组的访问可以获得分组名称和对应的xarray数据.
先来尝试访问一下第一个(python中的第一个元素的索引由0开始)分组的信息
1 | list(gb)[0] #访问第一个分组 |
1 | list(gb)[0][0] # 第一个分组的名称 |
1 | list(gb)[0][1] # 第一个分组的数据 |
我们可以对上述的三步简写为
1 | list(gb)[0][1] # 等效为list_first_group[1] |
查找各个分组中对应元素在原始数据中键的位置
groupby.groups
以 字典 形式返回各个分组(在这儿是month)中的元素在原分组坐标中(在这儿是time)的位置
1 | gb.groups.keys() |
上述说明了gb将数据分为了12组,每个组的名字分别为1、2……12,其中名字为1的组(即一月)中包含了时间序列中第0个、第12个……
时间序列中第0个可理解为ds.isel(time = 0),同理第12个可理解为ds.isel(time = 12).
分箱(Binning)
分箱(Binning),顾名思义如果要对数据进行筛选,可以指定分箱规则对数据进行筛选。
分箱与分割有所不同。分箱针对的对象是对数据指定规则,而分割针对维度坐标指定规则。
xarray中的分箱方法基于pandas.cut()实现
以 筛选出数据第0时刻 海温低于零摄氏度的格点位置和相应的海温数据 为例
1 | ds_0 = ds.isel(time = [0]) |
根据分箱,创建 groupby对象
ds.groupby_bins( data_var, bin, labels )
1 | sst_bin = [-10, 0, 10, 20, 30] # sst_bin是声明的分箱数组的间隔段。 |
迭代访问 groupby 对象
1 | for group_name, group_ds in sst_gb: |
逐个访问 groupby 对象
1 | list(sst_gb)[0][1] |
查看分箱名称及其数据在原始数据对应的位置
1 | sst_gb_bin = gb.groups |
Groupby 的应用
累计:合并分组成为完整的一组
转换:对各个分组分别给予计算
对于应用步骤而言,使用的方法是 .map(映射).
累计(“降维”)
groupby.map( function )
对于累计方法,以求解多年各月sst平均空间场为例说明累计的实现方法。
.map可接受一个 函数 作为其参数。下面我们来传递一个求平均的参数np.mean(此处的函数无括号):
1 | gb = ds.groupby("time.month") #导入分组数据 |
与Pandas包一样,xarray的groupby对象也内置许多的累计(aggregation)操作(如mean, min, max, std等).
这些内置操作能够简化上述常用操作代码的书写。
1 | # 这与上述的自定义函数的功能相同 |
对于分组方案中的每一个分组都作用.mean降维方法,然后通过自动组合的方式得到最后的统一的数据集monthsst。
这个数据集可以通过month索引多年各个月份的数据。
接下来试着做一下数据的累计操作,并绘图。
1 | # 北大西洋特定格点的多年月平均气候序列 |
1 | # 多年纬度月平均气候场 |
1 | # 多年1月与7月平均气候场之间的差异 |
转换
下面需从数据集中删除气候平均,从而得到变量随气候平均态变化的残差。一般将这个残差称为距平。
对转换(Transformations)操作而言,消除数据的气候平均是一个很好的例子。
转换操作对分组的对象进行操作,但不改变原数据的维度尺寸。
xarray 通过使用Groupby 算法使这些类型的转换变得容易。下面给出了计算去除月份温度差异的海温月数据。
1 | # 对 12 组中的对应组的海温数据(这个组内的每一天的海温数据)减去平均的海温数据 |
也可以简写为下面这种形式
1 | # 对 12 组中的对应组的海温数据(这个组内的每一天的海温数据)减去平均的海温数据 |
当经过上述去除季节性周期的影响后,便很容易发现气候变率的信号。
1 | # 北大西洋单点的时间序列 |
1 | # 2018 年 1 月 1 日与 1960 年 1 月 1 日之间 SST 之间的差异 |
Resample重采样
xarray 中的Resample(重采样)的处理方法与 Pandas 包几乎相同。就本质而言,Resample 也是一个分割数据的操作。
它与分割操作的基本语法类似。应当注意,对于 Resample 操作而言,其作用对象必须是时间维度。
ds.resample( time="freq" )
为说明 Resample 的用法,下面给出一个例子计算逐五年的平均值曲线。
对于 Resample 操作而言,与 Groupby 操作非常类似,首先也创建了一个DatasetResample对象。
.resample(time="5Y")是对时间进行重采样进行设置,维度为time,设置的时间间隔为 5 年。
应当指出这里的时间间隔写法与之前pd.date_range函数中的freq的时间间隔的关键词是一致的。
1 | resample_obj = ds_anom.resample(time="5Y") |
然后对这些分割好的 Resample 对象进行取平均,以便获得每一个分组好的 Resample 对象中的平均值。
1 | ds_anom_resample = resample_obj.mean(dim="time") |
为了说明进行重采样后的效果,下面来看一下(50°N, 60°E)的海温变化情况
1 | # 原始海温变化的时间序列 |
Coarsen(粗化)
coarsen(粗化)所做的事情与resample(重采样)类似。
resample仅可用于时间坐标,但coarsen对逻辑坐标和时间坐标均可使用。
同时coarsen不仅能作用一个维度,还可作用多个维度。粗化方法通常用来降低xarray对象的分辨率。
1 | ds_anom.sst.isel(time = 0).coords |
采用粗化的方法将其重采样至 5×10 的分辨率
ds.coarsen( dim1=size1, dim2=size2, ..., boundary )
1 | ds_anom.sst.coarsen( |
boundary 变量规定了如何处理数组尺寸与窗口尺寸之间不是倍数的情况。
若为trim,多余的不能整除的数据将被直接剔除;若为pad,则将其填充为 nan.
1 | ds_anom.sst.coarsen( |
对时间维的重采样,resample和coarsen均可有类似的结果。
1 | ds_anom_ = ds_anom.sst.sel(lon=300, lat=50) |
最后对比一下逐12个日期平均和12个月滑动平均的曲线
1 | # 5年平均序列 coarsen |
Rolling 滑动平均
Rolling 方法也与pandas 包中的类似,但是稍有不同的是,它可适用于任意维度。如果将其作用于时间维度,也可称之为滑动平均。
ds.rolling( dim, center )
time=12指定了对维度time以 12 个月为周期(月数据)变动时间窗,
center=True表明以当前窗的两侧筛选数据,否则是以当前窗的前 12 个月作为筛选目标(包括本身)
1 | ds_anom_rolling = ds_anom.rolling(time=12*5, center=True).mean() |
1 | # 原始海温变化的时间序列 |
为了更好的说明 Rolling 的作用,下面举一个简单的例子说明其功能
1 | da = xr.DataArray( |
1 | da.rolling(time=5, center=True).mean() |
若时间窗为偶数值,那么对应中心位置将会在平均位置偏右侧
1 | da.rolling(time=4, center=True).mean() |
若不指定参数center=True,则采用从当前元素往前筛选的方法
1 | da.rolling(time=5).mean() |
当然和 grouby 对象类似,也可用 list 来访问每一个滑动窗
1 | rolling_obj = da.rolling(time=5) |
线性多项式回归
.polyfit方法实现了回归功能,
第一个参数"time"指定拟合坐标为time,
第二数字参数指定为一元线性回归,
full = True代表回归方法不仅要返回拟合系数(一元回归即斜率和截距)还应当返回残差,矩阵秩和奇异值。
ds.polyfit( dim, degree, full )
1 | ds_poly = ds_anom.sst.polyfit("time", 1, full=True) |
线性趋势(斜率)
1 | ds_poly.polyfit_coefficients.isel(degree=0).plot() |
截距空间分布
1 | ds_poly.polyfit_coefficients.isel(degree=1).plot() |