Xarray - 数据对象
本文主要介绍 Xarray 的数据对象( DataArray 和 DataSet )相关的内容,包括数据对象的创建、读写、索引和插值等操作
1 | import numpy as np |
创建xarray对象
Xarray 包提供了两种数据储存结构: DataArray 类 和 Dataset 类
- DataArray 将维度名称,坐标和属性添加到多维数组
- Dataset 则是多个 DataArray 数组的集合
DataArray 类
DataArray 是一个容器,装一个变量的信息,比如可以把温度在时空的变化放在里面
xr.DataArray( data, dims, name, coords, attrs )
添加数据、维度名称和数组名称
可以注意到 xr.DataArray( ) 括号内的三行内容:
- data : 利用 Numpy 包的 np.ones 函数创建一个 2x6x6 numpy 类的全1数组
- dims : 把三个维度分别命名为 time, lon, lat
- name : 取了一个名字 Temperature Data
1 | da = xr.DataArray( |
添加坐标信息
对坐标数据而言,是用 coords = { } 大括号框定的区域。
"longtitude" : ( "lon", np.arange(0, 181, 30) ) 中 :
- "longtitude" 是坐标名称,
- "lon" 是坐标名称对应的维度,
- np.arange(0, 181, 30) 是维度 "lon" 对应坐标数据
1 | da = xr.DataArray( |
添加数据属性
1 | da = xr.DataArray( |
添加坐标属性
有时候坐标参数(如经纬度)需要附加一些信息, 例如可以附加经纬度的单位(degree)
添加坐标参数属性的方法即在坐标值的后面添加上一个 大括号 { "属性": value } 括好的数据,
如:"latitude":("lon",np.arange(0, 361, 30), {"unit": "degree"}) ,冒号的左侧为属性名称,右侧为对应的值。
对于多个属性的添加,需用 逗号 进行间隔,如 {"step": "5 degree","first value":1}
1 | da = xr.DataArray( |
提取数据、维度、坐标和属性信息
提取 da 数据 ( data ) :
1 | da.data |
提取 da 维度名称 ( dimensions ) :
1 | da.dims |
提取 da 坐标信息 ( coordinates ) :
1 | da.coords |
提取 da 属性 ( attributes ) :
1 | da.attrs |
练习 - 1
现已通过随机函数常见了一个名为 height 伪数据
1.不考虑创建的其他要素,试着以 height 为基础数据创建一个 DataArray 对象。下面提供了部分代码。
2.添加维度名字“x”和“y”。
3.添加数组名称“My random array”
4.分别对“x”和“y”维度添加坐标“longtitude”(经度)和“latitude”(纬度)。;经度从-180至180,步长为1;纬度从-90至90,步长为1。
1 | height = rng.random((180, 360)) * 400 |
Dataset 类
xr.Dataset( data_vars, coords, attrs )
Dataset 的对象可以将 多个变量 放在一起。可以定义每一个变量都有相应不同维度。
Dataset 由下列三个部分组成
- data_vars :变量信息, "var":(dims,data,{"attr":value}) / "var":DataArray
- coords :坐标信息
- attrs :属性信息
添加变量、坐标和属性
data_vars 的大括号 { "var": (dims, data, {"属性": value}) } 中包含了 a 和 b 两个变量,变量名后以小括号扩住这个变量的信息。小括号的信息包含下列信息
- 维度 :在命名维度名称的同时,也就确定了维度的大小。例子中包含两个维度x和y。
- 数据 :数据大小的确定根据维度的大小所决定。例子中是利用np.ones函数构建了一个3×4的全1矩阵,利用np.full函数构建项数为8全为3的一维数组。
- 属性 :用大括号包含。写法:名称字符串+冒号:+属性值字符串。不同属性之间用逗号间隔,类似于 {"atrri1": "First", "atrri2": "Second"}
coords 的大括号包含了具体的坐标参数。与 DataArray 类似。要确保维度名称代表的数据的数目和坐标参数的数目相一致。
attrs 的大括号包含了属性参数。与 DataArray 类似。
1 | ds = xr.Dataset( |
创建同一维度上但多个变量的坐标参数不一致的 Dataset
如果要创建一些在同一个坐标(Coordinates)上但坐标变量不一致的变量,我们不能采用简化的语法。
相反,我们需要使用到 DataArray 对象: data_vars = { "var": DataArray }
1 | x_a = np.arange(1, 4) |
练习 - 2
1.利用两个 DataArray 创建一个具有 height 和 gravity_anomaly 两个变量且具有 x 和 y 两个维度的 Dataset
1 | height = rng.random((360, 180)) * 400 |
2.在上题的基础上添加 latitude 和 longitude 两个坐标
- longitude : 从 -180 至 180,步长为1(采用 np.linspace 函数)
- latitude : 从 -90 至 90,步长为1
1 | xr.Dataset( |
3.添加属性到坐标(coordinates)和变量(variables):
- latitude : "type": "geodetic"
- longitude : "prime_meridian": "greenwich"
- height : "ellipsoid": "wgs84"
- gravity_anomaly : "ellipsoid": "grs80"
1 | height_da = xr.DataArray(height, |
数据读取与转换
pandas 数据类型转换
由 pandas 对象转换为 xarray 对象:对 pandas 对象使用 to_xarray 方法
由 xarray 对象转为 pandas 对象:对 xarray 对象使用 to_pandas 方法
Series → xarray
Series.to_xarray()
若要将变量 series(pandas 类型)转为 xarray 类型只需在变量后加上 .to_xarray() 方法即可。
由于只有一个变量,所以转换的结果是 xarray 中的 DataArray 类型。
1 | series = pd.Series(np.ones((10,)), index=list("abcdefghij")) |
DataArray → pandas
DataArray.to_pandas()
若要将 xarray 转为 pandas 类型,类似的在变量后加上 .to_pandas()
1 | arr.to_pandas() |
对于 xarray 的多变量 Dataset 对象同理可用类似对 pandas 对象的转换方法,只需要在对象后添加 to_series / to_dataframe 即可:
先创建一个 Dataset 对象 ds
1 | ds = xr.Dataset( |
DataArray → Series
DataArray.to_series()
将 ds(Dataset)中的变量 a 利用 .to_series() 转换为 pandas 中的 Series(列表)类型。
由于没有指定 index,则在默认情况下,index 默认为数字且从 0 开始,步长为 1.
1 | ds.a.to_series() |
如果要指定 index,则需在转换为 pandas 类型后,对 index 进行指定,比如
1 | myseries = ds.a.to_series() |
同理也可将 ds(Dataset)中的变量 b 转换为 pandas 类型,这里将 多维数据 使用 多层索引
1 | ds.b, ds.b.to_series() |
Dataset → DataFrame
Dataset.to_dataframe()
to_dataframe :将 DataArray 或 Dataset 对象转换为 pandas.dataframe 。注意到 DataArray 对象名称与转换为数据框的名称一样都为 a 。
1 | ds.a.to_dataframe() |
为保证数据的连续性,对于转换为DataFrame数组会发生广播。
类似于转换为列表,为保证数据的连续性,对于转换为DataFrame数组也会发生广播。
1 | ds.to_dataframe() |
数据输入输出
Xarray 最广泛使用的特性之一是它读写各种数据格式的能力。例如,Xarray 可以读取以下格式:
- NetCDF / GRIB (通过函数 open_dataset / open_mfdataset , to_netcdf / save_mfdataset )
- Zarr (通过函数 open_zarr , to_zarr )
- GeoTIFF / GDAL rasters (通过函数 open_rasterio )
NetCDF(Network Common Data Form)/ GRIB
Dataset.to_netcdf(path), DataArray.to_netcdf(path)
xr.open_dataset(path), xr.open_dataarray(path)
存储 Xarray 数据结构的推荐方法是 NetCDF(Network Common Data Form),这是一种二进制文件格式,用于起源于地球科学的自描述数据集。文件的后缀为.nc。Xarray 基于 netCDF 数据模型,因此磁盘上的 netCDF 文件直接对应于数据集对象。
Xarray 采用 open_dataset / open_dataarray 函数读取NetCDF 文件,采用 to_netcdf 方法将数据写入文件。
1 | import xarray as xr |
将 DataSets 和 DataArray 写入 nc 文件中
接下来首先创建一些数据集,并使用to_netcdf将数据写入硬盘
1 | ds1 = xr.Dataset( |
1 | # DataSets写入文件 |
读取 nc 文件
1 | xr.open_dataset(".data/ds1.nc") |
1 | xr.open_dataarray(".data/da1.nc") |
数据索引和分片
基于维度数字对数据进行索引和分片
引入:numpy 的索引和分片
获取 np_array 第一维度(axis0)索引为1和第二维度(axis1)索引为3 的值(维度默认索引起始值为0)
1 | np_array = np.random.randn(3,4) |
当然也可以进行分片(slice),获取一范围数组的数据
1 | np_array[:2,1:] |
DataArray 的索引和分片
xarray.isel( dim1=loc1, dim2=loc2, ... )
xarray.isel( dim1=[ ... ], dim2=[ ... ], ... )
xarray.isel( dim1=slice( start, end ), dim2=slice( start, end ), ... )
方法 .isel(integer selection) 是一个基于维度名称数字索引的筛选的方法。
通过 .isel 这个方法筛选了 arr 第一维度x索引值为1和第二维度y索引值为3 的值。
1 | arr = xr.DataArray(np_array, dims=("x","y")) |
xarray[ { "dim1": loc1, "dim2": loc2, "...": ... } ]
1 | arr[{"x":1, "y":3}] |
Dataset 的索引和切片
xarray.isel( dim1=loc1, dim2=loc2, ... )
xarray.isel( dim1=[ ... ], dim2=[ ... ], ... )
xarray.isel( dim1=slice( start, end ), dim2=slice( start, end ), ... )
对于这个 Dataset 的以 「维度名称数字分片」 需要使用 .isel 方法,同时对于各个维度用 slice 指定各个维度分片范围
这样的分片同时对 ds 中的变量 a 和 b 均有分片作用
None 在代码中可以代替初始值或者末尾值。
x=slice(None,2) 等价为在numpy中的 [:2] , y=slice(1,None) 等价为在numpy中的 [1:]
1 | ds = xr.Dataset( |
其他索引方法:head, tail, thin
xarray.head( dim1, dim2, ... )
对于 .head() 方法,有以下的例子
方法 .head() 获得了 ds 从 最左上角数据(首位, head) 到维度名称索引(x=2,y=3)的分片数据
1 | display(ds) |
xarray.tail( dim1, dim2, ... )
与 .head() 方法 相反 的 .tail() 方法则是从维度名称索引(x=2,y=2)至 最右下角数据(尾部, tail) 的分片数据。
1 | display(ds) |
xarray.thin( dim1, dim2, ... )
有时候我们需对原数据 缩小尺寸 ,不需要特别高精确度的数据。
通常的做法是给定一个 特定的步长 , 按照这个步长进行取值 。
dim1 给定了在 dim1 方向上的筛选数据的步长, dim2 给定了在 dim2 方向上的筛选数据的步长。
1 | display(ds) |
这样理解还是比较抽象,为便于理解这个函数的作用和意义,我们利用真实数据将原数组和缩小后的数组进行比较
1 | # import seaborn as sns |
基于坐标值对数据进行索引和分片
xarray.sel( dim1=value1, dim2=value2, ... )
若要使用坐标名称筛选数据,可以使用类似于维度名称筛选数据的方法。
只是使用的是 .sel() 方法而非 .isel() 方法。
1 | arr = xr.DataArray( |
xarray.loc[ { "dim1": value1, "dim2": value2, "...": ... } ]
1 | arr.loc[{"x":5.3, "y":"2009-04-30"}] |
xarray.sel( dim1=value1, dim2=value2, ..., method )
上述的筛选方法需要指定特定的坐标数据。如果缺少特定的坐标数据(比如你忘记了具体的值,只知道大致的值),可以使用 method 参数进行指定选取你给的坐标最近 "nearest" 的坐标数据。
1 | arr.sel(x=4, y="2009-04-01", method="nearest") |
xarray.sel( dim1=[ ... ],dim2=[ ... ], ... )
xarray.sel( dim1=slice( start, end ), dim2=slice( start, end ), ... )
当然除此之外,不单单选取一个值,也可以选取多重数据
对于坐标x而言( "x":[-3.2,2.1,5.3,6.5] ),坐标的命名是 「离散」 的,因此在选取坐标x的时候需要进行 离散 选取。
对于坐标y时间而言( pd.date_range("2009-01-05", "2009-06-30", freq="M") ),产生数据的方法是 连续 的。
这种 「连续性数据」 多个筛选则需要通过 slice 函数进行选取
1 | arr.sel(x=[-3.2, 6.5], y=slice("2009-04-01", "2009-05-31")) |
xarray.drop_sel( dim1, dim2, ... )
如果我们仅仅是想要抛弃原数据中的一些部分,则可以用 drop_sel 筛选抛弃的部分,留下剩余的部分。
1 | arr.drop_sel(x=[-3.2, 5.3]) |
练习 - 3
首先导入练习数据
1 | ds = xr.open_dataset(".data/air_temperature.nc") |
1.筛选前30个纬度坐标且第20至40个经度坐标的数据
1 | # 在这里写你的代码 |
2.筛选北纬75度(75°N)且时间在2013年1月1日至2013年10月15日的数据
1 | # 在这里写你的代码 |
3.去除经度260(100°W)至270(90°W)的值后的数据
1 | # 在这里写你的代码 |
插值和广播
平面插值
xarray.interp( dim1, dim2, ..., method )
有时候想要知道格点框中更加高分辨率的值,这时候可以使用 interp 函数进行数据插值。
1 | arr = xr.DataArray( |
如果给定插值后的范围大于原有的范围(外推)的话,在原有数据范围外的数据将会赋值为 NaN 。
xarray1.interp_like( xarray2 )
如果已经有一个包含给定插值模板的对象,只需用interp_like即可实现插值
1 | other = xr.DataArray( |
对于插值方法的选取,可以通过指定 method 方法(默认为 "linear" )来实现切换平面插值方法( "nearest" )。比如指定使用临近点插值
1 | arr.interp( |
其他的插值方法参见下表所示
method | 插值方法 | 一维插值 | 二维插值 |
---|---|---|---|
nearest | 临近点插值 | √ | √ |
linear | 线性插值 | √ | √ |
zero | 零阶样条插值 | √ | × |
slinear | 一阶样条插值 | √ | × |
quadratic | 二阶样条插值 | √ | × |
cubic | 三阶样条插值 | √ | × |
练习 - 4
通过增加步长的形式将纬度(lat)和经度(lon)的分辨率从 2.5° 增加到 1°。
1 | ds = xr.open_dataset(".data/air_temperature.nc") |
数据广播和对齐
维度名相同的广播
a+b自动筛选了a, b的 DataArray 中具有共同维度的共同坐标的数据进行相加
1 | a = xr.DataArray( |
xr.align( xarray1, xarray2, join )
等价于:对齐 → 加减
1 | a_, b_ = xr.align(a, b, join="inner") |
当然若要指定不同的广播方法,可以用 xr.align 函数的 join 进行指定。
默认情况下广播的方法是取交集,即join="inner"。除此之外,join的取值还有outer, left, right, exact, override六类取值。
> inner:提取两者变量索引的交集
> outer:提取两者变量索引的并集
> left:提取仅具有左侧变量索引对应的数据
> right:提取仅具有右侧变量索引对应的数据
> exact:检验索引是否完全对齐,若左右变量的索引无法对齐,会抛出 ValueError 错误
> override:如果索引、数据的尺寸大小相同,则将右侧变量的索引重写为左侧变量的的索引
1 | a_, b_ =xr.align(a, b, join="override") |
维度名不同的广播
类似的也有如下两个数组的广播(对齐)相加
1 | arr1 = xr.DataArray( |
xr.broadcast( xarray1, xarray2 )
等价于:广播 / 对齐 → 相加
1 | arr1_, arr2_ = xr.broadcast(arr1, arr2) #广播/对齐 |