雖然近些年來氣象數據基本都以netcdf (.nc)格式為主,但是不同機構,不同模式生成的數據仍存在差異,比如說有的經度坐標是0°-360°,有的是-180°-180°,有的緯度坐標是從南到北,有的緯度坐標是從北到南,然而這些處理起來都還算很簡單的。唯獨時間模塊,真的是讓人麻爪。尤其是在python中,時間格式的數據有可能是string,有可能是datetime64,有可能是object等等,每種都需要用不同的方法去調整,就算是同一種格式,也可能在處理的過程中遇到各種各樣的問題。那么我就把自己親身經歷過的各種崩潰瞬間分享一下,提供的解決辦法不一定是最優解,僅供參考,大家有更好的辦法也可以評論留言。
一、多模式結果處理中的時間格式不統一
這是之前在處理CMIP6模式中遇到的問題,我一共是處理了24個CMIP6模式(SSP245,以及historical兩個情景),里邊就有那么幾個模式獨立特行,跟別人格格不入。大部分的模式很乖,很好處理,時間格式默認就是datetime64格式的,比如以ACCESS-CM2模式的tas變量為例(僅利用CDO通過mergetime和線性插值預處理了原始文件):
import xarray as xr
f = xr.open_dataset('./ACCESS-CM2/tas/t2m.nc')
print(f)
文件信息如下:
<pre style="box-sizing: border-box; overflow: auto; font-family: monospace; font-size: 14px; display: block; padding: 1px 0px; margin: 0px; line-height: inherit; color: rgb(0, 0, 0); word-break: break-all; overflow-wrap: break-word; background-color: rgb(255, 255, 255); border: 0px; border-radius: 0px; white-space: pre-wrap; vertical-align: baseline; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><xarray.Dataset>
Dimensions: (lat: 73, lon: 144, nb2: 2, time: 31390)
Coordinates:
* lon (lon) float64 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
* lat (lat) float64 -90.0 -87.5 -85.0 -82.5 ... 82.5 85.0 87.5 90.0
* time (time) datetime64[ns] 2015-01-01T12:00:00 ... 2100-12-31T12:00:00
Dimensions without coordinates: nb2
Data variables:
time_bnds (time, nb2) datetime64[ns] ...
tas (time, lat, lon) float32 ...
那我們可以看到,其時間格式為datetime64[ns],這是最方便的格式,可以直接索引。比如說我需要選取2015年到2099年冬季的數據,那么直接通過以下指令就可以實現:
tas = f['tas'].loc[f.time.dt.month.isin([12,1,2])].loc['2015-12-01':'2100-02-28']
print(tas)
輸出信息如下:
<xarray.DataArray 'tas' (time: 7650, lat: 73, lon: 144)>
[80416800 values with dtype=float32]
Coordinates:
* lon (lon) float64 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
* lat (lat) float64 -90.0 -87.5 -85.0 -82.5 -80.0 ... 82.5 85.0 87.5 90.0
* time (time) datetime64[ns] 2015-12-01T12:00:00 ... 2100-02-28T12:00:00
Attributes:
standard_name: air_temperature
long_name: Near-Surface Air Temperature
units: K
comment: near-surface (usually, 2 meter) air temperature
cell_methods: area: time: mean
history: 2019-11-08T10:42:10Z altered by CMOR: Treated scalar dime...
那么,當遇到另一種時間格式時,就不能直接這樣索引了,比如說接下來的這個:
f = xr.open_dataset('/data/home/zenggang/yxy/CMIP6/SSP245/CanESM5/tas/t2m.nc')
print(f)
其文件信息如下:
<xarray.Dataset>
Dimensions: (lat: 73, lon: 144, nb2: 2, time: 31390)
Coordinates:
* lon (lon) float64 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
* lat (lat) float64 -90.0 -87.5 -85.0 -82.5 ... 82.5 85.0 87.5 90.0
* time (time) object 2015-01-01 12:00:00 ... 2100-12-31 12:00:00
Dimensions without coordinates: nb2
Data variables:
time_bnds (time, nb2) object ...
tas (time, lat, lon) float32 ...
注意咯,這里是object,那么我們還以之前的方法索引會發生什么呢?
tas = f['tas'].loc[f.time.dt.month.isin([12,1,2])].loc['2015-12-01':'2100-02-28']
哎?格式不支持,好氣哦!
不過Xarray庫提供了一個函數來進行轉換,因此這個格式也還算友好。
import xarray as xr
f = xr.open_dataset('/data/home/zenggang/yxy/CMIP6/SSP245/CanESM5/tas/t2m.nc')
f= f.assign_coords(time = f.indexes['time'].to_datetimeindex())
tas = f['tas'].loc[f.time.dt.month.isin([12,1,2])].loc['2015-12-01':'2100-02-28']
后來,我又遇到了另一種情況,就是有的模式的時間是0時(比如說 1979-01-01 00:00:00),而有的模式的時間是12時(比如說 1979-01-01 12:00:00),如果需要統一的話,只需要搭配datetime庫進行調整即可,比如說將所有的12時變為0時:
from datetime import datetime, timedelta
f = f.assign_coords(time = (f.indexes['time'].to_datetimeindex()- timedelta(hours=12)))
利用assign_coords重新聲明坐標時,將所有的時間減去了12小時。(利用assign_coords也可以重新聲明經緯度坐標等等)
還有一類模式有點反人類(不點名了),我目前也沒想到太好的辦法來處理,它所使用的不是標準日歷,他每個月都有30天,2月份也有30天,很離譜,這在xarray庫中無法被識別(轉換),簡單粗暴點就直接用CDO刪掉了每年的這一天,或者只能將全年的數據索引出來,通過reshape成(年,月,日,經度,緯度)的方法通過下標序號索引。
二、非標準日歷數據
這個是我處理CAM模式數據時遇到的問題(run了30年的敏感性試驗,生成的數據的時間是從0001-01-01到0032-01-01),后面處理時讓我瞬間崩潰了,生成的數據是cftime庫中的時間格式,但是表面還是寫著object類型,通過前邊介紹的assign_coords的方法根本行不通,報錯理由是
深層的原因就不在這里解釋了,只提供一個解決的方法,思路還是將時間轉為datetime64格式:
import xarray as xr
import pandas as pd
f = xr.open_dataset('./TS.nc')
time_tmp1 = f.indexes['time']
time_tmp2 = []
j = 0
for i in range(time_tmp1.shape[0]):
if ((i+1)%365==0):
j = j+1
else:
j = j
tmp = time_tmp1[i].replace(year = 2000+j)
a = tmp.year
b = tmp.month
c = tmp.day
time_tmp2.append(pd.to_datetime('{}/{}/{}'.format(a,b,c),format='%Y/%m/%d'))
f = f.assign_coords(time = time_tmp2)
中間那一大段的循環的目的,是將每個時間坐標統統加上2000年,使其從0001-01-01到0030-12-31變為2000-01-01到2032-01-01,這樣就又可以通過正常的時間索引方法進行索引了。
該文件默認格式的時間是cftime,其存在一個方法,.replace可以替換年,月,日,然后通過.year, .month,.day獲取新的年月日,再通過pd.to_datetime函數變為datetime64格式的日期,最后重新賦值給f即可。
原始f信息:
<xarray.Dataset>
Dimensions: (lat: 96, lon: 144, nb2: 2, time: 11681)
Coordinates:
* lon (lon) float64 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
* lat (lat) float64 -90.0 -88.11 -86.21 -84.32 ... 86.21 88.11 90.0
* time (time) object 0001-01-01 00:00:00 ... 0033-01-01 00:00:00
Dimensions without coordinates: nb2
Data variables:
time_bnds (time, nb2) object ...
TREFHT (time, lat, lon) float32 ...
TS (time, lat, lon) float32 ...
TSMN (time, lat, lon) float32 ...
TSMX (time, lat, lon) float32 ...
修改后f信息:
<xarray.Dataset>
Dimensions: (lat: 96, lon: 144, nb2: 2, time: 11681)
Coordinates:
* lon (lon) float64 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
* lat (lat) float64 -90.0 -88.11 -86.21 -84.32 ... 86.21 88.11 90.0
* time (time) datetime64[ns] 2000-01-01 2000-01-02 ... 2032-01-01
Dimensions without coordinates: nb2
Data variables:
time_bnds (time, nb2) object ...
TREFHT (time, lat, lon) float32 ...
TS (time, lat, lon) float32 ...
TSMN (time, lat, lon) float32 ...
TSMX (time, lat, lon) float32 ...
三、批量索引特定時間的數據
我主要的方向是極端天氣氣候事件,因此嘗嘗需要批量索引特定時間的數據,比如說先統計40年里發生的極端(高溫,低溫)事件的日期,然后從40年的逐日位勢高度數據里提取出發生事件當天的位勢高度異常,有沒有不寫一大長串的判斷循環就能實現的方法呢?
方法1:
在統計事件時,順便統計好每次事件發生時的時間坐標索引序號(是這40年里的第幾天發生的)
然后通過:
z = np.array([z_all[i] for i in number])
實現索引,其中,z為取出的發生極端事件當天的位勢高度,z_all為這40年的逐日位勢高度,number是每次事件發生時的時間坐標索引序號,比如說事件是這40年里的第3,6,8...天發生的,那么number就是[2,5,7,...],這里z和z_all都是np.array
實際上這里的本質還是通過循環解決的。
方法2:通過xarray直接索引特定時間
首先在統計事件時,統計好發生每次事件的時間,datetime64格式的!,索引時通過
z = z_all.loc[date]
直接完成。
其中z和z_all是xr.DataArray格式,date是統計好的時間列表。
并且,對于分析事件個例來說,通常還會提取爆發前后的一段時間進行逐日的分析,那么通過如下方法實現:
z_1day_before = z_all.loc[date - np.timedelta64(1,'D')]
這里以發生前一天的數據提取為例。D表示day
小結
看到這里大家應該可以發現,能否用python處理好數據的關鍵在于編程者能否將不同的庫的方法和函數靈活的組合起來,單純依靠基礎語言或者單一的庫,很難實現一些靈活的操作,也無法體現出py的方便和強大之處。有時候我看到曾經寫過的一些代碼,都想不起來當時是怎么能想到這樣處理的,因為靈活就意味著多種解決方法。實在不行的話,遇事不決就簡單粗暴的使用循環判斷吧!