Repository navigation
Make Table.as_array return empty arrays for empty tables - #8647
Conversation
mhvk
left a comment
There was a problem hiding this comment.
This looks good, but some in-line comments. Ideally, @taldcroft has a look, but if that doesn't work before the actual 3.2 release, I'd be happy to approve.
Codecov Report
@@ Coverage Diff @@
## master #8647 +/- ##
==========================================
- Coverage 86.95% 86.95% -0.01%
==========================================
Files 399 399
Lines 59326 59330 +4
Branches 1100 1100
==========================================
Hits 51588 51588
- Misses 7097 7101 +4
Partials 641 641
Continue to review full report at Codecov.
|
|
What about backporting this fix to LTS? |
|
@mcara - did something go wrong in the changelog? Lots of entries unrelated to your have been changed; it would be good to stick to just your item. |
|
@bsipocz - yes, it can likely be backported cleanly, so that would be OK, I think. But it would be good if @taldcroft could have a look... |
This comment has been minimized.
This comment has been minimized.
|
@mcara - as @bsipocz changed the milestone, the changelog entry is now in the wrong place. Apologies to ask you again to change things, but could you move it down to bug fixes in 2.0.13? (It will automatically be propagated to 3.2 as well.) Otherwise, this all looks good. I always really like it when we reduce the code! But one further favour: do also add a test that we will not get back your original problem! I.e., |
|
@mhvk Great idea to test astropy/astropy/table/table.py Lines 880 to 882 in 331767d >1 (allow 0). Then things work ... kind of...
Specifically, the issue is with the current implementation of assert isinstance(t.as_array(), np.ndarray)instead of Would that be OK? |
|
With regard to Empty tables:In [44]: t = Table(data=None, masked=True)
In [45]: t
Out[45]:
<Table masked=True length=0>
<No columns>
In [46]: t == None
/.../lib/python3.6/site-packages/astropy-4.0.dev24522-py3.6-macosx-10.12-x86_64.egg/astropy/table/table.py:2764: FutureWarning:
elementwise comparison failed; returning scalar instead,
but in the future will perform elementwise comparison
result = self.as_array() == other
/.../lib/python3.6/site-packages/astropy-4.0.dev24522-py3.6-macosx-10.12-x86_64.egg/astropy/table/table.py:2764:
FutureWarning: elementwise == comparison failed and returning scalar instead;
this will raise an error or perform elementwise comparison in the future.
result = self.as_array() == other
Out[46]: array([], dtype=bool)For empty arrays Non-empty Tables. Comparison to tablesIn [50]: t = Table(data=[[1,4], [3, 1.0]])
In [51]: t == t
Out[51]: array([ True, True])This seem to work "as expected" (about this later). For Non-empty Tables. Comparison to scalarsIn [55]: t == 1
/.../lib/python3.6/site-packages/astropy-4.0.dev24522-py3.6-macosx-10.12-x86_64.egg/astropy/table/table.py:2764:
FutureWarning: elementwise == comparison failed and returning scalar instead;
this will raise an error or perform elementwise comparison in the future.
result = self.as_array() == other
Out[55]: FalseSo, here we can see that comparison with scalars does not work. It must be done column-by-column.
Equality for In [58]: Table(data=[[1,4], [3, 1.0]]) == [[1,4], [3, 1.0]]
/.../lib/python3.6/site-packages/astropy-4.0.dev24522-py3.6-macosx-10.12-x86_64.egg/astropy/table/table.py:2764: FutureWarning:
elementwise == comparison failed and returning scalar instead;
this will raise an error or perform elementwise comparison in the future.
result = self.as_array() == other
Out[58]: FalseI would have hoped that this will actually compare the elements of the table. By comparison, In [1]: import pandas as pd
In [2]: df = pd.DataFrame({1: [10], 2: [20]})
In [3]: df == 10
Out[3]:
1 2
0 True FalseBut this is a bigger/larger discussion to have and IMO it deserves its own issue. |
|
@mhvk Would the following test suffice? # Regression test for gh-8640
assert isinstance(self.t == None, np.ndarray)
assert (self.t == None).size == 0 |
|
By the way, even a test like this one: In [2]: Table(masked=True).as_array() == None
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-2-7c0f737df615> in <module>
----> 1 Table(masked=True).as_array() == None
~/.../lib/python3.6/site-packages/numpy/ma/core.py in __eq__(self, other)
4043 and other considered equal only if both were fully masked.
4044 """
-> 4045 return self._comparison(other, operator.eq)
4046
4047 def __ne__(self, other):
~/.../lib/python3.6/site-packages/numpy/ma/core.py in _comparison(self, other, compare)
3996 # Now take care of the mask; the merged mask should have an item
3997 # masked if all fields were masked (in one and/or other).
-> 3998 mask = (mask == np.ones((), mask.dtype))
3999
4000 else:
ValueError: No fields found.
In [3]: Table(masked=False).as_array() == None
/.../bin/ipython:1: FutureWarning: elementwise == comparison failed and returning scalar instead;
this will raise an error or perform elementwise comparison in the future.
Out[3]: Falsedoes not work consistently: works for regular arrays but not for masked arrays. |
|
xref: numpy/numpy#13438 |
|
@mhvk By the way, adding back if len(self.columns) == 0:
return empty_init(0)makes |
|
We probably can fix the crash also by making the final array creation be |
|
@taldcroft - do you want to have a quick look here? It looks OK to me. @mcara - I missed the details: what in the end went wrong with just removing the |
|
@mcara - this looks good, thanks for getting into this and fixing this inconsistency. The code and tests look fine, my only concern now is about the scope of the change. To me this really looks like an API change since the output type is now different for a relatively simple operation that might occur in practice ( So I would propose moving this back to 3.2 and calling it an API change. In addition, the fix which now allows |
|
@mcara - regarding comparisons, thanks for bringing some fresh perspective. Can you open a new issue with some of the examples you have put here so we can discuss this in a clean issue? One quick note on a difference between |
|
Given feature freeze for 3.2 is gone, I am milestoning this to 3.2.1 instead, but @bsipocz can always re-milestone back to 3.2 if she so desires. I agree that this is more of an API change than a bug fix. |
|
Given it has the API change (but in fact more like a bugfix with an API change), I would be more comfortable to ship it in the main 3.2 release rather than in a bugfix. |
|
Sorry for the delay in answering. If you do not specify the type and leave it to an empty list _________________________________________________________________________________ TestRemove.test_delitem1[masked] _________________________________________________________________________________
self = <astropy.table.tests.test_table.TestRemove object at 0x136100978>, table_types = <astropy.table.tests.conftest.table_types.<locals>.TableTypes object at 0x136100e48>
def test_delitem1(self, table_types):
self._setup(table_types)
del self.t['a']
assert self.t.columns.keys() == []
assert self.t.as_array().size == 0
# Regression test for gh-8640
assert not self.t
> assert isinstance(self.t == None, np.ndarray)
astropy/table/tests/test_table.py:958:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
astropy/table/table.py:2755: in __eq__
result = (self.as_array().data == other) & (self.mask == false_mask)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Table length=0>
<No columns>, other = array([()], dtype=[])
def __eq__(self, other):
if isinstance(other, Table):
other = other.as_array()
if self.masked:
if isinstance(other, np.ma.MaskedArray):
result = self.as_array() == other
else:
# If mask is True, then by definition the row doesn't match
# because the other array is not masked.
false_mask = np.zeros(1, dtype=[(n, bool) for n in self.dtype.names])
result = (self.as_array().data == other) & (self.mask == false_mask)
else:
if isinstance(other, np.ma.MaskedArray):
# If mask is True, then by definition the row doesn't match
# because the other array is not masked.
false_mask = np.zeros(1, dtype=[(n, bool) for n in other.dtype.names])
result = (self.as_array() == other.data) & (other.mask == false_mask)
else:
> result = self.as_array() == other
E ValueError: No fields found.
astropy/table/table.py:2763: ValueError |
I probably disagree with the first point: we have arrived to this issue by accident and I doubt people would use do this kind of comparisons such as I agree with the second point that this is not a bug. However, as you mention |
No. I assume this will be wrapped up in a few days, so getting it into the RC won't be an issue. |
|
So, do I move the changelog entry? to what version? |
|
To the bugfix section of 3.2, and another entry for the API change section of 3.2. I think. But @taldcroft might chime in. |
|
I am not familiar with the procedures here, I got confused by #8647 (comment) and mentioning of |
|
@mcara - We're in feature freeze for 3.2. But the release is not out and bugfixes can be still added to it. We started to 3.2.1 milestone PRs that are expected to be finished after 3.2 is out or that are non blockers for the release. |
Core astropy is a big project and I generally avoid trying to guess what people will do (and they will often surprise you!!). Instead just make a straight judgement on whether API is changing and in this case that is unambiguous. |
|
|
Note: CircleCI |
|
I agree what seems the consensus above: merge this for 3.2, but don't backport. |
|
@taldcroft or @mhvk , please approve if you are happy with this now. Thanks! |
|
Thanks for reviewing this issue and PR! |
Make Table.as_array return empty arrays for empty tables
This PR fixes issues described in #8640:
Table().as_array()no longer returnsNonebut rather an emptynumpyarray.Table() == Nonewill no longer work.CC: @mhvk @pllim
edit: fixes #8579 and fixes #8640