Repository navigation
Support reading and writing multidimensional and object columns in ECSV - #11569
Conversation
|
👋 Thank you for your draft pull request! Do you know that you can use |
2deb55d to
8dea89b
Compare
|
Hello @taldcroft 👋! It looks like you've made some changes in your pull request, so I've checked the code again for style. There are no PEP8 style issues with this pull request - thanks! 🎉 Comment last updated at 2021-04-28 17:28:33 UTC |
ba2a9ba to
47582b6
Compare
47582b6 to
bc6082a
Compare
hamogu
left a comment
There was a problem hiding this comment.
I'd by lying if I claimed to understand all of this. The tests look reasonable, and I keep learning, just be reading the code.
Taking a step back though, I'm slightly worried that we move into this direction: https://xkcd.com/927/
The current ECSV is essentially a CSV with some added metadata, but most normal table readers (e.g. Excel) can still make sense of the data, even if some information (say, the unit of a column) is lost. With every addition, we are moving away from that. It may be possible to read this new thing into Excel, at least for fixed-length columns; you just get a lot of extra columns, some of which have only [ or ] in them. For variable length columns, it's getting even harder.
So, we have to ask: What is the purpose of ECSV? Is it a new universal storage format that -unlike fits or hdf5- is human-readable because the data is stored as ASCII (even if it's not human writable)? Or is a generally supported CSV format that essentially any CSV reader can read with some extra bells and whistles, so that, if I read is back into astropy, I get the column units automatically as opposed to reading the "meta-info" by hand and taking care of the units "by hand" in my following calculations (the way essentially anyone did it before QTable and all the non-astropy world still does it)?
That's a question that's somewhat bigger than this PR. Those two visions are also not mutually exclusive, e.g. the docs could say "ESCV can write any astropy data in any dimension, but if you want to read it back in outside of astropy, it might be best to limit yourself to scalar columns with numbers or strings in them and not arbitrary objects. Here is an example how to make a 2d column into three 1d colums or a coordinate into separate RA, DEC by hand before writing".
As a real-world example: I've adopted ECSV for most the "CALDB - prototype" files (I'm aware that an actual CALDB has a strict format definition, but there are reasons we don't go there quite yet) for ARCUS. The problem is now that I'm the only one in the collaboration that can edit these files, because every by-hand edit brakes the YAML somewhere (white space, commas, line breaks). It's convenient that I can rely on Astropy reading it back in and using the correct units for, e.g. the dimensions of a CCD, but it also caused significant extra work. If I did it again, I would opt for a normal CSV that gets read by io.ascii indenpended of the exact character separator etc, because guessing usually "just works", and just hope that it's all good or check for consistency (e.g. CCDs need to have roughly this size in mm, if they are > 1 m, then someone probably put the info in in the wrong unit). So, for my case, ECSV is on the cusp of being too complicated to be useful already. Again, this is not mutually exclusive, as I can continue to only write simple 1D columns, but it's something to consider.
| FAST_CLASSES = {} | ||
|
|
||
|
|
||
| def _check_multidim_table(table, max_ndim=1): |
There was a problem hiding this comment.
I almost wonder if this should live the table not in io.ascii, since it's applicable more generally. I don't know the internals of votable, fits etc, but I can imagine that non-ascii formats might have situations that also warrant this check.
There was a problem hiding this comment.
On the other hand, it's really only one lines of check, so it can be re-implemented there, if needed.
There was a problem hiding this comment.
Makes sense, this could go in table_helpers.
|
@aaryapatil You asked for review experience. If you want to look through a complex, gold-standard PR to see how everything looks when it's great, but that might still benefit from general thoughts then this one is for you! At least I learned new stuff about astropy and Python by just reading through the code. |
| FAST_CLASSES = {} | ||
|
|
||
|
|
||
| def _check_multidim_table(table, max_ndim=1): |
There was a problem hiding this comment.
Makes sense, this could go in table_helpers.
|
@hamogu - your concerns are well-founded. Version one of this #11155 was closer to the idea of all the data being readable as simple CSV. It basically flattened an N-d column into the required number of 1-d columns and then used a column naming convention. After the good discussion in #11368 I came to the opinion that the JSON approach is better. But backing up, the first branch point is whether to just throw an exception at N-d columns ("can't do it") or "find a way". I think that "finding a way" is not quite an example of that famous XKCD since we are extending an existing format. It has been encouraging to see ECSV getting some traction. Part the driver for #11368 and this PR is the possibility that the GAIA data processing consortium would use ECSV as an ASCII table format that is compatible with both TOPCAT and astropy. That really made me want to "find a way"! 😄 A distinct need there is to support variable length arrays (ala FITS), and for that the flattening approach just doesn't work. So we came to JSON encoding, at which point it makes sense to do the N-d arrays and object columns using the same JSON encoding. JSON is simple so the encode/decode tends to be fast, and of course it is supported in every imaginable language. |
|
p.s. @hamogu - thanks for the super-quick review! |
|
GAIA is a good use case and certainly much more high-profile than me. How would this get into TOPCAT? IS there coordinated TOPCAT development? |
nstarman
left a comment
There was a problem hiding this comment.
I'm really looking forward to this. I'm not familiar much with this portion of the astropy codebase... so while I poke around, I mostly limited myself here to docs comments. I've had docstrings on my mind recently 😅 .
| col_vals = [json.loads(val) for val in col.str_vals] | ||
| # Make a numpy object array of col_vals to look for None | ||
| data = np.array(col_vals, dtype=object) | ||
| mask = (data == None) # noqa |
There was a problem hiding this comment.
To mask only the == None comparison warning, I think it's
| mask = (data == None) # noqa | |
| mask = (data == None) # noqa: E711 |
| kind = np.dtype(col.dtype).kind | ||
| data[mask] = {'U': '', 'S': b''}.get(kind, 0) | ||
| # Finally make a MaskedArray with the filled data + mask | ||
| col.data = np.ma.array(data.astype(col.dtype), mask=mask) |
There was a problem hiding this comment.
Does @mhvk 's Masked class offer an alternative ? or is this for a serialization reason.
There was a problem hiding this comment.
Right now the Masked class is fully experimental and not meant for any "production" code.
| - `astropy.coordinates.EarthLocation` | ||
| - `astropy.coordinates.SkyCoord` | ||
| - `astropy.table.NdarrayMixin` | ||
| - Coordinate representation types such as `astropy.coordinates.SphericalRepresentation` |
There was a problem hiding this comment.
It would be great to have an example in the docs of putting a Representation in a Table.
Also, when did general CoordinateFrame objects become allowed in Tables?!? I thought it was only SkyCoord.
@hamogu, the STIL java table I/O library has ECSV support since June 2020 (somewhat stimulated by internal Gaia/DPAC suggestions), and STIL is used for table I/O both in TOPCAT and in GACS, ESA's Gaia archive service software. TOPCAT can therefore read/write ECSV already, and ECSV is going to be be provided as an output option in the next GACS release (soon). Up till now ECSV has only been capable of scalar-valued columns, while Gaia EDR3 (and later releases) will contain a number of array-valued columns. That discrepancy led the Gaia Archive Scientist to ask about the possibility of an array-valued enhancement to ECSV, and I (Gaia/DPAC member, STIL/TOPCAT author, someone with a small amount of Astropy contact) raised #11368 accordingly. Regarding future development: once this is written up in the APE, I will implement corresponding functionality in STIL, so it will make its way into TOPCAT and GACS. If there are other documented and language-neutral enhancements to the ECSV format in the future, I'd aim to continue to track them in STIL. |
|
@mbtaylor et al. - here is a view of the output ECSV that comes out of the new code. It is a little unfortunate that ECSV uses the double quote for quoting and that JSON does the same. The ECSV rule is that a single Space delimitedComma delimitedSource codePython implementation detailWhen a typed variable length array gets read back in, there are a couple of options:
Right now it does (2), so you get an array of |
0301eca to
c97bee7
Compare
|
@taldcroft - definitely see your logic, but of course for FITS we also know that in reality there are corrupt headers (e.g., with units not complying with the standard) and we still parse them. But that is the headers, not the data, so perhaps not quite the same. In any case, quite happy to go with exceptions now - changing from those to warnings if forced to is easier than realizing warnings was a mistake and then trying to change to exceptions! Separately: ready for a last review? |
|
Sounds good, we'll leave as an exception. I agree it is easier to remove an exception than go the other way. I see that all tests are passing, so this is indeed ready for final review. |
|
I do plan to squash this down at the end, there was a fair bit of meandering and once this is done I don't see too much value in all the intermediate commits. |
There was a problem hiding this comment.
This is definitely a gold-standard PR ;-) I got a chance to look through this final (?) version, and it looks great. Especially like the sub-type format. I won't say I understood everything (I learnt a lot about Python), but the documentation is super clear in terms of letting the user know that ECSV is recommended for lossless round-tripping and supports multi-dimensionality!
mhvk
left a comment
There was a problem hiding this comment.
Some leftover small nits here and there, but otherwise looks great! Tests are very nice too!
| def _check_multidim_table(self, table, max_ndim=1): | ||
| """Check that ``table`` has only 1-d columns. | ||
|
|
||
| If a reader class supports N-d columns then override this method. |
There was a problem hiding this comment.
OK, that makes sense. Note that the docstring still needs to be updated - if a reader supports N-d, it should increase max_ndim.
| out = serialize.represent_mixins_as_columns(table) | ||
| return out | ||
|
|
||
| def _check_multidim_table(self, table, max_ndim=1): |
There was a problem hiding this comment.
This method can now be removed, since the check will pass already.
| ndarrays within a Table. This is a subclass of numpy.ndarray | ||
| and has the same initialization options as ndarray(). | ||
| """ | ||
| __module__ = 'astropy.table.table' |
There was a problem hiding this comment.
My guess would be that this happens because the ndarray_mixin module is not included in the table API sections.
| @@ -37,6 +37,7 @@ | |||
| 'astropy.coordinates.earth.EarthLocation', | |||
| 'astropy.coordinates.sky_coordinate.SkyCoord', | |||
| 'astropy.table.table.NdarrayMixin', | |||
There was a problem hiding this comment.
Might it make sense to move the location here to where it actually is? (Maybe only possible if one does not define __module__ - which still feels like a hack best avoided...)
| return table | ||
|
|
||
|
|
||
| class ArrayWrapperInfo(ParentDtypeInfo): |
There was a problem hiding this comment.
OK, sounds good. Maybe add a comment to that extent? (or raise an issue)
|
FYI see astropy/astropy-APEs#68. |
|
@mhvk - I think I have addressed the last round of comments. 🤞 again on docs and tests (passing locally). |
mhvk
left a comment
There was a problem hiding this comment.
OK, looks good! Let's get this in - a really nice addition!!
|
Thanks for the really useful and timely review @mhvk! |
|
And I'll get to work on the What's New. |
Fix problem with multidim Table repr in Jupyter introduced in #11569
|
Might have broken backward compatibility? See #11803 |
Fix problem with multidim Table repr in Jupyter introduced in #11569
|
Just for the record, I had to backport one commit from here (6544aa8) to LTS to fix CI failures. |
Description
This is a second go at #11155 but using the ideas and specifications developed in #11368, in particular using JSON to serialize an array-valued element. This PR actually started from the code in #11155 since there was some useful infrastructure.
Key changes and features:
objecttype column is serialized using JSON on each item.ndim > 1) is serialized usingjson.dumps(col[idx].tolist())on each item.shapeattribute in the ECSV header is added in this case, whereshaperefers to the shape of each item (i.e. it iscol.shape[1:]).str(value), regardless of whether the column has aformatset.formatswhen writing) does not apply since values are always now represented withstror via JSON.io.asciireadersastropy.io.coreto help me out.NdArrayMixinandArrayWrapperclasses to work properlyrepresent_mixins_as_columns()to ensure it actually did the job. It used to be thatArrayWrapperwas not actually working. This means that theECSVmethods can assumeColumnsubclasses when actually writing out the data.TODO:
shapeparameter for write where the input corresponds to a variable length list.Fixes #11368