Skip to content

Commit 14d829c

Browse files
committed
example: concatenating trajectories
1 parent ffa80e1 commit 14d829c

File tree

3 files changed

+87
-12
lines changed

3 files changed

+87
-12
lines changed

examples/example_concat.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
Concatenating drifter datasets
3+
==============================
4+
"""
5+
import numpy as np
6+
import lzma
7+
import matplotlib.pyplot as plt
8+
import cartopy.crs as ccrs
9+
import xarray as xr
10+
import pandas as pd
11+
import trajan as ta
12+
13+
#%%
14+
# Importing a dataset with two drifters in the Barents Sea
15+
with lzma.open('barents.nc.xz') as barents:
16+
ds = xr.open_dataset(barents)
17+
ds.load()
18+
19+
assert 'obs' in ds.dims
20+
21+
#%%
22+
# Split into two observational datasets for this example
23+
24+
ds = ds.rename(drifter_names='trajectory').traj.condense_obs()
25+
print(ds)
26+
27+
d1 = ds.isel(trajectory=0).traj.to_1d().traj.to_2d()
28+
d2 = ds.isel(trajectory=1).traj.to_1d().traj.to_2d()
29+
print("d1=", d1)
30+
print("d2=", d2)
31+
32+
#%%
33+
# Concatenate two 2D datasets (with observation dimension).
34+
35+
dc = xr.concat((d1, d2), dim='trajectory')
36+
dc = dc.traj.condense_obs()
37+
print(dc)
38+
39+
assert np.all(ds.lat.values[~np.isnan(ds.lat.values)] ==
40+
dc.lat.values[~np.isnan(dc.lat.values)])
41+
42+
#%%
43+
# Concatenating two 1D datasets, with observations at different times.
44+
45+
d1 = d1.traj.to_1d(
46+
) # trivial conversion since `d1` only contains a single trajectory. No need for gridtime.
47+
d2 = d2.traj.to_1d() # Also trivial for d2.
48+
print(d1)
49+
50+
assert 'obs' not in d1.dims
51+
52+
#%%
53+
# Concatenating two 1D datasets will cause a lot of NaNs to be inserted.
54+
dc = xr.concat((d1, d2), dim='trajectory')
55+
print(dc)
56+
57+
assert np.all(ds.lat.values[~np.isnan(ds.lat.values)] ==
58+
dc.lat.values[~np.isnan(dc.lat.values)])
59+
60+
#%%
61+
# Converting to 2D and condensing the dataset will give a cleaner result.
62+
63+
dc = xr.concat((d1.traj.to_2d(), d2.traj.to_2d()),
64+
dim='trajectory').traj.condense_obs()
65+
print(dc)
66+
assert dc.sizes['obs'] == ds.sizes['obs']

trajan/traj1d.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ def to_2d(self, obs_dim='obs'):
3434
time = ds[self.time_varname].rename({
3535
self.time_varname: obs_dim
3636
}).expand_dims(
37-
dim={self.trajectory_dim: ds.sizes[self.trajectory_dim]})
37+
dim={
38+
self.trajectory_dim: ds.sizes[self.trajectory_dim]
39+
}).assign_coords({self.trajectory_dim: ds[self.trajectory_dim]})
3840
# TODO should also add cf_role here
3941
ds = ds.rename({self.time_varname: obs_dim})
4042
ds[self.time_varname] = time
@@ -119,19 +121,23 @@ def skill_along_trajectory(self, expected, **kwargs):
119121

120122
def skill_matching(traj, expected):
121123
traj = traj.where(np.isfinite(traj.lon), drop=True)
122-
expected_overlap = expected.sel(time=slice(traj.time[0], traj.time[-1]))
124+
expected_overlap = expected.sel(
125+
time=slice(traj.time[0], traj.time[-1]))
123126
mask = False
124-
if traj.sizes[self.obs_dim] != expected_overlap.sizes[expected_overlap.traj.obs_dim]:
125-
traj = traj.sel(time=slice(expected_overlap.time[0], expected_overlap.time[-1]))
127+
if traj.sizes[self.obs_dim] != expected_overlap.sizes[
128+
expected_overlap.traj.obs_dim]:
129+
traj = traj.sel(time=slice(expected_overlap.time[0],
130+
expected_overlap.time[-1]))
126131
mask = True
127132
s = traj.traj.skill(expected_overlap, **kwargs)
128133
if mask is True:
129-
s = s*np.nan # Mask out the skill score if the trajectories are not of equal length
134+
s = s * np.nan # Mask out the skill score if the trajectories are not of equal length
130135
s['start_time'] = traj.time[0]
131136
s['end_time'] = traj.time[-1]
132137
return s
133138

134-
s = self.ds.groupby(self.trajectory_dim).apply(skill_matching, expected=expected)
139+
s = self.ds.groupby(self.trajectory_dim).apply(skill_matching,
140+
expected=expected)
135141
s['start_lon'] = start_lon
136142
s['start_lat'] = start_lat
137143
s['end_lon'] = end_lon
@@ -151,8 +157,7 @@ def skill(self, expected, method='liu-weissberg', **kwargs) -> xr.Dataset:
151157
if numtraj_self > 1 and numtraj_expected > 1 and numtraj_self != numtraj_expected:
152158
raise ValueError(
153159
'Datasets must have the same number of trajectories, or a single trajectory. '
154-
f'This dataset: {numtraj_self}, expected: {numtraj_expected}.'
155-
)
160+
f'This dataset: {numtraj_self}, expected: {numtraj_expected}.')
156161

157162
numobs_self = self.ds.sizes[self.obs_dim]
158163
numobs_expected = expected.ds.sizes[expected.obs_dim]
@@ -162,9 +167,10 @@ def skill(self, expected, method='liu-weissberg', **kwargs) -> xr.Dataset:
162167
)
163168

164169
diff = np.max(
165-
np.abs((self.ds[self.obs_dim] -
166-
expected.ds[expected.obs_dim]).astype('timedelta64[s]').astype(
167-
np.float64)))
170+
np.abs((
171+
self.ds[self.obs_dim] -
172+
expected.ds[expected.obs_dim]).astype('timedelta64[s]').astype(
173+
np.float64)))
168174
if not np.isclose(diff, 0):
169175
raise ValueError(
170176
f"The two datasets must have approximately equal time coordinates, maximum difference: {diff} seconds. Consider using `gridtime` to interpolate one of the datasets."
@@ -193,7 +199,8 @@ def skill(self, expected, method='liu-weissberg', **kwargs) -> xr.Dataset:
193199
else:
194200
raise ValueError(f"Unknown skill-score method: {method}.")
195201

196-
s = skill_method(expected.traj.tlon, expected.traj.tlat, ds.traj.tlon, ds.traj.tlat, **kwargs)
202+
s = skill_method(expected.traj.tlon, expected.traj.tlat, ds.traj.tlon,
203+
ds.traj.tlat, **kwargs)
197204

198205
newcoords = dict(ds.lon.sizes)
199206
newcoords.pop('time')

trajan/traj2d.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ def to_1d(self):
254254
ds = ds.loc[{self.time_varname: ~pd.isna(ds[self.time_varname])}]
255255
_, ui = np.unique(ds[self.time_varname], return_index=True)
256256
ds = ds.isel({self.time_varname: ui})
257+
ds = ds.assign_coords(
258+
{self.trajectory_dim: ds[self.trajectory_dim]})
257259

258260
return ds
259261

0 commit comments

Comments
 (0)