diff --git a/Makefile b/Makefile index 170c05f..30a02a0 100644 --- a/Makefile +++ b/Makefile @@ -54,13 +54,19 @@ coverage: ## check code coverage quickly with the default Python coverage html $(BROWSER) htmlcov/index.html -docs: ## generate docs - pip install pdoc --ignore-installed - pdoc learningmachine/* --output-dir learningmachine-docs +docs: install ## generate docs + pip install black pdoc + black learningmachine/* --line-length=80 + find learningmachine/ -name "*.py" -exec autopep8 --max-line-length=80 --in-place {} + + pdoc -t docs learningmachine/* --output-dir learningmachine-docs + find . -name '__pycache__' -exec rm -fr {} + -servedocs: ## compile the docs watching for change - pip install pdoc --ignore-installed - pdoc learningmachine/* +servedocs: install ## compile the docs watching for change + pip install black pdoc + black learningmachine/* --line-length=80 + find learningmachine/ -name "*.py" -exec autopep8 --max-line-length=80 --in-place {} + + pdoc -t docs learningmachine/* + find . -name '__pycache__' -exec rm -fr {} + release: dist ## package and upload a release pip install twine @@ -76,10 +82,9 @@ install: clean ## install the package to the active Python's site-packages ##python3 -m black learningmachine --line-length 80 python3 -m pip install . -build-site: docs ## export mkdocs website to a folder - cd docs&&mkdocs build - cp -rf docs/site/* ../../Pro_Website/Techtonique.github.io/learningmachine - cd .. +build-site: docs ## export mkdocs website to a folder + cp -rf learningmachine-docs/* ../../Pro_Website/Techtonique.github.io/learningmachine + find . -name '__pycache__' -exec rm -fr {} + run-examples: ## run all examples with one command find examples -maxdepth 2 -name "*.py" -exec python3 {} \; diff --git a/README.md b/README.md index 80aa73a..86689ff 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # learningmachine -![PyPI](https://img.shields.io/pypi/v/learningmachine) [![PyPI - License](https://img.shields.io/pypi/l/learningmachine)](https://github.com/thierrymoudiki/learningmachine/blob/master/LICENSE) [![Downloads](https://pepy.tech/badge/learningmachine)](https://pepy.tech/project/learningmachine) +![PyPI](https://img.shields.io/pypi/v/learningmachine) [![PyPI - License](https://img.shields.io/pypi/l/learningmachine)](https://github.com/thierrymoudiki/learningmachine/blob/master/LICENSE) [![Downloads](https://pepy.tech/badge/learningmachine)](https://pepy.tech/project/learningmachine) [![Documentation](https://img.shields.io/badge/documentation-is_here-green)](https://techtonique.github.io/learningmachine/) Machine Learning with uncertainty quantification and interpretability. diff --git a/learningmachine/base.py b/learningmachine/base.py index eb12c90..b5d3ba0 100644 --- a/learningmachine/base.py +++ b/learningmachine/base.py @@ -1,4 +1,4 @@ -import pandas as pd +import pandas as pd import sklearn.metrics as skm import subprocess from functools import lru_cache @@ -32,9 +32,9 @@ def __init__( pi_method="kdesplitconformal", level=95, B=100, - nb_hidden = 0, - nodes_sim = "sobol", - activ = "relu", + nb_hidden=0, + nodes_sim="sobol", + activ="relu", params=None, seed=123, ): @@ -47,15 +47,23 @@ def __init__( self.method = method self.pi_method = pi_method self.level = level - self.B = B + self.B = B self.nb_hidden = nb_hidden - assert nodes_sim in ("sobol", "halton", "unif"), \ - "must have nodes_sim in ('sobol', 'halton', 'unif')" + assert nodes_sim in ( + "sobol", + "halton", + "unif", + ), "must have nodes_sim in ('sobol', 'halton', 'unif')" self.nodes_sim = "sobol" - assert activ in ("relu", "sigmoid", "tanh", - "leakyrelu", "elu", "linear"), \ - "must have activ in ('relu', 'sigmoid', 'tanh', 'leakyrelu', 'elu', 'linear')" - self.activ = activ + assert activ in ( + "relu", + "sigmoid", + "tanh", + "leakyrelu", + "elu", + "linear", + ), "must have activ in ('relu', 'sigmoid', 'tanh', 'leakyrelu', 'elu', 'linear')" + self.activ = activ self.params = params self.seed = seed self.obj = None @@ -63,8 +71,10 @@ def __init__( def load_learningmachine(self): # Install R packages - commands1_lm = 'base::system.file(package = "learningmachine")' # check "learningmachine" is installed - commands2_lm = 'base::system.file("learningmachine_r", package = "learningmachine")' # check "learningmachine" is installed locally + # check "learningmachine" is installed + commands1_lm = 'base::system.file(package = "learningmachine")' + # check "learningmachine" is installed locally + commands2_lm = 'base::system.file("learningmachine_r", package = "learningmachine")' exec_commands1_lm = subprocess.run( ["Rscript", "-e", commands1_lm], capture_output=True, text=True ) @@ -102,7 +112,9 @@ def load_learningmachine(self): ) except: # well, we tried try: - r("try(suppressWarnings(suppressMessages(library('learningmachine'))), silence=TRUE)") + r( + "try(suppressWarnings(suppressMessages(library('learningmachine'))), silence=TRUE)" + ) except: # well, we tried everything at this point r( "try(suppressWarnings(suppressMessages(library('learningmachine', lib.loc='learningmachine_r'))), silence=TRUE)" @@ -214,43 +226,57 @@ def score(self, X, y, scoring=None, **kwargs): return scoring_options[scoring](y, preds, **kwargs) - def summary(self, X, y, - class_index = None, - cl = None, - show_progress = True): - - if isinstance(X, pd.DataFrame): - X_r = r.matrix(FloatVector(X.values.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) + def summary( + self, + X, + y, + class_index=None, + cl=None, + type_ci="student", + show_progress=True, + ): + + if isinstance(X, pd.DataFrame): + X_r = r.matrix( + FloatVector(X.values.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) X_r.colnames = StrVector(self.column_names) else: - X_r = r.matrix(FloatVector(X.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) - + X_r = r.matrix( + FloatVector(X.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) + if cl is None: if self.type == "classification": - assert class_index is not None, "For classifiers, 'class_index' must be provided" + assert ( + class_index is not None + ), "For classifiers, 'class_index' must be provided" - return self.obj["summary"](X = X_r, - y = FactorVector(IntVector(y)), - class_index = int(class_index) + 1, - show_progress = show_progress + return self.obj["summary"]( + X=X_r, + y=FactorVector(IntVector(y)), + class_index=int(class_index) + 1, + type_ci=StrVector([type_ci]), + show_progress=show_progress, ) - + elif self.type == "regression": - - return self.obj["summary"](X = X_r, - y = FloatVector(y), - show_progress = show_progress + + return self.obj["summary"]( + X=X_r, + y=FloatVector(y), + type_ci=StrVector([type_ci]), + show_progress=show_progress, ) - - else: # cl is not None, parallel computing - pass + else: # cl is not None, parallel computing + pass diff --git a/learningmachine/classifier.py b/learningmachine/classifier.py index 3c2e238..2cfd0f9 100644 --- a/learningmachine/classifier.py +++ b/learningmachine/classifier.py @@ -1,5 +1,5 @@ import numpy as np -import pandas as pd +import pandas as pd import sklearn.metrics as skm from rpy2.robjects import r from rpy2.robjects.packages import importr @@ -7,7 +7,7 @@ FloatVector, IntVector, FactorVector, - StrVector + StrVector, ) from sklearn.base import ClassifierMixin from .base import Base @@ -30,20 +30,20 @@ def __init__( level=95, type_prediction_set="score", B=100, - nb_hidden = 0, - nodes_sim = "sobol", - activ = "relu", + nb_hidden=0, + nodes_sim="sobol", + activ="relu", seed=123, ): """ Initialize the model. """ super().__init__( - name = "Classifier", - type = "classification", + name="Classifier", + type="classification", method=method, pi_method=pi_method, - level=level, + level=level, B=B, nb_hidden=nb_hidden, nodes_sim=nodes_sim, @@ -51,26 +51,50 @@ def __init__( seed=seed, ) - self.type_prediction_set=type_prediction_set - - try: - r_obj_command = 'suppressWarnings(suppressMessages(library(learningmachine))); ' +\ - 'Classifier$new(method = ' + str(format_value(self.method)) + ', ' +\ - 'pi_method = ' + str(format_value(self.pi_method)) + ', ' +\ - 'level = ' + str(format_value(self.level)) + ', ' +\ - 'type_prediction_set = ' + str(format_value(self.type_prediction_set)) + ', ' +\ - 'B = ' + str(format_value(self.B)) + ', ' +\ - 'nb_hidden = ' + str(format_value(self.nb_hidden)) + ', ' +\ - 'nodes_sim = ' + str(format_value(self.nodes_sim)) + ', ' +\ - 'activ = ' + str(format_value(self.activ)) + ', ' +\ - 'seed = ' + str(format_value(self.seed)) + ')' + self.type_prediction_set = type_prediction_set + + try: + r_obj_command = ( + "suppressWarnings(suppressMessages(library(learningmachine))); " + + "Classifier$new(method = " + + str(format_value(self.method)) + + ", " + + "pi_method = " + + str(format_value(self.pi_method)) + + ", " + + "level = " + + str(format_value(self.level)) + + ", " + + "type_prediction_set = " + + str(format_value(self.type_prediction_set)) + + ", " + + "B = " + + str(format_value(self.B)) + + ", " + + "nb_hidden = " + + str(format_value(self.nb_hidden)) + + ", " + + "nodes_sim = " + + str(format_value(self.nodes_sim)) + + ", " + + "activ = " + + str(format_value(self.activ)) + + ", " + + "seed = " + + str(format_value(self.seed)) + + ")" + ) self.obj = r(r_obj_command) except Exception: try: - self.obj = r(f"suppressWarnings(suppressMessages(library(learningmachine))); Classifier$new(method = {format_value(self.method)}, pi_method = {format_value(self.pi_method)}, level = {format_value(self.level)}, type_prediction_set = {format_value(self.type_prediction_set)}, B = {format_value(self.B)}, nb_hidden = {format_value(self.nb_hidden)}, nodes_sim = {format_value(self.nodes_sim)}, activ = {format_value(self.activ)}, seed = {format_value(self.seed)})") + self.obj = r( + f"suppressWarnings(suppressMessages(library(learningmachine))); Classifier$new(method = {format_value(self.method)}, pi_method = {format_value(self.pi_method)}, level = {format_value(self.level)}, type_prediction_set = {format_value(self.type_prediction_set)}, B = {format_value(self.B)}, nb_hidden = {format_value(self.nb_hidden)}, nodes_sim = {format_value(self.nodes_sim)}, activ = {format_value(self.activ)}, seed = {format_value(self.seed)})" + ) except Exception: - self.obj = r(f"learningmachine::Classifier$new(method = {format_value(self.method)}, pi_method = {format_value(self.pi_method)}, level = {format_value(self.level)}, type_prediction_set = {format_value(self.type_prediction_set)}, B = {format_value(self.B)}, nb_hidden = {format_value(self.nb_hidden)}, nodes_sim = {format_value(self.nodes_sim)}, activ = {format_value(self.activ)}, seed = {format_value(self.seed)})") - + self.obj = r( + f"learningmachine::Classifier$new(method = {format_value(self.method)}, pi_method = {format_value(self.pi_method)}, level = {format_value(self.level)}, type_prediction_set = {format_value(self.type_prediction_set)}, B = {format_value(self.B)}, nb_hidden = {format_value(self.nb_hidden)}, nodes_sim = {format_value(self.nodes_sim)}, activ = {format_value(self.activ)}, seed = {format_value(self.seed)})" + ) + def fit(self, X, y, **kwargs): """ Fit the model according to the given training data. @@ -78,84 +102,87 @@ def fit(self, X, y, **kwargs): params_dict = {} for k, v in kwargs.items(): - if k == 'lambda_': - params_dict["lambda"] = v - elif '__' in k: - params_dict[k.replace('__', '.')] = v + if k == "lambda_": + params_dict["lambda"] = v + elif "__" in k: + params_dict[k.replace("__", ".")] = v else: - params_dict[k] = v + params_dict[k] = v if isinstance(X, pd.DataFrame): self.column_names = X.columns - X_r = r.matrix(FloatVector(X.values.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) + X_r = r.matrix( + FloatVector(X.values.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) X_r.colnames = StrVector(self.column_names) else: - X_r = r.matrix(FloatVector(X.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) - + X_r = r.matrix( + FloatVector(X.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) + if isinstance(y, pd.DataFrame) or isinstance(y, pd.Series): - y = y.values.ravel() + y = y.values.ravel() - self.obj["fit"](X_r, - FactorVector(IntVector(y)), - **params_dict) + self.obj["fit"](X_r, FactorVector(IntVector(y)), **params_dict) self.classes_ = np.unique(y) # /!\ do not remove return self - + def predict_proba(self, X): """ Predict using the model. - """ + """ - if isinstance(X, pd.DataFrame): - X_r = r.matrix(FloatVector(X.values.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) + if isinstance(X, pd.DataFrame): + X_r = r.matrix( + FloatVector(X.values.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) X_r.colnames = StrVector(self.column_names) - else: - X_r = r.matrix(FloatVector(X.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) - - if self.pi_method == "none": - if isinstance(X, pd.DataFrame): + else: + X_r = r.matrix( + FloatVector(X.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) + + if self.pi_method == "none": + if isinstance(X, pd.DataFrame): res = self.obj["predict_proba"](X_r) - return np.asarray(res) - if isinstance(X, pd.DataFrame): + return np.asarray(res) + if isinstance(X, pd.DataFrame): return r_list_to_namedtuple(self.obj["predict_proba"](X_r)) def predict(self, X): """ Predict using the model. - """ + """ - if isinstance(X, pd.DataFrame): - X_r = r.matrix(FloatVector(X.values.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) + if isinstance(X, pd.DataFrame): + X_r = r.matrix( + FloatVector(X.values.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) X_r.colnames = StrVector(self.column_names) - else: - X_r = r.matrix(FloatVector(X.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) - - if self.pi_method == "none": - return ( - np.asarray( - self.obj["predict"]( - X_r - ) - ) - 1 + else: + X_r = r.matrix( + FloatVector(X.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], ) - - return r_list_to_namedtuple(self.obj["predict"](X_r)) - + + if self.pi_method == "none": + return np.asarray(self.obj["predict"](X_r)) - 1 + + return r_list_to_namedtuple(self.obj["predict"](X_r)) diff --git a/learningmachine/regression.py b/learningmachine/regression.py index af734e2..10b6b3a 100644 --- a/learningmachine/regression.py +++ b/learningmachine/regression.py @@ -1,5 +1,5 @@ import numpy as np -import pandas as pd +import pandas as pd from rpy2.robjects import r from rpy2.robjects.packages import importr from rpy2.robjects.vectors import FloatVector, StrVector @@ -18,22 +18,22 @@ class Regressor(Base, RegressorMixin): """ def __init__( - self, + self, method="ranger", pi_method="none", level=95, B=100, - nb_hidden = 0, - nodes_sim = "sobol", - activ = "relu", + nb_hidden=0, + nodes_sim="sobol", + activ="relu", seed=123, ): """ Initialize the model. """ super().__init__( - name = "Regressor", - type = "regression", + name="Regressor", + type="regression", method=method, pi_method=pi_method, level=level, @@ -43,58 +43,81 @@ def __init__( activ=activ, seed=seed, ) - - try: - r_obj_command = 'suppressWarnings(suppressMessages(library(learningmachine))); ' +\ - 'Regressor$new(method = ' + str(format_value(self.method)) + ', ' +\ - 'pi_method = ' + str(format_value(self.pi_method)) + ', ' +\ - 'level = ' + str(format_value(self.level)) + ', ' +\ - 'B = ' + str(format_value(self.B)) + ', ' +\ - 'nb_hidden = ' + str(format_value(self.nb_hidden)) + ', ' +\ - 'nodes_sim = ' + str(format_value(self.nodes_sim)) + ', ' +\ - 'activ = ' + str(format_value(self.activ)) + ', ' +\ - 'seed = ' + str(format_value(self.seed)) + ')' + + try: + r_obj_command = ( + "suppressWarnings(suppressMessages(library(learningmachine))); " + + "Regressor$new(method = " + + str(format_value(self.method)) + + ", " + + "pi_method = " + + str(format_value(self.pi_method)) + + ", " + + "level = " + + str(format_value(self.level)) + + ", " + + "B = " + + str(format_value(self.B)) + + ", " + + "nb_hidden = " + + str(format_value(self.nb_hidden)) + + ", " + + "nodes_sim = " + + str(format_value(self.nodes_sim)) + + ", " + + "activ = " + + str(format_value(self.activ)) + + ", " + + "seed = " + + str(format_value(self.seed)) + + ")" + ) self.obj = r(r_obj_command) - except Exception: - try: - self.obj = r(f"suppressWarnings(suppressMessages(library(learningmachine))); Regressor$new(method = {format_value(self.method)}, pi_method = {format_value(self.pi_method)}, level = {format_value(self.level)}, B = {format_value(self.B)}, nb_hidden = {format_value(self.nb_hidden)}, nodes_sim = {format_value(self.nodes_sim)}, activ = {format_value(self.activ)}, seed = {format_value(self.seed)})") - except Exception: - self.obj = r(f"learningmachine::Regressor$new(method = {format_value(self.method)}, pi_method = {format_value(self.pi_method)}, level = {format_value(self.level)}, B = {format_value(self.B)}, nb_hidden = {format_value(self.nb_hidden)}, nodes_sim = {format_value(self.nodes_sim)}, activ = {format_value(self.activ)}, seed = {format_value(self.seed)})") - + except Exception: + try: + self.obj = r( + f"suppressWarnings(suppressMessages(library(learningmachine))); Regressor$new(method = {format_value(self.method)}, pi_method = {format_value(self.pi_method)}, level = {format_value(self.level)}, B = {format_value(self.B)}, nb_hidden = {format_value(self.nb_hidden)}, nodes_sim = {format_value(self.nodes_sim)}, activ = {format_value(self.activ)}, seed = {format_value(self.seed)})" + ) + except Exception: + self.obj = r( + f"learningmachine::Regressor$new(method = {format_value(self.method)}, pi_method = {format_value(self.pi_method)}, level = {format_value(self.level)}, B = {format_value(self.B)}, nb_hidden = {format_value(self.nb_hidden)}, nodes_sim = {format_value(self.nodes_sim)}, activ = {format_value(self.activ)}, seed = {format_value(self.seed)})" + ) + def fit(self, X, y, **kwargs): """ Fit the model according to the given training data. - """ + """ params_dict = {} for k, v in kwargs.items(): - if k == 'lambda_': - params_dict["lambda"] = v - elif '__' in k: - params_dict[k.replace('__', '.')] = v + if k == "lambda_": + params_dict["lambda"] = v + elif "__" in k: + params_dict[k.replace("__", ".")] = v else: - params_dict[k] = v + params_dict[k] = v if isinstance(X, pd.DataFrame): self.column_names = X.columns - X_r = r.matrix(FloatVector(X.values.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) + X_r = r.matrix( + FloatVector(X.values.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) X_r.colnames = StrVector(self.column_names) else: - X_r = r.matrix(FloatVector(X.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) + X_r = r.matrix( + FloatVector(X.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) if isinstance(y, pd.DataFrame) or isinstance(y, pd.Series): - y = y.values.ravel() - - self.obj["fit"](X_r, - FloatVector(y), - **params_dict - ) + y = y.values.ravel() + + self.obj["fit"](X_r, FloatVector(y), **params_dict) return self def predict(self, X): @@ -102,36 +125,33 @@ def predict(self, X): Predict using the model. """ - if isinstance(X, pd.DataFrame): - X_r = r.matrix(FloatVector(X.values.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) + if isinstance(X, pd.DataFrame): + X_r = r.matrix( + FloatVector(X.values.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) X_r.colnames = StrVector(self.column_names) - else: - X_r = r.matrix(FloatVector(X.ravel()), - byrow=True, - ncol=X.shape[1], - nrow=X.shape[0]) - - if self.pi_method == "none": - return(np.asarray(self.obj["predict"]( - X_r - ))) - return r_list_to_namedtuple(self.obj["predict"]( - X_r - )) - + else: + X_r = r.matrix( + FloatVector(X.ravel()), + byrow=True, + ncol=X.shape[1], + nrow=X.shape[0], + ) + + if self.pi_method == "none": + return np.asarray(self.obj["predict"](X_r)) + return r_list_to_namedtuple(self.obj["predict"](X_r)) + def update(self, newx, newy): """ update the model. """ - newx_r = base.as_vector(FloatVector(newx)) + newx_r = base.as_vector(FloatVector(newx)) + + self.obj["update"](newx_r, base.as_numeric(FloatVector([newy]))) - self.obj["update"](newx_r, - base.as_numeric(FloatVector([newy])) - ) - return self - diff --git a/learningmachine/utils.py b/learningmachine/utils.py index 8776853..12eba9f 100644 --- a/learningmachine/utils.py +++ b/learningmachine/utils.py @@ -1,4 +1,4 @@ -import numpy as np +import numpy as np import subprocess from rpy2.robjects import r from rpy2.robjects import NULL as rNULL @@ -75,21 +75,30 @@ def format_value(value): return f"{str(value).upper()}" if isinstance(value, int) or isinstance(value, float): return f"{value}" - + + # R list to namedtuple def r_list_to_namedtuple(r_list): - # Extract the names from the R list + # Extract the names from the R list if r_list.names is rNULL: - ##names = [f'obs{i+1}' for i in range(len(r_list))] + # names = [f'obs{i+1}' for i in range(len(r_list))] # Define a namedtuple type based on the names in the R list - ##DescribeResult = namedtuple('DescribeResult', names) + # DescribeResult = namedtuple('DescribeResult', names) # Extract elements from the R list and create a namedtuple - ##elements = {name: r_list.rx2(i+1) for i, name in enumerate(names)} - ##return DescribeResult(**elements) - return tuple([[int(r_list.rx2(i+1)[j]) for j in range(len(r_list.rx2(i+1)))] for i in range(len(r_list))]) + # elements = {name: r_list.rx2(i+1) for i, name in enumerate(names)} + # return DescribeResult(**elements) + return tuple( + [ + [ + int(r_list.rx2(i + 1)[j]) + for j in range(len(r_list.rx2(i + 1))) + ] + for i in range(len(r_list)) + ] + ) names = r_list.names # Define a namedtuple type based on the names in the R list - DescribeResult = namedtuple('DescribeResult', names) + DescribeResult = namedtuple("DescribeResult", names) # Extract elements from the R list and create a namedtuple - elements = {name: np.asarray(r_list.rx2(name)) for name in names} - return DescribeResult(**elements) \ No newline at end of file + elements = {name: np.asarray(r_list.rx2(name)) for name in names} + return DescribeResult(**elements) diff --git a/setup.py b/setup.py index bd9a083..65cc468 100644 --- a/setup.py +++ b/setup.py @@ -169,6 +169,6 @@ def install_packages(): packages=find_packages(include=["learningmachine", "learningmachine.*"]), test_suite="tests", url="https://github.com/Techtonique/learningmachine_python", - version="2.2.2", + version="2.3.0", zip_safe=False, )