@@ -19,9 +19,18 @@ def __init__(self, ds, trajectory_dim, obs_dim, time_varname):
19
19
super ().__init__ (ds , trajectory_dim , obs_dim , time_varname )
20
20
21
21
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" })
25
34
26
35
def is_1d (self ):
27
36
return True
@@ -30,54 +39,78 @@ def is_2d(self):
30
39
return False
31
40
32
41
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
+ """
33
55
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 ]})
41
59
ds = ds .rename ({self .time_varname : obs_dim })
42
60
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 ])
45
62
return ds
46
63
47
64
def to_1d (self ):
48
65
return self .ds .copy ()
49
66
50
67
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
+ """
51
78
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" })
53
80
54
81
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
+ """
56
92
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' )
59
94
60
95
u , v = self .velocity_components ()
61
96
u = u .squeeze ()
62
97
v = v .squeeze ()
63
98
u = u [np .isfinite (u )]
64
99
v = v [np .isfinite (v )]
65
100
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
68
102
69
103
ps = np .abs (np .fft .rfft (np .abs (u + 1j * v )))
70
104
freq = np .fft .rfftfreq (n = u .size , d = timestep_h .values )
71
105
freq [0 ] = np .nan
72
106
73
107
da = xr .DataArray (
74
108
data = ps ,
75
- name = 'velocity spectrum ' ,
109
+ name = 'velocity_spectrum ' ,
76
110
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
+ )
81
114
82
115
return da
83
116
@@ -146,9 +179,27 @@ def skill_matching(traj, expected):
146
179
147
180
return s
148
181
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
152
203
expected_trajdim = expected .trajectory_dim
153
204
self_trajdim = self .trajectory_dim
154
205
@@ -157,7 +208,8 @@ def skill(self, expected, method='liu-weissberg', **kwargs) -> xr.Dataset:
157
208
if numtraj_self > 1 and numtraj_expected > 1 and numtraj_self != numtraj_expected :
158
209
raise ValueError (
159
210
'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
+ )
161
213
162
214
numobs_self = self .ds .sizes [self .obs_dim ]
163
215
numobs_expected = expected .ds .sizes [expected .obs_dim ]
0 commit comments