Repository: blaze/blaze

Issue:
Merging column with same name as existing column
Right now, `merge` let's you merge a column with the same name as an existing column. You won't get an error when you merge, but you will get two columns when you try to select one of those columns:

e.g:

``` python
In [4]: merge(t, timestamp)
Out[4]: 
        event    timestamp    arch platform version  \
0   Miniconda  1379613528  x86_64    Linux   1.9.1   
1   Miniconda 1379620047  x86_64  Windows   1.9.1   
2   Miniconda 1379617759  x86_64    Linux   1.9.1   
3   Miniconda  1379620518  x86_64   MacOSX   1.9.1   
4   Miniconda  1379619791  x86_64    Linux   1.9.1   
5   Miniconda  1379622956  x86_64    Linux   1.9.1   
6   Miniconda  1379625368  x86_64    Linux   1.9.1   
7   Miniconda  1379628833  x86_64   MacOSX   1.9.1   
8   Miniconda  1379641599  x86_64  Windows   1.9.1   
9   Miniconda  1379673327  x86_64    Linux   1.9.1   
10  Miniconda  1379671549  x86_64    Linux   1.9.1   

             timestamp  
0  2013-09-19 12:58:48  
1  2013-09-19 14:47:27  
2  2013-09-19 14:09:19  
3  2013-09-19 14:55:18  
4  2013-09-19 14:43:11  
5  2013-09-19 15:35:56  
6  2013-09-19 16:16:08  
7  2013-09-19 17:13:53  
8  2013-09-19 20:46:39  
9  2013-09-20 05:35:27  
...

In [5]: t = merge(t, timestamp)

In [6]: t['timestamp']
Out[6]: 
     timestamp           timestamp
0   1379613528 2013-09-19 12:58:48
1   1379620047 2013-09-19 14:47:27
2   1379617759 2013-09-19 14:09:19
3   1379620518 2013-09-19 14:55:18
4   1379619791 2013-09-19 14:43:11
5   1379622956 2013-09-19 15:35:56
6   1379625368 2013-09-19 16:16:08
7   1379628833 2013-09-19 17:13:53
8   1379641599 2013-09-19 20:46:39
9   1379673327 2013-09-20 05:35:27
...

```

Some ideas: 
-Don't allow to merge a column with the same name as an existing column
-Have a property replace=True (to override that column with the new column)

I'd like to have the second one to be able to easily do in Blaze things like:

```
df['timestamp']=df['timestamp'].apply(lambda x: datetime.fromtimestamp(x))
```



Discussion:
Sounds good.  I'll try to have this done by tomorrow.


Resolving patch:
diff --git a/blaze/api/dplyr.py b/blaze/api/dplyr.py
--- a/blaze/api/dplyr.py
+++ b/blaze/api/dplyr.py
@@ -43,13 +43,15 @@ def select(t, *columns):
     return t[[c.name for c in columns]]
 
 
-def transform(t, **kwargs):
+def transform(t, replace=True, **kwargs):
     """ Add named columns to table
 
     >>> t = TableSymbol('t', '{x: int, y: int}')
     >>> transform(t, xy=t.x + t.y).columns
     ['x', 'y', 'xy']
     """
+    if replace and set(t.columns).intersection(set(kwargs)):
+        t = t[[c for c in t.columns if c not in kwargs]]
 
     args = [t] + [v.label(k) for k, v in kwargs.items()]
     return merge(*args)
diff --git a/blaze/expr/table.py b/blaze/expr/table.py
--- a/blaze/expr/table.py
+++ b/blaze/expr/table.py
@@ -9,7 +9,8 @@
 from datashape import dshape, DataShape, Record, isdimension, Option
 from datashape import coretypes as ct
 import datashape
-from toolz import concat, partial, first, compose, get, unique, second
+from toolz import (concat, partial, first, compose, get, unique, second,
+        isdistinct, frequencies)
 from . import scalar
 from .core import Expr, path
 from .scalar import ScalarSymbol, Number
@@ -1188,7 +1189,13 @@ def merge(*tables):
     if not child:
         raise ValueError("No common sub expression found for input tables")
 
-    return Merge(child, tables)
+    result = Merge(child, tables)
+
+    if not isdistinct(result.columns):
+        raise ValueError("Repeated columns found: " + ', '.join(k for k, v in
+            frequencies(result.columns).items() if v > 1))
+
+    return result
 
 
 class Merge(RowWise):
@@ -1225,6 +1232,12 @@ def schema(self):
         return dshape(Record(list(concat(c.schema[0].parameters[0] for c in
             self.children))))
 
+    def subterms(self):
+        yield self
+        for i in self.children:
+            for node in i.subterms():
+                yield node
+
 
 class Union(TableExpr):
     """ Merge the rows of many Tables together


Test patch:
diff --git a/blaze/api/tests/test_dplyr.py b/blaze/api/tests/test_dplyr.py
--- a/blaze/api/tests/test_dplyr.py
+++ b/blaze/api/tests/test_dplyr.py
@@ -30,6 +30,11 @@ def test_transform():
     expr = transform(t, x=amount + id)
     assert expr.columns == ['id', 'name', 'amount', 'x']
 
+    expr = transform(t, amount=amount + 1)
+    assert expr.columns == ['id', 'name', 'amount']
+    assert (t['amount'] + 1) in expr.subterms()
+    assert t[['id', 'name']] in expr.subterms()
+
 
 def test_groupby():
     g = group_by(t, name)
diff --git a/blaze/expr/tests/test_table.py b/blaze/expr/tests/test_table.py
--- a/blaze/expr/tests/test_table.py
+++ b/blaze/expr/tests/test_table.py
@@ -520,6 +520,13 @@ def test_merge():
     assert c.columns == ['name', 'balance', 'new']
 
 
+def test_merge_repeats():
+    accounts = TableSymbol('accounts',
+                           '{name: string, balance: int32, id: int32}')
+    with pytest.raises(ValueError):
+        merge(accounts, (accounts.balance + 1).label('balance'))
+
+
 @xfail(reason="Need to implement Merge.__getitem__ or Merge.project")
 def test_merge_getitem():
     accounts = TableSymbol('accounts',