Repository: vaexio/vaex

Issue:
Inconsistent dtypes after astype()
The following code leaves a dataframe with inconsistent dtypes;

```
import pyarrow.csv as csv
table = csv.read_csv(
    'test.csv',
    parse_options=csv.ParseOptions(delimiter='|'),
)
df = vaex.from_arrow_table(table)
df.rename_column(df.column_names[1], 'value')
df['value'] = df['value'].str.replace(',', '').evaluate()
df['value'] = (df['value'].astype('float')*1000).astype('int64').evaluate()
```

The column `value` should be dtype `int64`. But:

```
>> df['value']
Expression = value
Length: 1,305 dtype: str (column)
---------------------------------
   0  1.15369e+07
   1  1.15145e+07
   2  1.15784e+07
   3  1.15784e+07
   4  1.16032e+07
       ...       
1300  1.65851e+07
1301  1.66504e+07
1302  1.63488e+07
1303  1.64588e+07
1304  1.63489e+07
```

However:
```
>> df.columns
OrderedDict([('date', <vaex.column.ColumnStringArrow at 0x2a8fd8de048>),
             ('value',
              array([11536930, 11514505, 11578393, ..., 16348803, 16458799, 16348886],
                    dtype=int64))])
```

While:
```
>> df.dtypes
date     <class 'str'>
value    <class 'str'>
dtype: object
```

Am I right to assume, that this is a bug and not some subtle intended behaviour?


Discussion:
This looks like a bug to me, thanks for opening the issue!
Could you open an PR with just a unit test that fails so I can fix it, or maybe provide me with a self-contained example?
I managed to reproduce the issue. Unit-test coming up shortly. 

Resolving patch:
diff --git a/packages/vaex-core/vaex/dataframe.py b/packages/vaex-core/vaex/dataframe.py
--- a/packages/vaex-core/vaex/dataframe.py
+++ b/packages/vaex-core/vaex/dataframe.py
@@ -2786,6 +2786,12 @@ def select(self, boolean_expression, mode="replace", name="default"):
 
     def add_column(self, name, f_or_array, dtype=None):
         """Add an in memory array as a column."""
+        column_position = len(self.column_names)
+        if name in self.get_column_names():
+            column_position = self.column_names.index(name)
+            renamed = '__' +vaex.utils.find_valid_name(name, used=self.get_column_names())
+            self._rename(name, renamed)
+
         if isinstance(f_or_array, (np.ndarray, Column)):
             data = ar = f_or_array
             # it can be None when we have an 'empty' DataFrameArrays
@@ -2805,7 +2811,7 @@ def add_column(self, name, f_or_array, dtype=None):
                 self._column_aliases[name] = valid_name
             self.columns[valid_name] = f_or_array
             if valid_name not in self.column_names:
-                self.column_names.append(valid_name)
+                self.column_names.insert(column_position, valid_name)
             ar = f_or_array
             if dtype is not None:
                 self._dtypes_override[valid_name] = dtype
@@ -3131,14 +3137,15 @@ def add_virtual_column(self, name, expression, unique=False):
         # self.write_virtual_meta()
 
     def _rename(self, old, new, *expressions):
-        #for name, expr in self.virtual_columns.items():
+        if old in self._dtypes_override:
+            self._dtypes_override[new] = self._dtypes_override.pop(old)
         if old in self.columns:
             self.columns[new] = self.columns.pop(old)
         if old in self.virtual_columns:
             self.virtual_columns[new] = self.virtual_columns.pop(old)
         self._renamed_columns.append((old, new))
-        index = self.column_names.index(old)
-        self.column_names[index] = new
+        self.column_names.remove(old)
+        self.column_names.append(new)
         self.virtual_columns = {k:self[v]._rename(old, new).expression for k, v in self.virtual_columns.items()}
         for key, value in self.selection_histories.items():
             self.selection_histories[key] = list([k if k is None else k._rename(self, old, new) for k in value])


Test patch:
diff --git a/tests/astype_test.py b/tests/astype_test.py
--- a/tests/astype_test.py
+++ b/tests/astype_test.py
@@ -10,3 +10,13 @@ def test_astype(ds_local):
 
     assert ds.x.evaluate().dtype == np.float32
     assert ds.x.tolist() == ds_original.x.evaluate().astype(np.float32).tolist()
+
+
+def test_astype_str():
+    df = vaex.from_arrays(x=['10,010', '-50,0', '11,111'])
+
+    df['x'] = df['x'].str.replace(',', '').evaluate()
+    df['x'] = (df['x'].astype('float')).astype('int64').evaluate()
+
+    assert df.columns['x'].dtype == np.int64
+    assert df.x.dtype == np.int64
diff --git a/tests/rename_test.py b/tests/rename_test.py
--- a/tests/rename_test.py
+++ b/tests/rename_test.py
@@ -8,8 +8,6 @@ def test_rename(ds_filtered):
     xvalues = ds.x.tolist()
     qexpr = ds.q.expand().expression
     ds['x'] = ds.y # now r should still point to x
-    import pdb
-    # pdb.set_trace()
     assert ds.r.values.tolist() == xvalues
     assert ds.q.values.tolist() == qvalues
 
@@ -22,6 +20,14 @@ def test_reassign_virtual(ds_local):
     assert df.r.tolist() == (x+2).tolist()
 
 
+def test_reassign_column(ds_filtered):
+    df = ds_filtered.extract()
+    x = df.x.values
+    df['r'] = (df.x+1).evaluate()
+    df['r'] = (df.r+1).evaluate()
+    assert df.r.tolist() == (x+2).tolist()
+
+
 def test_rename_state_transfer():
     ds = vaex.from_scalars(x=3, y=4)
     ds['r'] = (ds.x**2 + ds.y**2)**0.5