Skip to content

Commit e54e045

Browse files
authored
Merge pull request #187 from knutfrode/dev
Fundamental change in sel and seltime methods - now not masking NaN-v…
2 parents dff4f97 + f212ee9 commit e54e045

4 files changed

Lines changed: 40 additions & 25 deletions

File tree

examples/example_concat.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
#%%
3333
# Concatenate two 2D datasets (with observation dimension).
3434

35-
dc = xr.concat((d1, d2), dim='trajectory')
35+
dc = xr.concat((d1, d2), dim='trajectory', join='outer')
3636
dc = dc.traj.condense_obs()
3737
print(dc)
3838

@@ -51,16 +51,17 @@
5151

5252
#%%
5353
# Concatenating two 1D datasets will cause a lot of NaNs to be inserted.
54-
dc = xr.concat((d1, d2), dim='trajectory')
54+
d1 = d1.drop_duplicates('time')
55+
d2 = d2.drop_duplicates('time')
56+
dc = xr.concat((d1, d2), dim='trajectory', join='outer')
5557
print(dc)
5658

5759
assert np.all(ds.lat.values[~np.isnan(ds.lat.values)] ==
5860
dc.lat.values[~np.isnan(dc.lat.values)])
5961

6062
#%%
6163
# Converting to 2D and condensing the dataset will give a cleaner result.
62-
6364
dc = xr.concat((d1.traj.to_2d(), d2.traj.to_2d()),
64-
dim='trajectory').traj.condense_obs()
65+
dim='trajectory', join='outer').traj.condense_obs()
6566
print(dc)
6667
assert dc.sizes['obs'] == ds.sizes['obs']

trajan/traj.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,7 @@ def distance_to_next(self):
752752
distance = xr.DataArray(distance, coords=lonfrom.coords, dims=lon.dims)
753753
distance = xr.concat((distance, distance.isel({self.obs_dim: -1})),
754754
dim=self.obs_dim) # repeating last time step to
755+
distance = distance.assign_coords({self.obs_dim: np.arange(self.ds.sizes[self.obs_dim])})
755756
return distance
756757

757758
def azimuth_to_next(self):
@@ -787,6 +788,8 @@ def azimuth_to_next(self):
787788
azimuth_forward = xr.concat(
788789
(azimuth_forward, azimuth_forward.isel({self.obs_dim: -1})),
789790
dim=self.obs_dim) # repeating last time step to
791+
azimuth_forward = azimuth_forward.assign_coords(
792+
{self.obs_dim: np.arange(self.ds.sizes[self.obs_dim])})
790793
return azimuth_forward
791794

792795
def velocity_components(self):

trajan/traj1d.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,10 @@ def sel(self, *args, **kwargs):
270270
return self.ds.sel(*args, **kwargs)
271271

272272
def seltime(self, t0=None, t1=None):
273-
return self.ds.sel({self.time_varname: slice(t0, t1)})
273+
# Preserving NaN in trajectories, as these provide information about gaps / segments
274+
subset_indices = np.where((self.ds[self.time_varname] >= pd.to_datetime(t0)) &
275+
(self.ds[self.time_varname] <= pd.to_datetime(t1)))[0]
276+
return self.ds.isel({self.obs_dim: slice(subset_indices.min(), subset_indices.max()+1)})
274277

275278
def iseltime(self, i):
276279
return self.ds.isel({self.time_varname: i})

trajan/traj2d.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,7 @@ def insert_nan_where(self, condition):
135135

136136
nd[varname] = da.astype(var.dtype)
137137

138-
nd = nd.drop_vars(
139-
(self.obs_dim, self.trajectory_dim)) # Remove coordinates
138+
nd = nd.assign_coords({self.obs_dim: np.arange(max_obs)}) # New obs index 1..N
140139

141140
return nd
142141

@@ -164,11 +163,14 @@ def drop_where(self, condition):
164163

165164
# Ensure all trajectories have equal length by padding with NaN at the end
166165
trajs = [
167-
t.pad(pad_width={self.obs_dim: (0, newlen - t.sizes[self.obs_dim])})
166+
t.pad(pad_width={self.obs_dim: (0, newlen - t.sizes[self.obs_dim])}).
167+
assign_coords({self.obs_dim: np.arange(newlen)}) # New obs index 1..N
168168
for t in trajs
169169
]
170170

171-
return xr.concat(trajs, dim=self.trajectory_dim)
171+
ds = xr.concat(trajs, dim=self.trajectory_dim, join='exact')
172+
173+
return ds
172174

173175
@__require_obs_dim__
174176
def condense_obs(self) -> xr.Dataset:
@@ -220,8 +222,7 @@ def condense_obs(self) -> xr.Dataset:
220222
ds = ds.isel({self.obs_dim: slice(0, maxN)})
221223

222224
# Write new observation coordinate.
223-
obs = np.arange(0, maxN)
224-
ds = ds.assign_coords({self.obs_dim: obs})
225+
ds = ds.assign_coords({self.obs_dim: np.arange(0, maxN)})
225226

226227
return ds
227228

@@ -246,11 +247,12 @@ def append(self, da, obs_dims=None):
246247

247248
def sel(self, *args, **kwargs):
248249
return self.ds.groupby(self.trajectory_dim).map(
249-
lambda d: ensure_time_dim(d.traj.to_1d().sel(*args, **kwargs), self
250-
.time_varname).traj.to_2d(self.obs_dim))
250+
lambda d: ensure_time_dim(d.traj.to_1d().traj.sel(*args, **kwargs), self.time_varname).traj.to_2d(self.obs_dim))
251251

252252
def seltime(self, t0=None, t1=None):
253-
return self.sel({self.time_varname: slice(t0, t1)})
253+
# Using TrajAn sel method that allows NaN
254+
return self.ds.groupby(self.trajectory_dim).map(
255+
lambda d: ensure_time_dim(d.traj.to_1d().traj.seltime(t0, t1), self.time_varname).traj.to_2d(self.obs_dim))
254256

255257
@__require_obs_dim__
256258
def iseltime(self, i):
@@ -275,18 +277,24 @@ def to_1d(self):
275277
)
276278
else:
277279
ds = self.ds.copy()
278-
ds = ds.dropna(self.obs_dim, how='all')
280+
281+
# Do not remove NaN's since these now have meaning
282+
#ds = ds.dropna(self.obs_dim, how='all')
283+
284+
# For 1D objects, we rename obs-dimension to name of time variable
285+
# so that time becomes a coordinate variable,
286+
# i.e. typically: time(traj, obs) -> time(time)
279287
ds = ds.assign_coords({self.obs_dim: ds[self.time_varname]})
280-
ds = ds.drop_vars(self.time_varname).rename(
281-
{self.obs_dim: self.time_varname})
282-
283-
ds[self.time_varname] = ds[self.time_varname].squeeze(
284-
self.trajectory_dim)
285-
ds = ds.loc[{self.time_varname: ~pd.isna(ds[self.time_varname])}]
286-
_, ui = np.unique(ds[self.time_varname], return_index=True)
287-
ds = ds.isel({self.time_varname: ui})
288-
ds = ds.assign_coords(
289-
{self.trajectory_dim: ds[self.trajectory_dim]})
288+
ds = ds.drop_vars(self.time_varname).rename({self.obs_dim: self.time_varname})
289+
ds[self.time_varname] = ds[self.time_varname].squeeze(self.trajectory_dim)
290+
291+
# Do not remove NaN's since these now have meaning
292+
#ds = ds.loc[{self.time_varname: ~pd.isna(ds[self.time_varname])}]
293+
#_, ui = np.unique(ds[self.time_varname], return_index=True)
294+
#ds = ds.isel({self.time_varname: ui})
295+
296+
# Keep trajectory dimension, although always length 1 for 1D objects
297+
ds = ds.assign_coords({self.trajectory_dim: ds[self.trajectory_dim]})
290298

291299
return ds
292300

0 commit comments

Comments
 (0)