SKSurrogate 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- SKSurrogate/DataProcess.py +409 -0
- SKSurrogate/NpyProximation.py +457 -0
- SKSurrogate/__init__.py +110 -0
- SKSurrogate/aml.py +1156 -0
- SKSurrogate/ci.py +93 -0
- SKSurrogate/deployment.py +31 -0
- SKSurrogate/eoa.py +381 -0
- SKSurrogate/execution.py +416 -0
- SKSurrogate/inference.py +224 -0
- SKSurrogate/mltrace.py +2138 -0
- SKSurrogate/modelbundle.py +600 -0
- SKSurrogate/monitoring.py +360 -0
- SKSurrogate/retraining.py +49 -0
- SKSurrogate/sensapprx.py +250 -0
- SKSurrogate/structsearch.py +1411 -0
- SKSurrogate/synthdat.py +503 -0
- sksurrogate-0.1.0.dist-info/METADATA +139 -0
- sksurrogate-0.1.0.dist-info/RECORD +21 -0
- sksurrogate-0.1.0.dist-info/WHEEL +5 -0
- sksurrogate-0.1.0.dist-info/entry_points.txt +2 -0
- sksurrogate-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DataProcess: A DataFrame preprocessing module for ML
|
|
3
|
+
===========================================================
|
|
4
|
+
|
|
5
|
+
The module is designed to do some preprocessing task on a given DataFrame.
|
|
6
|
+
Currently, the module detects types of columns data e.g., numerical (integer, float), categorical, boolean, ordinal,
|
|
7
|
+
datetime, and labels. To impose a text column, the type of the column should be declared before passing the DataFrame.
|
|
8
|
+
This can be done by a simple code such as `df.astype({'txt_column':pandas.StringDtype()})`
|
|
9
|
+
|
|
10
|
+
The module performs ordinal encoding, one hot encoding, date range to float transformation, label encoding, and
|
|
11
|
+
missing data imputation.
|
|
12
|
+
|
|
13
|
+
To Do
|
|
14
|
+
----------
|
|
15
|
+
Processing of Image data.
|
|
16
|
+
|
|
17
|
+
Code
|
|
18
|
+
----------
|
|
19
|
+
"""
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
import numpy
|
|
23
|
+
import pandas
|
|
24
|
+
import pandas as pd
|
|
25
|
+
from copy import copy
|
|
26
|
+
import re
|
|
27
|
+
from sklearn.base import BaseEstimator, TransformerMixin
|
|
28
|
+
from sklearn.pipeline import Pipeline
|
|
29
|
+
from sklearn.experimental import enable_iterative_imputer
|
|
30
|
+
from sklearn.impute import IterativeImputer
|
|
31
|
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DateTime2Num(TransformerMixin, BaseEstimator):
|
|
35
|
+
"""
|
|
36
|
+
Converts a date range data to numeric (float) between `lower` and `upper`.
|
|
37
|
+
|
|
38
|
+
:param cols: default: `None`; column names for transformation.
|
|
39
|
+
:param unit: default: 'day`; the time unit.
|
|
40
|
+
:param lower: default: `0.0`; the lower bound of the transformation range.
|
|
41
|
+
:param upper: default: `2.0`; the upper bound of the transformation range.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, cols=None, unit='day', lower=0., upper=2.):
|
|
45
|
+
if unit not in ['day', 'hour', 'minute', 'second']:
|
|
46
|
+
raise ValueError("`unit` must be either 'day', 'hour', 'minute', or 'second'.")
|
|
47
|
+
self.cols = cols
|
|
48
|
+
self.unit = unit
|
|
49
|
+
self.lower = lower
|
|
50
|
+
self.upper = upper
|
|
51
|
+
self.coefs = dict()
|
|
52
|
+
self.scale = 1.
|
|
53
|
+
self.scale = numpy.timedelta64(1, 's')
|
|
54
|
+
if self.unit == 'minute':
|
|
55
|
+
self.scale = 60.
|
|
56
|
+
self.scale = numpy.timedelta64(1, 'm')
|
|
57
|
+
elif self.unit == 'hour':
|
|
58
|
+
self.scale = 60 * 60.
|
|
59
|
+
self.scale = numpy.timedelta64(1, 'h')
|
|
60
|
+
elif self.unit == 'day':
|
|
61
|
+
self.scale = 60 * 60 * 24.
|
|
62
|
+
self.scale = numpy.timedelta64(1, 'D')
|
|
63
|
+
|
|
64
|
+
def fit(self, X):
|
|
65
|
+
"""
|
|
66
|
+
Fits and calculates the transformation parameters for date range transformation.
|
|
67
|
+
|
|
68
|
+
:param X: Input data
|
|
69
|
+
:return: `self`
|
|
70
|
+
"""
|
|
71
|
+
if self.cols is None:
|
|
72
|
+
return self
|
|
73
|
+
n_length = self.upper - self.lower
|
|
74
|
+
for clmn in self.cols:
|
|
75
|
+
t_vec = X[clmn].dropna().values
|
|
76
|
+
min_date, max_date = t_vec.min(), t_vec.max()
|
|
77
|
+
# t_length = (max_date - min_date).seconds / self.scale
|
|
78
|
+
t_length = (max_date - min_date) / self.scale
|
|
79
|
+
trans_coef = n_length / t_length
|
|
80
|
+
intercept = self.lower
|
|
81
|
+
self.coefs[clmn] = (trans_coef, intercept, min_date)
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def transform(self, X):
|
|
85
|
+
"""
|
|
86
|
+
Apply the calculated parameters to transform the datetime columns.
|
|
87
|
+
|
|
88
|
+
:param X: the input data
|
|
89
|
+
:return: transformed data
|
|
90
|
+
"""
|
|
91
|
+
if self.cols is None:
|
|
92
|
+
return X
|
|
93
|
+
TX = X
|
|
94
|
+
for clmn in self.cols:
|
|
95
|
+
vect = numpy.array(
|
|
96
|
+
[self.coefs[clmn][1] + self.coefs[clmn][0] * (_ - self.coefs[clmn][2]) / self.scale for _ in
|
|
97
|
+
TX[clmn].values])
|
|
98
|
+
TX[clmn] = vect
|
|
99
|
+
return TX
|
|
100
|
+
|
|
101
|
+
def fit_transform(self, X, y=None, **fit_params):
|
|
102
|
+
"""
|
|
103
|
+
Fits and transforms the input data.
|
|
104
|
+
|
|
105
|
+
:param X: input data;
|
|
106
|
+
:param y: target field; not required, but included to comply with scikit-learn's signature
|
|
107
|
+
:param fit_params: included to comply with scikit-learn's signature
|
|
108
|
+
:return: transformed DataFrame
|
|
109
|
+
"""
|
|
110
|
+
self.fit(X)
|
|
111
|
+
return self.transform(X)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class DataPreprocess(object):
|
|
115
|
+
"""
|
|
116
|
+
This class performs a few reprocessing task on the input DataFrame to provide a ML ready dataset.
|
|
117
|
+
|
|
118
|
+
:param df: the input raw DataFrame
|
|
119
|
+
:param bin_threshold: float between 0. and 1.; when the proportion of unique values to the total number of entities
|
|
120
|
+
falls below this number fo two valued columns, the type is deduced to be binary
|
|
121
|
+
:param cat_threshold: when the proportion of unique values to the total number of entities
|
|
122
|
+
falls below this number fo discrete columns, the type is deduced to be categorical or ordinal
|
|
123
|
+
:param force_threshold: boolean, indicate whether to force the deduction based on the provided thresholds or not
|
|
124
|
+
:param imputer: the imputer object; default `IterativeImputer` for missing values imputation
|
|
125
|
+
:param force_impute: boolean; default `True` to indicate the desire for missing value imputation
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __init__(self, df, bin_threshold=.1, cat_threshold=.1, force_threshold=False, imputer=None, force_impute=True,
|
|
129
|
+
text_processor=None):
|
|
130
|
+
self.df = copy(df)
|
|
131
|
+
self.bin_threshold = bin_threshold
|
|
132
|
+
self.cat_threshold = cat_threshold
|
|
133
|
+
self.force_threshold = force_threshold
|
|
134
|
+
self.columns = list(df.columns)
|
|
135
|
+
self.values = dict()
|
|
136
|
+
self.uniques = dict()
|
|
137
|
+
self.types = dict()
|
|
138
|
+
self.transforms = dict()
|
|
139
|
+
self.mapping = dict()
|
|
140
|
+
self.deduced_types = dict(float64=[], int64=[], datetime64=[], other=[], text=[],
|
|
141
|
+
binary=[], categorical=[], label=[], obsolete=[])
|
|
142
|
+
self.pivot_types = dict()
|
|
143
|
+
self.processed_frame = dict()
|
|
144
|
+
self.transformed_df = None
|
|
145
|
+
self.steps = []
|
|
146
|
+
for clmn in self.columns:
|
|
147
|
+
self.values[clmn] = self.df[clmn].dropna().values
|
|
148
|
+
self.uniques[clmn] = self.df[clmn].dropna().unique()
|
|
149
|
+
self.types[clmn] = self.df[clmn].dtypes.name
|
|
150
|
+
self.types_deduced = False
|
|
151
|
+
self.encoded = False
|
|
152
|
+
self.force_impute = force_impute
|
|
153
|
+
if imputer is None:
|
|
154
|
+
self.imputer = IterativeImputer()
|
|
155
|
+
else:
|
|
156
|
+
self.imputer = imputer
|
|
157
|
+
if text_processor is None:
|
|
158
|
+
self.txt_prcsr = TfidfVectorizer(sublinear_tf=True, max_features=1600, min_df=5, norm='l2',
|
|
159
|
+
ngram_range=(1, 3), stop_words='english')
|
|
160
|
+
else:
|
|
161
|
+
self.txt_prcsr = text_processor
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def numeric(val, typ='float64'):
|
|
165
|
+
trans_val = re.sub(r"[^\d\.]", "", str(val))
|
|
166
|
+
if typ == 'float64':
|
|
167
|
+
return float(trans_val)
|
|
168
|
+
elif typ == 'int64':
|
|
169
|
+
return int(trans_val)
|
|
170
|
+
|
|
171
|
+
def is_float(self, clmn):
|
|
172
|
+
"""
|
|
173
|
+
Determines if the type of the data for the column `clmn` is float or not
|
|
174
|
+
|
|
175
|
+
:param clmn: the name of the column
|
|
176
|
+
:return: boolean
|
|
177
|
+
"""
|
|
178
|
+
if self.types[clmn] == 'float64':
|
|
179
|
+
return True
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
def is_int(self, clmn):
|
|
183
|
+
"""
|
|
184
|
+
Determines if the type of the data for the column `clmn` is integer or not
|
|
185
|
+
|
|
186
|
+
:param clmn: the name of the column
|
|
187
|
+
:return: boolean
|
|
188
|
+
"""
|
|
189
|
+
if self.types[clmn] == 'int64':
|
|
190
|
+
return True
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
def is_object(self, clmn):
|
|
194
|
+
"""
|
|
195
|
+
Determines if the type of the data for the column `clmn` is object (e.g., string, blob, etc.) or not
|
|
196
|
+
|
|
197
|
+
:param clmn: the name of the column
|
|
198
|
+
:return: boolean
|
|
199
|
+
"""
|
|
200
|
+
if self.types[clmn] == 'object':
|
|
201
|
+
return True
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
def is_datetime(self, clmn):
|
|
205
|
+
"""
|
|
206
|
+
Determines if the type of the data for the column `clmn` is datetime or not
|
|
207
|
+
|
|
208
|
+
:param clmn: the name of the column
|
|
209
|
+
:return: boolean
|
|
210
|
+
"""
|
|
211
|
+
if 'datetime64' in self.types[clmn]:
|
|
212
|
+
return True
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
def is_text(self, clmn):
|
|
216
|
+
"""
|
|
217
|
+
Determine if the type of the data for the column `clmn` is string/text or not. A string column is of type
|
|
218
|
+
`pandas.StringDtype()`.
|
|
219
|
+
|
|
220
|
+
:param clmn: the name of the column
|
|
221
|
+
:return: boolean
|
|
222
|
+
"""
|
|
223
|
+
if 'string' in self.types[clmn]:
|
|
224
|
+
self.df[clmn].fillna("", inplace=True)
|
|
225
|
+
return True
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
def is_obsolete(self, clmn):
|
|
229
|
+
"""
|
|
230
|
+
Determines if the column is worth to be included in the analysis or not
|
|
231
|
+
|
|
232
|
+
:param clmn: the name of the column
|
|
233
|
+
:return: boolean; `True` if includes more than 1 value, `False` otherwise
|
|
234
|
+
"""
|
|
235
|
+
if len(self.uniques[clmn]) < 2:
|
|
236
|
+
return True
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
def is_bin(self, clmn):
|
|
240
|
+
"""
|
|
241
|
+
Determines if the type of the data for the column `clmn` is binary or not
|
|
242
|
+
|
|
243
|
+
:param clmn: the name of the column
|
|
244
|
+
:return: boolean
|
|
245
|
+
"""
|
|
246
|
+
ratio = len(self.uniques[clmn]) / len(self.values[clmn])
|
|
247
|
+
if self.force_threshold and (len(self.uniques[clmn]) == 2) and (ratio <= self.bin_threshold):
|
|
248
|
+
return True
|
|
249
|
+
if not self.force_threshold and (len(self.uniques[clmn]) == 2):
|
|
250
|
+
return True
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
def is_categorical(self, clmn):
|
|
254
|
+
"""
|
|
255
|
+
Determines if the type of the data for the column `clmn` is categorical or not
|
|
256
|
+
|
|
257
|
+
:param clmn: the name of the column
|
|
258
|
+
:return: boolean
|
|
259
|
+
"""
|
|
260
|
+
ratio = len(self.uniques[clmn]) / len(self.values[clmn])
|
|
261
|
+
if self.force_threshold and (ratio <= self.cat_threshold):
|
|
262
|
+
return True
|
|
263
|
+
elif (ratio <= self.cat_threshold) and (self.types[clmn] == 'object'):
|
|
264
|
+
return True
|
|
265
|
+
else:
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
def is_label(self, clmn):
|
|
269
|
+
"""
|
|
270
|
+
Determines if the type of the data for the column `clmn` is label or not
|
|
271
|
+
|
|
272
|
+
:param clmn: the name of the column
|
|
273
|
+
:return: boolean
|
|
274
|
+
"""
|
|
275
|
+
if (self.types[clmn] == 'object') and not self.is_categorical(clmn):
|
|
276
|
+
return True
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
def set_type(self, clmn, typ):
|
|
280
|
+
if typ not in self.deduced_types:
|
|
281
|
+
raise "Unknown type."
|
|
282
|
+
self.deduced_types[typ].append(clmn)
|
|
283
|
+
self.pivot_types[clmn] = typ
|
|
284
|
+
|
|
285
|
+
def deduce_types(self):
|
|
286
|
+
"""
|
|
287
|
+
Deduces the type of all columns.
|
|
288
|
+
|
|
289
|
+
:return: `None`
|
|
290
|
+
"""
|
|
291
|
+
for clmn in self.columns:
|
|
292
|
+
if clmn in self.pivot_types:
|
|
293
|
+
continue
|
|
294
|
+
if self.is_label(clmn):
|
|
295
|
+
self.deduced_types['label'].append(clmn)
|
|
296
|
+
self.pivot_types[clmn] = 'label'
|
|
297
|
+
elif self.is_text(clmn):
|
|
298
|
+
self.deduced_types['text'].append(clmn)
|
|
299
|
+
self.pivot_types[clmn] = 'text'
|
|
300
|
+
elif self.is_bin(clmn):
|
|
301
|
+
self.deduced_types['binary'].append(clmn)
|
|
302
|
+
self.pivot_types[clmn] = 'binary'
|
|
303
|
+
elif self.is_categorical(clmn):
|
|
304
|
+
self.deduced_types['categorical'].append(clmn)
|
|
305
|
+
self.pivot_types[clmn] = 'categorical'
|
|
306
|
+
elif self.is_datetime(clmn):
|
|
307
|
+
self.deduced_types['datetime64'].append(clmn)
|
|
308
|
+
self.pivot_types[clmn] = 'datetime64'
|
|
309
|
+
elif self.is_object(clmn):
|
|
310
|
+
self.deduced_types['other'].append(clmn)
|
|
311
|
+
self.pivot_types[clmn] = 'other'
|
|
312
|
+
elif self.is_int(clmn):
|
|
313
|
+
self.deduced_types['int64'].append(clmn)
|
|
314
|
+
self.pivot_types[clmn] = 'int64'
|
|
315
|
+
elif self.is_float(clmn):
|
|
316
|
+
self.deduced_types['float64'].append(clmn)
|
|
317
|
+
self.pivot_types[clmn] = 'float64'
|
|
318
|
+
elif self.is_obsolete(clmn):
|
|
319
|
+
self.deduced_types['obsolete'].append(clmn)
|
|
320
|
+
self.pivot_types[clmn] = 'obsolete'
|
|
321
|
+
self.types_deduced = True
|
|
322
|
+
|
|
323
|
+
def transform_label_bin(self, clmn):
|
|
324
|
+
"""
|
|
325
|
+
Transforms the determined label and binary columns.
|
|
326
|
+
|
|
327
|
+
:param clmn: the column's name
|
|
328
|
+
:return: `None`
|
|
329
|
+
"""
|
|
330
|
+
lbls = numpy.sort(self.uniques[clmn])
|
|
331
|
+
self.transforms[clmn] = {_: lbls[_] for _ in range(lbls.shape[0])}
|
|
332
|
+
self.mapping[clmn] = {lbls[_]: _ for _ in range(lbls.shape[0])}
|
|
333
|
+
|
|
334
|
+
def encode(self):
|
|
335
|
+
"""
|
|
336
|
+
Transforms the whole input DataFrame based on the deduced types and does missing data imputation if desired.
|
|
337
|
+
The resulted DataFrame will be stored in `transformed_df`
|
|
338
|
+
|
|
339
|
+
:return: Transformed and encoded dataframe
|
|
340
|
+
"""
|
|
341
|
+
if not self.types_deduced:
|
|
342
|
+
self.deduce_types()
|
|
343
|
+
self.steps = []
|
|
344
|
+
txt_clmn_df = None
|
|
345
|
+
org_df = copy(self.df)
|
|
346
|
+
for clmn in self.deduced_types['int64']:
|
|
347
|
+
values = org_df[clmn].astype(str).str.replace(r"[^\d\.]", "", regex=True)
|
|
348
|
+
org_df[clmn] = pandas.to_numeric(values, errors='coerce')
|
|
349
|
+
for clmn in self.deduced_types['float64']:
|
|
350
|
+
values = org_df[clmn].astype(str).str.replace(r"[^\d\.]", "", regex=True)
|
|
351
|
+
org_df[clmn] = pandas.to_numeric(values, errors='coerce')
|
|
352
|
+
if self.deduced_types['text']:
|
|
353
|
+
txt_clmn_df = self.df[self.deduced_types['text']]
|
|
354
|
+
org_df.drop(self.deduced_types['text'], axis=1, inplace=True)
|
|
355
|
+
ordinal_columns = self.deduced_types['binary'] + self.deduced_types['label']
|
|
356
|
+
for clmn in ordinal_columns:
|
|
357
|
+
self.transform_label_bin(clmn)
|
|
358
|
+
|
|
359
|
+
working_df = org_df.copy()
|
|
360
|
+
for clmn in ordinal_columns:
|
|
361
|
+
working_df[clmn] = working_df[clmn].map(self.mapping[clmn]).astype(float)
|
|
362
|
+
|
|
363
|
+
for clmn in self.deduced_types['categorical']:
|
|
364
|
+
dummy = pandas.get_dummies(
|
|
365
|
+
working_df[clmn].astype(object).fillna("__MISSING__"),
|
|
366
|
+
prefix=str(clmn),
|
|
367
|
+
prefix_sep="_",
|
|
368
|
+
dummy_na=False,
|
|
369
|
+
)
|
|
370
|
+
working_df = pandas.concat([working_df.drop(columns=[clmn]), dummy], axis=1)
|
|
371
|
+
|
|
372
|
+
for clmn in self.deduced_types['datetime64']:
|
|
373
|
+
working_df[clmn] = pandas.to_datetime(working_df[clmn], errors='coerce')
|
|
374
|
+
working_df[clmn] = (
|
|
375
|
+
(working_df[clmn] - pandas.Timestamp("1970-01-01"))
|
|
376
|
+
.dt.total_seconds()
|
|
377
|
+
.astype(float)
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
dtn = DateTime2Num(cols=self.deduced_types['datetime64'])
|
|
381
|
+
self.steps.append(('Date2Num', dtn))
|
|
382
|
+
self.steps.append(('Impute', self.imputer))
|
|
383
|
+
trans = Pipeline(self.steps)
|
|
384
|
+
|
|
385
|
+
# Keep the numeric matrix consistent with the original API while avoiding the deprecated category_encoders path.
|
|
386
|
+
encoded_frame = pandas.DataFrame(
|
|
387
|
+
trans.fit_transform(working_df),
|
|
388
|
+
columns=working_df.columns,
|
|
389
|
+
)
|
|
390
|
+
self.transformed_df = encoded_frame.copy()
|
|
391
|
+
|
|
392
|
+
rounded_columns = set(ordinal_columns).union(self.deduced_types['categorical'])
|
|
393
|
+
for clmn in self.transformed_df.columns.intersection(rounded_columns):
|
|
394
|
+
rounded = pandas.to_numeric(self.transformed_df[clmn], errors='coerce').round(0)
|
|
395
|
+
if self.force_impute:
|
|
396
|
+
self.transformed_df[clmn] = rounded.astype(int)
|
|
397
|
+
else:
|
|
398
|
+
self.transformed_df[clmn] = rounded.astype(float)
|
|
399
|
+
|
|
400
|
+
for clmn in self.deduced_types['text']:
|
|
401
|
+
txt_array = txt_clmn_df[clmn].values
|
|
402
|
+
processed_txt = self.txt_prcsr.fit_transform(txt_array)
|
|
403
|
+
length = processed_txt.shape[1]
|
|
404
|
+
clmn_names = ["%s_%d" % (clmn, _) for _ in range(length)]
|
|
405
|
+
self.transformed_df = pandas.concat(
|
|
406
|
+
[self.transformed_df, pandas.DataFrame(processed_txt.toarray(), columns=clmn_names)], axis=1)
|
|
407
|
+
|
|
408
|
+
self.encoded = True
|
|
409
|
+
return self.transformed_df
|