Conversation
…nes across all vertical levels, preserving the vertical structure of the data
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
|
Hey @rajeeja I'm not sure if the implementation here is what we are looking for with vertical cross sections. Let me start with an example. Below is temperature variable that varies in the vertical direction along Plotting one of the levels looks like the following:
Let's say we want to compute a vertical cross section along a line of constant latitude. We need to essentially sample points along that line to create our raster. The resulting plot should look something like this:
Notice this is very similar to us simply doing: uxds['t_isobaric'].cross_section.constant_latitude(lat).isel(Time=0, nIsoLevelsT=0).plot()
So the result above is similar to the bottom most plot in the stacked vertical cross section. However, instead of plotting the true Grid geometry, we are plotting the resulting raster I'm happy to look at this more tomorrow when we meet. Here's a great reference too |
|
Here's the code I used to generate the vertical cross section plot. import numpy as np
import xarray as xr
import matplotlib.pyplot as plt
lat = 40.0
samples = 150
subset = uxds['t_isobaric'].cross_section.constant_latitude(lat).isel(Time=0)
# Points to Sample
lons = np.linspace(-180, 180, samples)
lats = np.ones_like(lons) * lat
# find which face(s) each point hits
faces_along_lat = subset.uxgrid.get_faces_containing_point(
np.column_stack((lons, lats)), return_counts=False
)
# collapse to a 1D index: use -1 where no face hits
face_idx = np.array(
[row[0] if row else -1 for row in faces_along_lat],
dtype=int
)
# pre‐allocate and fill
n_levels = subset.sizes['nIsoLevelsT']
data = np.full((samples, n_levels), fill_value=np.nan, dtype=subset.dtype)
valid = face_idx >= 0
data[valid, :] = subset.values[face_idx[valid], :]
# wrap in DataArray so you keep coords
da = xr.DataArray(
data,
dims=('sample', 'level'),
coords={
'sample': np.arange(samples),
'lon' : ('sample', lons),
'lat' : ('sample', lats),
},
)
fig, ax = plt.subplots(figsize=(10, 4))
da.plot(x='lon', y='level', ax=ax, cmap='Blues')
ax.set_xlabel("Longitude")
ax.set_ylabel("Isobaric level index")
ax.set_title("Vertical Cross Section at 40 degrees latitude")
ax.set_xlim(-150, -50)
plt.tight_layout() |
|
Thanks, I will look into this and try to modify the code tonight, let's talk about it tomorrow when we meet. |
| def vertical_constant_latitude( | ||
| self, | ||
| lat: float, | ||
| vertical_coord: str = None, | ||
| inverse_indices: Union[ | ||
| List[str], Set[str], Tuple[List[str], bool], bool | ||
| ] = False, | ||
| ): |
There was a problem hiding this comment.
I'd lean towards not duplicating these methods here, since the core idea of a constant_latitude or constant_longitude cross-section is independent of any leading/vertical dimensions.
Instead, maybe we could adding the following parameters to enable us to sample the resulting spatial cross section along the transect, with some number of samples like in the example I shared above.
# default cross section, returns a UxDataArray with an associated Grid
uxds['t2m'].cross_section.constant_latitude(lat=45.0)
# one example, we can brainstorm other ideas
uxds['t2m'].cross_section.constant_latitude(lat=45.0, n_samples=100)
There was a problem hiding this comment.
Implementation for vertical cross-sections looks great, but I think I am a bit worried about the API direction this PR proposes:
- Changing the API of the user functions
uxda.cross_section.constant_latitude()anduxda.cross_section.constant_longitude()significantly.- The semantic seems to be changing. We have created and advertised these functions as them creating brand new subset grids so far during tutorials, etc. There is knowledge based on that and there might be many workflows created using them in that way. Now changing the semantics not only needs to be justified and not be right away, but also would break many UXarray workflows.
- Related to this, return types being converted to
xarray
- Related to this, return types being converted to
- The semantic seems to be changing. We have created and advertised these functions as them creating brand new subset grids so far during tutorials, etc. There is knowledge based on that and there might be many workflows created using them in that way. Now changing the semantics not only needs to be justified and not be right away, but also would break many UXarray workflows.
- I think there should still be a difference between how we implemented (horizontal) cross-sections so far (i.e. in the way described above that generates a subset grid) and how vertical cross-sections are meant to be (structured constant lat/lon data through sampling for mainly visual inspection purposes)
- I don't know if aiming to implement them all together in the same API at the cost of significant changes to the existing API is a good idea. Even though all of them are called "cross-sections", there is actually a usage difference between the existing ones and the vertical cross-section I believe, e.g. one can run stats on the output of horizontal cross-sections we used so far, but not on the output of what this PR implements I believe (at least no stats on the actual grid topology).
|
Thanks for the feedback. We've discussed a few of these points during our co-working
|
While this could help much for the sake of clear code and API, the difference of purposes between those existing and newer functions makes me hesitant about this, i.e. accessing two different purposes through the same API with parameters might not be the best option. Also, the same functions returning different objects (either |
How about keeping the existing functions exactly the same but adding a new one for the vertical only, which would look something like this:
|
I'm not too concerned about this, as long as:
Once an unstructured grid is sampled, such as onto a structured grid, we should no longer return UXarray objects, it should fall back to Xarray so that we do not need to reimplement things. |
|
The Notebook needs to be rethought completely to make a distinction on how to call for horizontal and vertical cross sections with the current implementation it does get a little tricky, but I agree with Philip that it will be simpler and more maintainable if we use a common interface for both. |
|
Pinging @brianpm for any feedback on these, especially regarding any expectations from the research/science applications for these types of workflows. Using the implementation in #1321, creating vertical (and also temporal) cross section plots is seamless since we can utilize existing Xarray functionality. Here's the data variable I'm using (taken from @rytam2's E3SM dataset) in our docs (which can be found here for download) # Compute a interpolated cross section by sampling the line of constant latitude
lat_cs = uxds['T'].compute().cross_section.constant_latitude(lat=45.0, interpolate=True)# vertical cross section
lat_cs.isel(time=0).plot(cmap='inferno')
# vertical cross section contour
lat_cs.isel(time=0).plot.contourf(levels=10, cmap='inferno')
# temporal cross section
lat_cs.isel(lev=0, time=[0, 1, 2, 3, 4]).plot(cmap='inferno')
|
This looks like a difference in perspective and it's understandable. We apparently need input from the actual users for the right perspective. To me though, the cross-section is designed to subset data on different references, and the existing functions are only either zonal or meridional with all those vertical levels still present (at least with the design so far), and despite the current understanding, they are not "horizontal". Vertical levels being preserved does not make the either horizontal or vertical.
And, because of this distinction, I don't think they should all be part of the same API. They are different in nature than the existing functions.
This cross-sections definitions make total sense to me, but I believe designing all of them in the same API with the help of optional parameters, which most of the time is easy to be overseen by the user, creates a lot of confusion at the user's end. |
Due to my comments above, I am still concerned about this way forward, considering the potential confusions for the end user of these functions.
I agree, but not from the same API through optional params |
Let's all try to collect as much user input on this possible then reunite to discuss. |
|
Thanks for the feedback @erogluorhan This implementation from MetPy could serve as a good design reference. |
While I understand the concern here, I don't believe that introducing additional parameters to extend functionality would lead to more confusion than introduction brand-new methods, which would be very similar to existing ones.
From what I've seen in the NCL and MetPy examples, generally speaking a cross-section is defined by taking some transect defined by two arbitrary lat-lon points and obtaining the data that falls between the path formed by connecting them. The ideal of vertical or temporal cross sections is then done after the fact by selecting the dimension you'd want to compare along the intersection, such as my example above for the Level and Time dimensions.
That's correct, but preserving vertical (or other) levels does enable us to perform vertical cross-section analysis, since we would have reduced our data from the grid geometry onto the samples along the transect. The great part of using Xarray for our implementation is that dimensions are preserved, and the user can dictate which ones they want to keep or drop, or which ones they'd like to select for plotting. I don't believe explicitly handling these through our implementation is necessary. |
Assuming that we do not make an explicit distinction between vertical, temporal, or other types of applications of cross sections, I'm not sure what the best way to split up the grid-based and interpolated cross sections would be. For example, we currently have:
We could add additional methods to the existing
Or, as I've put together in #1321, we could dictate the output by using an
|
|
Hey @falkojudt , we were able to come up with a vertical cross section implementation here but are having a hard time deciding how to design its function signatures, i.e. to add into existing functions or create new ones etc (Please see th conversation in this PR). From a meteorological use perspective, please let us know if you'd have any suggestions. |
|
Just wanted to chime in with a few things to consider.
|
I think having these functions designed as the main cross-section functionality at the time is giving us the actual hardship now re design. Maybe these current functions were/are more suitable for Also, we had a good conversation at today's Raijin monthly meeting and there were similar thoughts there; for instance, cross-sections should never return a new grid topology and rather only return some form of Maybe we need to consider moving away from providing these current functions under
If we decide to perform the above suggestion re current functionality, I think we'd have a couple options:
All that said, I still think that combining the existing and new ones in the same API through optional parameters (e.g. |
|
I agree with @erogluorhan that the current implementation of |
I like this idea. # Subset the original data to include elements that intersect a line of constant latitude
data_along_lat = uxds['T'].subset.constant_latitude(lat=45.0)
# Interpolate the result onto a structured Xarary dataset
data_along_lat_interpolated = data_along_lat.interp(lon=np.linspace(-180, 180, 10)Right now, |
|
Here are my thoughts:
|
This is something we can implement.
Is this something that can already be done easily with Xarray? |
I am not sure, hopefully someone else would know. |
|
Thanks for the great feedback and discussion here. We are now not implement vertical crossection as a separate method. Subsetting will handle the grid informed crossection that will return the data on the original grid elements, while, the crossection method will sample data along intersection. Closing this infavor of #1321 |








#1296