Repository: pandas-dev/pandas

Issue:
DataFrame.replace only replaces the first occurrence of replacement pattern
This is best explained by a screenshot:

![bad_replace](http://d.pr/i/vVgH+)

I'm running a pretty recent build of Pandas ('0.13.1-213-gc174c3d') on Python 2.7.5 on OS X 0.9.2.



Discussion:
Feels like a changing-dtype issue, or maybe a mixup of values and keys (True == 1 and False == 0):

```
>>> df = pd.DataFrame({"a": [True, False, True]})
>>> df
       a
0   True
1  False
2   True

[3 rows x 1 columns]
>>> df.replace({True: "Y", False: "N"})
   a
0  Y
1  N
2  Y

[3 rows x 1 columns]
>>> df.replace({"a": {True: "Y", False: "N"}})
      a
0     N
1     Y
2  True

[3 rows x 1 columns]
>>> df.astype(object).replace({"a": {True: "Y", False: "N"}})
   a
0  Y
1  N
2  Y

[3 rows x 1 columns]
```

but I've never fully understood the intended semantics of `replace`.

Yeah, it looks like the keys of the inner dict are being interpreted as indices to match, not values, which explains the Y/N, N/Y swap above.  For example:

```
>>> df = pd.DataFrame({"a": [True, False, True]})
>>> df.replace({"a": {0: "zero", 1: "one", 2: "two"}})
      a
0  zero
1   one
2   two

[3 rows x 1 columns]
```

Are you guys running master? I fixed a similar bug somewhat recently. 

@cpcloud: I am, at least.

Okay thanks I'll take a look. Looks like a dtype issue. @fonnesbeck thanks for the report. 

@jreback Is there a way to select and set a block by name? Something like `df._data['a'] = df._data['a'].some_method()`?

i want to operate on a block inplace or out of place but see the changes in the whole block manager

Nevermind.


Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -2093,7 +2093,7 @@ def convert_objects(self, convert_dates=True, convert_numeric=False,
             strings), non-convertibles get NaN
         convert_timedeltas : if True, attempt to soft convert timedeltas, if 'coerce',
             force conversion (and non-convertibles get NaT)
-        copy : Boolean, if True, return copy even if no copy is necessary 
+        copy : Boolean, if True, return copy even if no copy is necessary
             (e.g. no conversion was done), default is True.
             It is meant for internal use, not to be confused with `inplace` kw.
 
@@ -2410,13 +2410,14 @@ def replace(self, to_replace=None, value=None, inplace=False, limit=None,
             new_data = self._data
             if is_dictlike(to_replace):
                 if is_dictlike(value):  # {'A' : NA} -> {'A' : 0}
+                    res = self if inplace else self.copy()
                     for c, src in compat.iteritems(to_replace):
                         if c in value and c in self:
-                            new_data = new_data.replace(to_replace=src,
-                                                        value=value[c],
-                                                        filter=[c],
-                                                        inplace=inplace,
-                                                        regex=regex)
+                            res[c] = res[c].replace(to_replace=src,
+                                                    value=value[c],
+                                                    inplace=False,
+                                                    regex=regex)
+                    return None if inplace else res
 
                 # {'A': NA} -> 0
                 elif not com.is_list_like(value):
@@ -2428,7 +2429,7 @@ def replace(self, to_replace=None, value=None, inplace=False, limit=None,
                                                         inplace=inplace,
                                                         regex=regex)
                 else:
-                    raise TypeError('Fill value must be scalar, dict, or '
+                    raise TypeError('value argument must be scalar, dict, or '
                                     'Series')
 
             elif com.is_list_like(to_replace):  # [NA, ''] -> [0, 'missing']


Test patch:
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -8231,6 +8231,17 @@ def test_replace_str_to_str_chain(self):
         with tm.assertRaisesRegexp(ValueError, "Replacement not allowed .+"):
             df.replace({'a': dict(zip(astr, bstr))})
 
+    def test_replace_swapping_bug(self):
+        df = pd.DataFrame({'a': [True, False, True]})
+        res = df.replace({'a': {True: 'Y', False: 'N'}})
+        expect = pd.DataFrame({'a': ['Y', 'N', 'Y']})
+        tm.assert_frame_equal(res, expect)
+
+        df = pd.DataFrame({'a': [0, 1, 0]})
+        res = df.replace({'a': {0: 'Y', 1: 'N'}})
+        expect = pd.DataFrame({'a': ['Y', 'N', 'Y']})
+        tm.assert_frame_equal(res, expect)
+
     def test_combine_multiple_frames_dtypes(self):
 
         # GH 2759