Skip to content

Commit dc73315

Browse files
committed
BUG: GH3317 Fix frequency inference to be automatic in a DatetimeIndex if freq is not specified during _ensure_index
PERF: remove automatic downcasting, instead do on-demand via 'downcast=infer' TST: fix up failing tests CLN: reindex/fill in internals moved to core/internals/Block/reindex_items_from PERF: don't automatically downcast with a float block BUG: GH3317 reverse prior fix in tseries/offsets, to change slightly the multi reindex TST: identical index tests BUG: GH4618 provide automatic downcasting on a reindexed with method Series (in this case a shifted boolean then filled series)
1 parent 491e3c3 commit dc73315

File tree

16 files changed

+202
-65
lines changed

16 files changed

+202
-65
lines changed

doc/source/release.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ pandas 0.13
115115
- ``MultiIndex.astype()`` now only allows ``np.object_``-like dtypes and
116116
now returns a ``MultiIndex`` rather than an ``Index``. (:issue:`4039`)
117117

118+
- Infer and downcast dtype if ``downcast='infer'`` is passed to ``fillna/ffill/bfill`` (:issue:`4604`)
119+
118120
**Internal Refactoring**
119121

120122
In 0.13.0 there is a major refactor primarily to subclass ``Series`` from ``NDFrame``,
@@ -183,11 +185,9 @@ See :ref:`Internal Refactoring<whatsnew_0130.refactoring>`
183185

184186
- Indexing with dtype conversions fixed (:issue:`4463`, :issue:`4204`)
185187

186-
- Refactor Series.reindex to core/generic.py (:issue:`4604`), allow ``method=`` in reindexing
188+
- Refactor Series.reindex to core/generic.py (:issue:`4604`, :issue:`4618`), allow ``method=`` in reindexing
187189
on a Series to work
188190

189-
- Infer and downcast dtype if appropriate on ``ffill/bfill`` (:issue:`4604`)
190-
191191
**Experimental Features**
192192

193193
**Bug Fixes**
@@ -259,6 +259,8 @@ See :ref:`Internal Refactoring<whatsnew_0130.refactoring>`
259259
- Fix bug in ``pd.read_clipboard`` on windows with PY3 (:issue:`4561`); not decoding properly
260260
- ``tslib.get_period_field()`` and ``tslib.get_period_field_arr()`` now raise
261261
if code argument out of range (:issue:`4519`, :issue:`4520`)
262+
- Fix reindexing with multiple axes; if an axes match was not replacing the current axes, leading
263+
to a possible lazay frequency inference issue (:issue:`3317`)
262264

263265
pandas 0.12
264266
===========

doc/source/v0.13.0.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ API changes
9696
# and all methods take an inplace kwarg
9797
index.set_names(["bob", "cranberry"], inplace=True)
9898

99+
- Infer and downcast dtype if ``downcast='infer'`` is passed to ``fillna/ffill/bfill`` (:issue:`4604`)
100+
99101
Enhancements
100102
~~~~~~~~~~~~
101103

@@ -237,11 +239,9 @@ and behaviors. Series formerly subclassed directly from ``ndarray``. (:issue:`40
237239

238240
- Indexing with dtype conversions fixed (:issue:`4463`, :issue:`4204`)
239241

240-
- Refactor Series.reindex to core/generic.py (:issue:`4604`), allow ``method=`` in reindexing
242+
- Refactor Series.reindex to core/generic.py (:issue:`4604`, :issue:`4618`), allow ``method=`` in reindexing
241243
on a Series to work
242244

243-
- Infer and downcast dtype if appropriate on ``ffill/bfill`` (:issue:`4604`)
244-
245245
Bug Fixes
246246
~~~~~~~~~
247247

pandas/core/common.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -990,14 +990,20 @@ def _possibly_downcast_to_dtype(result, dtype):
990990
if issubclass(dtype.type, np.floating):
991991
return result.astype(dtype)
992992
elif dtype == np.bool_ or issubclass(dtype.type, np.integer):
993+
994+
# do a test on the first element, if it fails then we are done
995+
r = result.ravel()
996+
arr = np.array([ r[0] ])
997+
if (arr != arr.astype(dtype)).item():
998+
return result
999+
1000+
# a comparable, e.g. a Decimal may slip in here
1001+
elif not isinstance(r[0], (np.integer,np.floating,np.bool,int,float,bool)):
1002+
return result
1003+
9931004
if issubclass(result.dtype.type, (np.object_,np.number)) and notnull(result).all():
9941005
new_result = result.astype(dtype)
9951006
if (new_result == result).all():
996-
997-
# a comparable, e.g. a Decimal may slip in here
998-
if not isinstance(result.ravel()[0], (np.integer,np.floating,np.bool,int,float,bool)):
999-
return result
1000-
10011007
return new_result
10021008
except:
10031009
pass
@@ -1174,7 +1180,7 @@ def backfill_2d(values, limit=None, mask=None):
11741180
pass
11751181
return values
11761182

1177-
def interpolate_2d(values, method='pad', axis=0, limit=None, missing=None):
1183+
def interpolate_2d(values, method='pad', axis=0, limit=None, fill_value=None):
11781184
""" perform an actual interpolation of values, values will be make 2-d if needed
11791185
fills inplace, returns the result """
11801186

@@ -1187,10 +1193,10 @@ def interpolate_2d(values, method='pad', axis=0, limit=None, missing=None):
11871193
raise Exception("cannot interpolate on a ndim == 1 with axis != 0")
11881194
values = values.reshape(tuple((1,) + values.shape))
11891195

1190-
if missing is None:
1196+
if fill_value is None:
11911197
mask = None
11921198
else: # todo create faster fill func without masking
1193-
mask = mask_missing(transf(values), missing)
1199+
mask = mask_missing(transf(values), fill_value)
11941200

11951201
method = _clean_fill_method(method)
11961202
if method == 'pad':
@@ -1870,6 +1876,7 @@ def _astype_nansafe(arr, dtype, copy=True):
18701876

18711877

18721878
def _clean_fill_method(method):
1879+
if method is None: return None
18731880
method = method.lower()
18741881
if method == 'ffill':
18751882
method = 'pad'

pandas/core/frame.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -2280,12 +2280,9 @@ def _reindex_multi(self, axes, copy, fill_value):
22802280
fill_value=fill_value)
22812281
return self._constructor(new_values, index=new_index,
22822282
columns=new_columns)
2283-
elif row_indexer is not None:
2284-
return self._reindex_with_indexers({0: [new_index, row_indexer]}, copy=copy, fill_value=fill_value)
2285-
elif col_indexer is not None:
2286-
return self._reindex_with_indexers({1: [new_columns, col_indexer]}, copy=copy, fill_value=fill_value)
22872283
else:
2288-
return self.copy() if copy else self
2284+
return self._reindex_with_indexers({0: [new_index, row_indexer],
2285+
1: [new_columns, col_indexer]}, copy=copy, fill_value=fill_value)
22892286

22902287
def reindex_like(self, other, method=None, copy=True, limit=None,
22912288
fill_value=NA):

pandas/core/generic.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,7 @@ def reindex(self, *args, **kwargs):
987987

988988
# construct the args
989989
axes, kwargs = self._construct_axes_from_arguments(args, kwargs)
990-
method = kwargs.get('method')
990+
method = com._clean_fill_method(kwargs.get('method'))
991991
level = kwargs.get('level')
992992
copy = kwargs.get('copy', True)
993993
limit = kwargs.get('limit')
@@ -1082,6 +1082,7 @@ def reindex_axis(self, labels, axis=0, method=None, level=None, copy=True,
10821082

10831083
axis_name = self._get_axis_name(axis)
10841084
axis_values = self._get_axis(axis_name)
1085+
method = com._clean_fill_method(method)
10851086
new_index, indexer = axis_values.reindex(labels, method, level,
10861087
limit=limit, copy_if_needed=True)
10871088
return self._reindex_with_indexers({axis: [new_index, indexer]}, method=method, fill_value=fill_value,
@@ -1102,7 +1103,7 @@ def _reindex_with_indexers(self, reindexers, method=None, fill_value=np.nan, lim
11021103
# reindex the axis
11031104
if method is not None:
11041105
new_data = new_data.reindex_axis(
1105-
index, method=method, axis=baxis,
1106+
index, indexer=indexer, method=method, axis=baxis,
11061107
fill_value=fill_value, limit=limit, copy=copy)
11071108

11081109
elif indexer is not None:
@@ -1419,7 +1420,8 @@ def fillna(self, value=None, method=None, axis=0, inplace=False,
14191420
limit : int, default None
14201421
Maximum size gap to forward or backward fill
14211422
downcast : dict, default is None, a dict of item->dtype of what to
1422-
downcast if possible
1423+
downcast if possible, or the string 'infer' which will try to
1424+
downcast to an appropriate equal type (e.g. float64 to int64 if possible)
14231425
14241426
See also
14251427
--------
@@ -1438,6 +1440,7 @@ def fillna(self, value=None, method=None, axis=0, inplace=False,
14381440
if axis + 1 > self._AXIS_LEN:
14391441
raise ValueError(
14401442
"invalid axis passed for object type {0}".format(type(self)))
1443+
method = com._clean_fill_method(method)
14411444

14421445
if value is None:
14431446
if method is None:
@@ -1488,13 +1491,13 @@ def fillna(self, value=None, method=None, axis=0, inplace=False,
14881491
else:
14891492
return self._constructor(new_data)
14901493

1491-
def ffill(self, axis=0, inplace=False, limit=None):
1494+
def ffill(self, axis=0, inplace=False, limit=None, downcast=None):
14921495
return self.fillna(method='ffill', axis=axis, inplace=inplace,
1493-
limit=limit, downcast='infer')
1496+
limit=limit, downcast=downcast)
14941497

1495-
def bfill(self, axis=0, inplace=False, limit=None):
1498+
def bfill(self, axis=0, inplace=False, limit=None, downcast=None):
14961499
return self.fillna(method='bfill', axis=axis, inplace=inplace,
1497-
limit=limit, downcast='infer')
1500+
limit=limit, downcast=downcast)
14981501

14991502
def replace(self, to_replace=None, value=None, inplace=False, limit=None,
15001503
regex=False, method=None, axis=None):
@@ -2046,6 +2049,7 @@ def align(self, other, join='outer', axis=None, level=None, copy=True,
20462049
Aligned objects
20472050
"""
20482051
from pandas import DataFrame, Series
2052+
method = com._clean_fill_method(method)
20492053

20502054
if isinstance(other, DataFrame):
20512055
return self._align_frame(other, join=join, axis=axis, level=level,

pandas/core/index.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ def identical(self, other):
551551
Similar to equals, but check that other comparable attributes are also equal
552552
"""
553553
return self.equals(other) and all(
554-
[ getattr(self,c,None) == getattr(other,c,None) for c in self._comparables ])
554+
( getattr(self,c,None) == getattr(other,c,None) for c in self._comparables ))
555555

556556
def asof(self, label):
557557
"""
@@ -1555,6 +1555,7 @@ class MultiIndex(Index):
15551555
_names = FrozenList()
15561556
_levels = FrozenList()
15571557
_labels = FrozenList()
1558+
_comparables = ['names']
15581559

15591560
def __new__(cls, levels=None, labels=None, sortorder=None, names=None,
15601561
copy=False):

0 commit comments

Comments
 (0)