Module lca_algebraic.stats
Expand source code
import math
import random
import warnings
from time import time
from typing import Type, Dict, Tuple, List
import seaborn as sns
from SALib.analyze import sobol
from SALib.sample import saltelli, sobol_sequence
from ipywidgets import interact
from jinja2.nodes import Add
from matplotlib import pyplot as plt
from sympy import Float, Number, Add, AtomicExpr, Mul, Expr
from sympy.core.operations import AssocOp
from .base_utils import _method_unit
from .lca import *
from .lca import _expanded_names_to_names, _filter_param_values, _replace_fixed_params, _modelToExpr, _preMultiLCAAlgebric, _postMultiLCAAlgebric
from .params import _variable_params, _param_registry, FixedParamMode, _param_name
PARALLEL=False
def _parallel_map(f, items) :
if PARALLEL :
with concurrent.futures.ThreadPoolExecutor() as exec:
return exec.map(f, items)
else :
return map(f, items)
def _heatmap(df, title, vmax, ints=False):
''' Produce heatmap of a dataframe'''
fig, ax = plt.subplots(figsize=(17, 17))
sns.heatmap(df.transpose(), cmap="gist_heat_r", vmax=vmax, annot=True, fmt='.0f' if ints else '.2f', square=True)
plt.title(title, fontsize=20)
plt.yticks(rotation=0)
ax.tick_params(axis="x", labelsize=18)
ax.tick_params(axis="y", labelsize=18)
def _extract_var_params(lambdas):
required_param_names = set()
for lamb in lambdas :
required_param_names.update(_expanded_names_to_names(lamb.expanded_params))
var_params = _variable_params(required_param_names)
return sorted(var_params.values(), key=lambda p: (p.group, p.name))
@with_db_context
def oat_matrix(model, impacts, n=10, title='Impact variability (% of mean)'):
'''Generates a heatmap of the incertitude of the model, varying input parameters one a a time.'''
# Compile model into lambda functions for fast LCA
lambdas = _preMultiLCAAlgebric(model, impacts)
# Sort params by category
sorted_params = _extract_var_params(lambdas)
change = np.zeros((len(sorted_params), len(impacts)))
for iparam, param in enumerate(sorted_params):
params = {param.name: param.default for param in sorted_params}
# Compute range of values for given param
params[param.name] = param.range(n)
# Compute LCA
df = _postMultiLCAAlgebric(impacts, lambdas, **params)
# Compute change
change[iparam] = (df.max() - df.min()) / df.median() * 100
# Build final heatmap
change = pd.DataFrame(change,
index=[param.get_label() for param in sorted_params],
columns=[method_name(imp) for imp in impacts])
_heatmap(change.transpose(), title, 100, ints=True)
def _display_tabs(titlesAndContentF):
"""Generate tabs"""
tabs = []
titles = []
for title, content_f in titlesAndContentF:
titles.append(title)
tab = widgets.Output()
with tab:
content_f()
tabs.append(tab)
res = widgets.Tab(children=tabs)
for i, title in enumerate(titles):
res.set_title(i, title)
display(res)
def oat_dasboard(modelOrLambdas, impacts, varying_param: ParamDef, n=10, all_param_names=None,
figsize=(15, 15), figspace=(0.5, 0.5), sharex=True, cols=3, func_unit="kWh"):
'''
Analyse the evolution of impacts for a single parameter. The other parameters are set to their default values.
The result heatmap shows percentage of variation relative to median value.
Parameters
----------
model : activity, or lambdas as precomputed by preMultiLCAAlgebric, for faster computation
impacts : set of methods
param: parameter to analyse
n: number of samples of the parameter
figsize: Size of figure fro graphs : (15, 15 by default)
figspace: Space between figures for graphs : (0.5, 0.5) by default
sharex: Shared X axes ? True by default
'''
if all_param_names == None:
all_param_names = _param_registry().keys()
params = {name: _param_registry()[name].default for name in all_param_names}
# Compute range of values for given param
params[varying_param.name] = varying_param.range(n)
# print("Params: ", params)
if isinstance(modelOrLambdas, Activity):
with DbContext(modelOrLambdas) :
df = multiLCAAlgebric(modelOrLambdas, impacts, **params)
else:
df = _postMultiLCAAlgebric(impacts, modelOrLambdas, **params)
# Add X values in the table
pname = varying_param.name
if varying_param.unit:
pname = '%s [%s]' % (pname, varying_param.unit)
df.insert(0, pname, varying_param.range(n))
df = df.set_index(pname)
def table():
displayWithExportButton(df)
def graph():
with warnings.catch_warnings():
warnings.simplefilter("ignore")
nb_rows = int(math.ceil(len(impacts) / cols))
fig, axes = plt.subplots(figsize=figsize)
plt.subplots_adjust(None, None, None, None, figspace[0], figspace[1])
axes = df.plot(
ax=axes, sharex=sharex, subplots=True,
layout=(nb_rows, cols),
# legend=None,
kind='line' if varying_param.type == ParamType.FLOAT else 'bar')
axes = axes.flatten()
for ax, impact in zip(axes, impacts):
ax.set_ylim(ymin=0)
ax.set_ylabel(_method_unit(impact) + " / " + func_unit)
plt.show(fig)
def change():
ch = (df.max() - df.min()) / df.median() * 100
fig, ax = plt.subplots(figsize=(9, 6))
plt.title('Relative change for %s' % df.index.name)
ch.plot(kind='barh', rot=30)
ax.set_xlabel('Relative change of the median value (%)')
plt.tight_layout()
plt.show(fig)
_display_tabs([
("Graphs", graph),
("Data", table),
("Variation", change)
])
@with_db_context
def oat_dashboard_interact(model, methods, **kwparams):
'''Interactive dashboard, with a dropdown for selecting parameter
Parameters
----------
figsize: Size of figure fro graphs : (15, 15 by default)
figspace: Space between figures for graphs : (0.5, 0.5) by default
sharex: Shared X axes ? True by default
'''
lambdas = _preMultiLCAAlgebric(model, methods)
def process_func(param):
with DbContext(model):
oat_dasboard(lambdas, methods, _param_registry()[param], **kwparams)
param_list = _expanded_names_to_names(lambdas[0].expanded_params)
param_list = list(_variable_params(param_list).keys())
interact(process_func, param=param_list)
class StochasticMethod :
SALTELLI = "saltelli"
RAND = "rand"
SOBOL="sobol"
def _stochastics(
modelOrLambdas, methods, n=1000,
var_params=None, sample_method=StochasticMethod.SALTELLI,
**extra_fixed_params):
params, problem = _generate_random_params(n, sample_method, var_params)
# Fix other params
if extra_fixed_params :
params.update(extra_fixed_params)
Y = _compute_stochastics(modelOrLambdas, methods , params)
return problem, params, Y
def _compute_stochastics(modelOrLambdas, methods, params):
if isinstance(modelOrLambdas, Activity):
Y = multiLCAAlgebric(modelOrLambdas, methods, **params)
else:
Y = _postMultiLCAAlgebric(methods, modelOrLambdas, **params)
return Y
def _generate_random_params(n, sample_method=StochasticMethod.SALTELLI, var_params=None, seed=None):
''' Compute stochastic impacts for later analysis of incertitude '''
if var_params is None:
var_params = _variable_params().values()
if seed is None :
seed = int(time() * 1000)
random.seed(seed)
# Extract variable names
var_param_names = list([param if isinstance(param, str) else param.name for param in var_params])
problem = {
'num_vars': len(var_param_names),
'names': var_param_names,
'bounds': [[0, 1]] * len(var_param_names)}
print("Generating samples ...")
if sample_method == StochasticMethod.SALTELLI:
X = saltelli.sample(problem, n, calc_second_order=True)
elif sample_method == StochasticMethod.RAND:
X = np.random.rand(n, len(var_param_names))
elif sample_method == StochasticMethod.SOBOL:
X = sobol_sequence.sample(n * (len(var_param_names) * 2 + 2), len(var_param_names))
# elif sample_method == StochasticMethod.LATIN :
# X = latin.sample(problem, n)
else:
raise Exception("Unkown rand method " + sample_method)
# Map normalized 0-1 random values into real values
print("Transforming samples ...")
params = dict()
for i, param_name in enumerate(var_param_names):
param = _param_registry()[param_name]
params[param_name] = param.rand(X[:, i]).tolist()
# Add fixed parameters
for param in _param_registry().values():
if param.name not in var_param_names:
params[param.name] = param.default
return params, problem
class SobolResults :
def __init__(self, s1, s2, st, s1_conf=None, s2_conf=None, st_conf=None):
self.s1 = s1
self.s2 = s2
self.st = st
self.s1_conf = s1_conf
self.s2_conf = s2_conf
self.st_conf = st_conf
def _sobols(methods, problem, Y) -> SobolResults :
''' Computes sobols indices'''
s1 = np.zeros((len(problem['names']), len(methods)))
s1_conf = np.zeros((len(problem['names']), len(methods)))
s2 = np.zeros((len(problem['names']), len(problem['names']), len(methods)))
s2_conf = np.zeros((len(problem['names']), len(problem['names']), len(methods)))
st = np.zeros((len(problem['names']), len(methods)))
st_conf = np.zeros((len(problem['names']), len(methods)))
def process(args) :
imethod, method = args
print("Processing sobol for " + str(method))
y = Y[Y.columns[imethod]]
res = sobol.analyze(problem, y.to_numpy(), calc_second_order=True)
return imethod, res
for imethod, res in _parallel_map(process, enumerate(methods)):
try:
s1[:, imethod] = res["S1"]
s1_conf[:, imethod] = res["S1_conf"]
s2_ = np.nan_to_num(res["S2"])
s2_conf_ = np.nan_to_num(res["S2_conf"])
s2[:, :, imethod] = s2_ + np.transpose(s2_)
s2_conf[:, :, imethod] = s2_conf_ + np.transpose(s2_conf_)
st[:, imethod] = res["ST"]
st_conf[:, imethod] = res["ST_conf"]
except Exception as e:
error("Sobol failed on %s" % imethod[2], e)
return SobolResults(s1, s2, st, s1_conf, s2_conf, st_conf)
def _incer_stochastic_matrix(methods, param_names, Y, sob, name_type=NameType.LABEL):
''' Internal method computing matrix of parameter importance '''
def draw(indice, mode):
'''
Mode comes as ('s1' || 'st', 'raw' || 'percent')
'''
sx = sob.s1 if indice == "s1" else sob.st
if mode == 'raw':
data = sx
else:
# If percent, express result as percentage of standard deviation / mean
data = np.zeros((len(param_names), len(methods)))
for i, method in enumerate(methods):
# Total variance
var = np.var(Y[Y.columns[i]])
mean = np.mean(Y[Y.columns[i]])
if mean != 0:
data[:, i] = np.sqrt((sx[:, i] * var)) / mean * 100
param_labels = [_param_name(_param_registry()[name], name_type) for name in param_names]
df = pd.DataFrame(data, index=param_labels, columns=[method_name(method) for method in methods])
_heatmap(
df.transpose(),
title="Relative deviation of impacts (%)" if mode == 'percent' else "Sobol indices (part of variability)",
vmax=100 if mode == 'percent' else 1,
ints=mode == 'percent')
interact(draw,
indice=['s1', 'st'],
mode=[
('Raw indices', 'raw'),
('Relative to mean (%)', 'percent')]
)
@with_db_context
def incer_stochastic_matrix(model, methods, n=1000, name_type=NameType.LABEL):
'''
Method computing matrix of parameter importance
parameters
----------
var_params: Optional list of parameters to vary.
By default use all the parameters with distribution not FIXED
'''
lambdas = _preMultiLCAAlgebric(model, methods)
var_params = _extract_var_params(lambdas)
problem, _, Y = _stochastics(lambdas, methods, n, var_params)
print("Processing Sobol indices ...")
sob = _sobols(methods, problem, Y)
_incer_stochastic_matrix(methods, problem['names'], Y, sob, name_type=name_type)
def _incer_stochastic_violin(methods, Y, figsize=(15, 15), figspace=(0.5, 0.5), sharex=True, nb_cols=3):
''' Internal method for computing violin graph of impacts
Parameters
----------
methods: list of impact methods
Y : output
figsize: Size of figure for graphs : (15, 15 by default)
figspace: Space between figures for graphs : (0.5, 0.5) by default
sharex: Shared X axes ? True by default
nb_cols: Number of colums. 3 by default
'''
nb_rows = math.ceil(len(methods) / nb_cols)
fig, axes = plt.subplots(nb_rows, nb_cols, figsize=figsize, sharex=sharex)
plt.subplots_adjust(None, None, None, None, figspace[0], figspace[1])
for imethod, method, ax in zip(range(len(methods)), methods, axes.flatten()):
data = Y[Y.columns[imethod]]
median = np.median(data)
std = np.std(data)
mean = np.mean(data)
ax.violinplot(data, showmedians=True)
ax.title.set_text(method_name(method))
ax.set_ylim(ymin=0)
ax.set_ylabel(_method_unit(method))
ax.set_xticklabels([])
# Add text
textstr = '\n'.join((
r'$\mu=%.3g$' % (mean,),
r'$\mathrm{median}=%.3g$' % (median,),
r'$\sigma=%.3g$' % (std,)))
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
ax.text(0.05, 0.95, textstr, transform=ax.transAxes, fontsize=10,
verticalalignment='top', bbox=props)
plt.tick_params(axis='x', which='both', bottom=False, top=False, labelbottom=False)
plt.show(fig)
@with_db_context
def incer_stochastic_violin(modelOrLambdas, methods, n=1000, var_params=None, **kwparams):
'''
Method for computing violin graph of impacts
parameters
----------
var_params: Optional list of parameters to vary.
By default use all the parameters with distribution not FIXED
'''
_, _, Y = _stochastics(modelOrLambdas, methods, n, var_params)
_incer_stochastic_violin(methods, Y, **kwparams)
_percentiles = [10, 90, 25, 50, 75]
def _incer_stochastic_variations(methods, param_names, Y, sob1):
''' Method for computing violin graph of impacts '''
method_names = [method_name(method) for method in methods]
std = np.std(Y)
mean = np.mean(Y)
fig = plt.figure(num=None, figsize=(12, 6), dpi=80, facecolor='w', edgecolor='k')
ax = plt.gca()
tab20b = plt.get_cmap('tab20b')
tab20c = plt.get_cmap('tab20c')
ax.set_prop_cycle('color', [tab20b(k) if k < 1 else tab20c(k - 1) for k in np.linspace(0, 2, 40)])
relative_variance_pct = std * std / (mean * mean) * 100
totplt = plt.bar(np.arange(len(method_names)), relative_variance_pct, 0.8)
sum = np.zeros(len(methods))
plots = [totplt[0]]
for i_param, param_name in enumerate(param_names):
s1 = sob1[i_param, :]
curr_bar = s1 * relative_variance_pct
curr_plt = plt.bar(np.arange(len(method_names)), curr_bar, 0.8, bottom=sum)
sum += curr_bar
plots.append(curr_plt[0])
plt.legend(plots, ['Higher order'] + param_names, loc=(1, 0))
plt.xticks(np.arange(len(method_names)), method_names, rotation=90)
plt.title("variance / mean² (%)")
plt.show(fig)
def _incer_stochastic_data(methods, param_names, Y, sob1, sobt):
'''Show full stochastic output with sobol indices'''
data = np.zeros((len(param_names) * 2 + len(_percentiles) +2, len(methods)))
data[0, :] = np.mean(Y)
data[1, :] = np.std(Y)
for i, percentile in enumerate(_percentiles) :
data[2 + i, :] = np.percentile(Y, percentile, axis=0)
for i_param, param_name in enumerate(param_names):
s1 = sob1[i_param, :]
data[i_param + 2 + len(_percentiles), :] = s1
data[i_param + 2 + len(_percentiles) + len(param_names), :] = sobt[i_param, :]
rows = ["mean", "std"] + \
["p%d" % p for p in _percentiles] + \
["Sobol 1(%s)" % param for param in param_names] + \
["Sobol T(%s)" % param for param in param_names]
df = pd.DataFrame(data, index=rows, columns=[method_name(method) for method in methods])
displayWithExportButton(df)
@with_db_context
def incer_stochastic_dashboard(model, methods, n=1000, var_params=None, **kwparams):
''' Generates a dashboard with several statistics : matrix of parameter incertitude, violin diagrams, ...
parameters
----------
var_params: Optional list of parameters to vary.
By default use all the parameters with distribution not FIXED
figsize: Size of figure for violin plots : (15, 15) by default
figspace: Space between violin graphs (0.5, 0.5) by default
sharex: Share X axe for violin graph : True by default
'''
problem, _, Y = _stochastics(model, methods, n, var_params)
param_names = problem['names']
print("Processing Sobol indices ...")
sob = _sobols(methods, problem, Y)
def violin():
_incer_stochastic_violin(methods, Y, **kwparams)
def variation():
_incer_stochastic_variations(methods, param_names, Y, sob.s1)
def matrix():
_incer_stochastic_matrix(methods, problem['names'], Y, sob)
def data():
_incer_stochastic_data(methods, problem['names'], Y, sob.s1, sob.st)
_display_tabs([
("Violin graphs", violin),
("Impact variations", variation),
("Sobol matrix", matrix),
("Data", data)
])
def _round_expr(expr, num_digits):
''' Round all number in sympy expression with n digits'''
return expr.xreplace({n : Float(n, num_digits) if isinstance(n, Float) else n for n in expr.atoms(Number)})
@with_db_context
def sobol_simplify_model(
model, methods,
min_ratio=0.8, n=2000, var_params=None,
fixed_mode = FixedParamMode.MEDIAN,
num_digits=3,
simple_sums=True,
simple_products=True) -> List[LambdaWithParamNames]:
'''
Computes Sobol indices and selects main parameters for explaining sensibility of at least 'min_ratio',
Then generates simplified models for those parameters.
parameters
----------
min_ratio: [0, 1] minimum amount of first order variation (sum of S1) to explain
var_params: Optional list of parameters to vary.
fixed_mode : What to replace minor parameters with : MEDIAN by default
sob: [optional] Pre-computed sobol indices
simplify_sums: If true (default) remove terms in sums that are lower than 1%
returns
_______
List of LambdaWithParamNames, one per impact : with wraps the simplified expression together with the
list of required parameters and a fast complied lambda function for fast evaluation.
'''
# Default var param names
if var_params is None :
var_params = _variable_params().values()
var_param_names = list([param.name for param in var_params])
problem, params, Y = _stochastics(model, methods, n, var_params)
sob = _sobols(methods, problem, Y)
s1, s2 = sob.s1, sob.s2
res = []
# Generate simplified model
exprs, _ = _modelToExpr(model, methods)
for imethod, method in enumerate(methods) :
print("> Method : ", method_name(method))
s1_sum = np.sum(s1[:, imethod])
s2_sum = np.sum(s2[:, :, imethod]) / 2
print('S1: ', s1_sum)
print('S2: ', s2_sum)
print('ST: ', np.sum(sob.st[:, imethod]))
sum = 0
sorted_param_indices = list(range(0, len(var_param_names)))
sorted_param_indices = sorted(sorted_param_indices, key=lambda i : s1[i, imethod], reverse=True)
selected_params = []
sobols = dict()
for iparam, param in enumerate(sorted_param_indices) :
# S1
sum += s1[param, imethod]
param_name = var_param_names[param]
selected_params.append(param_name)
sobols[param_name] = s1[param, imethod]
# S2
#for iparam2 in range(0, iparam) :
# param2 = sorted_param_indices[iparam2]
# sum += s2[param, param2, imethod]
if sum > min_ratio :
break
print("Selected params : ", selected_params, "explains: ", sum)
expr = exprs[imethod]
# Replace extra fixed params
extraFixedParams = [param for param in _param_registry().values() if param.name not in selected_params]
expr = _replace_fixed_params(expr, extraFixedParams, fixed_mode=fixed_mode)
# Sympy simplification
expr = simplify(expr)
# Round numerical values to 3 digits
expr = _round_expr(expr, num_digits)
# Lambdify the expression
lambd = LambdaWithParamNames(expr, params=selected_params, sobols=sobols)
# Compute list of parameter values (monte carlo)
completed_params = lambd.complete_params(params)
expanded_params = _filter_param_values(completed_params, lambd.expanded_params)
# Extra step of simplification : simplify sums with neligeable terms
if simple_sums :
expr = _simplify_sums(expr, expanded_params)
if simple_products:
expr = _simplify_products(expr, expanded_params)
simplified_expr = simplify(expr)
display(simplified_expr)
res.append(LambdaWithParamNames(simplified_expr, params=selected_params, sobols=sobols))
return res
TERM_MIN_LEVEL = 0.01
def _rec_expression(exp, func) :
""" Recurse trough an expression, transforming each term with the result of f(term) """
def rec(term) :
if issubclass(exp.func, AtomicExpr):
return func(term)
else :
args = filter(lambda x: x is not None, list(func(arg) for arg in term.args))
return term.func(*args)
def _simplify_sums(expr, param_values) :
def replace_term(term, minv, maxv, max_max):
abs_max = max(abs(minv), abs(maxv))
if abs_max < (TERM_MIN_LEVEL * max_max):
return None
else:
return term
return _simplify_terms(expr, param_values, Add, replace_term)
def _simplify_products(expr, param_values) :
def replace_term(term, minv, maxv, max_max) :
# Close to 1 or -1 ?
for factor in [-1, 1]:
if abs(minv - factor) < TERM_MIN_LEVEL and abs(maxv - factor) < TERM_MIN_LEVEL:
if factor == -1:
return -1
else:
# * 1.0 : remove term
return None
return term
return _simplify_terms(expr, param_values, Mul, replace_term)
def _simplify_terms(expr, expanded_param_values, op:Type[AssocOp], replace) :
# Determine max normalized value of this term, for all param values (monte carlo)
min_max_cache : Dict[str, Tuple[float, float]] = dict()
def min_max(term) :
# In cache ?
key = str(term)
if key in min_max_cache :
return min_max_cache[key]
# Non varying ?
if len(term.free_symbols) == 0:
values = [term.evalf()]
else:
lambd_term = lambdify(expanded_param_values.keys(), term)
values = lambd_term(**expanded_param_values)
minv = np.min(values)
maxv = np.max(values)
min_max_cache[key] = (minv, maxv)
return (minv, maxv)
# Cleanup :keep only most relevant terms
def cleanup(exp):
if (not isinstance(exp, Expr)) or issubclass(exp.func, AtomicExpr):
return exp
# For Op, only select terms than are relevant
if exp.func == op :
# Compute max of max
def abs_max(minv, maxv) :
return max(abs(minv), abs(maxv))
max_max = max([abs_max(*min_max(arg)) for arg in exp.args])
# Only keep term above level
args = [replace(arg, *min_max(arg), max_max) for arg in exp.args]
else:
args = exp.args
args = [cleanup(arg) for arg in args if arg is not None]
return exp.func(*args)
return cleanup(expr)
def _hline(x1, x2, y, linewidth=1, linestyle='solid'):
ymin, ymax = plt.ylim()
xmin, xmax = plt.xlim()
minx = (x1 - xmin) / (xmax - xmin)
maxx = (x2 - xmin) / (xmax - xmin)
plt.axhline(ymax * y, color='k', xmin=minx, xmax=maxx, linewidth=linewidth, linestyle=linestyle)
def _vline(x, ymin, ymax, linewidth=1, linestyle='solid'):
plt.axvline(x, color='k', ymin=ymin, ymax=ymax, linewidth=linewidth, linestyle=linestyle)
def _graph(data, unit, title, ax, alpha=1, textboxtop=0.95, textboxright=0.95, color=None,
limit_xrange=False, percentiles=[5,95], fontsize=12):
if ax is not None:
plt.sca(ax)
else:
ax = plt.gca()
median = np.median(data)
std = np.std(data)
mean = np.mean(data)
xmin= np.min(data)
p9995 = np.percentile(data, 99.95)
pvals = [np.percentile(data, perc) for perc in percentiles]
variability = std / mean
args = dict()
if color:
args['color'] = color
if limit_xrange :
plt.xlim(xmin, p9995)
plt.hist(data, 200, alpha=alpha, **args)
perc_strs = [r'$p%d=%.3g$' % (p,pval) for p, pval in zip(percentiles, pvals)]
textstr = '\n'.join([
r'$\mu=%.3g$' % (mean,),
r'$\mathrm{median}=%.3g$' % (median,),
r'$\sigma=%.3g$' % (std,),
r'$\sigma/\mu=%.3g$' % (variability,)
] + perc_strs)
props = dict(boxstyle='round', facecolor='wheat' if not color else color, alpha=0.5)
ax.text(textboxright, textboxtop, textstr, transform=ax.transAxes, fontsize=fontsize,
verticalalignment='top', ha='right', bbox=props)
# Axes
ax.set_xlabel(unit, dict(fontsize=14))
ax.set_yticks([])
ax.set_title(title, dict(fontsize=16))
return dict(
median=median,
std=std,
p=pvals,
mean=mean,
var=variability)
def distrib(*args, **kwargs) :
"""
Show distributions together with statistical outcomes
Synonym of #graphs()
parameters
----------
model: normalized model
methods: List of impacts
Y: output of processing. If None, monte carlo will be processed again
nb_cols : number of colons to display graphs on
invert : list of methods for which result should be inverted (1/X). None by default
scales : Dict of method => scale, for multiplying results. To be used with unit overrides
unit_overrides : Dict of method => string for overriding unit, in respect to custom scales
height: Height of graph : 10 inches be default
width : Width of graphs : 15 inches by default
"""
return graphs(*args, **kwargs)
@with_db_context
def graphs(
model, methods,
Y=None, nb_cols=1, axes=None, title=None,
invert=None,
scales=None, # Dict of method => scale
unit_overrides=None,
height=10, width=15,
func_unit="kWh",
**kwargs):
"""
Show distributions together with statistical outcomes
parameters
----------
model: normalized model
methods: List of impacts
Y: output of processing. If None, monte carlo will be processed again
nb_cols : number of colons to display graphs on
invert : list of methods for which result should be inverted (1/X). None by default
scales : Dict of method => scale, for multiplying results. To be used with unit overrides
unit_overrides : Dict of method => string for overriding unit, in respect to custom scales
height: Height of graph : 10 inches be default
width : Width of graphs : 15 inches by default
"""
if Y is None:
_, _, Y = _stochastics(model, methods, n=10000)
if axes is None:
nb_rows = math.ceil(len(methods) / nb_cols)
fig, axes = plt.subplots(nb_rows, nb_cols, figsize=(width, height * nb_rows))
if isinstance(axes, np.ndarray):
axes = axes.flatten()
else:
axes = [axes]
plt.subplots_adjust(hspace=0.4)
res=dict()
for i, method, ax in zip(range(len(methods)), methods, axes):
data = Y[Y.columns[i]]
if invert and method in invert:
data = 1 / data
if scales and method in scales:
data = data * scales[method]
graph_title = title if title else method_name(method)
if unit_overrides and method in unit_overrides:
unit = unit_overrides[method]
else:
unit = _method_unit(method)
unit += " / " + func_unit
stats = _graph(
data, unit,
graph_title,
ax=ax,
**kwargs)
res[graph_title + (' [%s]' % unit)] = stats
for i in range(0, -len(methods) % nb_cols):
ax = axes.flatten()[-(i + 1)]
ax.axis("off")
return pd.DataFrame(res)
@with_db_context
def compare_simplified(
model, methods, simpl_lambdas,
scales=None, # Dict of method => scale
unit_overrides=None,
nb_cols=2, height=10, width=15, textboxright=0.6, r2_height=0.65, func_unit="kWh",
**kwargs):
'''
Compare distribution of simplified model with full model
Parameters
----------
model: Model
methods : Impact methods
simpl_lambdas : Simplified lambdas, as returned by sobol_simplify_model(...)
nb_cols: number of columns for displaying graphs
percentiles: List of percentiles to compute [5, 95] by default
'''
# Raw model
lambdas = _preMultiLCAAlgebric(model, methods)
nb_rows = math.ceil(len(methods) / nb_cols)
fig, axes = plt.subplots(nb_rows, nb_cols, figsize=(width, height * nb_rows))
plt.tight_layout()
plt.subplots_adjust(hspace=0.3)
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
for i, lambd, simpl_lambd, method, ax in zip(range(len(methods)), lambdas, simpl_lambdas, methods, axes.flatten()):
params, _ = _generate_random_params(100000, sample_method=StochasticMethod.RAND)
# Run Monte Carlo on full model
Y1 = _compute_stochastics([lambd], [method], params)
d1 = Y1[Y1.columns[0]]
# Run monte carlo of simplified model
Y2 = _compute_stochastics([simpl_lambd], [method], params)
d2 = Y2[Y2.columns[0]]
r_value = r_squared(Y1, Y2)
title = method_name(method)
if scales and method in scales:
d1 = d1 * scales[method]
d2 = d2 * scales[method]
if unit_overrides and method in unit_overrides:
unit = unit_overrides[method]
else:
unit = _method_unit(method)
unit += " / " + func_unit
_graph(d1, unit, title, ax=ax, alpha=0.6, color=colors[0], **kwargs)
_graph(d2, unit, title, ax=ax, alpha=0.6, textboxright=textboxright, color=colors[1], **kwargs)
ax.text(0.9, r2_height, "R² : %0.3g" % r_value, transform=ax.transAxes, fontsize=14,
verticalalignment='top', ha='right')
# Hide missing graphs
for i in range(0, -len(methods) % nb_cols):
ax = axes.flatten()[-(i + 1)]
ax.axis("off")
Functions
def compare_simplified(model, methods, simpl_lambdas, scales=None, unit_overrides=None, nb_cols=2, height=10, width=15, textboxright=0.6, r2_height=0.65, func_unit='kWh', **kwargs)
-
Compare distribution of simplified model with full model
Parameters
model
:Model
methods
:Impact
methods
simpl_lambdas
:Simplified
lambdas
,as
returned
by
sobol_simplify_model()
(...
)nb_cols
:number
ofcolumns
for
displaying
graphs()
percentiles
:List
ofpercentiles
to
compute
[5
,95
]by
default
Expand source code
@with_db_context def compare_simplified( model, methods, simpl_lambdas, scales=None, # Dict of method => scale unit_overrides=None, nb_cols=2, height=10, width=15, textboxright=0.6, r2_height=0.65, func_unit="kWh", **kwargs): ''' Compare distribution of simplified model with full model Parameters ---------- model: Model methods : Impact methods simpl_lambdas : Simplified lambdas, as returned by sobol_simplify_model(...) nb_cols: number of columns for displaying graphs percentiles: List of percentiles to compute [5, 95] by default ''' # Raw model lambdas = _preMultiLCAAlgebric(model, methods) nb_rows = math.ceil(len(methods) / nb_cols) fig, axes = plt.subplots(nb_rows, nb_cols, figsize=(width, height * nb_rows)) plt.tight_layout() plt.subplots_adjust(hspace=0.3) colors = plt.rcParams["axes.prop_cycle"].by_key()["color"] for i, lambd, simpl_lambd, method, ax in zip(range(len(methods)), lambdas, simpl_lambdas, methods, axes.flatten()): params, _ = _generate_random_params(100000, sample_method=StochasticMethod.RAND) # Run Monte Carlo on full model Y1 = _compute_stochastics([lambd], [method], params) d1 = Y1[Y1.columns[0]] # Run monte carlo of simplified model Y2 = _compute_stochastics([simpl_lambd], [method], params) d2 = Y2[Y2.columns[0]] r_value = r_squared(Y1, Y2) title = method_name(method) if scales and method in scales: d1 = d1 * scales[method] d2 = d2 * scales[method] if unit_overrides and method in unit_overrides: unit = unit_overrides[method] else: unit = _method_unit(method) unit += " / " + func_unit _graph(d1, unit, title, ax=ax, alpha=0.6, color=colors[0], **kwargs) _graph(d2, unit, title, ax=ax, alpha=0.6, textboxright=textboxright, color=colors[1], **kwargs) ax.text(0.9, r2_height, "R² : %0.3g" % r_value, transform=ax.transAxes, fontsize=14, verticalalignment='top', ha='right') # Hide missing graphs for i in range(0, -len(methods) % nb_cols): ax = axes.flatten()[-(i + 1)] ax.axis("off")
def distrib(*args, **kwargs)
-
Show distributions together with statistical outcomes
Synonym of #graphs()
parameters
model
:normalized
model
methods
:List
ofimpacts
Y
:output
ofprocessing.
If
None
,monte
carlo
will
be
processed
again
nb_cols
:number
ofcolons
to
display
graphs()
on
invert
:list
ofmethods
for
which
result
should
be
inverted
(1
/X
).
None
by
defaultscales
:Dict
ofmethod
=>scale
,for
multiplying
results.
To
be
used
with
unit
overrides
unit_overrides
:Dict
ofmethod
=>string
for
overriding
unit
,in
respect
to
custom
scales
height
:Height
ofgraph
:10
inches
be
defaultwidth
:Width
ofgraphs()
:15
inches
by
default
Expand source code
def distrib(*args, **kwargs) : """ Show distributions together with statistical outcomes Synonym of #graphs() parameters ---------- model: normalized model methods: List of impacts Y: output of processing. If None, monte carlo will be processed again nb_cols : number of colons to display graphs on invert : list of methods for which result should be inverted (1/X). None by default scales : Dict of method => scale, for multiplying results. To be used with unit overrides unit_overrides : Dict of method => string for overriding unit, in respect to custom scales height: Height of graph : 10 inches be default width : Width of graphs : 15 inches by default """ return graphs(*args, **kwargs)
def graphs(model, methods, Y=None, nb_cols=1, axes=None, title=None, invert=None, scales=None, unit_overrides=None, height=10, width=15, func_unit='kWh', **kwargs)
-
Show distributions together with statistical outcomes
parameters
model
:normalized
model
methods
:List
ofimpacts
Y
:output
ofprocessing.
If
None
,monte
carlo
will
be
processed
again
nb_cols
:number
ofcolons
to
display
graphs()
on
invert
:list
ofmethods
for
which
result
should
be
inverted
(1
/X
).
None
by
defaultscales
:Dict
ofmethod
=>scale
,for
multiplying
results.
To
be
used
with
unit
overrides
unit_overrides
:Dict
ofmethod
=>string
for
overriding
unit
,in
respect
to
custom
scales
height
:Height
ofgraph
:10
inches
be
defaultwidth
:Width
ofgraphs()
:15
inches
by
default
Expand source code
@with_db_context def graphs( model, methods, Y=None, nb_cols=1, axes=None, title=None, invert=None, scales=None, # Dict of method => scale unit_overrides=None, height=10, width=15, func_unit="kWh", **kwargs): """ Show distributions together with statistical outcomes parameters ---------- model: normalized model methods: List of impacts Y: output of processing. If None, monte carlo will be processed again nb_cols : number of colons to display graphs on invert : list of methods for which result should be inverted (1/X). None by default scales : Dict of method => scale, for multiplying results. To be used with unit overrides unit_overrides : Dict of method => string for overriding unit, in respect to custom scales height: Height of graph : 10 inches be default width : Width of graphs : 15 inches by default """ if Y is None: _, _, Y = _stochastics(model, methods, n=10000) if axes is None: nb_rows = math.ceil(len(methods) / nb_cols) fig, axes = plt.subplots(nb_rows, nb_cols, figsize=(width, height * nb_rows)) if isinstance(axes, np.ndarray): axes = axes.flatten() else: axes = [axes] plt.subplots_adjust(hspace=0.4) res=dict() for i, method, ax in zip(range(len(methods)), methods, axes): data = Y[Y.columns[i]] if invert and method in invert: data = 1 / data if scales and method in scales: data = data * scales[method] graph_title = title if title else method_name(method) if unit_overrides and method in unit_overrides: unit = unit_overrides[method] else: unit = _method_unit(method) unit += " / " + func_unit stats = _graph( data, unit, graph_title, ax=ax, **kwargs) res[graph_title + (' [%s]' % unit)] = stats for i in range(0, -len(methods) % nb_cols): ax = axes.flatten()[-(i + 1)] ax.axis("off") return pd.DataFrame(res)
def incer_stochastic_dashboard(model, methods, n=1000, var_params=None, **kwparams)
-
Generates a dashboard with several statistics : matrix of parameter incertitude, violin diagrams, …
parameters
- var_params: Optional list of parameters to vary.
- By default use all the parameters with distribution not FIXED
figsize
:Size
offigure
for
violin
plots
: (15
,15
)by
defaultfigspace
:Space
between
violin
graphs()
(0.5
,0.5
)by
defaultsharex
:Share
X
axe
for
violin
graph
:True
by
default
Expand source code
@with_db_context def incer_stochastic_dashboard(model, methods, n=1000, var_params=None, **kwparams): ''' Generates a dashboard with several statistics : matrix of parameter incertitude, violin diagrams, ... parameters ---------- var_params: Optional list of parameters to vary. By default use all the parameters with distribution not FIXED figsize: Size of figure for violin plots : (15, 15) by default figspace: Space between violin graphs (0.5, 0.5) by default sharex: Share X axe for violin graph : True by default ''' problem, _, Y = _stochastics(model, methods, n, var_params) param_names = problem['names'] print("Processing Sobol indices ...") sob = _sobols(methods, problem, Y) def violin(): _incer_stochastic_violin(methods, Y, **kwparams) def variation(): _incer_stochastic_variations(methods, param_names, Y, sob.s1) def matrix(): _incer_stochastic_matrix(methods, problem['names'], Y, sob) def data(): _incer_stochastic_data(methods, problem['names'], Y, sob.s1, sob.st) _display_tabs([ ("Violin graphs", violin), ("Impact variations", variation), ("Sobol matrix", matrix), ("Data", data) ])
def incer_stochastic_matrix(model, methods, n=1000, name_type=NameType.LABEL)
-
Method computing matrix of parameter importance
parameters
var_params: Optional list of parameters to vary. By default use all the parameters with distribution not FIXED
Expand source code
@with_db_context def incer_stochastic_matrix(model, methods, n=1000, name_type=NameType.LABEL): ''' Method computing matrix of parameter importance parameters ---------- var_params: Optional list of parameters to vary. By default use all the parameters with distribution not FIXED ''' lambdas = _preMultiLCAAlgebric(model, methods) var_params = _extract_var_params(lambdas) problem, _, Y = _stochastics(lambdas, methods, n, var_params) print("Processing Sobol indices ...") sob = _sobols(methods, problem, Y) _incer_stochastic_matrix(methods, problem['names'], Y, sob, name_type=name_type)
def incer_stochastic_violin(modelOrLambdas, methods, n=1000, var_params=None, **kwparams)
-
Method for computing violin graph of impacts
parameters
var_params: Optional list of parameters to vary. By default use all the parameters with distribution not FIXED
Expand source code
@with_db_context def incer_stochastic_violin(modelOrLambdas, methods, n=1000, var_params=None, **kwparams): ''' Method for computing violin graph of impacts parameters ---------- var_params: Optional list of parameters to vary. By default use all the parameters with distribution not FIXED ''' _, _, Y = _stochastics(modelOrLambdas, methods, n, var_params) _incer_stochastic_violin(methods, Y, **kwparams)
def oat_dasboard(modelOrLambdas, impacts, varying_param, n=10, all_param_names=None, figsize=(15, 15), figspace=(0.5, 0.5), sharex=True, cols=3, func_unit='kWh')
-
Analyse the evolution of impacts for a single parameter. The other parameters are set to their default values. The result heatmap shows percentage of variation relative to median value.
Parameters
model
:activity
, orlambdas
as
precomputed
by
preMultiLCAAlgebric
,for
faster
computation
impacts
:set
ofmethods
param
:parameter
to
analyse
n
:number
ofsamples
ofthe
parameter
figsize
:Size
offigure
fro
graphs()
: (15
,15
by
default)figspace
:Space
between
figures
for
graphs()
: (0.5
,0.5
)by
defaultsharex
:Shared
X
axes
?True
by
default
Expand source code
def oat_dasboard(modelOrLambdas, impacts, varying_param: ParamDef, n=10, all_param_names=None, figsize=(15, 15), figspace=(0.5, 0.5), sharex=True, cols=3, func_unit="kWh"): ''' Analyse the evolution of impacts for a single parameter. The other parameters are set to their default values. The result heatmap shows percentage of variation relative to median value. Parameters ---------- model : activity, or lambdas as precomputed by preMultiLCAAlgebric, for faster computation impacts : set of methods param: parameter to analyse n: number of samples of the parameter figsize: Size of figure fro graphs : (15, 15 by default) figspace: Space between figures for graphs : (0.5, 0.5) by default sharex: Shared X axes ? True by default ''' if all_param_names == None: all_param_names = _param_registry().keys() params = {name: _param_registry()[name].default for name in all_param_names} # Compute range of values for given param params[varying_param.name] = varying_param.range(n) # print("Params: ", params) if isinstance(modelOrLambdas, Activity): with DbContext(modelOrLambdas) : df = multiLCAAlgebric(modelOrLambdas, impacts, **params) else: df = _postMultiLCAAlgebric(impacts, modelOrLambdas, **params) # Add X values in the table pname = varying_param.name if varying_param.unit: pname = '%s [%s]' % (pname, varying_param.unit) df.insert(0, pname, varying_param.range(n)) df = df.set_index(pname) def table(): displayWithExportButton(df) def graph(): with warnings.catch_warnings(): warnings.simplefilter("ignore") nb_rows = int(math.ceil(len(impacts) / cols)) fig, axes = plt.subplots(figsize=figsize) plt.subplots_adjust(None, None, None, None, figspace[0], figspace[1]) axes = df.plot( ax=axes, sharex=sharex, subplots=True, layout=(nb_rows, cols), # legend=None, kind='line' if varying_param.type == ParamType.FLOAT else 'bar') axes = axes.flatten() for ax, impact in zip(axes, impacts): ax.set_ylim(ymin=0) ax.set_ylabel(_method_unit(impact) + " / " + func_unit) plt.show(fig) def change(): ch = (df.max() - df.min()) / df.median() * 100 fig, ax = plt.subplots(figsize=(9, 6)) plt.title('Relative change for %s' % df.index.name) ch.plot(kind='barh', rot=30) ax.set_xlabel('Relative change of the median value (%)') plt.tight_layout() plt.show(fig) _display_tabs([ ("Graphs", graph), ("Data", table), ("Variation", change) ])
def oat_dashboard_interact(model, methods, **kwparams)
-
Interactive dashboard, with a dropdown for selecting parameter
Parameters
figsize
:Size
offigure
fro
graphs()
: (15
,15
by
default)figspace
:Space
between
figures
for
graphs()
: (0.5
,0.5
)by
defaultsharex
:Shared
X
axes
?True
by
default
Expand source code
@with_db_context def oat_dashboard_interact(model, methods, **kwparams): '''Interactive dashboard, with a dropdown for selecting parameter Parameters ---------- figsize: Size of figure fro graphs : (15, 15 by default) figspace: Space between figures for graphs : (0.5, 0.5) by default sharex: Shared X axes ? True by default ''' lambdas = _preMultiLCAAlgebric(model, methods) def process_func(param): with DbContext(model): oat_dasboard(lambdas, methods, _param_registry()[param], **kwparams) param_list = _expanded_names_to_names(lambdas[0].expanded_params) param_list = list(_variable_params(param_list).keys()) interact(process_func, param=param_list)
def oat_matrix(model, impacts, n=10, title='Impact variability (% of mean)')
-
Generates a heatmap of the incertitude of the model, varying input parameters one a a time.
Expand source code
@with_db_context def oat_matrix(model, impacts, n=10, title='Impact variability (% of mean)'): '''Generates a heatmap of the incertitude of the model, varying input parameters one a a time.''' # Compile model into lambda functions for fast LCA lambdas = _preMultiLCAAlgebric(model, impacts) # Sort params by category sorted_params = _extract_var_params(lambdas) change = np.zeros((len(sorted_params), len(impacts))) for iparam, param in enumerate(sorted_params): params = {param.name: param.default for param in sorted_params} # Compute range of values for given param params[param.name] = param.range(n) # Compute LCA df = _postMultiLCAAlgebric(impacts, lambdas, **params) # Compute change change[iparam] = (df.max() - df.min()) / df.median() * 100 # Build final heatmap change = pd.DataFrame(change, index=[param.get_label() for param in sorted_params], columns=[method_name(imp) for imp in impacts]) _heatmap(change.transpose(), title, 100, ints=True)
def sobol_simplify_model(model, methods, min_ratio=0.8, n=2000, var_params=None, fixed_mode='median', num_digits=3, simple_sums=True, simple_products=True)
-
Computes Sobol indices and selects main parameters for explaining sensibility of at least 'min_ratio', Then generates simplified models for those parameters.
parameters
min_ratio
: [0
,1
]minimum
amount
offirst
order
variation
(sum
ofS1
)to
explain
- var_params: Optional list of parameters to vary.
fixed_mode
:What
to
replace
minor
parameters
with
:MEDIAN
by
defaultsob
: [optional]Pre
-computed
sobol
indices
simplify_sums
:If
true
(default)remove
terms
in
sums
that
are
lower
than
1
%returns
_______
List of LambdaWithParamNames, one per impact : with wraps the simplified expression together with the list of required parameters and a fast complied lambda function for fast evaluation.
Expand source code
@with_db_context def sobol_simplify_model( model, methods, min_ratio=0.8, n=2000, var_params=None, fixed_mode = FixedParamMode.MEDIAN, num_digits=3, simple_sums=True, simple_products=True) -> List[LambdaWithParamNames]: ''' Computes Sobol indices and selects main parameters for explaining sensibility of at least 'min_ratio', Then generates simplified models for those parameters. parameters ---------- min_ratio: [0, 1] minimum amount of first order variation (sum of S1) to explain var_params: Optional list of parameters to vary. fixed_mode : What to replace minor parameters with : MEDIAN by default sob: [optional] Pre-computed sobol indices simplify_sums: If true (default) remove terms in sums that are lower than 1% returns _______ List of LambdaWithParamNames, one per impact : with wraps the simplified expression together with the list of required parameters and a fast complied lambda function for fast evaluation. ''' # Default var param names if var_params is None : var_params = _variable_params().values() var_param_names = list([param.name for param in var_params]) problem, params, Y = _stochastics(model, methods, n, var_params) sob = _sobols(methods, problem, Y) s1, s2 = sob.s1, sob.s2 res = [] # Generate simplified model exprs, _ = _modelToExpr(model, methods) for imethod, method in enumerate(methods) : print("> Method : ", method_name(method)) s1_sum = np.sum(s1[:, imethod]) s2_sum = np.sum(s2[:, :, imethod]) / 2 print('S1: ', s1_sum) print('S2: ', s2_sum) print('ST: ', np.sum(sob.st[:, imethod])) sum = 0 sorted_param_indices = list(range(0, len(var_param_names))) sorted_param_indices = sorted(sorted_param_indices, key=lambda i : s1[i, imethod], reverse=True) selected_params = [] sobols = dict() for iparam, param in enumerate(sorted_param_indices) : # S1 sum += s1[param, imethod] param_name = var_param_names[param] selected_params.append(param_name) sobols[param_name] = s1[param, imethod] # S2 #for iparam2 in range(0, iparam) : # param2 = sorted_param_indices[iparam2] # sum += s2[param, param2, imethod] if sum > min_ratio : break print("Selected params : ", selected_params, "explains: ", sum) expr = exprs[imethod] # Replace extra fixed params extraFixedParams = [param for param in _param_registry().values() if param.name not in selected_params] expr = _replace_fixed_params(expr, extraFixedParams, fixed_mode=fixed_mode) # Sympy simplification expr = simplify(expr) # Round numerical values to 3 digits expr = _round_expr(expr, num_digits) # Lambdify the expression lambd = LambdaWithParamNames(expr, params=selected_params, sobols=sobols) # Compute list of parameter values (monte carlo) completed_params = lambd.complete_params(params) expanded_params = _filter_param_values(completed_params, lambd.expanded_params) # Extra step of simplification : simplify sums with neligeable terms if simple_sums : expr = _simplify_sums(expr, expanded_params) if simple_products: expr = _simplify_products(expr, expanded_params) simplified_expr = simplify(expr) display(simplified_expr) res.append(LambdaWithParamNames(simplified_expr, params=selected_params, sobols=sobols)) return res
Classes
class SobolResults (s1, s2, st, s1_conf=None, s2_conf=None, st_conf=None)
-
Expand source code
class SobolResults : def __init__(self, s1, s2, st, s1_conf=None, s2_conf=None, st_conf=None): self.s1 = s1 self.s2 = s2 self.st = st self.s1_conf = s1_conf self.s2_conf = s2_conf self.st_conf = st_conf
class StochasticMethod
-
Expand source code
class StochasticMethod : SALTELLI = "saltelli" RAND = "rand" SOBOL="sobol"
Class variables
var RAND
-
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
var SALTELLI
-
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
var SOBOL
-
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.