Skip to content

Commit d7bec6c

Browse files
author
y-p
committed
Merge pull request #3304 from y-p/panel_od
PR: Panel and Panel.from_dict() don't honor ordering when passed OrderedDict
2 parents e264115 + 8acb690 commit d7bec6c

File tree

4 files changed

+58
-9
lines changed

4 files changed

+58
-9
lines changed

RELEASE.rst

+1
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ pandas 0.11.0
287287
- Fix Python ascii file parsing when integer falls outside of floating point
288288
spacing (GH3258_)
289289
- fixed pretty priniting of sets (GH3294_)
290+
- Panel() and Panel.from_dict() now respects ordering when give OrderedDict (GH3303_)
290291

291292
.. _GH3294: https://github.com/pydata/pandas/issues/3294
292293
.. _GH622: https://github.com/pydata/pandas/issues/622

pandas/core/panel.py

+19-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from pandas.core.index import (Index, MultiIndex, _ensure_index,
1414
_get_combined_index)
1515
from pandas.core.indexing import _maybe_droplevels, _is_list_like
16-
from pandas.core.internals import (BlockManager,
16+
from pandas.core.internals import (BlockManager,
1717
create_block_manager_from_arrays,
1818
create_block_manager_from_blocks)
1919
from pandas.core.series import Series
@@ -274,14 +274,18 @@ def _from_axes(cls, data, axes):
274274
return cls(data, **d)
275275

276276
def _init_dict(self, data, axes, dtype=None):
277+
from pandas.util.compat import OrderedDict
277278
haxis = axes.pop(self._het_axis)
278279

279280
# prefilter if haxis passed
280281
if haxis is not None:
281282
haxis = _ensure_index(haxis)
282-
data = dict((k, v) for k, v in data.iteritems() if k in haxis)
283+
data = OrderedDict((k, v) for k, v in data.iteritems() if k in haxis)
283284
else:
284-
haxis = Index(_try_sort(data.keys()))
285+
ks = data.keys()
286+
if not isinstance(data,OrderedDict):
287+
ks = _try_sort(ks)
288+
haxis = Index(ks)
285289

286290
for k, v in data.iteritems():
287291
if isinstance(v, dict):
@@ -341,11 +345,11 @@ def from_dict(cls, data, intersect=False, orient='items', dtype=None):
341345
-------
342346
Panel
343347
"""
344-
from collections import defaultdict
348+
from pandas.util.compat import OrderedDict,OrderedDefaultdict
345349

346350
orient = orient.lower()
347351
if orient == 'minor':
348-
new_data = defaultdict(dict)
352+
new_data = OrderedDefaultdict(dict)
349353
for col, df in data.iteritems():
350354
for item, s in df.iteritems():
351355
new_data[item][col] = s
@@ -354,7 +358,10 @@ def from_dict(cls, data, intersect=False, orient='items', dtype=None):
354358
raise ValueError('only recognize items or minor for orientation')
355359

356360
d = cls._homogenize_dict(cls, data, intersect=intersect, dtype=dtype)
357-
d[cls._info_axis] = Index(sorted(d['data'].keys()))
361+
ks = d['data'].keys()
362+
if not isinstance(d['data'],OrderedDict):
363+
ks = list(sorted(ks))
364+
d[cls._info_axis] = Index(ks)
358365
return cls(**d)
359366

360367
def __getitem__(self, key):
@@ -1491,9 +1498,13 @@ def _homogenize_dict(self, frames, intersect=True, dtype=None):
14911498
-------
14921499
dict of aligned results & indicies
14931500
"""
1494-
result = {}
1501+
from pandas.util.compat import OrderedDict
14951502

1496-
adj_frames = {}
1503+
result = dict()
1504+
if isinstance(frames,OrderedDict): # caller differs dict/ODict, presered type
1505+
result = OrderedDict()
1506+
1507+
adj_frames = OrderedDict()
14971508
for k, v in frames.iteritems():
14981509
if isinstance(v, dict):
14991510
adj_frames[k] = self._constructor_sliced(v)

pandas/tests/test_panel.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
assert_frame_equal,
2020
assert_series_equal,
2121
assert_almost_equal,
22-
ensure_clean)
22+
ensure_clean,
23+
makeCustomDataframe as mkdf
24+
)
2325
import pandas.core.panel as panelm
2426
import pandas.util.testing as tm
2527

@@ -904,6 +906,16 @@ def test_constructor_dict_mixed(self):
904906
data['ItemB'] = self.panel['ItemB'].values[:, :-1]
905907
self.assertRaises(Exception, Panel, data)
906908

909+
def test_ctor_orderedDict(self):
910+
from pandas.util.compat import OrderedDict
911+
keys = list(set(np.random.randint(0,5000,100)))[:50] # unique random int keys
912+
d = OrderedDict([(k,mkdf(10,5)) for k in keys])
913+
p = Panel(d)
914+
self.assertTrue(list(p.items) == keys)
915+
916+
p = Panel.from_dict(d)
917+
self.assertTrue(list(p.items) == keys)
918+
907919
def test_constructor_resize(self):
908920
data = self.panel._data
909921
items = self.panel.items[:-1]

pandas/util/compat.py

+25
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,28 @@ def __and__(self, other):
475475
Counter = _Counter
476476
else:
477477
from collections import OrderedDict, Counter
478+
479+
# http://stackoverflow.com/questions/4126348
480+
# Thanks to @martineau at SO
481+
482+
class OrderedDefaultdict(OrderedDict):
483+
def __init__(self, *args, **kwargs):
484+
newdefault = None
485+
newargs = ()
486+
if args:
487+
newdefault = args[0]
488+
if not (newdefault is None or callable(newdefault)):
489+
raise TypeError('first argument must be callable or None')
490+
newargs = args[1:]
491+
self.default_factory = newdefault
492+
super(self.__class__, self).__init__(*newargs, **kwargs)
493+
494+
def __missing__ (self, key):
495+
if self.default_factory is None:
496+
raise KeyError(key)
497+
self[key] = value = self.default_factory()
498+
return value
499+
500+
def __reduce__(self): # optional, for pickle support
501+
args = self.default_factory if self.default_factory else tuple()
502+
return type(self), args, None, None, self.items()

0 commit comments

Comments
 (0)