Skip to content

Commit 530f2a8

Browse files
committed
BUG: allow replacement with a single list
This requires adding the ``method`` keyword parameter back in.
1 parent e5b196d commit 530f2a8

File tree

4 files changed

+110
-12
lines changed

4 files changed

+110
-12
lines changed

doc/source/release.rst

+2
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ pandas 0.13
152152
passed a string (:issue:`4763`). Pass a ``list`` of one element (containing
153153
the string) instead.
154154
- Remove undocumented/unused ``kind`` keyword argument from ``read_excel``, and ``ExcelFile``. (:issue:`4713`, :issue:`4712`)
155+
- The ``method`` argument of ``NDFrame.replace()`` is valid again, so that a
156+
a list can be passed to ``to_replace`` (:issue:`4743`).
155157

156158
**Internal Refactoring**
157159

pandas/core/common.py

+9
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ def wrapper(arr, mask, limit=None):
10471047
np.int64)
10481048

10491049

1050+
10501051
def pad_1d(values, limit=None, mask=None):
10511052

10521053
dtype = values.dtype.name
@@ -1188,6 +1189,14 @@ def _consensus_name_attr(objs):
11881189
return None
11891190
return name
11901191

1192+
1193+
_fill_methods = {'pad': pad_1d, 'backfill': backfill_1d}
1194+
1195+
def _get_fill_func(method):
1196+
method = _clean_fill_method(method)
1197+
return _fill_methods[method]
1198+
1199+
11911200
#----------------------------------------------------------------------
11921201
# Lots of little utilities
11931202

pandas/core/generic.py

+42-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import numpy as np
66
import pandas.lib as lib
77

8+
import pandas as pd
89
from pandas.core.base import PandasObject
910
from pandas.core.index import Index, MultiIndex, _ensure_index
1011
import pandas.core.indexing as indexing
@@ -19,6 +20,33 @@
1920
_infer_dtype_from_scalar, _maybe_promote,
2021
ABCSeries)
2122

23+
24+
25+
def is_dictlike(x):
26+
return isinstance(x, (dict, com.ABCSeries))
27+
28+
29+
def _single_replace(self, to_replace, method, inplace, limit):
30+
orig_dtype = self.dtype
31+
result = self if inplace else self.copy()
32+
fill_f = com._get_fill_func(method)
33+
34+
mask = com.mask_missing(result.values, to_replace)
35+
values = fill_f(result.values, limit=limit, mask=mask)
36+
37+
if values.dtype == orig_dtype and inplace:
38+
return
39+
40+
result = pd.Series(values, index=self.index, name=self.name,
41+
dtype=self.dtype)
42+
43+
if inplace:
44+
self._data = result._data
45+
return
46+
47+
return result
48+
49+
2250
class NDFrame(PandasObject):
2351

2452
"""
@@ -1581,7 +1609,7 @@ def bfill(self, axis=0, inplace=False, limit=None, downcast=None):
15811609
limit=limit, downcast=downcast)
15821610

15831611
def replace(self, to_replace=None, value=None, inplace=False, limit=None,
1584-
regex=False, method=None, axis=None):
1612+
regex=False, method='pad', axis=None):
15851613
"""
15861614
Replace values given in 'to_replace' with 'value'.
15871615
@@ -1643,14 +1671,19 @@ def replace(self, to_replace=None, value=None, inplace=False, limit=None,
16431671
string. Otherwise, `to_replace` must be ``None`` because this
16441672
parameter will be interpreted as a regular expression or a list,
16451673
dict, or array of regular expressions.
1674+
method : string, optional, {'pad', 'ffill', 'bfill'}
1675+
The method to use when for replacement, when ``to_replace`` is a
1676+
``list``.
16461677
16471678
See also
16481679
--------
1649-
reindex, asfreq, fillna
1680+
NDFrame.reindex
1681+
NDFrame.asfreq
1682+
NDFrame.fillna
16501683
16511684
Returns
16521685
-------
1653-
filled : DataFrame
1686+
filled : NDFrame
16541687
16551688
Raises
16561689
------
@@ -1681,26 +1714,23 @@ def replace(self, to_replace=None, value=None, inplace=False, limit=None,
16811714
if not com.is_bool(regex) and to_replace is not None:
16821715
raise AssertionError("'to_replace' must be 'None' if 'regex' is "
16831716
"not a bool")
1684-
if method is not None:
1685-
from warnings import warn
1686-
warn('the "method" argument is deprecated and will be removed in'
1687-
'v0.13; this argument has no effect')
1688-
16891717
if axis is not None:
16901718
from warnings import warn
16911719
warn('the "axis" argument is deprecated and will be removed in'
16921720
'v0.13; this argument has no effect')
16931721

16941722
self._consolidate_inplace()
16951723

1696-
def is_dictlike(x):
1697-
return isinstance(x, (dict, com.ABCSeries))
1698-
16991724
if value is None:
1725+
if isinstance(to_replace, list):
1726+
return _single_replace(self, to_replace, method, inplace,
1727+
limit)
1728+
17001729
if not is_dictlike(to_replace):
17011730
if not is_dictlike(regex):
17021731
raise TypeError('If "to_replace" and "value" are both None'
1703-
' then regex must be a mapping')
1732+
' and "to_replace" is not a list, then '
1733+
'regex must be a mapping')
17041734
to_replace = regex
17051735
regex = True
17061736

pandas/tests/test_series.py

+57
Original file line numberDiff line numberDiff line change
@@ -4608,6 +4608,63 @@ def test_replace(self):
46084608
result = ser.replace([0, 1, 2, 3, 4], [4, 3, 2, 1, 0])
46094609
assert_series_equal(result, Series([4, 3, 2, 1, 0]))
46104610

4611+
def test_replace_with_single_list(self):
4612+
ser = Series([0, 1, 2, 3, 4])
4613+
result = ser.replace([1,2,3])
4614+
assert_series_equal(result, Series([0,0,0,0,4]))
4615+
4616+
s = ser.copy()
4617+
s.replace([1,2,3],inplace=True)
4618+
assert_series_equal(s, Series([0,0,0,0,4]))
4619+
4620+
# make sure things don't get corrupted when fillna call fails
4621+
s = ser.copy()
4622+
with tm.assertRaises(ValueError):
4623+
s.replace([1,2,3],inplace=True,method='crash_cymbal')
4624+
assert_series_equal(s, ser)
4625+
4626+
def test_replace_mixed_types(self):
4627+
s = Series(np.arange(5))
4628+
4629+
def check_replace(to_rep, val, expected):
4630+
sc = s.copy()
4631+
r = s.replace(to_rep, val)
4632+
sc.replace(to_rep, val, inplace=True)
4633+
assert_series_equal(expected, r)
4634+
assert_series_equal(expected, sc)
4635+
4636+
# should NOT upcast to float
4637+
e = Series([0,1,2,3,4])
4638+
tr, v = [3], [3.0]
4639+
check_replace(tr, v, e)
4640+
4641+
# MUST upcast to float
4642+
e = Series([0,1,2,3.5,4])
4643+
tr, v = [3], [3.5]
4644+
check_replace(tr, v, e)
4645+
4646+
# casts to object
4647+
e = Series([0,1,2,3.5,'a'])
4648+
tr, v = [3,4], [3.5,'a']
4649+
check_replace(tr, v, e)
4650+
4651+
# again casts to object
4652+
e = Series([0,1,2,3.5,Timestamp('20130101')])
4653+
tr, v = [3,4],[3.5,Timestamp('20130101')]
4654+
check_replace(tr, v, e)
4655+
4656+
# casts to float
4657+
e = Series([0,1,2,3.5,1])
4658+
tr, v = [3,4],[3.5,True]
4659+
check_replace(tr, v, e)
4660+
4661+
# test an object with dates + floats + integers + strings
4662+
dr = date_range('1/1/2001', '1/10/2001',
4663+
freq='D').to_series().reset_index(drop=True)
4664+
r = dr.astype(object).replace([dr[0],dr[1],dr[2]], [1.0,2,'a'])
4665+
assert_series_equal(r, Series([1.0,2,'a'] +
4666+
dr[3:].tolist(),dtype=object))
4667+
46114668
def test_asfreq(self):
46124669
ts = Series([0., 1., 2.], index=[datetime(2009, 10, 30),
46134670
datetime(2009, 11, 30),

0 commit comments

Comments
 (0)