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