Repository: pandas-dev/pandas

Issue:
series __finalized__ not correctly called in merge?
I got some help from Jeff on [stackoverflow](http://stackoverflow.com/questions/23200524/propagate-pandas-series-metadata-through-joins), but either I'm misunderstanding the way `__finalized__` works, or there's a bug in how it's called. My intent was to preserve series metadata after 2 dataframes being merged, and I believe `__finalize__` should be able to handle this.

I define a couple dataframes, and assign metadata values to all the series:

```
import numpy as np
import pandas as pd
np.random.seed(10)
df1 = pd.DataFrame(np.random.randint(0, 4, (3, 2)), columns=['a', 'b'])
df2 = pd.DataFrame(np.random.randint(0, 4, (3, 2)), columns=['c', 'd'])
df1
     a  b
  0  1  1
  1  0  3
  2  0  1
df2
     c  d
  0  3  0
  1  1  1
  2  0  1
```

Then I assign metadata field `filename` to series

```
pd.Series._metadata = ['name', 'filename']

for c1 in df1:
    df1[c1].filename = 'fname1.csv'
for c2 in df2:
    df2[c2].filename = 'fname2.csv'
```

Now, I'm defining `__finalize__` for series, which I understand is able to propagate metadata from one series to the other, for example when I want to merge. But when I define a `__finalize__` that prints off the metadata that I've already assigned, it looks like by the time it calls `__finalize__`, it no longer has the metadata.

```
def finalize_ser(self, other, method=None, **kwargs):
  print 'Self meta: {}'.format(getattr(self, 'filename', None))
  print 'Other meta: {}'.format(getattr(other, 'filename', None))

  for name in self._metadata:
      object.__setattr__(self, name, getattr(other, name, ''))
  return self

pd.Series.__finalize__ = finalize_ser
```

When I call `merge`, I never see the correct metadata printed off

```
df1.merge(df2, left_on=['a'], right_on=['c'], how='inner')
  Self meta: None
  Other meta: None
  Self meta: None
  Other meta: None
  Self meta: None
  Other meta: None
  Self meta: None
  Other meta: None
  Out[5]:
     a  b  c  d
  0  1  1  1  1
  1  0  3  0  1
  2  0  1  0  1
```

It appears the metadata is lost before it gets to the `__finalize__` call, though it's still in the original series

```
df1.a.filename  # => 'fname1.csv'
mgd.a.filename  # => AttributeError
```

Is this expected or is there a bug?



Discussion:

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -1827,8 +1827,9 @@ def __finalize__(self, other, method=None, **kwargs):
             types of propagation actions based on this
 
         """
-        for name in self._metadata:
-            object.__setattr__(self, name, getattr(other, name, None))
+        if isinstance(other, NDFrame):
+            for name in self._metadata:
+                object.__setattr__(self, name, getattr(other, name, None))
         return self
 
     def __getattr__(self, name):
diff --git a/pandas/tools/merge.py b/pandas/tools/merge.py
--- a/pandas/tools/merge.py
+++ b/pandas/tools/merge.py
@@ -195,7 +195,7 @@ def get_result(self):
                                       copy=self.copy)
 
         result_data = join_op.get_result()
-        result = DataFrame(result_data)
+        result = DataFrame(result_data).__finalize__(self, method='merge')
 
         self._maybe_add_join_keys(result, left_indexer, right_indexer)
 


Test patch:
diff --git a/pandas/tests/test_generic.py b/pandas/tests/test_generic.py
--- a/pandas/tests/test_generic.py
+++ b/pandas/tests/test_generic.py
@@ -3,7 +3,7 @@
 from datetime import datetime, timedelta
 import operator
 import nose
-
+import copy
 import numpy as np
 from numpy import nan
 import pandas as pd
@@ -866,6 +866,37 @@ def test_metadata_propagation_indiv(self):
         result = df.resample('1T')
         self.check_metadata(df,result)
 
+        # merging with override
+        # GH 6923
+        _metadata = DataFrame._metadata
+        _finalize = DataFrame.__finalize__
+
+        np.random.seed(10)
+        df1 = DataFrame(np.random.randint(0, 4, (3, 2)), columns=['a', 'b'])
+        df2 = DataFrame(np.random.randint(0, 4, (3, 2)), columns=['c', 'd'])
+        DataFrame._metadata = ['filename']
+        df1.filename = 'fname1.csv'
+        df2.filename = 'fname2.csv'
+
+        def finalize(self, other, method=None, **kwargs):
+
+            for name in self._metadata:
+                if method == 'merge':
+                    left, right = other.left, other.right
+                    value = getattr(left, name, '') + '|' + getattr(right, name, '')
+                    object.__setattr__(self, name, value)
+                else:
+                    object.__setattr__(self, name, getattr(other, name, ''))
+
+            return self
+
+        DataFrame.__finalize__ = finalize
+        result = df1.merge(df2, left_on=['a'], right_on=['c'], how='inner')
+        self.assertEquals(result.filename,'fname1.csv|fname2.csv')
+
+        DataFrame._metadata = _metadata
+        DataFrame.__finalize__ = _finalize
+
 class TestPanel(tm.TestCase, Generic):
     _typ = Panel
     _comparator = lambda self, x, y: assert_panel_equal(x, y)