From b67c1c57aa73c11901f8eea9b6f863e5eda2ad6a Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 8 Jun 2016 01:51:42 +0100
Subject: [PATCH] Implemented redim method on Dimensioned objects
Additionally adds rename method on interfaces
---
holoviews/core/data/__init__.py | 31 ++++++++++++++++-
holoviews/core/data/dictionary.py | 4 +++
holoviews/core/data/interface.py | 4 +++
holoviews/core/data/iris.py | 15 ++++++++
holoviews/core/data/ndelement.py | 4 +++
holoviews/core/data/pandas.py | 7 +++-
holoviews/core/dimension.py | 58 +++++++++++++++++++++++++++++++
7 files changed, 121 insertions(+), 2 deletions(-)
diff --git a/holoviews/core/data/__init__.py b/holoviews/core/data/__init__.py
index d6db45fbf6..a684945ca8 100644
--- a/holoviews/core/data/__init__.py
+++ b/holoviews/core/data/__init__.py
@@ -32,7 +32,7 @@
except ImportError:
pass
-from ..dimension import Dimension
+from ..dimension import Dimension, replace_dimensions
from ..element import Element
from ..spaces import HoloMap
from .. import util
@@ -390,6 +390,35 @@ def shape(self):
return self.interface.shape(self)
+ def redim(self, specs=None, **dimensions):
+ """
+ Replace dimensions on the dataset and allows renaming
+ dimensions in the dataset. Dimension mapping should map
+ between the old dimension name and either a dictionary of the
+ new attributes or a completely new dimension to replace it
+ with.
+ """
+ if specs is None:
+ applies = True
+ else:
+ if not isinstance(specs, list):
+ specs = [specs]
+ applies = any(self.matches(spec) for spec in specs)
+
+ if applies:
+ kdims = replace_dimensions(self.kdims, dimensions)
+ vdims = replace_dimensions(self.vdims, dimensions)
+ renames = {pk.name: nk.name for pk, nk in
+ zip(self.kdims+self.vdims, kdims+vdims)
+ if pk != nk}
+ renamed = self.data
+ if renames:
+ renamed = self.interface.rename(self, renames)
+ return self.clone(renamed, kdims=kdims, vdims=vdims)
+ else:
+ return self
+
+
def dimension_values(self, dim, expanded=True, flat=True):
"""
Returns the values along a particular dimension. If unique
diff --git a/holoviews/core/data/dictionary.py b/holoviews/core/data/dictionary.py
index 0611ff990f..c825dc737e 100644
--- a/holoviews/core/data/dictionary.py
+++ b/holoviews/core/data/dictionary.py
@@ -122,6 +122,10 @@ def add_dimension(cls, dataset, dimension, dim_pos, values, vdim):
data.insert(dim_pos, (dim, values))
return OrderedDict(data)
+ @classmethod
+ def rename(cls, dataset, renames):
+ return OrderedDict([(renames.get(k, k), v)
+ for k,v in dataset.data.items()])
@classmethod
def concat(cls, dataset_objs):
diff --git a/holoviews/core/data/interface.py b/holoviews/core/data/interface.py
index 1b334151ca..5eb9f1e9f0 100644
--- a/holoviews/core/data/interface.py
+++ b/holoviews/core/data/interface.py
@@ -204,3 +204,7 @@ def shape(cls, dataset):
@classmethod
def length(cls, dataset):
return len(dataset.data)
+
+ @classmethod
+ def rename(cls, dataset, renames):
+ return dataset.data
diff --git a/holoviews/core/data/iris.py b/holoviews/core/data/iris.py
index 84e2ccffd4..162a2f9379 100644
--- a/holoviews/core/data/iris.py
+++ b/holoviews/core/data/iris.py
@@ -198,6 +198,21 @@ def range(cls, dataset, dimension):
return (np.nanmin(values), np.nanmax(values))
+ @classmethod
+ def rename(cls, dataset, renames):
+ """
+ Rename coords on the Cube.
+ """
+ new_dataset = dataset.data.copy()
+ for name, new_name in renames.items():
+ if name == dataset.data.name():
+ new_dataset.rename(new_name)
+ for coord in dataset.data.dim_coords:
+ if name == coord.name():
+ coord.rename(new_name)
+ return new_dataset
+
+
@classmethod
def length(cls, dataset):
"""
diff --git a/holoviews/core/data/ndelement.py b/holoviews/core/data/ndelement.py
index 859825253a..5af397558e 100644
--- a/holoviews/core/data/ndelement.py
+++ b/holoviews/core/data/ndelement.py
@@ -75,6 +75,10 @@ def validate(cls, columns):
def dimension_type(cls, columns, dim):
return Dimensioned.get_dimension_type(columns, dim)
+ @classmethod
+ def rename(cls, dataset, renames):
+ return dataset.data.redim(**renames)
+
@classmethod
def shape(cls, columns):
return (len(columns), len(columns.dimensions()))
diff --git a/holoviews/core/data/pandas.py b/holoviews/core/data/pandas.py
index f1df76c9cd..852023f339 100644
--- a/holoviews/core/data/pandas.py
+++ b/holoviews/core/data/pandas.py
@@ -151,6 +151,11 @@ def reindex(cls, columns, kdims=None, vdims=None):
return columns.data
+ @classmethod
+ def rename(cls, dataset, renames):
+ return dataset.data.rename(columns=renames)
+
+
@classmethod
def sort(cls, columns, by=[]):
import pandas as pd
@@ -210,7 +215,7 @@ def add_dimension(cls, columns, dimension, dim_pos, values, vdim):
@classmethod
def dframe(cls, columns, dimensions):
if dimensions:
- return columns.reindex(columns=dimensions)
+ return columns.reindex(dimensions).data.copy()
else:
return columns.data.copy()
diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py
index 60f648bd9d..16ea963103 100644
--- a/holoviews/core/dimension.py
+++ b/holoviews/core/dimension.py
@@ -39,6 +39,35 @@ def param_aliases(d):
return d
+def replace_dimensions(dimensions, overrides):
+ """
+ Replaces dimensions in a list with a dictionary of overrides.
+ Overrides should be indexed by the dimension name with values that
+ is either a Dimension object, a string name or a dictionary
+ specifying the dimension parameters to override.
+ """
+ replaced = []
+ for d in dimensions:
+ if d.name in overrides:
+ override = overrides[d.name]
+ else:
+ override = None
+
+ if override is None:
+ replaced.append(d)
+ elif isinstance(override, basestring):
+ replaced.append(d(override))
+ elif isinstance(override, Dimension):
+ replaced.append(override)
+ elif isinstance(override, dict):
+ replaced.append(d(**override))
+ else:
+ raise ValueError('Dimension can only be overridden '
+ 'with another dimension or a dictionary '
+ 'of attributes')
+ return replaced
+
+
class Dimension(param.Parameterized):
"""
Dimension objects are used to specify some important general
@@ -766,6 +795,35 @@ def select(self, selection_specs=None, **kwargs):
return selection
+ def redim(self, specs=None, **dimensions):
+ """
+ Replaces existing dimensions in an object with new dimensions
+ or changing specific attributes of a dimensions. Dimension
+ mapping should map between the old dimension name and either a
+ dictionary of the new attributes or a completely new dimension
+ to replace it with.
+ """
+ if specs is None:
+ applies = True
+ else:
+ if not isinstance(specs, list):
+ specs = [specs]
+ applies = any(self.matches(spec) for spec in specs)
+
+ redimmed = self
+ if self._deep_indexable:
+ deep_mapped = [(k, v.redim(specs, **dimensions))
+ for k, v in self.items()]
+ redimmed = self.clone(deep_mapped)
+
+ if applies:
+ kdims = replace_dimensions(self.kdims, dimensions)
+ vdims = replace_dimensions(self.vdims, dimensions)
+ return redimmed.clone(kdims=kdims, vdims=vdims)
+ else:
+ return redimmed
+
+
def dimension_values(self, dimension, expanded=True, flat=True):
"""
Returns the values along the specified dimension. This method