Passed
Push — dev ( 1b3874...6a1e3c )
by Konstantinos
03:33
created

green_magic.data.variables.features   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 130
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 27
eloc 88
dl 0
loc 130
rs 10
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A StateMachine.current() 0 3 1
A StateMachine.state() 0 4 1
A StateMachine.update() 0 9 4
A FeatureFunction.is_callable() 0 6 3
A TrackingFeature.values() 0 2 1
A AbstractFeature.nb_unique() 0 2 1
A PhiFeatureFunction.__call__() 0 2 1
A BaseFeature.values() 0 3 1
A FeatureInterface.values() 0 3 1
A TrackingFeature.state() 0 4 1
A FeatureFunction.values() 0 2 1
A FeatureFunction.state() 0 3 1
A TrackingFeature.update() 0 2 1
A FeatureFunction.is_label() 0 4 2
A FeatureState.__str__() 0 2 1
A TrackingFeature.from_callable() 0 4 1
A TrackingFeature.label() 0 2 1

2 Functions

Rating   Name   Duplication   Size   Complexity  
A _string_validator() 0 3 2
A _list_validator() 0 3 2
1
from abc import ABC, abstractmethod
2
import attr
3
4
5
class FeatureInterface(ABC):
6
    @abstractmethod
7
    def values(self, dataset):
8
        raise NotImplementedError
9
10
#### HELPERS
11
def _list_validator(self, attribute, value):
12
    if not type(value) == list:
13
        raise ValueError(f'Expected a list; instead a {type(value).__name__} was given.')
14
15
def _string_validator(self, attribute, value):
16
    if not type(value) == str:
17
        raise ValueError(f'Expected a string; instead a {type(value).__name__} was given.')
18
19
20
class AbstractFeature(FeatureInterface, ABC):
21
    def nb_unique(self):
22
        pass
23
24
25
@attr.s
26
class BaseFeature(AbstractFeature):
27
    label = attr.ib(init=True)
28
29
    def values(self, dataset):
30
        """A default implementation of the values method"""
31
        return dataset[self.label]
32
33
34
@attr.s
35
class FeatureState:
36
    key = attr.ib(init=True)
37
    reporter = attr.ib(init=True)
38
39
    def __str__(self):
40
        return self.key
41
42
43
@attr.s
44
class FeatureFunction(BaseFeature):
45
    """Example: Assume we have a datapoint v = [v_1, v_2, .., v_n, and 2 feature functions f_1, f_2\n
46
    Then we can produce an encoded vector (eg to feed for training a ML model) like: encoded_vector = [f_1(v), f_2(v)]
47
    """
48
    function = attr.ib(init=True)
49
    @function.validator
50
    def is_callable(self, attribute, value):
51
        if not callable(value):
52
            raise ValueError(f"Expected a callable object; instead {type(value)} was given.")
53
        if value.func_code.co_argcount < 1:
54
            raise ValueError(f"Expected a callable that takes at least 1 argument; instead a callable that takes no arguments was given.")
55
56
    label = attr.ib(init=True, default=None)
57
    @label.validator
58
    def is_label(self, attribute, value):
59
        if value is None:
60
            self.label = self.function.func_name
61
62
    def values(self, dataset):
63
        return self.function(dataset)
64
65
    @property
66
    def state(self):
67
        return FeatureState(self.label, self.function)
68
69
70
@attr.s
71
class StateMachine:
72
    states = attr.ib(init=True)
73
    init_state = attr.ib(init=True)
74
    _current = attr.ib(init=False, default=attr.Factory(lambda self: self.init_state, takes_self=True))
75
76
    @property
77
    def current(self):
78
        return self._current
79
80
    def update(self, *args, **kwargs):
81
        if 1 < len(args):
82
            self.states[args[0]] = args[1]
83
            self._current = args[0]
84
        elif 0 < len(args):
85
            if args[0] in self.states:
86
                self._current = args[0]
87
            else:
88
                raise RuntimeError(f"Requested to set the current state to '{args[0]}', it is not in existing [{', '.join(sorted(self.states))}]")
89
90
    @property
91
    def state(self):
92
        """Construct an object representing the current state"""
93
        return FeatureState(self._current, self.states[self._current])
94
95
96
@attr.s
97
class TrackingFeature:
98
    feature = attr.ib(init=True)
99
    state_machine = attr.ib(init=True)
100
    variable_type = attr.ib(init=True, default=None)
101
102
    @classmethod
103
    def from_callable(cls, a_callable, label=None, variable_type=None):
104
        """Construct a feature that has one extract/report capability. Input id is correlated to the features position on the vector (see FeatureFunction above)"""
105
        return TrackingFeature(FeatureFunction(a_callable, label), StateMachine({'raw': a_callable}, 'raw'), variable_type)
106
107
    def values(self, dataset):
108
        return self.state_machine.state.reporter(dataset)
109
110
    def label(self):
111
        return self.feature.label
112
113
    @property
114
    def state(self):
115
        """Returns the current state"""
116
        return self.state_machine.state
117
118
    def update(self, *args, **kwargs):
119
        self.state_machine.update(*args, **kwargs)
120
121
122
@attr.s
123
class FeatureIndex:
124
    keys = attr.ib(init=True, validator=_list_validator)
125
126
127
class PhiFeatureFunction:
128
    def __call__(self, *args, **kwargs):
129
        raise NotImplementedError