Skip to content

Commit

Permalink
Merge branch 'improve-slices-#158252633' into rel-5.2.94
Browse files Browse the repository at this point in the history
  • Loading branch information
Crunch.io Jenkins Account committed Jun 21, 2018
2 parents 71b3af4 + 52a2d01 commit d8e1b0e
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 153 deletions.
16 changes: 0 additions & 16 deletions src/cr/cube/crunch_cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,22 +568,6 @@ def dim_types(self):
def ndim(self):
return len(self.dimensions)

@lazyproperty
def row_dim_ind(self):
"""
Index of the row dimension in the cube
:rtype: int
"""
return 0 if self.ndim < 3 else 1

@lazyproperty
def col_dim_ind(self):
"""
Index of the column dimension in the cube
:rtype: int
"""
return 1 if self.ndim < 3 else 2

@lazyproperty
def ind_selected(self):
return self._get_mr_slice()
Expand Down
202 changes: 98 additions & 104 deletions src/cr/cube/cube_slice.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'''Home of the CubeSlice class.'''

from functools import partial
import numpy as np

from .utils import lazyproperty


# pylint: disable=too-few-public-methods
class CubeSlice(object):
'''Implementation of CubeSlice class.
Expand All @@ -13,10 +13,32 @@ class CubeSlice(object):
achieved by slicing.
'''

row_dim_ind = 0
col_dim_ind = 1

def __init__(self, cube, index):
self._cube = cube
self._index = index

def __getattr__(self, attr):
cube_attr = getattr(self._cube, attr)

# API Method calls
if callable(cube_attr):
return partial(self._call_cube_method, attr)

# API properties
get_only_last_two = (
self._cube.ndim == 3 and
hasattr(cube_attr, '__len__') and
attr != 'name'
)
if get_only_last_two:
return cube_attr[-2:]

# If not defined on self._cube, return CubeSlice properties
return cube_attr

def _update_args(self, kwargs):

if self.ndim < 3:
Expand All @@ -25,134 +47,106 @@ def _update_args(self, kwargs):
# pass them to the underlying cube (which is the slice).
return kwargs

# Handling API methods that include 'axis' parameter

axis = kwargs.get('axis')
if axis is None or isinstance(axis, tuple):
# If no axis was passed, we don't need to update anything. If axis
# was a tuple, it means that the caller was expecting a 3D cube.
return kwargs
# Expected usage of the 'axis' parameter from CubeSlice is 0, 1, or
# None. CrunchCube handles all other logic. The only 'smart' thing
# about the handling here, is that the axes are increased for 3D cubes.
# This way the 3Dness is hidden from the user and he still sees 2D
# crosstabs, with col and row axes (0 and 1), which are transformed to
# corresponding numbers in case of 3D cubes (namely 1 and 2). In the
# case of None, we need to analyze across all valid dimensions, and the
# CrunchCube takes care of that (no need to update axis if it's None).
# If the user provides a tuple, it's considered that he "knows" what
# he's doing, and the axis argument is not updated in this case.
if isinstance(axis, int):
kwargs['axis'] += 1

# Handling API methods that include H&S parameter

# For most cr.cube methods, we use the 'include_transforms_for_dims'
# parameter name. For some, namely the prune_indices, we use the
# 'transforms'. These are parameters that tell to the cr.cube "which
# dimensions to include the H&S for". The only point of this parameter
# (from the perspective of the cr.exporter) is to exclude the 0th
# dimension's H&S in the case of 3D cubes.
hs_dims_key = (
'transforms'
if 'transforms' in kwargs else
'include_transforms_for_dims'
)
hs_dims = kwargs.get(hs_dims_key)
if isinstance(hs_dims, list):
# Keep the 2D illusion for the user. If a user sees a 2D slice, he
# still needs to be able to address both dimensions (for which he
# wants the H&S included) as 0 and 1. Since these are offset by a 0
# dimension in a 3D case, inside the cr.cube, we need to increase
# the indexes of the required dims.
kwargs[hs_dims_key] = [dim + 1 for dim in hs_dims]

kwargs['axis'] += 1
return kwargs

def _update_result(self, result):
if self.ndim < 3 or len(result) - 1 < self._index:
return result
result = result[self._index]
if not isinstance(result, np.ndarray):
if isinstance(result, tuple):
return np.array(result)
elif not isinstance(result, np.ndarray):
result = np.array([result])
return result

def _call_cube_method(self, method, *args, **kwargs):
kwargs = self._update_args(kwargs)
result = getattr(self._cube, method)(*args, **kwargs)
if method in ('labels', 'inserted_hs_indices'):
return result[-2:]
return self._update_result(result)

# Properties

@lazyproperty
def ndim(self):
'''Get number of dimensions.'''
return self._cube.ndim

@lazyproperty
def name(self):
@property
def table_name(self):
'''Get slice name.
In case of 2D return cube name. In case of 3D, return the combination
of the cube name with the label of the corresponding slice
(nth label of the 0th dimension).
'''
title = self._cube.name

if self.ndim < 3:
return title
return None

title = self._cube.name
table_name = self._cube.labels()[0][self._index]
return '%s: %s' % (title, table_name)

@lazyproperty
def rows_title(self):
'''Get title of the rows dimension.
For 3D it's the 1st dimension (0th dimension of the current slice).
'''
return self._cube.dimensions[1].name

@lazyproperty
def inserted_rows_indices(self):
''' Get correct inserted rows indices for the corresponding slice.
For 3D cubes, a list of tuples is returned from the cube, when invoking
the inserted_hs_indices method. The job of this property is to fetch
the correct tuple (the one corresponding to the current slice index),
and return the 0th value (the one corresponding to the rows).
'''
return self._cube.inserted_hs_indices()[self._index][0]

@lazyproperty
def has_means(self):
'''Get has_means from cube.'''
return self._cube.has_means

@lazyproperty
def dimensions(self):
'''Get slice dimensions.
@property
def has_ca(self):
'''Check if the cube slice has the CA dimension.
For 2D cubes just get their dimensions. For 3D cubes, don't get the
first dimension (only take slice dimensions).
This is used to distinguish between slices that are considered 'normal'
(like CAT x CAT), that might be a part of te 3D cube that has 0th dim
as the CA items (subvars). In such a case, we still need to process
the slices 'normally', and not address the CA items constraints.
'''
return self._cube.dimensions[-2:]

# API Methods

def labels(self, *args, **kwargs):
'''Return correct labels for slice.'''
return self._cube.labels(*args, **kwargs)[-2:]

def prune_indices(self, *args, **kwargs):
'''Extract correct row/col prune indices from 3D cube.'''
if self.ndim < 3:
return self._cube.prune_indices(*args, **kwargs)
return list(self._cube.prune_indices(*args, **kwargs)[self._index])

def as_array(self, *args, **kwargs):
'''Call cube's as_array, and return correct slice.'''
return self._call_cube_method('as_array', *args, **kwargs)

def proportions(self, *args, **kwargs):
'''Call cube's proportions, and return correct slice.'''
return self._call_cube_method('proportions', *args, **kwargs)

def margin(self, *args, **kwargs):
'''Call cube's margin, and return correct slice.'''
return self._call_cube_method('margin', *args, **kwargs)

def population_counts(self, *args, **kwargs):
'''Get population counts.'''
return self._call_cube_method('population_counts', *args, **kwargs)

@lazyproperty
def standardized_residuals(self):
'''Get cube's standardized residuals.'''
return self._cube.standardized_residuals

def index(self, *args, **kwargs):
'''Get index.'''
return self._call_cube_method('index', *args, **kwargs)

def zscore(self, *args, **kwargs):
'''Get index.'''
return self._call_cube_method('zscore', *args, **kwargs)

@lazyproperty
def dim_types(self):
'''Get dimension types of the cube slice.'''
return self._cube.dim_types[-2:]

def pvals(self, *args, **kwargs):
'''Get pvals of the cube.'''
return self._call_cube_method('pvals', *args, **kwargs)

def inserted_hs_indices(self, *args, **kwargs):
'''Get inserted H&S indices.'''
return self._cube.inserted_hs_indices(*args, **kwargs)[-2:]
return 'categorical_array' in self.dim_types

@property
def mr_dim_ind(self):
'''Get the correct index of the MR dimension in the cube slice.'''
mr_dim_ind = self._cube.mr_dim_ind
if self.ndim == 3:
if isinstance(mr_dim_ind, int):
return mr_dim_ind - 1
elif isinstance(mr_dim_ind, tuple):
return tuple(i - 1 for i in mr_dim_ind)

return mr_dim_ind

@property
def ca_main_axis(self):
'''For univariate CA, the main axis is the categorical axis'''
ca_ind = self.dim_types.index('categorical_array')
if ca_ind is not None:
return 1 - ca_ind
else:
return None
Loading

0 comments on commit d8e1b0e

Please sign in to comment.