simdec 1.1.0__tar.gz → 1.2.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.
@@ -7,6 +7,9 @@ __pycache__
7
7
  .cache/*
8
8
  **/.ipynb_checkpoints
9
9
 
10
+ # Env variables with direnv
11
+ .envrc
12
+
10
13
  # OS generated files
11
14
  .DS_Store*
12
15
 
@@ -29,4 +32,6 @@ htmlcov/*
29
32
  build/*
30
33
  dist/*
31
34
  sdist/*
32
- docs/_build
35
+ docs/html
36
+ docs/jupyter_execute
37
+ app.html
@@ -161,3 +161,39 @@ a release:
161
161
  5. Check that release has been deployed to PyPI
162
162
  6. Check documentation is built and deployed to readthedocs
163
163
  7. Check that auto-generated PR is auto-merged on the conda-forge feedstock repo
164
+
165
+ ## Dashboard
166
+ A live dashboard is available at:
167
+
168
+ https://simdec.io
169
+
170
+ The DNS records are available on CPanel and the rest is hosted on Google Cloud
171
+ Platform.
172
+
173
+ Developing locally requires the installation of GCP CLI and Docker engine.
174
+
175
+ A few helper commands are provided in the Makefile.
176
+
177
+ ### Local use
178
+
179
+ The dashboard can be run locally using:
180
+
181
+ make serve-dev
182
+
183
+ If you want to test OAuth, you need to export the following env variables:
184
+
185
+ export PANEL_OAUTH_REDIRECT_URI=http://localhost:5006/app
186
+ export PANEL_OAUTH_KEY=[VALUE IN GCP Secret Manager]
187
+ export PANEL_OAUTH_SECRET=[VALUE IN GCP Secret Manager]
188
+ export PANEL_OAUTH_ENCRYPTION=[VALUE IN GCP Secret Manager]
189
+
190
+ Use the CLI tool `direnv` for convenience. Then you can serve with
191
+ OAuth support:
192
+
193
+ make serve-oauth
194
+
195
+ ### Deployment
196
+
197
+ New version can either be deployed from the CI with `cloudbuild` or locally:
198
+
199
+ make production
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: simdec
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Sensitivity analysis using simulation decomposition
5
5
  Project-URL: homepage, https://www.simdec.fi/
6
6
  Project-URL: documentation, https://simdec.readthedocs.io
@@ -16,22 +16,32 @@ Classifier: License :: OSI Approved :: BSD License
16
16
  Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
- Requires-Python: >=3.9
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Requires-Python: >=3.10
23
23
  Requires-Dist: numpy
24
24
  Requires-Dist: pandas
25
- Requires-Dist: panel
26
25
  Requires-Dist: salib
27
26
  Requires-Dist: seaborn
28
- Requires-Dist: typer[all]
27
+ Provides-Extra: dashboard
28
+ Requires-Dist: cryptography; extra == 'dashboard'
29
+ Requires-Dist: panel>=1.3.8; extra == 'dashboard'
29
30
  Provides-Extra: dev
31
+ Requires-Dist: accessible-pygments; extra == 'dev'
32
+ Requires-Dist: cryptography; extra == 'dev'
30
33
  Requires-Dist: hatch; extra == 'dev'
34
+ Requires-Dist: myst-nb; extra == 'dev'
35
+ Requires-Dist: numpydoc; extra == 'dev'
36
+ Requires-Dist: panel>=1.3.8; extra == 'dev'
31
37
  Requires-Dist: pre-commit; extra == 'dev'
32
- Requires-Dist: simdec[doc,test]; extra == 'dev'
38
+ Requires-Dist: pydata-sphinx-theme; extra == 'dev'
39
+ Requires-Dist: pytest; extra == 'dev'
40
+ Requires-Dist: pytest-cov; extra == 'dev'
41
+ Requires-Dist: sphinx; extra == 'dev'
33
42
  Provides-Extra: doc
34
43
  Requires-Dist: accessible-pygments; extra == 'doc'
44
+ Requires-Dist: myst-nb; extra == 'doc'
35
45
  Requires-Dist: numpydoc; extra == 'doc'
36
46
  Requires-Dist: pydata-sphinx-theme; extra == 'doc'
37
47
  Requires-Dist: sphinx; extra == 'doc'
@@ -69,30 +79,11 @@ The library is distributed on PyPi and can be installed with:
69
79
 
70
80
  pip install simdec
71
81
 
72
- To install the development version, download the source and from the root of
73
- the repository:
74
-
75
- pip install -e .[dev]
76
-
77
82
  ## Dashboard
78
83
  A live dashboard is available at:
79
84
 
80
85
  https://simdec.io
81
86
 
82
- ### Local use
83
-
84
- The dashboard can be run locally using:
85
-
86
- make serve
87
-
88
- ### Deployment
89
-
90
- The live version of the dashboard is hosted on GCP. To deploy a new version:
91
-
92
- PANEL_TOKEN=... make production
93
-
94
- ###
95
-
96
87
  ## Citations
97
88
 
98
89
  The algorithms and visualizations used in this package came primarily out of
@@ -103,13 +94,6 @@ Foundation, and Finnish Foundation for Economic Education.
103
94
  If you use SimDec in your research we would appreciate a citation to the
104
95
  following publications:
105
96
 
106
- - Kozlova, M., & Yeomans, J. S. (2022). Monte Carlo Enhancement via Simulation
107
- Decomposition: A “Must-Have” Inclusion for Many Disciplines. _INFORMS
108
- Transactions on Education, 22_(3), 147-159. DOI:10.1287/ited.2019.0240.
109
- - Kozlova, M., Moss, R. J., Yeomans, J. S., & Caers, J. (forthcoming).
110
- Uncovering Heterogeneous Effects in Computational Models for Sustainable
111
- Decision-making. _Environmental Modelling & Software_.
112
- - Kozlova, M., Moss, R. J., Roy, P., Alam, A., & Yeomans, J. S. (forthcoming).
113
- SimDec algorithm. In M. Kozlova & J. S. Yeomans (Eds.), _Sensitivity Analysis
114
- for Business, Technology, and Policymaking Made Easy with Simulation
115
- Decomposition_. Routledge.
97
+ - Kozlova, M., & Yeomans, J. S. (2022). Monte Carlo Enhancement via Simulation Decomposition: A “Must-Have” Inclusion for Many Disciplines. _INFORMS Transactions on Education, 22_(3), 147-159. [Available here](https://pubsonline.informs.org/doi/10.1287/ited.2019.0240).
98
+ - Kozlova, M., Moss, R. J., Yeomans, J. S., & Caers, J. (2024). Uncovering Heterogeneous Effects in Computational Models for Sustainable Decision-making. _Environmental Modelling & Software_, 171, 105898. [https://doi.org/10.1016/j.envsoft.2023.105898](https://doi.org/10.1016/j.envsoft.2023.105898)
99
+ - Kozlova, M., Moss, R. J., Roy, P., Alam, A., & Yeomans, J. S. (forthcoming). SimDec algorithm and guidelines for its usage and interpretation. In M. Kozlova & J. S. Yeomans (Eds.), _Sensitivity Analysis for Business, Technology, and Policymaking. Made Easy with Simulation Decomposition_. Routledge.
@@ -27,30 +27,11 @@ The library is distributed on PyPi and can be installed with:
27
27
 
28
28
  pip install simdec
29
29
 
30
- To install the development version, download the source and from the root of
31
- the repository:
32
-
33
- pip install -e .[dev]
34
-
35
30
  ## Dashboard
36
31
  A live dashboard is available at:
37
32
 
38
33
  https://simdec.io
39
34
 
40
- ### Local use
41
-
42
- The dashboard can be run locally using:
43
-
44
- make serve
45
-
46
- ### Deployment
47
-
48
- The live version of the dashboard is hosted on GCP. To deploy a new version:
49
-
50
- PANEL_TOKEN=... make production
51
-
52
- ###
53
-
54
35
  ## Citations
55
36
 
56
37
  The algorithms and visualizations used in this package came primarily out of
@@ -61,13 +42,6 @@ Foundation, and Finnish Foundation for Economic Education.
61
42
  If you use SimDec in your research we would appreciate a citation to the
62
43
  following publications:
63
44
 
64
- - Kozlova, M., & Yeomans, J. S. (2022). Monte Carlo Enhancement via Simulation
65
- Decomposition: A “Must-Have” Inclusion for Many Disciplines. _INFORMS
66
- Transactions on Education, 22_(3), 147-159. DOI:10.1287/ited.2019.0240.
67
- - Kozlova, M., Moss, R. J., Yeomans, J. S., & Caers, J. (forthcoming).
68
- Uncovering Heterogeneous Effects in Computational Models for Sustainable
69
- Decision-making. _Environmental Modelling & Software_.
70
- - Kozlova, M., Moss, R. J., Roy, P., Alam, A., & Yeomans, J. S. (forthcoming).
71
- SimDec algorithm. In M. Kozlova & J. S. Yeomans (Eds.), _Sensitivity Analysis
72
- for Business, Technology, and Policymaking Made Easy with Simulation
73
- Decomposition_. Routledge.
45
+ - Kozlova, M., & Yeomans, J. S. (2022). Monte Carlo Enhancement via Simulation Decomposition: A “Must-Have” Inclusion for Many Disciplines. _INFORMS Transactions on Education, 22_(3), 147-159. [Available here](https://pubsonline.informs.org/doi/10.1287/ited.2019.0240).
46
+ - Kozlova, M., Moss, R. J., Yeomans, J. S., & Caers, J. (2024). Uncovering Heterogeneous Effects in Computational Models for Sustainable Decision-making. _Environmental Modelling & Software_, 171, 105898. [https://doi.org/10.1016/j.envsoft.2023.105898](https://doi.org/10.1016/j.envsoft.2023.105898)
47
+ - Kozlova, M., Moss, R. J., Roy, P., Alam, A., & Yeomans, J. S. (forthcoming). SimDec algorithm and guidelines for its usage and interpretation. In M. Kozlova & J. S. Yeomans (Eds.), _Sensitivity Analysis for Business, Technology, and Policymaking. Made Easy with Simulation Decomposition_. Routledge.
@@ -0,0 +1,115 @@
1
+ @article{Herman2017,
2
+ doi = {10.21105/joss.00097},
3
+ year = {2017},
4
+ month = {jan},
5
+ publisher = {The Open Journal},
6
+ volume = {2},
7
+ number = {9},
8
+ author = {Jon Herman and Will Usher},
9
+ title = {{SALib}: An open-source {Python} library for Sensitivity Analysis},
10
+ journal = {The Journal of Open Source Software}
11
+ }
12
+
13
+ @article{Kozlova2024,
14
+ title = {Uncovering heterogeneous effects in computational models for sustainable decision-making},
15
+ journal = {Environmental Modelling & Software},
16
+ volume = {171},
17
+ pages = {105898},
18
+ year = {2024},
19
+ issn = {1364-8152},
20
+ doi = {10.1016/j.envsoft.2023.105898},
21
+ author = {Mariia Kozlova and Robert J. Moss and Julian Scott Yeomans and Jef Caers},
22
+ keywords = {Global sensitivity analysis, Simulation decomposition, Monte Carlo simulation, Decision-making under uncertainty}
23
+ }
24
+
25
+ @article{Roy2023,
26
+ doi = {10.21105/joss.05309},
27
+ year = {2023},
28
+ publisher = {The Open Journal},
29
+ volume = {8},
30
+ number = {84},
31
+ pages = {5309},
32
+ author = {Pamphile T. Roy and Art B. Owen and Maximilian Balandat and Matt Haberland},
33
+ title = {Quasi-Monte Carlo Methods in {Python}},
34
+ journal = {Journal of Open Source Software}
35
+ }
36
+
37
+ @article{Virtanen2020,
38
+ title={SciPy 1.0: fundamental algorithms for scientific computing in {P}ython},
39
+ author={Virtanen, Pauli and Gommers, Ralf and Oliphant, Travis E and Haberland, Matt and Reddy, Tyler and Cournapeau, David and Burovski, Evgeni and Peterson, Pearu and Weckesser, Warren and Bright, Jonathan et al.},
40
+ journal={Nature methods},
41
+ volume={17},
42
+ number={3},
43
+ pages={261--272},
44
+ year={2020},
45
+ publisher={Nature Publishing Group},
46
+ doi={10.1038/s41592-019-0686-2}
47
+ }
48
+
49
+ @book{Saltelli2007,
50
+ author = {Saltelli, Andrea and Ratto, Marco and Andres, Terry and Campolongo, Francesca and Cariboni, Jessica and Gatelli, Debora and Saisana, Michaela and Tarantola, Stefano},
51
+ booktitle = {Global Sensitivity Analysis. The Primer},
52
+ doi = {10.1002/9780470725184},
53
+ isbn = {9780470725184},
54
+ month = {dec},
55
+ pages = {237--275},
56
+ publisher = {John Wiley {\&} Sons, Ltd},
57
+ title = {{Global Sensitivity Analysis. The Primer}},
58
+ year = {2007}
59
+ }
60
+
61
+ @article{sobol1993,
62
+ title={Sensitivity analysis for non-linear mathematical models, originally “Sensitivity estimates for non-linear mathematical models”},
63
+ author={Sobol, Ilya M},
64
+ journal={Math Model Comput Exp},
65
+ volume={1},
66
+ pages={407--414},
67
+ year={1993}
68
+ }
69
+
70
+ @report{europeancommission2021,
71
+ title = {Better {{Regulation Toolbox}}},
72
+ author = {{European Commission}},
73
+ date = {2021-11},
74
+ url = {https://ec.europa.eu/info/law/law-making-process/planning-and-proposing-law/better-regulation-why-and-how/better-regulation-guidelines-and-toolbox_en},
75
+ city = {Brussels},
76
+ keywords = {\#nosource}
77
+ }
78
+
79
+ @software{panel,
80
+ author = {Philipp Rudiger and
81
+ Marc Skov Madsen and
82
+ Simon Høxbro Hansen and
83
+ Maxime Liquet and
84
+ Andrew and
85
+ Xavier Artusi and
86
+ James A. Bednar and
87
+ Chris B and
88
+ Jean-Luc Stevens and
89
+ Christoph Deil and
90
+ Demetris Roumis and
91
+ Julia Signell and
92
+ Mateusz Paprocki and
93
+ Jerry Wu and
94
+ Jon Mease and
95
+ Arne and
96
+ Coderambling and
97
+ Hugues-Yanis Amanieu and
98
+ thuydotm and
99
+ Simon and
100
+ sdc50 and
101
+ Luca Fabbri and
102
+ kbowen and
103
+ Theom and
104
+ Joel Ostblom and
105
+ Govinda Totla and
106
+ Niko Föhr and
107
+ TBym},
108
+ title = {holoviz/panel: Version 1.4.3},
109
+ month = may,
110
+ year = 2024,
111
+ publisher = {Zenodo},
112
+ version = {v1.4.3},
113
+ doi = {10.5281/zenodo.11261266},
114
+ url = {https://doi.org/10.5281/zenodo.11261266}
115
+ }
@@ -0,0 +1,76 @@
1
+ ---
2
+ title: 'Simulation Decomposition in Python'
3
+ tags:
4
+ - Python
5
+ - SimDec
6
+ - statistics
7
+ - Sensitivity Analysis
8
+ - Visualization
9
+ authors:
10
+ - name: Pamphile T. Roy
11
+ affiliation: 1
12
+ corresponding: true
13
+ orcid: 0000-0001-9816-1416
14
+ - name: Mariia Kozlova
15
+ affiliation: 2
16
+ orcid: 0000-0002-6952-7682
17
+ affiliations:
18
+ - name: Consulting Manao GmbH, Vienna, Austria
19
+ index: 1
20
+ - name: LUT Business School, LUT University, Lappeenranta, Finland
21
+ index: 2
22
+ date: 1 April 2024
23
+ bibliography: paper.bib
24
+
25
+ ---
26
+
27
+ # Summary
28
+
29
+ Uncertainties are everywhere. Whether you are developing a new Artificial Intelligence (AI) system,
30
+ running complex simulations or making an experiment in a lab, uncertainties
31
+ influence the system. Therefore, an approach is needed to understand how these uncertainties impact the system's performance.
32
+
33
+ SimDec offers a novel visual way to understand the intricate role that
34
+ uncertainties play. A clear Python Application Programming Interface (API) and a no-code interactive web
35
+ dashboard make uncertainty analysis with SimDec accessible to everyone.
36
+
37
+ # Statement of need
38
+
39
+ From real life experiments to numerical simulations, uncertainties play a
40
+ crucial role in the system under study. With the advent of AI
41
+ and new regulations such as the [AI Act](https://artificialintelligenceact.eu) or the
42
+ *Better Regulation Guideline* [@europeancommission2021], there is a growing need for explainability and
43
+ impact assessments of systems under uncertainties.
44
+
45
+ Traditional methods to analyse the uncertainties focus on quantitative methods
46
+ to compare the importance of factors, there is a large body of literature and
47
+ the field is known as: Sensitivity Analysis (SA) [@Saltelli2007]. The indices of Sobol' are a
48
+ prominent example of such methods [@sobol1993].
49
+
50
+ Simulation Decomposition or SimDec moves the field of SA forward by supplementing the computation of sensitivity indices with the visualization of the type of interactions involved, which proves critical for understanding the system's behavior and decision-making [@Kozlova2024].
51
+ In short, SimDec is a hybrid uncertainty-sensitivity analysis approach
52
+ that reveals the critical behavior of a computational model or an empirical
53
+ dataset. It decomposes the distribution of the output
54
+ (target variable) by automatically forming scenarios that reveal the most critical behavior of the system. The scenarios are formed out of the most influential input variables (defined with variance-based sensitivity indices) by breaking down their numeric ranges into states (e.g. _low_ and _high_) and creating an exhaustive list of their combinations (e.g. (i) _low_ _**A**_ & _low_ _**B**_, (ii) _low_ _**A**_ & _high_ _**B**_, (iii) _high_ **_A_** & _low_ **_B_**, and (iv) _high_ **_A_** and _high_ **_B_**). The resulting visualization shows how different
55
+ output ranges can be achieved and what kind of critical interactions affect
56
+ the output—as seen in \autoref{fig:simdec}. The method has shown value for
57
+ various computational models from different fields, including business,
58
+ environment, and engineering, as well as an emerging evidence of use for
59
+ empirical data and AI.
60
+
61
+ ![SimDec: explanation of output by most important inputs. A simulation dataset of a structural reliability model with one key output variable and four input variables is used for this case. Inputs 3 and 1 have the highest sensitivity indices and thus are automatically chosen for decomposition. The most influential input 3 divides the distribution of the output into three main states with distinct colors. Input 1 further subdivides them into shades. From the graph, it becomes obvious that input 1 influences the output when input 3 is low, but has a negligible effect when input 3 is medium or high.\label{fig:simdec}](simdec_presentation.png)
62
+
63
+ Besides proposing a comprehensive yet simple API through a Python package
64
+ available on PyPi, SimDec is also made available
65
+ to practitioners through an online dashboard at [https://simdec.io](https://simdec.io). The project
66
+ relies on powerful variance-based sensitivity analysis methods from SALib [@Herman2017] and
67
+ SciPy [@Virtanen2020; @Roy2023]—notably the Quasi-Monte Carlo capabilities with
68
+ `sp.stats.qmc` and in the future sensitivity indices with `sp.stats.sensitivity_indices`.
69
+ The dashboard is made possible thanks to Panel [@panel].
70
+
71
+ # Acknowledgements
72
+
73
+ The work on this open-source software was supported by grant #220177 from
74
+ Finnish Foundation for Economic Foundation.
75
+
76
+ # References
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "simdec"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  description = "Sensitivity analysis using simulation decomposition"
9
9
  readme = "README.md"
10
- requires-python = ">=3.9"
10
+ requires-python = ">=3.10"
11
11
  license = "BSD-3-Clause"
12
12
  authors = [
13
13
  { name = "Pamphile Roy" },
@@ -23,9 +23,9 @@ classifiers = [
23
23
  "Operating System :: OS Independent",
24
24
  "Programming Language :: Python",
25
25
  "Programming Language :: Python :: 3",
26
- "Programming Language :: Python :: 3.9",
27
26
  "Programming Language :: Python :: 3.10",
28
27
  "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
29
  ]
30
30
 
31
31
  dependencies = [
@@ -33,11 +33,14 @@ dependencies = [
33
33
  "pandas",
34
34
  "SALib",
35
35
  "seaborn",
36
- "typer[all]",
37
- "panel",
38
36
  ]
39
37
 
40
38
  [project.optional-dependencies]
39
+ dashboard = [
40
+ "panel>=1.3.8",
41
+ "cryptography",
42
+ ]
43
+
41
44
  test = [
42
45
  "pytest",
43
46
  "pytest-cov",
@@ -48,32 +51,37 @@ doc = [
48
51
  "pydata-sphinx-theme",
49
52
  "accessible-pygments",
50
53
  "numpydoc",
54
+ "myst-nb",
51
55
  ]
52
56
 
53
57
  dev = [
54
- "simdec[doc,test]",
58
+ "simdec[doc,test,dashboard]",
55
59
  "pre-commit",
56
60
  "hatch",
57
61
  ]
58
62
 
59
- [project.scripts]
60
- simdec = "simdec.workflow:app"
61
-
62
63
  [project.urls]
63
64
  homepage = "https://www.simdec.fi/"
64
65
  documentation = "https://simdec.readthedocs.io"
65
66
  source = "https://github.com/Simulation-Decomposition/simdec-python"
66
67
 
67
68
  [tool.hatch]
69
+ build.targets.wheel.packages = ["src/simdec"]
68
70
  build.targets.sdist.exclude = [
69
71
  ".github",
70
72
  "docs",
73
+ "panel",
71
74
  "tests",
72
75
  "*.rst",
73
76
  "*.yml",
74
77
  ".*",
78
+ "Makefile",
79
+ "Dockerfile",
75
80
  ]
76
81
 
82
+ [project.entry-points."panel.auth"]
83
+ custom_google = "simdec.auth:CustomGoogleLoginHandler"
84
+
77
85
  [tool.pytest.ini_options]
78
86
  addopts = "--durations 10"
79
87
  testpaths = [
@@ -0,0 +1,17 @@
1
+ from panel.auth import GoogleLoginHandler
2
+ from panel.io.resources import CDN_DIST
3
+
4
+
5
+ class CustomGoogleLoginHandler(GoogleLoginHandler):
6
+ def _simple_get(self):
7
+ html = self._login_template.render(errormessage="", PANEL_CDN=CDN_DIST)
8
+ self.write(html)
9
+
10
+ async def get(self):
11
+ if "login" in self.request.uri and "state" not in self.request.uri:
12
+ self._simple_get()
13
+ else:
14
+ await super().get()
15
+
16
+ async def post(self):
17
+ await super().get()
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
+ from hashlib import blake2b
4
5
  from typing import Literal
5
6
 
6
7
  import numpy as np
@@ -14,13 +15,15 @@ __all__ = ["decomposition", "states_expansion"]
14
15
  def states_expansion(states: list[int], inputs: pd.DataFrame) -> list[list[str]]:
15
16
  """Expand states list to fully represent all scenarios."""
16
17
  inputs = pd.DataFrame(inputs)
17
- for i, state in enumerate(states):
18
+ expanded_states = []
19
+ for state in states:
18
20
  if isinstance(state, int):
19
- states: list
20
21
  if state == 2:
21
- states[i] = ["low", "high"]
22
+ expanded_states.append(["low", "high"])
22
23
  elif state == 3:
23
- states[i] = ["low", "medium", "high"]
24
+ expanded_states.append(["low", "medium", "high"])
25
+ else:
26
+ expanded_states.append(state)
24
27
 
25
28
  # categorical for a given variable
26
29
  cat_cols = inputs.select_dtypes(exclude=["number"])
@@ -33,9 +36,9 @@ def states_expansion(states: list[int], inputs: pd.DataFrame) -> list[list[str]]
33
36
 
34
37
  for i, states_cat_ in zip(cat_cols_idx, states_cats_):
35
38
  n_unique = np.unique(inputs.iloc[:, i]).size
36
- states[i] = list(states_cat_) if n_unique < 5 else states[i]
39
+ expanded_states[i] = list(states_cat_) if n_unique < 5 else expanded_states[i]
37
40
 
38
- return states
41
+ return expanded_states
39
42
 
40
43
 
41
44
  @dataclass
@@ -46,6 +49,17 @@ class DecompositionResult:
46
49
  states: list[int]
47
50
  bin_edges: np.ndarray
48
51
 
52
+ def __reduce__(self):
53
+ h = blake2b(key=b"result hashing", digest_size=20)
54
+
55
+ h.update(str(self.var_names).encode())
56
+ h.update(str(self.statistic).encode())
57
+ h.update(str(self.bins).encode())
58
+ h.update(str(self.states).encode())
59
+ h.update(str(self.bin_edges).encode())
60
+
61
+ return [h.hexdigest()]
62
+
49
63
 
50
64
  def decomposition(
51
65
  inputs: pd.DataFrame,
@@ -95,11 +109,9 @@ def decomposition(
95
109
  var_names = inputs.columns
96
110
 
97
111
  cat_cols = inputs.select_dtypes(exclude=["number"])
98
- cat_cols_idx = []
99
112
  for cat_col in cat_cols:
100
113
  codes, cat_states_ = pd.factorize(inputs[cat_col])
101
114
  inputs[cat_col] = codes
102
- cat_cols_idx.append(inputs.columns.get_loc(cat_col))
103
115
 
104
116
  inputs = inputs.to_numpy()
105
117
  output = output.to_numpy()
@@ -109,19 +121,22 @@ def decomposition(
109
121
 
110
122
  # only keep the explained variance corresponding to `dec_limit`
111
123
  sensitivity_indices = sensitivity_indices[var_order]
112
- n_var_dec = np.where(np.cumsum(sensitivity_indices) < dec_limit)[0].size
113
- n_var_dec = max(1, n_var_dec) # keep at least one variable
114
- n_var_dec = min(5, n_var_dec) # use at most 5 variables
124
+
125
+ if auto_ordering:
126
+ n_var_dec = np.where(np.cumsum(sensitivity_indices) < dec_limit)[0].size
127
+ n_var_dec = max(1, n_var_dec) # keep at least one variable
128
+ n_var_dec = min(5, n_var_dec) # use at most 5 variables
129
+ else:
130
+ n_var_dec = inputs.shape[1]
115
131
 
116
132
  # 2. states formation
117
133
  if states is None:
118
134
  states = 3 if n_var_dec < 3 else 2
119
135
  states = [states] * n_var_dec
120
136
 
121
- # categorical for a given variable
122
- for i in cat_cols_idx:
137
+ for i in range(n_var_dec):
123
138
  n_unique = np.unique(inputs[:, i]).size
124
- states[i] = n_unique if n_unique < 5 else states[i]
139
+ states[i] = n_unique if n_unique <= 5 else states[i]
125
140
 
126
141
  if auto_ordering:
127
142
  var_names = var_names[var_order[:n_var_dec]].tolist()
@@ -145,8 +160,20 @@ def decomposition(
145
160
  bins.append(inputs)
146
161
  return statistic_method(inputs)
147
162
 
163
+ # make bins with equal number of samples for a given dimension
164
+ # sort and then split in n-state
165
+ sorted_inputs = np.sort(inputs, axis=0)
166
+ bin_edges = []
167
+ for i, states_ in enumerate(states):
168
+ splits = np.array_split(sorted_inputs[:, i], states_)
169
+ bin_edges_ = [splits_[0] for splits_ in splits]
170
+ bin_edges_.append(splits[-1][-1]) # last point to close the edges
171
+ # bin_edges_ = np.unique(bin_edges_) # remove duplicate points, sorted
172
+ bin_edges_ += 1e-10 * np.linspace(0, 1, len(bin_edges_))
173
+ bin_edges.append(bin_edges_)
174
+
148
175
  res = stats.binned_statistic_dd(
149
- inputs, values=output, statistic=statistic_, bins=states
176
+ inputs, values=output, statistic=statistic_, bins=bin_edges
150
177
  )
151
178
 
152
179
  bins = pd.DataFrame(bins[1:]).T
@@ -1,6 +1,9 @@
1
+ import copy
2
+ import functools
1
3
  import itertools
2
4
  from typing import Literal
3
5
 
6
+ import colorsys
4
7
  import matplotlib as mpl
5
8
  import matplotlib.pyplot as plt
6
9
  import numpy as np
@@ -11,7 +14,12 @@ from pandas.io.formats.style import Styler
11
14
  __all__ = ["visualization", "tableau", "palette"]
12
15
 
13
16
 
14
- sequential_palettes = [
17
+ SEQUENTIAL_PALETTES = [
18
+ "#DC267F",
19
+ "#E8EA2F",
20
+ "#26DCD1",
21
+ "#C552E4",
22
+ "#3F45D0",
15
23
  "Oranges",
16
24
  "Purples",
17
25
  "Reds",
@@ -33,7 +41,58 @@ sequential_palettes = [
33
41
  ]
34
42
 
35
43
 
36
- def palette(states: list[int]) -> list[list[float]]:
44
+ @functools.cache
45
+ def sequential_cmaps():
46
+ cmaps = []
47
+ for cmap in SEQUENTIAL_PALETTES:
48
+ try:
49
+ cmap_ = mpl.colormaps[cmap]
50
+ except KeyError:
51
+ color = mpl.colors.hex2color(cmap)
52
+ cmap_ = single_color_to_colormap(color)
53
+ cmaps.append(cmap_)
54
+ return cmaps
55
+
56
+
57
+ def single_color_to_colormap(
58
+ rgba_color: list[float] | str, *, factor: float = 0.5
59
+ ) -> mpl.colors.LinearSegmentedColormap:
60
+ """Create a linear colormap using a single color."""
61
+ if isinstance(rgba_color, str):
62
+ rgba_color = mpl.colors.hex2color(rgba_color)
63
+ # discard alpha channel
64
+ if len(rgba_color) == 4:
65
+ *rgb_color, alpha = rgba_color
66
+ else:
67
+ alpha = 1.0
68
+ rgb_color = rgba_color
69
+ rgba_color = list(rgba_color) + [1]
70
+
71
+ # lighten and darken from factor around single color
72
+ hls_color = colorsys.rgb_to_hls(*rgb_color)
73
+
74
+ lightness = hls_color[1]
75
+ lightened_hls_color = (hls_color[0], lightness * (1 + factor), hls_color[2])
76
+ lightened_rgb_color = list(colorsys.hls_to_rgb(*lightened_hls_color))
77
+
78
+ darkened_hls_color = (hls_color[0], lightness * (1 - factor), hls_color[2])
79
+ darkened_rgb_color = list(colorsys.hls_to_rgb(*darkened_hls_color))
80
+
81
+ lightened_rgba_color = lightened_rgb_color + [alpha]
82
+ darkened_rgba_color = darkened_rgb_color + [alpha]
83
+
84
+ # convert to CMAP
85
+ cmap = mpl.colors.LinearSegmentedColormap.from_list(
86
+ "CustomSingleColor",
87
+ [lightened_rgba_color, rgba_color, darkened_rgba_color],
88
+ N=3,
89
+ )
90
+ return cmap
91
+
92
+
93
+ def palette(
94
+ states: list[int], cmaps: list[mpl.colors.LinearSegmentedColormap] = None
95
+ ) -> list[list[float]]:
37
96
  """Colour palette.
38
97
 
39
98
  The product of the states gives the number of scenarios. For each
@@ -43,20 +102,32 @@ def palette(states: list[int]) -> list[list[float]]:
43
102
  ----------
44
103
  states : list of int
45
104
  List of possible states for the considered parameter.
46
-
105
+ cmaps : list of LinearSegmentedColormap
106
+ List of colormaps. Must have the same number of colormaps as the number
107
+ of first level of states.
47
108
  Returns
48
109
  -------
49
- palette : list of int of size (n, 4)
50
- List of colours corresponding to scenarios.
110
+ palette : list of float of size (n, 4)
111
+ List of colors corresponding to scenarios. RGBA formatted.
51
112
  """
113
+ n_cmaps = states[0]
114
+ if cmaps is None:
115
+ cmaps = sequential_cmaps()[:n_cmaps]
116
+ else:
117
+ cmaps = cmaps[:n_cmaps]
118
+ if len(cmaps) != n_cmaps:
119
+ raise ValueError(
120
+ f"Must have the same number of cmaps ({len(cmaps)}) as the "
121
+ f"number of first states ({n_cmaps})"
122
+ )
123
+
52
124
  colors = []
53
125
  # one palette per first level state, could use more palette when there are
54
126
  # many levels
55
127
  n_shades = int(np.prod(states[1:]))
56
- for i in range(states[0]):
57
- palette_ = sequential_palettes[i]
58
- cmap = mpl.colormaps[palette_].resampled(n_shades + 1)
59
- colors.append(cmap(range(1, n_shades + 1)))
128
+ for i in range(n_cmaps):
129
+ cmap = cmaps[i].resampled(n_shades)
130
+ colors.append(cmap(np.linspace(0, 1, n_shades)))
60
131
 
61
132
  return np.concatenate(colors).tolist()
62
133
 
@@ -108,7 +179,7 @@ def visualization(
108
179
  elif kind == "boxplot":
109
180
  ax = sns.boxplot(
110
181
  bins,
111
- palette=palette[::-1],
182
+ palette=palette,
112
183
  orient="h",
113
184
  order=list(bins.columns)[::-1],
114
185
  ax=ax,
@@ -132,6 +203,8 @@ def tableau(
132
203
  ----------
133
204
  var_names : list of str
134
205
  Variables name.
206
+ statistic : ndarray of shape (n_factors, 1)
207
+ Statistic in each bin.
135
208
  states : list of int or list of str
136
209
  For each variable, number of states. Can either be a scalar or a list.
137
210
 
@@ -155,16 +228,17 @@ def tableau(
155
228
  table.rename(columns={"index": "colour"}, inplace=True)
156
229
 
157
230
  # Default states for 2 or 3
158
- for i, state in enumerate(states):
231
+ states_ = copy.deepcopy(states)
232
+ for i, state in enumerate(states_):
159
233
  if isinstance(state, int):
160
- states: list
234
+ states_: list
161
235
  if state == 2:
162
- states[i] = ["low", "high"]
236
+ states_[i] = ["low", "high"]
163
237
  elif state == 3:
164
- states[i] = ["low", "medium", "high"]
238
+ states_[i] = ["low", "medium", "high"]
165
239
 
166
240
  # get the list of states
167
- gen_states = [range(x) if isinstance(x, int) else x for x in states]
241
+ gen_states = [range(x) if isinstance(x, int) else x for x in states_]
168
242
  states_ = np.asarray(list(itertools.product(*gen_states)))
169
243
  for i, var_name in enumerate(var_names):
170
244
  table.insert(loc=i + 1, column=var_name, value=states_[:, i])
@@ -180,10 +254,10 @@ def tableau(
180
254
  # only select/ordering interesting columns
181
255
  table = table[["colour", "std", "min", "mean", "max", "probability"]]
182
256
 
183
- table.insert(loc=0, column="N°", value=np.arange(1, stop=len(table) + 1))
257
+ table.insert(loc=0, column="N°", value=np.arange(1, stop=len(table) + 1)[::-1])
184
258
 
185
259
  # style the colour background with palette
186
- cmap = mpl.colors.ListedColormap(palette[::-1])
260
+ cmap = mpl.colors.ListedColormap(palette)
187
261
  styler = table.style
188
262
  styler.format(precision=2)
189
263
  styler.background_gradient(subset=["colour"], cmap=cmap)
simdec-1.1.0/Dockerfile DELETED
@@ -1,47 +0,0 @@
1
- ## Step 1/3: Base image
2
- FROM python:3.11-slim-bookworm as base
3
- RUN \
4
- --mount=type=cache,target=/var/cache/apt \
5
- apt-get update && apt-get upgrade -y && apt install curl -y
6
-
7
- ## Step 2/3: Build/Install app dependencies
8
- FROM base as builder
9
- # Allow statements and log messages to immediately appear in the Knative logs
10
- ENV PYTHONUNBUFFERED True
11
-
12
- COPY pyproject.toml README.md /
13
-
14
- RUN --mount=type=cache,target=/var/cache/apt apt install -y gcc git
15
- RUN --mount=type=cache,mode=0777,target=/root/.cache/pip \
16
- pip install --upgrade pip setuptools wheel && \
17
- pip install -e . && \
18
- mkdir -p /app/src
19
-
20
- COPY tests/data /app/tests/data
21
- COPY src/simdec /app/src/simdec
22
- COPY app.py /app/app.py
23
- # matplotlib cache
24
- RUN mkdir -p /app/.config/matplotlib
25
-
26
- # Step 3/3: Image
27
- FROM base as panel
28
- # Allow statements and log messages to immediately appear in the Knative logs
29
- ENV PYTHONUNBUFFERED True
30
- ENV PYTHONPATH=/app/src
31
- ENV PYTHONIOENCODING=utf-8
32
- ENV ENV=production
33
- ENV PORT=8080
34
-
35
- ARG PANEL_TOKEN
36
-
37
- COPY --from=builder /usr/local/lib/python3.11/site-packages/ /usr/local/lib/python3.11/site-packages/
38
- COPY --from=builder /usr/local/bin/panel /usr/local/bin/panel
39
- COPY --from=builder /app/src /app/src
40
- COPY --from=builder /app/tests/data /app/tests/data
41
- COPY --from=builder /app/app.py /app/app.py
42
-
43
- WORKDIR /app
44
- RUN useradd app && usermod -a -G app app
45
- USER app
46
- # Run the web service on container startup.
47
- CMD panel serve app.py --address 0.0.0.0 --port $PORT --allow-websocket-origin="*" --basic-auth=$PANEL_TOKEN --num-procs 2 --cookie-secret my_super_safe_cookie_secret
simdec-1.1.0/Makefile DELETED
@@ -1,95 +0,0 @@
1
- .PHONY: help prepare doc test serve build publish-production
2
- .DEFAULT_GOAL := help
3
- SHELL:=/bin/bash
4
-
5
- ifndef version
6
- override version = development
7
- endif
8
-
9
- ifndef region
10
- override region = europe-north1
11
- endif
12
-
13
- ifndef project
14
- override project = delta-entity-401706
15
- endif
16
-
17
- ifndef PANEL_TOKEN
18
- # only for local dev, another token is used for deployment
19
- override PANEL_TOKEN = e41ea4c145bf13a60c8779c24356
20
- endif
21
-
22
-
23
- # Add help text after each target name starting with '\#\#'
24
- help: ## show this help
25
- @echo -e "Help for this makefile\n"
26
- @echo "Possible commands are:"
27
- @grep -h "##" $(MAKEFILE_LIST) | grep -v grep | sed -e 's/\(.*\):.*##\(.*\)/ \1: \2/'
28
-
29
- prepare: ## Install dependencies and pre-commit hook
30
- pip install -e ".[dev]"
31
- pre-commit install
32
- gcloud init
33
- gcloud auth configure-docker
34
-
35
- doc: ## Build Sphinx documentation
36
- sphinx-build -b html docs docs/html
37
-
38
- test: ## Run tests with coverage
39
- pytest --cov simdec --cov-report term-missing
40
-
41
- serve-dev: ## Serve Panel dashboard - Dev mode
42
- panel serve app.py --show --autoreload
43
-
44
- serve: ## Serve Panel dashboard - Prod mode
45
- panel serve app.py
46
-
47
- build-local:
48
- docker build -f ./Dockerfile \
49
- --build-arg PANEL_TOKEN=$(PANEL_TOKEN) \
50
- --tag simdec-panel:$(version) \
51
- --pull \
52
- ./.
53
-
54
- run-local: build-local
55
- docker run --rm -it \
56
- --name=simdec-panel \
57
- --memory=1g \
58
- --cpuset-cpus=0 \
59
- -e ENV=development \
60
- -p "8080:8080" \
61
- simdec-panel:$(version)
62
-
63
- build:
64
- docker build -f ./Dockerfile \
65
- --platform linux/amd64 \
66
- --build-arg PANEL_TOKEN=$(PANEL_TOKEN) \
67
- --tag simdec-panel:$(version) \
68
- --pull \
69
- ./.
70
-
71
- run: build
72
- docker run --rm -it \
73
- --name=simdec-panel \
74
- --memory=1g \
75
- --cpuset-cpus=0 \
76
- -e ENV=development \
77
- -p "8080:8080" \
78
- simdec-panel:$(version)
79
-
80
- publish-production: build
81
- docker tag simdec-panel:$(version) $(region)-docker.pkg.dev/$(project)/simdec-panel/simdec-panel:$(version)
82
- docker push $(region)-docker.pkg.dev/$(project)/simdec-panel/simdec-panel:$(version)
83
-
84
- production: publish-production
85
- @echo "Deploying '$(version)'."
86
- gcloud run deploy simdec-panel \
87
- --concurrency=10 \
88
- --max-instances=1 \
89
- --region=$(region) \
90
- --port=8080 \
91
- --set-env-vars ENV=production \
92
- --allow-unauthenticated \
93
- --timeout=600 \
94
- --image=$(region)-docker.pkg.dev/$(project)/simdec-panel/simdec-panel:$(version) \
95
- --memory 1Gi
simdec-1.1.0/app.py DELETED
@@ -1,358 +0,0 @@
1
- import bisect
2
- import io
3
-
4
- from bokeh.models import PrintfTickFormatter
5
- from bokeh.models.widgets.tables import NumberFormatter
6
- import matplotlib.pyplot as plt
7
- import numpy as np
8
- import pandas as pd
9
- import panel as pn
10
-
11
- import simdec as sd
12
-
13
-
14
- # panel app
15
- pn.extension(template="material")
16
- pn.extension("tabulator")
17
-
18
- pn.config.sizing_mode = "stretch_width"
19
- pn.config.throttled = True
20
- font_size = "12pt"
21
-
22
-
23
- @pn.cache
24
- def load_data(text_fname):
25
- if text_fname is None:
26
- text_fname = "tests/data/stress.csv"
27
- else:
28
- text_fname = io.BytesIO(text_fname)
29
-
30
- data = pd.read_csv(text_fname)
31
- pn.state.clear_caches()
32
- return data
33
-
34
-
35
- @pn.cache
36
- def column_inputs(data, output):
37
- inputs = list(data.columns)
38
- inputs.remove(output)
39
- return inputs
40
-
41
-
42
- @pn.cache
43
- def filtered_inputs(data, input_names):
44
- return data[input_names]
45
-
46
-
47
- @pn.cache
48
- def column_output(data):
49
- return list(data.columns)
50
-
51
-
52
- @pn.cache
53
- def filtered_output(data, output_name):
54
- return data[output_name]
55
-
56
-
57
- @pn.cache
58
- def sensitivity_indices(inputs, output):
59
- sensitivity_indices = sd.sensitivity_indices(inputs=inputs, output=output)
60
- if 0.01 < sum(sensitivity_indices.si) < 2.0:
61
- indices = sensitivity_indices.si
62
- else:
63
- indices = sensitivity_indices.first_order
64
- return indices
65
-
66
-
67
- def sensitivity_indices_table(si, inputs):
68
- var_names = inputs.columns
69
- var_order = np.argsort(si)[::-1]
70
- var_names = var_names[var_order].tolist()
71
-
72
- si = list(si[var_order])
73
- sum_si = sum(si)
74
-
75
- var_names.append("Sum of Indices")
76
- si_numerics = si.copy()
77
- si.append(0)
78
- si_numerics.append(sum_si)
79
-
80
- d = {"Inputs": var_names, "Indices": si, "": si_numerics}
81
- df = pd.DataFrame(data=d)
82
- formatters = {
83
- "Indices": {"type": "progress", "max": sum_si, "color": "#007eff"},
84
- "": NumberFormatter(format="0.00"),
85
- }
86
- widget = pn.widgets.Tabulator(
87
- df,
88
- show_index=False,
89
- formatters=formatters,
90
- theme="bulma",
91
- )
92
- widget.style.apply(
93
- lambda x: ["font-style: italic"] * 3, axis=1, subset=df.index[-1]
94
- )
95
- widget.style.apply(lambda x: ["font-size: 11pt"] * len(si))
96
- return widget
97
-
98
-
99
- @pn.cache
100
- def explained_variance(si):
101
- return sum(si) + np.finfo(np.float64).eps
102
-
103
-
104
- def filtered_si(sensitivity_indices_table, input_names):
105
- df = sensitivity_indices_table.value
106
- si = []
107
- for input_name in input_names:
108
- si.append(df.loc[df["Inputs"] == input_name, "Indices"])
109
- return np.asarray(si).flatten()
110
-
111
-
112
- def explained_variance_80(sensitivity_indices_table):
113
- si = sensitivity_indices_table.value["Indices"]
114
- pos_80 = bisect.bisect_right(np.cumsum(si), 0.8)
115
-
116
- # pos_80 = max(2, pos_80)
117
- # pos_80 = min(len(si), pos_80)
118
-
119
- input_names = sensitivity_indices_table.value["Inputs"]
120
- return input_names.to_list()[: pos_80 + 1]
121
-
122
-
123
- def decomposition(dec_limit, si, inputs, output):
124
- return sd.decomposition(
125
- inputs=inputs,
126
- output=output,
127
- sensitivity_indices=si,
128
- dec_limit=dec_limit,
129
- auto_ordering=False,
130
- )
131
-
132
-
133
- def palette(res):
134
- return sd.palette(res.states)
135
-
136
-
137
- def n_bins_auto(res):
138
- min_ = np.nanmin(res.bins)
139
- max_ = np.nanmax(res.bins)
140
- return len(np.histogram_bin_edges(res.bins, bins="auto", range=(min_, max_))) - 1
141
-
142
-
143
- def display_n_bins(kind):
144
- if kind != "Stacked histogram":
145
- return
146
-
147
- return selector_n_bins
148
-
149
-
150
- def figure(res, palette, n_bins, kind, output_name):
151
- kind = "histogram" if kind == "Stacked histogram" else "boxplot"
152
- plt.close("all")
153
- fig, ax = plt.subplots()
154
- _ = sd.visualization(
155
- bins=res.bins, palette=palette, n_bins=n_bins, kind=kind, ax=ax
156
- )
157
- ax.set(xlabel=output_name)
158
- return fig
159
-
160
-
161
- def states_from_data(res, inputs):
162
- return sd.states_expansion(states=res.states, inputs=inputs)
163
-
164
-
165
- def tableau(res, states, palette):
166
- # use a notebook to see the styling
167
- _, styler = sd.tableau(
168
- statistic=res.statistic,
169
- var_names=res.var_names,
170
- states=states,
171
- bins=res.bins,
172
- palette=palette,
173
- )
174
- return styler
175
-
176
-
177
- def tableau_states(res, states):
178
- data = []
179
- for var_name, states_, bin_edges in zip(res.var_names, states, res.bin_edges):
180
- for i, state in enumerate(states_):
181
- data.append([var_name, state, bin_edges[i], bin_edges[i + 1]])
182
-
183
- table = pd.DataFrame(data, columns=["variable", "state", "min", "max"])
184
- table.set_index(["variable", "state"], inplace=True)
185
-
186
- styler = table.style
187
- styler.format(precision=2)
188
- styler.set_table_styles([{"selector": "th", "props": [("text-align", "center")]}])
189
- return styler
190
-
191
-
192
- # Bindings
193
- text_fname = pn.widgets.FileInput(sizing_mode="stretch_width", accept=".csv")
194
-
195
- interactive_file = pn.bind(load_data, text_fname)
196
-
197
- interactive_column_output = pn.bind(column_output, interactive_file)
198
- selector_output = pn.widgets.Select(name="Output", options=interactive_column_output)
199
- interactive_output = pn.bind(filtered_output, interactive_file, selector_output)
200
-
201
- interactive_column_input = pn.bind(column_inputs, interactive_file, selector_output)
202
- selector_inputs_sensitivity = pn.widgets.MultiSelect(
203
- name="Inputs", value=interactive_column_input, options=interactive_column_input
204
- )
205
- interactive_inputs = pn.bind(
206
- filtered_output, interactive_file, selector_inputs_sensitivity
207
- )
208
-
209
- interactive_sensitivity_indices = pn.bind(
210
- sensitivity_indices, interactive_inputs, interactive_output
211
- )
212
- interactive_explained_variance = pn.bind(
213
- explained_variance, interactive_sensitivity_indices
214
- )
215
-
216
- interactive_sensitivity_indices_table = pn.bind(
217
- sensitivity_indices_table, interactive_sensitivity_indices, interactive_inputs
218
- )
219
-
220
- interactive_explained_variance_80 = pn.bind(
221
- explained_variance_80, interactive_sensitivity_indices_table
222
- )
223
- selector_inputs_decomposition = pn.widgets.MultiChoice(
224
- name="Select inputs for decomposition",
225
- value=interactive_explained_variance_80,
226
- options=selector_inputs_sensitivity,
227
- solid=False,
228
- )
229
- interactive_inputs_decomposition = pn.bind(
230
- filtered_output, interactive_file, selector_inputs_decomposition
231
- )
232
-
233
- interactive_filtered_si = pn.bind(
234
- filtered_si, interactive_sensitivity_indices_table, selector_inputs_decomposition
235
- )
236
- interactive_filtered_explained_variance = pn.bind(
237
- explained_variance, interactive_filtered_si
238
- )
239
- indicator_explained_variance = pn.indicators.Number(
240
- name="Explained variance ratio from selected inputs:",
241
- value=interactive_filtered_explained_variance,
242
- title_size=font_size,
243
- font_size=font_size,
244
- format="{value:.2f}",
245
- )
246
-
247
-
248
- interactive_decomposition = pn.bind(
249
- decomposition,
250
- interactive_explained_variance,
251
- interactive_filtered_si,
252
- interactive_inputs_decomposition,
253
- interactive_output,
254
- )
255
- interactive_palette = pn.bind(palette, interactive_decomposition)
256
-
257
- switch_histogram_boxplot = pn.widgets.RadioButtonGroup(
258
- name="Switch histogram - boxplot",
259
- options=["Stacked histogram", "Boxplot"],
260
- )
261
-
262
- interactive_n_bins_auto = pn.bind(n_bins_auto, interactive_decomposition)
263
- selector_n_bins = pn.widgets.EditableIntSlider(
264
- name="Number of bins",
265
- start=0,
266
- end=100,
267
- value=interactive_n_bins_auto,
268
- step=10,
269
- # bar_color="#FFFFFF", # does not work
270
- format=PrintfTickFormatter(format="%d bins"),
271
- )
272
- conditional_selector_n_bins = pn.bind(display_n_bins, switch_histogram_boxplot)
273
-
274
-
275
- interactive_figure = pn.bind(
276
- figure,
277
- interactive_decomposition,
278
- interactive_palette,
279
- selector_n_bins,
280
- switch_histogram_boxplot,
281
- selector_output,
282
- )
283
-
284
-
285
- interactive_states = pn.bind(
286
- states_from_data, interactive_decomposition, interactive_inputs_decomposition
287
- )
288
- interactive_tableau = pn.bind(
289
- tableau, interactive_decomposition, interactive_states, interactive_palette
290
- )
291
- interactive_tableau_states = pn.bind(
292
- tableau_states, interactive_decomposition, interactive_states
293
- )
294
-
295
- # App layout
296
-
297
- top_description = """
298
- ## Data
299
- """
300
-
301
- si_description = """
302
- ## Sensitivity Indices
303
- """
304
-
305
- decomposition_description = """
306
- ## Decomposition
307
- """
308
-
309
- table_description = """
310
- ## Scenarios
311
- """
312
-
313
- states_description = """
314
- ## Details on inputs' states
315
- """
316
-
317
-
318
- pn_params = pn.layout.WidgetBox(
319
- pn.pane.Markdown(top_description, styles={"color": "#0072b5"}),
320
- text_fname,
321
- selector_output,
322
- selector_inputs_sensitivity,
323
- pn.pane.Markdown(decomposition_description, styles={"color": "#0072b5"}),
324
- selector_inputs_decomposition,
325
- indicator_explained_variance,
326
- pn.pane.Markdown("## Visualization", styles={"color": "#0072b5"}),
327
- switch_histogram_boxplot,
328
- conditional_selector_n_bins,
329
- max_width=350,
330
- sizing_mode="stretch_width",
331
- ).servable(area="sidebar")
332
-
333
- pn_app = pn.Column(
334
- pn.Row(
335
- pn.Column(
336
- pn.panel(
337
- pn.pane.Matplotlib(
338
- interactive_figure,
339
- tight=True,
340
- format="svg",
341
- sizing_mode="stretch_both",
342
- max_height=500,
343
- height_policy="min",
344
- )
345
- ),
346
- pn.Spacer(height=50),
347
- pn.pane.Markdown(si_description, styles={"color": "#0072b5"}),
348
- pn.Column(interactive_sensitivity_indices_table, width=400),
349
- ),
350
- pn.Column(
351
- pn.pane.Markdown(table_description, styles={"color": "#0072b5"}),
352
- pn.panel(interactive_tableau),
353
- pn.Spacer(height=125),
354
- pn.pane.Markdown(states_description, styles={"color": "#0072b5"}),
355
- pn.panel(interactive_tableau_states),
356
- ),
357
- )
358
- ).servable(title="Simulation Decomposition Dashboard")
File without changes
File without changes
File without changes