You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
table: unable to replace a column by one with a different type #3809
When a column in a table is replaced by one with the same name but a different type, the new type is ignored and only the values are copied. This leads to some unexpected behaviour.
For example:
In [1]: from astropy.table import Table, Column
In [2]: tbl = Table({'col': [1., 2., 3.]})
In [3]: tbl['col'].dtype
Out[3]: dtype('float64')
In [4]: tbl['col'] = tbl['col'].astype('float32')
In [5]: tbl['col'].dtype
Out[5]: dtype('float64')
I would have expected to see dtype('float32') at the end instead.
If the type of the new column is incompatible, the assignment unexpectedly fails altogether:
In [6]: tbl['col'] = Column(['a', 'b', 'c'], name='col')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-12-26b5b6b20a41> in <module>()
----> 1 tbl['col'] = Column(['a', 'b', 'c'], name='col')
/home/gb/dev/astropy/astropy/table/table.py in __setitem__(self, item, value)
925 if isinstance(item, six.string_types):
926 # Set an existing column
--> 927 self.columns[item][:] = value
928
929 elif isinstance(item, (int, np.integer)):
/home/gb/dev/astropy/astropy/table/column.py in __setitem__(self, index, value)
825 # order-of-magnitude speed-up. [#2994]
826 def __setitem__(self, index, value):
--> 827 self.data[index] = value
828
829 # # Set slices using a view of the underlying data, as it gives an
ValueError: could not convert string to float: 'a'
(I stumbled upon this behaviour by trying to convert magnitudes from doubles to singles before writing a big table into a FITS file.)
@barentsen - I see your perspective, but this type immutability is by design and you find the same behavior in numpy structured arrays. As seen in the traceback you have:
if isinstance(item, six.string_types):
# Set an existing column
self.columns[item][:] = value
Since Column is a numpy array subclass this basically defers to whatever numpy wants to do for assigning, which might be broadcasting or whatever, but the array type never changes.
I think the behavior you propose would have it's own surprises for people. For instance the following example:
>>> c1 = Column([1.0, 2.0], dtype='f4', name='c1', description='col 1')
>>> c2 = Column([1.5, 2.5], dtype='f8', name='c2', description='column 2')
>>> t = Table([c1, c2])
>>> t['c1'] = t['c1'] + np.sqrt([1, 2])
>>> t['c1'].dtype.name # Changed from float32 to float64 which would surprise some people
float64
>>> t['c1'] = t['c2'] + t['c1']
>>> t['c1'].description # When adding columns metadata taken from leftmost value
'column 2' # shouldn't this be 'col 1' ?!?
In theory we could make a method for this like replace_column, or else just document this (not totally obvious) trick. This takes advantage of the pre-existing dictionary of columns that you can update in place. Don't be tempted to stop after the first line, even though your tbl will appear to have been fixed. This is an illusion and downstream breakage will happen!
I did end up discovering the tbl.columns['col'] = new_column workaround after some trial and error. A replace_column method would have been far easier to discover however, and would offer a place to document these pitfalls, so I'd be 👍 for adding replace_column.
Would you be able to clarify why the 2nd line in your workaround is needed? i.e. tbl = Table(tbl.columns, copy=False)
I have simply been using
... and table.fits had correctly absorbed the change, i.e. 'col' was a single rather than a double in the FITS table. Was I lucky? When would trouble occur?
Does this need to be clarified in the docs? I was a little surprised too, but also see the reasoning (well, I'd be less surprised when Table had an underlying structured array, but now that the data is stored column-wise I'm more surprised).
Without the Table statement you can get into a pickle like this:
In [3]: t = Table([[1]], names=('a'))
In [4]: t.columns['b'] = t['a']
In [5]: t.columns['c'] = t['a']
In [6]: t
Out[6]:
<Table masked=False length=1>
a a a
int64 int64 int64
----- ----- -----
1 1 1
Also, columns have a parent_table attribute that doesn't get set properly.
The copy=False was working for me, but that must have been on master. Can you try that? Otherwise you just need to use the default copy=True.
Some of these problems I just mentioned could be fixed, so I might have a look at that to support this better.
Funny to see how one of my first PRs, creating a new column automatically when assignment was to a column that did not yet exist, now comes back to bite! I think the easiest trick to explain is simply
tbl.remove_column(col)
tbl[col] = ...
But given that we have add_column and remove_column, the idea of having a replace_column seems sensible. @barentsen - do you want to have a go? It seems it should be relatively straightforward.
I like the idea of adding replace_column because its docstring would provide a convenient place to document the difference between a "replacement operation" and a "column assignment". I would probably not have opened this issue if replace_column existed, because I did look for a function with such a name at first.
Unfortunately I'm in the middle of a change of jobs/continents, which prevents me from turning this into a PR in the next few weeks.
Activity
taldcroft commentedon Jun 2, 2015
@barentsen - I see your perspective, but this type immutability is by design and you find the same behavior in numpy structured arrays. As seen in the traceback you have:
Since
Columnis a numpy array subclass this basically defers to whatever numpy wants to do for assigning, which might be broadcasting or whatever, but the array type never changes.I think the behavior you propose would have it's own surprises for people. For instance the following example:
A 2-line workaround is:
In theory we could make a method for this like
replace_column, or else just document this (not totally obvious) trick. This takes advantage of the pre-existing dictionary of columns that you can update in place. Don't be tempted to stop after the first line, even though yourtblwill appear to have been fixed. This is an illusion and downstream breakage will happen!barentsen commentedon Jun 2, 2015
Many thanks for the clarification @taldcroft !
I did end up discovering the
tbl.columns['col'] = new_columnworkaround after some trial and error. Areplace_columnmethod would have been far easier to discover however, and would offer a place to document these pitfalls, so I'd be 👍 for addingreplace_column.Would you be able to clarify why the 2nd line in your workaround is needed? i.e.
tbl = Table(tbl.columns, copy=False)I have simply been using
... and
table.fitshad correctly absorbed the change, i.e. 'col' was a single rather than a double in the FITS table. Was I lucky? When would trouble occur?Thanks!
barentsen commentedon Jun 2, 2015
Oh, by the way:
tbl = Table(tbl.columns, copy=False)raises:
ValueError: Cannot use copy=False with a dict data inputso I'm using
tbl = Table(tbl, copy=False)embray commentedon Jun 2, 2015
Does this need to be clarified in the docs? I was a little surprised too, but also see the reasoning (well, I'd be less surprised when Table had an underlying structured array, but now that the data is stored column-wise I'm more surprised).
taldcroft commentedon Jun 3, 2015
Without the Table statement you can get into a pickle like this:
Also, columns have a
parent_tableattribute that doesn't get set properly.The
copy=Falsewas working for me, but that must have been on master. Can you try that? Otherwise you just need to use the default copy=True.Some of these problems I just mentioned could be fixed, so I might have a look at that to support this better.
mhvk commentedon Jun 4, 2015
Funny to see how one of my first PRs, creating a new column automatically when assignment was to a column that did not yet exist, now comes back to bite! I think the easiest trick to explain is simply
But given that we have
add_columnandremove_column, the idea of having areplace_columnseems sensible. @barentsen - do you want to have a go? It seems it should be relatively straightforward.barentsen commentedon Jun 7, 2015
I like the idea of adding
replace_columnbecause its docstring would provide a convenient place to document the difference between a "replacement operation" and a "column assignment". I would probably not have opened this issue ifreplace_columnexisted, because I did look for a function with such a name at first.Unfortunately I'm in the middle of a change of jobs/continents, which prevents me from turning this into a PR in the next few weeks.
taldcroft commentedon Aug 24, 2015
See #4090.