@@ -19,9 +19,18 @@ def __init__(self, ds, trajectory_dim, obs_dim, time_varname):
1919 super ().__init__ (ds , trajectory_dim , obs_dim , time_varname )
2020
2121 def timestep (self ):
22- """Time step between observations in seconds."""
23- return ((self .ds .time [1 ] - self .ds .time [0 ]) /
24- np .timedelta64 (1 , 's' )).values
22+ """
23+ Calculate the time step between observations in seconds.
24+
25+ Returns
26+ -------
27+ xarray.DataArray
28+ Time step between observations with a single value.
29+ Attributes:
30+ - units: seconds
31+ """
32+ timestep = ((self .ds .time [1 ] - self .ds .time [0 ]) / np .timedelta64 (1 , 's' )).values
33+ return xr .DataArray (timestep , name = "timestep" , attrs = {"units" : "seconds" })
2534
2635 def is_1d (self ):
2736 return True
@@ -30,54 +39,78 @@ def is_2d(self):
3039 return False
3140
3241 def to_2d (self , obs_dim = 'obs' ):
42+ """
43+ Convert the dataset to a 2D representation.
44+
45+ Parameters
46+ ----------
47+ obs_dim : str, optional
48+ Name of the observation dimension in the 2D representation, by default 'obs'.
49+
50+ Returns
51+ -------
52+ xarray.Dataset
53+ Dataset with a 2D representation of trajectories.
54+ """
3355 ds = self .ds .copy ()
34- time = ds [self .time_varname ].rename ({
35- self .time_varname : obs_dim
36- }).expand_dims (
37- dim = {
38- self .trajectory_dim : ds .sizes [self .trajectory_dim ]
39- }).assign_coords ({self .trajectory_dim : ds [self .trajectory_dim ]})
40- # TODO should also add cf_role here
56+ time = ds [self .time_varname ].rename ({self .time_varname : obs_dim }).expand_dims (
57+ dim = {self .trajectory_dim : ds .sizes [self .trajectory_dim ]}
58+ ).assign_coords ({self .trajectory_dim : ds [self .trajectory_dim ]})
4159 ds = ds .rename ({self .time_varname : obs_dim })
4260 ds [self .time_varname ] = time
43- ds [obs_dim ] = np .arange (0 , ds .sizes [obs_dim ])
44-
61+ ds [obs_dim ] = xr .DataArray (np .arange (0 , ds .sizes [obs_dim ]), dims = [obs_dim ])
4562 return ds
4663
4764 def to_1d (self ):
4865 return self .ds .copy ()
4966
5067 def time_to_next (self ):
68+ """
69+ Calculate the time difference to the next observation.
70+
71+ Returns
72+ -------
73+ xarray.DataArray
74+ Time difference to the next observation with the same dimensions as the dataset.
75+ Attributes:
76+ - units: seconds
77+ """
5178 time_step = self .ds .time [1 ] - self .ds .time [0 ]
52- return time_step
79+ return xr . DataArray ( time_step , name = "time_to_next" , attrs = { "units" : "seconds" })
5380
5481 def velocity_spectrum (self ):
55-
82+ """
83+ Calculate the velocity spectrum for a single trajectory.
84+
85+ Returns
86+ -------
87+ xarray.DataArray
88+ Velocity spectrum with dimensions ('period').
89+ Attributes:
90+ - units: power
91+ """
5692 if self .ds .sizes [self .trajectory_dim ] > 1 :
57- raise ValueError (
58- 'Spectrum can only be calculated for a single trajectory' )
93+ raise ValueError ('Spectrum can only be calculated for a single trajectory' )
5994
6095 u , v = self .velocity_components ()
6196 u = u .squeeze ()
6297 v = v .squeeze ()
6398 u = u [np .isfinite (u )]
6499 v = v [np .isfinite (v )]
65100
66- timestep_h = (self .ds .time [1 ] - self .ds .time [0 ]) / np .timedelta64 (
67- 1 , 'h' ) # hours since start
101+ timestep_h = (self .ds .time [1 ] - self .ds .time [0 ]) / np .timedelta64 (1 , 'h' ) # hours since start
68102
69103 ps = np .abs (np .fft .rfft (np .abs (u + 1j * v )))
70104 freq = np .fft .rfftfreq (n = u .size , d = timestep_h .values )
71105 freq [0 ] = np .nan
72106
73107 da = xr .DataArray (
74108 data = ps ,
75- name = 'velocity spectrum ' ,
109+ name = 'velocity_spectrum ' ,
76110 dims = ['period' ],
77- coords = {'period' : (['period' ], 1 / freq , {
78- 'units' : 'hours'
79- })},
80- attrs = {'units' : 'power' })
111+ coords = {'period' : (['period' ], 1 / freq , {'units' : 'hours' })},
112+ attrs = {'units' : 'power' }
113+ )
81114
82115 return da
83116
@@ -146,9 +179,27 @@ def skill_matching(traj, expected):
146179
147180 return s
148181
149- def skill (self , expected , method = 'liu-weissberg' , ** kwargs ) -> xr .Dataset :
150-
151- expected = expected .traj # Normalise
182+ def skill (self , expected , method = 'liu-weissberg' , ** kwargs ) -> xr .DataArray :
183+ """
184+ Calculate the skill score for trajectories.
185+
186+ Parameters
187+ ----------
188+ expected : Traj1d
189+ Expected trajectory dataset.
190+ method : str, optional
191+ Skill score method, by default 'liu-weissberg'.
192+ **kwargs : dict
193+ Additional arguments for the skill score calculation.
194+
195+ Returns
196+ -------
197+ xarray.DataArray
198+ Skill score with dimensions matching the dataset.
199+ Attributes:
200+ - method: Skill score calculation method.
201+ """
202+ expected = expected .traj # Normalize
152203 expected_trajdim = expected .trajectory_dim
153204 self_trajdim = self .trajectory_dim
154205
@@ -157,7 +208,8 @@ def skill(self, expected, method='liu-weissberg', **kwargs) -> xr.Dataset:
157208 if numtraj_self > 1 and numtraj_expected > 1 and numtraj_self != numtraj_expected :
158209 raise ValueError (
159210 'Datasets must have the same number of trajectories, or a single trajectory. '
160- f'This dataset: { numtraj_self } , expected: { numtraj_expected } .' )
211+ f'This dataset: { numtraj_self } , expected: { numtraj_expected } .'
212+ )
161213
162214 numobs_self = self .ds .sizes [self .obs_dim ]
163215 numobs_expected = expected .ds .sizes [expected .obs_dim ]
0 commit comments