Skip to content

Commit

Permalink
Merge a0f2556 into 7b4d464
Browse files Browse the repository at this point in the history
  • Loading branch information
twheys committed Jan 4, 2019
2 parents 7b4d464 + a0f2556 commit cbe9a98
Show file tree
Hide file tree
Showing 45 changed files with 820 additions and 292 deletions.
8 changes: 2 additions & 6 deletions fireant/formats.py
Expand Up @@ -6,11 +6,7 @@
time,
)

from fireant.utils import (
MAX_NUMBER,
MAX_STRING,
MAX_TIMESTAMP,
)
from fireant.slicer.totals import TOTALS_MARKERS

INF_VALUE = "Inf"
NULL_VALUE = 'null'
Expand Down Expand Up @@ -68,7 +64,7 @@ def dimension_value(value):
When True, dates and datetimes will be converted to ISO strings. The time is omitted for dates. When False, the
datetime will be converted to a POSIX timestamp (millis-since-epoch).
"""
if value in {MAX_STRING, MAX_NUMBER, MAX_TIMESTAMP}:
if value in TOTALS_MARKERS:
return 'Totals'

if pd.isnull(value):
Expand Down
1 change: 1 addition & 0 deletions fireant/slicer/__init__.py
Expand Up @@ -29,6 +29,7 @@
CumSum,
Operation,
RollingMean,
Share,
)
from .references import (
DayOverDay,
Expand Down
17 changes: 9 additions & 8 deletions fireant/slicer/dimensions.py
Expand Up @@ -76,10 +76,11 @@ def is_(self, value: bool):
:return:
A slicer query filter used to filter a slicer query to results where this dimension is True or False.
"""
return BooleanFilter(self.definition, value)
return BooleanFilter(self.key, self.definition, value)


class PatternFilterableMixin:
key = None
definition = None
pattern_definition_attribute = 'definition'

Expand All @@ -98,7 +99,7 @@ def like(self, pattern, *patterns):
matches the pattern.
"""
definition = getattr(self, self.pattern_definition_attribute)
return PatternFilter(definition, pattern, *patterns)
return PatternFilter(self.key, definition, pattern, *patterns)

def not_like(self, pattern, *patterns):
"""
Expand All @@ -115,7 +116,7 @@ def not_like(self, pattern, *patterns):
matches the pattern.
"""
definition = getattr(self, self.pattern_definition_attribute)
return AntiPatternFilter(definition, pattern, *patterns)
return AntiPatternFilter(self.key, definition, pattern, *patterns)


class CategoricalDimension(PatternFilterableMixin, Dimension):
Expand All @@ -142,7 +143,7 @@ def isin(self, values: Iterable):
A slicer query filter used to filter a slicer query to results where this dimension is one of a set of
values. Opposite of #notin.
"""
return ContainsFilter(self.definition, values)
return ContainsFilter(self.key, self.definition, values)

def notin(self, values):
"""
Expand All @@ -155,7 +156,7 @@ def notin(self, values):
A slicer query filter used to filter a slicer query to results where this dimension is *not* one of a set of
values. Opposite of #isin.
"""
return ExcludesFilter(self.definition, values)
return ExcludesFilter(self.key, self.definition, values)


class _UniqueDimensionBase(PatternFilterableMixin, Dimension):
Expand All @@ -170,7 +171,7 @@ def isin(self, values):
A slicer query filter used to filter a slicer query to results where this dimension is one of a set of
values. Opposite of #notin.
"""
return ContainsFilter(self.definition, values)
return ContainsFilter(self.key, self.definition, values)

def notin(self, values):
"""
Expand All @@ -183,7 +184,7 @@ def notin(self, values):
A slicer query filter used to filter a slicer query to results where this dimension is *not* one of a set of
values. Opposite of #isin.
"""
return ExcludesFilter(self.definition, values)
return ExcludesFilter(self.key, self.definition, values)


class UniqueDimension(_UniqueDimensionBase):
Expand Down Expand Up @@ -288,7 +289,7 @@ def between(self, start, stop):
A slicer query filter used to filter a slicer query to results where this dimension is between the values
start and stop.
"""
return RangeFilter(self.definition, start, stop)
return RangeFilter(self.key, self.definition, start, stop)


class TotalsDimension(Dimension):
Expand Down
4 changes: 4 additions & 0 deletions fireant/slicer/exceptions.py
Expand Up @@ -18,6 +18,10 @@ class RollupException(SlicerException):
pass


class MissingTotalsForShareException(Exception):
pass


class MetricRequiredException(SlicerException):
pass

Expand Down
41 changes: 24 additions & 17 deletions fireant/slicer/filters.py
Expand Up @@ -15,11 +15,15 @@ def __repr__(self):


class DimensionFilter(Filter):
pass
def __init__(self, dimension_key, definition):
super().__init__(definition)
self.dimension_key = dimension_key


class MetricFilter(Filter):
pass
def __init__(self, metric_key, definition):
super().__init__(definition)
self.metric_key = metric_key


class ComparatorFilter(MetricFilter):
Expand All @@ -31,36 +35,43 @@ class Operator(object):
gte = 'gte'
lte = 'lte'

def __init__(self, metric_definition, operator, value):
def __init__(self, metric_key, metric_definition, operator, value):
definition = getattr(metric_definition, operator)(value)
super(ComparatorFilter, self).__init__(definition)
super(ComparatorFilter, self).__init__(metric_key, definition)


class BooleanFilter(DimensionFilter):
def __init__(self, element_key, value):
definition = element_key if value else Not(element_key)
super(BooleanFilter, self).__init__(definition)
def __init__(self, dimension_key, dimension_definition, value):
definition = dimension_definition \
if value \
else Not(dimension_definition)

super(BooleanFilter, self).__init__(dimension_key, definition)


class ContainsFilter(DimensionFilter):
def __init__(self, dimension_definition, values):
def __init__(self, dimension_key, dimension_definition, values):
definition = dimension_definition.isin(values)
super(ContainsFilter, self).__init__(definition)
super(ContainsFilter, self).__init__(dimension_key, definition)


class ExcludesFilter(DimensionFilter):
def __init__(self, dimension_definition, values):
def __init__(self, dimension_key, dimension_definition, values):
definition = dimension_definition.notin(values)
super(ExcludesFilter, self).__init__(definition)
super(ExcludesFilter, self).__init__(dimension_key, definition)


class RangeFilter(DimensionFilter):
def __init__(self, dimension_definition, start, stop):
def __init__(self, dimension_key, dimension_definition, start, stop):
definition = dimension_definition[start:stop]
super(RangeFilter, self).__init__(definition)
super(RangeFilter, self).__init__(dimension_key, definition)


class PatternFilter(DimensionFilter):
def __init__(self, dimension_key, dimension_definition, pattern, *patterns):
definition = self._apply(dimension_definition, (pattern,) + patterns)
super(PatternFilter, self).__init__(dimension_key, definition)

def _apply(self, dimension_definition, patterns):
definition = Lower(dimension_definition).like(Lower(patterns[0]))

Expand All @@ -69,10 +80,6 @@ def _apply(self, dimension_definition, patterns):

return definition

def __init__(self, dimension_definition, pattern, *patterns):
definition = self._apply(dimension_definition, (pattern,) + patterns)
super(PatternFilter, self).__init__(definition)


class AntiPatternFilter(PatternFilter):
def _apply(self, dimension_definition, pattern):
Expand Down
28 changes: 22 additions & 6 deletions fireant/slicer/metrics.py
@@ -1,3 +1,6 @@
from fireant.utils import (
immutable,
)
from .base import SlicerElement
from .filters import ComparatorFilter

Expand Down Expand Up @@ -30,24 +33,37 @@ def __init__(self, key, definition, label=None, precision=None, prefix=None, suf
self.precision = precision
self.prefix = prefix
self.suffix = suffix
self._share = False

def __eq__(self, other):
return ComparatorFilter(self.definition, ComparatorFilter.Operator.eq, other)
return ComparatorFilter(self.key, self.definition, ComparatorFilter.Operator.eq, other)

def __ne__(self, other):
return ComparatorFilter(self.definition, ComparatorFilter.Operator.ne, other)
return ComparatorFilter(self.key, self.definition, ComparatorFilter.Operator.ne, other)

def __gt__(self, other):
return ComparatorFilter(self.definition, ComparatorFilter.Operator.gt, other)
return ComparatorFilter(self.key, self.definition, ComparatorFilter.Operator.gt, other)

def __ge__(self, other):
return ComparatorFilter(self.definition, ComparatorFilter.Operator.gte, other)
return ComparatorFilter(self.key, self.definition, ComparatorFilter.Operator.gte, other)

def __lt__(self, other):
return ComparatorFilter(self.definition, ComparatorFilter.Operator.lt, other)
return ComparatorFilter(self.key, self.definition, ComparatorFilter.Operator.lt, other)

def __le__(self, other):
return ComparatorFilter(self.definition, ComparatorFilter.Operator.lte, other)
return ComparatorFilter(self.key, self.definition, ComparatorFilter.Operator.lte, other)

def __repr__(self):
return "slicer.metrics.{}".format(self.key)

@property
@immutable
def share(self):
self._share = True
return self

@property
@immutable
def share(self):
self._share = True
return self
66 changes: 65 additions & 1 deletion fireant/slicer/operations.py
@@ -1,8 +1,14 @@
import numpy as np
import pandas as pd

from fireant.utils import format_metric_key
from fireant.slicer.references import reference_key
from fireant.slicer.totals import get_totals_marker_for_dtype
from fireant.utils import (
format_dimension_key,
format_metric_key,
reduce_data_frame_levels,
)
from .dimensions import Dimension
from .metrics import Metric


Expand Down Expand Up @@ -194,3 +200,61 @@ def apply(self, data_frame, reference):
.apply(self.rolling_mean)

return self.rolling_mean(data_frame[df_key])


class Share(_BaseOperation):
def __init__(self, metric: Metric, over: Dimension = None, precision=2):
super(Share, self).__init__(
key='share({},{})'.format(getattr(metric, 'key', metric),
getattr(over, 'key', over), ),
label='Share of {} over {}'.format(getattr(metric, 'label', metric),
getattr(over, 'label', over)),
prefix=None,
suffix='%',
precision=precision,
)

self.metric = metric
self.over = over

@property
def metrics(self):
return [metric
for metric in [self.metric]
if isinstance(metric, Metric)]

@property
def operations(self):
return [op_and_children
for operation in [self.metric]
if isinstance(operation, Operation)
for op_and_children in [operation] + operation.operations]

def apply(self, data_frame, reference):
f_metric_key = format_metric_key(reference_key(self.metric, reference))

if self.over is None:
df = data_frame[f_metric_key]
return 100 * df / df

if not isinstance(data_frame.index, pd.MultiIndex):
marker = get_totals_marker_for_dtype(data_frame.index.dtype)
totals = data_frame.loc[marker, f_metric_key]
return 100 * data_frame[f_metric_key] / totals

f_over_key = format_dimension_key(self.over.key)
idx = data_frame.index.names.index(f_over_key)
group_levels = data_frame.index.names[idx:]
over_dim_value = get_totals_marker_for_dtype(data_frame.index.levels[idx].dtype)
totals_key = (slice(None),) * idx + (slice(over_dim_value, over_dim_value),)

totals = reduce_data_frame_levels(data_frame.loc[totals_key, f_metric_key], group_levels)

def apply_totals(df):
return 100 * reduce_data_frame_levels(df / totals, group_levels)

return data_frame[f_metric_key] \
.groupby(level=group_levels) \
.apply(apply_totals) \
.reorder_levels(order=data_frame.index.names) \
.sort_index()

0 comments on commit cbe9a98

Please sign in to comment.