-
+
@@ -276,34 +284,64 @@
if (s > 90) { color = '#00f700' }
return color
}
- const get_score = function() {
- let url = g1.url.parse(window.location)
- $.ajax({
- url: url + '?_action=score&_metric=' + encodeURIComponent($('#metric').val()),
- method: 'POST',
- success: function(resp) {
- let score = Number.parseFloat(JSON.parse(resp).score * 100).toPrecision(4)
- score = Number.parseFloat(score)
- $('.donut-segment').attr('stroke-dasharray', `${score} ${100 - score}`)
- $('tspan').html(`${score}%`)
- $('.donut-segment').attr('stroke', get_score_color(score))
- $('#resultcnt').show()
- let inputcols = fh_meta.meta.columns.filter((col) => !$('#exclude').val().concat($('#targetcol').val()).includes(col.name))
- $('#predicttabtemplate').template({COLS: inputcols.map(e => ({name: e.name, type: e.type})), row: fh_meta.formdata[0]})
- }
- })
+ const get_score = function(resp) {
+ if ($('#modelchoice').val() != 'TopCause') {
+ $('#tcresult').hide()
+ let url = g1.url.parse(window.location)
+ $.ajax({
+ url: url + '?_action=score&_metric=' + encodeURIComponent($('#metric').val()),
+ method: 'POST',
+ success: function(resp) {
+ let score = (resp.score * 100).toPrecision(4)
+ score = Number.parseFloat(score)
+ $('.donut-segment').attr('stroke-dasharray', `${score} ${100 - score}`)
+ $('tspan').html(`${score}%`)
+ $('.donut-segment').attr('stroke', get_score_color(score))
+ $('#resultcnt').show()
+ let inputcols = fh_meta.meta.columns.filter((col) => !$('#exclude').val().concat($('#targetcol').val()).includes(col.name))
+ $('#predicttabtemplate')
+ .on('template', modifyModelChoice)
+ .template({COLS: inputcols.map(e => ({name: e.name, type: e.type})), row: fh_meta.formdata[0]})
+ }
+ })
+ } else {
+ $('#tcresult').show()
+ $('#tcresult').formhandler({data: resp.result_})
+ }
}
const post_train = function (target_col) {
$('#resultcnt').hide()
let url = g1.url.parse(window.location)
url.hash = ''
+ url = url + '?_action=retrain&target_col=' + encodeURIComponent(target_col)
+ if ($('#modelchoice').val() != 'TopCause') {
+ url = url + '&_metric=' + encodeURIComponent($('#metric').val())
+ }
$.ajax({
- url: url + '?_action=retrain&target_col=' + encodeURIComponent(target_col) + '&_metric=' + encodeURIComponent($('#metric').val()),
+ url: url,
method: 'POST',
success: get_score
})
}
+ const modifyModelChoice = function() {
+ $('.tcdisable').attr('disabled', $('#modelchoice').val() == 'TopCause')
+ $('.selectpicker').selectpicker('refresh')
+ if ($('#modelchoice').val() == 'TopCause') {
+ let url = g1.url.parse(window.location)
+ url.hash = ''
+ $.getJSON(url + '?_params').done(function(result) {
+ $('#tcresult').show()
+ $('#tcresult').formhandler({data: result.attrs.result_})
+ })
+ } else {
+ $('#tcresult').hide()
+ }
+ }
$(document).ready(function() {
+ // If TopCause is selected, enable only some inputs
+ $('#modelchoice').change(modifyModelChoice)
+ $('#modelchoice').trigger('change')
+
$('#downloadbtn').hide()
$('#resultcnt').hide()
let url = g1.url.parse(window.location)
@@ -312,7 +350,9 @@
$('.formhandler').on('load', function(obj) {
fh_meta = obj
let inputcols = obj.meta.columns.filter((col) => !$('#exclude').val().concat($('#targetcol').val()).includes(col.name))
- $('#predicttabtemplate').template({COLS: inputcols.map(e => ({name: e.name, type: e.type})), row: obj.formdata[0]})
+ $('#predicttabtemplate')
+ .on('template', modifyModelChoice)
+ .template({COLS: inputcols.map(e => ({name: e.name, type: e.type})), row: obj.formdata[0]})
}).formhandler({
pageSize: 5,
export: false
diff --git a/gramex/handlers/mlhandler.py b/gramex/handlers/mlhandler.py
index c7e4d3326..362e49753 100644
--- a/gramex/handlers/mlhandler.py
+++ b/gramex/handlers/mlhandler.py
@@ -11,6 +11,7 @@
from gramex.handlers import FormHandler
from gramex.http import NOT_FOUND, BAD_REQUEST
from gramex.install import _mkdir, safe_rmtree
+from gramex.ml import TopCause
from gramex import cache
import joblib
import pandas as pd
@@ -224,12 +225,19 @@ def _filterrows(cls, data, **kwargs):
@classmethod
def _assemble_pipeline(cls, data, force=False, mclass='', params=None):
+ if params is None:
+ params = {}
# If the model exists, return it
if op.exists(cls.model_path) and not force:
return joblib.load(cls.model_path)
+ model_kwargs = cls.config_store.load('model', {})
+ if not mclass:
+ mclass = model_kwargs.get('class', False)
+
+ # No pipeline for TopCause
# If preprocessing is not enabled, return the root model
- if not cls.get_opt('pipeline', True):
+ if mclass == 'TopCause' or not cls.get_opt('pipeline', True):
return search_modelclass(mclass)(**params)
# Else assemble the preprocessing pipeline
@@ -252,8 +260,6 @@ def _assemble_pipeline(cls, data, force=False, mclass='', params=None):
[('ohe', OneHotEncoder(sparse=False), categoricals),
('scaler', StandardScaler(), numericals)]
)
- model_kwargs = cls.config_store.load('model', {})
- mclass = model_kwargs.get('class', False)
if mclass:
model = search_modelclass(mclass)(**model_kwargs.get('params', {}))
cls.set_opt('params', model.get_params())
@@ -318,8 +324,9 @@ def get(self, *path_args, **path_kwargs):
}
try:
model = cache.open(self.model_path, joblib.load)
+ estimator = model[-1] if hasattr(model, '__getitem__') else model
attrs = {
- k: v for k, v in vars(model[-1]).items() if re.search(r'[^_]+_$', k)
+ k: v for k, v in vars(estimator).items() if re.search(r'[^_]+_$', k)
}
except FileNotFoundError:
attrs = {}
@@ -367,17 +374,19 @@ def _train(self, data=None):
data = self._filtercols(data)
data = self._filterrows(data)
self.model = self._assemble_pipeline(data, force=True)
- if not isinstance(self.model[-1], TransformerMixin):
+ estimator = self.model[-1] if hasattr(self.model, '__getitem__') else self.model
+ if not isinstance(estimator, (TransformerMixin, TopCause)):
target = data[target_col]
train = data[[c for c in data if c != target_col]]
_fit(self.model, train, target, self.model_path)
result = {'score': self.model.score(train, target)}
else:
- _fit(self.model, data, path=self.model_path)
+ target = data[target_col] if isinstance(estimator, TopCause) else None
+ _fit(self.model, data, target, self.model_path)
# Note: Fitted sklearn estimators store their parameters
# in attributes whose names end in an underscore. E.g. in the case of PCA,
# attributes are named `explained_variance_`. The `_train` action returns them.
- result = {k: v for k, v in vars(self.model[-1]).items() if re.search(r'[^_]+_$', k)}
+ result = {k: v for k, v in vars(estimator).items() if re.search(r'[^_]+_$', k)}
return result
def _retrain(self):
diff --git a/gramex/topcause.py b/gramex/topcause.py
index 0944808e4..f0007f88d 100644
--- a/gramex/topcause.py
+++ b/gramex/topcause.py
@@ -176,6 +176,6 @@ def fit(self, X, y, sample_weight=None): # noqa - capital X is a sklearn conv
results = pd.DataFrame(results).T
results.loc[results['p'] > self.max_p, ('value', 'gain')] = np.nan
- self.result_ = results.sort_values('gain', ascending=False)
+ self.result_ = results.sort_values('gain', ascending=False).reset_index()
return self