在前面一个章节,我们学习了常用的时间序列的生成方法,这一节,则是非常方便的如何使用xarray进行数据集的时间维度的抽取合并操作。逐步的学习,摸鱼咯大佬的花式索引学会也不是什么难事。
这一章的框架是按照xarray提供的不同的数据抽取方式,逐项讲解xarray下的时间序列的抽取,在最后,还会涉及一些不同数据集按照时间维进行合并的方法。当然,我还是才刚刚才探讨这个方向,肯定有些贻笑于方家的地方,希望和大家一起讨论学习。
ps:在后续写文章的时候,发现还有可以啰嗦的,那就干脆再开一章,下一章来讨论groupby、merge等合并归类拼接时间等操作。
数字索引取值法
数字索引切片是最基础的切片方式,逻辑理论完全基于列表切片和numpy的array切片,这里,我们就不得不简单回顾一下数字索引切片。数字索引切片的基本逻辑有
import xarray as xr
import numpy as np
import pandas as pd
file=r'F:\aaaa\air.mon.mean.nc'
ds=xr.open_dataset(file)
ds
该数据集是气温的水平空间、时间数据集。 然后提取单独提取时间序列以方便操作,实际上使用时一般是直接在上述air的相关维度进行操作。
ds.time
这是一个长度为867的时间序列,每个序列以纳秒为最小单位,宏观分辨率为月,起于1948年1月,终于2020年3月。 数字索引取值法的语法规则非常类似于列表、ndarray,只须给出start、end、step三个参数即可,这三个参数不是全部必须的。 例如我想提取前15个元素值:
ds.time[0:15]
我想提取前30个元素,但每两月取一次值:
ds.time[0:30:2]
上面1948-01后面直接是1948-03,2月被跳过了。数字索引的劣势是,不依靠内部元素,则取值不方便,例如我现在需要提取1989-01到1999-04,则数字索引方法还需要计算1948-01到1989-01的距离才能确定索引值。 若数字索引超过范围,则会默认将内部元素全部取出,以下代码,要求提取到1500索引,但我们知道这个序列仅有867长度,程序则会将现有值全部取出。
ds.time[0:1500]
. loc 取值法
重量级的来咧。loc取值法可以说才是xarray对时间序列取值的神,通过简单了解,你就可以飞速处理时间序列。 loc是xarray基于pandas的loc语句进行开发的,所以完全遵循pandas的loc语句的规则,loc语句拥有两种确定取值范围的方法,一是以内部存放值为单位进行取值,二是以一个布尔值表确定取值,下面分别介绍之。 loc按照存放值可能性的切片法
data=ds.time.loc['1948-01-01':'1949-12-01']
data
data=ds.time.loc['1948-01-01':'1949-12-01':2]
data
i_need_month=['1949-10-01','1950-10-01','1999-10-01']
data=ds.time.loc[i_need_month]
data=ds.time.loc[['1949-10-01','1950-10-01','1999-10-01']]#与上面效果一致,只是代码合并了
data
loc按照布尔值表的切片法 该方法允许使用者给loc传入一个布尔值表(True、False),然后按照这个布尔值表确定取值,真则取,假则弃。例如我们生成一个仅第一个为真,其余全为假的布尔值表,则仅会提取第一个真对应的1948-01的数据:
bool_array=[True]+[False]*866
data=ds.time.loc[bool_array]
data
那么压力来到如何生成需要的布尔值表,因为我们不可能像上面例子这样手动逐个生成布尔值表。这里不妨看这样一个例子:
bool_data=np.array('1949-01-01').astype('datetime64[D]')<np.array('1949-05-01').astype('datetime64[D]')
bool_data
True
在python中,允许时间进行比较,并生成布尔值,上面判定1949年1月是小于1949年5月的,所以上式成立,返回真。那下面就简单了,我们假定对时间序列进行是否大于1949年1月的判定,并返回一个布尔值表。
bool_array=np.array('1949-01-01').astype('datetime64[D]')<ds.time
接下来只要将这个表放进loc语句里,就可以提取对应数据了:
data=ds.time.loc[bool_array]
data
但是我们发现缺失了1949年1月,这是因为在生成布尔值表时,我们给出的逻辑为绝对的小于,1949-01是=1949-01的,所以返回假,要提取到1月,我们可以提前到1948-12,或者将逻辑符号改为<=。
bool_array=np.array('1949-01-01').astype('datetime64[D]')<=ds.time
data=ds.time.loc[bool_array]
data
下面就是运用逻辑运算,进行更加复杂的一波流取值。和【&】逻辑就是数学里的取交集,或【|】逻辑就是数学里的取并集。我们先提前用两个简单的布尔表学习一下。
a=np.array([True,False,False])
b=np.array([True,True,False])
c=a&b
c
array([ True, False, False])
和逻辑下,当两个对应位置的逻辑值都为真时,才返回真,其余全假。
a=np.array([True,False,False])
b=np.array([True,True,False])
c=a|b
c
array([ True, True, False])
或逻辑下,当两个对应位置的逻辑值只要一个为真,就返回真。 提取1955年1月到1966年12月,1988年1月到1988年6月,1999年5月到2001年1月的数据,我们就可以在一条命令中实现。这个命令是嵌套过的,先进行和运算,再或运算,如果不能理解,可以用初中数学那个在x轴上画取值范围的方法去套:
import datetime
t1=pd.to_datetime(datetime.date(1955,1,1))
t2=pd.to_datetime(datetime.date(1967,1,1))
t3=pd.to_datetime(datetime.date(1988,1,1))
t4=pd.to_datetime(datetime.date(1988,7,1))
t5=pd.to_datetime(datetime.date(1999,5,1))
t6=pd.to_datetime(datetime.date(2001,2,1))
data=ds.time.loc[((ds.time>=t1)&(ds.time<=t2))|
((ds.time>=t3)&(ds.time<=t4))|
((ds.time>=t5)&(ds.time<=t6))]
data
如何返回固定月份的数据 在实验中,我们要求仅返回12月的数据,怎么进行呢,最先想到的,就是使用步长为12,每十二个月进行一次切片:
data=ds.time.loc['1949-12-01'::12]
data
在实验中,我们要求仅返回11、12月的数据,又怎么进行呢,显然切片法解决不了,下面引入xarray继承pandas的isin方法。isin返回的是布尔值,刚好满足loc取值参数的要求。
data=ds.time.loc[ds.time.dt.month.isin([11,12])]
dt.month可以直接在time内部比较时间,dt有许多模块,这里month只有1-12可选,若是day,则对应为1-31。例如:
data=ds.time.loc[ds.time.dt.day.isin([1])]
上面要求判定每个时间是不是1号,前面我们知道确实全部都是1号,则整个时间序列全部符合要求,全部返回True,全部被提取出来了。若判定为2号,则全部不符合要求,全部返回False,数据全部舍弃,返回一个空数组。
data=ds.time.loc[ds.time.dt.day.isin([2])]
进一步的,我要看这些时间,是不是在15点,则:
data=ds.time.loc[ds.time.dt.hour.isin([15])]
在前面我们已经知道,每个时间都是1日零时零分零秒的,则全部不是15点,全部不符合要求,故返回一个全为假的布尔表,loc根据这个全为假的布尔表,返回一个空数组。 如何对数据进行操作 上面对时间序列的处理,都是讲明原理,仅仅对时间序列进行操作,下面我们将对air进行相关操作。在loc语句中,各维相互之间不干扰,用自己的方法提取即可,唯一需记住,维度的相关位置非常重要,时间是第一维,则时间切片也在第一维:
air_1949_1950=ds['air'].loc['1949-01-01':'1950-12-01',:,:]
air_1949_1950
下面这个与上面相同,要求切取1955年1月到1966年12月,1988年1月到1988年6月,1999年5月到2001年1月,北纬10-50,东经90-100的数据,则提取语句为:
import datetime
t1=pd.to_datetime(datetime.date(1955,1,1))
t2=pd.to_datetime(datetime.date(1967,1,1))
t3=pd.to_datetime(datetime.date(1988,1,1))
t4=pd.to_datetime(datetime.date(1988,7,1))
t5=pd.to_datetime(datetime.date(1999,5,1))
t6=pd.to_datetime(datetime.date(2001,2,1))
air=ds['air'].loc[((ds.time>=t1)&(ds.time<=t2))|
((ds.time>=t3)&(ds.time<=t4))|
((ds.time>=t5)&(ds.time<=t6)),50:10,90:100]
air
很明显,时间切片用的布尔值表,经纬切片用的元素值索引,但是互不干扰。上面就是loc常用的一些取值套路了,当然根据官网介绍,还有一些方法,但是这些方法很少使用例如:
data=ds.time.loc[dict(time=slice('1949-01-01','1950-12-01'))]
感兴趣的可以去https://docs.xarray.dev/en/stable/user-guide/indexing.html#assigning-values-with-indexing 这个网址学习,这是xarray官网关于索引和筛选的介绍。
. sel 取值法
该取值法与loc不同,不接受直接切片,必须指出对应维,允许模糊搜索。
data=ds.time.sel(time='1949-01-01')
data
上面的括号里必须指出这是对time的操作,否则报错。sel也允许传入一个列表来提取数据
data=ds.time.sel(time=['1949-01-01','1950-01-01'])
上面提到的不接受直接切片仅仅相对于loc的快捷直接切片如来说的,sel还是接受间接切片的:
data=ds.time.sel(time=slice('1949-01-01','1950-01-01'))
sel取值,最常用的是模糊搜索这个参数,这个参数是loc没有的,通过不同的关键字,实现模糊搜索。举个例子,现在有1959-12-29,假定我需要一个最近的数据来代替这一日的数据,则可以通过sel来实现,默认情况下sel的该参数为None,这时因为时间序列里没有和1959-12-29一致的将会报错,并提示你修改method参数搜索方式。
data=ds.time.sel(time='1959-12-29',method=None)
下面将这个参数修改为最接近:
data=ds.time.sel(time='1959-12-29',method='nearest')
程序说最接近这个日期的为1960年1月,这时没有问题的,因为仅相差两天,而12-29与上一个日期节点12-01相差28天。这里引发一个问题,就是跨月又跨年,如果规定12月某天仅能用12月数据代表,那就不合适了,于是继续修改method为pad,他的意思是向搜索这个日期最近的前一个日期完成搜索,那么1959-12-29的前一个时间节点就为1959-12-01,符合要求了:
data=ds.time.sel(time='1959-12-29',method='pad')