forked from hlin117/mdlp-discretization
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdiscretization.py
More file actions
177 lines (145 loc) · 6.71 KB
/
Copy pathdiscretization.py
File metadata and controls
177 lines (145 loc) · 6.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Title : discretization.py
# Author : Henry Lin <hlin117@gmail.com>
# License : BSD 3 clause
#==============================================================================
from __future__ import division
import numpy as np
from sklearn.base import BaseEstimator
from sklearn.base import TransformerMixin
from sklearn.utils import check_array, check_X_y, column_or_1d
from _mdlp import MDLPDiscretize
class MDLP(BaseEstimator, TransformerMixin):
"""Implements the MDLP discretization criterion from Usama Fayyad's
paper "Multi-Interval Discretization of Continuous-Valued
Attributes for Classification Learning". Given the class labels
for each sample, this transformer attempts to discretize a
continuous attribute by minimizing the entropy at each interval.
Attributes
----------
min_depth : The minimum depth of the interval splitting. Overrides
the MDLP stopping criterion. If the entropy at a given interval
is found to be zero before `min_depth`, the algorithm will stop.
Defaults to 0.
cut_points_ : A dictionary mapping indices to a numpy array. Each
numpy array is a sorted list of cut points found from
discretization.
dimensions_ : Describes whether `X` is a 2-D or 1-D array.
continuous_features_ : A list of indices indicating which columns
should be discretized.
If `X` is a 1-D array, then should be None. Otherwise, for a
2-D array, defaults to `range(X.shape[1])`.
Examples
--------
>>> from discretization import MDLP
>>> from sklearn.datasets import load_iris
>>> iris = load_iris()
>>> X = iris.data
>>> y = iris.target
>>> mdlp = MDLP()
>>> conv_X = mdlp.fit_transform(X, y)
`conv_X` will be the same shape as `X`, except it will contain
integers instead of continuous attributes representing the results
of the discretization process.
To retrieve the explicit intervals of the discretization of, say,
the third column (index 2), one can do
>>> mdlp.cat2intervals(conv_X, 2)
which would return a list of tuples `(a, b)`. Each tuple represents
the contnuous interval (a, b], where `a` can be `float("-inf")`,
and `b` can be `float("inf")`.
"""
def __init__(self, continuous_features=None, min_depth=0, shuffle=True):
self.continuous_features_ = continuous_features
self.min_depth = min_depth
self.shuffle = shuffle
self.cut_points_ = None
self.dimensions_ = None
def fit(self, X, y):
"""Finds the intervals of interest from the input data.
Parameters
----------
X : The array containing features to be discretized. Continuous
features should be specified by the `continuous_features`
attribute if `X` is a 2-D array.
y : A list or array of class labels corresponding to `X`.
"""
self.dimensions_ = len(X.shape)
if self.dimensions_ > 2:
raise ValueError("Invalid input dimension for `X`. Input shape is"
"{0}".format(X.shape))
X = check_array(X, force_all_finite=True, ensure_2d=False)
y = column_or_1d(y)
y = check_array(y, ensure_2d=False, dtype=int)
X, y = check_X_y(X, y)
if self.dimensions_ == 2:
if self.continuous_features_ is None:
self.continuous_features_ = range(X.shape[1])
self.cut_points_ = dict()
for index, col in enumerate(X.T):
if index not in self.continuous_features_:
continue
cut_points = MDLPDiscretize(col, y, self.shuffle, self.min_depth)
self.cut_points_[index] = cut_points
else:
if self.continuous_features_ is not None:
raise ValueError("Passed in a 1-d column of continuous features, "
"but continuous_features is not None")
self.continuous_features_ = None
cut_points = MDLPDiscretize(X, y, self.shuffle, self.min_depth)
self.cut_points_ = cut_points
return self
def transform(self, X, y=None):
"""Converts the continuous features in X into integers from
0... k-1 (`k` is the number of intervals the discretizer created
from a given continuous feature.)
"""
if self.cut_points_ is None:
raise ValueError("You must fit the MDLP discretizer before "
"transforming data.")
if self.dimensions_ == 1:
output = np.searchsorted(self.cut_points_, X)
else:
output = X.copy()
for i in self.continuous_features_:
output[:, i] = np.searchsorted(self.cut_points_[i], X[:, i])
return output
def cat2intervals(self, X, index=None):
"""Converts a categorical feature into a list of intervals.
"""
# TODO: Throw warning if `self.dimensions_` == 1 and index is not None
if self.dimensions_ == 1:
return self._assign_intervals(X, index)
elif self.dimensions_ == 2 and index is None:
raise ValueError("Index of `X` to be discretized needs to be "
"specified.")
else:
cp_indices = X.T[index]
return self._assign_intervals(cp_indices, index)
def cts2cat(self, col, index=None):
"""Converts each continuous feature from index `index` into
a categorical feature from the input column `col`.
"""
if self.dimensions_ == 1:
return np.searchsorted(self.cut_points_, col)
if self.dimensions_ == 2 and index is None:
raise ValueError("Index of `X` to be discretized needs to be "
"specified.")
return np.searchsorted(self.cut_points_[index], col)
def _assign_intervals(self, cp_indices, index):
"""Assigns the cut point indices `cp_indices` (representing
categorical features) into a list of intervals.
"""
# Case for a 1-D array
if self.dimensions_ == 1:
cut_points = self.cut_points_
else:
cut_points = self.cut_points_[index]
non_zero_mask = cp_indices[cp_indices - 1 != -1].astype(int) - 1
fronts = np.zeros(cp_indices.shape)
fronts[cp_indices == 0] = float("-inf")
fronts[cp_indices != 0] = cut_points[non_zero_mask]
numCuts = len(cut_points)
backs = np.zeros(cp_indices.shape)
non_numCuts_mask = cp_indices[cp_indices != numCuts].astype(int)
backs[cp_indices == numCuts] = float("inf")
backs[cp_indices != numCuts] = cut_points[non_numCuts_mask]
return [(front, back) for front, back in zip(fronts, backs)]