Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question regarding deployment on Heroku #38

Closed
hkoppen opened this issue Dec 4, 2020 · 67 comments
Closed

Question regarding deployment on Heroku #38

hkoppen opened this issue Dec 4, 2020 · 67 comments
Labels
help wanted Extra attention is needed

Comments

@hkoppen
Copy link

hkoppen commented Dec 4, 2020

I just tried to deploy my app on Heroku by directly importing the github project.
However, I did not manage to "add the buildpack" correctly - I'm still generating a slug larger than 500MB. I did

  • add the folder bin from https://github.com/niteoweb/heroku-buildpack-shell.git to my project folder,
  • add the folder .heroku including the file run.sh containing "pip install -y xgboost".
    What am I doing wrong, do I have to add the buildpack somewhere in Heroku itself?
@oegedijk
Copy link
Owner

oegedijk commented Dec 4, 2020

Ah, yes, you need to add the buildpack through the heroku GUI. Just go to the settings page of your heroku project and there you should find the add buildpack option. THere you can drop in the https://github.com/niteoweb/heroku-buildpack-shell.git link and it should be added to your heroku slug.

@oegedijk
Copy link
Owner

oegedijk commented Dec 4, 2020

also, you need to run pip uninstall -y xgboost instead of pip install...

@oegedijk
Copy link
Owner

oegedijk commented Dec 4, 2020

(noticed that is actually a typo in the docs, just fixed it)

@oegedijk
Copy link
Owner

oegedijk commented Dec 7, 2020

Did you manage to get it to work? I opened an issue with dtreeviz here to make the xgboost dependency optional, so that in the future it should be less of a headache.

@hkoppen
Copy link
Author

hkoppen commented Dec 7, 2020

Unfortunately, I'm not quite there. Building worked (after adding the Python buildpack as well 👼 ), but the app itself crashes due to 2020-12-07T10:45:58.008591+00:00 app[web.1]: bash: gunicorn: command not found.
I am using

from unittest.mock import MagicMock
import sys
sys.modules["xgboost"] = MagicMock()

from explainerdashboard import ExplainerDashboard

app = ExplainerDashboard.from_config("dashboard_v3.yaml").flask_server()
server = app.server

if __name__ == '__main__':
    app.run_server()

and the procfile
web: gunicorn app:server
following this article. Do I have to adapt to explainerdashboards' architecture somehow?

@oegedijk
Copy link
Owner

oegedijk commented Dec 7, 2020

Ah, you need to add a requirements.txt file with:

explainerdashboard==0.2.13.2
gunicorn

@hkoppen
Copy link
Author

hkoppen commented Dec 7, 2020

Ok I forgot to mention the file, which was not contain gunicorn.

Now I am facing an en-/decoding error:

2020-12-07T11:25:14.091849+00:00 app[web.1]: [2020-12-07 11:25:14 +0000] [18] [ERROR] Exception in worker process
2020-12-07T11:25:14.091872+00:00 app[web.1]: Traceback (most recent call last):
2020-12-07T11:25:14.091873+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 583, in spawn_worker
2020-12-07T11:25:14.091874+00:00 app[web.1]:     worker.init_process()
2020-12-07T11:25:14.091874+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/workers/base.py", line 119, in init_process
2020-12-07T11:25:14.091874+00:00 app[web.1]:     self.load_wsgi()
2020-12-07T11:25:14.091874+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/workers/base.py", line 144, in load_wsgi
2020-12-07T11:25:14.091875+00:00 app[web.1]:     self.wsgi = self.app.wsgi()
2020-12-07T11:25:14.091875+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/base.py", line 67, in wsgi
2020-12-07T11:25:14.091876+00:00 app[web.1]:     self.callable = self.load()
2020-12-07T11:25:14.091876+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 49, in load
2020-12-07T11:25:14.091876+00:00 app[web.1]:     return self.load_wsgiapp()
2020-12-07T11:25:14.091876+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 39, in load_wsgiapp
2020-12-07T11:25:14.091877+00:00 app[web.1]:     return util.import_app(self.app_uri)
2020-12-07T11:25:14.091877+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/util.py", line 358, in import_app
2020-12-07T11:25:14.091877+00:00 app[web.1]:     mod = importlib.import_module(module)
2020-12-07T11:25:14.091877+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/importlib/__init__.py", line 126, in import_module
2020-12-07T11:25:14.091878+00:00 app[web.1]:     return _bootstrap._gcd_import(name[level:], package, level)
2020-12-07T11:25:14.091878+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 994, in _gcd_import
2020-12-07T11:25:14.091879+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 971, in _find_and_load
2020-12-07T11:25:14.091879+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
2020-12-07T11:25:14.091879+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
2020-12-07T11:25:14.091879+00:00 app[web.1]:   File "<frozen importlib._bootstrap_external>", line 678, in exec_module
2020-12-07T11:25:14.091880+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
2020-12-07T11:25:14.091880+00:00 app[web.1]:   File "/app/app.py", line 7, in <module>
2020-12-07T11:25:14.091880+00:00 app[web.1]:     app = ExplainerDashboard.from_config("dashboard_v3.yaml").flask_server()
2020-12-07T11:25:14.091881+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/explainerdashboard/dashboards.py", line 467, in from_config
2020-12-07T11:25:14.091881+00:00 app[web.1]:     explainer = BaseExplainer.from_file(config['dashboard']['explainerfile'])
2020-12-07T11:25:14.091881+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/explainerdashboard/explainers.py", line 189, in from_file
2020-12-07T11:25:14.091881+00:00 app[web.1]:     return joblib.load(filepath)
2020-12-07T11:25:14.091882+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/joblib/numpy_pickle.py", line 585, in load
2020-12-07T11:25:14.091882+00:00 app[web.1]:     obj = _unpickle(fobj, filename, mmap_mode)
2020-12-07T11:25:14.091882+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/joblib/numpy_pickle.py", line 504, in _unpickle
2020-12-07T11:25:14.091882+00:00 app[web.1]:     obj = unpickler.load()
2020-12-07T11:25:14.091882+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/pickle.py", line 1050, in load
2020-12-07T11:25:14.091883+00:00 app[web.1]:     dispatch[key[0]](self)
2020-12-07T11:25:14.091883+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/pickle.py", line 1398, in load_reduce
2020-12-07T11:25:14.091883+00:00 app[web.1]:     stack[-1] = func(*args)
2020-12-07T11:25:14.091883+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/numba/core/serialize.py", line 208, in _unpickle__CustomPickled
2020-12-07T11:25:14.091883+00:00 app[web.1]:     ctor, states = loads(serialized)
2020-12-07T11:25:14.091884+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/numba/core/serialize.py", line 112, in _rebuild_function
2020-12-07T11:25:14.091884+00:00 app[web.1]:     code = _rebuild_code(*code_reduced)
2020-12-07T11:25:14.091884+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/numba/core/serialize.py", line 132, in _rebuild_code
2020-12-07T11:25:14.091884+00:00 app[web.1]:     raise RuntimeError("incompatible bytecode version")
2020-12-07T11:25:14.091890+00:00 app[web.1]: RuntimeError: incompatible bytecode version
2020-12-07T11:25:14.093752+00:00 app[web.1]: [2020-12-07 11:25:14 +0000] [18] [INFO] Worker exiting (pid: 18)


2020-12-07T11:25:14.169747+00:00 app[web.1]: [2020-12-07 11:25:14 +0000] [10] [ERROR] Exception in worker process
2020-12-07T11:25:14.169749+00:00 app[web.1]: Traceback (most recent call last):
2020-12-07T11:25:14.169756+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/arbiter.py", line 583, in spawn_worker
2020-12-07T11:25:14.169757+00:00 app[web.1]:     worker.init_process()
2020-12-07T11:25:14.169757+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/workers/base.py", line 119, in init_process
2020-12-07T11:25:14.169757+00:00 app[web.1]:     self.load_wsgi()
2020-12-07T11:25:14.169758+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/workers/base.py", line 144, in load_wsgi
2020-12-07T11:25:14.169758+00:00 app[web.1]:     self.wsgi = self.app.wsgi()
2020-12-07T11:25:14.169759+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/base.py", line 67, in wsgi
2020-12-07T11:25:14.169759+00:00 app[web.1]:     self.callable = self.load()
2020-12-07T11:25:14.169760+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 49, in load
2020-12-07T11:25:14.169760+00:00 app[web.1]:     return self.load_wsgiapp()
2020-12-07T11:25:14.169760+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 39, in load_wsgiapp
2020-12-07T11:25:14.169761+00:00 app[web.1]:     return util.import_app(self.app_uri)
2020-12-07T11:25:14.169761+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/util.py", line 358, in import_app
2020-12-07T11:25:14.169762+00:00 app[web.1]:     mod = importlib.import_module(module)
2020-12-07T11:25:14.169762+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/importlib/__init__.py", line 126, in import_module
2020-12-07T11:25:14.169763+00:00 app[web.1]:     return _bootstrap._gcd_import(name[level:], package, level)
2020-12-07T11:25:14.169763+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 994, in _gcd_import
2020-12-07T11:25:14.169764+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 971, in _find_and_load
2020-12-07T11:25:14.169764+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
2020-12-07T11:25:14.169765+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
2020-12-07T11:25:14.169765+00:00 app[web.1]:   File "<frozen importlib._bootstrap_external>", line 678, in exec_module
2020-12-07T11:25:14.169765+00:00 app[web.1]:   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
2020-12-07T11:25:14.169766+00:00 app[web.1]:   File "/app/app.py", line 7, in <module>
2020-12-07T11:25:14.169766+00:00 app[web.1]:     app = ExplainerDashboard.from_config("dashboard_v3.yaml").flask_server()
2020-12-07T11:25:14.169767+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/explainerdashboard/dashboards.py", line 467, in from_config
2020-12-07T11:25:14.169767+00:00 app[web.1]:     explainer = BaseExplainer.from_file(config['dashboard']['explainerfile'])
2020-12-07T11:25:14.169768+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/explainerdashboard/explainers.py", line 189, in from_file
2020-12-07T11:25:14.169768+00:00 app[web.1]:     return joblib.load(filepath)
2020-12-07T11:25:14.169768+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/joblib/numpy_pickle.py", line 585, in load
2020-12-07T11:25:14.169769+00:00 app[web.1]:     obj = _unpickle(fobj, filename, mmap_mode)
2020-12-07T11:25:14.169769+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/joblib/numpy_pickle.py", line 504, in _unpickle
2020-12-07T11:25:14.169770+00:00 app[web.1]:     obj = unpickler.load()
2020-12-07T11:25:14.169770+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/pickle.py", line 1050, in load
2020-12-07T11:25:14.169771+00:00 app[web.1]:     dispatch[key[0]](self)
2020-12-07T11:25:14.169771+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/pickle.py", line 1398, in load_reduce
2020-12-07T11:25:14.169771+00:00 app[web.1]:     stack[-1] = func(*args)
2020-12-07T11:25:14.169772+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/numba/core/serialize.py", line 208, in _unpickle__CustomPickled
2020-12-07T11:25:14.169772+00:00 app[web.1]:     ctor, states = loads(serialized)
2020-12-07T11:25:14.169778+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/numba/core/serialize.py", line 112, in _rebuild_function
2020-12-07T11:25:14.169778+00:00 app[web.1]:     code = _rebuild_code(*code_reduced)
2020-12-07T11:25:14.169778+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/numba/core/serialize.py", line 132, in _rebuild_code
2020-12-07T11:25:14.169779+00:00 app[web.1]:     raise RuntimeError("incompatible bytecode version")
2020-12-07T11:25:14.169779+00:00 app[web.1]: RuntimeError: incompatible bytecode version

@hkoppen
Copy link
Author

hkoppen commented Dec 7, 2020

Maybe it's because gunicorn is not designed for Windows and I should use waitress instead?

@oegedijk
Copy link
Owner

oegedijk commented Dec 7, 2020

Ah, then the python version on heroku and the one you used to pickle the explainer is probably not compatible (pickle does not guarantee unpickles across versions):

You can learn about setting runtime versions here and here

Basically you should add a runtime.txt file with the python version:

python-3.8.6

Supported versions are python-3.9.0, python-3.8.6, python-3.7.9 and python-3.6.12

@oegedijk
Copy link
Owner

oegedijk commented Dec 7, 2020

Maybe it's because gunicorn is not designed for Windows and I should use waitress instead?

Ah, yes. I don't know much about windows deployment (didnt even knew that heroku supported it to be honest :), but waitress is then probably the way to go. If you manage to get it to work could you let me know so that I can add instructions to the docs?

@hkoppen
Copy link
Author

hkoppen commented Dec 7, 2020

I managed to track down every bug/mistake and got it running with gunicorn :-)

For future reference, I'm using the following app.py:

from unittest.mock import MagicMock
import sys
sys.modules["xgboost"] = MagicMock()

from explainerdashboard import ExplainerDashboard

app = ExplainerDashboard.from_config("dashboard_v3.yaml").app
server = app.server

if __name__ == '__main__':
    app.run_server()

Using .flask_server() is somehow not so smart.

@oegedijk
Copy link
Owner

oegedijk commented Dec 7, 2020

ah, really? That is strange, should be equivalent. db.flask_server() literally just returns self.app.server. Anyway, I will just use the db.app.server notation then in the documentation from now on. I've also clarified the deployment to heroku documentation a bit: https://explainerdashboard.readthedocs.io/en/latest/deployment.html#deploying-to-heroku Could you let me know if there is anything I should add to make the chance of accidental bugs smaller for future users?

Do you have a public link for the dashboard? Would be curious what you built.

@hkoppen
Copy link
Author

hkoppen commented Dec 8, 2020

Uh, never mind, I guess I put it into the wrong spot. The following works fine as well.

db = ExplainerDashboard.from_config("dashboard_v3.yaml")
app = db.app
server = db.flask_server()

The documentation looks great. Maybe you want to explain why you are using --preload --timeout 60 since it's strictly speaking not needed?

Sure, I will share it, but I have another problem: the plots are empty 😁 although I have loaded the data in the Github project. What could be the reason for this? Do you even need the data to be present when loading a dashboard from disk?

@oegedijk
Copy link
Owner

oegedijk commented Dec 8, 2020

Ah yeah: --preload is only needed when you have multiple workers. It assures that all the code is properly loaded for each worker, otherwise they somehow seem to get out of sync. The --timeout is the amount of time that the startup of your server is allowed to take, so when you have some expensive imports or calculations to be done before starting the dashboard, it can help to to increase this.

If the plots are empty, something is definitely broken: you would want to check the logs for the stacktrace. All the data should be contained in the explainer.joblib (or explainer.pkl or whatever). So you would need to have both the explainer.joblib (or .pkl) and the dashboard.yaml in the repo to launch the dashboard.

@hkoppen
Copy link
Author

hkoppen commented Dec 9, 2020

I have the yaml and the explainer-file in the repo. Locally, loading works fine, but on Heroku, the plots are empty. I can't find anything in the logs:

2020-12-09T16:27:58.344000+00:00 heroku[web.1]: Starting process with command `gunicorn app:server`
2020-12-09T16:28:00.564417+00:00 app[web.1]: [2020-12-09 16:28:00 +0000] [4] [INFO] Starting gunicorn 20.0.4
2020-12-09T16:28:00.564982+00:00 app[web.1]: [2020-12-09 16:28:00 +0000] [4] [INFO] Listening at: http://0.0.0.0:12619 (4)
2020-12-09T16:28:00.565101+00:00 app[web.1]: [2020-12-09 16:28:00 +0000] [4] [INFO] Using worker: sync
2020-12-09T16:28:00.569038+00:00 app[web.1]: [2020-12-09 16:28:00 +0000] [10] [INFO] Booting worker with pid: 10
2020-12-09T16:28:00.603277+00:00 app[web.1]: [2020-12-09 16:28:00 +0000] [11] [INFO] Booting worker with pid: 11
2020-12-09T16:28:00.977409+00:00 heroku[web.1]: State changed from starting to up
2020-12-09T16:28:11.766502+00:00 app[web.1]: Building ExplainerDashboard..
2020-12-09T16:28:11.769214+00:00 app[web.1]: Building ExplainerDashboard..
2020-12-09T16:28:11.778317+00:00 app[web.1]: Generating layout...
2020-12-09T16:28:11.781447+00:00 app[web.1]: Generating layout...
2020-12-09T16:28:11.795301+00:00 app[web.1]: Calculating dependencies...
2020-12-09T16:28:11.795376+00:00 app[web.1]: Registering callbacks...
2020-12-09T16:28:11.798934+00:00 app[web.1]: Calculating dependencies...
2020-12-09T16:28:11.799016+00:00 app[web.1]: Registering callbacks...
2020-12-09T16:28:59.000000+00:00 app[api]: Build succeeded
2020-12-09T16:30:33.057134+00:00 heroku[router]: at=info method=GET path="/" host=... request_id=6fd55fb9-c7f6-4981-b2c3-c0a8ebb43632 fwd="62.216.209.193" dyno=web.1 connect=1ms service=28ms status=200 bytes=973 protocol=https
2020-12-09T16:30:33.051409+00:00 app[web.1]: 10.14.25.48 - - [09/Dec/2020:16:30:33 +0000] "GET / HTTP/1.1" 200 765 "https://dashboard.heroku.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.230979+00:00 app[web.1]: 10.35.77.100 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_renderer/react@16.v1_8_3m1607531128.14.0.min.js HTTP/1.1" 200 4898 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.236167+00:00 app[web.1]: 10.14.25.48 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_renderer/polyfill@7.v1_8_3m1607531128.8.7.min.js HTTP/1.1" 200 34243 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.235239+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_renderer/react@16.v1_8_3m1607531128.14.0.min.js" host=... request_id=d6d60976-eb2a-41b6-9dfc-04c56486f20d fwd="62.216.209.193" dyno=web.1 connect=5ms service=11ms status=200 bytes=5153 protocol=https
2020-12-09T16:30:33.375435+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_html_components/dash_html_components.v1_1_1m1607531134.min.js" host=... request_id=60ba5ea1-0689-4227-aa4d-7f9116124bb9 fwd="62.216.209.193" dyno=web.1 connect=4ms service=6ms status=200 bytes=19163 protocol=https
2020-12-09T16:30:33.357114+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_core_components/dash_core_components-shared.v1_13_0m1607531133.js" host=... request_id=4ac3b6c0-2002-4b08-9470-b26216374cea fwd="62.216.209.193" dyno=web.1 connect=7ms service=6ms status=200 bytes=9940 protocol=https
2020-12-09T16:30:33.362676+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_core_components/dash_core_components.v1_13_0m1607531133.min.js" host=... request_id=9df2dd92-7f4c-43db-adc4-00cf14ff37d9 fwd="62.216.209.193" dyno=web.1 connect=4ms service=24ms status=200 bytes=119092 protocol=https
2020-12-09T16:30:33.402190+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_bootstrap_components/_components/dash_bootstrap_components.v0_10_7m1607531131.min.js" host=... request_id=def225a7-91a2-4f8c-8776-5102851f66cf fwd="62.216.209.193" dyno=web.1 connect=1ms service=14ms status=200 bytes=52759 protocol=https
2020-12-09T16:30:33.244014+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_renderer/polyfill@7.v1_8_3m1607531128.8.7.min.js" host=... request_id=6e91a5c4-2867-489c-81bb-fecc106db2ed fwd="62.216.209.193" dyno=web.1 connect=2ms service=27ms status=200 bytes=34499 protocol=https
2020-12-09T16:30:33.342814+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_table/bundle.v4_11_0m1607531132.js" host=... request_id=c08e9a18-2af6-4d16-9c9c-11264040ad02 fwd="62.216.209.193" dyno=web.1 connect=1ms service=5ms status=200 bytes=11269 protocol=https
2020-12-09T16:30:33.307532+00:00 app[web.1]: 10.35.77.100 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_renderer/react-dom@16.v1_8_3m1607531128.14.0.min.js HTTP/1.1" 200 38049 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.329677+00:00 app[web.1]: 10.39.236.138 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_renderer/prop-types@15.v1_8_3m1607531128.7.2.min.js HTTP/1.1" 200 832 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.342219+00:00 app[web.1]: 10.10.227.188 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_table/bundle.v4_11_0m1607531132.js HTTP/1.1" 200 11013 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.353983+00:00 app[web.1]: 10.11.191.99 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_core_components/dash_core_components-shared.v1_13_0m1607531133.js HTTP/1.1" 200 9685 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.355899+00:00 app[web.1]: 10.10.88.48 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_core_components/dash_core_components.v1_13_0m1607531133.min.js HTTP/1.1" 200 118835 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.371658+00:00 app[web.1]: 10.14.25.48 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_html_components/dash_html_components.v1_1_1m1607531134.min.js HTTP/1.1" 200 18907 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.396720+00:00 app[web.1]: 10.39.236.138 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_bootstrap_components/_components/dash_bootstrap_components.v0_10_7m1607531131.min.js HTTP/1.1" 200 52503 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.425132+00:00 app[web.1]: 10.10.227.188 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-component-suites/dash_renderer/dash_renderer.v1_8_3m1607531128.min.js HTTP/1.1" 200 59285 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.311666+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_renderer/react-dom@16.v1_8_3m1607531128.14.0.min.js" host=... request_id=6ffa561e-09ac-485e-b804-4881c6a82682 fwd="62.216.209.193" dyno=web.1 connect=7ms service=9ms status=200 bytes=38305 protocol=https
2020-12-09T16:30:33.330282+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_renderer/prop-types@15.v1_8_3m1607531128.7.2.min.js" host=... request_id=faee708b-efb1-4a18-9c66-9e3ccc437a23 fwd="62.216.209.193" dyno=web.1 connect=1ms service=2ms status=200 bytes=1086 protocol=https
2020-12-09T16:30:33.427968+00:00 heroku[router]: at=info method=GET path="/_dash-component-suites/dash_renderer/dash_renderer.v1_8_3m1607531128.min.js" host=... request_id=ba2d4b1e-dade-43ec-ba46-cd064861c039 fwd="62.216.209.193" dyno=web.1 connect=5ms service=13ms status=200 bytes=59541 protocol=https
2020-12-09T16:30:33.716655+00:00 heroku[router]: at=info method=GET path="/_dash-layout" host=... request_id=f3f91682-0f88-4e52-92c6-d2c034867c14 fwd="62.216.209.193" dyno=web.1 connect=2ms service=27ms status=200 bytes=131727 protocol=https
2020-12-09T16:30:33.694495+00:00 app[web.1]: 10.39.236.138 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-dependencies HTTP/1.1" 200 730 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.711493+00:00 app[web.1]: 10.10.88.48 - - [09/Dec/2020:16:30:33 +0000] "GET /_dash-layout HTTP/1.1" 200 131524 "https://.../" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
2020-12-09T16:30:33.696281+00:00 heroku[router]: at=info method=GET path="/_dash-dependencies" host=... request_id=4b5d7967-3082-4aa2-9f44-57819bd9665e fwd="62.216.209.193" dyno=web.1 connect=4ms service=4ms status=200 bytes=930 protocol=https

@oegedijk
Copy link
Owner

Hmm, that is quite strange. Two potential reasons:

  1. somehow no callbacks at all are triggered/firing. Could you check if other interaction functionality on the dashboard works (e.g. group cats should change the features in the dropdown, etc)
  2. The stacktrace of the failed callbacks somehow do not show up in the log (perhaps looking at the wrong log, or not the right filter settings?)

@hkoppen
Copy link
Author

hkoppen commented Dec 10, 2020

Group cats is disabled atm, but choosing/changing an index in FeatureInputComponent does not show/change anything as well.
Logs - I'm using More > View Logs in Heroku next to Open App where I can only filter for web processes (which I'm not), are there any alternatives to view the logs?

@hkoppen
Copy link
Author

hkoppen commented Dec 11, 2020

I tried loading the explainer file only, building the dashboard in app.py - no difference. I also tried constructing the dashboard it self in app.py, which is not working due to memory limitations on Heroku. My next & last idea is to deploy one of your Titanic dashboards ...

@oegedijk
Copy link
Owner

Yeah, probably handy to first test something that you know should work:

generate_dashboard.py:

from explainerdashboard import ClassifierExplainer, ExplainerDashboard
from explainerdashboard.custom import *

explainer = ClassifierExplainer(model, X_test, y_test)

# building an ExplainerDashboard ensures that all necessary properties 
# get calculated:
db = ExplainerDashboard(explainer, [ShapDependenceComposite, WhatIfComposite],
                        title='Awesome Dashboard', hide_whatifpdp=True)

# store both the explainer and the dashboard configuration:
explainer.dump("explainer.joblib")
db.to_yaml("dashboard.yaml")

Now run python generate_dashboard.py

dashboard.py:

from explainerdashboard import ClassifierExplainer, ExplainerDashboard

explainer = ClassifierExplainer.from_file("explainer.joblib")
# you can override params during load from_config:
db = ExplainerDashboard.from_config(explainer, "dashboard.yaml", title="Awesomer Title")

app = db.flask_server()

Now run gunicorn dashboard:app.

If it works, push the repo and see if it works on heroku as well.

@hkoppen
Copy link
Author

hkoppen commented Dec 14, 2020

Locally it works, but the result is https://hk-db-test.herokuapp.com/. :(
(Note that hide_whatifpdp=True is not doing anything)

@oegedijk
Copy link
Owner

Hmm, weird. Do you maybe have the dashboard linked to a public github repo so that I can fork it and see if I can get it to work?

@hkoppen
Copy link
Author

hkoppen commented Dec 15, 2020

Yup, it's here: https://github.com/hkoppen/Dashboard_Test

@oegedijk
Copy link
Owner

Found the problem (now just need to find the solution):

(Btw, if you install the papertrail add-on (it's for free), you can see real time logs including stacktraces like below to help you debug)

Dec 15 02:06:02 db-test-oege app/web.1 Exception on /_dash-update-component [POST]
Dec 15 02:06:02 db-test-oege app/web.1 Traceback (most recent call last):
Dec 15 02:06:02 db-test-oege app/web.1   File "/app/.heroku/python/lib/python3.7/site-packages/dash/dash.py", line 1072, in dispatch
Dec 15 02:06:02 db-test-oege app/web.1     func = self.callback_map[output]["callback"]
Dec 15 02:06:02 db-test-oege app/web.1 KeyError: 'pdp-graph-AGdDw6dDQk.figure'
Dec 15 02:06:02 db-test-oege app/web.1 During handling of the above exception, another exception occurred:
Dec 15 02:06:02 db-test-oege app/web.1 Traceback (most recent call last):
Dec 15 02:06:02 db-test-oege app/web.1   File "/app/.heroku/python/lib/python3.7/site-packages/flask/app.py", line 2447, in wsgi_app
Dec 15 02:06:02 db-test-oege app/web.1     response = self.full_dispatch_request()
Dec 15 02:06:02 db-test-oege app/web.1   File "/app/.heroku/python/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
Dec 15 02:06:02 db-test-oege app/web.1     rv = self.handle_user_exception(e)
Dec 15 02:06:02 db-test-oege app/web.1   File "/app/.heroku/python/lib/python3.7/site-packages/flask/app.py", line 1821, in handle_user_exception
Dec 15 02:06:02 db-test-oege app/web.1     reraise(exc_type, exc_value, tb)
Dec 15 02:06:02 db-test-oege app/web.1   File "/app/.heroku/python/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
Dec 15 02:06:02 db-test-oege app/web.1     raise value
Dec 15 02:06:02 db-test-oege app/web.1   File "/app/.heroku/python/lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request
Dec 15 02:06:02 db-test-oege app/web.1     rv = self.dispatch_request()
Dec 15 02:06:02 db-test-oege app/web.1   File "/app/.heroku/python/lib/python3.7/site-packages/flask/app.py", line 1936, in dispatch_request
Dec 15 02:06:02 db-test-oege app/web.1     return self.view_functions[rule.endpoint](**req.view_args)
Dec 15 02:06:02 db-test-oege app/web.1   File "/app/.heroku/python/lib/python3.7/site-packages/dash/dash.py", line 1075, in dispatch
Dec 15 02:06:02 db-test-oege app/web.1     raise KeyError(msg.format(output))
Dec 15 02:06:02 db-test-oege app/web.1 KeyError: "Callback function not found for output 'pdp-graph-AGdDw6dDQk.figure', perhaps you forgot to prepend the '@'?"

So the callbacks are not working for some reason (hence why you don't see the graphs, nor does the random index button work)...

I will have to investigate why, but it is very odd that it works for the titanicexplainer.herokuapp.com deployment but not for this one. Hmm.

@oegedijk
Copy link
Owner

Got it: change Procfile to:

web: gunicorn --preload dashboard:app

Not sure exactly what is so magical about preload (not a gunicorn expert), but if you don't add it all kind of weird stuff starts to happen.

@oegedijk
Copy link
Owner

@oegedijk
Copy link
Owner

I think I have an idea what is going one:

https://docs.gunicorn.org/en/stable/settings.html:

preload_app
--preload
False
Load application code before the worker processes are forked.

In order to make sure that all dash elements are unique, I add a random uuid string at the end of each component id. This makes it so that you can include multiple ExplainerComponents of the same type in the same layout, as they will all have unique ids and callbacks. However if you do not pass --preload then each gunicorn worker instantiates its own app with its own random uuid strings at the end of components! So then the different workers do not agree on the names of the ids and the callbacks fail.

Will add a clearer warning to the docs that --preload is essential...

@hkoppen
Copy link
Author

hkoppen commented Dec 15, 2020

Yup, that's it. Damn it, we talked about it exactly 7 days ago!

Edit: Now I can move on to deploy the app via Docker ;-)

@oegedijk oegedijk added the help wanted Extra attention is needed label Dec 16, 2020
@carlryn
Copy link

carlryn commented Dec 17, 2020

I'm having the same error about callbacks not found. The --preload option solves for running from one cotainer with gunicorn, but when scaling with docker swarm the error shows again.
Should this be expected? :) Anything I can do to make it work? :)

@moeller84
Copy link

Could you test it on your docker swarm? I'm actually also curious why you need to deploy on a docker swarm, do you have that many users simultaneously, or is it just that all dashboard by default are deployed to swarms at your organization?

Hi, it is still not working. All our dashboards are deployed by default via docker. It seems like it still assigns uuid names to callbacks. Could it have something to do with running the dashboard through gunicorn and wsgi?

@oegedijk
Copy link
Owner

Is this the default dashboard or did you make your own custom dashboards?

@oegedijk
Copy link
Owner

So for example, when I call:

db = ExplainerDashboard(explainer)
list(db.app.callback_map.values())

I get the following output, where you can see that all the callback id's end with two digits ('10', '23', etc) instead of a uuid string of length 5.

Do you see the same?

[{'inputs': [{'id': 'importances-depth-10', 'property': 'value'},
   {'id': 'importances-group-cats-10', 'property': 'value'},
   {'id': 'importances-permutation-or-shap-10', 'property': 'value'},
   {'id': 'pos-label-10', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.overview_components.ImportancesComponent.component_callbacks.<locals>.update_importances(depth, cats, permutation_shap, pos_label)>},
 {'inputs': [{'id': 'clas-model-summary-cutoff-20', 'property': 'value'},
   {'id': 'pos-label-20', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassifierModelSummaryComponent.component_callbacks.<locals>.update_classifier_summary(cutoff, pos_label)>},
 {'inputs': [{'id': 'precision-binsize-or-quantiles-21', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.PrecisionComponent.component_callbacks.<locals>.update_div_visibility(bins_or_quantiles)>},
 {'inputs': [{'id': 'precision-binsize-21', 'property': 'value'},
   {'id': 'precision-quantiles-21', 'property': 'value'},
   {'id': 'precision-binsize-or-quantiles-21', 'property': 'value'},
   {'id': 'precision-cutoff-21', 'property': 'value'},
   {'id': 'precision-multiclass-21', 'property': 'value'},
   {'id': 'pos-label-21', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.PrecisionComponent.component_callbacks.<locals>.update_precision_graph(bin_size, quantiles, bins, cutoff, multiclass, pos_label)>},
 {'inputs': [{'id': 'confusionmatrix-cutoff-22', 'property': 'value'},
   {'id': 'confusionmatrix-percentage-22', 'property': 'value'},
   {'id': 'confusionmatrix-binary-22', 'property': 'value'},
   {'id': 'pos-label-22', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ConfusionMatrixComponent.component_callbacks.<locals>.update_confusionmatrix_graph(cutoff, normalized, binary, pos_label)>},
 {'inputs': [{'id': 'cumulative-precision-percentile-23', 'property': 'value'},
   {'id': 'pos-label-23', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.CumulativePrecisionComponent.component_callbacks.<locals>.update_cumulative_precision_graph(percentile, pos_label)>},
 {'inputs': [{'id': 'cumulative-precision-cutoff-23', 'property': 'value'},
   {'id': 'pos-label-23', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.CumulativePrecisionComponent.component_callbacks.<locals>.update_cumulative_precision_percentile(cutoff, pos_label)>},
 {'inputs': [{'id': 'liftcurve-cutoff-24', 'property': 'value'},
   {'id': 'liftcurve-percentage-24', 'property': 'value'},
   {'id': 'pos-label-24', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.LiftCurveComponent.component_callbacks.<locals>.update_precision_graph(cutoff, percentage, pos_label)>},
 {'inputs': [{'id': 'classification-cutoff-25', 'property': 'value'},
   {'id': 'classification-percentage-25', 'property': 'value'},
   {'id': 'pos-label-25', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassificationComponent.component_callbacks.<locals>.update_precision_graph(cutoff, percentage, pos_label)>},
 {'inputs': [{'id': 'rocauc-cutoff-26', 'property': 'value'},
   {'id': 'pos-label-26', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.RocAucComponent.component_callbacks.<locals>.update_precision_graph(cutoff, pos_label)>},
 {'inputs': [{'id': 'prauc-cutoff-27', 'property': 'value'},
   {'id': 'pos-label-27', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.PrAucComponent.component_callbacks.<locals>.update_precision_graph(cutoff, pos_label)>},
 {'inputs': [{'id': 'cutoffconnector-percentile-28', 'property': 'value'},
   {'id': 'pos-label-28', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.connectors.CutoffPercentileComponent.component_callbacks.<locals>.update_cutoff(percentile, pos_label)>},
 {'inputs': [{'id': 'cutoffconnector-cutoff-28', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.connectors.CutoffConnector.component_callbacks.<locals>.update_cutoffs(cutoff)>},
 {'inputs': [{'id': 'random-index-clas-button-30', 'property': 'n_clicks'}],
  'state': [{'id': 'random-index-clas-slider-30', 'property': 'value'},
   {'id': 'random-index-clas-labels-30', 'property': 'value'},
   {'id': 'random-index-clas-pred-or-perc-30', 'property': 'value'},
   {'id': 'pos-label-30', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassifierRandomIndexComponent.component_callbacks.<locals>.update_index(n_clicks, slider_range, labels, pred_or_perc, pos_label)>},
 {'inputs': [{'id': 'random-index-clas-pred-or-perc-30', 'property': 'value'},
   {'id': 'pos-label-30', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassifierRandomIndexComponent.component_callbacks.<locals>.update_slider_label(pred_or_perc, pos_label)>},
 {'inputs': [{'id': 'clas-prediction-index-31', 'property': 'value'},
   {'id': 'pos-label-31', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassifierPredictionSummaryComponent.component_callbacks.<locals>.update_output_div(index, pos_label)>},
 {'inputs': [{'id': 'contributions-graph-index-32', 'property': 'value'},
   {'id': 'contributions-graph-depth-32', 'property': 'value'},
   {'id': 'contributions-graph-sorting-32', 'property': 'value'},
   {'id': 'contributions-graph-orientation-32', 'property': 'value'},
   {'id': 'contributions-graph-group-cats-32', 'property': 'value'},
   {'id': 'pos-label-32', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapContributionsGraphComponent.component_callbacks.<locals>.update_output_div(index, depth, sort, orientation, cats, pos_label)>},
 {'inputs': [{'id': 'pdp-group-cats-33', 'property': 'value'}],
  'state': [{'id': 'pos-label-33', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.overview_components.PdpComponent.component_callbacks.<locals>.update_pdp_graph(cats, pos_label)>},
 {'inputs': [{'id': 'pdp-index-33', 'property': 'value'},
   {'id': 'pdp-col-33', 'property': 'value'},
   {'id': 'pdp-dropna-33', 'property': 'value'},
   {'id': 'pdp-sample-33', 'property': 'value'},
   {'id': 'pdp-gridlines-33', 'property': 'value'},
   {'id': 'pdp-gridpoints-33', 'property': 'value'},
   {'id': 'pos-label-33', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.overview_components.PdpComponent.component_callbacks.<locals>.update_pdp_graph(index, col, drop_na, sample, gridlines, gridpoints, pos_label)>},
 {'inputs': [{'id': 'contributions-table-index-34', 'property': 'value'},
   {'id': 'contributions-table-depth-34', 'property': 'value'},
   {'id': 'contributions-table-sorting-34', 'property': 'value'},
   {'id': 'contributions-table-group-cats-34', 'property': 'value'},
   {'id': 'pos-label-34', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapContributionsTableComponent.component_callbacks.<locals>.update_output_div(index, depth, sort, cats, pos_label)>},
 {'inputs': [{'id': 'random-index-clas-index-30', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.connectors.IndexConnector.component_callbacks.<locals>.update_indexes(index)>},
 {'inputs': [{'id': 'feature-input-index-40', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.overview_components.FeatureInputComponent.component_callbacks.<locals>.update_whatif_inputs(index)>},
 {'inputs': [{'id': 'random-index-clas-button-41', 'property': 'n_clicks'}],
  'state': [{'id': 'random-index-clas-slider-41', 'property': 'value'},
   {'id': 'random-index-clas-labels-41', 'property': 'value'},
   {'id': 'random-index-clas-pred-or-perc-41', 'property': 'value'},
   {'id': 'pos-label-41', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassifierRandomIndexComponent.component_callbacks.<locals>.update_index(n_clicks, slider_range, labels, pred_or_perc, pos_label)>},
 {'inputs': [{'id': 'random-index-clas-pred-or-perc-41', 'property': 'value'},
   {'id': 'pos-label-41', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassifierRandomIndexComponent.component_callbacks.<locals>.update_slider_label(pred_or_perc, pos_label)>},
 {'inputs': [{'id': 'pos-label-42', 'property': 'value'},
   {'id': 'feature-input-Sex-input-40', 'property': 'value'},
   {'id': 'feature-input-Deck-input-40', 'property': 'value'},
   {'id': 'feature-input-PassengerClass-input-40', 'property': 'value'},
   {'id': 'feature-input-Fare-input-40', 'property': 'value'},
   {'id': 'feature-input-Embarked-input-40', 'property': 'value'},
   {'id': 'feature-input-Age-input-40', 'property': 'value'},
   {'id': 'feature-input-No_of_siblings_plus_spouses_on_board-input-40',
    'property': 'value'},
   {'id': 'feature-input-No_of_parents_plus_children_on_board-input-40',
    'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassifierPredictionSummaryComponent.component_callbacks.<locals>.update_output_div(pos_label, *inputs)>},
 {'inputs': [{'id': 'contributions-graph-depth-43', 'property': 'value'},
   {'id': 'contributions-graph-sorting-43', 'property': 'value'},
   {'id': 'contributions-graph-orientation-43', 'property': 'value'},
   {'id': 'contributions-graph-group-cats-43', 'property': 'value'},
   {'id': 'pos-label-43', 'property': 'value'},
   {'id': 'feature-input-Sex-input-40', 'property': 'value'},
   {'id': 'feature-input-Deck-input-40', 'property': 'value'},
   {'id': 'feature-input-PassengerClass-input-40', 'property': 'value'},
   {'id': 'feature-input-Fare-input-40', 'property': 'value'},
   {'id': 'feature-input-Embarked-input-40', 'property': 'value'},
   {'id': 'feature-input-Age-input-40', 'property': 'value'},
   {'id': 'feature-input-No_of_siblings_plus_spouses_on_board-input-40',
    'property': 'value'},
   {'id': 'feature-input-No_of_parents_plus_children_on_board-input-40',
    'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapContributionsGraphComponent.component_callbacks.<locals>.update_output_div(depth, sort, orientation, cats, pos_label, *inputs)>},
 {'inputs': [{'id': 'contributions-table-depth-44', 'property': 'value'},
   {'id': 'contributions-table-sorting-44', 'property': 'value'},
   {'id': 'contributions-table-group-cats-44', 'property': 'value'},
   {'id': 'pos-label-44', 'property': 'value'},
   {'id': 'feature-input-Sex-input-40', 'property': 'value'},
   {'id': 'feature-input-Deck-input-40', 'property': 'value'},
   {'id': 'feature-input-PassengerClass-input-40', 'property': 'value'},
   {'id': 'feature-input-Fare-input-40', 'property': 'value'},
   {'id': 'feature-input-Embarked-input-40', 'property': 'value'},
   {'id': 'feature-input-Age-input-40', 'property': 'value'},
   {'id': 'feature-input-No_of_siblings_plus_spouses_on_board-input-40',
    'property': 'value'},
   {'id': 'feature-input-No_of_parents_plus_children_on_board-input-40',
    'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapContributionsTableComponent.component_callbacks.<locals>.update_output_div(depth, sort, cats, pos_label, *inputs)>},
 {'inputs': [{'id': 'pdp-group-cats-45', 'property': 'value'}],
  'state': [{'id': 'pos-label-45', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.overview_components.PdpComponent.component_callbacks.<locals>.update_pdp_graph(cats, pos_label)>},
 {'inputs': [{'id': 'pdp-col-45', 'property': 'value'},
   {'id': 'pdp-dropna-45', 'property': 'value'},
   {'id': 'pdp-sample-45', 'property': 'value'},
   {'id': 'pdp-gridlines-45', 'property': 'value'},
   {'id': 'pdp-gridpoints-45', 'property': 'value'},
   {'id': 'pos-label-45', 'property': 'value'},
   {'id': 'feature-input-Sex-input-40', 'property': 'value'},
   {'id': 'feature-input-Deck-input-40', 'property': 'value'},
   {'id': 'feature-input-PassengerClass-input-40', 'property': 'value'},
   {'id': 'feature-input-Fare-input-40', 'property': 'value'},
   {'id': 'feature-input-Embarked-input-40', 'property': 'value'},
   {'id': 'feature-input-Age-input-40', 'property': 'value'},
   {'id': 'feature-input-No_of_siblings_plus_spouses_on_board-input-40',
    'property': 'value'},
   {'id': 'feature-input-No_of_parents_plus_children_on_board-input-40',
    'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.overview_components.PdpComponent.component_callbacks.<locals>.update_pdp_graph(col, drop_na, sample, gridlines, gridpoints, pos_label, *inputs)>},
 {'inputs': [{'id': 'random-index-clas-index-41', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.connectors.IndexConnector.component_callbacks.<locals>.update_indexes(index)>},
 {'inputs': [{'id': 'shap-summary-graph-50', 'property': 'clickData'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapSummaryComponent.component_callbacks.<locals>.display_scatter_click_data(clickdata)>},
 {'inputs': [{'id': 'shap-summary-type-50', 'property': 'value'},
   {'id': 'shap-summary-group-cats-50', 'property': 'value'},
   {'id': 'shap-summary-depth-50', 'property': 'value'},
   {'id': 'shap-summary-index-50', 'property': 'value'},
   {'id': 'pos-label-50', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapSummaryComponent.component_callbacks.<locals>.update_shap_summary_graph(summary_type, cats, depth, index, pos_label)>},
 {'inputs': [{'id': 'shap-dependence-col-51', 'property': 'value'}],
  'state': [{'id': 'shap-dependence-group-cats-51', 'property': 'value'},
   {'id': 'pos-label-51', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapDependenceComponent.component_callbacks.<locals>.set_color_col_dropdown(col, cats, pos_label)>},
 {'inputs': [{'id': 'shap-dependence-color-col-51', 'property': 'value'},
   {'id': 'shap-dependence-index-51', 'property': 'value'},
   {'id': 'pos-label-51', 'property': 'value'}],
  'state': [{'id': 'shap-dependence-col-51', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapDependenceComponent.component_callbacks.<locals>.update_dependence_graph(color_col, index, pos_label, col)>},
 {'inputs': [{'id': 'shap-dependence-group-cats-51', 'property': 'value'}],
  'state': [{'id': 'shap-dependence-col-51', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapDependenceComponent.component_callbacks.<locals>.update_dependence_shap_scatter_graph(cats, old_col)>},
 {'inputs': [{'id': 'shap-summary-group-cats-50', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapSummaryDependenceConnector.component_callbacks.<locals>.update_dependence_shap_scatter_graph(cats)>},
 {'inputs': [{'id': 'shap-summary-graph-50', 'property': 'clickData'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.ShapSummaryDependenceConnector.component_callbacks.<locals>.display_scatter_click_data(clickdata)>},
 {'inputs': [{'id': 'interaction-summary-graph-60', 'property': 'clickData'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.InteractionSummaryComponent.component_callbacks.<locals>.display_scatter_click_data(clickdata)>},
 {'inputs': [{'id': 'interaction-summary-group-cats-60', 'property': 'value'},
   {'id': 'pos-label-60', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.InteractionSummaryComponent.component_callbacks.<locals>.update_interaction_scatter_graph(cats, pos_label)>},
 {'inputs': [{'id': 'interaction-summary-col-60', 'property': 'value'},
   {'id': 'interaction-summary-depth-60', 'property': 'value'},
   {'id': 'interaction-summary-type-60', 'property': 'value'},
   {'id': 'interaction-summary-index-60', 'property': 'value'},
   {'id': 'pos-label-60', 'property': 'value'},
   {'id': 'interaction-summary-group-cats-60', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.InteractionSummaryComponent.component_callbacks.<locals>.update_interaction_scatter_graph(col, depth, summary_type, index, pos_label, cats)>},
 {'inputs': [{'id': 'interaction-dependence-group-cats-61',
    'property': 'value'},
   {'id': 'pos-label-61', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.InteractionDependenceComponent.component_callbacks.<locals>.update_interaction_dependence_interact_col(cats, pos_label)>},
 {'inputs': [{'id': 'interaction-dependence-col-61', 'property': 'value'},
   {'id': 'pos-label-61', 'property': 'value'}],
  'state': [{'id': 'interaction-dependence-group-cats-61',
    'property': 'value'},
   {'id': 'interaction-dependence-interact-col-61', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.shap_components.InteractionDependenceComponent.component_callbacks.<locals>.update_interaction_dependence_interact_col(col, pos_label, cats, old_interact_col)>},
 {'inputs': [{'id': 'interaction-dependence-interact-col-61',
    'property': 'value'},
   {'id': 'interaction-dependence-index-61', 'property': 'value'},
   {'id': 'pos-label-61', 'property': 'value'},
   {'id': 'interaction-dependence-col-61', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.InteractionDependenceComponent.component_callbacks.<locals>.update_dependence_graph(interact_col, index, pos_label, col)>},
 {'inputs': [{'id': 'interaction-summary-group-cats-60', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.InteractionSummaryDependenceConnector.component_callbacks.<locals>.update_dependence_shap_scatter_graph(cats)>},
 {'inputs': [{'id': 'interaction-summary-col-60', 'property': 'value'},
   {'id': 'interaction-summary-graph-60', 'property': 'clickData'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.shap_components.InteractionSummaryDependenceConnector.component_callbacks.<locals>.update_interact_col_highlight(col, clickdata)>},
 {'inputs': [{'id': 'decisiontrees-index-70', 'property': 'value'},
   {'id': 'decisiontrees-highlight-70', 'property': 'value'},
   {'id': 'pos-label-70', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.decisiontree_components.DecisionTreesComponent.component_callbacks.<locals>.update_tree_graph(index, highlight, pos_label)>},
 {'inputs': [{'id': 'decisiontrees-graph-70', 'property': 'clickData'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.decisiontree_components.DecisionTreesComponent.component_callbacks.<locals>.update_highlight(clickdata)>},
 {'inputs': [{'id': 'decisionpath-table-index-71', 'property': 'value'},
   {'id': 'decisionpath-table-highlight-71', 'property': 'value'},
   {'id': 'pos-label-71', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.decisiontree_components.DecisionPathTableComponent.component_callbacks.<locals>.update_decisiontree_table(index, highlight, pos_label)>},
 {'inputs': [{'id': 'random-index-clas-button-72', 'property': 'n_clicks'}],
  'state': [{'id': 'random-index-clas-slider-72', 'property': 'value'},
   {'id': 'random-index-clas-labels-72', 'property': 'value'},
   {'id': 'random-index-clas-pred-or-perc-72', 'property': 'value'},
   {'id': 'pos-label-72', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassifierRandomIndexComponent.component_callbacks.<locals>.update_index(n_clicks, slider_range, labels, pred_or_perc, pos_label)>},
 {'inputs': [{'id': 'random-index-clas-pred-or-perc-72', 'property': 'value'},
   {'id': 'pos-label-72', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.classifier_components.ClassifierRandomIndexComponent.component_callbacks.<locals>.update_slider_label(pred_or_perc, pos_label)>},
 {'inputs': [{'id': 'decisionpath-button-73', 'property': 'n_clicks'}],
  'state': [{'id': 'decisionpath-index-73', 'property': 'value'},
   {'id': 'decisionpath-highlight-73', 'property': 'value'},
   {'id': 'pos-label-73', 'property': 'value'}],
  'callback': <function explainerdashboard.dashboard_components.decisiontree_components.DecisionPathGraphComponent.component_callbacks.<locals>.update_tree_graph(n_clicks, index, highlight, pos_label)>},
 {'inputs': [{'id': 'random-index-clas-index-72', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.connectors.IndexConnector.component_callbacks.<locals>.update_indexes(index)>},
 {'inputs': [{'id': 'decisiontrees-highlight-70', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.connectors.HighlightConnector.component_callbacks.<locals>.update_highlights(highlight)>},
 {'inputs': [{'id': 'pos-label-0', 'property': 'value'}],
  'state': [],
  'callback': <function explainerdashboard.dashboard_components.connectors.PosLabelConnector.component_callbacks.<locals>.update_pos_labels(pos_label)>}]

@moeller84
Copy link

I am using the default dashboard, and just switch things off and on in the dashboard.yaml file.

So when i do

dashboard = ExplainerDashboard(explainer, server=app, url_base_pathname="/dashboard/", **params)
list(dashboard.app.callback_map.values())

i get the following output, which seems to have the uuid extension.
[{'inputs': [{'id': 'random-index-clas-button-DKVkx0', 'property': 'n_clicks'}], 'state': [{'id': 'random-index-clas-slider-DKVkx0', 'property': 'value'}, {'id': 'random-index-clas-labels-DKVkx0', 'property': 'value'}, {'id': 'random-index-clas-pred-or-perc-DKVkx0', 'property': 'value'}, {'id': 'pos-label-DKVkx0', 'property': 'value'}], 'callback': <function ClassifierRandomIndexComponent.component_callbacks.<locals>.update_index at 0x7efd29c67200>}, {'inputs': [{'id': 'random-index-clas-pred-or-perc-DKVkx0', 'property': 'value'}, {'id': 'pos-label-DKVkx0', 'property': 'value'}], 'state': [], 'callback': <function ClassifierRandomIndexComponent.component_callbacks.<locals>.update_slider_label at 0x7efd29c673b0>}, {'inputs': [{'id': 'clas-prediction-index-DKVkx1', 'property': 'value'}, {'id': 'pos-label-DKVkx1', 'property': 'value'}], 'state': [], 'callback': <function ClassifierPredictionSummaryComponent.component_callbacks.<locals>.update_output_div at 0x7efd29c674d0>}, {'inputs': [{'id': 'contributions-graph-index-DKVkx2', 'property': 'value'}, {'id': 'contributions-graph-depth-DKVkx2', 'property': 'value'}, {'id': 'contributions-graph-sorting-DKVkx2', 'property': 'value'}, {'id': 'contributions-graph-orientation-DKVkx2', 'property': 'value'}, {'id': 'contributions-graph-group-cats-DKVkx2', 'property': 'value'}, {'id': 'pos-label-DKVkx2', 'property': 'value'}], 'state': [], 'callback': <function ShapContributionsGraphComponent.component_callbacks.<locals>.update_output_div at 0x7efd29c675f0>}, {'inputs': [{'id': 'pdp-group-cats-DKVkx3', 'property': 'value'}], 'state': [{'id': 'pos-label-DKVkx3', 'property': 'value'}], 'callback': <function PdpComponent.component_callbacks.<locals>.update_pdp_graph at 0x7efd29c67710>}, {'inputs': [{'id': 'pdp-index-DKVkx3', 'property': 'value'}, {'id': 'pdp-col-DKVkx3', 'property': 'value'}, {'id': 'pdp-dropna-DKVkx3', 'property': 'value'}, {'id': 'pdp-sample-DKVkx3', 'property': 'value'}, {'id': 'pdp-gridlines-DKVkx3', 'property': 'value'}, {'id': 'pdp-gridpoints-DKVkx3', 'property': 'value'}, {'id': 'pos-label-DKVkx3', 'property': 'value'}], 'state': [], 'callback': <function PdpComponent.component_callbacks.<locals>.update_pdp_graph at 0x7efd29c677a0>}, {'inputs': [{'id': 'contributions-table-index-DKVkx4', 'property': 'value'}, {'id': 'contributions-table-depth-DKVkx4', 'property': 'value'}, {'id': 'contributions-table-sorting-DKVkx4', 'property': 'value'}, {'id': 'contributions-table-group-cats-DKVkx4', 'property': 'value'}, {'id': 'pos-label-DKVkx4', 'property': 'value'}], 'state': [], 'callback': <function ShapContributionsTableComponent.component_callbacks.<locals>.update_output_div at 0x7efd29c678c0>}, {'inputs': [{'id': 'random-index-clas-index-DKVkx0', 'property': 'value'}], 'state': [], 'callback': <function IndexConnector.component_callbacks.<locals>.update_indexes at 0x7efd29c679e0>}, {'inputs': [{'id': 'feature-input-index-a4zhT0', 'property': 'value'}], 'state': [], 'callback': <function FeatureInputComponent.component_callbacks.<locals>.update_whatif_inputs at 0x7efd29c67b00>}, {'inputs': [{'id': 'random-index-clas-button-a4zhT1', 'property': 'n_clicks'}], 'state': [{'id': 'random-index-clas-slider-a4zhT1', 'property': 'value'}, {'id': 'random-index-clas-labels-a4zhT1', 'property': 'value'}, {'id': 'random-index-clas-pred-or-perc-a4zhT1', 'property': 'value'}, {'id': 'pos-label-a4zhT1', 'property': 'value'}], 'callback': <function ClassifierRandomIndexComponent.component_callbacks.<locals>.update_index at 0x7efd29c67c20>}, {'inputs': [{'id': 'random-index-clas-pred-or-perc-a4zhT1', 'property': 'value'}, {'id': 'pos-label-a4zhT1', 'property': 'value'}], 'state': [], 'callback': <function ClassifierRandomIndexComponent.component_callbacks.<locals>.update_slider_label at 0x7efd29c67dd0>}, {'inputs': [{'id': 'pos-label-a4zhT2', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_mitkundeoverblik_login-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-rejse_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_bestandspraemie-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-alder-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-avg_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_tilbud-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-max_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-nyeste_forsikringsprodukt-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_nyeste_forsikringsprodukt-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_rejse-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_rejse-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-min_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-uddannelsesfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-indbo_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_skade-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_personbil-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_indbo-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-postcode-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ejd_vaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-bebo_arl-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personbil_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-habitation_zone_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-bilfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ejerforholdsfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ulykke_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-conzoom_gruppe_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_indbo-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-boernefaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-civilstandsfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-rejse_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_personbil-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-boligtypefaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-conzoom_type_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-formuefaktor_v2-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-seneste_salgskanal_mds-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personindkomstfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-indbo_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personbil_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_policelinje-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ulykke_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-husstandsindkomstfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_acc-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_police-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-beskaeftigelsesfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_svar_nps-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-aldersfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_forste_varsling-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motor_acc_1y-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_barn_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_barn_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_varsling-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_personbil_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_rejse_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_personbil_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_rejse_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_motorcykel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motorcykel_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-max_nps_svar-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-min_nps_svar-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-seneste_svar_nps-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-barn_ulykke_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_nyvaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_aarlig_koersel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_personbil_nyvaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_aarlig_koersel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_indbo_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-motorcykel_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-barn_ulykke_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_indbo_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_segment-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_ulykke_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_varslinger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_ulykke_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_marketing_segment-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motorcykel_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_biler_i_huset-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-unikke_varslinger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-motorcykel_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_motorcykel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_forste_redning-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_redninger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_redning-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-unikke_redninger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_barn_ulykke_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_personer-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_barn_ulykke_1m-input-a4zhT0', 'property': 'value'}], 'state': [], 'callback': <function ClassifierPredictionSummaryComponent.component_callbacks.<locals>.update_output_div at 0x7efd29c67e60>}, {'inputs': [{'id': 'contributions-graph-depth-a4zhT3', 'property': 'value'}, {'id': 'contributions-graph-sorting-a4zhT3', 'property': 'value'}, {'id': 'contributions-graph-orientation-a4zhT3', 'property': 'value'}, {'id': 'contributions-graph-group-cats-a4zhT3', 'property': 'value'}, {'id': 'pos-label-a4zhT3', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_mitkundeoverblik_login-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-rejse_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_bestandspraemie-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-alder-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-avg_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_tilbud-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-max_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-nyeste_forsikringsprodukt-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_nyeste_forsikringsprodukt-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_rejse-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_rejse-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-min_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-uddannelsesfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-indbo_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_skade-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_personbil-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_indbo-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-postcode-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ejd_vaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-bebo_arl-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personbil_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-habitation_zone_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-bilfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ejerforholdsfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ulykke_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-conzoom_gruppe_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_indbo-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-boernefaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-civilstandsfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-rejse_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_personbil-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-boligtypefaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-conzoom_type_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-formuefaktor_v2-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-seneste_salgskanal_mds-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personindkomstfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-indbo_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personbil_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_policelinje-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ulykke_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-husstandsindkomstfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_acc-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_police-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-beskaeftigelsesfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_svar_nps-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-aldersfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_forste_varsling-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motor_acc_1y-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_barn_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_barn_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_varsling-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_personbil_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_rejse_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_personbil_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_rejse_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_motorcykel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motorcykel_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-max_nps_svar-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-min_nps_svar-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-seneste_svar_nps-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-barn_ulykke_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_nyvaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_aarlig_koersel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_personbil_nyvaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_aarlig_koersel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_indbo_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-motorcykel_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-barn_ulykke_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_indbo_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_segment-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_ulykke_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_varslinger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_ulykke_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_marketing_segment-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motorcykel_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_biler_i_huset-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-unikke_varslinger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-motorcykel_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_motorcykel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_forste_redning-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_redninger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_redning-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-unikke_redninger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_barn_ulykke_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_personer-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_barn_ulykke_1m-input-a4zhT0', 'property': 'value'}], 'state': [], 'callback': <function ShapContributionsGraphComponent.component_callbacks.<locals>.update_output_div at 0x7efd29bc6050>}, {'inputs': [{'id': 'contributions-table-depth-a4zhT4', 'property': 'value'}, {'id': 'contributions-table-sorting-a4zhT4', 'property': 'value'}, {'id': 'contributions-table-group-cats-a4zhT4', 'property': 'value'}, {'id': 'pos-label-a4zhT4', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_mitkundeoverblik_login-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-rejse_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_bestandspraemie-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-alder-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-avg_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_tilbud-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-max_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-nyeste_forsikringsprodukt-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_nyeste_forsikringsprodukt-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_rejse-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_rejse-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-min_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-uddannelsesfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-indbo_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_skade-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_personbil-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_indbo-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-postcode-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ejd_vaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-bebo_arl-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personbil_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-habitation_zone_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-bilfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ejerforholdsfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ulykke_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-conzoom_gruppe_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_indbo-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-boernefaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-civilstandsfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-rejse_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_personbil-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-boligtypefaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-conzoom_type_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-formuefaktor_v2-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-seneste_salgskanal_mds-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personindkomstfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-indbo_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personbil_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_policelinje-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ulykke_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-husstandsindkomstfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_acc-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_police-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-beskaeftigelsesfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_svar_nps-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-aldersfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_forste_varsling-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motor_acc_1y-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_barn_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_barn_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_varsling-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_personbil_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_rejse_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_personbil_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_rejse_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_motorcykel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motorcykel_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-max_nps_svar-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-min_nps_svar-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-seneste_svar_nps-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-barn_ulykke_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_nyvaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_aarlig_koersel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_personbil_nyvaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_aarlig_koersel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_indbo_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-motorcykel_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-barn_ulykke_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_indbo_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_segment-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_ulykke_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_varslinger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_ulykke_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_marketing_segment-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motorcykel_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_biler_i_huset-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-unikke_varslinger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-motorcykel_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_motorcykel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_forste_redning-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_redninger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_redning-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-unikke_redninger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_barn_ulykke_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_personer-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_barn_ulykke_1m-input-a4zhT0', 'property': 'value'}], 'state': [], 'callback': <function ShapContributionsTableComponent.component_callbacks.<locals>.update_output_div at 0x7efd29bc6170>}, {'inputs': [{'id': 'pdp-group-cats-a4zhT5', 'property': 'value'}], 'state': [{'id': 'pos-label-a4zhT5', 'property': 'value'}], 'callback': <function PdpComponent.component_callbacks.<locals>.update_pdp_graph at 0x7efd29bc6290>}, {'inputs': [{'id': 'pdp-col-a4zhT5', 'property': 'value'}, {'id': 'pdp-dropna-a4zhT5', 'property': 'value'}, {'id': 'pdp-sample-a4zhT5', 'property': 'value'}, {'id': 'pdp-gridlines-a4zhT5', 'property': 'value'}, {'id': 'pdp-gridpoints-a4zhT5', 'property': 'value'}, {'id': 'pos-label-a4zhT5', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_mitkundeoverblik_login-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-rejse_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_bestandspraemie-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-alder-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-avg_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_tilbud-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-max_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-nyeste_forsikringsprodukt-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_nyeste_forsikringsprodukt-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_rejse-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_rejse-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-min_police_levetid-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-uddannelsesfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-indbo_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_skade-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_personbil-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_indbo-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-postcode-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ejd_vaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-bebo_arl-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personbil_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-habitation_zone_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-bilfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ejerforholdsfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ulykke_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-conzoom_gruppe_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_indbo-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-boernefaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-civilstandsfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-rejse_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_personbil-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-boligtypefaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-conzoom_type_navn-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-formuefaktor_v2-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-seneste_salgskanal_mds-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personindkomstfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-indbo_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-personbil_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_policelinje-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-ulykke_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-husstandsindkomstfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_acc-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_police-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-beskaeftigelsesfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_svar_nps-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-aldersfaktor-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_forste_varsling-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motor_acc_1y-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_barn_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_barn_ulykke-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_varsling-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_personbil_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_rejse_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_personbil_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_rejse_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_nysalg_motorcykel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motorcykel_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-max_nps_svar-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-min_nps_svar-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-seneste_svar_nps-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-barn_ulykke_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_nyvaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_aarlig_koersel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_personbil_nyvaerdi-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-sum_aarlig_koersel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_indbo_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-motorcykel_seneste_salgsdato_id-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-barn_ulykke_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_indbo_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_segment-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-AU_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_ulykke_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_varslinger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_ulykke_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-mest_brugte_bil_marketing_segment-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_motorcykel_1m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_biler_i_huset-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-unikke_varslinger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-motorcykel_antal-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-har_haft_motorcykel-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_forste_redning-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-HU_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_redninger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-dage_siden_seneste_redning-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-unikke_redninger-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_barn_ulykke_3m-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-RP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_neg-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_pos-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-UP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-FP_overall-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_personer-input-a4zhT0', 'property': 'value'}, {'id': 'feature-input-antal_tilbud_barn_ulykke_1m-input-a4zhT0', 'property': 'value'}], 'state': [], 'callback': <function PdpComponent.component_callbacks.<locals>.update_pdp_graph at 0x7efd29bc63b0>}, {'inputs': [{'id': 'random-index-clas-index-a4zhT1', 'property': 'value'}], 'state': [], 'callback': <function IndexConnector.component_callbacks.<locals>.update_indexes at 0x7efd29bc6440>}, {'inputs': [{'id': 'shap-summary-graph-64E3o0', 'property': 'clickData'}], 'state': [], 'callback': <function ShapSummaryComponent.component_callbacks.<locals>.display_scatter_click_data at 0x7efd29bc6560>}, {'inputs': [{'id': 'shap-summary-type-64E3o0', 'property': 'value'}, {'id': 'shap-summary-group-cats-64E3o0', 'property': 'value'}, {'id': 'shap-summary-depth-64E3o0', 'property': 'value'}, {'id': 'shap-summary-index-64E3o0', 'property': 'value'}, {'id': 'pos-label-64E3o0', 'property': 'value'}], 'state': [], 'callback': <function ShapSummaryComponent.component_callbacks.<locals>.update_shap_summary_graph at 0x7efd29bc6680>}, {'inputs': [{'id': 'shap-dependence-col-64E3o1', 'property': 'value'}], 'state': [{'id': 'shap-dependence-group-cats-64E3o1', 'property': 'value'}, {'id': 'pos-label-64E3o1', 'property': 'value'}], 'callback': <function ShapDependenceComponent.component_callbacks.<locals>.set_color_col_dropdown at 0x7efd29bc67a0>}, {'inputs': [{'id': 'shap-dependence-color-col-64E3o1', 'property': 'value'}, {'id': 'shap-dependence-index-64E3o1', 'property': 'value'}, {'id': 'pos-label-64E3o1', 'property': 'value'}], 'state': [{'id': 'shap-dependence-col-64E3o1', 'property': 'value'}], 'callback': <function ShapDependenceComponent.component_callbacks.<locals>.update_dependence_graph at 0x7efd29bc6950>}, {'inputs': [{'id': 'shap-dependence-group-cats-64E3o1', 'property': 'value'}], 'state': [{'id': 'shap-dependence-col-64E3o1', 'property': 'value'}], 'callback': <function ShapDependenceComponent.component_callbacks.<locals>.update_dependence_shap_scatter_graph at 0x7efd29bc6a70>}, {'inputs': [{'id': 'shap-summary-group-cats-64E3o0', 'property': 'value'}], 'state': [], 'callback': <function ShapSummaryDependenceConnector.component_callbacks.<locals>.update_dependence_shap_scatter_graph at 0x7efd29bc6b00>}, {'inputs': [{'id': 'shap-summary-graph-64E3o0', 'property': 'clickData'}], 'state': [], 'callback': <function ShapSummaryDependenceConnector.component_callbacks.<locals>.display_scatter_click_data at 0x7efd29bc6c20>}, {'inputs': [{'id': 'pos-label-0', 'property': 'value'}], 'state': [], 'callback': <function PosLabelConnector.component_callbacks.<locals>.update_pos_labels at 0x7efd29bc6d40>}]

@oegedijk
Copy link
Owner

Are you sure you're on the latest (pypi) version (0.2.17)?

In case you're installing through conda: the conda version is a bit behind (0.2.15) because we're dealing with some conda-forge dependency conflicts, and that version has not yet has the uuid fix implemented.

@moeller84
Copy link

Im quite sure it is version 0.2.17 we are deploying through docker

looking in indexes: http://artifactory-singlep.p001.alm.brand.dk/artifactory/api/pypi/pypi-virtual/simple Processing /project Requirement already satisfied: explainerdashboard in /opt/venv/lib/python3.7/site-packages (from for-p-afgang-dashboard==0.1.0) (**_0.2.17_**)

but we are also building a venv. So maybe that messes something up?

@oegedijk
Copy link
Owner

Shouldn't mess things up, using virtual envs myself. Only thing I can think of right now is that you have a cached build step from the docker build that is still using 0.2.15. So could try to prune the cache and see if that helps.

Will build a dashboard myself inside a docker container, and see if I run into the same issue. Do you have a reproducible example with Dockerfile that generates the error? (can just be with the titanic dataset)

@oegedijk
Copy link
Owner

This seems to work fine, with no uuid strings. Haven't tried with docker swarm though:

generate_dashboard.py

from sklearn.ensemble import RandomForestClassifier

from explainerdashboard import *
from explainerdashboard.datasets import *

X_train, y_train, X_test, y_test = titanic_survive()
model = RandomForestClassifier(n_estimators=50, max_depth=5)
model.fit(X_train, y_train)

explainer = ClassifierExplainer(model, X_test, y_test, 
                                 cats=["Sex", 'Deck', 'Embarked'],
                                 labels=['Not Survived', 'Survived'],
                                 descriptions=feature_descriptions)

db = ExplainerDashboard(explainer)

db.to_yaml("dashboard.yaml", explainerfile="explainer.joblib", dump_explainer=True)

run_dashboard.py

import waitress
from explainerdashboard import *

db = ExplainerDashboard.from_config("dashboard.yaml")

print(list(db.app.callback_map.values()))

if __name__ == "__main__":
    waitress.serve(db.app.server, host='0.0.0.0', port=9050)

Dockerfile

FROM python:3.8

RUN pip install explainerdashboard

COPY generate_dashboard.py ./
COPY run_dashboard.py ./

RUN python generate_dashboard.py

EXPOSE 9050
CMD ["python", "./run_dashboard.py"]
$ docker build -t explainerdashboard .
$ docker run -p 9050:9050 explainerdashboard

@oegedijk
Copy link
Owner

oegedijk commented Jan 5, 2021

@moeller84 Did you manage to get it to work?

@moeller84
Copy link

@moeller84 Did you manage to get it to work?

Sorry. No i did not, unfortunatly.

@moeller84
Copy link

moeller84 commented Jan 5, 2021

When i read the source code it seems like you are still generating uuids when name is None, but then just suffixing a number in the end.

EDIT: when i recreate your example from above i dont get the generated uuids.

@oegedijk
Copy link
Owner

oegedijk commented Jan 5, 2021

Yes, each composite (base for a tab), simply adds a number to the end to self.name, e.g.

class ImportancesComposite(ExplainerComponent):
    def __init__(self, explainer, title="Feature Importances", name=None,
                    hide_importances=False,
                    hide_selector=True, **kwargs):
        """Overview tab of feature importances

        Can show both permutation importances and mean absolute shap values.

        Args:
            explainer (Explainer): explainer object constructed with either
                        ClassifierExplainer() or RegressionExplainer()
            title (str, optional): Title of tab or page. Defaults to 
                        "Feature Importances".
            name (str, optional): unique name to add to Component elements. 
                        If None then random uuid is generated to make sure 
                        it's unique. Defaults to None.
            hide_importances (bool, optional): hide the ImportancesComponent
            hide_selector (bool, optional): hide the post label selector. 
                Defaults to True.
        """
        super().__init__(explainer, title, name)

        self.importances = ImportancesComponent(
                explainer, name=self.name+"0", hide_selector=hide_selector, **kwargs)

    def layout(self):
        return html.Div([
            dbc.Row([
                make_hideable(
                    dbc.Col([
                        self.importances.layout(),
                    ]), hide=self.hide_importances),
            ], style=dict(margin=25))
        ])

Then ExplainerDashboard instantiates all the tabs using ExplainerTabsLayout, which has the line

self.tabs  = [instantiate_component(tab, explainer, name=str(i+1), **kwargs) for i, tab in enumerate(tabs)]

So each tab gets the name "1", "2", 3", etc. And then each subcomponent gets the name "11", "12", etc.

Are you defining custom components? Or defining them before you add them to ExplainerDashboard?

E.g.

tab = ImportancesComposite()
ExplainerDashboard(tab).run

Would result in a random uuid name for tab

@moeller84
Copy link

Hi
We tried running the dashboard on a single container. That works. But running on multiple containers / swarm gives cause to the problem.

@oegedijk
Copy link
Owner

So in a swarm it starts generating uuid names but in a single container it doesn't?

That seems super strange... Again, the only thing I can think of is old versions of explainerdashboard in a cached docker layer.

@oegedijk
Copy link
Owner

I'm gonna see if I can build some diagnostic functionality that makes it easier to see the whole component tree, including .name properties, and would also give a warning when it detects any uuid .name...

@moeller84
Copy link

it also does generate uuid names with a single container. But it seems that callback names are being mixed when running on more than one container

@oegedijk
Copy link
Owner

ah, okay, that at least is an easier to understand problem. So the example I gave you didn't give uuid names right?

Is there any code you can share on how you generate the dashboard? Because you have to be doing something custom otherwise it would just work out of the box.

@moeller84
Copy link

dashboard.yaml

dashboard:
  explainerfile: data/processed/explainer.joblib
  params:
    title: Fastholdelses model
    hide_header: false
    hide_shapsummary: false
    header_hide_title: false
    header_hide_selector: false
    block_selector_callbacks: false
    pos_label: null
    fluid: true
    mode: dash
    width: 1000
    height: 800
    external_stylesheets: null
#    server: true
#    url_base_pathname: null
    responsive: true
    logins: null
    port: 8050
    tabs:
    #- importances
    #- model_summary
    - contributions
    - whatif
    - shap_dependence
    #- shap_interaction
    #- decision_trees

__init__.py

import logging
from pathlib import Path
from flask import Flask

from for_p_afgang_dashboard.extensions import setup_extensions
from explainerdashboard import ClassifierExplainer, ExplainerDashboard
import yaml

# Metadata for the package
# fmt: off
__version__ = "0.1.0"
__url__ = "https://lspgitlab01.alm.brand.dk/advanced-analytics/for_p_afgang_dashboard"
__description__ = "explainer dashboard for for_p_afgang model performance"
__author__ = "Niels Møller-Hansen"
__email__ = "abnimo@almbrand.dk"
# fmt: on

logger = logging.getLogger("api_logger")
file_path = Path(__file__)


def create_app(config):
    logger.info("Starting app...")
    logger.debug(f"Using config {config}")
    app = Flask("for_p_afgang_dashboard")
    app.config.from_object(config)

    @app.route("/health")
    def healthcheck():
        return "Healthy", 200

    setup_extensions(app)

    dashboard_yaml_path = file_path.parent.joinpath("dashboard.yaml")
    explainerfile = str(file_path.parent.joinpath("data").joinpath("explainer.joblib"))
    logger.info(explainerfile)
    config = yaml.safe_load(open(dashboard_yaml_path, "r"))
    params = config["dashboard"]["params"]
    explainer = ClassifierExplainer.from_file(explainerfile)
    print("X:", len(explainer.X))
    logger.info(f"Explainer contains {len(explainer.X)} samples")
    dashboard = ExplainerDashboard(
        explainer, server=app, url_base_pathname="/", **params
    )
    print(list(dashboard.app.callback_map.values()))

    @app.route("/")
    def return_dashboard():
        return dashboard.app.index()

    logger.info("Explainer dashboard loaded")
    return app

@oegedijk
Copy link
Owner

oegedijk commented Jan 12, 2021

Ah, I think I got it!

In the yaml I see:

tabs:
    #- importances
    #- model_summary
    - contributions
    - whatif
    - shap_dependence
    #- shap_interaction
    #- decision_trees

So that equates to ExplainerDashboard(explainer, ["contributions", "whatif", "shap_dependence"]).

The string tab indicators get converted by

def _convert_str_tabs(self, component):

def _convert_str_tabs(self, component):
        if isinstance(component, str):
            if component == 'importances':
                return ImportancesTab
            elif component == 'model_summary':
                return ModelSummaryTab
            elif component == 'contributions':
                return ContributionsTab
            elif component == 'whatif':
                return WhatIfTab
            elif component == 'shap_dependence':
                return ShapDependenceTab
            elif component == 'shap_interaction':
                return ShapInteractionsTab
            elif component == 'decision_trees':
                return  DecisionTreesTab
        return component

These ImportancesTab, ModelSummaryTab, have actually been deprecated. They are only there for backward compatibility reasons: they have been deprecated in favor of ImportancesComposite, etc, but I had not adjusted this helper method. So I will fix this in the next release, but in the meanwhile, I think if you change dashboard.yaml to:

dashboard:
  explainerfile: data/processed/explainer.joblib
  params:
    title: Fastholdelses model
    hide_header: false
    hide_shapsummary: false
    header_hide_title: false
    header_hide_selector: false
    block_selector_callbacks: false
    pos_label: null
    fluid: true
    mode: dash
    width: 1000
    height: 800
    external_stylesheets: null
#    server: true
#    url_base_pathname: null
    responsive: true
    logins: null
    port: 8050
    importances: false
    model_summary: false
    shap_interaction: false
    decision_trees: false

So this is equivalent of passing booleans to switch off tabs: ExplainerDashboard(explainer, importances=False, model_summary=False, shap_interaction=False, decision_trees=False)

@oegedijk
Copy link
Owner

Just released https://github.com/oegedijk/explainerdashboard/releases/tag/v0.2.20 which should fix this issue...

@oegedijk
Copy link
Owner

I think you can also simplify the loading of the dashboard:

def create_app(config):
    logger.info("Starting app...")
    logger.debug(f"Using config {config}")
    app = Flask("for_p_afgang_dashboard")
    app.config.from_object(config)

    @app.route("/health")
    def healthcheck():
        return "Healthy", 200

    setup_extensions(app)

    explainerfile = str(file_path.parent.joinpath("data").joinpath("explainer.joblib"))
    dashboard_yaml_path = file_path.parent.joinpath("dashboard.yaml")
    logger.info(explainerfile)
    
    dashboard = ExplainerDashboard.from_config(
        explainerfile , dashboard_yaml_path, server=app, url_base_pathname="/")
    logger.info(f"Explainer contains {len(dashboard.explainer)} samples")
    print(list(dashboard.app.callback_map.values()))

    @app.route("/")
    def return_dashboard():
        return dashboard.app.index()

    logger.info("Explainer dashboard loaded")
    return app

@moeller84
Copy link

i updated to the latest version and also altered the .yaml file. That leaves me with this error (having touched anything else):

Traceback (most recent call last):
  File "/home/niels/.pyenv/versions/3.7.9/envs/for_p_afgang_dashboard/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/home/niels/.pyenv/versions/3.7.9/envs/for_p_afgang_dashboard/lib/python3.7/site-packages/flask/cli.py", line 184, in find_app_by_string
    app = call_factory(script_info, attr, args)
  File "/home/niels/.pyenv/versions/3.7.9/envs/for_p_afgang_dashboard/lib/python3.7/site-packages/flask/cli.py", line 115, in call_factory
    return app_factory(*arguments)
  File "/home/niels/projektmappe/for_p_afgang_dashboard/app/for_p_afgang_dashboard/__init__.py", line 43, in create_app
    explainer, server=app, url_base_pathname="/", **params
  File "/home/niels/.pyenv/versions/3.7.9/envs/for_p_afgang_dashboard/lib/python3.7/site-packages/explainerdashboard/dashboards.py", line 465, in __init__
    fluid=fluid))
  File "/home/niels/.pyenv/versions/3.7.9/envs/for_p_afgang_dashboard/lib/python3.7/site-packages/explainerdashboard/dashboards.py", line 88, in __init__
    self.tabs  = [instantiate_component(tab, explainer, name=str(i+1), **kwargs) for i, tab in enumerate(tabs)]
  File "/home/niels/.pyenv/versions/3.7.9/envs/for_p_afgang_dashboard/lib/python3.7/site-packages/explainerdashboard/dashboards.py", line 88, in <listcomp>
    self.tabs  = [instantiate_component(tab, explainer, name=str(i+1), **kwargs) for i, tab in enumerate(tabs)]
  File "/home/niels/.pyenv/versions/3.7.9/envs/for_p_afgang_dashboard/lib/python3.7/site-packages/explainerdashboard/dashboard_methods.py", line 431, in instantiate_component
    component = component(explainer, name=name, **kwargs)
  File "/home/niels/.pyenv/versions/3.7.9/envs/for_p_afgang_dashboard/lib/python3.7/site-packages/explainerdashboard/dashboard_components/composites.py", line 271, in __init__
    hide_selector=hide_selector, **kwargs)
  File "/home/niels/.pyenv/versions/3.7.9/envs/for_p_afgang_dashboard/lib/python3.7/site-packages/explainerdashboard/dashboard_components/shap_components.py", line 1027, in __init__
    if not self.explainer.onehot_cols:
AttributeError: 'XGBClassifierExplainer' object has no attribute 'onehot_cols'

@oegedijk
Copy link
Owner

ah, yeah, you have to rebuild the explainer with the new version: I made some breaking changes how categorical features and one hot encoded features are handled internally in order to support categorical features. (on the plus side: categorical features are supported now!)

@carlryn
Copy link

carlryn commented Jan 12, 2021

Is there a reason why you are using UUIDs in the first place? Thinking you could just set seed and do randomization with numbers to get deterministic names.

E.g line 177 in dashboard_methods.py
if not hasattr(self, "name") or self.name is None: self.name = name or "uuid"+shortuuid.ShortUUID().random(length=5)

@oegedijk
Copy link
Owner

Original goal was to generate a unique name that is both short and url-friendly (planning on adding querystring support at some point). But I guess that could be done simpler and without the shortuuid dependency, e.g.: https://proinsias.github.io/til/Python-UUID-generate-random-but-reproducible-with-seed/

Got a code suggestion?

@oegedijk
Copy link
Owner

Is it working now? Shall I close the issue?

@carlryn
Copy link

carlryn commented Jan 20, 2021

This seems to be working now! Ran with several workers on gunicorn and also saved callback id names which all matches.

@oegedijk
Copy link
Owner

Awesome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants