复制代码
在海洋与气象科学领域,编程工具的掌握对于研究生来说至关重要。在这个领域中,NCL、Matlab和Python是最为广泛使用的工具。
NCL早在气象领域应用广泛,提供了大量便捷的函数,但由于其已经停止更新,并且需要基于Linux的平台进行安装和使用,应用受限。Matlab则因其高效的矩阵计算能力而被广泛使用,但其使用受限于版权问题。
近年来,随着神经网络和深度学习模型在海洋气象研究中的广泛应用,Python逐渐成为科研高校和研究机构的首选工具。
相较之下,Python作为一种开源免费的工具,具有广泛的吸引力。通过提供各种不同的包,Python能够方便地读取海洋和气象数据。其中,`xarray` 是我特别推荐的。
目前,`xarray` 支持多种文件格式,并且能够与其他包无缝集成,使其成为海洋与气象数据分析中的一种多功能选择。
import xarray as xr
# 或许我们可以使用简化的名称例如
# import xarray as xr
# 以绝对路径打开
ds = xr.open_dataset(r'C:/Users/VICTUS/Desktop/data/tas_Amon_CESM2-WACCM_historical_r1i1p1f1_gn_185001-201412.nc')
# 以相对路径打开
# ds = xarray.open_dataset(r'tas_Amon_CESM2-WACCM_historical_r1i1p1f1_gn_185001-201412.nc')
# 如果你在import的部分对于xarray进行了简化的命名,则打开文件的方式为:
# ds = xr.open_dataset(r'tas_Amon_CESM2-WACCM_historical_r1i1p1f1_gn_185001-201412.nc')
## 1.2 读取数据
#%% md
打开数据集的命令只读取 `netCDF` 文件中的元数据。在进行其他操作之前,它不会尝试读取任何数据。
`xarray.DataSet` 对象有许多方法用于访问坐标、属性和数据。数据变量保存在一个类似于 `dict(字典)` 的结构中,即 `ds.data_vars`:
#%%
ds.data_vars
#%% md
只需循环数据集,就可以循环查看数据变量,数据集会依次返回每个变量的名称:
#%%
for varname in ds:
print( "读取的变量为:" + varname)
#%% md
可以使用单个变量的名称访问该变量,或者将其作为一个类似于 `dict` 的键
#%%
ds['tas']
#%% md
为方便使用,xarray 还提供了作为 python 属性的数据变量访问权限
#%%
ds.tas
#%% md
因此,`ds.tas` 是一个`xarray.DataArray`,它有自己的元数据,可提供有关变量本身的更多信息。在本例中,它是以**开尔文为单位的近地面空气温度**。
#%% md
### 总结
#### 使用xarray打开nc文件的方法
```python
import xarray as xr
ds = xr.open_dataset(r'file_path')
其中,file_path是你的文件所放置的位置,请注意你的文件是否为绝对路径。
注意点: 请养成你的**文件路径中不存在中文**的好习惯,同时请将路径放置在' '中。
```
#### 读取变量
```python
import xarray as xr
ds = xr.open_dataset(r'file_path')
tas = ds['tas']
或者
tas = ds.tas
```
#%% md
> https://docs.xarray.dev/en/stable/user-guide/data-structures.html
#%% md
## 1.3 按时间和空间对数据集进行提取(切片和切割)
Xarray 总共支持四种不同类型的索引,如下所示,这里只需要有个概念即可:
![image.png](attachment:image.png)
但是我这里只介绍其中两种个人常用的:sel 和 isel
#%% md
- xarray 可为符合 CF 标准的数据创建带标签的坐标索引。这使得在时间和空间上选择数据子集变得简单易行。
下面介绍了数据切片的一些常见使用方法,重新加载库和数据集,这里将导入的 `xarray` 库重新简化命名为`xr`
#%%
import xarray as xr
ds = xr.open_dataset(r'C:/Users/VICTUS/Desktop/data/tas_Amon_CESM2-WACCM_historical_r1i1p1f1_gn_185001-201412.nc')
#%% md
选择数据变量 `tas` 并将其引用保存在另一个 python 变量中,命名为:
`tas`
#%%
tas = ds.tas
#%%
tas
#%% md
xarray 构建在 numpy 的基础之上,内部将数据存储为 numpy 数组。它支持许多 numpy 操作,因此可以找出底层数据的形状,并使用 numpy 风格的索引
观测上面的变量,可以发现:
`tas` 是由 `time x lat x lon` 组成的三维数据,这里的顺序不是确定好的的,不是 latxlonxtime ,也不是 timexlonxlat ;使用 `tas.shape` 可以查看三个坐标对应的数量
#%%
tas.shape
#%% md
1980 表示时间time有1980个数值,
192 表示纬度lat有192个间隔,
288 表示经度lon有288个间隔
那么如何切片呢,比如说我想提取出第一个时刻对应的经纬度组成的网格数据
#%%
tas[0,:]
#%% md
通过只选择第一个时间索引,它创建了一个没有时间维度的 DataArray,但时间仍然是一个与任何变量都不相关联的坐标,因为它旁边不再有 `*`。现在它只有一个值:第一个时间索引的值。上面的索引选择等同于使用 `isel`,如下所示
#%%
tas.isel(time=0)
#%% md
使用方法 `tas[0,:]` 更简洁,使用`tas.isel(time=0)`方法更具描述性,但结果是一样的。
#%% md
xarray 的强大之处在于数据与坐标的紧密联系。因此,可以使用等效的 `.sel` 操作符,但要使用坐标值。例如,要选择包括印度洋和澳大利亚在内的区域,可以使用 `slice` 来指明所需的经纬度值范围,并将其作为键/值对传递给 `sel`。 `slice`将包括小于**或等于**上限值的坐标值,而不像基本 python 中的`range`那样排除上限值。
#%%
tas.sel(lon=slice(20,160),lat=slice(-80,25))
#%% md
以上命令可以串联,因此可以依次执行多个操作。例如,选择上述区域和第一次索引
#%%
tas.isel(time=0).sel(lon=slice(20,160), lat=slice(-80,25))
#%% md
在这种情况下,使用 `isel` 选择时间比指定日期更方便,但也可以使用 `sel` 明确指定日期
#%%
tas.sel(time='1850-01-15T12:00:00', lon=slice(20,160), lat=slice(-80,25))
#%% md
也可以在 时间 维度上使用 `slice `,选择某个时间范围内的数据,比如说选择 1871 年 3 月至 11 月:
#%%
tas.sel(time=slice('1871-03','1871-11'), lon=slice(20,160), lat=slice(-80,25))
#%% md
**slice - 切片** 运算符选择上界和下界之间的值。如果使用 `sel` 时需要单个坐标值,它必须与坐标数组中的*精确*值相对应,或者使用指定的 `method` 参数告诉 xarray 如何选择值。例如,只选择最靠近布里斯班的单元格中的值
#%%
tas.sel(lat=-27.47, lon=153.03, method='nearest')
#%% md
因此,数据中最接近的位置位于 lat=-27.8 , lon=152.5。
#%% md
参考
> https://docs.xarray.dev/en/stable/user-guide/indexing.html
#%% md
## 1.4 使用xarray绘图
#%% md
### 绘制单层数据
#%% md
xarray 库有一个内置的 `plot` 函数,它是[matplotlib 库](https://matplotlib.org)的一个封装包。
xarray plot 函数的[文档](http://xarray.pydata.org/en/stable/plotting.html)非常出色。请阅读该文档,深入了解 xarray 绘图功能的强大之处。本手册所涉及的内容只够入门和在后续教程中使用绘图。
由于 xarray 数据数组包含数据的所有相关元数据和坐标,因此只需花费很少的精力就能创建信息丰富的绘图。
#%% md
绘图的第一步还是先导入xarray 工具包 :
```python
import xarray as xr
```
#%% md
下一步是使用[jupyter函数](https://ipython.readthedocs.io/en/stable/interactive/magics.html),使 matplotlib 图形可以显示在笔记本中
#%%
%matplotlib inline
#%% md
现在打开与之前相同的数据集
#%%
import xarray as xr
ds = xr.open_dataset(r'C:/Users/VICTUS/Desktop/data/tas_Amon_CESM2-WACCM_historical_r1i1p1f1_gn_185001-201412.nc')
#%% md
请注意: `xarray.DataArray` 定义了 `plot` 方法,它对 `xarray.Dataset` 无效。因此,要绘制`tas`.需要先取出变量:`tas`。
同时,由于这里读取的tas是具有多个时间步长的数据,我们只需要一个时刻的数据,所以在取出变量的同时,取出它的第一个时间步长。
还记得上面讲过对时间数据进行切片的方法,我们可以依靠`isel`命令
#%%
tas = ds.tas
tas.isel(time=0).plot(size=6)
#%% md
我们可以同时使用多个命令,如上所示,在取出第一个时间步长的数据后直接在末尾添加`.plot`,就可以使结果可视化。
#%%
tas.isel(time=0).sel(lon=slice(20,160), lat=slice(-80,25)).plot(size=6)
#%% md
当然,我们也可以在提取第一个时间步长的同时选择感兴趣的区域进行可视化,如上所示。
#%% md
xarray 会根据绘制数据的维数自动猜测绘图类型。如果传入的维数过多,`xarray` 将默认显示直方图
- 这也是为什么上面我选择提取出一个时间步长再进行可视化的原因,这样得到的数据只是一个二维的数据,由`lonxlat`组成
- 想象一下,如果我们同时包含多个时间步长的数据,那么这就是一个三维的数据,即:`timexlonxlat`
#%%
tas.sel(time=slice('1871-01','1876-12'), lon=slice(20,160), lat=slice(-80,25)).plot(size=6)
#%% md
如果维度足够小,它就会绘制出一条直线。例如,在某一个空间点上的近地表温度的时间序列(注意:这是在选择一个点,因此 `sel` 需要数据中存在的确切位置,或者指定 `method` 参数来告诉它如何从数据中选择位置)
- 下面我将选择南京的经纬度位置,提取出它对应的时间序列
请注意,对于cmip6的模式数据,其时间格式可能并非标准的格式,如果直接使用以下命令,
```python
tas.sel(time=slice('1871-01','1876-12')).sel(lat=-27.47, lon=153.03, method='nearest').plot(size=6)
```
可能会发生报错:
```python
ImportError: Plotting of arrays of cftime.datetime objects or arrays indexed by cftime.datetime objects requires the optional `nc-time-axis` (v1.2.0 or later) package.
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
```
这里提示需要导入一个库: `nc-time-axis` (v1.2.0 or later) package
```python
- https://pypi.org/project/nc-time-axis/
支持conda 安装:
- conda install -c conda-forge nc-time-axis
或者 pip 安装:
- pip install nc-time-axis
```
#%%
import xarray as xr
import nc_time_axis
ds = xr.open_dataset(r'C:/Users/VICTUS/Desktop/data/tas_Amon_CESM2-WACCM_historical_r1i1p1f1_gn_185001-201412.nc')
tas = ds.tas
tas.sel(time=slice('1871-01','1876-12')).sel(lat=28, lon=118, method='nearest').plot(size=6)
#%% md
或者可以通过其他解决方法:
可以发现,上述文件的时间类型为cftime.DatetimeNoLeap
- 既然该文件的时间类型不支持,我们可以将其转换为支持的时间格式,这里可以使用`pandas`来进行转换
#%% md
实现的逻辑也很简单,对于每个时间进行循环处理,批量转换后再赋给原本的变量,从而替换掉原本tas中的时间
#%%
import pandas as pd
converted_times = [pd.Timestamp(t.strftime("%Y-%m-%d %H:%M:%S")) for t in tas.time.values]
#
print(converted_times)
tas['time'] = converted_times
tas.sel(time=slice('1871-01','1876-12')).sel(lat=28, lon=118, method='nearest').plot(size=6)
###############################################################################################
## 其他替换时间的方法
###############################################################################################
# import datetime
# start_year = 1850
# end_year = 2014
# # 创建时间序列,按月递增,每个月为 30 天
# dates = []
# for year in range(start_year, end_year + 1):
# for month in range(1, 13): # 12个月
# day = 15 # 假设每个月的15号作为时间点
# dates.append(datetime.datetime(year=year, month=month, day=day))
# # 将日期数组赋值给 tas['time']
# tas['time'] = dates
# tas.sel(time=slice('1871-01','1876-12')).sel(lat=28, lon=118, method='nearest').plot(size=6)
#%% md
可以发现,可以正常绘制出某个点的时间序列了
#%% md
### 绘制气压层数据
#%% md
现在从同一数据集中打开一个新数据集,这是所有压力水平下的气温数据
#%%
ds_air = xr.open_dataset('C:/Users/VICTUS/Desktop/data/ta_Amon_EC-Earth3-Veg_historical_r1i1p1f1_gr_185001-185012.nc')
#%%
ds_air
#%% md
选择气温数据变量
#%%
ta = ds_air.ta
ta
#%% md
选取第一个时间步长以及喜马拉雅山对应的经度,绘制出从极点到另一个极点的大气垂直剖面图。请注意,绘图时使用了 `yincrease=False`选项来反转 y 轴,因为压力会随高度的增加而减小。
#%%
ta.isel(time=0).sel(lon=86.93, method='nearest').plot(size=6, yincrease=False)
#%% md
## 1.5 计算基本指标
#%% md
xarray 构建在 [numpy](http://numpy.org)之上,这意味着它以许多封装的方法实现了许多 numpy 运算指令,而那些没有实现的运算指令仍可用于包含有 `xarray.DataArray` 对象的底层 numpy 数组。
这只是一个简短的介绍,还是气候数据。关于计算的 xarray 文档](http://xarray.pydata.org/en/stable/computation.html) 非常出色,包含更多示例和更多细节。
![image.png](attachment:image.png)
下面只简单展示最基础的命令
#%%
import xarray as xr
import matplotlib.pyplot as plt
%matplotlib inline
#%% md
将 CMIP6 空气温度数据集加载到变量 `ds` 中,并创建一个变量 `tas` ,直接引用近地面空气温度 `DataArray` 。这里使用和之前一样的数据
#%%
ds = xr.open_dataset(r'C:/Users/VICTUS/Desktop/data/tas_Amon_CESM2-WACCM_historical_r1i1p1f1_gn_185001-201412.nc')
tas = ds.tas
#%%