├── .github
└── workflows
│ ├── issue_workflow.yml
│ └── publish.yml
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── __init__.py
├── _extensions
├── kmasiello
│ └── positconfslides
│ │ ├── _extension.yml
│ │ ├── assets
│ │ ├── backgrounds
│ │ │ ├── 30-70-dark.svg
│ │ │ ├── 30-70-light.svg
│ │ │ ├── 30-70-white.svg
│ │ │ ├── brackets-dark.svg
│ │ │ ├── brackets-light.svg
│ │ │ ├── content-dark.svg
│ │ │ ├── content-light.svg
│ │ │ ├── content2-light.svg
│ │ │ ├── toc-dark.svg
│ │ │ ├── toc-light.svg
│ │ │ ├── toc-people-dark.svg
│ │ │ ├── toc-people-light.svg
│ │ │ ├── toc2-dark.svg
│ │ │ └── toc2-light.svg
│ │ └── posit-slides.js
│ │ ├── custom.scss
│ │ └── positconfslides.lua
├── quarto-ext
│ └── shinylive
│ │ ├── README.md
│ │ ├── _extension.yml
│ │ ├── resources
│ │ └── css
│ │ │ └── shinylive-quarto.css
│ │ └── shinylive.lua
└── shafayetShafee
│ └── bsicons
│ ├── _extension.yml
│ ├── assets
│ ├── css
│ │ └── all.css
│ └── webfonts
│ │ ├── bootstrap-icons.woff
│ │ └── bootstrap-icons.woff2
│ └── bsicons.lua
├── _publish.yml
├── _quarto.yml
├── apps
├── examples
│ ├── 0.0-penguins
│ │ ├── app.py
│ │ ├── penguins.csv
│ │ └── requirements.txt
│ ├── 1.0-basic-app
│ │ └── app.py
│ ├── 1.1-table-only
│ │ ├── app.py
│ │ └── penguins.csv
│ ├── 1.2-table-summary
│ │ ├── app.py
│ │ └── penguins.csv
│ ├── 1.3-table-input-slider
│ │ ├── app.py
│ │ └── penguins.csv
│ ├── 1.4-table-graph-input-slider
│ │ ├── app.py
│ │ ├── penguins.csv
│ │ └── plots.py
│ ├── 2.0-simple-reactive-calc
│ │ └── app.py
│ ├── 2.1-reactive-calc
│ │ ├── app.py
│ │ ├── penguins.csv
│ │ └── plots.py
│ ├── 3.0-modal
│ │ └── app.py
│ ├── 3.3-css
│ │ ├── app.py
│ │ └── www
│ │ │ └── my_styles.css
│ ├── 4.0.0-navsets
│ │ └── app.py
│ ├── 4.0.0.0-cards
│ │ └── app.py
│ ├── 4.0.1-rows-columns
│ │ └── app.py
│ ├── 4.0.2-nested-columns
│ │ └── app.py
│ ├── 4.1.1-dynamic-ui
│ │ ├── app.py
│ │ └── weather.csv
│ └── 4.1.2-conditional-panel
│ │ └── app.py
├── problem-sets
│ ├── 1-getting-started
│ │ ├── 1.0-hello-world
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── 1.1-data-frame
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 1.2-debug
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── model_data.csv
│ │ │ └── requirements.txt
│ │ ├── 1.3-filter-input
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 1.4-filter-connect
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 1.5-debug
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 1.6-debug
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 1.7-add-plot
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ └── requirements.txt
│ ├── 2-basic-ui
│ │ ├── 2.1-sidebar
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 2.2-cards
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 2.3-cards-switch
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 2.4-layout-columns
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ └── requirements.txt
│ ├── 3-reactivity
│ │ ├── 3.1-reactive-calc
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 3.2-stacking-reactives
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ └── requirements.txt
│ ├── 4-dynamic-ui
│ │ ├── 4.1-render-express
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ └── requirements.txt
│ ├── 4-ui-customization
│ │ ├── 4.1-tabs
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── weather.csv
│ │ ├── 4.2-cards
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── weather.csv
│ │ ├── 4.3-layout
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── weather.csv
│ │ ├── 4.4-ui-composition
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── weather.csv
│ │ ├── 4.5-value-boxes
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── weather.csv
│ │ ├── 4.6-dynamic-ui
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── weather.csv
│ │ ├── 4.7-conditional-panel
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── weather.csv
│ │ └── requirements.txt
│ ├── 5-reactive-effects
│ │ ├── 5.1-reactive-event
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ ├── 5.2-reactive-effect
│ │ │ ├── README
│ │ │ ├── app-solution.py
│ │ │ ├── app.py
│ │ │ ├── data_import.py
│ │ │ ├── plots.py
│ │ │ ├── requirements.txt
│ │ │ └── simulated-data.csv
│ │ └── requirements.txt
│ └── requirements.txt
├── target-app
│ ├── app.py
│ ├── data_import.py
│ ├── manifest.json
│ ├── plots.py
│ ├── requirements.txt
│ ├── rsconnect-python
│ │ └── target-app.json
│ └── simulated-data.csv
└── utilities
│ └── multiple-choice
│ ├── app.py
│ └── questions.json
├── exercises
├── 1-hello-world.qmd
├── 2-basic-ui.qmd
├── 3-reactivity.qmd
├── 4-dynamic-ui.qmd
└── 5-reactive-effect.qmd
├── helpers.py
├── images
├── git-download-button.png
└── shiny-course_image
│ ├── shiny-2x.png
│ ├── shiny-2x.webp
│ └── shiny.pxd
│ ├── QuickLook
│ ├── Icon.webp
│ └── Thumbnail.webp
│ ├── data
│ ├── 3F96BFC4-F9B9-4D8F-A3A9-DEE75FBCD65C
│ └── AF550587-953F-4997-9677-0C075842D366-76320-00013F899D49BA33
│ └── metadata.info
├── import-helpers.quarto_ipynb
├── index.qmd
├── requirements.txt
└── styles.css
/.github/workflows/issue_workflow.yml:
--------------------------------------------------------------------------------
1 | name: Issue Management
2 |
3 | on:
4 | issues:
5 | types: [opened]
6 |
7 | jobs:
8 | triage_or_add_to_project:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Check if the issue creator is a maintainer
13 | id: check_maintainer
14 | run: |
15 | echo "::set-output name=is_maintainer::$(curl -s -H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' \
16 | https://api.github.com/repos/${{ github.repository }}/collaborators/${{ github.event.issue.user.login }}/permission \
17 | | jq -r '.permission' | grep -q 'admin' && echo 'true' || echo 'false')"
18 |
19 | - name: Apply "needs triage" label to issues created by non-maintainers
20 | if: steps.check_maintainer.outputs.is_maintainer == 'false'
21 | uses: actions/github-script@v6
22 | with:
23 | script: |
24 | github.rest.issues.addLabels({
25 | issue_number: context.issue.number,
26 | owner: context.repo.owner,
27 | repo: context.repo.repo,
28 | labels: ["needs-triage"]
29 | })
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | on:
2 | workflow_dispatch:
3 | push:
4 | branches: main
5 |
6 | name: Quarto Publish
7 |
8 | jobs:
9 | build-deploy:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 | steps:
14 | - name: Check out repository
15 | uses: actions/checkout@v2
16 |
17 | - name: Set up Quarto
18 | uses: quarto-dev/quarto-actions/setup@v2
19 |
20 | - name: Install Python and Dependencies
21 | uses: actions/setup-python@v4
22 | with:
23 | python-version: '3.10'
24 | cache: 'pip'
25 | - run: pip install jupyter
26 | - run: pip install -r requirements.txt
27 |
28 | - name: Render and Publish
29 | uses: quarto-dev/quarto-actions/publish@v2
30 | with:
31 | target: gh-pages
32 | env:
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 | .Rproj.user
162 |
163 | /.quarto/
164 | .DS_Store
165 | _site
166 | site_libs
167 |
168 | /.luarc.json
169 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[python]": {
3 | "editor.defaultFormatter": "ms-python.black-formatter"
4 | },
5 | "python.formatting.provider": "none"
6 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 RStudio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reactive Web Dashboards with Shiny Course
2 |
3 | This is the repository for Talk Python's [Reactive Web Dashboards with Shiny course](https://training.talkpython.fm/courses/reactive-web-dashboards-with-shiny-for-data-science). This 2-hour video course is 100% free so [visit the course page and sign up](https://training.talkpython.fm/courses/reactive-web-dashboards-with-shiny-for-data-science) if you're interested.
4 |
5 | # Installation
6 |
7 | You will need to install a few things to render the website locally:
8 |
9 | 1) [Install quarto](https://quarto.org/docs/get-started/)
10 | 2) Install the shinylive python package `pip install shinylive --upgrade`
11 | 3) Install the shinylive quarto materials `quarto add quarto-ext/shinylive`
12 |
13 | ## How to edit the materials
14 |
15 | This is a quarto website, so to make changes to the course text modify the `.qmd` files, or the `_quarto.yml`.
16 | To do a live preview run `quarto preview --render html`, note that while `--render html` is a bit slower, it's the best way to see changes with the included applications.
17 |
18 | ## Creating an including Shiny Apps
19 |
20 | All of the apps live in the `apps` folder, which means that you can use VS Code to edit and test them out.
21 | To include an application insert an asis quarto chuck which looks like this:
22 |
23 | ```{python}
24 | #| echo: false
25 | #| output: asis
26 |
27 | include_shiny_folder("apps/basic-app")
28 | ```
29 |
30 | You can also pass optins to this function to modify the behaviour of the included app.
31 |
32 | To include a set of problem tabs, your app should have two application files. `app.py` which shows the starting point for the problem and `app-solution.py` which shows the target application.
33 | You can then use the `problem_tabs` function to include the tabs.
34 |
35 | ```{python}
36 | #| echo: false
37 | #| output: asis
38 |
39 | problem_tabs("apps/basic-app")
40 | ```
41 |
42 | ## Inserting multiple choice questions
43 |
44 | You can insert a shinylive app which displays sets of multiple choice questions by supplying a dictionary.
45 | It is a good idea to always wrap this dictionary with the `Quiz` class which validates that it is the right format for the application.
46 |
47 | ```{python}
48 | # | echo: false
49 | # | output: asis
50 |
51 | from helpers import multiple_choice_app, Quiz
52 |
53 | questions = Quiz(
54 | {
55 | "What ui input is used for plots?": {
56 | "choices": ["ui.input_plot", "ui.plot_input", "ui.plotInput"],
57 | "answer": "ui.Input_plot",
58 | },
59 | "How do you remove a reacitve link??": {
60 | "choices": ["reactive.isolate", "req", "reactive.Effect"],
61 | "answer": "reactive.isolate",
62 | },
63 | "What should you use to save an image of a plot to disk?": {
64 | "choices": ["reactive.Calc", "@ui.output_plot", "reactive.Effect"],
65 | "answer": "reactive.Effect",
66 | },
67 | }
68 | )
69 |
70 | multiple_choice_app(questions)
71 | ```
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/__init__.py
--------------------------------------------------------------------------------
/_extensions/kmasiello/positconfslides/_extension.yml:
--------------------------------------------------------------------------------
1 | title: positconfslides
2 | author: Thomas Mock, Carlos Scheidegger, Katie Masiello
3 | version: 1.0.0
4 | quarto-required: ">=1.2.0"
5 | contributes:
6 | formats:
7 | revealjs:
8 | theme: [custom.scss]
9 | filters:
10 | - positconfslides.lua
11 |
12 |
--------------------------------------------------------------------------------
/_extensions/kmasiello/positconfslides/assets/backgrounds/30-70-light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_extensions/kmasiello/positconfslides/assets/backgrounds/30-70-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_extensions/kmasiello/positconfslides/assets/posit-slides.js:
--------------------------------------------------------------------------------
1 | // the world's your oyster
--------------------------------------------------------------------------------
/_extensions/kmasiello/positconfslides/custom.scss:
--------------------------------------------------------------------------------
1 | /*-- scss:defaults --*/
2 |
3 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');
4 | @import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
5 | @import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@300;400');
6 |
7 | $font-family-sans-serif: 'Open Sans', sans-serif !default;
8 | $font-family-monospace: 'Source Code Pro', monospace !default;
9 | $presentation-heading-font: "Open Sans", sans-serif !default;
10 | $presentation-title-slide-text-align: left;
11 | $presentation-heading-font-weight: 300;
12 | $code-block-font-size: 0.80em;
13 |
14 | // colors
15 | $body-bg: #FFFFFF;
16 | $body-color: #404041;
17 | $code-color: #9a4665;
18 | $selection-bg: #93bdbf;
19 | $link-color: #3D6E74;
20 |
21 | // dark colors
22 | $dark-bg-text-color: #F0F0F0;
23 | $dark-bg-code-color: #cca2b2;
24 | $dark-bg-link-color: #93bdbf;
25 |
26 | /*-- scss:rules --*/
27 |
28 | #title-slide .subtitle {
29 | font-size: 60px;
30 | font-weight: 600;
31 | }
32 |
33 | #title-slide .quarto-title-author-name {
34 | font-size: 50px;
35 | font-weight: 300;
36 | }
37 |
38 | #title-slide .institute {
39 | font-weight: 1000;
40 | }
41 |
42 | // dark slides
43 | section div.has-dark-background {
44 | color: $dark-bg-text-color;
45 | }
46 |
47 | section div.has-dark-background h2 {
48 | font-weight: 300 !important;
49 | color: $dark-bg-text-color;
50 | }
51 |
52 | section div.has-dark-background code {
53 | color: $dark-bg-code-color;
54 | }
55 |
56 | section div.has-dark-background a {
57 | color: $dark-bg-link-color;
58 | }
59 |
60 | section div.has-dark-background blockquote {
61 | color: #c9c9c9;
62 | }
63 |
64 | // don't add margin to last column
65 |
66 | .reveal .columns>.column:last-child>:not(ul, ol) {
67 | margin-left: 0;
68 | }
69 |
70 | // footer details
71 | .reveal .slide-number a {
72 | font-family: 'Barlow Condensed', sans-serif;
73 | }
74 |
75 | .reveal .footer p {
76 | font-family: 'Barlow Condensed', sans-serif;
77 | }
78 |
79 | // font sizes
80 |
81 | .smaller {
82 | font-size: smaller
83 | }
84 |
85 | .medium {
86 | font-size: medium
87 | }
88 |
89 | .large {
90 | font-size: 150%;
91 | }
92 |
93 | .larger {
94 | font-size: 250%;
95 | }
96 |
97 | .largest {
98 | font-size: 500%;
99 | }
--------------------------------------------------------------------------------
/_extensions/kmasiello/positconfslides/positconfslides.lua:
--------------------------------------------------------------------------------
1 | function many_columns(conf)
2 | local background_image = conf.image
3 |
4 | local outerColumns = pandoc.Div({})
5 | outerColumns.classes:insert("columns")
6 | if conf.dark then
7 | outerColumns.classes:insert("has-dark-background")
8 | end
9 |
10 | for i, v in ipairs(conf.columns) do
11 | local innerColumn = pandoc.Div(v)
12 | innerColumn.classes:insert("column")
13 | innerColumn.attributes.width = conf.widths[i];
14 | outerColumns.content:insert(innerColumn)
15 | end
16 |
17 | local header_block = pandoc.Header(2, "")
18 |
19 | header_block.attr.attributes["background-image"] = "_extensions/positconfslides/assets/backgrounds/" .. background_image .. ".svg"
20 | header_block.attr.attributes["background-size"] = "contain"
21 |
22 | local new_blocks = pandoc.List({})
23 | new_blocks:insert(header_block)
24 | new_blocks:insert(outerColumns)
25 |
26 | return new_blocks
27 | end
28 |
29 | local slide_styles = {}
30 |
31 | function blank_column(_content)
32 | return { pandoc.RawBlock("html", " ") }
33 | end
34 |
35 | function header_column_narrow_light(c)
36 | local result = pandoc.List({})
37 | local title = pandoc.utils.stringify(c[1].content)
38 | result:insert(pandoc.RawBlock("html", "
" .. title .. " "))
39 | return result
40 | end
41 |
42 | function header_column_narrow_dark(c)
43 | local result = pandoc.List({})
44 | local title = pandoc.utils.stringify(c[1].content)
45 | result:insert(pandoc.RawBlock("html", "" .. title .. " "))
46 | return result
47 | end
48 |
49 | function content_column_wide(c)
50 | local result = pandoc.List({})
51 | local title = pandoc.utils.stringify(c[1].content)
52 | result:extend(c)
53 | result:remove(1)
54 | return result
55 | end
56 |
57 | function content_column(c)
58 | local result = pandoc.List({})
59 | local title = pandoc.utils.stringify(c[1].content)
60 | result:insert(pandoc.RawBlock("html", "" .. title .. " "))
61 | result:extend(c)
62 | result:remove(2)
63 | return result
64 | end
65 |
66 | function bracket_content(c)
67 | local result = pandoc.List({})
68 | local title = pandoc.utils.stringify(c[1].content)
69 | result:insert(pandoc.RawBlock("html", "" .. title .. " "))
70 | result:extend(c)
71 | result:remove(2)
72 | return result
73 | end
74 |
75 | local blank = pandoc.RawBlock("html", " ")
76 | for i, v in ipairs({
77 | { image = "30-70-dark",
78 | dark = true,
79 | widths = { "35%", "65%" },
80 | columns = { header_column_narrow_light, content_column_wide },
81 | },
82 | { image = "30-70-light",
83 | widths = { "35%", "65%" },
84 | columns = { header_column_narrow_dark, content_column_wide },
85 | },
86 | { image = "30-70-white",
87 | widths = { "35%", "65%" },
88 | columns = { header_column_narrow_dark, content_column_wide },
89 | },
90 | { image = "brackets-dark",
91 | dark = true,
92 | widths = { "100%" },
93 | columns = { bracket_content },
94 | },
95 | { image = "brackets-light",
96 | widths = { "100%" },
97 | columns = { bracket_content },
98 | },
99 | { image = "content-dark",
100 | dark = true,
101 | widths = { "100%" },
102 | columns = { content_column },
103 | },
104 | { image = "content-light",
105 | widths = { "100%" },
106 | columns = { content_column },
107 | },
108 | { image = "content2-light",
109 | widths = { "100%" },
110 | columns = { content_column },
111 | },
112 | { image = "toc-dark",
113 | dark = true,
114 | widths = { "100%" },
115 | columns = { content_column },
116 | },
117 | { image = "toc-light",
118 | widths = { "100%" },
119 | columns = { content_column },
120 | },
121 | { image = "toc-people-dark",
122 | dark = true,
123 | widths = { "100%" },
124 | columns = { content_column },
125 | },
126 | { image = "toc-people-light",
127 | widths = { "100%" },
128 | columns = { content_column },
129 | },
130 | { image = "toc2-dark",
131 | dark = true,
132 | widths = { "100%" },
133 | columns = { content_column },
134 | },
135 | { image = "toc2-light",
136 | widths = { "100%" },
137 | columns = { content_column },
138 | }}) do
139 | slide_styles[v.image] = (function(v)
140 | setmetatable(v.columns, getmetatable(pandoc.List({})))
141 | return function(content)
142 | return many_columns({
143 | dark = v.dark,
144 | image = v.image,
145 | widths = v.widths,
146 | columns = v.columns:map(function(col) return col(content) end)
147 | })
148 | end
149 | end)(v)
150 | end
151 |
152 | -- TODO dark-section needs light text
153 |
154 | function mysplit (inputstr, pattern)
155 | if sep == nil then
156 | sep = "%s"
157 | end
158 | local t = {}
159 | local matched = false
160 | for str in string.gmatch(inputstr, pattern) do
161 | matched = true
162 | table.insert(t, str)
163 | end
164 | if not matched then
165 | return { inputstr }
166 | end
167 | return t
168 | end
169 |
170 | function process_header_attributes(header)
171 | local title = pandoc.utils.stringify(header.content)
172 | local match = title:match(" {.*}$")
173 | if match == nil then return header end
174 | local attribute = match:sub(3, -2)
175 | header.content = pandoc.Plain({ pandoc.Str(title:gsub(" {.*}$", "")) })
176 | for i, v in pairs(mysplit(attribute, " ")) do
177 | if v:sub(1, 1) == "#" then
178 | header.attr.identifier = v:sub(2)
179 | elseif v:sub(1, 1) == "." then
180 | table.insert(header.attr.classes, v:sub(2))
181 | else
182 | local key, value = i:match("(.*)=(.*)")
183 | if key and value then
184 | header.attr.attributes[key] = value
185 | else
186 | header.attr.attributes[i] = true
187 | end
188 | end
189 | end
190 | return header
191 | end
192 |
193 |
194 | function process(content, classes)
195 | if classes == nil then
196 | classes = content[1].attr.classes
197 | end
198 | for i, v in ipairs(classes) do
199 | if slide_styles[v] then
200 | return slide_styles[v](content)
201 | end
202 | end
203 | return content
204 | end
205 |
206 | function Pandoc(doc)
207 | quarto.doc.add_html_dependency({
208 | name = 'posit_slides',
209 | scripts = { 'assets/posit-slides.js' },
210 | })
211 |
212 | local new_blocks = pandoc.List({})
213 | local sections = pandoc.utils.make_sections(false, nil, doc.blocks)
214 |
215 | for i, div in ipairs(sections) do
216 | if div.t ~= "Div" then
217 | new_blocks:insert(div)
218 | else
219 | if div.content[1].t == "Header" then
220 | div.content[1] = process_header_attributes(div.content[1])
221 | end
222 | new_blocks:extend(process(div.content))
223 | end
224 | end
225 | doc.blocks = new_blocks
226 | return doc
227 | end
228 |
--------------------------------------------------------------------------------
/_extensions/quarto-ext/shinylive/README.md:
--------------------------------------------------------------------------------
1 | # Shinylive package methods
2 |
3 | ## Methods
4 |
5 | ### R
6 |
7 | Interaction:
8 |
9 | ```
10 | Rscript -e 'shinylive:::quarto_ext()' [methods] [args]
11 | ```
12 |
13 | ### Python
14 |
15 | Interaction:
16 |
17 | ```
18 | shinylive [methods] [args]
19 | ```
20 |
21 | ## CLI Methods
22 |
23 | * `extension info`
24 | * Package, version, asset version, and script paths information
25 | * `extension base-htmldeps`
26 | * Quarto html dependencies for the base shinylive integration
27 | * `extension language-resources`
28 | * Language specific resource files for the quarto html dependency named `shinylive`
29 | * `extension app-resources`
30 | * App specific resource files for the quarto html dependency named `shinylive`
31 |
32 | ### CLI Interface
33 | * `extension info`
34 | * Prints information about the extension including:
35 | * `version`: The version of the R package
36 | * `assets_version`: The version of the web assets
37 | * `scripts`: A list of paths scripts that are used by the extension,
38 | mainly `codeblock-to-json`
39 | * Example
40 | ```
41 | {
42 | "version": "0.1.0",
43 | "assets_version": "0.2.0",
44 | "scripts": {
45 | "codeblock-to-json": "//shinylive-0.2.0/scripts/codeblock-to-json.js"
46 | }
47 | }
48 | ```
49 | * `extension base-htmldeps`
50 | * Prints the language agnostic quarto html dependencies as a JSON array.
51 | * The first html dependency is the `shinylive` service workers.
52 | * The second html dependency is the `shinylive` base dependencies. This
53 | dependency will contain the core `shinylive` asset scripts (JS files
54 | automatically sourced), stylesheets (CSS files that are automatically
55 | included), and resources (additional files that the JS and CSS files can
56 | source).
57 | * Example
58 | ```
59 | [
60 | {
61 | "name": "shinylive-serviceworker",
62 | "version": "0.2.0",
63 | "meta": { "shinylive:serviceworker_dir": "." },
64 | "serviceworkers": [
65 | {
66 | "source": "//shinylive-0.2.0/shinylive-sw.js",
67 | "destination": "/shinylive-sw.js"
68 | }
69 | ]
70 | },
71 | {
72 | "name": "shinylive",
73 | "version": "0.2.0",
74 | "scripts": [{
75 | "name": "shinylive/load-shinylive-sw.js",
76 | "path": "//shinylive-0.2.0/shinylive/load-shinylive-sw.js",
77 | "attribs": { "type": "module" }
78 | }],
79 | "stylesheets": [{
80 | "name": "shinylive/shinylive.css",
81 | "path": "//shinylive-0.2.0/shinylive/shinylive.css"
82 | }],
83 | "resources": [
84 | {
85 | "name": "shinylive/shinylive.js",
86 | "path": "//shinylive-0.2.0/shinylive/shinylive.js"
87 | },
88 | ... # [ truncated ]
89 | ]
90 | }
91 | ]
92 | ```
93 | * `extension language-resources`
94 | * Prints the language-specific resource files as JSON that should be added to the quarto html dependency.
95 | * For r-shinylive, this includes the webr resource files
96 | * For py-shinylive, this includes the pyodide and pyright resource files.
97 | * Example
98 | ```
99 | [
100 | {
101 | "name": "shinylive/webr/esbuild.d.ts",
102 | "path": "//shinylive-0.2.0/shinylive/webr/esbuild.d.ts"
103 | },
104 | {
105 | "name": "shinylive/webr/libRblas.so",
106 | "path": "//shinylive-0.2.0/shinylive/webr/libRblas.so"
107 | },
108 | ... # [ truncated ]
109 | ]
110 | * `extension app-resources`
111 | * Prints app-specific resource files as JSON that should be added to the `"shinylive"` quarto html dependency.
112 | * Currently, r-shinylive does not return any resource files.
113 | * Example
114 | ```
115 | [
116 | {
117 | "name": "shinylive/pyodide/anyio-3.7.0-py3-none-any.whl",
118 | "path": "//shinylive-0.2.0/shinylive/pyodide/anyio-3.7.0-py3-none-any.whl"
119 | },
120 | {
121 | "name": "shinylive/pyodide/appdirs-1.4.4-py2.py3-none-any.whl",
122 | "path": "//shinylive-0.2.0/shinylive/pyodide/appdirs-1.4.4-py2.py3-none-any.whl"
123 | },
124 | ... # [ truncated ]
125 | ]
126 | ```
127 |
--------------------------------------------------------------------------------
/_extensions/quarto-ext/shinylive/_extension.yml:
--------------------------------------------------------------------------------
1 | name: shinylive
2 | title: Embedded Shinylive applications
3 | author: Winston Chang
4 | version: 0.1.0
5 | quarto-required: ">=1.2.198"
6 | contributes:
7 | filters:
8 | - shinylive.lua
9 |
--------------------------------------------------------------------------------
/_extensions/quarto-ext/shinylive/resources/css/shinylive-quarto.css:
--------------------------------------------------------------------------------
1 | div.output-content,
2 | div.shinylive-wrapper {
3 | background-color: rgba(250, 250, 250, 0.65);
4 | border: 1px solid rgba(233, 236, 239, 0.65);
5 | border-radius: 0.5rem;
6 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.04), 0px 3px 7px rgba(0, 0, 0, 0.04),
7 | 0px 12px 30px rgba(0, 0, 0, 0.07);
8 | margin-top: 32px;
9 | margin-bottom: 32px;
10 | }
11 |
12 | div.shinylive-wrapper {
13 | margin: 1em 0;
14 | border-radius: 8px;
15 | }
16 |
17 | .shinylive-container {
18 | background-color: #eeeff2;
19 | min-height: auto;
20 | }
21 |
22 | .shinylive-container > div {
23 | box-shadow: none;
24 | }
25 |
26 | .editor-container .cm-editor .cm-scroller {
27 | font-size: 13px;
28 | line-height: 1.5;
29 | }
30 |
31 | iframe.app-frame {
32 | /* Override the default margin from Bootstrap */
33 | margin-bottom: 0;
34 | }
35 |
--------------------------------------------------------------------------------
/_extensions/shafayetShafee/bsicons/_extension.yml:
--------------------------------------------------------------------------------
1 | title: Support for Bootstrap Icons
2 | author: Shafayet Khan Shafee
3 | version: 1.0.0
4 | quarto-required: ">=1.2.0"
5 | contributes:
6 | shortcodes:
7 | - bsicons.lua
8 |
9 |
--------------------------------------------------------------------------------
/_extensions/shafayetShafee/bsicons/assets/webfonts/bootstrap-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/_extensions/shafayetShafee/bsicons/assets/webfonts/bootstrap-icons.woff
--------------------------------------------------------------------------------
/_extensions/shafayetShafee/bsicons/assets/webfonts/bootstrap-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/_extensions/shafayetShafee/bsicons/assets/webfonts/bootstrap-icons.woff2
--------------------------------------------------------------------------------
/_extensions/shafayetShafee/bsicons/bsicons.lua:
--------------------------------------------------------------------------------
1 | local function ensureHtmlDeps()
2 | quarto.doc.addHtmlDependency({
3 | name = "bootstrap-icons",
4 | version = "1.9.1",
5 | stylesheets = {"assets/css/all.css"}
6 | })
7 | end
8 |
9 | local function isEmpty(s)
10 | return s == nil or s == ''
11 | end
12 |
13 | local str = pandoc.utils.stringify
14 |
15 | return {
16 | ["bi"] = function(args, kwargs)
17 | local icon = str(args[1])
18 | local size = str(kwargs["size"])
19 | local color = str(kwargs["color"])
20 | local label = str(kwargs["label"])
21 | local class = str(kwargs["class"])
22 |
23 | if not isEmpty(size) then
24 | size = "font-size: " .. size .. ";"
25 | else
26 | size = ''
27 | end
28 |
29 | if not isEmpty(color) then
30 | color = "color: " .. color .. ";"
31 | else
32 | color = ''
33 | end
34 |
35 | local style = "style=\"" .. size .. color .. "\""
36 |
37 | if not isEmpty(label) then
38 | label = " aria-label=\"" .. label .. "\""
39 | end
40 |
41 | if isEmpty(class) then
42 | class = ''
43 | end
44 |
45 | local role = "role=\"img\""
46 | local aria_hidden = "aria-hidden=\"true\""
47 |
48 | if quarto.doc.isFormat("html:js") then
49 | ensureHtmlDeps()
50 | if isEmpty(label) then
51 | return pandoc.RawInline(
52 | 'html',
53 | " "
54 | )
55 | else
56 | return pandoc.RawInline(
57 | 'html',
58 | " "
59 | )
60 | end
61 | else
62 | return pandoc.Null()
63 | end
64 |
65 | end
66 | }
--------------------------------------------------------------------------------
/_publish.yml:
--------------------------------------------------------------------------------
1 | - source: project
2 | quarto-pub:
3 | - id: 95f693d8-9af0-46bc-aa91-883fc946d55d
4 | url: 'https://gordon.quarto.pub/shiny-for-python-workshop'
5 |
--------------------------------------------------------------------------------
/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | type: website
3 |
4 | website:
5 | title: "Shiny for Python Workshop"
6 | sidebar:
7 | style: docked
8 | contents:
9 | - section: "Exercises"
10 | contents:
11 | - exercises/1-hello-world.qmd
12 | - exercises/2-basic-ui.qmd
13 | - exercises/3-reactivity.qmd
14 | - exercises/5-reactive-effect.qmd
15 | - exercises/4-dynamic-ui.qmd
16 |
17 | navbar:
18 | tools:
19 | - icon: github
20 | href: https://github.com/talkpython/reactive-web-dashboards-with-shiny-course
21 | left:
22 | - href: index.qmd
23 | text: Home
24 |
25 | filters:
26 | - shinylive
27 |
28 | format:
29 | html:
30 | theme: cosmo
31 | margin-right: 0px
32 | css: styles.css
33 | toc: true
34 | toc-location: left
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/apps/examples/0.0-penguins/app.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pandas as pd
4 | import seaborn as sns
5 |
6 | import shiny.experimental as x
7 | from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
8 |
9 | sns.set_theme()
10 |
11 | # https://raw.githubusercontent.com/jcheng5/simplepenguins.R/main/penguins.csv
12 |
13 | df = pd.read_csv(Path(__file__).parent / "penguins.csv", na_values="NA")
14 | numeric_cols = df.select_dtypes(include=["float64"]).columns.tolist()
15 | species = df["Species"].unique().tolist()
16 | species.sort()
17 |
18 | app_ui = x.ui.page_sidebar(
19 | x.ui.sidebar(
20 | ui.input_selectize(
21 | "xvar", "X variable", numeric_cols, selected="Bill Length (mm)"
22 | ),
23 | ui.input_selectize(
24 | "yvar", "Y variable", numeric_cols, selected="Bill Depth (mm)"
25 | ),
26 | ui.input_checkbox_group(
27 | "species", "Filter by species", species, selected=species
28 | ),
29 | ui.hr(),
30 | ui.input_switch("by_species", "Show species", value=True),
31 | ui.input_switch("show_margins", "Show marginal plots", value=True),
32 | ),
33 | x.ui.output_plot("scatter"),
34 | )
35 |
36 |
37 | def server(input: Inputs, output: Outputs, session: Session):
38 | @reactive.Calc
39 | def filtered_df() -> pd.DataFrame:
40 | """Returns a Pandas data frame that includes only the desired rows"""
41 |
42 | # This calculation "req"uires that at least one species is selected
43 | req(len(input.species()) > 0)
44 |
45 | # Filter the rows so we only include the desired species
46 | return df[df["Species"].isin(input.species())]
47 |
48 | @output
49 | @render.plot
50 | def scatter():
51 | """Generates a plot for Shiny to display to the user"""
52 |
53 | # The plotting function to use depends on whether margins are desired
54 | plotfunc = sns.jointplot if input.show_margins() else sns.scatterplot
55 |
56 | plotfunc(
57 | data=filtered_df(),
58 | x=input.xvar(),
59 | y=input.yvar(),
60 | hue="Species" if input.by_species() else None,
61 | hue_order=species,
62 | legend=False,
63 | )
64 |
65 |
66 | app = App(app_ui, server)
67 |
--------------------------------------------------------------------------------
/apps/examples/0.0-penguins/requirements.txt:
--------------------------------------------------------------------------------
1 | shiny==0.5.1
2 | shinyswatch==0.2.4
3 | seaborn==0.12.2
4 | matplotlib==3.7.1
--------------------------------------------------------------------------------
/apps/examples/1.0-basic-app/app.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from shiny import App, render, ui, reactive
4 |
5 | app_ui = ui.page_fluid(
6 | ui.input_radio_buttons(
7 | "command_choice", "Choose a command", choices=["Option 1", "Option 2"]
8 | ),
9 | ui.output_ui("dynamic_text"),
10 | ui.input_action_button("compute_button", "COMPUTE"),
11 | )
12 |
13 |
14 | def server(input, output, session):
15 | @output
16 | @render.ui
17 | def dynamic_text():
18 | if input.command_choice() == "Option 1":
19 | id = "output_text_1"
20 | else:
21 | id = "output_text_2"
22 | return ui.output_text(id)
23 |
24 | @output
25 | @render.text
26 | @reactive.event(input.compute_button)
27 | def output_text_1():
28 | return "Result from option 1"
29 |
30 | @output
31 | @render.text
32 | @reactive.event(input.compute_button)
33 | def output_text_2():
34 | return "Result from option 2"
35 |
36 |
37 | app = App(app_ui, server)
38 |
--------------------------------------------------------------------------------
/apps/examples/1.1-table-only/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, render, ui
2 | import pandas as pd
3 | from pathlib import Path
4 |
5 | infile = Path(__file__).parent / "penguins.csv"
6 | penguins = pd.read_csv(infile)
7 |
8 | app_ui = ui.page_fluid(
9 | ui.h2("Hello Penguins!"),
10 | ui.output_data_frame("table"),
11 | )
12 |
13 |
14 | def server(input, output, session):
15 | @output
16 | @render.data_frame
17 | def table():
18 | return penguins
19 |
20 |
21 | app = App(app_ui, server)
22 |
--------------------------------------------------------------------------------
/apps/examples/1.2-table-summary/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, render, ui
2 | import pandas as pd
3 | from pathlib import Path
4 |
5 | infile = Path(__file__).parent / "penguins.csv"
6 | penguins = pd.read_csv(infile)
7 |
8 | app_ui = ui.page_fluid(
9 | ui.h2("Hello Penguins!"),
10 | ui.output_data_frame("table"),
11 | )
12 |
13 |
14 | def server(input, output, session):
15 | @output
16 | @render.data_frame
17 | def table():
18 | summary = (
19 | penguins.copy()
20 | .set_index("species")
21 | .groupby(level="species")
22 | .agg({"bill_length": "mean", "bill_depth": "mean"})
23 | .reset_index()
24 | )
25 | return summary
26 |
27 |
28 | app = App(app_ui, server)
29 |
--------------------------------------------------------------------------------
/apps/examples/1.3-table-input-slider/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, render, ui
2 | import pandas as pd
3 | from pathlib import Path
4 |
5 | infile = Path(__file__).parent / "penguins.csv"
6 | penguins = pd.read_csv(infile)
7 |
8 | app_ui = ui.page_fluid(
9 | ui.h2("Hello Penguins!"),
10 | ui.input_slider(
11 | "mass",
12 | "Mass",
13 | 2000,
14 | 8000,
15 | 6000,
16 | ),
17 | ui.output_data_frame("table"),
18 | )
19 |
20 |
21 | def server(input, output, session):
22 | @output
23 | @render.data_frame
24 | def table():
25 | df = penguins.copy()
26 | filtered = df.loc[df["body_mass"] < input.mass()]
27 | summary = (
28 | filtered.set_index("species")
29 | .groupby(level="species")
30 | .agg({"bill_length": "mean", "bill_depth": "mean"})
31 | .reset_index()
32 | )
33 | return summary
34 |
35 |
36 | app = App(app_ui, server)
37 |
--------------------------------------------------------------------------------
/apps/examples/1.4-table-graph-input-slider/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, render, ui
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import dist_plot
5 |
6 | infile = Path(__file__).parent / "penguins.csv"
7 | penguins = pd.read_csv(infile)
8 |
9 | app_ui = ui.page_fluid(
10 | ui.h2("Hello Penguins!"),
11 | ui.input_slider(
12 | "mass",
13 | "Mass",
14 | 2000,
15 | 8000,
16 | 6000,
17 | ),
18 | ui.output_data_frame("table"),
19 | ui.output_plot("dist"),
20 | )
21 |
22 |
23 | def server(input, output, session):
24 | @output
25 | @render.data_frame
26 | def table():
27 | df = penguins.copy()
28 | filtered = df.loc[df["body_mass"] < input.mass()]
29 | summary = (
30 | filtered.set_index("species")
31 | .groupby(level="species")
32 | .agg({"bill_length": "mean", "bill_depth": "mean"})
33 | .reset_index()
34 | )
35 | return summary
36 |
37 | @output
38 | @render.plot
39 | def dist():
40 | df = penguins.copy()
41 | filtered = df.loc[df["body_mass"] < input.mass()]
42 | return dist_plot(filtered)
43 |
44 |
45 | app = App(app_ui, server)
46 |
--------------------------------------------------------------------------------
/apps/examples/1.4-table-graph-input-slider/plots.py:
--------------------------------------------------------------------------------
1 | from plotnine import ggplot, geom_density, aes, theme_light, geom_point, stat_smooth
2 |
3 |
4 | def dist_plot(df):
5 | plot = (
6 | ggplot(df, aes(x="body_mass", fill="species"))
7 | + geom_density(alpha=0.2)
8 | + theme_light()
9 | )
10 | return plot
11 |
12 |
13 | def scatter_plot(df, trend_line=False):
14 | plot = (
15 | ggplot(
16 | df,
17 | aes(
18 | x="bill_length",
19 | y="bill_depth",
20 | color="species",
21 | group="species",
22 | ),
23 | )
24 | + geom_point()
25 | + theme_light()
26 | )
27 |
28 | if trend_line:
29 | plot = plot + stat_smooth()
30 |
31 | return plot
32 |
--------------------------------------------------------------------------------
/apps/examples/2.0-simple-reactive-calc/app.py:
--------------------------------------------------------------------------------
1 | from shiny import Inputs, Outputs, Session, App, reactive, render, req, ui
2 | import pandas as pd
3 | from plotnine import ggplot, geom_histogram, labs, aes
4 | import numpy as np
5 |
6 | app_ui = ui.page_fluid(
7 | ui.input_slider("n_rows", "Sample rows", 0, 100, 20),
8 | ui.row(ui.column(4, ui.output_table("df")), ui.column(8, ui.output_plot("hist"))),
9 | )
10 |
11 |
12 | def server(input: Inputs, output: Outputs, session: Session):
13 | @output
14 | @render.table
15 | def df():
16 | rand = np.random.rand(input.n_rows(), 1)
17 | df = pd.DataFrame(rand, columns=["col_1"])
18 | return df
19 |
20 | @output
21 | @render.plot
22 | def hist():
23 | rand = np.random.rand(input.n_rows(), 1)
24 | df = pd.DataFrame(rand, columns=["col_1"])
25 | plot = (
26 | ggplot(df, aes(x="col_1"))
27 | + geom_histogram(binwidth=0.1, fill="blue", color="black")
28 | + labs(x="Random Values", y="Frequency", title="Histogram of Random Data")
29 | )
30 | return plot
31 |
32 |
33 | app = App(app_ui, server)
34 |
--------------------------------------------------------------------------------
/apps/examples/2.1-reactive-calc/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, render, ui, reactive
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import dist_plot, scatter_plot
5 |
6 | infile = Path(__file__).parent / "penguins.csv"
7 | penguins = pd.read_csv(infile)
8 |
9 | app_ui = ui.page_fluid(
10 | ui.h2("Hello Penguins!"),
11 | ui.input_slider(
12 | "mass",
13 | "Mass",
14 | 2000,
15 | 8000,
16 | 6000,
17 | ),
18 | ui.output_data_frame("table"),
19 | ui.output_plot("dist"),
20 | ui.input_checkbox("trend", "Add trendline"),
21 | ui.output_plot("scatter"),
22 | )
23 |
24 |
25 | def server(input, output, session):
26 | @reactive.Calc
27 | def filt_df():
28 | df = penguins.copy()
29 | filtered = df.loc[df["body_mass"] < input.mass()]
30 | return filtered
31 |
32 | @output
33 | @render.data_frame
34 | def table():
35 | summary = (
36 | filt_df()
37 | .set_index("species")
38 | .groupby(level="species")
39 | .agg({"bill_length": "mean", "bill_depth": "mean"})
40 | .reset_index()
41 | )
42 | return summary
43 |
44 | @output
45 | @render.plot
46 | def dist():
47 | return dist_plot(filt_df())
48 |
49 | @output
50 | @render.plot
51 | def scatter():
52 | return scatter_plot(filt_df(), input.trend())
53 |
54 |
55 | app = App(app_ui, server)
56 |
--------------------------------------------------------------------------------
/apps/examples/2.1-reactive-calc/plots.py:
--------------------------------------------------------------------------------
1 | from plotnine import ggplot, geom_density, aes, theme_light, geom_point, stat_smooth
2 |
3 |
4 | def dist_plot(df):
5 | plot = (
6 | ggplot(df, aes(x="body_mass", fill="species"))
7 | + geom_density(alpha=0.2)
8 | + theme_light()
9 | )
10 | return plot
11 |
12 |
13 | def scatter_plot(df, trend_line=False):
14 | plot = (
15 | ggplot(
16 | df,
17 | aes(
18 | x="bill_length",
19 | y="bill_depth",
20 | color="species",
21 | group="species",
22 | ),
23 | )
24 | + geom_point()
25 | + theme_light()
26 | )
27 |
28 | if trend_line:
29 | plot = plot + stat_smooth()
30 |
31 | return plot
32 |
--------------------------------------------------------------------------------
/apps/examples/3.0-modal/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, Inputs, Outputs, Session, reactive, ui, render
2 |
3 | app_ui = ui.page_fluid(
4 | ui.input_action_button("show", "Show modal dialog"), ui.output_text("txt")
5 | )
6 |
7 |
8 | def server(input: Inputs, output: Outputs, session: Session):
9 | @reactive.Effect
10 | @reactive.event(input.show)
11 | def show_modal():
12 | m = ui.modal(
13 | "This is a somewhat important message.",
14 | title="Click outside the modal to close",
15 | easy_close=True,
16 | footer=None,
17 | )
18 | ui.modal_show(m)
19 |
20 |
21 | app = App(app_ui, server)
22 |
--------------------------------------------------------------------------------
/apps/examples/3.3-css/app.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from shiny import App, Inputs, Outputs, Session, render, ui
4 |
5 | css_path = Path(__file__).parent / "www" / "my_styles.css"
6 |
7 | app_ui = ui.page_fluid(
8 | {"class": "blue-body"},
9 | ui.include_css(css_path),
10 | ui.input_slider("num", "Number:", min=10, max=100, value=30),
11 | ui.div(
12 | {"class": "bold-output;"},
13 | ui.output_text("slider_val"),
14 | ),
15 | )
16 |
17 |
18 | def server(input: Inputs, output: Outputs, session: Session):
19 | @output
20 | @render.text
21 | def slider_val():
22 | return f"{input.num()}"
23 |
24 |
25 | app = App(app_ui, server)
26 |
--------------------------------------------------------------------------------
/apps/examples/3.3-css/www/my_styles.css:
--------------------------------------------------------------------------------
1 | .blue-body {
2 | background-color: rgba(0, 128, 255, 0.5);
3 | }
4 |
5 | .bold-output {
6 | font-weight: bold;
7 | }
--------------------------------------------------------------------------------
/apps/examples/4.0.0-navsets/app.py:
--------------------------------------------------------------------------------
1 | from shiny import Inputs, Outputs, Session, App, reactive, render, req, ui
2 |
3 | app_ui = ui.page_fluid(
4 | ui.navset_tab(
5 | ui.nav(
6 | "Tab1",
7 | ui.input_slider("slider", "Slider", 0, 100, 20),
8 | ),
9 | ui.nav("Tab2", ui.input_action_button("button", "Button")),
10 | ui.nav("Tab3", ui.input_action_button("button2", "Button 2")),
11 | )
12 | )
13 |
14 | app = App(app_ui, None)
15 |
--------------------------------------------------------------------------------
/apps/examples/4.0.0.0-cards/app.py:
--------------------------------------------------------------------------------
1 | from shiny import Inputs, Outputs, Session, App, reactive, render, req, ui
2 | import shiny.experimental as x
3 |
4 | app_ui = ui.page_fluid(
5 | ui.column(
6 | 3,
7 | x.ui.card(
8 | x.ui.card_header("Slider card"),
9 | ui.input_slider("n", "N", 0, 100, 20),
10 | ui.output_text_verbatim("txt"),
11 | fill=False,
12 | ),
13 | )
14 | )
15 |
16 |
17 | def server(input: Inputs, output: Outputs, session: Session):
18 | @output
19 | @render.text
20 | def txt():
21 | return f"n*2 is {input.n() * 2}"
22 |
23 |
24 | app = App(app_ui, server)
25 |
--------------------------------------------------------------------------------
/apps/examples/4.0.1-rows-columns/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, ui
2 | import matplotlib.pyplot as plt
3 | import numpy as np
4 |
5 | style = "border: 1px solid #999;"
6 |
7 | app_ui = ui.page_fluid(
8 | ui.row(
9 | ui.column(4, "row-1 col-1", style=style),
10 | ui.column(4, "row-1 col-2", style=style),
11 | ui.column(4, "row-1 col-3", style=style),
12 | ),
13 | ui.row(
14 | ui.column(6, "row-2 col-1", style=style),
15 | ui.column(6, "row-2 col-2", style=style),
16 | ),
17 | )
18 |
19 |
20 | app = App(app_ui, None)
21 |
--------------------------------------------------------------------------------
/apps/examples/4.0.2-nested-columns/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, ui
2 | import matplotlib.pyplot as plt
3 | import numpy as np
4 |
5 | style = "border: 1px solid #999;"
6 |
7 | app_ui = ui.page_fluid(
8 | ui.row(
9 | ui.column(
10 | 6,
11 | "row-1 col-2",
12 | ui.row(
13 | ui.column(4, "nest-1", style=style),
14 | ui.column(4, "nest-2", style=style),
15 | ui.column(4, "nest-2", style=style),
16 | ),
17 | style=style,
18 | ),
19 | ui.column(4, "row-1 col-2", style=style),
20 | ),
21 | )
22 |
23 |
24 | app = App(app_ui, None)
25 |
--------------------------------------------------------------------------------
/apps/examples/4.1.1-dynamic-ui/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, render, ui
2 |
3 | app_ui = ui.page_fluid(
4 | ui.input_checkbox("show_checkbox", "Show Checkbox"),
5 | ui.panel_conditional(
6 | "input.show_checkbox",
7 | ui.input_checkbox("show_slider", "Show Slider"),
8 | ),
9 | ui.output_ui("dynamic_slider"),
10 | )
11 |
12 |
13 | def server(input, output, session):
14 | @output
15 | @render.ui
16 | def dynamic_slider():
17 | print(input.show_slider())
18 | if input.show_slider():
19 | return ui.input_slider("n", "N", 0, 100, 20)
20 |
21 |
22 | app = App(app_ui, server)
23 |
--------------------------------------------------------------------------------
/apps/examples/4.1.2-conditional-panel/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, render, ui
2 |
3 | app_ui = ui.page_fluid(
4 | ui.input_checkbox("show_slider", "Show Slider"),
5 | ui.panel_conditional(
6 | "input.show_slider", ui.input_slider("slider", "Slider", 0, 100, 50)
7 | ),
8 | )
9 |
10 | app = App(app_ui, None)
11 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.0-hello-world/README:
--------------------------------------------------------------------------------
1 | Modify the app to add "Hello World!" to the main heading.
2 | Click the play button to run the app.
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.0-hello-world/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import ui
2 |
3 | ui.h1("Hello World!")
4 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.0-hello-world/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import ui
2 |
3 | ui.h1("")
4 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.0-hello-world/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.1-data-frame/README:
--------------------------------------------------------------------------------
1 | Add the right `@render` decorator to the `penguins_df` function to get the data frame to render.
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.1-data-frame/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import ui, render
2 | import pandas as pd
3 | from pathlib import Path
4 |
5 | infile = Path(__file__).parent / "simulated-data.csv"
6 | df = pd.read_csv(infile)
7 | df = df.drop(columns=["text"])
8 |
9 | # The render.data_frame decorator is used to display dataframes.
10 |
11 |
12 | @render.data_frame
13 | def penguins_df():
14 | return df
15 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.1-data-frame/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import ui, render
2 | import pandas as pd
3 | from pathlib import Path
4 |
5 | infile = Path(__file__).parent / "simulated-data.csv"
6 | df = pd.read_csv(infile)
7 | df = df.drop(columns=["text"])
8 |
9 |
10 | @render.____
11 | def penguins_df():
12 | return df
13 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.1-data-frame/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.2-debug/README:
--------------------------------------------------------------------------------
1 | This app is not rendering properly, can you figure out what's wrong?
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.2-debug/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import ui, render
2 | import pandas as pd
3 | from pathlib import Path
4 |
5 | infile = Path(__file__).parent / "model_data.csv"
6 | df = pd.read_csv(infile)
7 | df = df.drop(columns=["text"])
8 |
9 |
10 | # It's important that you use the right decorator for your funciton's return value
11 | # in this case since the function is returning a dataframe we need to use the
12 | # `@render.data_frame` decorator.
13 | @render.data_frame
14 | def penguins_df():
15 | return df
16 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.2-debug/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import ui, render
2 | import pandas as pd
3 | from pathlib import Path
4 |
5 | infile = Path(__file__).parent / "model_data.csv"
6 | df = pd.read_csv(infile)
7 | df = df.drop(columns=["text"])
8 |
9 |
10 | @render.plot
11 | def penguins_df():
12 | return df
13 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.2-debug/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.3-filter-input/README:
--------------------------------------------------------------------------------
1 | Add a select input to the app which lets the user select one of the accounts. For reference the account list is:
2 | `["Berge & Berge", "Fritsch & Fritsch", "Hintz & Hintz", "Mosciski and Sons", "Wolff Ltd"]`
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.3-filter-input/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui
2 | import pandas as pd
3 | from pathlib import Path
4 | from data_import import df
5 |
6 | # All the inputs start with `ui.input_*`, which adds an input to the app.
7 | # Note that this input doesn't do anything because we haven't connected it
8 | # to a rendering function.
9 |
10 | ui.input_select(
11 | id="account",
12 | label="Account",
13 | choices=[
14 | "Berge & Berge",
15 | "Fritsch & Fritsch",
16 | "Hintz & Hintz",
17 | "Mosciski and Sons",
18 | "Wolff Ltd",
19 | ],
20 | )
21 |
22 |
23 | @render.data_frame
24 | def table():
25 | account_counts = df.groupby("account").size().reset_index(name="counts")
26 | return account_counts
27 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.3-filter-input/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui
2 | import pandas as pd
3 | from pathlib import Path
4 | from data_import import df
5 |
6 |
7 | @render.data_frame
8 | def table():
9 | account_counts = df.groupby("account").size().reset_index(name="counts")
10 | return account_counts
11 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.3-filter-input/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 |
4 | file_path = Path(__file__).parent / "simulated-data.csv"
5 |
6 | print("Running!")
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.3-filter-input/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.4-filter-connect/README:
--------------------------------------------------------------------------------
1 | The select input is not connected to the output table so changing it doesn't do anything. Modify the app so that the data frame is filtered when the selection changes.
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.4-filter-connect/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | import pandas as pd
3 | from pathlib import Path
4 | from data_import import df
5 |
6 | ui.input_select(
7 | id="account",
8 | label="Account",
9 | choices=[
10 | "Berge & Berge",
11 | "Fritsch & Fritsch",
12 | "Hintz & Hintz",
13 | "Mosciski and Sons",
14 | "Wolff Ltd",
15 | ],
16 | )
17 |
18 |
19 | @render.data_frame
20 | def table():
21 | # When we call the account input with `input.account()` we can use its value
22 | # with regular Python code. This will also cause the rendering function
23 | # to rerun whenever the user changes the account value.
24 | account_subset = df[df["account"] == input.account()]
25 | account_counts = (
26 | account_subset.groupby(["account", "sub_account"])
27 | .size()
28 | .reset_index(name="count")
29 | )
30 | return account_counts
31 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.4-filter-connect/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | import pandas as pd
3 | from pathlib import Path
4 | from data_import import df
5 |
6 | ui.input_select(
7 | id="account",
8 | label="Account",
9 | choices=[
10 | "Berge & Berge",
11 | "Fritsch & Fritsch",
12 | "Hintz & Hintz",
13 | "Mosciski and Sons",
14 | "Wolff Ltd",
15 | ],
16 | )
17 |
18 |
19 | @render.data_frame
20 | def table():
21 | account_subset = df
22 | account_counts = (
23 | account_subset.groupby(["account", "sub_account"])
24 | .size()
25 | .reset_index(name="count")
26 | )
27 | return account_counts
28 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.4-filter-connect/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.4-filter-connect/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.5-debug/README:
--------------------------------------------------------------------------------
1 | Something is wrong with this application, can you fix it?
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.5-debug/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | import pandas as pd
3 | from pathlib import Path
4 | from data_import import df
5 |
6 | ui.input_select(
7 | "account",
8 | "Account",
9 | choices=[
10 | "Berge & Berge",
11 | "Fritsch & Fritsch",
12 | "Hintz & Hintz",
13 | "Mosciski and Sons",
14 | "Wolff Ltd",
15 | ],
16 | )
17 |
18 |
19 | @render.data_frame
20 | def table():
21 | # Remember that inputs are callable values, so whenever you refer to them
22 | # in rendering functions you need to call them (`input.account()` not `input.account`)
23 | account_subset = df[df["account"] == input.account()]
24 | account_counts = (
25 | account_subset.groupby("sub_account").size().reset_index(name="counts")
26 | )
27 | return account_counts
28 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.5-debug/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | import pandas as pd
3 | from pathlib import Path
4 | from data_import import df
5 |
6 | ui.input_select(
7 | "account",
8 | "Account",
9 | choices=[
10 | "Berge & Berge",
11 | "Fritsch & Fritsch",
12 | "Hintz & Hintz",
13 | "Mosciski and Sons",
14 | "Wolff Ltd",
15 | ],
16 | )
17 |
18 |
19 | @render.data_frame
20 | def table():
21 | account_subset = df[df["account"] == input.account]
22 | account_counts = (
23 | account_subset.groupby("sub_account").size().reset_index(name="counts")
24 | )
25 | return account_counts
26 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.5-debug/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.5-debug/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.6-debug/README:
--------------------------------------------------------------------------------
1 | This app is generating the error "No current reactive context", can you resolve it?
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.6-debug/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | import pandas as pd
3 | from pathlib import Path
4 | from data_import import df
5 |
6 | ui.input_select(
7 | "account",
8 | "Account",
9 | choices=[
10 | "Berge & Berge",
11 | "Fritsch & Fritsch",
12 | "Hintz & Hintz",
13 | "Mosciski and Sons",
14 | "Wolff Ltd",
15 | ],
16 | )
17 |
18 |
19 | @render.data_frame
20 | def table():
21 | # Inputs can only be called within a reactive context, which
22 | # means that if you refer to them outside of a rendering function
23 | # you'll get an error.
24 | account_subset = df[df["account"] == input.account()]
25 | account_counts = (
26 | account_subset.groupby("sub_account").size().reset_index(name="counts")
27 | )
28 | return account_counts
29 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.6-debug/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | import pandas as pd
3 | from pathlib import Path
4 | from data_import import df
5 |
6 | ui.input_select(
7 | "account",
8 | "Account",
9 | choices=[
10 | "Berge & Berge",
11 | "Fritsch & Fritsch",
12 | "Hintz & Hintz",
13 | "Mosciski and Sons",
14 | "Wolff Ltd",
15 | ],
16 | )
17 |
18 |
19 | account_subset = df[df["account"] == input.account()]
20 |
21 |
22 | @render.data_frame
23 | def table():
24 | account_counts = (
25 | account_subset.groupby("sub_account").size().reset_index(name="counts")
26 | )
27 | return account_counts
28 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.6-debug/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.6-debug/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.7-add-plot/README:
--------------------------------------------------------------------------------
1 | Use the `plot_precision_recall_curve` function from the `plots.py` module to add a second plot output to the app. Note that you should use the `@render_plotly` decorator
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.7-add-plot/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve
4 | from shinywidgets import render_plotly
5 |
6 | ui.input_select(
7 | "account",
8 | "Account",
9 | choices=[
10 | "Berge & Berge",
11 | "Fritsch & Fritsch",
12 | "Hintz & Hintz",
13 | "Mosciski and Sons",
14 | "Wolff Ltd",
15 | ],
16 | )
17 |
18 | # It's a good idea to use helper functions for things like drawing plots
19 | # or displaying data frames because it makes your app more modular and easy to
20 | # read.
21 | @render_plotly
22 | def precision_recall_plot():
23 | account_subset = df[df["account"] == input.account()]
24 | return plot_precision_recall_curve(
25 | account_subset, "is_electronics", "training_score"
26 | )
27 |
28 |
29 | @render_plotly
30 | def auc_plot():
31 | account_subset = df[df["account"] == input.account()]
32 | return plot_auc_curve(account_subset, "is_electronics", "training_score")
33 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.7-add-plot/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve
4 | from shinywidgets import render_plotly
5 |
6 | ui.input_select(
7 | "account",
8 | "Account",
9 | choices=[
10 | "Berge & Berge",
11 | "Fritsch & Fritsch",
12 | "Hintz & Hintz",
13 | "Mosciski and Sons",
14 | "Wolff Ltd",
15 | ],
16 | )
17 |
18 |
19 | @render_plotly
20 | def auc_plot():
21 | account_subset = df[df["account"] == input.account()]
22 | return plot_auc_curve(account_subset, "is_electronics", "training_score")
23 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.7-add-plot/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.7-add-plot/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/1.7-add-plot/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/1-getting-started/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.1-sidebar/README:
--------------------------------------------------------------------------------
1 | Use `ui.sidebar()` to put the Account dropdown in a sidebar.
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.1-sidebar/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve
4 | from shinywidgets import render_plotly
5 |
6 | # The ui.sidebar can be included anywhere in your app script,
7 | # but by convention it usually goes near the top.
8 | with ui.sidebar():
9 | ui.input_select(
10 | "account",
11 | "Account",
12 | choices=[
13 | "Berge & Berge",
14 | "Fritsch & Fritsch",
15 | "Hintz & Hintz",
16 | "Mosciski and Sons",
17 | "Wolff Ltd",
18 | ],
19 | )
20 |
21 |
22 | @render_plotly
23 | def precision_recall_plot():
24 | account_subset = df[df["account"] == input.account()]
25 | return plot_precision_recall_curve(
26 | account_subset, "is_electronics", "training_score"
27 | )
28 |
29 |
30 | @render_plotly
31 | def auc_plot():
32 | account_subset = df[df["account"] == input.account()]
33 | return plot_auc_curve(account_subset, "is_electronics", "training_score")
34 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.1-sidebar/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve
4 | from shinywidgets import render_plotly
5 |
6 | ui.input_select(
7 | "account",
8 | "Account",
9 | choices=[
10 | "Berge & Berge",
11 | "Fritsch & Fritsch",
12 | "Hintz & Hintz",
13 | "Mosciski and Sons",
14 | "Wolff Ltd",
15 | ],
16 | )
17 |
18 |
19 | @render_plotly
20 | def precision_recall_plot():
21 | account_subset = df[df["account"] == input.account()]
22 | return plot_precision_recall_curve(
23 | account_subset, "is_electronics", "training_score"
24 | )
25 |
26 |
27 | @render_plotly
28 | def auc_plot():
29 | account_subset = df[df["account"] == input.account()]
30 | return plot_auc_curve(account_subset, "is_electronics", "training_score")
31 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.1-sidebar/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.1-sidebar/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.1-sidebar/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.2-cards/README:
--------------------------------------------------------------------------------
1 | Wrap the two plots in cards.
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.2-cards/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve
4 | from shinywidgets import render_plotly
5 |
6 | with ui.sidebar():
7 | ui.input_select(
8 | "account",
9 | "Account",
10 | choices=[
11 | "Berge & Berge",
12 | "Fritsch & Fritsch",
13 | "Hintz & Hintz",
14 | "Mosciski and Sons",
15 | "Wolff Ltd",
16 | ],
17 | )
18 |
19 |
20 | # Adding a ui.card context manager will add a card to the UI.
21 | # This is useful for grouping related elements together.
22 | # You can add as many ui elements as you like to the card.
23 | with ui.card():
24 |
25 | @render_plotly
26 | def precision_recall_plot():
27 | account_subset = df[df["account"] == input.account()]
28 | return plot_precision_recall_curve(
29 | account_subset, "is_electronics", "training_score"
30 | )
31 |
32 |
33 | with ui.card():
34 |
35 | @render_plotly
36 | def auc_plot():
37 | account_subset = df[df["account"] == input.account()]
38 | return plot_auc_curve(account_subset, "is_electronics", "training_score")
39 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.2-cards/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve
4 | from shinywidgets import render_plotly
5 |
6 | with ui.sidebar():
7 | ui.input_select(
8 | "account",
9 | "Account",
10 | choices=[
11 | "Berge & Berge",
12 | "Fritsch & Fritsch",
13 | "Hintz & Hintz",
14 | "Mosciski and Sons",
15 | "Wolff Ltd",
16 | ],
17 | )
18 |
19 |
20 | @render_plotly
21 | def precision_recall_plot():
22 | account_subset = df[df["account"] == input.account()]
23 | return plot_precision_recall_curve(
24 | account_subset, "is_electronics", "training_score"
25 | )
26 |
27 |
28 | @render_plotly
29 | def auc_plot():
30 | account_subset = df[df["account"] == input.account()]
31 | return plot_auc_curve(account_subset, "is_electronics", "training_score")
32 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.2-cards/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.2-cards/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.2-cards/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.3-cards-switch/README:
--------------------------------------------------------------------------------
1 | Lets add a dropdown to let the user select the model metrics, this involves a few steps:
2 | 1) Add a `ui.input_select` item to the card with two options
3 | 2) Modify the `@render.plotly` to conditionally display one plot or the other based on that dropdown
4 |
5 | You will only need one `@render.plotly` function.
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.3-cards-switch/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve
4 | from shinywidgets import render_plotly
5 |
6 | with ui.sidebar():
7 | ui.input_select(
8 | "account",
9 | "Account",
10 | choices=[
11 | "Berge & Berge",
12 | "Fritsch & Fritsch",
13 | "Hintz & Hintz",
14 | "Mosciski and Sons",
15 | "Wolff Ltd",
16 | ],
17 | )
18 |
19 | # Cards are great for grouping related elements together.
20 | with ui.card():
21 | ui.card_header("Model Metrics")
22 |
23 | @render_plotly
24 | def metric_plot():
25 | account_subset = df[df["account"] == input.account()]
26 | if input.metric() == "ROC Curve":
27 | return plot_auc_curve(account_subset, "is_electronics", "training_score")
28 | else:
29 | return plot_precision_recall_curve(
30 | account_subset, "is_electronics", "training_score"
31 | )
32 |
33 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
34 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.3-cards-switch/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve
4 | from shinywidgets import render_plotly
5 |
6 | with ui.sidebar():
7 | ui.input_select(
8 | "account",
9 | "Account",
10 | choices=[
11 | "Berge & Berge",
12 | "Fritsch & Fritsch",
13 | "Hintz & Hintz",
14 | "Mosciski and Sons",
15 | "Wolff Ltd",
16 | ],
17 | )
18 |
19 | with ui.card():
20 |
21 | @render_plotly
22 | def precision_recall_plot():
23 | account_subset = df[df["account"] == input.account()]
24 | return plot_precision_recall_curve(
25 | account_subset, "is_electronics", "training_score"
26 | )
27 |
28 |
29 | with ui.card():
30 |
31 | @render_plotly
32 | def auc_plot():
33 | account_subset = df[df["account"] == input.account()]
34 | return plot_auc_curve(account_subset, "is_electronics", "training_score")
35 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.3-cards-switch/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.3-cards-switch/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.3-cards-switch/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.4-layout-columns/README:
--------------------------------------------------------------------------------
1 | Use `ui.layout_columns()` to split the app into two columns.
2 |
3 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.4-layout-columns/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
4 | from shinywidgets import render_plotly
5 |
6 | with ui.sidebar():
7 | ui.input_select(
8 | "account",
9 | "Account",
10 | choices=[
11 | "Berge & Berge",
12 | "Fritsch & Fritsch",
13 | "Hintz & Hintz",
14 | "Mosciski and Sons",
15 | "Wolff Ltd",
16 | ],
17 | )
18 |
19 | # `layout_columns` lays child elements out side by side.
20 | # You can add anything to the layout columns context manager, but it
21 | # works best with cards and value boxes.
22 | with ui.layout_columns():
23 |
24 | with ui.card():
25 | ui.card_header("Model Metrics")
26 |
27 | @render_plotly
28 | def metric_plot():
29 | account_subset = df[df["account"] == input.account()]
30 | if input.metric() == "ROC Curve":
31 | return plot_auc_curve(
32 | account_subset, "is_electronics", "training_score"
33 | )
34 | else:
35 | return plot_precision_recall_curve(
36 | account_subset, "is_electronics", "training_score"
37 | )
38 |
39 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
40 |
41 | with ui.card():
42 | ui.card_header("Model Scores")
43 |
44 | @render_plotly
45 | def score_dist():
46 | account_subset = df[df["account"] == input.account()]
47 | return plot_score_distribution(account_subset)
48 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.4-layout-columns/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
4 | from shinywidgets import render_plotly
5 |
6 | with ui.sidebar():
7 | ui.input_select(
8 | "account",
9 | "Account",
10 | choices=[
11 | "Berge & Berge",
12 | "Fritsch & Fritsch",
13 | "Hintz & Hintz",
14 | "Mosciski and Sons",
15 | "Wolff Ltd",
16 | ],
17 | )
18 |
19 | with ui.card():
20 | ui.card_header("Model Metrics")
21 |
22 | @render_plotly
23 | def metric_plot():
24 | account_subset = df[df["account"] == input.account()]
25 | if input.metric() == "ROC Curve":
26 | return plot_auc_curve(account_subset, "is_electronics", "training_score")
27 | else:
28 | return plot_precision_recall_curve(
29 | account_subset, "is_electronics", "training_score"
30 | )
31 |
32 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
33 |
34 |
35 | with ui.card():
36 | ui.card_header("Model Scores")
37 |
38 | @render_plotly
39 | def score_dist():
40 | account_subset = df[df["account"] == input.account()]
41 | return plot_score_distribution(account_subset)
42 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.4-layout-columns/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.4-layout-columns/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/2.4-layout-columns/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/2-basic-ui/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.1-reactive-calc/README:
--------------------------------------------------------------------------------
1 | Extract the account filtering logic into a reactive calculation.
2 |
3 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.1-reactive-calc/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from shiny import reactive
3 | from data_import import df
4 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
5 | from shinywidgets import render_plotly
6 |
7 |
8 | # You should always extract repeated logic into a reactive.cacl
9 | # in addition to making your app easier to read it will also
10 | # make it run faster because the reactive calc is calculated once
11 | # even if many other rendering functions retrieve its value.
12 | @reactive.calc
13 | def account_data():
14 | return df[df["account"] == input.account()]
15 |
16 |
17 | with ui.sidebar():
18 | ui.input_select(
19 | "account",
20 | "Account",
21 | choices=[
22 | "Berge & Berge",
23 | "Fritsch & Fritsch",
24 | "Hintz & Hintz",
25 | "Mosciski and Sons",
26 | "Wolff Ltd",
27 | ],
28 | )
29 |
30 | with ui.layout_columns():
31 |
32 | with ui.card():
33 | ui.card_header("Model Metrics")
34 |
35 | @render_plotly
36 | def metric_plot():
37 | # `account_data` is called similar to an input.
38 | if input.metric() == "ROC Curve":
39 | return plot_auc_curve(
40 | account_data(), "is_electronics", "training_score"
41 | )
42 | else:
43 | return plot_precision_recall_curve(
44 | account_data(), "is_electronics", "training_score"
45 | )
46 |
47 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
48 |
49 | with ui.card():
50 | ui.card_header("Model Scores")
51 |
52 | @render_plotly
53 | def score_dist():
54 | # `account_data` is called similar to an input.
55 | return plot_score_distribution(account_data())
56 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.1-reactive-calc/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from shiny import reactive
3 | from data_import import df
4 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
5 | from shinywidgets import render_plotly
6 |
7 | with ui.sidebar():
8 | ui.input_select(
9 | "account",
10 | "Account",
11 | choices=[
12 | "Berge & Berge",
13 | "Fritsch & Fritsch",
14 | "Hintz & Hintz",
15 | "Mosciski and Sons",
16 | "Wolff Ltd",
17 | ],
18 | )
19 |
20 | with ui.layout_columns():
21 |
22 | with ui.card():
23 | ui.card_header("Model Metrics")
24 |
25 | @render_plotly
26 | def metric_plot():
27 | account_subset = df[df["account"] == input.account()]
28 | if input.metric() == "ROC Curve":
29 | return plot_auc_curve(
30 | account_subset, "is_electronics", "training_score"
31 | )
32 | else:
33 | return plot_precision_recall_curve(
34 | account_subset, "is_electronics", "training_score"
35 | )
36 |
37 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
38 |
39 | with ui.card():
40 | ui.card_header("Model Scores")
41 |
42 | @render_plotly
43 | def score_dist():
44 | account_subset = df[df["account"] == input.account()]
45 | return plot_score_distribution(account_subset)
46 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.1-reactive-calc/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.1-reactive-calc/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.1-reactive-calc/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.2-stacking-reactives/README:
--------------------------------------------------------------------------------
1 | We have a second sidebar input which allows the user to filter the dataset by the number of characters in the `text` field.
2 | Add a second reactive calculation to the app which filters the `account_data()` reactive.
3 |
4 | For reference `input.chars()` returns a tuple with the lower and upper range of a value, and you can filter the data frame with:
5 | `df[df["text"].str.len().between(*input.chars())]`.
6 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.2-stacking-reactives/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from shiny import reactive
3 | from data_import import df
4 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
5 | from shinywidgets import render_plotly
6 |
7 |
8 | @reactive.calc
9 | def account_data():
10 | return df[df["account"] == input.account()]
11 |
12 |
13 | # You can call reactive calculations from other reactive calculations.
14 | # This is very useful for performance because the calculation will be
15 | # minimally rerun.
16 | @reactive.calc()
17 | def character_filter():
18 | return account_data()[account_data()["text"].str.len().between(*input.chars())]
19 |
20 |
21 | with ui.sidebar():
22 | ui.input_select(
23 | "account",
24 | "Account",
25 | choices=[
26 | "Berge & Berge",
27 | "Fritsch & Fritsch",
28 | "Hintz & Hintz",
29 | "Mosciski and Sons",
30 | "Wolff Ltd",
31 | ],
32 | )
33 |
34 | ui.input_slider(
35 | "chars",
36 | "Text length",
37 | min=0,
38 | max=df["text"].str.len().max(),
39 | value=[500, 6000],
40 | )
41 |
42 | with ui.layout_columns():
43 |
44 | with ui.card():
45 | ui.card_header("Model Metrics")
46 |
47 | @render_plotly
48 | def metric_plot():
49 | if input.metric() == "ROC Curve":
50 | return plot_auc_curve(
51 | character_filter(), "is_electronics", "training_score"
52 | )
53 | else:
54 | return plot_precision_recall_curve(
55 | character_filter(), "is_electronics", "training_score"
56 | )
57 |
58 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
59 |
60 | with ui.card():
61 | ui.card_header("Model Scores")
62 |
63 | @render_plotly
64 | def score_dist():
65 | return plot_score_distribution(character_filter())
66 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.2-stacking-reactives/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from data_import import df
3 | from shiny import reactive
4 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
5 | from shinywidgets import render_plotly
6 |
7 |
8 | @reactive.calc
9 | def account_data():
10 | return df[df["account"] == input.account()]
11 |
12 |
13 | with ui.sidebar():
14 | ui.input_select(
15 | "account",
16 | "Account",
17 | choices=[
18 | "Berge & Berge",
19 | "Fritsch & Fritsch",
20 | "Hintz & Hintz",
21 | "Mosciski and Sons",
22 | "Wolff Ltd",
23 | ],
24 | )
25 |
26 | ui.input_slider(
27 | "chars",
28 | "Text length",
29 | min=0,
30 | max=df["text"].str.len().max(),
31 | value=[500, 6000],
32 | )
33 |
34 | with ui.layout_columns():
35 |
36 | with ui.card():
37 | ui.card_header("Model Metrics")
38 |
39 | @render_plotly
40 | def metric_plot():
41 | if input.metric() == "ROC Curve":
42 | return plot_auc_curve(account_data, "is_electronics", "training_score")
43 | else:
44 | return plot_precision_recall_curve(
45 | account_data(), "is_electronics", "training_score"
46 | )
47 |
48 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
49 |
50 | with ui.card():
51 | ui.card_header("Model Scores")
52 |
53 | @render_plotly
54 | def score_dist():
55 | return plot_score_distribution(account_data())
56 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.2-stacking-reactives/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.2-stacking-reactives/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/3.2-stacking-reactives/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/3-reactivity/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-dynamic-ui/4.1-render-express/README:
--------------------------------------------------------------------------------
1 | Use `render.express` to create a second dropdown which only contains the unique `sub_account` values of the filtered data.
2 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-dynamic-ui/4.1-render-express/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from shiny import reactive
3 | from data_import import df
4 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
5 | from shinywidgets import render_plotly
6 |
7 |
8 | @reactive.calc()
9 | def account_data():
10 | return df[
11 | (df["account"] == input.account()) & (df["sub_account"] == input.sub_account())
12 | ]
13 |
14 |
15 | @reactive.calc()
16 | def character_filter():
17 | return account_data()[(account_data()["text"].str.len().between(*input.chars()))]
18 |
19 |
20 | with ui.sidebar():
21 | ui.input_select(
22 | "account",
23 | "Account",
24 | choices=[
25 | "Berge & Berge",
26 | "Fritsch & Fritsch",
27 | "Hintz & Hintz",
28 | "Mosciski and Sons",
29 | "Wolff Ltd",
30 | ],
31 | )
32 |
33 | # You shouldn't use return values with `render.express` instead just call the inputs
34 | # and rendering funcitons and they will be added to the app.
35 | @render.express
36 | def sub_selector():
37 | # We can't use the account_data reactive here because we are using the
38 | # sub_account input as part of that reactive.
39 | choice_data = df[df["account"] == input.account()]
40 | choices = choice_data["sub_account"].unique().tolist()
41 | ui.input_select("sub_account", "Sub Account", choices=choices)
42 |
43 | ui.input_slider(
44 | "chars",
45 | "Text length",
46 | min=0,
47 | max=df["text"].str.len().max(),
48 | value=[500, 6000],
49 | )
50 |
51 | with ui.layout_columns():
52 |
53 | with ui.card():
54 | ui.card_header("Model Metrics")
55 |
56 | @render_plotly
57 | def metric_plot():
58 | if input.metric() == "ROC Curve":
59 | return plot_auc_curve(
60 | character_filter(), "is_electronics", "training_score"
61 | )
62 | else:
63 | return plot_precision_recall_curve(
64 | character_filter(), "is_electronics", "training_score"
65 | )
66 |
67 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
68 |
69 | with ui.card():
70 | ui.card_header("Model Scores")
71 |
72 | @render_plotly
73 | def score_dist():
74 | return plot_score_distribution(character_filter())
75 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-dynamic-ui/4.1-render-express/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from shiny import reactive
3 | from data_import import df
4 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
5 | from shinywidgets import render_plotly
6 |
7 |
8 | @reactive.calc
9 | def account_data():
10 | return df[df["account"] == input.account()]
11 |
12 |
13 | @reactive.calc()
14 | def character_filter():
15 | return account_data()[(account_data()["text"].str.len().between(*input.chars()))]
16 |
17 |
18 | with ui.sidebar():
19 | ui.input_select(
20 | "account",
21 | "Account",
22 | choices=[
23 | "Berge & Berge",
24 | "Fritsch & Fritsch",
25 | "Hintz & Hintz",
26 | "Mosciski and Sons",
27 | "Wolff Ltd",
28 | ],
29 | )
30 |
31 | ui.input_slider(
32 | "chars",
33 | "Text length",
34 | min=0,
35 | max=df["text"].str.len().max(),
36 | value=[500, 6000],
37 | )
38 |
39 | with ui.layout_columns():
40 |
41 | with ui.card():
42 | ui.card_header("Model Metrics")
43 |
44 | @render_plotly
45 | def metric_plot():
46 | if input.metric() == "ROC Curve":
47 | return plot_auc_curve(
48 | character_filter(), "is_electronics", "training_score"
49 | )
50 | else:
51 | return plot_precision_recall_curve(
52 | character_filter(), "is_electronics", "training_score"
53 | )
54 |
55 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
56 |
57 | with ui.card():
58 | ui.card_header("Model Scores")
59 |
60 | @render_plotly
61 | def score_dist():
62 | return plot_score_distribution(character_filter())
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-dynamic-ui/4.1-render-express/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-dynamic-ui/4.1-render-express/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-dynamic-ui/4.1-render-express/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-dynamic-ui/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.1-tabs/README:
--------------------------------------------------------------------------------
1 | Use `ui.navset_tabs` and `ui.nav` to put the dataframe output on a second tab.
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.1-tabs/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 |
6 | infile = Path(__file__).parent / "weather.csv"
7 | weather = pd.read_csv(infile)
8 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
9 |
10 |
11 | app_ui = ui.page_fluid(
12 | ui.panel_title("Weather error"),
13 | ui.layout_sidebar(
14 | ui.panel_sidebar(
15 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
16 | ui.input_selectize(
17 | "cities",
18 | "Select Cities",
19 | weather["city"].unique().tolist(),
20 | selected="BUFFALO",
21 | multiple=True,
22 | ),
23 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
24 | width=3,
25 | ),
26 | ui.panel_main(
27 | ui.navset_tab(
28 | ui.nav(
29 | "Error",
30 | ui.output_plot("error_distribution"),
31 | ui.output_plot("error_by_day"),
32 | ),
33 | ui.nav("Data", ui.output_data_frame("data")),
34 | )
35 | ),
36 | ),
37 | )
38 |
39 |
40 | def server(input, output, session):
41 | @reactive.Calc
42 | def filtered_data():
43 | df = weather.copy()
44 | df = df[df["city"].isin(input.cities())]
45 | df["date"] = pd.to_datetime(df["date"])
46 | dates = pd.to_datetime(input.dates())
47 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
48 | return df
49 |
50 | @output
51 | @render.plot
52 | def error_distribution():
53 | return temp_distirbution(filtered_data())
54 |
55 | @output
56 | @render.plot
57 | def error_by_day():
58 | return daily_error(filtered_data(), input.alpha())
59 |
60 | @output
61 | @render.data_frame
62 | def data():
63 | return filtered_data()
64 |
65 |
66 | app = App(app_ui, server)
67 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.1-tabs/app.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 |
12 | card1 = x.ui.card(ui.output_plot("Plot"))
13 | tab1 = ui.nav(
14 | "Tab1",
15 | card1,
16 | ui.output_text("some_text"),
17 | )
18 | tab2 = ui.nav("Tab2", ui.output_data_frame("data"))
19 | tab3 = ui.nav("Tab3", ui.output_image("image"))
20 | ui.navset_tab(tab1, tab2, tab3)
21 |
22 |
23 | app_ui = ui.page_fluid(
24 | ui.layout_sidebar(
25 | ui.panel_sidebar(
26 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
27 | ui.input_selectize(
28 | "cities",
29 | "Select Cities",
30 | weather["city"].unique().tolist(),
31 | selected="BUFFALO",
32 | multiple=True,
33 | ),
34 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
35 | width=3,
36 | ),
37 | ui.panel_main(
38 | ui.output_plot("error_distribution"),
39 | ui.output_plot("error_by_day"),
40 | ui.output_data_frame("data"),
41 | ),
42 | )
43 | )
44 |
45 |
46 | def server(input, output, session):
47 | @reactive.Calc
48 | def filtered_data():
49 | df = weather.copy()
50 | df = df[df["city"].isin(input.cities())]
51 | df["date"] = pd.to_datetime(df["date"])
52 | dates = pd.to_datetime(input.dates())
53 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
54 | return df
55 |
56 | @output
57 | @render.plot
58 | def error_distribution():
59 | return temp_distirbution(filtered_data())
60 |
61 | @output
62 | @render.plot
63 | def error_by_day():
64 | return daily_error(filtered_data(), input.alpha())
65 |
66 | @output
67 | @render.data_frame
68 | def data():
69 | return filtered_data()
70 |
71 |
72 | app = App(app_ui, server)
73 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.1-tabs/plots.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from plotnine import (
3 | ggplot,
4 | aes,
5 | geom_density,
6 | theme_light,
7 | labs,
8 | geom_point,
9 | theme,
10 | element_text,
11 | )
12 |
13 |
14 | def temp_distirbution(plot_df: pd.DataFrame) -> ggplot:
15 | plot_df = plot_df[["observed_temp", "forecast_temp", "date"]]
16 | plot_df = pd.melt(
17 | plot_df, id_vars="date", value_vars=["observed_temp", "forecast_temp"]
18 | )
19 | out = (
20 | ggplot(plot_df, aes(x="value", group="variable", color="variable"))
21 | + geom_density()
22 | + theme_light()
23 | )
24 | return out
25 |
26 |
27 | def daily_error(plot_df: pd.DataFrame, alpha: float) -> ggplot:
28 | out = (
29 | ggplot(plot_df, aes(x="date", y="error"))
30 | + geom_point(alpha=alpha)
31 | + theme_light()
32 | + theme(axis_text_x=element_text(rotation=45, hjust=1))
33 | )
34 | return out
35 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.1-tabs/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.2-cards/README:
--------------------------------------------------------------------------------
1 | Shiny has a few great components in the `experimental` module, try wrapping the two graphs in `x.ui.card()`. Note that you first need to import the experimental module with `import shiny.experimental as x`.
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.2-cards/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 |
12 | app_ui = ui.page_fluid(
13 | ui.panel_title("Weather error"),
14 | ui.layout_sidebar(
15 | ui.panel_sidebar(
16 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
17 | ui.input_selectize(
18 | "cities",
19 | "Select Cities",
20 | weather["city"].unique().tolist(),
21 | selected="BUFFALO",
22 | multiple=True,
23 | ),
24 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
25 | width=3,
26 | ),
27 | ui.panel_main(
28 | ui.navset_tab(
29 | ui.nav(
30 | "Error",
31 | x.ui.card(
32 | x.ui.card_header("Distribution"),
33 | ui.output_plot("error_distribution"),
34 | ),
35 | x.ui.card(
36 | x.ui.card_header("Error by day"),
37 | ui.output_plot("error_by_day"),
38 | ),
39 | ),
40 | ui.nav("Data", ui.output_data_frame("data")),
41 | )
42 | ),
43 | ),
44 | )
45 |
46 |
47 | def server(input, output, session):
48 | @reactive.Calc
49 | def filtered_data():
50 | df = weather.copy()
51 | df = df[df["city"].isin(input.cities())]
52 | df["date"] = pd.to_datetime(df["date"])
53 | dates = pd.to_datetime(input.dates())
54 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
55 | return df
56 |
57 | @output
58 | @render.plot
59 | def error_distribution():
60 | return temp_distirbution(filtered_data())
61 |
62 | @output
63 | @render.plot
64 | def error_by_day():
65 | return daily_error(filtered_data(), input.alpha())
66 |
67 | @output
68 | @render.data_frame
69 | def data():
70 | return filtered_data()
71 |
72 |
73 | app = App(app_ui, server)
74 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.2-cards/app.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 |
12 | app_ui = ui.page_fluid(
13 | ui.panel_title("Weather error"),
14 | ui.layout_sidebar(
15 | ui.panel_sidebar(
16 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
17 | ui.input_selectize(
18 | "cities",
19 | "Select Cities",
20 | weather["city"].unique().tolist(),
21 | selected="BUFFALO",
22 | multiple=True,
23 | ),
24 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
25 | width=3,
26 | ),
27 | ui.panel_main(
28 | ui.navset_tab(
29 | ui.nav(
30 | "Error",
31 | ui.output_plot("error_distribution"),
32 | ui.output_plot("error_by_day"),
33 | ),
34 | ui.nav("Data", ui.output_data_frame("data")),
35 | )
36 | ),
37 | ),
38 | )
39 |
40 |
41 | def server(input, output, session):
42 | @reactive.Calc
43 | def filtered_data():
44 | df = weather.copy()
45 | df = df[df["city"].isin(input.cities())]
46 | df["date"] = pd.to_datetime(df["date"])
47 | dates = pd.to_datetime(input.dates())
48 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
49 | return df
50 |
51 | @output
52 | @render.plot
53 | def error_distribution():
54 | return temp_distirbution(filtered_data())
55 |
56 | @output
57 | @render.plot
58 | def error_by_day():
59 | return daily_error(filtered_data(), input.alpha())
60 |
61 | @output
62 | @render.data_frame
63 | def data():
64 | return filtered_data()
65 |
66 |
67 | app = App(app_ui, server)
68 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.2-cards/plots.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from plotnine import (
3 | ggplot,
4 | aes,
5 | geom_density,
6 | theme_light,
7 | labs,
8 | geom_point,
9 | theme,
10 | element_text,
11 | )
12 |
13 |
14 | def temp_distirbution(plot_df: pd.DataFrame) -> ggplot:
15 | plot_df = plot_df[["observed_temp", "forecast_temp", "date"]]
16 | plot_df = pd.melt(
17 | plot_df, id_vars="date", value_vars=["observed_temp", "forecast_temp"]
18 | )
19 | out = (
20 | ggplot(plot_df, aes(x="value", group="variable", color="variable"))
21 | + geom_density()
22 | + theme_light()
23 | )
24 | return out
25 |
26 |
27 | def daily_error(plot_df: pd.DataFrame, alpha: float) -> ggplot:
28 | out = (
29 | ggplot(plot_df, aes(x="date", y="error"))
30 | + geom_point(alpha=alpha)
31 | + theme_light()
32 | + theme(axis_text_x=element_text(rotation=45, hjust=1))
33 | )
34 | return out
35 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.2-cards/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.3-layout/README:
--------------------------------------------------------------------------------
1 | Use `ui.row` and `ui.col` to layout the two cards side-by-side.
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.3-layout/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 |
12 | app_ui = ui.page_fluid(
13 | ui.panel_title("Weather error"),
14 | ui.layout_sidebar(
15 | ui.panel_sidebar(
16 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
17 | ui.input_selectize(
18 | "cities",
19 | "Select Cities",
20 | weather["city"].unique().tolist(),
21 | selected="BUFFALO",
22 | multiple=True,
23 | ),
24 | width=3,
25 | ),
26 | ui.panel_main(
27 | ui.navset_tab(
28 | ui.nav(
29 | "Error",
30 | ui.row(
31 | ui.column(
32 | 6,
33 | x.ui.card(
34 | x.ui.card_header("Distribution"),
35 | ui.output_plot("error_distribution"),
36 | ),
37 | ),
38 | ui.column(
39 | 6,
40 | x.ui.card(
41 | x.ui.card_header("Error by day"),
42 | ui.output_plot("error_by_day"),
43 | ui.input_slider(
44 | "alpha", "Plot Alpha", value=0.5, min=0, max=1
45 | ),
46 | ),
47 | ),
48 | ),
49 | ),
50 | ui.nav("Data", ui.output_data_frame("data")),
51 | )
52 | ),
53 | ),
54 | )
55 |
56 |
57 | def server(input, output, session):
58 | @reactive.Calc
59 | def filtered_data():
60 | df = weather.copy()
61 | df = df[df["city"].isin(input.cities())]
62 | df["date"] = pd.to_datetime(df["date"])
63 | dates = pd.to_datetime(input.dates())
64 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
65 | return df
66 |
67 | @output
68 | @render.plot
69 | def error_distribution():
70 | return temp_distirbution(filtered_data())
71 |
72 | @output
73 | @render.plot
74 | def error_by_day():
75 | return daily_error(filtered_data(), input.alpha())
76 |
77 | @output
78 | @render.data_frame
79 | def data():
80 | return filtered_data()
81 |
82 |
83 | app = App(app_ui, server)
84 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.3-layout/app.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 |
12 | app_ui = ui.page_fluid(
13 | ui.panel_title("Weather error"),
14 | ui.layout_sidebar(
15 | ui.panel_sidebar(
16 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
17 | ui.input_selectize(
18 | "cities",
19 | "Select Cities",
20 | weather["city"].unique().tolist(),
21 | selected="BUFFALO",
22 | multiple=True,
23 | ),
24 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
25 | width=3,
26 | ),
27 | ui.panel_main(
28 | ui.navset_tab(
29 | ui.nav(
30 | "Error",
31 | x.ui.card(
32 | x.ui.card_header("Distribution"),
33 | ui.output_plot("error_distribution"),
34 | ),
35 | x.ui.card(
36 | x.ui.card_header("Error by day"),
37 | ui.output_plot("error_by_day"),
38 | ),
39 | ),
40 | ui.nav("Data", ui.output_data_frame("data")),
41 | )
42 | ),
43 | ),
44 | )
45 |
46 |
47 | def server(input, output, session):
48 | @reactive.Calc
49 | def filtered_data():
50 | df = weather.copy()
51 | df = df[df["city"].isin(input.cities())]
52 | df["date"] = pd.to_datetime(df["date"])
53 | dates = pd.to_datetime(input.dates())
54 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
55 | return df
56 |
57 | @output
58 | @render.plot
59 | def error_distribution():
60 | return temp_distirbution(filtered_data())
61 |
62 | @output
63 | @render.plot
64 | def error_by_day():
65 | return daily_error(filtered_data(), input.alpha())
66 |
67 | @output
68 | @render.data_frame
69 | def data():
70 | return filtered_data()
71 |
72 |
73 | app = App(app_ui, server)
74 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.3-layout/plots.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from plotnine import (
3 | ggplot,
4 | aes,
5 | geom_density,
6 | theme_light,
7 | labs,
8 | geom_point,
9 | theme,
10 | element_text,
11 | )
12 |
13 |
14 | def temp_distirbution(plot_df: pd.DataFrame) -> ggplot:
15 | plot_df = plot_df[["observed_temp", "forecast_temp", "date"]]
16 | plot_df = pd.melt(
17 | plot_df, id_vars="date", value_vars=["observed_temp", "forecast_temp"]
18 | )
19 | out = (
20 | ggplot(plot_df, aes(x="value", group="variable", color="variable"))
21 | + geom_density()
22 | + theme_light()
23 | )
24 | return out
25 |
26 |
27 | def daily_error(plot_df: pd.DataFrame, alpha: float) -> ggplot:
28 | out = (
29 | ggplot(plot_df, aes(x="date", y="error"))
30 | + geom_point(alpha=alpha)
31 | + theme_light()
32 | + theme(axis_text_x=element_text(rotation=45, hjust=1))
33 | )
34 | return out
35 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.3-layout/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.4-ui-composition/README:
--------------------------------------------------------------------------------
1 | Our ui code is getting a little complicated. Try extracting the two tab sections into their own objects.
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.4-ui-composition/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 | data_tab = ui.nav("Data", ui.output_data_frame("data"))
12 | error_tab = ui.nav(
13 | "Error",
14 | ui.row(
15 | ui.column(
16 | 6,
17 | x.ui.card(
18 | x.ui.card_header("Distribution"),
19 | ui.output_plot("error_distribution"),
20 | ),
21 | ),
22 | ui.column(
23 | 6,
24 | x.ui.card(
25 | x.ui.card_header("Error by day"),
26 | ui.output_plot("error_by_day"),
27 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
28 | ),
29 | ),
30 | ),
31 | )
32 |
33 | app_ui = ui.page_fluid(
34 | ui.panel_title("Weather error"),
35 | ui.layout_sidebar(
36 | ui.panel_sidebar(
37 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
38 | ui.input_selectize(
39 | "cities",
40 | "Select Cities",
41 | weather["city"].unique().tolist(),
42 | selected="BUFFALO",
43 | multiple=True,
44 | ),
45 | width=3,
46 | ),
47 | ui.panel_main(
48 | ui.navset_tab(
49 | error_tab,
50 | data_tab,
51 | )
52 | ),
53 | ),
54 | )
55 |
56 |
57 | def server(input, output, session):
58 | @reactive.Calc
59 | def filtered_data():
60 | df = weather.copy()
61 | df = df[df["city"].isin(input.cities())]
62 | df["date"] = pd.to_datetime(df["date"])
63 | dates = pd.to_datetime(input.dates())
64 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
65 | return df
66 |
67 | @output
68 | @render.plot
69 | def error_distribution():
70 | return temp_distirbution(filtered_data())
71 |
72 | @output
73 | @render.plot
74 | def error_by_day():
75 | return daily_error(filtered_data(), input.alpha())
76 |
77 | @output
78 | @render.data_frame
79 | def data():
80 | return filtered_data()
81 |
82 |
83 | app = App(app_ui, server)
84 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.4-ui-composition/app.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 |
12 | app_ui = ui.page_fluid(
13 | ui.panel_title("Weather error"),
14 | ui.layout_sidebar(
15 | ui.panel_sidebar(
16 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
17 | ui.input_selectize(
18 | "cities",
19 | "Select Cities",
20 | weather["city"].unique().tolist(),
21 | selected="BUFFALO",
22 | multiple=True,
23 | ),
24 | width=3,
25 | ),
26 | ui.panel_main(
27 | ui.navset_tab(
28 | ui.nav(
29 | "Error",
30 | ui.row(
31 | ui.column(
32 | 6,
33 | x.ui.card(
34 | x.ui.card_header("Distribution"),
35 | ui.output_plot("error_distribution"),
36 | ),
37 | ),
38 | ui.column(
39 | 6,
40 | x.ui.card(
41 | x.ui.card_header("Error by day"),
42 | ui.output_plot("error_by_day"),
43 | ui.input_slider(
44 | "alpha", "Plot Alpha", value=0.5, min=0, max=1
45 | ),
46 | ),
47 | ),
48 | ),
49 | ),
50 | ui.nav("Data", ui.output_data_frame("data")),
51 | )
52 | ),
53 | ),
54 | )
55 |
56 |
57 | def server(input, output, session):
58 | @reactive.Calc
59 | def filtered_data():
60 | df = weather.copy()
61 | df = df[df["city"].isin(input.cities())]
62 | df["date"] = pd.to_datetime(df["date"])
63 | dates = pd.to_datetime(input.dates())
64 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
65 | return df
66 |
67 | @output
68 | @render.plot
69 | def error_distribution():
70 | return temp_distirbution(filtered_data())
71 |
72 | @output
73 | @render.plot
74 | def error_by_day():
75 | return daily_error(filtered_data(), input.alpha())
76 |
77 | @output
78 | @render.data_frame
79 | def data():
80 | return filtered_data()
81 |
82 |
83 | app = App(app_ui, server)
84 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.4-ui-composition/plots.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from plotnine import (
3 | ggplot,
4 | aes,
5 | geom_density,
6 | theme_light,
7 | labs,
8 | geom_point,
9 | theme,
10 | element_text,
11 | )
12 |
13 |
14 | def temp_distirbution(plot_df: pd.DataFrame) -> ggplot:
15 | plot_df = plot_df[["observed_temp", "forecast_temp", "date"]]
16 | plot_df = pd.melt(
17 | plot_df, id_vars="date", value_vars=["observed_temp", "forecast_temp"]
18 | )
19 | out = (
20 | ggplot(plot_df, aes(x="value", group="variable", color="variable"))
21 | + geom_density()
22 | + theme_light()
23 | )
24 | return out
25 |
26 |
27 | def daily_error(plot_df: pd.DataFrame, alpha: float) -> ggplot:
28 | out = (
29 | ggplot(plot_df, aes(x="date", y="error"))
30 | + geom_point(alpha=alpha)
31 | + theme_light()
32 | + theme(axis_text_x=element_text(rotation=45, hjust=1))
33 | )
34 | return out
35 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.4-ui-composition/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.5-value-boxes/README:
--------------------------------------------------------------------------------
1 | Use the `x.value_box` container function to add three value boxes on their own `ui.row`. Note that we have provided the server functions for you.
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.5-value-boxes/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 | data_tab = ui.nav("Data", ui.output_data_frame("data"))
12 | error_tab = ui.nav(
13 | "Error",
14 | ui.row(
15 | ui.column(4, x.ui.value_box("Hot days", ui.output_text("hot_days"))),
16 | ui.column(4, x.ui.value_box("Cold days", ui.output_text("cold_days"))),
17 | ui.column(4, x.ui.value_box("Mean Error", ui.output_text("mean_error"))),
18 | ),
19 | ui.row(
20 | ui.column(
21 | 6,
22 | x.ui.card(
23 | x.ui.card_header("Distribution"),
24 | ui.output_plot("error_distribution"),
25 | ),
26 | ),
27 | ui.column(
28 | 6,
29 | x.ui.card(
30 | x.ui.card_header("Error by day"),
31 | ui.output_plot("error_by_day"),
32 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
33 | ),
34 | ),
35 | ),
36 | )
37 |
38 | app_ui = ui.page_fluid(
39 | ui.panel_title("Weather error"),
40 | ui.layout_sidebar(
41 | ui.panel_sidebar(
42 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
43 | ui.input_selectize(
44 | "cities",
45 | "Select Cities",
46 | weather["city"].unique().tolist(),
47 | selected="BUFFALO",
48 | multiple=True,
49 | ),
50 | width=3,
51 | ),
52 | ui.panel_main(
53 | ui.navset_tab(
54 | error_tab,
55 | data_tab,
56 | )
57 | ),
58 | ),
59 | )
60 |
61 |
62 | def server(input, output, session):
63 | @reactive.Calc
64 | def filtered_data() -> pd.DataFrame:
65 | df = weather.copy()
66 | df = df[df["city"].isin(input.cities())]
67 | df["date"] = pd.to_datetime(df["date"])
68 | dates = pd.to_datetime(input.dates())
69 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
70 | return df
71 |
72 | @output
73 | @render.plot
74 | def error_distribution():
75 | return temp_distirbution(filtered_data())
76 |
77 | @output
78 | @render.plot
79 | def error_by_day():
80 | return daily_error(filtered_data(), input.alpha())
81 |
82 | @output
83 | @render.data_frame
84 | def data():
85 | return filtered_data()
86 |
87 | @output
88 | @render.text
89 | def mean_error():
90 | mean_error = filtered_data()["error"].mean()
91 | return round(mean_error, 2)
92 |
93 | @output
94 | @render.text
95 | def hot_days():
96 | hot_days = filtered_data()["error"] > 0
97 | return sum(hot_days)
98 |
99 | @output
100 | @render.text
101 | def cold_days():
102 | hot_days = filtered_data()["error"] < 0
103 | return sum(hot_days)
104 |
105 |
106 | app = App(app_ui, server)
107 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.5-value-boxes/app.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 | data_tab = ui.nav("Data", ui.output_data_frame("data"))
12 | error_tab = ui.nav(
13 | "Error",
14 | ui.row(
15 | ui.column(
16 | 6,
17 | x.ui.card(
18 | x.ui.card_header("Distribution"),
19 | ui.output_plot("error_distribution"),
20 | ),
21 | ),
22 | ui.column(
23 | 6,
24 | x.ui.card(
25 | x.ui.card_header("Error by day"),
26 | ui.output_plot("error_by_day"),
27 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
28 | ),
29 | ),
30 | ),
31 | )
32 |
33 | app_ui = ui.page_fluid(
34 | ui.panel_title("Weather error"),
35 | ui.layout_sidebar(
36 | ui.panel_sidebar(
37 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
38 | ui.input_selectize(
39 | "cities",
40 | "Select Cities",
41 | weather["city"].unique().tolist(),
42 | selected="BUFFALO",
43 | multiple=True,
44 | ),
45 | width=3,
46 | ),
47 | ui.panel_main(
48 | ui.navset_tab(
49 | error_tab,
50 | data_tab,
51 | )
52 | ),
53 | ),
54 | )
55 |
56 |
57 | def server(input, output, session):
58 | @reactive.Calc
59 | def filtered_data():
60 | df = weather.copy()
61 | df = df[df["city"].isin(input.cities())]
62 | df["date"] = pd.to_datetime(df["date"])
63 | dates = pd.to_datetime(input.dates())
64 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
65 | return df
66 |
67 | @output
68 | @render.plot
69 | def error_distribution():
70 | return temp_distirbution(filtered_data())
71 |
72 | @output
73 | @render.plot
74 | def error_by_day():
75 | return daily_error(filtered_data(), input.alpha())
76 |
77 | @output
78 | @render.data_frame
79 | def data():
80 | return filtered_data()
81 |
82 | @output
83 | @render.text
84 | def mean_error():
85 | mean_error = filtered_data()["error"].mean()
86 | return round(mean_error, 2)
87 |
88 | @output
89 | @render.text
90 | def hot_days():
91 | hot_days = filtered_data()["error"] > 0
92 | return sum(hot_days)
93 |
94 | @output
95 | @render.text
96 | def cold_days():
97 | hot_days = filtered_data()["error"] < 0
98 | return sum(hot_days)
99 |
100 |
101 | app = App(app_ui, server)
102 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.5-value-boxes/plots.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from plotnine import (
3 | ggplot,
4 | aes,
5 | geom_density,
6 | theme_light,
7 | labs,
8 | geom_point,
9 | theme,
10 | element_text,
11 | )
12 |
13 |
14 | def temp_distirbution(plot_df: pd.DataFrame) -> ggplot:
15 | plot_df = plot_df[["observed_temp", "forecast_temp", "date"]]
16 | plot_df = pd.melt(
17 | plot_df, id_vars="date", value_vars=["observed_temp", "forecast_temp"]
18 | )
19 | out = (
20 | ggplot(plot_df, aes(x="value", group="variable", color="variable"))
21 | + geom_density()
22 | + theme_light()
23 | )
24 | return out
25 |
26 |
27 | def daily_error(plot_df: pd.DataFrame, alpha: float) -> ggplot:
28 | out = (
29 | ggplot(plot_df, aes(x="date", y="error"))
30 | + geom_point(alpha=alpha)
31 | + theme_light()
32 | + theme(axis_text_x=element_text(rotation=45, hjust=1))
33 | )
34 | return out
35 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.5-value-boxes/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.6-dynamic-ui/README:
--------------------------------------------------------------------------------
1 | We've added a `state` selector, use `ui.output_ui` to dynamically generate a city selector which only includes cities from the selected states.
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.6-dynamic-ui/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 | data_tab = ui.nav("Data", ui.output_data_frame("data"))
12 | error_tab = ui.nav(
13 | "Error",
14 | ui.row(
15 | ui.column(
16 | 4, x.ui.value_box("Hotter than forecast", ui.output_text("hot_days"))
17 | ),
18 | ui.column(
19 | 4, x.ui.value_box("Colder than forecast", ui.output_text("cold_days"))
20 | ),
21 | ui.column(4, x.ui.value_box("Mean Error", ui.output_text("mean_error"))),
22 | ),
23 | ui.row(
24 | ui.column(
25 | 6,
26 | x.ui.card(
27 | x.ui.card_header("Distribution"),
28 | ui.output_plot("error_distribution"),
29 | ),
30 | ),
31 | ui.column(
32 | 6,
33 | x.ui.card(
34 | x.ui.card_header("Error by day"),
35 | ui.output_plot("error_by_day"),
36 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
37 | ),
38 | ),
39 | ),
40 | )
41 |
42 | app_ui = ui.page_fluid(
43 | ui.panel_title("Weather error"),
44 | ui.layout_sidebar(
45 | ui.panel_sidebar(
46 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
47 | ui.input_selectize(
48 | "states",
49 | "Select States",
50 | weather["state"].unique().tolist(),
51 | selected="CO",
52 | multiple=True,
53 | ),
54 | ui.output_ui("cities_ui"),
55 | width=3,
56 | ),
57 | ui.panel_main(
58 | ui.navset_tab(
59 | error_tab,
60 | data_tab,
61 | )
62 | ),
63 | ),
64 | )
65 |
66 |
67 | def server(input, output, session):
68 | @output
69 | @render.ui
70 | def cities_ui():
71 | df = weather.copy()
72 | df = df[df["state"].isin(input.states())]
73 | city_options = df["city"].unique().tolist()
74 | return ui.input_selectize(
75 | "cities",
76 | "Select Cities",
77 | choices=city_options,
78 | selected=city_options[0],
79 | multiple=True,
80 | )
81 |
82 | @reactive.Calc
83 | def filtered_data() -> pd.DataFrame:
84 | df = weather.copy()
85 | df = df[df["city"].isin(input.cities())]
86 | df["date"] = pd.to_datetime(df["date"])
87 | dates = pd.to_datetime(input.dates())
88 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
89 | return df
90 |
91 | @output
92 | @render.plot
93 | def error_distribution():
94 | return temp_distirbution(filtered_data())
95 |
96 | @output
97 | @render.plot
98 | def error_by_day():
99 | return daily_error(filtered_data(), input.alpha())
100 |
101 | @output
102 | @render.data_frame
103 | def data():
104 | return filtered_data()
105 |
106 | @output
107 | @render.text
108 | def mean_error():
109 | mean_error = filtered_data()["error"].mean()
110 | return round(mean_error, 2)
111 |
112 | @output
113 | @render.text
114 | def hot_days():
115 | hot_days = filtered_data()["error"] > 0
116 | return sum(hot_days)
117 |
118 | @output
119 | @render.text
120 | def cold_days():
121 | hot_days = filtered_data()["error"] < 0
122 | return sum(hot_days)
123 |
124 |
125 | app = App(app_ui, server)
126 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.6-dynamic-ui/app.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 | data_tab = ui.nav("Data", ui.output_data_frame("data"))
12 | error_tab = ui.nav(
13 | "Error",
14 | ui.row(
15 | ui.column(
16 | 4, x.ui.value_box("Hotter than forecast", ui.output_text("hot_days"))
17 | ),
18 | ui.column(
19 | 4, x.ui.value_box("Colder than forecast", ui.output_text("cold_days"))
20 | ),
21 | ui.column(4, x.ui.value_box("Mean Error", ui.output_text("mean_error"))),
22 | ),
23 | ui.row(
24 | ui.column(
25 | 6,
26 | x.ui.card(
27 | x.ui.card_header("Distribution"),
28 | ui.output_plot("error_distribution"),
29 | ),
30 | ),
31 | ui.column(
32 | 6,
33 | x.ui.card(
34 | x.ui.card_header("Error by day"),
35 | ui.output_plot("error_by_day"),
36 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
37 | ),
38 | ),
39 | ),
40 | )
41 |
42 | app_ui = ui.page_fluid(
43 | ui.panel_title("Weather error"),
44 | ui.layout_sidebar(
45 | ui.panel_sidebar(
46 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
47 | ui.input_selectize(
48 | "states",
49 | "Select States",
50 | weather["state"].unique().tolist(),
51 | multiple=True,
52 | ),
53 | width=3,
54 | ),
55 | ui.panel_main(
56 | ui.navset_tab(
57 | error_tab,
58 | data_tab,
59 | )
60 | ),
61 | ),
62 | )
63 |
64 |
65 | def server(input, output, session):
66 | @reactive.Calc
67 | def filtered_data() -> pd.DataFrame:
68 | df = weather.copy()
69 | df = df[df["city"].isin(input.cities())]
70 | df["date"] = pd.to_datetime(df["date"])
71 | dates = pd.to_datetime(input.dates())
72 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
73 | return df
74 |
75 | @output
76 | @render.plot
77 | def error_distribution():
78 | return temp_distirbution(filtered_data())
79 |
80 | @output
81 | @render.plot
82 | def error_by_day():
83 | return daily_error(filtered_data(), input.alpha())
84 |
85 | @output
86 | @render.data_frame
87 | def data():
88 | return filtered_data()
89 |
90 | @output
91 | @render.text
92 | def mean_error():
93 | mean_error = filtered_data()["error"].mean()
94 | return round(mean_error, 2)
95 |
96 | @output
97 | @render.text
98 | def hot_days():
99 | hot_days = filtered_data()["error"] > 0
100 | return sum(hot_days)
101 |
102 | @output
103 | @render.text
104 | def cold_days():
105 | hot_days = filtered_data()["error"] < 0
106 | return sum(hot_days)
107 |
108 |
109 | app = App(app_ui, server)
110 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.6-dynamic-ui/plots.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from plotnine import (
3 | ggplot,
4 | aes,
5 | geom_density,
6 | theme_light,
7 | labs,
8 | geom_point,
9 | theme,
10 | element_text,
11 | )
12 |
13 |
14 | def temp_distirbution(plot_df: pd.DataFrame) -> ggplot:
15 | plot_df = plot_df[["observed_temp", "forecast_temp", "date"]]
16 | plot_df = pd.melt(
17 | plot_df, id_vars="date", value_vars=["observed_temp", "forecast_temp"]
18 | )
19 | out = (
20 | ggplot(plot_df, aes(x="value", group="variable", color="variable"))
21 | + geom_density()
22 | + theme_light()
23 | )
24 | return out
25 |
26 |
27 | def daily_error(plot_df: pd.DataFrame, alpha: float) -> ggplot:
28 | out = (
29 | ggplot(plot_df, aes(x="date", y="error"))
30 | + geom_point(alpha=alpha)
31 | + theme_light()
32 | + theme(axis_text_x=element_text(rotation=45, hjust=1))
33 | )
34 | return out
35 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.6-dynamic-ui/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.7-conditional-panel/README:
--------------------------------------------------------------------------------
1 | Use `ui.conditional_panel` to show or hide a column selector based on whether the "Data" tab is visible. Note that you'll need to use a JavaScript logical test for conditional panel which should be `input.tabs === 'Data'`.
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.7-conditional-panel/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 | data_tab = ui.nav("Data", ui.output_data_frame("data"))
12 | error_tab = ui.nav(
13 | "Error",
14 | ui.row(
15 | ui.column(
16 | 4, x.ui.value_box("Hotter than forecast", ui.output_text("hot_days"))
17 | ),
18 | ui.column(
19 | 4, x.ui.value_box("Colder than forecast", ui.output_text("cold_days"))
20 | ),
21 | ui.column(4, x.ui.value_box("Mean Error", ui.output_text("mean_error"))),
22 | ),
23 | ui.row(
24 | ui.column(
25 | 6,
26 | x.ui.card(
27 | x.ui.card_header("Distribution"),
28 | ui.output_plot("error_distribution"),
29 | ),
30 | ),
31 | ui.column(
32 | 6,
33 | x.ui.card(
34 | x.ui.card_header("Error by day"),
35 | ui.output_plot("error_by_day"),
36 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
37 | ),
38 | ),
39 | ),
40 | )
41 |
42 | app_ui = ui.page_fluid(
43 | ui.panel_title("Weather error"),
44 | ui.layout_sidebar(
45 | ui.panel_sidebar(
46 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
47 | ui.input_selectize(
48 | "states",
49 | "Select States",
50 | weather["state"].unique().tolist(),
51 | selected="CO",
52 | multiple=True,
53 | ),
54 | ui.output_ui("cities_ui"),
55 | ui.panel_conditional(
56 | "input.tabs === 'Data'",
57 | ui.input_selectize(
58 | "columns",
59 | "Display Columns",
60 | choices=weather.columns.tolist(),
61 | selected=weather.columns.tolist(),
62 | multiple=True,
63 | ),
64 | ),
65 | width=3,
66 | ),
67 | ui.panel_main(ui.navset_tab(error_tab, data_tab, id="tabs")),
68 | ),
69 | )
70 |
71 |
72 | def server(input, output, session):
73 | @output
74 | @render.ui
75 | def cities_ui():
76 | df = weather.copy()
77 | df = df[df["state"].isin(input.states())]
78 | city_options = df["city"].unique().tolist()
79 | return ui.input_selectize(
80 | "cities",
81 | "Select Cities",
82 | choices=city_options,
83 | selected=city_options[0],
84 | multiple=True,
85 | )
86 |
87 | @reactive.Calc
88 | def filtered_data() -> pd.DataFrame:
89 | df = weather.copy()
90 | df = df[df["city"].isin(input.cities())]
91 | df["date"] = pd.to_datetime(df["date"])
92 | dates = pd.to_datetime(input.dates())
93 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
94 | return df
95 |
96 | @output
97 | @render.plot
98 | def error_distribution():
99 | return temp_distirbution(filtered_data())
100 |
101 | @output
102 | @render.plot
103 | def error_by_day():
104 | return daily_error(filtered_data(), input.alpha())
105 |
106 | @output
107 | @render.data_frame
108 | def data():
109 | return filtered_data().loc[:, input.columns()]
110 |
111 | @output
112 | @render.text
113 | def mean_error():
114 | mean_error = filtered_data()["error"].mean()
115 | return round(mean_error, 2)
116 |
117 | @output
118 | @render.text
119 | def hot_days():
120 | hot_days = filtered_data()["error"] > 0
121 | return sum(hot_days)
122 |
123 | @output
124 | @render.text
125 | def cold_days():
126 | hot_days = filtered_data()["error"] < 0
127 | return sum(hot_days)
128 |
129 |
130 | app = App(app_ui, server)
131 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.7-conditional-panel/app.py:
--------------------------------------------------------------------------------
1 | from shiny import ui, render, reactive, App
2 | import pandas as pd
3 | from pathlib import Path
4 | from plots import temp_distirbution, daily_error
5 | import shiny.experimental as x
6 |
7 | infile = Path(__file__).parent / "weather.csv"
8 | weather = pd.read_csv(infile)
9 | weather["error"] = weather["observed_temp"] - weather["forecast_temp"]
10 |
11 | data_tab = ui.nav("Data", ui.output_data_frame("data"))
12 | error_tab = ui.nav(
13 | "Error",
14 | ui.row(
15 | ui.column(
16 | 4, x.ui.value_box("Hotter than forecast", ui.output_text("hot_days"))
17 | ),
18 | ui.column(
19 | 4, x.ui.value_box("Colder than forecast", ui.output_text("cold_days"))
20 | ),
21 | ui.column(4, x.ui.value_box("Mean Error", ui.output_text("mean_error"))),
22 | ),
23 | ui.row(
24 | ui.column(
25 | 6,
26 | x.ui.card(
27 | x.ui.card_header("Distribution"),
28 | ui.output_plot("error_distribution"),
29 | ),
30 | ),
31 | ui.column(
32 | 6,
33 | x.ui.card(
34 | x.ui.card_header("Error by day"),
35 | ui.output_plot("error_by_day"),
36 | ui.input_slider("alpha", "Plot Alpha", value=0.5, min=0, max=1),
37 | ),
38 | ),
39 | ),
40 | )
41 |
42 | app_ui = ui.page_fluid(
43 | ui.panel_title("Weather error"),
44 | ui.layout_sidebar(
45 | ui.panel_sidebar(
46 | ui.input_date_range("dates", "Date", start="2022-01-01", end="2022-01-30"),
47 | ui.input_selectize(
48 | "states",
49 | "Select States",
50 | weather["state"].unique().tolist(),
51 | selected="CO",
52 | multiple=True,
53 | ),
54 | ui.output_ui("cities_ui"),
55 | width=3,
56 | ),
57 | ui.panel_main(
58 | ui.navset_tab(
59 | error_tab,
60 | data_tab,
61 | )
62 | ),
63 | ),
64 | )
65 |
66 |
67 | def server(input, output, session):
68 | @output
69 | @render.ui
70 | def cities_ui():
71 | df = weather.copy()
72 | df = df[df["state"].isin(input.states())]
73 | city_options = df["city"].unique().tolist()
74 | return ui.input_selectize(
75 | "cities",
76 | "Select Cities",
77 | choices=city_options,
78 | selected=city_options[0],
79 | multiple=True,
80 | )
81 |
82 | @reactive.Calc
83 | def filtered_data() -> pd.DataFrame:
84 | df = weather.copy()
85 | df = df[df["city"].isin(input.cities())]
86 | df["date"] = pd.to_datetime(df["date"])
87 | dates = pd.to_datetime(input.dates())
88 | df = df[(df["date"] > dates[0]) & (df["date"] <= dates[1])]
89 | return df
90 |
91 | @output
92 | @render.plot
93 | def error_distribution():
94 | return temp_distirbution(filtered_data())
95 |
96 | @output
97 | @render.plot
98 | def error_by_day():
99 | return daily_error(filtered_data(), input.alpha())
100 |
101 | @output
102 | @render.data_frame
103 | def data():
104 | return filtered_data()
105 |
106 | @output
107 | @render.text
108 | def mean_error():
109 | mean_error = filtered_data()["error"].mean()
110 | return round(mean_error, 2)
111 |
112 | @output
113 | @render.text
114 | def hot_days():
115 | hot_days = filtered_data()["error"] > 0
116 | return sum(hot_days)
117 |
118 | @output
119 | @render.text
120 | def cold_days():
121 | hot_days = filtered_data()["error"] < 0
122 | return sum(hot_days)
123 |
124 |
125 | app = App(app_ui, server)
126 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.7-conditional-panel/plots.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from plotnine import (
3 | ggplot,
4 | aes,
5 | geom_density,
6 | theme_light,
7 | labs,
8 | geom_point,
9 | theme,
10 | element_text,
11 | )
12 |
13 |
14 | def temp_distirbution(plot_df: pd.DataFrame) -> ggplot:
15 | plot_df = plot_df[["observed_temp", "forecast_temp", "date"]]
16 | plot_df = pd.melt(
17 | plot_df, id_vars="date", value_vars=["observed_temp", "forecast_temp"]
18 | )
19 | out = (
20 | ggplot(plot_df, aes(x="value", group="variable", color="variable"))
21 | + geom_density()
22 | + theme_light()
23 | )
24 | return out
25 |
26 |
27 | def daily_error(plot_df: pd.DataFrame, alpha: float) -> ggplot:
28 | out = (
29 | ggplot(plot_df, aes(x="date", y="error"))
30 | + geom_point(alpha=alpha)
31 | + theme_light()
32 | + theme(axis_text_x=element_text(rotation=45, hjust=1))
33 | )
34 | return out
35 |
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/4.7-conditional-panel/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/4-ui-customization/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.1-reactive-event/README:
--------------------------------------------------------------------------------
1 | Add a `ui.input_action_button` and a `@reactive.event` decorator to make the app only update when the button is clicked.
2 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.1-reactive-event/app-solution.py:
--------------------------------------------------------------------------------
1 | from shutil import ignore_patterns
2 | from shiny.express import render, ui, input
3 | from shiny import reactive
4 | from data_import import df
5 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
6 | from shinywidgets import render_plotly
7 |
8 |
9 | @reactive.calc()
10 | def account_data():
11 | return df[
12 | (df["account"] == input.account()) & (df["sub_account"] == input.sub_account())
13 | ]
14 |
15 |
16 | # You can control execution by adding `@reactive.event` to any reactive function.
17 | # This tells Shiny to only trigger this renderer when a particular input changes.
18 | # Note that when inputs are used in reactive.events they are not called (input.update not input.update())
19 | # This is because we are watching the reactive itself for changes, not making use of the current value.
20 | @reactive.calc()
21 | @reactive.event(input.update, ignore_init=True)
22 | def character_filter():
23 | return account_data()[(account_data()["text"].str.len().between(*input.chars()))]
24 |
25 |
26 | with ui.sidebar():
27 | ui.input_select(
28 | "account",
29 | "Account",
30 | choices=[
31 | "Berge & Berge",
32 | "Fritsch & Fritsch",
33 | "Hintz & Hintz",
34 | "Mosciski and Sons",
35 | "Wolff Ltd",
36 | ],
37 | )
38 |
39 | @render.express
40 | def sub_selector():
41 | # We can't use the account_data reactive here because we are using the
42 | # sub_account input as part of that reactive.
43 | choice_data = df[df["account"] == input.account()]
44 | choices = choice_data["sub_account"].unique().tolist()
45 | ui.input_select("sub_account", "Sub Account", choices=choices)
46 |
47 | ui.input_slider(
48 | "chars",
49 | "Text length",
50 | min=0,
51 | max=df["text"].str.len().max(),
52 | value=[500, 6000],
53 | )
54 |
55 | ui.input_action_button("update", "Update", class_="btn-primary")
56 |
57 | with ui.layout_columns():
58 |
59 | with ui.card():
60 | ui.card_header("Model Metrics")
61 |
62 | @render_plotly
63 | def metric_plot():
64 | if input.metric() == "ROC Curve":
65 | return plot_auc_curve(
66 | character_filter(), "is_electronics", "training_score"
67 | )
68 | else:
69 | return plot_precision_recall_curve(
70 | character_filter(), "is_electronics", "training_score"
71 | )
72 |
73 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
74 |
75 | with ui.card():
76 | ui.card_header("Model Scores")
77 |
78 | @render_plotly
79 | def score_dist():
80 | return plot_score_distribution(character_filter())
81 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.1-reactive-event/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from shiny import reactive
3 | from data_import import df
4 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
5 | from shinywidgets import render_plotly
6 |
7 |
8 | @reactive.calc()
9 | def account_data():
10 | return df[
11 | (df["account"] == input.account()) & (df["sub_account"] == input.sub_account())
12 | ]
13 |
14 |
15 | @reactive.calc()
16 | def character_filter():
17 | return account_data()[(account_data()["text"].str.len().between(*input.chars()))]
18 |
19 |
20 | with ui.sidebar():
21 | ui.input_select(
22 | "account",
23 | "Account",
24 | choices=[
25 | "Berge & Berge",
26 | "Fritsch & Fritsch",
27 | "Hintz & Hintz",
28 | "Mosciski and Sons",
29 | "Wolff Ltd",
30 | ],
31 | )
32 |
33 | @render.express
34 | def sub_selector():
35 | # We can't use the account_data reactive here because we are using the
36 | # sub_account input as part of that reactive.
37 | choice_data = df[df["account"] == input.account()]
38 | choices = choice_data["sub_account"].unique().tolist()
39 | ui.input_select("sub_account", "Sub Account", choices=choices)
40 |
41 | ui.input_slider(
42 | "chars",
43 | "Text length",
44 | min=0,
45 | max=df["text"].str.len().max(),
46 | value=[500, 6000],
47 | )
48 |
49 | with ui.layout_columns():
50 |
51 | with ui.card():
52 | ui.card_header("Model Metrics")
53 |
54 | def metric_plot():
55 | if input.metric() == "ROC Curve":
56 | return plot_auc_curve(
57 | character_filter(), "is_electronics", "training_score"
58 | )
59 | else:
60 | return plot_precision_recall_curve(
61 | character_filter(), "is_electronics", "training_score"
62 | )
63 |
64 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
65 |
66 | with ui.card():
67 | ui.card_header("Model Scores")
68 |
69 | @render_plotly
70 | def score_dist():
71 | return plot_score_distribution(character_filter())
72 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.1-reactive-event/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.1-reactive-event/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.1-reactive-event/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.2-reactive-effect/README:
--------------------------------------------------------------------------------
1 | Add a `ui.input_action_button` and a `@reactive.event` decorator to make a button that, when clicked, resets the slider to its initial values.
2 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.2-reactive-effect/app-solution.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from shiny import reactive
3 | from data_import import df
4 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
5 | from shinywidgets import render_plotly
6 |
7 |
8 | @reactive.calc()
9 | def account_data():
10 | return df[
11 | (df["account"] == input.account()) & (df["sub_account"] == input.sub_account())
12 | ]
13 |
14 |
15 | @reactive.calc()
16 | def character_filter():
17 | return account_data()[(account_data()["text"].str.len().between(*input.chars()))]
18 |
19 |
20 | # Reactive effects are used for side effects, not values.
21 | # They are similar to callback functions in other frameworks.
22 | @reactive.effect
23 | @reactive.event(input.reset)
24 | def reset_slider():
25 | ui.update_slider("chars", value=[500, 6000])
26 |
27 |
28 | with ui.sidebar():
29 | ui.input_select(
30 | "account",
31 | "Account",
32 | choices=[
33 | "Berge & Berge",
34 | "Fritsch & Fritsch",
35 | "Hintz & Hintz",
36 | "Mosciski and Sons",
37 | "Wolff Ltd",
38 | ],
39 | )
40 |
41 | @render.express
42 | def sub_selector():
43 | # We can't use the account_data reactive here because we are using the
44 | # sub_account input as part of that reactive.
45 | choice_data = df[df["account"] == input.account()]
46 | choices = choice_data["sub_account"].unique().tolist()
47 | ui.input_select("sub_account", "Sub Account", choices=choices)
48 |
49 | ui.input_slider(
50 | "chars",
51 | "Text length",
52 | min=0,
53 | max=df["text"].str.len().max(),
54 | value=[500, 6000],
55 | )
56 |
57 | ui.input_action_button("reset", "Reset Slider", class_="btn-primary")
58 |
59 | with ui.layout_columns():
60 |
61 | with ui.card():
62 | ui.card_header("Model Metrics")
63 |
64 | @render_plotly
65 | def metric_plot():
66 | if input.metric() == "ROC Curve":
67 | return plot_auc_curve(
68 | character_filter(), "is_electronics", "training_score"
69 | )
70 | else:
71 | return plot_precision_recall_curve(
72 | character_filter(), "is_electronics", "training_score"
73 | )
74 |
75 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
76 |
77 | with ui.card():
78 | ui.card_header("Model Scores")
79 |
80 | @render_plotly
81 | def score_dist():
82 | return plot_score_distribution(character_filter())
83 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.2-reactive-effect/app.py:
--------------------------------------------------------------------------------
1 | from shiny.express import render, ui, input
2 | from shiny import reactive
3 | from data_import import df
4 | from plots import plot_auc_curve, plot_precision_recall_curve, plot_score_distribution
5 | from shinywidgets import render_plotly
6 |
7 |
8 | @reactive.calc()
9 | def account_data():
10 | return df[
11 | (df["account"] == input.account()) & (df["sub_account"] == input.sub_account())
12 | ]
13 |
14 |
15 | @reactive.calc()
16 | def character_filter():
17 | return account_data()[(account_data()["text"].str.len().between(*input.chars()))]
18 |
19 |
20 | with ui.sidebar():
21 | ui.input_select(
22 | "account",
23 | "Account",
24 | choices=[
25 | "Berge & Berge",
26 | "Fritsch & Fritsch",
27 | "Hintz & Hintz",
28 | "Mosciski and Sons",
29 | "Wolff Ltd",
30 | ],
31 | )
32 |
33 | @render.express
34 | def sub_selector():
35 | # We can't use the account_data reactive here because we are using the
36 | # sub_account input as part of that reactive.
37 | choice_data = df[df["account"] == input.account()]
38 | choices = choice_data["sub_account"].unique().tolist()
39 | ui.input_select("sub_account", "Sub Account", choices=choices)
40 |
41 | ui.input_slider(
42 | "chars",
43 | "Text length",
44 | min=0,
45 | max=df["text"].str.len().max(),
46 | value=[500, 6000],
47 | )
48 |
49 | with ui.layout_columns():
50 |
51 | with ui.card():
52 | ui.card_header("Model Metrics")
53 |
54 | @render_plotly
55 | def metric_plot():
56 | if input.metric() == "ROC Curve":
57 | return plot_auc_curve(
58 | character_filter(), "is_electronics", "training_score"
59 | )
60 | else:
61 | return plot_precision_recall_curve(
62 | character_filter(), "is_electronics", "training_score"
63 | )
64 |
65 | ui.input_select("metric", "Metric", choices=["ROC Curve", "Precision Recall"])
66 |
67 | with ui.card():
68 | ui.card_header("Model Scores")
69 |
70 | @render_plotly
71 | def score_dist():
72 | return plot_score_distribution(character_filter())
73 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.2-reactive-effect/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 | import numpy as np
4 |
5 | file_path = Path(__file__).parent / "simulated-data.csv"
6 |
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.2-reactive-effect/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 |
8 | import plotly.io as pio
9 |
10 | # Set the default plotly theme to resemble ggplot's theme_light
11 | pio.templates.default = "plotly_white"
12 |
13 |
14 | def plot_score_distribution(df: DataFrame):
15 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
16 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
17 | return fig
18 |
19 |
20 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
21 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
22 | roc_auc = auc(fpr, tpr)
23 |
24 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
25 |
26 | fig = px.line(
27 | roc_df,
28 | x="False Positive Rate",
29 | y="True Positive Rate",
30 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
31 | labels={
32 | "False Positive Rate": "False Positive Rate",
33 | "True Positive Rate": "True Positive Rate",
34 | },
35 | )
36 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
37 | return fig
38 |
39 |
40 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
41 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
42 |
43 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
44 |
45 | fig = px.line(
46 | pr_df,
47 | x="Recall",
48 | y="Precision",
49 | title="Precision-Recall Curve",
50 | labels={"Recall": "Recall", "Precision": "Precision"},
51 | )
52 | return fig
53 |
54 |
55 | def plot_api_response(df):
56 | account = df["account"].unique()
57 |
58 | data = np.random.lognormal(0, 1 / len(account), 10000)
59 | df = pd.DataFrame({"Value": data})
60 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
61 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
62 | return fig
63 |
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/5.2-reactive-effect/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/5-reactive-effects/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/problem-sets/requirements.txt:
--------------------------------------------------------------------------------
1 | pandas
2 | plotly
--------------------------------------------------------------------------------
/apps/target-app/app.py:
--------------------------------------------------------------------------------
1 | from numpy import char, place
2 | from shiny.express import ui, input, render
3 | from shiny import reactive
4 |
5 | from plots import (
6 | plot_score_distribution,
7 | plot_auc_curve,
8 | plot_precision_recall_curve,
9 | )
10 | import faicons as fa
11 | import io
12 | from shinywidgets import render_plotly
13 | from data_import import df
14 |
15 |
16 | @reactive.calc()
17 | def account_data():
18 | return df[
19 | (df["account"] == input.account()) & (df["sub_account"] == input.sub_account())
20 | ]
21 |
22 |
23 | @reactive.calc()
24 | def character_filter():
25 | return account_data()[(account_data()["text"].str.len().between(*input.chars()))]
26 |
27 |
28 | @reactive.effect
29 | @reactive.event(input.reset)
30 | def reset_vals():
31 | ui.update_slider("chars", value=[500, 6000])
32 |
33 |
34 | with ui.sidebar():
35 | ui.input_select(
36 | "account",
37 | "Account",
38 | choices=[
39 | "Berge & Berge",
40 | "Fritsch & Fritsch",
41 | "Hintz & Hintz",
42 | "Mosciski and Sons",
43 | "Wolff Ltd",
44 | ],
45 | )
46 |
47 | @render.express
48 | def sub_selector():
49 | choice_data = df[df["account"] == input.account()]
50 | choices = choice_data["sub_account"].unique().tolist()
51 | ui.input_select("sub_account", "Sub Account", choices=choices)
52 |
53 | with ui.tooltip(id="btn_tooltip", placement="right"):
54 | ui.input_slider(
55 | "chars",
56 | "Text length",
57 | min=0,
58 | max=df["text"].str.len().max(),
59 | value=[500, 6000],
60 | )
61 | "The number of characters in the text"
62 |
63 | ui.input_action_button("reset", "Reset Values", class_="btn-primary")
64 |
65 | with ui.nav_panel("Training Dashboard"):
66 | with ui.layout_columns():
67 | with ui.card():
68 | ui.card_header("Model Metrics")
69 |
70 | @render_plotly
71 | def metric_plot():
72 | if input.metric() == "ROC Curve":
73 | return plot_auc_curve(
74 | character_filter(), "is_electronics", "training_score"
75 | )
76 | else:
77 | return plot_precision_recall_curve(
78 | character_filter(), "is_electronics", "training_score"
79 | )
80 |
81 | ui.input_select(
82 | "metric", "Metric", choices=["ROC Curve", "Precision Recall"]
83 | )
84 |
85 | with ui.card():
86 | ui.card_header("Model Scores")
87 |
88 | @render_plotly
89 | def score_dist():
90 | return plot_score_distribution(character_filter())
91 |
92 |
93 | vb_theme = "bg-blue"
94 |
95 | with ui.nav_panel("Data"):
96 | with ui.layout_columns():
97 | with ui.value_box(theme=vb_theme):
98 | "Number of records"
99 |
100 | @render.text
101 | def data_count():
102 | return str(character_filter().shape[0])
103 |
104 | with ui.value_box(theme=vb_theme):
105 | "Mean Score"
106 |
107 | @render.text
108 | def mean_score(theme=vb_theme):
109 | return f"{character_filter()['training_score'].mean() * 100:.2f}%"
110 |
111 | with ui.value_box(theme=vb_theme):
112 | "Mean Text Length"
113 |
114 | @render.text
115 | def mean_text_length():
116 | return f"{character_filter()['text'].str.len().mean():.2f} characters"
117 |
118 | with ui.card(full_screen=True):
119 | with ui.card_header():
120 | "Data"
121 | with ui.popover(
122 | title="Download",
123 | class_="d-inline-block pull-right",
124 | ):
125 | fa.icon_svg("download")
126 |
127 | @render.download(filename=lambda: f"{input.account()}_scores.csv")
128 | def download_data():
129 | with io.BytesIO() as buf:
130 | account_data().to_csv(buf, index=False)
131 | buf.seek(0)
132 | yield buf.getvalue()
133 |
134 | @render.data_frame
135 | def data_output():
136 | return account_data().drop(columns=["text"])
137 |
--------------------------------------------------------------------------------
/apps/target-app/data_import.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pandas as pd
3 |
4 | file_path = Path(__file__).parent / "simulated-data.csv"
5 |
6 | print("Running!")
7 | df = pd.read_csv(file_path, dtype={"sub_account": str})
8 | df["date"] = pd.to_datetime(df["date"], errors="coerce")
9 |
--------------------------------------------------------------------------------
/apps/target-app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "locale": "en_US.UTF-8",
4 | "metadata": {
5 | "appmode": "python-shiny",
6 | "entrypoint": "app"
7 | },
8 | "python": {
9 | "version": "3.11.4",
10 | "package_manager": {
11 | "name": "pip",
12 | "version": "24.0",
13 | "package_file": "requirements.txt"
14 | }
15 | },
16 | "files": {
17 | "requirements.txt": {
18 | "checksum": "d40adc5fb1d3a2fbf665f73725689501"
19 | },
20 | "app.py": {
21 | "checksum": "876a7c1b6a172fc84add63b9b0e295bd"
22 | },
23 | "plots.py": {
24 | "checksum": "b517859f4d6a8a1f59b00bdd6c12bccc"
25 | },
26 | "simulated-data.csv": {
27 | "checksum": "90a2519c364580b74d0c40e568c182ee"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/apps/target-app/plots.py:
--------------------------------------------------------------------------------
1 | import plotly.express as px
2 | from pandas import DataFrame
3 | import pandas as pd
4 | from sklearn.metrics import roc_curve, auc, precision_recall_curve
5 | import numpy as np
6 |
7 | import plotly.io as pio
8 |
9 | # Set the default plotly theme to resemble ggplot's theme_light
10 | pio.templates.default = "plotly_white"
11 |
12 |
13 | def plot_score_distribution(df: DataFrame):
14 | fig = px.histogram(df, x="training_score", nbins=50, title="Model scores")
15 | fig.update_layout(xaxis_title="Score", yaxis_title="Density")
16 | return fig
17 |
18 |
19 | def plot_auc_curve(df: DataFrame, true_col: str, pred_col: str):
20 | fpr, tpr, _ = roc_curve(df[true_col], df[pred_col])
21 | roc_auc = auc(fpr, tpr)
22 |
23 | roc_df = DataFrame({"False Positive Rate": fpr, "True Positive Rate": tpr})
24 |
25 | fig = px.line(
26 | roc_df,
27 | x="False Positive Rate",
28 | y="True Positive Rate",
29 | title=f"Receiver Operating Characteristic (ROC) - AUC: {roc_auc.round(2)}",
30 | labels={
31 | "False Positive Rate": "False Positive Rate",
32 | "True Positive Rate": "True Positive Rate",
33 | },
34 | )
35 | fig.add_shape(type="line", line=dict(dash="dash"), x0=0, x1=1, y0=0, y1=1)
36 | return fig
37 |
38 |
39 | def plot_precision_recall_curve(df: DataFrame, true_col: str, pred_col: str):
40 | precision, recall, _ = precision_recall_curve(df[true_col], df[pred_col])
41 |
42 | pr_df = DataFrame({"Recall": recall, "Precision": precision})
43 |
44 | fig = px.line(
45 | pr_df,
46 | x="Recall",
47 | y="Precision",
48 | title="Precision-Recall Curve",
49 | labels={"Recall": "Recall", "Precision": "Precision"},
50 | )
51 | return fig
52 |
53 |
54 | def plot_api_response(df):
55 | account = df["account"].unique()
56 |
57 | data = np.random.lognormal(0, 1 / len(account), 10000)
58 | df = pd.DataFrame({"Value": data})
59 | fig = px.histogram(df, x="Value", nbins=50, title="API response time")
60 | fig.update_layout(xaxis_title="Seconds", yaxis_title="Density")
61 | return fig
62 |
--------------------------------------------------------------------------------
/apps/target-app/requirements.txt:
--------------------------------------------------------------------------------
1 | shiny==0.8.1
2 | pandas
3 | plotly
4 | shinywidgets==0.3.1
5 | scikit-learn
6 | faicons
--------------------------------------------------------------------------------
/apps/target-app/rsconnect-python/target-app.json:
--------------------------------------------------------------------------------
1 | {
2 | "https://api.shinyapps.io": {
3 | "server_url": "https://api.shinyapps.io",
4 | "filename": "/Users/gordon/Documents/presentations/shiny-python-workshop-2023/apps/target-app",
5 | "app_url": "https://gordonposit.shinyapps.io/target-app/",
6 | "app_id": 11543078,
7 | "app_guid": null,
8 | "title": "target-app",
9 | "app_mode": "python-shiny",
10 | "app_store_version": 1
11 | }
12 | }
--------------------------------------------------------------------------------
/apps/utilities/multiple-choice/app.py:
--------------------------------------------------------------------------------
1 | from shiny import App, render, ui, reactive, req
2 | import json
3 | from pathlib import Path
4 |
5 | with open(Path(__file__).parent / "questions.json", "r") as file:
6 | questions = json.load(file)
7 |
8 | app_ui = ui.page_fluid(ui.output_ui("question"), ui.output_ui("validation"))
9 |
10 |
11 | def server(input, output, session):
12 | q_num = reactive.Value(0)
13 |
14 | question_keys = list(questions.keys())
15 |
16 | @reactive.Calc
17 | def current_q():
18 | return questions[question_keys[q_num()]]
19 |
20 | @output
21 | @render.ui
22 | def question():
23 | return ui.div(
24 | ui.h5(question_keys[q_num()], style="margin-bottom: 20px"),
25 | ui.input_radio_buttons(
26 | "question",
27 | "",
28 | choices=current_q()["choices"],
29 | selected="",
30 | ),
31 | )
32 |
33 | @output
34 | @render.ui
35 | def validation():
36 | req(input.question())
37 | if input.question() == current_q()["answer"]:
38 | if q_num() == len(question_keys) - 1:
39 | return ui.p("Correct! Quiz complete.")
40 | else:
41 | return ui.div(
42 | ui.p("Correct!"), ui.input_action_button("next", "Next Question")
43 | )
44 | return ui.p("Not quite right, try again")
45 |
46 | @reactive.Effect
47 | @reactive.event(input.next)
48 | def next_question():
49 | q_num.set(q_num.get() + 1)
50 |
51 |
52 | app = App(app_ui, server)
53 |
--------------------------------------------------------------------------------
/apps/utilities/multiple-choice/questions.json:
--------------------------------------------------------------------------------
1 | {
2 | "What is your favourite color?": {
3 | "choices": [
4 | "blue",
5 | "green",
6 | "gold"
7 | ],
8 | "answer": "green"
9 | },
10 | "What is your favourite food?": {
11 | "choices": [
12 | "beans",
13 | "apples",
14 | "alfalfa"
15 | ],
16 | "answer": "beans"
17 | }
18 | }
--------------------------------------------------------------------------------
/exercises/1-hello-world.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hello Shiny"
3 | ---
4 |
5 | These exercises should be done after Chapter 2 of the course.
6 |
7 | ```{python}
8 | # | echo: false
9 | import os
10 |
11 | os.chdir("..")
12 | from helpers import problem_tabs
13 |
14 | ```
15 |
16 |
17 | ```{python}
18 | # | echo: false
19 | # | output: asis
20 |
21 | problem_tabs("1.0-hello-world")
22 | ```
23 |
24 |
25 | ```{python}
26 | # | echo: false
27 | # | output: asis
28 |
29 | problem_tabs("1.1-data-frame")
30 | ```
31 |
32 | ```{python}
33 | # | echo: false
34 | # | output: asis
35 |
36 | problem_tabs("1.2-debug")
37 | ```
38 |
39 | ```{python}
40 | # | echo: false
41 | # | output: asis
42 |
43 | problem_tabs("1.3-filter-input")
44 | ```
45 |
46 | ```{python}
47 | # | echo: false
48 | # | output: asis
49 |
50 | problem_tabs("1.4-filter-connect")
51 | ```
52 |
53 | ```{python}
54 | # | echo: false
55 | # | output: asis
56 |
57 | problem_tabs("1.5-debug")
58 | ```
59 |
60 | ```{python}
61 | # | echo: false
62 | # | output: asis
63 |
64 | problem_tabs("1.6-debug")
65 | ```
66 |
67 | ```{python}
68 | # | echo: false
69 | # | output: asis
70 |
71 | problem_tabs("1.7-add-plot")
72 | ```
--------------------------------------------------------------------------------
/exercises/2-basic-ui.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Basic UI"
3 | ---
4 | These exercises should be done after Chapter 3 of the course.
5 | ```{python}
6 | # | echo: false
7 | import os
8 |
9 | os.chdir("..")
10 | from helpers import problem_tabs
11 |
12 | ```
13 |
14 | ```{python}
15 | # | echo: false
16 | # | output: asis
17 |
18 | problem_tabs("2.1-sidebar")
19 | ```
20 |
21 | ```{python}
22 | # | echo: false
23 | # | output: asis
24 |
25 | problem_tabs("2.2-cards")
26 | ```
27 |
28 | ```{python}
29 | # | echo: false
30 | # | output: asis
31 |
32 | problem_tabs("2.3-cards-switch")
33 | ```
34 |
35 | ```{python}
36 | # | echo: false
37 | # | output: asis
38 |
39 | problem_tabs("2.4-layout-columns")
40 | ```
--------------------------------------------------------------------------------
/exercises/3-reactivity.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Reactivity"
3 | ---
4 | These exercises should be done after Chapter 4 of the course.
5 | ```{python}
6 | # | echo: false
7 | import os
8 |
9 | os.chdir("..")
10 | from helpers import problem_tabs
11 |
12 | ```
13 |
14 | ```{python}
15 | # | echo: false
16 | # | output: asis
17 |
18 | problem_tabs("3.1-reactive-calc")
19 | ```
20 |
21 | ```{python}
22 | # | echo: false
23 | # | output: asis
24 |
25 | problem_tabs("3.2-stacking-reactives")
26 | ```
--------------------------------------------------------------------------------
/exercises/4-dynamic-ui.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Dynamic UI"
3 | ---
4 | These exercises should be done after Chapter 5 of the course.
5 | ```{python}
6 | # | echo: false
7 | import os
8 |
9 | os.chdir("..")
10 | from helpers import problem_tabs
11 |
12 | ```
13 |
14 | ```{python}
15 | # | echo: false
16 | # | output: asis
17 |
18 | problem_tabs("4.1-render-express")
19 | ```
--------------------------------------------------------------------------------
/exercises/5-reactive-effect.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Reactive Effects"
3 | ---
4 | These exercises should be done after Chapter 6 of the course.
5 | ```{python}
6 | # | echo: false
7 | import os
8 |
9 | os.chdir("..")
10 | from helpers import problem_tabs
11 |
12 | ```
13 |
14 | ```{python}
15 | # | echo: false
16 | # | output: asis
17 |
18 | problem_tabs("5.1-reactive-event")
19 | ```
20 |
21 |
22 | ```{python}
23 | # | echo: false
24 | # | output: asis
25 |
26 | problem_tabs("5.2-reactive-effect")
27 | ```
--------------------------------------------------------------------------------
/helpers.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import glob
3 | import tempfile
4 | import shutil
5 | import os
6 | import json
7 |
8 |
9 | class QuartoPrint(list):
10 | def __init__(self, data):
11 | super().__init__(data)
12 |
13 | def __str__(self):
14 | return "\n".join(str(item) for item in self)
15 |
16 | def append_file(self, file_path: str, file_name: str = None):
17 | if file_name is not None:
18 | self.append(f"## file: {file_name}")
19 |
20 | with open(file_path, "r") as app_file:
21 | app_contents = app_file.read()
22 | self.append(app_contents)
23 |
24 |
25 | def list_files(path: str) -> list:
26 | files = glob.glob(path + "/**", recursive=True)
27 | files = [file for file in files if not glob.os.path.isdir(file)]
28 | return files
29 |
30 |
31 | def include_shiny_folder(
32 | path: str,
33 | file_name: str = "app.py",
34 | exclusions: list = [],
35 | components: str = "editor, viewer",
36 | viewer_height: str = "800",
37 | extra_object: any = "",
38 | ) -> None:
39 | print(
40 | _include_shiny_folder(
41 | path, file_name, exclusions, components, viewer_height, extra_object
42 | )
43 | )
44 |
45 |
46 | def _include_shiny_folder(
47 | path: str,
48 | file_name: str = "app.py",
49 | exclusions: list = [],
50 | components: str = "editor, viewer",
51 | viewer_height: str = "800",
52 | extra_object: any = "",
53 | ) -> QuartoPrint:
54 | folder_path = Path(__name__).parent / path
55 |
56 | # Start with the header
57 | block = QuartoPrint(
58 | [
59 | "```{shinylive-python}",
60 | "#| standalone: true",
61 | f"#| components: [{components}]",
62 | "#| layout: horizontal",
63 | f"#| viewerHeight: {viewer_height}",
64 | ]
65 | )
66 |
67 | # Print contents of the main application
68 | block.append_file(folder_path / file_name, None)
69 |
70 | exclude_list = ["__pycache__"] + [file_name] + exclusions
71 |
72 | files = list_files(path)
73 |
74 | path_list = [
75 | string
76 | for string in files
77 | if not any(exclusion in string for exclusion in exclude_list)
78 | ]
79 |
80 | file_names = [string.replace(f"{str(folder_path)}/", "") for string in path_list]
81 |
82 | # Additional files need to start with ## file:
83 | for x, y in zip(path_list, file_names):
84 | block.append_file(x, y)
85 |
86 | # Finish with the closing tag
87 | block.append("```")
88 | return block
89 |
90 |
91 | def collapse_prompt(prompt: str) -> list:
92 | out = [
93 | "",
94 | '::: {.callout-note collapse="false"}',
95 | "## Exercise",
96 | prompt,
97 | ":::",
98 | "",
99 | ]
100 | return out
101 |
102 |
103 | def parse_readme(path: str) -> str:
104 | file_path = Path(__name__).parent / path / "README"
105 | file_contents = ""
106 | with open(file_path, "r") as file:
107 | file_contents = file.read()
108 | return file_contents
109 |
110 |
111 | def problem_tabs(folder_name: str) -> None:
112 |
113 | import os
114 |
115 | def find_problem_set_folder(base_path, target_path):
116 | for root, dirs, files in os.walk(base_path):
117 | for name in dirs:
118 | full_path = os.path.join(root, name)
119 | if target_path in full_path:
120 | return full_path
121 | raise FileNotFoundError(
122 | f"Folder matching path '{target_path}' not found in '{base_path}'."
123 | )
124 |
125 | path = find_problem_set_folder("apps/problem-sets", folder_name)
126 |
127 | formatted_title = "## " + folder_name.replace("-", " ").title()
128 |
129 | block = QuartoPrint(
130 | [
131 | formatted_title,
132 | "::::: {.column-screen-inset-right}",
133 | "::: {.panel-tabset}",
134 | "## Goal",
135 | ]
136 | )
137 | prompt = parse_readme(path)
138 | block.extend(collapse_prompt(prompt))
139 | block.extend(
140 | _include_shiny_folder(
141 | path,
142 | "app-solution.py",
143 | exclusions=["app.py", "README"],
144 | components="viewer",
145 | )
146 | )
147 | block.append("## Problem")
148 | block.extend(collapse_prompt(prompt))
149 | block.extend(
150 | _include_shiny_folder(path, "app.py", exclusions=["app-solution.py", "README"])
151 | )
152 | block.append("## Solution")
153 | block.extend(collapse_prompt(prompt))
154 | block.extend(
155 | _include_shiny_folder(path, "app-solution.py", exclusions=["app.py", "README"])
156 | )
157 | block.append("## {{< bi github >}}")
158 | block.append(
159 | f"The source code for this exercise is [here](https://github.com/talkpython/reactive-web-dashboards-with-shiny-course/tree/main/{path})."
160 | )
161 |
162 | block.append(":::")
163 | block.append(":::::")
164 | print(block)
165 |
166 |
167 | class Quiz(dict):
168 | def __init__(self, data):
169 | super().__init__(data)
170 | self.validate()
171 |
172 | def validate(self):
173 | if not isinstance(self, dict):
174 | raise ValueError("Invalid data format: The data should be a dictionary.")
175 | for key, value in self.items():
176 | if not isinstance(value, dict):
177 | raise ValueError(
178 | f"Invalid data format for '{key}': The value should be a dictionary."
179 | )
180 | if "choices" not in value or "answer" not in value:
181 | raise ValueError(
182 | f"Invalid data format for '{key}': Missing 'choices' or 'answer' key."
183 | )
184 | if not isinstance(value["choices"], list) or not all(
185 | isinstance(choice, str) for choice in value["choices"]
186 | ):
187 | raise ValueError(
188 | f"Invalid data format for '{key}': 'choices' should be a list of strings."
189 | )
190 | if not isinstance(value["answer"], str):
191 | raise ValueError(
192 | f"Invalid data format for '{key}': 'answer' should be a string."
193 | )
194 | if value["answer"] not in value["choices"]:
195 | raise ValueError(
196 | f"Invalid data format for '{key}': '{value['answer']}' is not one of the choices."
197 | )
198 |
199 | return True
200 |
201 |
202 | def multiple_choice_app(questions: Quiz):
203 | questions = Quiz(questions)
204 | temp_dir = tempfile.mkdtemp("temp_folder")
205 | shutil.copy("apps/utilities/multiple-choice/app.py", temp_dir)
206 | with open(os.path.join(temp_dir, "questions.json"), "w") as file:
207 | json.dump(questions, file)
208 |
209 | print("::: callout-note")
210 | print("## Test your understanding")
211 | include_shiny_folder(temp_dir, components="viewer", viewer_height="250")
212 | print(":::")
213 |
--------------------------------------------------------------------------------
/images/git-download-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/images/git-download-button.png
--------------------------------------------------------------------------------
/images/shiny-course_image/shiny-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/images/shiny-course_image/shiny-2x.png
--------------------------------------------------------------------------------
/images/shiny-course_image/shiny-2x.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/images/shiny-course_image/shiny-2x.webp
--------------------------------------------------------------------------------
/images/shiny-course_image/shiny.pxd/QuickLook/Icon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/images/shiny-course_image/shiny.pxd/QuickLook/Icon.webp
--------------------------------------------------------------------------------
/images/shiny-course_image/shiny.pxd/QuickLook/Thumbnail.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/images/shiny-course_image/shiny.pxd/QuickLook/Thumbnail.webp
--------------------------------------------------------------------------------
/images/shiny-course_image/shiny.pxd/data/3F96BFC4-F9B9-4D8F-A3A9-DEE75FBCD65C:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/images/shiny-course_image/shiny.pxd/data/3F96BFC4-F9B9-4D8F-A3A9-DEE75FBCD65C
--------------------------------------------------------------------------------
/images/shiny-course_image/shiny.pxd/data/AF550587-953F-4997-9677-0C075842D366-76320-00013F899D49BA33:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/images/shiny-course_image/shiny.pxd/data/AF550587-953F-4997-9677-0C075842D366-76320-00013F899D49BA33
--------------------------------------------------------------------------------
/images/shiny-course_image/shiny.pxd/metadata.info:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talkpython/reactive-web-dashboards-with-shiny-course/2cbf3888e6e03a37a1a6743a0c7a203cf581b73f/images/shiny-course_image/shiny.pxd/metadata.info
--------------------------------------------------------------------------------
/import-helpers.quarto_ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "metadata": {},
6 | "source": [
7 | "# | echo: false\n",
8 | "import os\n",
9 | "\n",
10 | "os.chdir(\"..\")\n",
11 | "from helpers import problem_tabs, include_shiny_folder\n",
12 | "\n",
13 | "print(\"foo!\")"
14 | ],
15 | "id": "018ca038",
16 | "execution_count": null,
17 | "outputs": []
18 | }
19 | ],
20 | "metadata": {
21 | "kernelspec": {
22 | "name": "python3",
23 | "language": "python",
24 | "display_name": "Python 3 (ipykernel)",
25 | "path": "/Users/gordon/Documents/presentations/shiny-python-workshop-2023/.venv/share/jupyter/kernels/python3"
26 | }
27 | },
28 | "nbformat": 4,
29 | "nbformat_minor": 5
30 | }
--------------------------------------------------------------------------------
/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Reactive Web Dashboards with Shiny"
3 | ---
4 |
5 | # Web applications without the headaches
6 |
7 | Shiny lets you build beautiful, performant dashboards and web applications without a lot of effort.
8 | Instead of manually managing callback functions and application state, Shiny uses a reactive framework to automatically determine the best way to run your application.
9 | This means that you can focus on communicating insights rather than debugging your web application.
10 |
11 | This site is a companion to the [Talk Python video course](https://training.talkpython.fm/purchase/access_code/f3180ced-3d5c-42ad-a318-e81e6355a4fd), and provides some exercises that let you practice what you learned in the videos.
12 |
13 | # Who is this course for?
14 |
15 | This course assumes that you have a basic understanding of Python, but does not require any knowledge of web applications, JavaScript, or HTML.
16 | If you can read a CSV, manipulate data, and draw a plot in Python, then you have everything you need to start building a Shiny application.
17 |
18 | # Setup
19 |
20 | There are two ways to work through the examples and exercises in this workshop.
21 |
22 | ## 1) In your web browser with Shinylive
23 |
24 | Shinylive allows you to run full-featured shiny apps in your browser, and includes a basic editor which is good enough to run examples.
25 | As a result you should be able to work through all of the examples in the workshop using just the browser without installing anything locally.
26 |
27 | ## 2) Locally with VS Code
28 |
29 | While Shinylive is great, it likely isn't the environment you'll use to develop Shiny apps, and so it makes sense to set up VS Code and run the examples locally. To do this, follow these steps before the workshop:
30 |
31 | 1) Install [VS Code](https://code.visualstudio.com/)
32 | 2) Install the [Shiny extension for VS Code](https://marketplace.visualstudio.com/items?itemName=posit.shiny)
33 | 3) Clone the repository with `git clone https://github.com/rstudio/shiny-python-workshop-2023.git`, or alternately download the repository as a zip file: \
34 | {width="287"}
35 | 4) Navigate to the project directory and create a new virtual environment with `python3 -m venv .venv`
36 | 5) Set your Python interpreter to the virtual environment with `CMD + SHIFT + P` > `Select Interpreter`
37 | 6) Open a new terminal prompt, which should switch to `(.venv)`
38 | 7) Install the relevant packages with `pip install -r requirements.txt`
39 |
40 | All of the example apps are stored in the `/apps` directory.
41 | The examples are in `apps/examples` and the problem sets are in `apps/problem-sets`.
42 | If you've installed the Shiny extension for VS Code, you can run any of the apps by opening the `app.py` file and clicking the play button in the top right. ([See screenshot](https://camo.githubusercontent.com/5d947e6dff7d74fd1cf221e79583105c42e4986ae673ce79733ce5edbfdcdda5/68747470733a2f2f7368696e792e7273747564696f2e636f6d2f70792f646f63732f6173736574732f7673636f64652e706e67))
43 |
44 | Alternatively, run them from the command line with `shiny run --reload`.
45 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | shiny
2 | shinylive
3 | pandas
4 | plotnine
5 | seaborn
6 | jupyter
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | /* css styles */
2 |
3 | #vcenter {
4 | vertical-align: middle;
5 | }
6 |
7 | .callout .shinylive-wrapper {
8 | border: none;
9 | box-shadow: none;
10 | margin: 0;
11 | }
12 |
13 | .someclass {
14 | fill: rgb(240, 102, 255);
15 | }
16 |
17 | .panel-tabset .shinylive-container {
18 | max-height: calc(80vh - 110px);
19 | overflow: auto;
20 | margin-bottom: 20px;
21 | }
22 |
23 | .v-center-container {
24 | display: flex;
25 | justify-content: center;
26 | align-items: center;
27 | height: 90%;
28 | background-color: brown;
29 | }
30 |
31 | .reveal div.column {
32 | background-color: blueviolet;
33 | }
--------------------------------------------------------------------------------