|
4 | 4 | "cell_type": "markdown", |
5 | 5 | "metadata": {}, |
6 | 6 | "source": [ |
7 | | - "# Tutorial on how to handle Parcels output\n", |
8 | | - "This tutorial covers the format of the trajectory output exported by Parcels. **Parcels does not include advanced analysis or plotting functionality**, which users are suggested to write themselves to suit their research goals. Here we provide some starting points to discover the parcels output.\n", |
| 7 | + "# Tutorial on how to analyse Parcels output" |
| 8 | + ] |
| 9 | + }, |
| 10 | + { |
| 11 | + "cell_type": "markdown", |
| 12 | + "metadata": {}, |
| 13 | + "source": [ |
| 14 | + "This tutorial covers the format of the trajectory output exported by Parcels. **Parcels does not include advanced analysis or plotting functionality**, which users are suggested to write themselves to suit their research goals. Here we provide some starting points to explore the parcels output files yourself.\n", |
9 | 15 | "\n", |
10 | 16 | "* [**Reading the output file**](#Reading-the-output-file)\n", |
11 | 17 | "* [**Trajectory data structure**](#Trajectory-data-structure)\n", |
12 | 18 | "* [**Analysis**](#Analysis)\n", |
13 | 19 | "* [**Plotting**](#Plotting)\n", |
14 | 20 | "* [**Animations**](#Animations)\n", |
15 | 21 | "\n", |
16 | | - "First we need to create some parcels output to analyse. We simulate some particles using the setup described in the [Delay start tutorial](https://nbviewer.jupyter.org/github/OceanParcels/parcels/blob/master/parcels/examples/tutorial_delaystart.ipynb)." |
| 22 | + "First we need to create some parcels output to analyse. We simulate a set of particles using the setup described in the [Delay start tutorial](https://nbviewer.jupyter.org/github/OceanParcels/parcels/blob/master/parcels/examples/tutorial_delaystart.ipynb)." |
17 | 23 | ] |
18 | 24 | }, |
19 | 25 | { |
|
62 | 68 | "metadata": {}, |
63 | 69 | "source": [ |
64 | 70 | "## Reading the output file\n", |
65 | | - "### netCDF4\n", |
66 | | - "The [`parcels.particlefile.ParticleFile`](https://oceanparcels.org/gh-pages/html/#parcels.particlefile.ParticleFile) class creates a netCDF file to store the particle trajectories. It uses the [**`netCDF4` module**](https://unidata.github.io/netcdf4-python/netCDF4/index.html), which is also suitable to open and read the files for analysis. The [`Dataset` class](https://unidata.github.io/netcdf4-python/netCDF4/index.html#netCDF4.Dataset) opens a netCDF file in reading mode by default. Data can be accessed with the `Dataset.variables` dictionary which can return (masked) numpy arrays." |
| 71 | + "### Using the netCDF4 package\n", |
| 72 | + "The [`parcels.particlefile.ParticleFile`](https://oceanparcels.org/gh-pages/html/#parcels.particlefile.ParticleFile) class creates a netCDF file to store the particle trajectories. It uses the [**`netCDF4` package**](https://unidata.github.io/netcdf4-python/netCDF4/index.html), which is also suitable to open and read the files for analysis. The [`Dataset` class](https://unidata.github.io/netcdf4-python/netCDF4/index.html#netCDF4.Dataset) opens a netCDF file in reading mode by default. Data can be accessed with the `Dataset.variables` dictionary which can return (masked) numpy arrays." |
67 | 73 | ] |
68 | 74 | }, |
69 | 75 | { |
|
129 | 135 | "cell_type": "markdown", |
130 | 136 | "metadata": {}, |
131 | 137 | "source": [ |
132 | | - "### xarray\n", |
133 | | - "An often used alternative which also comes with the parcels installation is [**xarray**](http://xarray.pydata.org/en/stable/index.html). Its labelled arrays allow for intuitive and accessible handling of data stored in the netCDF format." |
| 138 | + "### Using the xarray package\n", |
| 139 | + "An often-used alternative to netCDF4, which also comes with the parcels installation, is [**xarray**](http://xarray.pydata.org/en/stable/index.html). Its labelled arrays allow for intuitive and accessible handling of data stored in the netCDF format." |
134 | 140 | ] |
135 | 141 | }, |
136 | 142 | { |
|
203 | 209 | "metadata": {}, |
204 | 210 | "source": [ |
205 | 211 | "## Trajectory data structure\n", |
206 | | - "The data in the netCDF file are organised according to the [CF-conventions](http://cfconventions.org/cf-conventions/v1.6.0/cf-conventions.html#discrete-sampling-geometries) implemented with the [NCEI trajectory template](http://www.nodc.noaa.gov/data/formats/netcdf/v2.0/trajectoryIncomplete.cdl). The data is stored in a **two-dimensional array** with the dimensions **`traj`** and **`obs`**. Each particle trajectory is essentially stored as a time series where the coordinate data (**lon**, **lat**, **time**) are a function of the observation (`obs`).\n", |
| 212 | + "The data in the netCDF file are organised according to the [CF-conventions](http://cfconventions.org/cf-conventions/v1.6.0/cf-conventions.html#discrete-sampling-geometries) implemented with the [NCEI trajectory template](http://www.nodc.noaa.gov/data/formats/netcdf/v2.0/trajectoryIncomplete.cdl). The data is stored in a **two-dimensional array** with the dimensions **`traj`** and **`obs`**. Each particle trajectory is essentially stored as a time series where the coordinate data (**`lon`**, **`lat`**, **`time`**) are a function of the observation (`obs`).\n", |
207 | 213 | "\n", |
208 | | - "In the current output dataset we find **10 particles** and **13 observations**. Not every particle has 13 observations however; since we released particles at different times some particle trajectories are shorter than others." |
| 214 | + "The output dataset used here contains **10 particles** and **13 observations**. Not every particle has 13 observations however; since we released particles at different times some particle trajectories are shorter than others." |
209 | 215 | ] |
210 | 216 | }, |
211 | 217 | { |
|
241 | 247 | "cell_type": "markdown", |
242 | 248 | "metadata": {}, |
243 | 249 | "source": [ |
244 | | - "Note how the first observation occurs at a different time for each trajectory. **`obs` != time**" |
| 250 | + "Note how the first observation occurs at a different time for each trajectory. **`obs` != `time`**" |
245 | 251 | ] |
246 | 252 | }, |
247 | 253 | { |
248 | 254 | "cell_type": "markdown", |
249 | 255 | "metadata": {}, |
250 | 256 | "source": [ |
251 | 257 | "## Analysis\n", |
252 | | - "Sometimes trajectories are analysed like they are stored: as individual time series. If we want to study the distance travelled as a function of time, the time we are interested in is the time relative to the start of the each particular trajectory: the array operations are simple since each trajectory is analysed as a function of `obs`. The time variable is only needed to express the results in the correct units." |
| 258 | + "Sometimes, trajectories are analysed as they are stored: as individual time series. If we want to study the distance travelled as a function of time, the time we are interested in is the time relative to the start of the each particular trajectory: the array operations are simple since each trajectory is analysed as a function of **`obs`**. The time variable is only needed to express the results in the correct units." |
253 | 259 | ] |
254 | 260 | }, |
255 | 261 | { |
|
260 | 266 | "source": [ |
261 | 267 | "x = data_xarray['lon'].values\n", |
262 | 268 | "y = data_xarray['lat'].values\n", |
263 | | - "distance = np.cumsum(np.sqrt(np.square(np.diff(x))+np.square(np.diff(y))),axis=1) # d = (dx^2 + dy^2)^(1/2)" |
| 269 | + "distance = np.cumsum(np.sqrt(np.square(np.diff(x))+np.square(np.diff(y))),axis=1) # d = (dx^2 + dy^2)^(1/2)" |
264 | 270 | ] |
265 | 271 | }, |
266 | 272 | { |
|
293 | 299 | "source": [ |
294 | 300 | "plt.figure()\n", |
295 | 301 | "ax = plt.axes()\n", |
296 | | - "ax.set_ylabel('Distance travelled')\n", |
| 302 | + "ax.set_ylabel('Distance travelled [m]')\n", |
297 | 303 | "ax.set_xlabel('observation',weight='bold')\n", |
298 | 304 | "d_plot = ax.plot(distance.transpose())\n", |
299 | 305 | "plt.show()" |
|
332 | 338 | "\n", |
333 | 339 | "plt.figure()\n", |
334 | 340 | "ax = plt.axes()\n", |
335 | | - "ax.set_ylabel('Distance travelled')\n", |
336 | | - "ax.set_xlabel('time [hours]',weight='bold')\n", |
| 341 | + "ax.set_ylabel('Distance travelled [m]')\n", |
| 342 | + "ax.set_xlabel('time since release [hours]',weight='bold')\n", |
337 | 343 | "d_plot = ax.plot(t[1:],distance.transpose())\n", |
338 | 344 | "plt.show()" |
339 | 345 | ] |
|
356 | 362 | "mean_lon_x = []\n", |
357 | 363 | "mean_lat_x = []\n", |
358 | 364 | "\n", |
359 | | - "timerange = np.arange(np.nanmin(data_xarray['time'].values), np.nanmax(data_xarray['time'].values)+np.timedelta64(delta(hours=2)), delta(hours=2)) # timerange in nanoseconds\n", |
| 365 | + "timerange = np.arange(np.nanmin(data_xarray['time'].values), \n", |
| 366 | + " np.nanmax(data_xarray['time'].values)+np.timedelta64(delta(hours=2)), \n", |
| 367 | + " delta(hours=2)) # timerange in nanoseconds\n", |
360 | 368 | "\n", |
361 | 369 | "for time in timerange:\n", |
362 | | - " if np.all(np.any(data_xarray['time']==time,axis=1)): # if all trajectories share an observation at time\n", |
363 | | - " mean_lon_x += [np.nanmean(data_xarray['lon'].where(data_xarray['time']==time).values)] # find the data that share the time\n", |
364 | | - " mean_lat_x += [np.nanmean(data_xarray['lat'].where(data_xarray['time']==time).values)] # find the data that share the time" |
| 370 | + " if np.all(np.any(data_xarray['time']==time,axis=1)): # if all trajectories share an observation at time\n", |
| 371 | + " mean_lon_x += [np.nanmean(data_xarray['lon'].where(data_xarray['time']==time).values)] # find the data that share the time\n", |
| 372 | + " mean_lat_x += [np.nanmean(data_xarray['lat'].where(data_xarray['time']==time).values)] # find the data that share the time" |
365 | 373 | ] |
366 | 374 | }, |
367 | 375 | { |
|
381 | 389 | "mean_lon_n = []\n", |
382 | 390 | "mean_lat_n = []\n", |
383 | 391 | "\n", |
384 | | - "timerange = np.arange(np.nanmin(time_netcdf4), np.nanmax(time_netcdf4)+delta(hours=2).total_seconds(), delta(hours=2).total_seconds()) \n", |
| 392 | + "timerange = np.arange(np.nanmin(time_netcdf4), \n", |
| 393 | + " np.nanmax(time_netcdf4)+delta(hours=2).total_seconds(), \n", |
| 394 | + " delta(hours=2).total_seconds()) \n", |
385 | 395 | "\n", |
386 | 396 | "for time in timerange:\n", |
387 | | - " if np.all(np.any(time_netcdf4 == time, axis=1)): # if all trajectories share an observation at time\n", |
388 | | - " mean_lon_n += [np.mean(lon_netcdf4[time_netcdf4 == time])] # find the data that share the time\n", |
389 | | - " mean_lat_n += [np.mean(lat_netcdf4[time_netcdf4 == time])] # find the data that share the time" |
| 397 | + " if np.all(np.any(time_netcdf4 == time, axis=1)): # if all trajectories share an observation at time\n", |
| 398 | + " mean_lon_n += [np.mean(lon_netcdf4[time_netcdf4 == time])] # find the data that share the time\n", |
| 399 | + " mean_lat_n += [np.mean(lat_netcdf4[time_netcdf4 == time])] # find the data that share the time" |
390 | 400 | ] |
391 | 401 | }, |
392 | 402 | { |
|
424 | 434 | "metadata": {}, |
425 | 435 | "source": [ |
426 | 436 | "## Plotting\n", |
427 | | - "Parcels output consists of particle trajectories through time and space. An important way to explore patterns in this information is to draw the trajectories in space. Parcels provides the [`ParticleSet.show()`](https://oceanparcels.org/gh-pages/html/#parcels.particleset.ParticleSet.show) method to quickly look at results, but users are encouraged to create their own figures, for example by using the comprehensive [**matplotlib**](https://matplotlib.org/) library. Here we show a basic setup on how to process the parcels output into trajectory plots and animations.\n", |
| 437 | + "Parcels output consists of particle trajectories through time and space. An important way to explore patterns in this information is to draw the trajectories in space. Parcels provides the [`ParticleSet.show()`](https://oceanparcels.org/gh-pages/html/#parcels.particleset.ParticleSet.show) method and the [`plotTrajectoryFile`](https://oceanparcels.org/gh-pages/html/#module-scripts.plottrajectoriesfile) script to quickly plot at results, but users are encouraged to create their own figures, for example by using the comprehensive [**matplotlib**](https://matplotlib.org/) library. Here we show a basic setup on how to process the parcels output into trajectory plots and animations.\n", |
428 | 438 | "\n", |
429 | | - "Here are some other packages to help you make beautiful figures:\n", |
| 439 | + "Some other packages to help you make beautiful figures are:\n", |
430 | 440 | "* [**cartopy**](https://scitools.org.uk/cartopy/docs/latest/), a map-drawing tool especially compatible with matplotlib\n", |
431 | 441 | "* [**cmocean**](https://matplotlib.org/cmocean/), a set of beautiful colormaps" |
432 | 442 | ] |
|
486 | 496 | "cell_type": "markdown", |
487 | 497 | "metadata": {}, |
488 | 498 | "source": [ |
489 | | - "Plotting trajectories only shows information about how particles move in space. To discover some patterns, you want to be able to see how the particles move in both time and space. To do this matplotlib offers an [animation package](https://matplotlib.org/3.3.2/api/animation_api.html). Here we show how to use the [`FuncAnimation`](https://matplotlib.org/3.3.2/api/_as_gen/matplotlib.animation.FuncAnimation.html#matplotlib.animation.FuncAnimation) class to animate parcels trajectory data.\n", |
| 499 | + "Trajectory plots like the ones above can become very cluttered for large sets of particles. To better see patterns, it's a good idea to create an animation in time and space. To do this, matplotlib offers an [animation package](https://matplotlib.org/3.3.2/api/animation_api.html). Here we show how to use the [`FuncAnimation`](https://matplotlib.org/3.3.2/api/_as_gen/matplotlib.animation.FuncAnimation.html#matplotlib.animation.FuncAnimation) class to animate parcels trajectory data.\n", |
490 | 500 | "\n", |
491 | | - "To correctly reveal the patterns in time we must remember that [the `obs` dimension does not necessarily correspond to the `time` variable](#Trajectory-data-structure). In the animation of the particles we usually want to draw the points at each consecutive moment in time, not necessarily at each moment since the start of the trajectory. To do this we must [select the correct data](#Conditional-selection) in each rendering." |
| 501 | + "To correctly reveal the patterns in time we must remember that [the `obs` dimension does not necessarily correspond to the `time` variable](#Trajectory-data-structure). In the animation of the particles, we usually want to draw the points at each consecutive moment in time, not necessarily at each moment since the start of the trajectory. To do this we must [select the correct data](#Conditional-selection) in each rendering." |
492 | 502 | ] |
493 | 503 | }, |
494 | 504 | { |
|
501 | 511 | "from IPython.display import HTML\n", |
502 | 512 | "\n", |
503 | 513 | "outputdt = delta(hours=2)\n", |
504 | | - "timerange = np.arange(np.nanmin(data_xarray['time'].values), np.nanmax(data_xarray['time'].values)+np.timedelta64(outputdt), outputdt) # timerange in nanoseconds" |
| 514 | + "timerange = np.arange(np.nanmin(data_xarray['time'].values),\n", |
| 515 | + " np.nanmax(data_xarray['time'].values)+np.timedelta64(outputdt), \n", |
| 516 | + " outputdt) # timerange in nanoseconds" |
505 | 517 | ] |
506 | 518 | }, |
507 | 519 | { |
|
0 commit comments