Skip to content

PR: Panel and Panel.from_dict() don't honor ordering when passed OrderedDict #3304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
4 commits merged into from Apr 9, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ pandas 0.11.0
- Fix Python ascii file parsing when integer falls outside of floating point
spacing (GH3258_)
- fixed pretty priniting of sets (GH3294_)
- Panel() and Panel.from_dict() now respects ordering when give OrderedDict (GH3303_)

.. _GH3294: https://github.com/pydata/pandas/issues/3294
.. _GH622: https://github.com/pydata/pandas/issues/622
Expand Down
27 changes: 19 additions & 8 deletions pandas/core/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from pandas.core.index import (Index, MultiIndex, _ensure_index,
_get_combined_index)
from pandas.core.indexing import _maybe_droplevels, _is_list_like
from pandas.core.internals import (BlockManager,
from pandas.core.internals import (BlockManager,
create_block_manager_from_arrays,
create_block_manager_from_blocks)
from pandas.core.series import Series
Expand Down Expand Up @@ -274,14 +274,18 @@ def _from_axes(cls, data, axes):
return cls(data, **d)

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

# prefilter if haxis passed
if haxis is not None:
haxis = _ensure_index(haxis)
data = dict((k, v) for k, v in data.iteritems() if k in haxis)
data = OrderedDict((k, v) for k, v in data.iteritems() if k in haxis)
else:
haxis = Index(_try_sort(data.keys()))
ks = data.keys()
if not isinstance(data,OrderedDict):
ks = _try_sort(ks)
haxis = Index(ks)

for k, v in data.iteritems():
if isinstance(v, dict):
Expand Down Expand Up @@ -341,11 +345,11 @@ def from_dict(cls, data, intersect=False, orient='items', dtype=None):
-------
Panel
"""
from collections import defaultdict
from pandas.util.compat import OrderedDict,OrderedDefaultdict

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

d = cls._homogenize_dict(cls, data, intersect=intersect, dtype=dtype)
d[cls._info_axis] = Index(sorted(d['data'].keys()))
ks = d['data'].keys()
if not isinstance(d['data'],OrderedDict):
ks = list(sorted(ks))
d[cls._info_axis] = Index(ks)
return cls(**d)

def __getitem__(self, key):
Expand Down Expand Up @@ -1491,9 +1498,13 @@ def _homogenize_dict(self, frames, intersect=True, dtype=None):
-------
dict of aligned results & indicies
"""
result = {}
from pandas.util.compat import OrderedDict

adj_frames = {}
result = dict()
if isinstance(frames,OrderedDict): # caller differs dict/ODict, presered type
result = OrderedDict()

adj_frames = OrderedDict()
for k, v in frames.iteritems():
if isinstance(v, dict):
adj_frames[k] = self._constructor_sliced(v)
Expand Down
14 changes: 13 additions & 1 deletion pandas/tests/test_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
assert_frame_equal,
assert_series_equal,
assert_almost_equal,
ensure_clean)
ensure_clean,
makeCustomDataframe as mkdf
)
import pandas.core.panel as panelm
import pandas.util.testing as tm

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

def test_ctor_orderedDict(self):
from pandas.util.compat import OrderedDict
keys = list(set(np.random.randint(0,5000,100)))[:50] # unique random int keys
d = OrderedDict([(k,mkdf(10,5)) for k in keys])
p = Panel(d)
self.assertTrue(list(p.items) == keys)

p = Panel.from_dict(d)
self.assertTrue(list(p.items) == keys)

def test_constructor_resize(self):
data = self.panel._data
items = self.panel.items[:-1]
Expand Down
25 changes: 25 additions & 0 deletions pandas/util/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,3 +475,28 @@ def __and__(self, other):
Counter = _Counter
else:
from collections import OrderedDict, Counter

# http://stackoverflow.com/questions/4126348
# Thanks to @martineau at SO

class OrderedDefaultdict(OrderedDict):
def __init__(self, *args, **kwargs):
newdefault = None
newargs = ()
if args:
newdefault = args[0]
if not (newdefault is None or callable(newdefault)):
raise TypeError('first argument must be callable or None')
newargs = args[1:]
self.default_factory = newdefault
super(self.__class__, self).__init__(*newargs, **kwargs)

def __missing__ (self, key):
if self.default_factory is None:
raise KeyError(key)
self[key] = value = self.default_factory()
return value

def __reduce__(self): # optional, for pickle support
args = self.default_factory if self.default_factory else tuple()
return type(self), args, None, None, self.items()