SKSurrogate 0.1.0__tar.gz
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-0.1.0/PKG-INFO +139 -0
- sksurrogate-0.1.0/SKSurrogate/DataProcess.py +409 -0
- sksurrogate-0.1.0/SKSurrogate/NpyProximation.py +457 -0
- sksurrogate-0.1.0/SKSurrogate/__init__.py +110 -0
- sksurrogate-0.1.0/SKSurrogate/aml.py +1156 -0
- sksurrogate-0.1.0/SKSurrogate/ci.py +93 -0
- sksurrogate-0.1.0/SKSurrogate/deployment.py +31 -0
- sksurrogate-0.1.0/SKSurrogate/eoa.py +381 -0
- sksurrogate-0.1.0/SKSurrogate/execution.py +416 -0
- sksurrogate-0.1.0/SKSurrogate/inference.py +224 -0
- sksurrogate-0.1.0/SKSurrogate/mltrace.py +2138 -0
- sksurrogate-0.1.0/SKSurrogate/modelbundle.py +600 -0
- sksurrogate-0.1.0/SKSurrogate/monitoring.py +360 -0
- sksurrogate-0.1.0/SKSurrogate/retraining.py +49 -0
- sksurrogate-0.1.0/SKSurrogate/sensapprx.py +250 -0
- sksurrogate-0.1.0/SKSurrogate/structsearch.py +1411 -0
- sksurrogate-0.1.0/SKSurrogate/synthdat.py +503 -0
- sksurrogate-0.1.0/SKSurrogate.egg-info/PKG-INFO +139 -0
- sksurrogate-0.1.0/SKSurrogate.egg-info/SOURCES.txt +31 -0
- sksurrogate-0.1.0/SKSurrogate.egg-info/dependency_links.txt +1 -0
- sksurrogate-0.1.0/SKSurrogate.egg-info/entry_points.txt +2 -0
- sksurrogate-0.1.0/SKSurrogate.egg-info/requires.txt +13 -0
- sksurrogate-0.1.0/SKSurrogate.egg-info/top_level.txt +1 -0
- sksurrogate-0.1.0/setup.cfg +4 -0
- sksurrogate-0.1.0/setup.py +48 -0
- sksurrogate-0.1.0/tests/test_optimized_paths.py +1400 -0
- sksurrogate-0.1.0/tests/test_phase1_endpoints.py +323 -0
- sksurrogate-0.1.0/tests/test_phase2_endpoints.py +373 -0
- sksurrogate-0.1.0/tests/test_phase3_endpoints.py +349 -0
- sksurrogate-0.1.0/tests/test_phase4_endpoints.py +436 -0
- sksurrogate-0.1.0/tests/test_phase5_endpoints.py +167 -0
- sksurrogate-0.1.0/tests/test_phase6_endpoints.py +216 -0
- sksurrogate-0.1.0/tests/test_phase7_endpoints.py +133 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: SKSurrogate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A python package that implements or wraps a few machine learning tools Scikit-Learn style.
|
|
5
|
+
Home-page: https://github.com/mghasemi/sksurrogate.git
|
|
6
|
+
Author: Mehdi Ghasemi
|
|
7
|
+
Author-email: mehdi.ghasemi@gmail.com
|
|
8
|
+
License: MIT License
|
|
9
|
+
Keywords: Auto Machine Learning,AutoML,Optimization,Surrogate Optimization,Numerical,Machine Learning,Regression,Random Search
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: scipy
|
|
12
|
+
Requires-Dist: pandas
|
|
13
|
+
Requires-Dist: matplotlib
|
|
14
|
+
Requires-Dist: scikit-learn
|
|
15
|
+
Requires-Dist: SALib
|
|
16
|
+
Requires-Dist: imbalanced-learn
|
|
17
|
+
Requires-Dist: joblib
|
|
18
|
+
Requires-Dist: eli5
|
|
19
|
+
Requires-Dist: skrebate
|
|
20
|
+
Requires-Dist: tqdm
|
|
21
|
+
Requires-Dist: peewee
|
|
22
|
+
Requires-Dist: category_encoders
|
|
23
|
+
Dynamic: author
|
|
24
|
+
Dynamic: author-email
|
|
25
|
+
Dynamic: description
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: keywords
|
|
28
|
+
Dynamic: license
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
|Quality| |Doc| |Licence|
|
|
33
|
+
|
|
34
|
+
|Logo|
|
|
35
|
+
|
|
36
|
+
SKSurrogate
|
|
37
|
+
==================
|
|
38
|
+
``SKSurrogate`` is a suite of tools which implements surrogate optimization for expensive
|
|
39
|
+
functions based on ``scikit-learn``. The main purpose of ``SKSurrogate`` is to facilitate
|
|
40
|
+
hyperparameter optimization for machine learning models and optimized pipeline design (AutoML).
|
|
41
|
+
|
|
42
|
+
The version of the surrogate optimization implemented here heavily relies on regressors.
|
|
43
|
+
A custom regressor based on Hilbert Space techniques is implemented, but all ``scikit-learn``
|
|
44
|
+
regressors are accepted for optimization.
|
|
45
|
+
|
|
46
|
+
Finding an optimized pipeline -based on a given list of transformers and estimators is
|
|
47
|
+
a time-consuming task. A version of evolutionary optimization has been implemented to reduce
|
|
48
|
+
its time in lieu of global optimality.
|
|
49
|
+
|
|
50
|
+
``SKSurrogate`` is positioned as a scientific/research library: the numerical methods are
|
|
51
|
+
validated against benchmarks, but the API surface evolves with ongoing research work.
|
|
52
|
+
|
|
53
|
+
Installation
|
|
54
|
+
------------
|
|
55
|
+
From PyPI::
|
|
56
|
+
|
|
57
|
+
pip install sksurrogate
|
|
58
|
+
|
|
59
|
+
or from source (development)::
|
|
60
|
+
|
|
61
|
+
pip install git+https://github.com/mghasemi/sksurrogate.git
|
|
62
|
+
|
|
63
|
+
Data identity and schema validation
|
|
64
|
+
-----------------------------------
|
|
65
|
+
``SKSurrogate`` now records a deterministic dataset fingerprint and a JSON-serializable schema
|
|
66
|
+
for each tracked dataset. Each call to ``mltrack.RegisterData(...)`` stores dataset metadata,
|
|
67
|
+
including the target column, row count, column names, dtypes, nullability, and categorical values.
|
|
68
|
+
This allows a run to be identified and later compared against a different dataset or a refreshed
|
|
69
|
+
training sample.
|
|
70
|
+
|
|
71
|
+
The tracker also exposes ``validate_data(...)`` and ``validate_prediction_data(...)`` to compare
|
|
72
|
+
incoming data against the stored schema before training or inference. By default the checks are
|
|
73
|
+
strict: missing columns, reordered columns, dtype mismatches, and unknown categorical values fail
|
|
74
|
+
with actionable errors. For compatibility workflows, these checks can be relaxed explicitly with
|
|
75
|
+
``missing_columns='ignore'`` or ``unknown_categories='ignore'``.
|
|
76
|
+
|
|
77
|
+
The intended workflow is:
|
|
78
|
+
|
|
79
|
+
1. Register the training dataset with ``mltrack.RegisterData(frame, target)``.
|
|
80
|
+
2. Validate training data before fitting with ``tracker.validate_data(frame, target=...)``.
|
|
81
|
+
3. Validate prediction inputs with ``tracker.validate_prediction_data(frame)`` before calling
|
|
82
|
+
``predict`` on a model or pipeline.
|
|
83
|
+
4. Use the stored ``dataset_fingerprint`` and ``dataset_schema`` metadata for reproducibility,
|
|
84
|
+
drift checks, and auditability.
|
|
85
|
+
|
|
86
|
+
Dependencies
|
|
87
|
+
----------------
|
|
88
|
+
``SKSurrogate`` heavily depends on `NumPy <http://www.numpy.org/>`_,
|
|
89
|
+
`scipy <https://www.scipy.org/>`_, and `scikit-learn <https://scikit-learn.org/stable/>`_,
|
|
90
|
+
for its main functionalities. Other dependencies include `pandas <https://pandas.pydata.org/>`_,
|
|
91
|
+
`matplotlib <https://matplotlib.org/>`_, `SALib <https://github.com/SALib/SALib>`_,
|
|
92
|
+
`peewee <https://github.com/coleifer/peewee>`_, and
|
|
93
|
+
`tqdm <https://github.com/tqdm/tqdm>`_.
|
|
94
|
+
|
|
95
|
+
Documentation
|
|
96
|
+
----------------
|
|
97
|
+
The documentation is produced by `Sphinx <http://www.sphinx-doc.org/en/stable/>`_ and is intended to cover code usage
|
|
98
|
+
as well as a bit of theory to explain each method briefly.
|
|
99
|
+
For more details refer to the documentation at `sksurrogate.rtfd.io <http://sksurrogate.readthedocs.io/>`_.
|
|
100
|
+
The optional web UI and control-plane API are documented in the
|
|
101
|
+
`Web UI and Control-Plane API guide <https://sksurrogate.readthedocs.io/en/latest/controlplane.html>`_.
|
|
102
|
+
The `step-by-step UI manual <https://sksurrogate.readthedocs.io/en/latest/ui-manual.html>`_
|
|
103
|
+
includes screenshots and example CSV files.
|
|
104
|
+
|
|
105
|
+
License
|
|
106
|
+
================
|
|
107
|
+
This code is distributed under `MIT license <https://en.wikipedia.org/wiki/MIT_License>`_:
|
|
108
|
+
|
|
109
|
+
MIT License
|
|
110
|
+
----------------
|
|
111
|
+
|
|
112
|
+
Copyright (c) 2019 Mehdi Ghasemi
|
|
113
|
+
|
|
114
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
115
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
116
|
+
in the Software without restriction, including without limitation the rights
|
|
117
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
118
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
119
|
+
furnished to do so, subject to the following conditions:
|
|
120
|
+
|
|
121
|
+
The above copyright notice and this permission notice shall be included in all
|
|
122
|
+
copies or substantial portions of the Software.
|
|
123
|
+
|
|
124
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
125
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
126
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
127
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
128
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
129
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
130
|
+
SOFTWARE.
|
|
131
|
+
|
|
132
|
+
.. |Logo| image:: ./docs/images/funnel.png
|
|
133
|
+
.. |Quality| image:: https://api.codacy.com/project/badge/Grade/9a4d3e663cb04128a06d47efc85f23da
|
|
134
|
+
:target: https://www.codacy.com/app/mghasemi/sksurrogate?utm_source=github.com&utm_medium=referral&utm_content=mghasemi/sksurrogate&utm_campaign=Badge_Grade
|
|
135
|
+
.. |Doc| image:: https://readthedocs.org/projects/sksurrogate/badge/?version=latest
|
|
136
|
+
:target: https://sksurrogate.readthedocs.io/en/latest/?badge=latest
|
|
137
|
+
:alt: Documentation Status
|
|
138
|
+
.. |Licence| image:: https://img.shields.io/badge/license-MIT-blue.svg
|
|
139
|
+
:target: https://github.com/mghasemi/sksurrogate/blob/master/License.txt
|
|
@@ -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
|