├── .github
├── ISSUE_TEMPLATE
│ └── example_request.yaml
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── deploy.yml
│ ├── hf_sync.yml
│ └── typos.yaml
├── .gitignore
├── .typos.toml
├── Dockerfile
├── Makefile
├── README.md
├── _server
├── README.md
└── main.py
├── assets
└── marimo-learn.png
├── daft
├── 01_what_makes_daft_special.py
└── README.md
├── duckdb
├── 009_loading_json.py
├── 01_getting_started.py
├── DuckDB_Loading_CSVs.py
└── README.md
├── functional_programming
├── 05_functors.py
├── 06_applicatives.py
├── CHANGELOG.md
└── README.md
├── optimization
├── 01_least_squares.py
├── 02_linear_program.py
├── 03_minimum_fuel_optimal_control.py
├── 04_quadratic_program.py
├── 05_portfolio_optimization.py
├── 06_convex_optimization.py
├── 07_sdp.py
└── README.md
├── polars
├── 01_why_polars.py
├── 02_dataframes.py
├── 04_basic_operations.py
├── 05_reactive_plots.py
├── 08_working_with_columns.py
├── 09_data_types.py
├── 10_strings.py
├── 12_aggregations.py
├── 13_window_functions.py
├── 14_user_defined_functions.py
└── README.md
├── probability
├── 01_sets.py
├── 02_axioms.py
├── 03_probability_of_or.py
├── 04_conditional_probability.py
├── 05_independence.py
├── 06_probability_of_and.py
├── 07_law_of_total_probability.py
├── 08_bayes_theorem.py
├── 09_random_variables.py
├── 10_probability_mass_function.py
├── 11_expectation.py
├── 12_variance.py
├── 13_bernoulli_distribution.py
├── 14_binomial_distribution.py
├── 15_poisson_distribution.py
├── 16_continuous_distribution.py
├── 17_normal_distribution.py
├── 18_central_limit_theorem.py
├── 19_maximum_likelihood_estimation.py
├── 20_naive_bayes.py
├── 21_logistic_regression.py
└── README.md
├── python
├── 001_numbers.py
├── 002_strings.py
├── 003_collections.py
├── 004_conditional_logic.py
├── 005_loops.py
├── 006_dictionaries.py
├── 007_advanced_collections.py
├── 008_functions.py
├── 009_modules.py
├── 010_exceptions.py
└── README.md
└── scripts
├── build.py
├── preview.py
└── templates
└── index.html
/.github/ISSUE_TEMPLATE/example_request.yaml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/refs/heads/master/src/schemas/json/github-issue-forms.json
2 | name: '🚀 Course or notebook proposal'
3 | description: Request a course or notebook
4 | type: Example
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Let us know what you'd like to see added!
10 | - type: textarea
11 | id: example-description
12 | attributes:
13 | label: Description
14 | description: 'Description of the notebook or course to add. Please make the reason and usecases as detailed as possible. If you intend to submit a PR for this issue, tell us in the description. Thanks!'
15 | placeholder: I would like a notebook or course on ...
16 | validations:
17 | required: true
18 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## 📝 Summary
2 |
3 |
8 |
9 | ## 📋 Checklist
10 |
11 | - [ ] I have included package dependencies in the notebook file [using `--sandbox`](https://docs.marimo.io/guides/package_reproducibility/)
12 | - [ ] If adding a course, include a `README.md`
13 | - [ ] Keep language direct and simple.
14 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | on:
4 | push:
5 | branches: ['main']
6 | workflow_dispatch:
7 |
8 | concurrency:
9 | group: 'pages'
10 | cancel-in-progress: false
11 |
12 | env:
13 | UV_SYSTEM_PYTHON: 1
14 |
15 | jobs:
16 | build:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 |
21 | - name: 🚀 Install uv
22 | uses: astral-sh/setup-uv@v4
23 |
24 | - name: 🐍 Set up Python
25 | uses: actions/setup-python@v5
26 | with:
27 | python-version: 3.12
28 |
29 | - name: 📦 Install dependencies
30 | run: |
31 | uv pip install marimo jinja2
32 |
33 | - name: 🛠️ Export notebooks
34 | run: |
35 | python scripts/build.py
36 |
37 | - name: 📤 Upload artifact
38 | uses: actions/upload-pages-artifact@v3
39 | with:
40 | path: _site
41 |
42 | deploy:
43 | needs: build
44 |
45 | permissions:
46 | pages: write
47 | id-token: write
48 |
49 | environment:
50 | name: github-pages
51 | url: ${{ steps.deployment.outputs.page_url }}
52 | runs-on: ubuntu-latest
53 | steps:
54 | - name: 🚀 Deploy to GitHub Pages
55 | id: deployment
56 | uses: actions/deploy-pages@v4
57 |
--------------------------------------------------------------------------------
/.github/workflows/hf_sync.yml:
--------------------------------------------------------------------------------
1 | name: Sync to Hugging Face hub
2 | on:
3 | push:
4 | branches: [main]
5 |
6 | # to run this workflow manually from the Actions tab
7 | workflow_dispatch:
8 |
9 | jobs:
10 | sync-to-hub:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | with:
15 | fetch-depth: 0
16 |
17 | - name: Configure Git
18 | run: |
19 | git config --global user.name "GitHub Action"
20 | git config --global user.email "action@github.com"
21 |
22 | - name: Prepend frontmatter to README
23 | run: |
24 | if [ -f README.md ] && ! grep -q "^---" README.md; then
25 | FRONTMATTER="---
26 | title: marimo learn
27 | emoji: 🧠
28 | colorFrom: blue
29 | colorTo: indigo
30 | sdk: docker
31 | sdk_version: \"latest\"
32 | app_file: app.py
33 | pinned: false
34 | ---
35 |
36 | "
37 | echo "$FRONTMATTER$(cat README.md)" > README.md
38 | git add README.md
39 | git commit -m "Add HF frontmatter to README" || echo "No changes to commit"
40 | fi
41 |
42 | - name: Push to hub
43 | env:
44 | HF_TOKEN: ${{ secrets.HF_TOKEN }}
45 | run: git push -f https://mylessss:$HF_TOKEN@huggingface.co/spaces/marimo-team/marimo-learn main
46 |
--------------------------------------------------------------------------------
/.github/workflows/typos.yaml:
--------------------------------------------------------------------------------
1 | name: Typos
2 |
3 | # Run on pull requests
4 | on:
5 | pull_request: {}
6 |
7 | jobs:
8 | typos:
9 | name: Check for typos
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: 🛑 Cancel Previous Runs
13 | uses: styfle/cancel-workflow-action@0.12.1
14 | - uses: actions/checkout@v4
15 | - uses: crate-ci/typos@v1.29.4
16 | with:
17 | config: .typos.toml
18 |
--------------------------------------------------------------------------------
/.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 | # UV
98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | #uv.lock
102 |
103 | # poetry
104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105 | # This is especially recommended for binary packages to ensure reproducibility, and is more
106 | # commonly ignored for libraries.
107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108 | #poetry.lock
109 |
110 | # pdm
111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112 | #pdm.lock
113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114 | # in version control.
115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116 | .pdm.toml
117 | .pdm-python
118 | .pdm-build/
119 |
120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121 | __pypackages__/
122 |
123 | # Celery stuff
124 | celerybeat-schedule
125 | celerybeat.pid
126 |
127 | # SageMath parsed files
128 | *.sage.py
129 |
130 | # Environments
131 | .env
132 | .venv
133 | env/
134 | venv/
135 | ENV/
136 | env.bak/
137 | venv.bak/
138 |
139 | # Spyder project settings
140 | .spyderproject
141 | .spyproject
142 |
143 | # Rope project settings
144 | .ropeproject
145 |
146 | # mkdocs documentation
147 | /site
148 |
149 | # mypy
150 | .mypy_cache/
151 | .dmypy.json
152 | dmypy.json
153 |
154 | # Pyre type checker
155 | .pyre/
156 |
157 | # pytype static type analyzer
158 | .pytype/
159 |
160 | # Cython debug symbols
161 | cython_debug/
162 |
163 | # PyCharm
164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166 | # and can be added to the global gitignore or merged into this file. For a more nuclear
167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168 | #.idea/
169 |
170 | # PyPI configuration file
171 | .pypirc
172 |
173 | # Marimo specific
174 | __marimo__
175 |
176 | # Generated site content
177 | _site/
178 |
--------------------------------------------------------------------------------
/.typos.toml:
--------------------------------------------------------------------------------
1 | [default]
2 | extend-ignore-re = [
3 | # LaTeX math expressions
4 | "\\\\\\[.*?\\\\\\]",
5 | "\\\\\\(.*?\\\\\\)",
6 | "\\$\\$.*?\\$\\$",
7 | "\\$.*?\\$",
8 | # LaTeX commands
9 | "\\\\[a-zA-Z]+\\{.*?\\}",
10 | "\\\\[a-zA-Z]+",
11 | # LaTeX subscripts and superscripts
12 | "_\\{.*?\\}",
13 | "\\^\\{.*?\\}"
14 | ]
15 |
16 | # Words to explicitly accept
17 | [default.extend-words]
18 | pn = "pn"
19 |
20 | # You can also exclude specific files or directories if needed
21 | # [files]
22 | # extend-exclude = ["*.tex", "docs/*.md"]
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
2 |
3 | WORKDIR /app
4 |
5 | # Create a non-root user
6 | RUN useradd -m appuser
7 |
8 | # Copy application files
9 | COPY _server/main.py _server/main.py
10 | COPY polars/ polars/
11 | COPY duckdb/ duckdb/
12 |
13 | # Set proper ownership
14 | RUN chown -R appuser:appuser /app
15 |
16 | # Switch to non-root user
17 | USER appuser
18 |
19 | # Create virtual environment and install dependencies
20 | RUN uv venv
21 | RUN uv export --script _server/main.py | uv pip install -r -
22 |
23 | ENV PORT=7860
24 | EXPOSE 7860
25 |
26 | CMD ["uv", "run", "_server/main.py"]
27 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | uv pip install marimo jinja2 markdown
3 |
4 | build:
5 | rm -rf _site
6 | uv run scripts/build.py
7 |
8 | serve:
9 | uv run python -m http.server --directory _site
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | A curated collection of educational marimo notebooks || 💬 Discord
7 |
8 |
9 | # 📚 Learn
10 |
11 | This repository contains a curated collection of educational
12 | [marimo](https://github.com/marimo-team/marimo) notebooks. These Python notebooks,
13 | organized into courses, explain fundamental concepts for an expanding set of
14 | topics, including academic topics like computer science, linear algebra,
15 | probability, and machine learning, as well as applied topics such as how to use
16 | popular packages such as polars, pytorch, matplotlib, and more.
17 |
18 | Our goal with `marimo learn` is to create a central repository of high quality
19 | notebooks for educators, students, and practitioners.
20 |
21 | ## Courses
22 |
23 | - 🐍 [Python](python/): Learn the basics of the Python programming language
24 | - ⚖️ [Optimization](optimization/): Learn how to solve optimization problems, with applications to finance and control
25 |
26 | **Coming soon.**
27 |
28 | - 🎲 Probability
29 | - 📏 Linear algebra
30 | - ❄️ Polars
31 | - 🔥 Pytorch
32 | - 🗄️ Duckdb
33 | - 💜 Daft
34 | - 📈 Altair
35 | - 📈 Plotly
36 | - 📈 matplotlib
37 |
38 |
39 | _We're seeking contributions! If you'd like to contribute, please [reach out](https://github.com/marimo-team/learn/issues/new?template=example_request.yaml)._
40 |
41 | ## Adding notebooks
42 |
43 | We welcome community contributions of notebooks and entire courses (folders of
44 | notebooks on a single topic).
45 |
46 | To get started, please start by [opening an issue](https://github.com/marimo-team/learn/issues/new?template=example_request.yaml) and proposing the notebook
47 | or course you'd like to contribute. Once your proposal is accepted, draft
48 | your notebook and submit it for review as a pull request. We hold learn notebooks to a high
49 | standard, and we may go through a few rounds of reviews before your notebook
50 | is merged.
51 |
52 | Here's a contribution checklist:
53 |
54 | - [ ] Include package dependencies in notebook files [using
55 | `--sandbox`](https://docs.marimo.io/guides/package_reproducibility/)
56 | - [ ] If adding a course, include a `README.md`
57 | - [ ] Keep language direct and simple.
58 |
59 | If you aren't comfortable adding a new notebook or course, you can also request
60 | what you'd like to see by [filing an issue](https://github.com/marimo-team/learn/issues/new?template=example_request.yaml).
61 |
62 | ## Building and Previewing
63 |
64 | The site is built using a Python script that exports marimo notebooks to HTML and generates an index page.
65 |
66 | ```bash
67 | # Build the site
68 | python scripts/build.py --output-dir _site
69 |
70 | # Preview the site (builds first)
71 | python scripts/preview.py
72 |
73 | # Preview without rebuilding
74 | python scripts/preview.py --no-build
75 | ```
76 |
77 | ## Community
78 |
79 | We're building a community. Come hang out with us!
80 |
81 | - 🌟 [Star us on GitHub](https://github.com/marimo-team/examples)
82 | - 💬 [Chat with us on Discord](https://discord.gg/rT48v2Y9fe)
83 | - 📧 [Subscribe to our Newsletter](https://marimo.io/newsletter)
84 | - ☁️ [Join our Cloud Waitlist](https://marimo.io/cloud)
85 | - ✏️ [Start a GitHub Discussion](https://github.com/marimo-team/marimo/discussions)
86 | - 🦋 [Follow us on Bluesky](https://bsky.app/profile/marimo.io)
87 | - 🐦 [Follow us on Twitter](https://twitter.com/marimo_io)
88 | - 🕴️ [Follow us on LinkedIn](https://www.linkedin.com/company/marimo-io)
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/_server/README.md:
--------------------------------------------------------------------------------
1 | # marimo learn server
2 |
3 | This folder contains server code for hosting marimo apps.
4 |
5 | ## Running the server
6 |
7 | ```bash
8 | cd _server
9 | uv run --no-project main.py
10 | ```
11 |
12 | ## Building a Docker image
13 |
14 | From the root directory, run:
15 |
16 | ```bash
17 | docker build -t marimo-learn .
18 | ```
19 |
20 | ## Running the Docker container
21 |
22 | ```bash
23 | docker run -p 7860:7860 marimo-learn
24 | ```
25 |
--------------------------------------------------------------------------------
/_server/main.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.12"
3 | # dependencies = [
4 | # "fastapi",
5 | # "marimo",
6 | # "starlette",
7 | # "python-dotenv",
8 | # "pydantic",
9 | # "duckdb",
10 | # "altair==5.5.0",
11 | # "beautifulsoup4==4.13.3",
12 | # "httpx==0.28.1",
13 | # "marimo",
14 | # "nest-asyncio==1.6.0",
15 | # "numba==0.61.0",
16 | # "numpy==2.1.3",
17 | # "polars==1.24.0",
18 | # ]
19 | # ///
20 |
21 | import logging
22 | import os
23 | from pathlib import Path
24 |
25 | import marimo
26 | from dotenv import load_dotenv
27 | from fastapi import FastAPI, Request
28 | from fastapi.responses import HTMLResponse
29 |
30 | # Load environment variables
31 | load_dotenv()
32 |
33 | # Set up logging
34 | logging.basicConfig(level=logging.INFO)
35 | logger = logging.getLogger(__name__)
36 |
37 | # Get port from environment variable or use default
38 | PORT = int(os.environ.get("PORT", 7860))
39 |
40 | root_dir = Path(__file__).parent.parent
41 |
42 | ROOTS = [
43 | root_dir / "polars",
44 | root_dir / "duckdb",
45 | ]
46 |
47 |
48 | server = marimo.create_asgi_app(include_code=True)
49 | app_names: list[str] = []
50 |
51 | for root in ROOTS:
52 | for filename in root.iterdir():
53 | if filename.is_file() and filename.suffix == ".py":
54 | app_path = root.stem + "/" + filename.stem
55 | server = server.with_app(path=f"/{app_path}", root=str(filename))
56 | app_names.append(app_path)
57 |
58 | # Create a FastAPI app
59 | app = FastAPI()
60 |
61 | logger.info(f"Mounting {len(app_names)} apps")
62 | for app_name in app_names:
63 | logger.info(f" /{app_name}")
64 |
65 |
66 | @app.get("/")
67 | async def home(request: Request):
68 | html_content = """
69 |
70 |
71 |
72 | marimo learn
73 |
74 |
75 | Welcome to marimo learn!
76 | This is a collection of interactive tutorials for learning data science libraries with marimo.
77 | Check out the GitHub repository for more information.
78 |
79 |
80 | """
81 | return HTMLResponse(content=html_content)
82 |
83 |
84 | app.mount("/", server.build())
85 |
86 | # Run the server
87 | if __name__ == "__main__":
88 | import uvicorn
89 |
90 | uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="info")
91 |
--------------------------------------------------------------------------------
/assets/marimo-learn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marimo-team/learn/a50306f0bf215ade3aa2f735d72deff2d9ec267f/assets/marimo-learn.png
--------------------------------------------------------------------------------
/daft/README.md:
--------------------------------------------------------------------------------
1 | # Learn Daft
2 |
3 | _🚧 This collection is a work in progress. Please help us add notebooks!_
4 |
5 | This collection of marimo notebooks is designed to teach you the basics of
6 | Daft, a distributed dataframe engine that unifies data engineering, analytics & ML/AI workflows.
7 |
8 | **Help us build this course! ⚒️**
9 |
10 | We're seeking contributors to help us build these notebooks. Every contributor
11 | will be acknowledged as an author in this README and in their contributed
12 | notebooks. Head over to the [tracking
13 | issue](https://github.com/marimo-team/learn/issues/43) to sign up for a planned
14 | notebook or propose your own.
15 |
16 | **Running notebooks.** To run a notebook locally, use
17 |
18 | ```bash
19 | uvx marimo edit
20 | ```
21 |
22 | You can also open notebooks in our online playground by appending marimo.app/ to a notebook's URL.
23 |
24 | **Thanks to all our notebook authors!**
25 |
26 | * [Péter Gyarmati](https://github.com/peter-gy)
27 |
--------------------------------------------------------------------------------
/duckdb/009_loading_json.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.11"
3 | # dependencies = [
4 | # "marimo",
5 | # "duckdb==1.2.1",
6 | # "sqlglot==26.11.1",
7 | # "polars[pyarrow]==1.25.2",
8 | # ]
9 | # ///
10 |
11 | import marimo
12 |
13 | __generated_with = "0.12.8"
14 | app = marimo.App(width="medium")
15 |
16 |
17 | @app.cell(hide_code=True)
18 | def _(mo):
19 | mo.md(
20 | r"""
21 | # Loading JSON
22 |
23 | DuckDB supports reading and writing JSON through the `json` extension that should be present in most distributions and is autoloaded on first-use. If it's not, you can [install and load](https://duckdb.org/docs/stable/data/json/installing_and_loading.html) it manually like any other extension.
24 |
25 | In this tutorial we'll cover 4 different ways we can transfer JSON data in and out of DuckDB:
26 |
27 | - [`FROM`](https://duckdb.org/docs/stable/sql/query_syntax/from.html) statement.
28 | - [`read_json`](https://duckdb.org/docs/stable/data/json/loading_json#the-read_json-function) function.
29 | - [`COPY`](https://duckdb.org/docs/stable/sql/statements/copy#copy--from) statement.
30 | - [`IMPORT DATABASE`](https://duckdb.org/docs/stable/sql/statements/export.html) statement.
31 | """
32 | )
33 | return
34 |
35 |
36 | @app.cell(hide_code=True)
37 | def _(mo):
38 | mo.md(
39 | r"""
40 | ## Using `FROM`
41 |
42 | Loading data using `FROM` is simple and straightforward. We use a path or URL to the file we want to load where we'd normally put a table name. When we do this, DuckDB attempts to infer the right way to read the file including the correct format and column types. In most cases this is all we need to load data into DuckDB.
43 | """
44 | )
45 | return
46 |
47 |
48 | @app.cell
49 | def _(mo):
50 | _df = mo.sql(
51 | f"""
52 | SELECT * FROM 'https://raw.githubusercontent.com/vega/vega-datasets/refs/heads/main/data/cars.json';
53 | """
54 | )
55 | return
56 |
57 |
58 | @app.cell(hide_code=True)
59 | def _(mo):
60 | mo.md(
61 | r"""
62 | ## Using `read_json`
63 |
64 | For greater control over how the JSON is read, we can directly call the [`read_json`](https://duckdb.org/docs/stable/data/json/loading_json#the-read_json-function) function. It supports a few different arguments — some common ones are:
65 |
66 | - `format='array'` or `format='newline_delimited'` - the former tells DuckDB that the rows should be read from a top-level JSON array while the latter means the rows should be read from JSON objects separated by a newline (JSONL/NDJSON).
67 | - `ignore_errors=true` - skips lines with parse errors when reading newline delimited JSON.
68 | - `columns={columnName: type, ...}` - lets you set types for individual columns manually.
69 | - `dateformat` and `timestampformat` - controls how DuckDB attempts to parse [Date](https://duckdb.org/docs/stable/sql/data_types/date) and [Timestamp](https://duckdb.org/docs/stable/sql/data_types/timestamp) types. Use the format specifiers specified in the [docs](https://duckdb.org/docs/stable/sql/functions/dateformat.html#format-specifiers).
70 |
71 | We could rewrite the previous query more explicitly as:
72 | """
73 | )
74 | return
75 |
76 |
77 | @app.cell
78 | def _(mo):
79 | cars_df = mo.sql(
80 | f"""
81 | SELECT *
82 | FROM
83 | read_json(
84 | 'https://raw.githubusercontent.com/vega/vega-datasets/refs/heads/main/data/cars.json',
85 | format = 'array',
86 | columns = {{
87 | Name:'VARCHAR',
88 | Miles_per_Gallon:'FLOAT',
89 | Cylinders:'FLOAT',
90 | Displacement:'FLOAT',
91 | Horsepower:'FLOAT',
92 | Weight_in_lbs:'FLOAT',
93 | Acceleration:'FLOAT',
94 | Year:'DATE',
95 | Origin:'VARCHAR'
96 | }},
97 | dateformat = '%Y-%m-%d'
98 | )
99 | ;
100 | """
101 | )
102 | return (cars_df,)
103 |
104 |
105 | @app.cell(hide_code=True)
106 | def _(mo):
107 | mo.md(r"""Other than singular files we can read [multiple files](https://duckdb.org/docs/stable/data/multiple_files/overview.html) at a time by either passing a list of files or a UNIX glob pattern.""")
108 | return
109 |
110 |
111 | @app.cell(hide_code=True)
112 | def _(mo):
113 | mo.md(
114 | r"""
115 | ## Using `COPY`
116 |
117 | `COPY` is for useful both for importing and exporting data in a variety of formats including JSON. For example, we can import data into an existing table from a JSON file.
118 | """
119 | )
120 | return
121 |
122 |
123 | @app.cell
124 | def _(mo):
125 | _df = mo.sql(
126 | f"""
127 | CREATE OR REPLACE TABLE cars2 (
128 | Name VARCHAR,
129 | Miles_per_Gallon VARCHAR,
130 | Cylinders VARCHAR,
131 | Displacement FLOAT,
132 | Horsepower FLOAT,
133 | Weight_in_lbs FLOAT,
134 | Acceleration FLOAT,
135 | Year DATE,
136 | Origin VARCHAR
137 | );
138 | """
139 | )
140 | return (cars2,)
141 |
142 |
143 | @app.cell
144 | def _(cars2, mo):
145 | _df = mo.sql(
146 | f"""
147 | COPY cars2 FROM 'https://raw.githubusercontent.com/vega/vega-datasets/refs/heads/main/data/cars.json' (FORMAT json, ARRAY true, DATEFORMAT '%Y-%m-%d');
148 | SELECT * FROM cars2;
149 | """
150 | )
151 | return
152 |
153 |
154 | @app.cell(hide_code=True)
155 | def _(mo):
156 | mo.md(r"""Similarly, we can write data from a table or select statement to a JSON file. For example, we create a new JSONL file with just the car names and miles per gallon. We first create a temporary directory to avoid cluttering our project directory.""")
157 | return
158 |
159 |
160 | @app.cell
161 | def _(Path):
162 | from tempfile import TemporaryDirectory
163 |
164 | TMP_DIR = TemporaryDirectory()
165 | COPY_PATH = Path(TMP_DIR.name) / "cars_mpg.jsonl"
166 | print(COPY_PATH)
167 | return COPY_PATH, TMP_DIR, TemporaryDirectory
168 |
169 |
170 | @app.cell
171 | def _(COPY_PATH, cars2, mo):
172 | _df = mo.sql(
173 | f"""
174 | COPY (
175 | SELECT
176 | Name AS car_name,
177 | "Miles_per_Gallon" AS mpg
178 | FROM cars2
179 | WHERE mpg IS NOT null
180 | ) TO '{COPY_PATH}' (FORMAT json);
181 | """
182 | )
183 | return
184 |
185 |
186 | @app.cell
187 | def _(COPY_PATH, Path):
188 | Path(COPY_PATH).exists()
189 | return
190 |
191 |
192 | @app.cell(hide_code=True)
193 | def _(mo):
194 | mo.md(
195 | r"""
196 | ## Using `IMPORT DATABASE`
197 |
198 | The last method we can use to load JSON data is using the `IMPORT DATABASE` statement. It works in conjunction with `EXPORT DATABASE` to save and load an entire database to and from a directory. For example let's try and export our default in-memory database.
199 | """
200 | )
201 | return
202 |
203 |
204 | @app.cell
205 | def _(Path, TMP_DIR):
206 | EXPORT_PATH = Path(TMP_DIR.name) / "cars_export"
207 | print(EXPORT_PATH)
208 | return (EXPORT_PATH,)
209 |
210 |
211 | @app.cell
212 | def _(EXPORT_PATH, mo):
213 | _df = mo.sql(
214 | f"""
215 | EXPORT DATABASE '{EXPORT_PATH}' (FORMAT json);
216 | """
217 | )
218 | return
219 |
220 |
221 | @app.cell
222 | def _(EXPORT_PATH, Path):
223 | list(Path(EXPORT_PATH).iterdir())
224 | return
225 |
226 |
227 | @app.cell(hide_code=True)
228 | def _(mo):
229 | mo.md(r"""We can then load the database back into DuckDB.""")
230 | return
231 |
232 |
233 | @app.cell
234 | def _(EXPORT_PATH, mo):
235 | _df = mo.sql(
236 | f"""
237 | DROP TABLE IF EXISTS cars2;
238 | IMPORT DATABASE '{EXPORT_PATH}';
239 | SELECT * FROM cars2;
240 | """
241 | )
242 | return
243 |
244 |
245 | @app.cell(hide_code=True)
246 | def _(TMP_DIR):
247 | TMP_DIR.cleanup()
248 | return
249 |
250 |
251 | @app.cell(hide_code=True)
252 | def _(mo):
253 | mo.md(
254 | r"""
255 | ## Further Reading
256 |
257 | - Complete information on the JSON support in DuckDB can be found in their [documentation](https://duckdb.org/docs/stable/data/json/overview.html).
258 | - You can also learn more about using SQL in marimo from the [examples](https://github.com/marimo-team/marimo/tree/main/examples/sql).
259 | """
260 | )
261 | return
262 |
263 |
264 | @app.cell
265 | def _():
266 | import marimo as mo
267 | from pathlib import Path
268 | return Path, mo
269 |
270 |
271 | if __name__ == "__main__":
272 | app.run()
273 |
--------------------------------------------------------------------------------
/duckdb/DuckDB_Loading_CSVs.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # "plotly.express",
6 | # "plotly==6.0.1",
7 | # "duckdb==1.2.1",
8 | # "sqlglot==26.11.1",
9 | # "pyarrow==19.0.1",
10 | # "polars==1.27.1",
11 | # ]
12 | # ///
13 |
14 | import marimo
15 |
16 | __generated_with = "0.12.10"
17 | app = marimo.App(width="medium")
18 |
19 |
20 | @app.cell(hide_code=True)
21 | def _(mo):
22 | mo.md(r"""#Loading CSVs with DuckDB""")
23 | return
24 |
25 |
26 | @app.cell(hide_code=True)
27 | def _(mo):
28 | mo.md(
29 | r"""
30 | I remember when I first learnt about DuckDB, it was a gamechanger — I used to load the data I wanted to work on to a database software like MS SQL Server, and then build a bridge to an IDE with the language I wanted to use like Python, or R; it was quite the hassle. DuckDB changed my whole world — now I could just import the data file into the IDE, or notebook, make a duckdb connection, and there we go! But then, I realized I didn't even need the step of first importing the file using python. I could just query the csv file directly using SQL through a DuckDB connection.
31 |
32 | ##Introduction
33 | I found this dataset on the evolution of AI research by discipline from OECD , and it piqued my interest. I feel like publications in natural language processing drastically jumped in the mid 2010s, and I'm excited to find out if that's the case.
34 |
35 | In this notebook, we'll:
36 |
37 | Import the CSV file into the notebook
38 | Create another table within the database based on the CSV
39 | Dig into publications on natural language processing have evolved over the years
40 |
41 | """
42 | )
43 | return
44 |
45 |
46 | @app.cell(hide_code=True)
47 | def _(mo):
48 | mo.md(r"""##Load the CSV""")
49 | return
50 |
51 |
52 | @app.cell
53 | def _(mo):
54 | _df = mo.sql(
55 | f"""
56 | /* Another way to load the CSV could be
57 | SELECT *
58 | FROM read_csv('https://github.com/Mustjaab/Loading_CSVs_in_DuckDB/blob/main/AI_Research_Data.csv')
59 | */
60 | SELECT *
61 | FROM "https://raw.githubusercontent.com/Mustjaab/Loading_CSVs_in_DuckDB/refs/heads/main/AI_Research_Data.csv"
62 | LIMIT 5;
63 | """
64 | )
65 | return
66 |
67 |
68 | @app.cell(hide_code=True)
69 | def _(mo):
70 | mo.md(r"""##Create Another Table""")
71 | return
72 |
73 |
74 | @app.cell
75 | def _(mo):
76 | Discipline_Analysis = mo.sql(
77 | f"""
78 | -- Build a table based on the CSV where it just contains the specified columns
79 | CREATE TABLE Domain_Analysis AS
80 | SELECT Year, Concept, publications FROM "https://raw.githubusercontent.com/Mustjaab/Loading_CSVs_in_DuckDB/refs/heads/main/AI_Research_Data.csv"
81 | """
82 | )
83 | return Discipline_Analysis, Domain_Analysis
84 |
85 |
86 | @app.cell
87 | def _(Domain_Analysis, mo):
88 | Analysis = mo.sql(
89 | f"""
90 | SELECT *
91 | FROM Domain_Analysis
92 | GROUP BY Concept, Year, publications
93 | ORDER BY Year
94 | """
95 | )
96 | return (Analysis,)
97 |
98 |
99 | @app.cell
100 | def _(Domain_Analysis, mo):
101 | _df = mo.sql(
102 | f"""
103 | SELECT
104 | AVG(CASE WHEN Year < 2020 THEN publications END) AS avg_pre_2020,
105 | AVG(CASE WHEN Year >= 2020 THEN publications END) AS avg_2020_onward
106 | FROM Domain_Analysis
107 | WHERE Concept = 'Natural language processing';
108 | """
109 | )
110 | return
111 |
112 |
113 | @app.cell
114 | def _(Domain_Analysis, mo):
115 | NLP_Analysis = mo.sql(
116 | f"""
117 | SELECT
118 | publications,
119 | CASE
120 | WHEN Year < 2020 THEN 'Pre-2020'
121 | ELSE '2020-onward'
122 | END AS period
123 | FROM Domain_Analysis
124 | WHERE Year >= 2000
125 | AND Concept = 'Natural language processing';
126 | """,
127 | output=False
128 | )
129 | return (NLP_Analysis,)
130 |
131 |
132 | @app.cell
133 | def _(NLP_Analysis, px):
134 | px.box(NLP_Analysis, x='period', y='publications', color='period')
135 | return
136 |
137 |
138 | @app.cell(hide_code=True)
139 | def _(mo):
140 | mo.md(r""" We can see there's a significant increase in NLP publications 2020 and onwards which definitely makes sense provided the rapid emergence of commercial large language models, and AI assistants.
""")
141 |
142 | @app.cell(hide_code=True)
143 | def _(mo):
144 | mo.md(
145 | r"""
146 | ##Conclusion
147 | In this notebook, we learned how to:
148 |
149 | Load a CSV into DuckDB
150 | Create other tables using the imported CSV
151 | Seamlessly analyze and visualize data between SQL, and Python cells
152 |
153 | """
154 | )
155 | return
156 |
157 |
158 | @app.cell
159 | def _():
160 | import pyarrow
161 | import polars
162 | return polars, pyarrow
163 |
164 |
165 | @app.cell
166 | def _():
167 | import marimo as mo
168 | import plotly.express as px
169 | return mo, px
170 |
171 |
172 | if __name__ == "__main__":
173 | app.run()
174 |
--------------------------------------------------------------------------------
/duckdb/README.md:
--------------------------------------------------------------------------------
1 | # Learn DuckDB
2 |
3 | _🚧 This collection is a work in progress. Please help us add notebooks!_
4 |
5 | This collection of marimo notebooks is designed to teach you the basics of
6 | DuckDB, a fast in-memory OLAP engine that can interoperate with Dataframes.
7 | These notebooks also show how marimo gives DuckDB superpowers.
8 |
9 | **Help us build this course! ⚒️**
10 |
11 | We're seeking contributors to help us build these notebooks. Every contributor
12 | will be acknowledged as an author in this README and in their contributed
13 | notebooks. Head over to the [tracking
14 | issue](https://github.com/marimo-team/learn/issues/48) to sign up for a planned
15 | notebook or propose your own.
16 |
17 | **Running notebooks.** To run a notebook locally, use
18 |
19 | ```bash
20 | uvx marimo edit
21 | ```
22 |
23 | You can also open notebooks in our online playground by appending marimo.app/ to a notebook's URL.
24 |
25 |
26 | **Authors.**
27 |
28 | Thanks to all our notebook authors!
29 |
30 | * [Mustjaab](https://github.com/Mustjaab)
31 | * [julius383](https://github.com/julius383)
32 |
--------------------------------------------------------------------------------
/functional_programming/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog of the functional-programming course
2 |
3 | ## 2025-04-16
4 |
5 | **applicatives.py**
6 |
7 | - replace `return NotImplementedError` with `raise NotImplementedError`
8 |
9 | - add `Either` applicative
10 | - Add `Alternative`
11 |
12 | ## 2025-04-11
13 |
14 | **functors.py**
15 |
16 | - add `Bifunctor` section
17 |
18 | - replace `return NotImplementedError` with `raise NotImplementedError`
19 |
20 | ## 2025-04-08
21 |
22 | **functors.py**
23 |
24 | - restructure the notebook
25 | - replace `f` in the function signatures with `g` to indicate regular functions and
26 | distinguish from functors
27 | - move `Maybe` funtor to section `More Functor instances`
28 |
29 | - add `Either` functor
30 |
31 | - add `unzip` utility function for functors
32 |
33 | ## 2025-04-07
34 |
35 | **applicatives.py**
36 |
37 | - the `apply` method of `Maybe` _Applicative_ should return `None` when `fg` or `fa` is
38 | `None`
39 |
40 | - add `sequenceL` as a classmethod for `Applicative` and add examples for `Wrapper`,
41 | `Maybe`, `List`
42 | - add description for utility functions of `Applicative`
43 |
44 | - refine the implementation of `IO` _Applicative_
45 | - reimplement `get_chars` with `IO.sequenceL`
46 |
47 | - add an example to show that `ListMonoidal` is equivalent to `List` _Applicative_
48 |
49 | ## 2025-04-06
50 |
51 | **applicatives.py**
52 |
53 | - remove `sequenceL` from `Applicative` because it should be a classmethod but can't be
54 | generically implemented
55 |
56 | ## 2025-04-02
57 |
58 | **functors.py**
59 |
60 | - Migrate to `python3.13`
61 |
62 | - Replace all occurrences of
63 |
64 | ```python
65 | class Functor(Generic[A])
66 | ```
67 |
68 | with
69 |
70 | ```python
71 | class Functor[A]
72 | ```
73 |
74 | for conciseness
75 |
76 | - Use `fa` in function signatures instead of `a` when `fa` is a _Functor_
77 |
78 | **applicatives.py**
79 |
80 | - `0.1.0` version of notebook `06_applicatives.py`
81 |
82 | ## 2025-03-16
83 |
84 | **functors.py**
85 |
86 | - Use uppercased letters for `Generic` types, e.g. `A = TypeVar("A")`
87 | - Refactor the `Functor` class, changing `fmap` and utility methods to `classmethod`
88 |
89 | For example:
90 |
91 | ```python
92 | @dataclass
93 | class Wrapper(Functor, Generic[A]):
94 | value: A
95 |
96 | @classmethod
97 | def fmap(cls, f: Callable[[A], B], a: "Wrapper[A]") -> "Wrapper[B]":
98 | return Wrapper(f(a.value))
99 |
100 | >>> Wrapper.fmap(lambda x: x + 1, wrapper)
101 | Wrapper(value=2)
102 | ```
103 |
104 | - Move the `check_functor_law` method from `Functor` class to a standard function
105 |
106 | - Rename `ListWrapper` to `List` for simplicity
107 | - Remove the `Just` class
108 |
109 | - Rewrite proofs
110 |
111 | ## 2025-03-13
112 |
113 | **functors.py**
114 |
115 | - `0.1.0` version of notebook `05_functors`
116 |
117 | Thank [Akshay](https://github.com/akshayka) and [Haleshot](https://github.com/Haleshot)
118 | for reviewing
119 |
120 | ## 2025-03-11
121 |
122 | **functors.py**
123 |
124 | - Demo version of notebook `05_functors.py`
125 |
--------------------------------------------------------------------------------
/functional_programming/README.md:
--------------------------------------------------------------------------------
1 | # Learn Functional Programming
2 |
3 | _🚧 This collection is a [work in progress](https://github.com/marimo-team/learn/issues/51)._
4 |
5 | This series of marimo notebooks introduces the powerful paradigm of functional
6 | programming through Python. Taking inspiration from Haskell and Category
7 | Theory, we'll build a strong foundation in FP concepts that can transform how
8 | you approach software development.
9 |
10 | ## What You'll Learn
11 |
12 | **Using only Python's standard library**, we'll construct functional
13 | programming concepts from first principles.
14 |
15 | Topics include:
16 |
17 | + Currying and higher-order functions
18 | + Functors, Applicatives, and Monads
19 | + Category theory fundamentals
20 |
21 | ## Running Notebooks
22 |
23 | ### Locally
24 |
25 | To run a notebook locally, use
26 |
27 | ```bash
28 | uvx marimo edit
29 | ```
30 |
31 | For example, run the `Functor` tutorial with
32 |
33 | ```bash
34 | uvx marimo edit https://github.com/marimo-team/learn/blob/main/functional_programming/05_functors.py
35 | ```
36 |
37 | ### On Our Online Playground
38 |
39 | You can also open notebooks in our online playground by appending `marimo.app/` to a notebook's URL like:
40 |
41 | https://marimo.app/https://github.com/marimo-team/learn/blob/main/functional_programming/05_functors.py
42 |
43 | ### On Our Landing Page
44 |
45 | Open the notebooks in our landing page page [here](https://marimo-team.github.io/learn/functional_programming/05_functors.html)
46 |
47 | ## Collaboration
48 |
49 | If you're interested in collaborating or have questions, please reach out to me
50 | on Discord (@eugene.hs).
51 |
52 | ## Description of notebooks
53 |
54 | Check [here](https://github.com/marimo-team/learn/issues/51) for current series
55 | structure.
56 |
57 | | Notebook | Title | Key Concepts | Prerequisites |
58 | |----------|-------|--------------|---------------|
59 | | [05. Functors](https://github.com/marimo-team/learn/blob/main/functional_programming/05_functors.py) | Category Theory and Functors | Category Theory, Functor, fmap, Bifunctor | Basic Python, Functions |
60 | | [06. Applicatives](https://github.com/marimo-team/learn/blob/main/functional_programming/06_applicatives.py) | Applicative programming with effects | Applicative Functor, pure, apply, Effectful programming, Alternative | Functors |
61 |
62 | **Authors.**
63 |
64 | Thanks to all our notebook authors!
65 |
66 | - [métaboulie](https://github.com/metaboulie)
67 |
68 | **Reviewers.**
69 |
70 | Thanks to all our notebook reviews!
71 |
72 | - [Haleshot](https://github.com/Haleshot)
73 |
--------------------------------------------------------------------------------
/optimization/01_least_squares.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.11"
3 | # dependencies = [
4 | # "cvxpy==1.6.0",
5 | # "marimo",
6 | # "numpy==2.2.2",
7 | # ]
8 | # ///
9 |
10 | import marimo
11 |
12 | __generated_with = "0.11.0"
13 | app = marimo.App()
14 |
15 |
16 | @app.cell
17 | def _():
18 | import marimo as mo
19 | return (mo,)
20 |
21 |
22 | @app.cell(hide_code=True)
23 | def _(mo):
24 | mo.md(
25 | r"""
26 | # Least squares
27 |
28 | In a least-squares problem, we have measurements $A \in \mathcal{R}^{m \times
29 | n}$ (i.e., $m$ rows and $n$ columns) and $b \in \mathcal{R}^m$. We seek a vector
30 | $x \in \mathcal{R}^{n}$ such that $Ax$ is close to $b$. The matrices $A$ and $b$ are problem data or constants, and $x$ is the variable we are solving for.
31 |
32 | Closeness is defined as the sum of the squared differences:
33 |
34 | \[ \sum_{i=1}^m (a_i^Tx - b_i)^2, \]
35 |
36 | also known as the $\ell_2$-norm squared, $\|Ax - b\|_2^2$.
37 |
38 | For example, we might have a dataset of $m$ users, each represented by $n$ features. Each row $a_i^T$ of $A$ is the feature vector for user $i$, while the corresponding entry $b_i$ of $b$ is the measurement we want to predict from $a_i^T$, such as ad spending. The prediction for user $i$ is given by $a_i^Tx$.
39 |
40 | We find the optimal value of $x$ by solving the optimization problem
41 |
42 | \[
43 | \begin{array}{ll}
44 | \text{minimize} & \|Ax - b\|_2^2.
45 | \end{array}
46 | \]
47 |
48 | Let $x^\star$ denote the optimal $x$. The quantity $r = Ax^\star - b$ is known as the residual. If $\|r\|_2 = 0$, we have a perfect fit.
49 | """
50 | )
51 | return
52 |
53 |
54 | @app.cell(hide_code=True)
55 | def _(mo):
56 | mo.md(
57 | r"""
58 | ## Example
59 |
60 | In this example, we use the Python library [CVXPY](https://github.com/cvxpy/cvxpy) to construct and solve a least-squares problems.
61 | """
62 | )
63 | return
64 |
65 |
66 | @app.cell
67 | def _():
68 | import cvxpy as cp
69 | import numpy as np
70 | return cp, np
71 |
72 |
73 | @app.cell
74 | def _():
75 | m = 20
76 | n = 15
77 | return m, n
78 |
79 |
80 | @app.cell
81 | def _(m, n, np):
82 | np.random.seed(0)
83 | A = np.random.randn(m, n)
84 | b = np.random.randn(m)
85 | return A, b
86 |
87 |
88 | @app.cell
89 | def _(A, b, cp, n):
90 | x = cp.Variable(n)
91 | objective = cp.sum_squares(A @ x - b)
92 | problem = cp.Problem(cp.Minimize(objective))
93 | optimal_value = problem.solve()
94 | return objective, optimal_value, problem, x
95 |
96 |
97 | @app.cell
98 | def _(A, b, cp, mo, optimal_value, x):
99 | mo.md(
100 | f"""
101 | - The optimal value is **{optimal_value:.04f}**.
102 | - The optimal value of $x$ is {mo.as_html(list(x.value))}
103 | - The norm of the residual is **{cp.norm(A @ x - b, p=2).value:0.4f}**
104 | """
105 | )
106 | return
107 |
108 |
109 | @app.cell(hide_code=True)
110 | def _(mo):
111 | mo.md(
112 | r"""
113 | ## Further reading
114 |
115 | For a primer on least squares, with many real-world examples, check out the free book
116 | [Vectors, Matrices, and Least Squares](https://web.stanford.edu/~boyd/vmls/), which is used for undergraduate linear algebra education at Stanford.
117 | """
118 | )
119 | return
120 |
121 |
122 | if __name__ == "__main__":
123 | app.run()
124 |
--------------------------------------------------------------------------------
/optimization/02_linear_program.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.13"
3 | # dependencies = [
4 | # "cvxpy==1.6.0",
5 | # "marimo",
6 | # "matplotlib==3.10.0",
7 | # "numpy==2.2.2",
8 | # "wigglystuff==0.1.9",
9 | # ]
10 | # ///
11 |
12 | import marimo
13 |
14 | __generated_with = "0.11.0"
15 | app = marimo.App()
16 |
17 |
18 | @app.cell
19 | def _():
20 | import marimo as mo
21 | return (mo,)
22 |
23 |
24 | @app.cell(hide_code=True)
25 | def _(mo):
26 | mo.md(
27 | r"""
28 | # Linear program
29 |
30 | A linear program is an optimization problem with a linear objective and affine
31 | inequality constraints. A common standard form is the following:
32 |
33 | \[
34 | \begin{array}{ll}
35 | \text{minimize} & c^Tx \\
36 | \text{subject to} & Ax \leq b.
37 | \end{array}
38 | \]
39 |
40 | Here $A \in \mathcal{R}^{m \times n}$, $b \in \mathcal{R}^m$, and $c \in \mathcal{R}^n$ are problem data and $x \in \mathcal{R}^{n}$ is the optimization variable. The inequality constraint $Ax \leq b$ is elementwise.
41 |
42 | For example, we might have $n$ different products, each constructed out of $m$ components. Each entry $A_{ij}$ is the amount of component $i$ required to build one unit of product $j$. Each entry $b_i$ is the total amount of component $i$ available. We lose $c_j$ for each unit of product $j$ ($c_j < 0$ indicates profit). Our goal then is to choose how many units of each product $j$ to make, $x_j$, in order to minimize loss without exceeding our budget for any component.
43 |
44 | In addition to a solution $x^\star$, we obtain a dual solution $\lambda^\star$. A positive entry $\lambda^\star_i$ indicates that the constraint $a_i^Tx \leq b_i$ holds with equality for $x^\star$ and suggests that changing $b_i$ would change the optimal value.
45 |
46 | **Why linear programming?** Linear programming is a way to achieve an optimal outcome, such as maximum utility or lowest cost, subject to a linear objective function and affine constraints. Developed in the 20th century, linear programming is widely used today to solve problems in resource allocation, scheduling, transportation, and more. The discovery of polynomial-time algorithms to solve linear programs was of tremendous worldwide importance and entered the public discourse, even making the front page of the New York Times.
47 |
48 | In the late 20th and early 21st century, researchers generalized linear programming to a much wider class of problems called convex optimization problems. Nearly all convex optimization problems can be solved efficiently and reliably, and even more difficult problems are readily solved by a sequence of convex optimization problems. Today, convex optimization is used to fit machine learning models, land rockets in real-time at SpaceX, plan trajectories for self-driving cars at Waymo, execute many billions of dollars of financial trades a day, and much more.
49 |
50 | This marimo learn course uses CVXPY, a modeling language for convex optimization problems developed originally at Stanford, to construct and solve convex programs.
51 | """
52 | )
53 | return
54 |
55 |
56 | @app.cell(hide_code=True)
57 | def _(mo):
58 | mo.md(
59 | f"""
60 | {mo.image("https://www.debugmind.com/wp-content/uploads/2020/01/spacex-1.jpg")}
61 | _SpaceX solves convex optimization problems onboard to land its rockets, using CVXGEN, a code generator for quadratic programming developed at Stephen Boyd’s Stanford lab. Photo by SpaceX, licensed CC BY-NC 2.0._
62 | """
63 | )
64 | return
65 |
66 |
67 | @app.cell(hide_code=True)
68 | def _(mo):
69 | mo.md(
70 | r"""
71 | ## Example
72 |
73 | Here we use CVXPY to construct and solve a linear program.
74 | """
75 | )
76 | return
77 |
78 |
79 | @app.cell
80 | def _():
81 | import cvxpy as cp
82 | import numpy as np
83 | return cp, np
84 |
85 |
86 | @app.cell(hide_code=True)
87 | def _(np):
88 | A = np.array(
89 | [
90 | [0.76103773, 0.12167502],
91 | [0.44386323, 0.33367433],
92 | [1.49407907, -0.20515826],
93 | [0.3130677, -0.85409574],
94 | [-2.55298982, 0.6536186],
95 | [0.8644362, -0.74216502],
96 | [2.26975462, -1.45436567],
97 | [0.04575852, -0.18718385],
98 | [1.53277921, 1.46935877],
99 | [0.15494743, 0.37816252],
100 | ]
101 | )
102 |
103 | b = np.array(
104 | [
105 | 2.05062369,
106 | 0.94934659,
107 | 0.89559424,
108 | 1.04389978,
109 | 2.45035643,
110 | -0.95479445,
111 | -0.83801349,
112 | -0.26562529,
113 | 2.35763652,
114 | 0.98286942,
115 | ]
116 | )
117 | return A, b
118 |
119 |
120 | @app.cell(hide_code=True)
121 | def _(mo):
122 | mo.md(r"""We've randomly generated problem data $A$ and $B$. The vector for $c$ is shown below. Try playing with the value of $c$ by dragging the components, and see how the level curves change in the visualization below.""")
123 | return
124 |
125 |
126 | @app.cell
127 | def _(mo, np):
128 | from wigglystuff import Matrix
129 |
130 | c_widget = mo.ui.anywidget(Matrix(matrix=np.array([[0.1, -0.2]]), step=0.01))
131 | c_widget
132 | return Matrix, c_widget
133 |
134 |
135 | @app.cell
136 | def _(c_widget, np):
137 | c = np.array(c_widget.value["matrix"][0])
138 | return (c,)
139 |
140 |
141 | @app.cell
142 | def _(A, b, c, cp):
143 | x = cp.Variable(A.shape[1])
144 | prob = cp.Problem(cp.Minimize(c.T @ x), [A @ x <= b])
145 | _ = prob.solve()
146 | x_star = x.value
147 | return prob, x, x_star
148 |
149 |
150 | @app.cell(hide_code=True)
151 | def _(mo):
152 | mo.md(r"""Below, we plot the feasible region of the problem — the intersection of the inequalities — and the level curves of the objective function. The optimal value $x^\star$ is the point farthest in the feasible region in the direction $-c$.""")
153 | return
154 |
155 |
156 | @app.cell(hide_code=True)
157 | def _(A, b, c, make_plot, x_star):
158 | make_plot(A, b, c, x_star)
159 | return
160 |
161 |
162 | @app.cell(hide_code=True)
163 | def _(np):
164 | import matplotlib.pyplot as plt
165 |
166 |
167 | def make_plot(A, b, c, x_star):
168 | # Define a grid over a region that covers the feasible set.
169 | # You might need to adjust these limits.
170 | x_vals = np.linspace(-1, 1, 400)
171 | y_vals = np.linspace(1, 3, 400)
172 | X, Y = np.meshgrid(x_vals, y_vals)
173 |
174 | # Flatten the grid points into an (N,2) array.
175 | points = np.vstack([X.ravel(), Y.ravel()]).T
176 |
177 | # For each point, check if it satisfies all the constraints: A @ x <= b.
178 | # A dot product: shape of A @ x.T will be (m, N). We add a little tolerance.
179 | feasible = np.all(np.dot(A, points.T) <= (b[:, None] + 1e-8), axis=0)
180 | feasible = feasible.reshape(X.shape)
181 |
182 | # Create the figure with a white background.
183 | fig = plt.figure(figsize=(8, 6), facecolor="white")
184 | ax = fig.add_subplot(111)
185 | ax.set_facecolor("white")
186 |
187 | # Plot the feasible region.
188 | # Since "feasible" is a boolean array (False=0, True=1), we set contour levels so that
189 | # the region with value 1 (feasible) gets filled.
190 | ax.contourf(
191 | X,
192 | Y,
193 | feasible.astype(float),
194 | levels=[-0.5, 0.5, 1.5],
195 | colors=["white", "gray"],
196 | alpha=0.5,
197 | )
198 |
199 | # Plot level curves of the objective function c^T x.
200 | # Compute c^T x over the grid:
201 | Z = c[0] * X + c[1] * Y
202 | # Choose several levels for the iso-cost lines.
203 | levels = np.linspace(np.min(Z), np.max(Z), 20)
204 | contours = ax.contour(
205 | X, Y, Z, levels=levels, colors="gray", linestyles="--", linewidths=1
206 | )
207 |
208 | # Draw the vector -c as an arrow starting at x_star.
209 | norm_c = np.linalg.norm(c)
210 | if norm_c > 0:
211 | head_width = norm_c * 0.1
212 | head_length = norm_c * 0.1
213 | # The arrow starts at x_star and points in the -c direction.
214 | ax.arrow(
215 | x_star[0],
216 | x_star[1],
217 | -c[0],
218 | -c[1],
219 | head_width=head_width,
220 | head_length=head_length,
221 | fc="black",
222 | ec="black",
223 | length_includes_head=True,
224 | )
225 | # Label the arrow near its tip.
226 | ax.text(
227 | x_star[0] - c[0] * 1.05,
228 | x_star[1] - c[1] * 1.05,
229 | r"$-c$",
230 | color="black",
231 | fontsize=12,
232 | )
233 |
234 | # Optionally, mark and label the point x_star.
235 | ax.plot(x_star[0], x_star[1], "ko", markersize=5)
236 | ax.text(
237 | x_star[0],
238 | x_star[1],
239 | r"$\mathbf{x}^\star$",
240 | color="black",
241 | fontsize=12,
242 | verticalalignment="bottom",
243 | horizontalalignment="right",
244 | )
245 | # Label the axes and set title.
246 | ax.set_xlabel("$x_1$")
247 | ax.set_ylabel("$x_2$")
248 | ax.set_title("Feasible Region and Level Curves of $c^Tx$")
249 | ax.set_xlim(np.min(x_vals), np.max(x_vals))
250 | ax.set_ylim(np.min(y_vals), np.max(y_vals))
251 | return ax
252 | return make_plot, plt
253 |
254 |
255 | @app.cell(hide_code=True)
256 | def _(mo, prob, x):
257 | mo.md(
258 | f"""
259 | The optimal value is {prob.value:.04f}.
260 |
261 | A solution $x$ is {mo.as_html(list(x.value))}
262 | A dual solution is is {mo.as_html(list(prob.constraints[0].dual_value))}
263 | """
264 | )
265 | return
266 |
267 |
268 | if __name__ == "__main__":
269 | app.run()
270 |
--------------------------------------------------------------------------------
/optimization/03_minimum_fuel_optimal_control.py:
--------------------------------------------------------------------------------
1 | import marimo
2 |
3 | __generated_with = "0.11.0"
4 | app = marimo.App()
5 |
6 |
7 | @app.cell
8 | def _():
9 | import marimo as mo
10 | return (mo,)
11 |
12 |
13 | @app.cell(hide_code=True)
14 | def _(mo):
15 | mo.md(
16 | r"""
17 | # Minimal fuel optimal control
18 |
19 | This notebook includes an application of linear programming to controlling a
20 | physical system, adapted from [Convex
21 | Optimization](https://web.stanford.edu/~boyd/cvxbook/) by Boyd and Vandenberghe.
22 |
23 | We consider a linear dynamical system with state $x(t) \in \mathbf{R}^n$, for $t = 0, \ldots, T$. At each time step $t = 0, \ldots, T - 1$, an actuator or input signal $u(t)$ is applied, affecting the state. The dynamics
24 | of the system is given by the linear recurrence
25 |
26 | \[
27 | x(t + 1) = Ax(t) + bu(t), \quad t = 0, \ldots, T - 1,
28 | \]
29 |
30 | where $A \in \mathbf{R}^{n \times n}$ and $b \in \mathbf{R}^n$ are given and encode how the system evolves. The initial state $x(0)$ is also given.
31 |
32 | The _minimum fuel optimal control problem_ is to choose the inputs $u(0), \ldots, u(T - 1)$ so as to achieve
33 | a given desired state $x_\text{des} = x(T)$ while minimizing the total fuel consumed
34 |
35 | \[
36 | F = \sum_{t=0}^{T - 1} f(u(t)).
37 | \]
38 |
39 | The function $f : \mathbf{R} \to \mathbf{R}$ tells us how much fuel is consumed as a function of the input, and is given by
40 |
41 | \[
42 | f(a) = \begin{cases}
43 | |a| & |a| \leq 1 \\
44 | 2|a| - 1 & |a| > 1.
45 | \end{cases}
46 | \]
47 |
48 | This means the fuel use is proportional to the magnitude of the signal between $-1$ and $1$, but for larger signals the marginal fuel efficiency is half.
49 |
50 | **This notebook.** In this notebook we use CVXPY to formulate the minimum fuel optimal control problem as a linear program. The notebook lets you play with the initial and target states, letting you see how they affect the planned trajectory of inputs $u$.
51 |
52 | First, we create the **problem data**.
53 | """
54 | )
55 | return
56 |
57 |
58 | @app.cell
59 | def _():
60 | import numpy as np
61 | return (np,)
62 |
63 |
64 | @app.cell
65 | def _():
66 | n, T = 3, 30
67 | return T, n
68 |
69 |
70 | @app.cell
71 | def _(np):
72 | A = np.array([[-1, 0.4, 0.8], [1, 0, 0], [0, 1, 0]])
73 | b = np.array([[1, 0, 0.3]]).T
74 | return A, b
75 |
76 |
77 | @app.cell(hide_code=True)
78 | def _(mo, n, np):
79 | import wigglystuff
80 |
81 | x0_widget = mo.ui.anywidget(wigglystuff.Matrix(np.zeros((1, n))))
82 | xdes_widget = mo.ui.anywidget(wigglystuff.Matrix(np.array([[7, 2, -6]])))
83 |
84 | _a = mo.md(
85 | rf"""
86 |
87 | Choose a value for $x_0$ ...
88 |
89 | {x0_widget}
90 | """
91 | )
92 |
93 | _b = mo.md(
94 | rf"""
95 | ... and for $x_\text{{des}}$
96 |
97 | {xdes_widget}
98 | """
99 | )
100 |
101 | mo.hstack([_a, _b], justify="space-around")
102 | return wigglystuff, x0_widget, xdes_widget
103 |
104 |
105 | @app.cell
106 | def _(x0_widget, xdes_widget):
107 | x0 = x0_widget.matrix
108 | xdes = xdes_widget.matrix
109 | return x0, xdes
110 |
111 |
112 | @app.cell(hide_code=True)
113 | def _(mo):
114 | mo.md(r"""**Next, we specify the problem as a linear program using CVXPY.** This problem is linear because the objective and constraints are affine. (In fact, the objective is piecewise affine, but CVXPY rewrites it to be affine for you.)""")
115 | return
116 |
117 |
118 | @app.cell
119 | def _():
120 | import cvxpy as cp
121 | return (cp,)
122 |
123 |
124 | @app.cell
125 | def _(A, T, b, cp, mo, n, x0, xdes):
126 | X, u = cp.Variable(shape=(n, T + 1)), cp.Variable(shape=(1, T))
127 |
128 | objective = cp.sum(cp.maximum(cp.abs(u), 2 * cp.abs(u) - 1))
129 | constraints = [
130 | X[:, 1:] == A @ X[:, :-1] + b @ u,
131 | X[:, 0] == x0,
132 | X[:, -1] == xdes,
133 | ]
134 |
135 | fuel_used = cp.Problem(cp.Minimize(objective), constraints).solve()
136 | mo.md(f"Achieved a fuel usage of {fuel_used:.02f}. 🚀")
137 | return X, constraints, fuel_used, objective, u
138 |
139 |
140 | @app.cell(hide_code=True)
141 | def _(mo):
142 | mo.md(
143 | """
144 | Finally, we plot the chosen inputs over time.
145 |
146 | **🌊 Try it!** Change the initial and desired states; how do fuel usage and controls change? Can you explain what you see? You can also try experimenting with the value of $T$.
147 | """
148 | )
149 | return
150 |
151 |
152 | @app.cell
153 | def _(plot_solution, u):
154 | plot_solution(u)
155 | return
156 |
157 |
158 | @app.cell
159 | def _(T, cp, np):
160 | def plot_solution(u: cp.Variable):
161 | import matplotlib.pyplot as plt
162 |
163 | plt.step(np.arange(T), u.T.value)
164 | plt.axis("tight")
165 | plt.xlabel("$t$")
166 | plt.ylabel("$u$")
167 | return plt.gca()
168 | return (plot_solution,)
169 |
170 |
171 | if __name__ == "__main__":
172 | app.run()
173 |
--------------------------------------------------------------------------------
/optimization/04_quadratic_program.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.13"
3 | # dependencies = [
4 | # "cvxpy==1.6.0",
5 | # "marimo",
6 | # "matplotlib==3.10.0",
7 | # "numpy==2.2.2",
8 | # "wigglystuff==0.1.9",
9 | # ]
10 | # ///
11 |
12 | import marimo
13 |
14 | __generated_with = "0.11.0"
15 | app = marimo.App()
16 |
17 |
18 | @app.cell
19 | def _():
20 | import marimo as mo
21 | return (mo,)
22 |
23 |
24 | @app.cell(hide_code=True)
25 | def _(mo):
26 | mo.md(
27 | r"""
28 | # Quadratic program
29 |
30 | A quadratic program is an optimization problem with a quadratic objective and
31 | affine equality and inequality constraints. A common standard form is the
32 | following:
33 |
34 | \[
35 | \begin{array}{ll}
36 | \text{minimize} & (1/2)x^TPx + q^Tx\\
37 | \text{subject to} & Gx \leq h \\
38 | & Ax = b.
39 | \end{array}
40 | \]
41 |
42 | Here $P \in \mathcal{S}^{n}_+$, $q \in \mathcal{R}^n$, $G \in \mathcal{R}^{m \times n}$, $h \in \mathcal{R}^m$, $A \in \mathcal{R}^{p \times n}$, and $b \in \mathcal{R}^p$ are problem data and $x \in \mathcal{R}^{n}$ is the optimization variable. The inequality constraint $Gx \leq h$ is elementwise.
43 |
44 | **Why quadratic programming?** Quadratic programs are convex optimization problems that generalize both least-squares and linear programming.They can be solved efficiently and reliably, even in real-time.
45 |
46 | **An example from finance.** A simple example of a quadratic program arises in finance. Suppose we have $n$ different stocks, an estimate $r \in \mathcal{R}^n$ of the expected return on each stock, and an estimate $\Sigma \in \mathcal{S}^{n}_+$ of the covariance of the returns. Then we solve the optimization problem
47 |
48 | \[
49 | \begin{array}{ll}
50 | \text{minimize} & (1/2)x^T\Sigma x - r^Tx\\
51 | \text{subject to} & x \geq 0 \\
52 | & \mathbf{1}^Tx = 1,
53 | \end{array}
54 | \]
55 |
56 | to find a nonnegative portfolio allocation $x \in \mathcal{R}^n_+$ that optimally balances expected return and variance of return.
57 |
58 | When we solve a quadratic program, in addition to a solution $x^\star$, we obtain a dual solution $\lambda^\star$ corresponding to the inequality constraints. A positive entry $\lambda^\star_i$ indicates that the constraint $g_i^Tx \leq h_i$ holds with equality for $x^\star$ and suggests that changing $h_i$ would change the optimal value.
59 | """
60 | )
61 | return
62 |
63 |
64 | @app.cell(hide_code=True)
65 | def _(mo):
66 | mo.md(
67 | r"""
68 | ## Example
69 |
70 | In this example, we use CVXPY to construct and solve a quadratic program.
71 | """
72 | )
73 | return
74 |
75 |
76 | @app.cell
77 | def _():
78 | import cvxpy as cp
79 | import numpy as np
80 | return cp, np
81 |
82 |
83 | @app.cell(hide_code=True)
84 | def _(mo):
85 | mo.md("""First we generate synthetic data. In this problem, we don't include equality constraints, only inequality.""")
86 | return
87 |
88 |
89 | @app.cell
90 | def _(np):
91 | m = 4
92 | n = 2
93 |
94 | np.random.seed(1)
95 | q = np.random.randn(n)
96 | G = np.random.randn(m, n)
97 | h = G @ np.random.randn(n)
98 | return G, h, m, n, q
99 |
100 |
101 | @app.cell(hide_code=True)
102 | def _(mo, np):
103 | import wigglystuff
104 |
105 | P_widget = mo.ui.anywidget(
106 | wigglystuff.Matrix(np.array([[4.0, -1.4], [-1.4, 4]]), step=0.1)
107 | )
108 |
109 | mo.md(
110 | f"""
111 | The quadratic form $P$ is equal to the symmetrized version of this
112 | matrix:
113 |
114 | {P_widget.center()}
115 | """
116 | )
117 | return P_widget, wigglystuff
118 |
119 |
120 | @app.cell
121 | def _(P_widget, np):
122 | P = 0.5 * (np.array(P_widget.matrix) + np.array(P_widget.matrix).T)
123 | return (P,)
124 |
125 |
126 | @app.cell(hide_code=True)
127 | def _(mo):
128 | mo.md(r"""Next, we specify the problem. Notice that we use the `quad_form` function from CVXPY to create the quadratic form $x^TPx$.""")
129 | return
130 |
131 |
132 | @app.cell
133 | def _(G, P, cp, h, n, q):
134 | x = cp.Variable(n)
135 |
136 | problem = cp.Problem(
137 | cp.Minimize((1 / 2) * cp.quad_form(x, P) + q.T @ x),
138 | [G @ x <= h],
139 | )
140 | _ = problem.solve()
141 | return problem, x
142 |
143 |
144 | @app.cell(hide_code=True)
145 | def _(mo, problem, x):
146 | mo.md(
147 | f"""
148 | The optimal value is {problem.value:.04f}.
149 |
150 | A solution $x$ is {mo.as_html(list(x.value))}
151 | A dual solution is is {mo.as_html(list(problem.constraints[0].dual_value))}
152 | """
153 | )
154 | return
155 |
156 |
157 | @app.cell
158 | def _(G, P, h, plot_contours, q, x):
159 | plot_contours(P, G, h, q, x.value)
160 | return
161 |
162 |
163 | @app.cell(hide_code=True)
164 | def _(mo):
165 | mo.md(
166 | r"""
167 | In this plot, the gray shaded region is the feasible region (points satisfying the inequality), and the ellipses are level curves of the quadratic form.
168 |
169 | **🌊 Try it!** Try changing the entries of $P$ above with your mouse. How do the
170 | level curves and the optimal value of $x$ change? Can you explain what you see?
171 | """
172 | )
173 | return
174 |
175 |
176 | @app.cell(hide_code=True)
177 | def _(P, mo):
178 | mo.md(
179 | rf"""
180 | The above contour lines were generated with
181 |
182 | \[
183 | P= \begin{{bmatrix}}
184 | {P[0, 0]:.01f} & {P[0, 1]:.01f} \\
185 | {P[1, 0]:.01f} & {P[1, 1]:.01f} \\
186 | \end{{bmatrix}}
187 | \]
188 | """
189 | )
190 | return
191 |
192 |
193 | @app.cell(hide_code=True)
194 | def _(np):
195 | def plot_contours(P, G, h, q, x_star):
196 | import matplotlib.pyplot as plt
197 |
198 | # Create a grid of x and y values.
199 | x = np.linspace(-5, 5, 400)
200 | y = np.linspace(-5, 5, 400)
201 | X, Y = np.meshgrid(x, y)
202 |
203 | # Compute the quadratic form Q(x, y) = a*x^2 + 2*b*x*y + c*y^2.
204 | # Here, a = P[0,0], b = P[0,1] (and P[1,0]), c = P[1,1]
205 | Z = (
206 | 0.5 * (P[0, 0] * X**2 + 2 * P[0, 1] * X * Y + P[1, 1] * Y**2)
207 | + q[0] * X
208 | + q[1] * Y
209 | )
210 |
211 | # --- Evaluate the constraints on the grid ---
212 | # We stack X and Y to get a list of (x,y) points.
213 | points = np.vstack([X.ravel(), Y.ravel()]).T
214 |
215 | # Start with all points feasible
216 | feasible = np.ones(points.shape[0], dtype=bool)
217 |
218 | # Apply the inequality constraints Gx <= h.
219 | # Each row of G and corresponding h defines a condition.
220 | for i in range(G.shape[0]):
221 | # For a given point x, the condition is: G[i,0]*x + G[i,1]*y <= h[i]
222 | feasible &= points.dot(G[i]) <= h[i] + 1e-8 # small tolerance
223 | # Reshape the boolean mask back to grid shape.
224 | feasible_grid = feasible.reshape(X.shape)
225 |
226 | # --- Plot the feasible region and contour lines---
227 | plt.figure(figsize=(8, 6))
228 |
229 | # Use contourf to fill the region where feasible_grid is True.
230 | # We define two levels, so that points that are True (feasible) get one
231 | # color.
232 | plt.contourf(
233 | X,
234 | Y,
235 | feasible_grid,
236 | levels=[-0.5, 0.5, 1.5],
237 | colors=["white", "gray"],
238 | alpha=0.5,
239 | )
240 |
241 | contours = plt.contour(X, Y, Z, levels=10, cmap="viridis")
242 | plt.clabel(contours, inline=True, fontsize=8)
243 | plt.title("Feasible region and level curves")
244 | plt.xlabel("$x_1$")
245 | plt.ylabel("$y_2$")
246 | # plt.colorbar(contours, label='Q(x, y)')
247 |
248 | ax = plt.gca()
249 | # Optionally, mark and label the point x_star.
250 | ax.plot(x_star[0], x_star[1], "ko", markersize=5)
251 | ax.text(
252 | x_star[0],
253 | x_star[1],
254 | r"$\mathbf{x}^\star$",
255 | color="black",
256 | fontsize=12,
257 | verticalalignment="bottom",
258 | horizontalalignment="right",
259 | )
260 | return plt.gca()
261 | return (plot_contours,)
262 |
263 |
264 | if __name__ == "__main__":
265 | app.run()
266 |
--------------------------------------------------------------------------------
/optimization/05_portfolio_optimization.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.13"
3 | # dependencies = [
4 | # "cvxpy==1.6.0",
5 | # "marimo",
6 | # "matplotlib==3.10.0",
7 | # "numpy==2.2.2",
8 | # "scipy==1.15.1",
9 | # "wigglystuff==0.1.9",
10 | # ]
11 | # ///
12 |
13 | import marimo
14 |
15 | __generated_with = "0.11.2"
16 | app = marimo.App()
17 |
18 |
19 | @app.cell
20 | def _():
21 | import marimo as mo
22 | return (mo,)
23 |
24 |
25 | @app.cell(hide_code=True)
26 | def _(mo):
27 | mo.md(r"""# Portfolio optimization""")
28 | return
29 |
30 |
31 | @app.cell(hide_code=True)
32 | def _(mo):
33 | mo.md(
34 | r"""
35 | In this example we show how to use CVXPY to design a financial portfolio; this is called _portfolio optimization_.
36 |
37 | In portfolio optimization we have some amount of money to invest in any of $n$ different assets.
38 | We choose what fraction $w_i$ of our money to invest in each asset $i$, $i=1, \ldots, n$. The goal is to maximize return of the portfolio while minimizing risk.
39 | """
40 | )
41 | return
42 |
43 |
44 | @app.cell(hide_code=True)
45 | def _(mo):
46 | mo.md(
47 | r"""
48 | ## Asset returns and risk
49 |
50 | We will only model investments held for one period. The initial prices are $p_i > 0$. The end of period prices are $p_i^+ >0$. The asset (fractional) returns are $r_i = (p_i^+-p_i)/p_i$. The portfolio (fractional) return is $R = r^Tw$.
51 |
52 | A common model is that $r$ is a random variable with mean ${\bf E}r = \mu$ and covariance ${\bf E{(r-\mu)(r-\mu)^T}} = \Sigma$.
53 | It follows that $R$ is a random variable with ${\bf E}R = \mu^T w$ and ${\bf var}(R) = w^T\Sigma w$. In real-world applications, $\mu$ and $\Sigma$ are estimated from data and models, and $w$ is chosen using a library like CVXPY.
54 |
55 | ${\bf E}R$ is the (mean) *return* of the portfolio. ${\bf var}(R)$ is the *risk* of the portfolio. Portfolio optimization has two competing objectives: high return and low risk.
56 | """
57 | )
58 | return
59 |
60 |
61 | @app.cell(hide_code=True)
62 | def _(mo):
63 | mo.md(
64 | r"""
65 | ## Classical (Markowitz) portfolio optimization
66 |
67 | Classical (Markowitz) portfolio optimization solves the optimization problem
68 | """
69 | )
70 | return
71 |
72 |
73 | @app.cell(hide_code=True)
74 | def _(mo):
75 | mo.md(
76 | r"""
77 | $$
78 | \begin{array}{ll} \text{maximize} & \mu^T w - \gamma w^T\Sigma w\\
79 | \text{subject to} & {\bf 1}^T w = 1, w \geq 0,
80 | \end{array}
81 | $$
82 | """
83 | )
84 | return
85 |
86 |
87 | @app.cell(hide_code=True)
88 | def _(mo):
89 | mo.md(
90 | r"""
91 | where $w \in {\bf R}^n$ is the optimization variable and $\gamma >0$ is a constant called the *risk aversion parameter*. The constraint $\mathbf{1}^Tw = 1$ says the portfolio weight vector must sum to 1, and $w \geq 0$ says that we can't invest a negative amount into any asset.
92 |
93 | The objective $\mu^Tw - \gamma w^T\Sigma w$ is the *risk-adjusted return*. Varying $\gamma$ gives the optimal *risk-return trade-off*.
94 | We can get the same risk-return trade-off by fixing return and minimizing risk.
95 | """
96 | )
97 | return
98 |
99 |
100 | @app.cell(hide_code=True)
101 | def _(mo):
102 | mo.md(
103 | r"""
104 | ## Example
105 |
106 | In the following code we compute and plot the optimal risk-return trade-off for $10$ assets. First we generate random problem data $\mu$ and $\Sigma$.
107 | """
108 | )
109 | return
110 |
111 |
112 | @app.cell
113 | def _():
114 | import numpy as np
115 | return (np,)
116 |
117 |
118 | @app.cell(hide_code=True)
119 | def _(mo, np):
120 | import wigglystuff
121 |
122 | mu_widget = mo.ui.anywidget(
123 | wigglystuff.Matrix(
124 | np.array(
125 | [
126 | [1.6],
127 | [0.6],
128 | [0.5],
129 | [1.1],
130 | [0.9],
131 | [2.3],
132 | [1.7],
133 | [0.7],
134 | [0.9],
135 | [0.3],
136 | ]
137 | )
138 | )
139 | )
140 |
141 |
142 | mo.md(
143 | rf"""
144 | The value of $\mu$ is
145 |
146 | {mu_widget.center()}
147 |
148 | _Try changing the entries of $\mu$ and see how the plots below change._
149 | """
150 | )
151 | return mu_widget, wigglystuff
152 |
153 |
154 | @app.cell
155 | def _(mu_widget, np):
156 | np.random.seed(1)
157 | n = 10
158 | mu = np.array(mu_widget.matrix)
159 | Sigma = np.random.randn(n, n)
160 | Sigma = Sigma.T.dot(Sigma)
161 | return Sigma, mu, n
162 |
163 |
164 | @app.cell(hide_code=True)
165 | def _(mo):
166 | mo.md("""Next, we solve the problem for 100 different values of $\gamma$""")
167 | return
168 |
169 |
170 | @app.cell
171 | def _(Sigma, mu, n):
172 | import cvxpy as cp
173 |
174 | w = cp.Variable(n)
175 | gamma = cp.Parameter(nonneg=True)
176 | ret = mu.T @ w
177 | risk = cp.quad_form(w, Sigma)
178 | prob = cp.Problem(cp.Maximize(ret - gamma * risk), [cp.sum(w) == 1, w >= 0])
179 | return cp, gamma, prob, ret, risk, w
180 |
181 |
182 | @app.cell
183 | def _(cp, gamma, np, prob, ret, risk):
184 | _SAMPLES = 100
185 | risk_data = np.zeros(_SAMPLES)
186 | ret_data = np.zeros(_SAMPLES)
187 | gamma_vals = np.logspace(-2, 3, num=_SAMPLES)
188 | for _i in range(_SAMPLES):
189 | gamma.value = gamma_vals[_i]
190 | prob.solve()
191 | risk_data[_i] = cp.sqrt(risk).value
192 | ret_data[_i] = ret.value
193 | return gamma_vals, ret_data, risk_data
194 |
195 |
196 | @app.cell(hide_code=True)
197 | def _(mo):
198 | mo.md("""Plotted below are the risk return tradeoffs for two values of $\gamma$ (blue squares), and the risk return tradeoffs for investing fully in each asset (red circles)""")
199 | return
200 |
201 |
202 | @app.cell(hide_code=True)
203 | def _(Sigma, cp, gamma_vals, mu, n, ret_data, risk_data):
204 | import matplotlib.pyplot as plt
205 |
206 | markers_on = [29, 40]
207 | fig = plt.figure()
208 | ax = fig.add_subplot(111)
209 | plt.plot(risk_data, ret_data, "g-")
210 | for marker in markers_on:
211 | plt.plot(risk_data[marker], ret_data[marker], "bs")
212 | ax.annotate(
213 | "$\\gamma = %.2f$" % gamma_vals[marker],
214 | xy=(risk_data[marker] + 0.08, ret_data[marker] - 0.03),
215 | )
216 | for _i in range(n):
217 | plt.plot(cp.sqrt(Sigma[_i, _i]).value, mu[_i], "ro")
218 | plt.xlabel("Standard deviation")
219 | plt.ylabel("Return")
220 | plt.show()
221 | return ax, fig, marker, markers_on, plt
222 |
223 |
224 | @app.cell(hide_code=True)
225 | def _(mo):
226 | mo.md(
227 | r"""
228 | We plot below the return distributions for the two risk aversion values marked on the trade-off curve.
229 | Notice that the probability of a loss is near 0 for the low risk value and far above 0 for the high risk value.
230 | """
231 | )
232 | return
233 |
234 |
235 | @app.cell(hide_code=True)
236 | def _(gamma, gamma_vals, markers_on, np, plt, prob, ret, risk):
237 | import scipy.stats as spstats
238 |
239 | plt.figure()
240 | for midx, _idx in enumerate(markers_on):
241 | gamma.value = gamma_vals[_idx]
242 | prob.solve()
243 | x = np.linspace(-2, 5, 1000)
244 | plt.plot(
245 | x,
246 | spstats.norm.pdf(x, ret.value, risk.value),
247 | label="$\\gamma = %.2f$" % gamma.value,
248 | )
249 | plt.xlabel("Return")
250 | plt.ylabel("Density")
251 | plt.legend(loc="upper right")
252 | plt.show()
253 | return midx, spstats, x
254 |
255 |
256 | if __name__ == "__main__":
257 | app.run()
258 |
--------------------------------------------------------------------------------
/optimization/06_convex_optimization.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.13"
3 | # dependencies = [
4 | # "cvxpy==1.6.0",
5 | # "marimo",
6 | # "numpy==2.2.2",
7 | # ]
8 | # ///
9 |
10 | import marimo
11 |
12 | __generated_with = "0.11.2"
13 | app = marimo.App()
14 |
15 |
16 | @app.cell
17 | def _():
18 | import marimo as mo
19 | return (mo,)
20 |
21 |
22 | @app.cell(hide_code=True)
23 | def _(mo):
24 | mo.md(
25 | r"""
26 | # Convex optimization
27 |
28 | In the previous tutorials, we learned about least squares, linear programming,
29 | and quadratic programming, and saw applications of each. We also learned that these problem
30 | classes can be solved efficiently and reliably using CVXPY. That's because these problem classes are a special
31 | case of a more general class of tractable problems, called **convex optimization problems.**
32 |
33 | A convex optimization problem is an optimization problem that minimizes a convex
34 | function, subject to affine equality constraints and convex inequality
35 | constraints ($f_i(x)\leq 0$, where $f_i$ is a convex function).
36 |
37 | **CVXPY.** CVXPY lets you specify and solve any convex optimization problem,
38 | abstracting away the more specific problem classes. You start with CVXPY's **atomic functions**, like `cp.exp`, `cp.log`, and `cp.square`, and compose them to build more complex convex functions. As long as the functions are composed in the right way — as long as they are "DCP-compliant" — your resulting problem will be convex and solvable by CVXPY.
39 | """
40 | )
41 | return
42 |
43 |
44 | @app.cell(hide_code=True)
45 | def _(mo):
46 | mo.md(
47 | r"""
48 | **🛑 Stop!** Before proceeding, read the CVXPY docs to learn about atomic functions and the DCP ruleset:
49 |
50 | https://www.cvxpy.org/tutorial/index.html
51 | """
52 | )
53 | return
54 |
55 |
56 | @app.cell(hide_code=True)
57 | def _(mo):
58 | mo.md(r"""**Is my problem DCP-compliant?** Below is a sample CVXPY problem. It is DCP-compliant. Try typing in other problems and seeing if they are DCP-compliant. If you know your problem is convex, there exists a way to express it in a DCP-compliant way.""")
59 | return
60 |
61 |
62 | @app.cell
63 | def _(mo):
64 | import cvxpy as cp
65 | import numpy as np
66 |
67 | x = cp.Variable(3)
68 | P_sqrt = np.random.randn(3, 3)
69 |
70 | objective = cp.log(np.random.randn(3) @ x) - cp.sum_squares(P_sqrt @ x)
71 | constraints = [x >= 0, cp.sum(x) == 1]
72 | problem = cp.Problem(cp.Maximize(objective), constraints)
73 | mo.md(f"Is my problem DCP? `{problem.is_dcp()}`")
74 | return P_sqrt, constraints, cp, np, objective, problem, x
75 |
76 |
77 | @app.cell
78 | def _(problem):
79 | problem.solve()
80 | return
81 |
82 |
83 | @app.cell
84 | def _(x):
85 | x.value
86 | return
87 |
88 |
89 | if __name__ == "__main__":
90 | app.run()
91 |
--------------------------------------------------------------------------------
/optimization/07_sdp.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.13"
3 | # dependencies = [
4 | # "cvxpy==1.6.0",
5 | # "marimo",
6 | # "numpy==2.2.2",
7 | # "wigglystuff==0.1.9",
8 | # ]
9 | # ///
10 |
11 | import marimo
12 |
13 | __generated_with = "0.11.2"
14 | app = marimo.App()
15 |
16 |
17 | @app.cell
18 | def _():
19 | import marimo as mo
20 | return (mo,)
21 |
22 |
23 | @app.cell(hide_code=True)
24 | def _(mo):
25 | mo.md(r"""# Semidefinite program""")
26 | return
27 |
28 |
29 | @app.cell(hide_code=True)
30 | def _(mo):
31 | mo.md(
32 | r"""
33 | _This notebook introduces an advanced topic._ A semidefinite program (SDP) is an optimization problem of the form
34 |
35 | \[
36 | \begin{array}{ll}
37 | \text{minimize} & \mathbf{tr}(CX) \\
38 | \text{subject to} & \mathbf{tr}(A_iX) = b_i, \quad i=1,\ldots,p \\
39 | & X \succeq 0,
40 | \end{array}
41 | \]
42 |
43 | where $\mathbf{tr}$ is the trace function, $X \in \mathcal{S}^{n}$ is the optimization variable and $C, A_1, \ldots, A_p \in \mathcal{S}^{n}$, and $b_1, \ldots, b_p \in \mathcal{R}$ are problem data, and $X \succeq 0$ is a matrix inequality. Here $\mathcal{S}^{n}$ denotes the set of $n$-by-$n$ symmetric matrices.
44 |
45 | **Example.** An example of an SDP is to complete a covariance matrix $\tilde \Sigma \in \mathcal{S}^{n}_+$ with missing entries $M \subset \{1,\ldots,n\} \times \{1,\ldots,n\}$:
46 |
47 | \[
48 | \begin{array}{ll}
49 | \text{minimize} & 0 \\
50 | \text{subject to} & \Sigma_{ij} = \tilde \Sigma_{ij}, \quad (i,j) \notin M \\
51 | & \Sigma \succeq 0,
52 | \end{array}
53 | \]
54 | """
55 | )
56 | return
57 |
58 |
59 | @app.cell(hide_code=True)
60 | def _(mo):
61 | mo.md(
62 | r"""
63 | ## Example
64 |
65 | In the following code, we show how to specify and solve an SDP with CVXPY.
66 | """
67 | )
68 | return
69 |
70 |
71 | @app.cell
72 | def _():
73 | import cvxpy as cp
74 | import numpy as np
75 | return cp, np
76 |
77 |
78 | @app.cell
79 | def _(np):
80 | # Generate a random SDP.
81 | n = 3
82 | p = 3
83 | np.random.seed(1)
84 | C = np.random.randn(n, n)
85 | A = []
86 | b = []
87 | for i in range(p):
88 | A.append(np.random.randn(n, n))
89 | b.append(np.random.randn())
90 | return A, C, b, i, n, p
91 |
92 |
93 | @app.cell
94 | def _(A, C, b, cp, n, p):
95 | # Create a symmetric matrix variable.
96 | X = cp.Variable((n, n), symmetric=True)
97 |
98 | # The operator >> denotes matrix inequality, with X >> 0 constraining X
99 | # to be positive semidefinite
100 | constraints = [X >> 0]
101 | constraints += [cp.trace(A[i] @ X) == b[i] for i in range(p)]
102 | prob = cp.Problem(cp.Minimize(cp.trace(C @ X)), constraints)
103 | _ = prob.solve()
104 | return X, constraints, prob
105 |
106 |
107 | @app.cell
108 | def _(X, mo, prob, wigglystuff):
109 | mo.md(
110 | f"""
111 | The optimal value is {prob.value:0.4f}.
112 |
113 | A solution for $X$ is (rounded to the nearest decimal) is:
114 |
115 | {mo.ui.anywidget(wigglystuff.Matrix(X.value)).center()}
116 | """
117 | )
118 | return
119 |
120 |
121 | @app.cell
122 | def _():
123 | import wigglystuff
124 | return (wigglystuff,)
125 |
126 |
127 | if __name__ == "__main__":
128 | app.run()
129 |
--------------------------------------------------------------------------------
/optimization/README.md:
--------------------------------------------------------------------------------
1 | # Learn optimization
2 |
3 | This collection of marimo notebooks teaches you the basics of convex
4 | optimization.
5 |
6 | After working through these notebooks, you'll understand how to create
7 | and solve optimization problems using the Python library
8 | [CVXPY](https://github.com/cvxpy/cvxpy), as well as how to apply what you've
9 | learned to real-world problems such as portfolio allocation in finance,
10 | control of vehicles, and more.
11 |
12 | 
13 |
14 | _SpaceX solves convex optimization problems onboard to land its rockets, using CVXGEN, a code generator for quadratic programming developed at Stephen Boyd’s Stanford lab. Photo by SpaceX, licensed CC BY-NC 2.0._
15 |
16 | **Running notebooks.** To run a notebook locally, use
17 |
18 | ```bash
19 | uvx marimo edit
20 | ```
21 |
22 | For example, run the least-squares tutorial with
23 |
24 | ```bash
25 | uvx marimo edit https://github.com/marimo-team/learn/blob/main/optimization/01_least_squares.py
26 | ```
27 |
28 | You can also open notebooks in our online playground by appending `marimo.app/`
29 | to a notebook's URL: [marimo.app/github.com/marimo-team/learn/blob/main/optimization/01_least_squares.py](https://marimo.app/https://github.com/marimo-team/learn/blob/main/optimization/01_least_squares.py).
30 |
31 | **Thanks to all our notebook authors!**
32 |
33 | * [Akshay Agrawal](https://github.com/akshayka)
34 |
--------------------------------------------------------------------------------
/polars/09_data_types.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.11"
3 | # dependencies = [
4 | # "polars==1.18.0",
5 | # "marimo",
6 | # ]
7 | # ///
8 |
9 | import marimo
10 |
11 | __generated_with = "0.12.0"
12 | app = marimo.App(width="medium")
13 |
14 |
15 | @app.cell(hide_code=True)
16 | def _(mo):
17 | mo.md(
18 | r"""
19 | # Data Types
20 |
21 | Author: [Deb Debnath](https://github.com/debajyotid2)
22 |
23 | **Note**: The following tutorial has been adapted from the Polars [documentation](https://docs.pola.rs/user-guide/concepts/data-types-and-structures/).
24 | """
25 | )
26 | return
27 |
28 |
29 | @app.cell(hide_code=True)
30 | def _(mo):
31 | mo.md(
32 | r"""
33 | Polars supports a variety of data types that fall broadly under the following categories:
34 |
35 | - Numeric data types: integers and floating point numbers.
36 | - Nested data types: lists, structs, and arrays.
37 | - Temporal: dates, datetimes, times, and time deltas.
38 | - Miscellaneous: strings, binary data, Booleans, categoricals, enums, and objects.
39 |
40 | All types support missing values represented by `null` which is different from `NaN` used in floating point data types. The numeric datatypes in Polars loosely follow the type system of the Rust language, since its core functionalities are built in Rust.
41 |
42 | [Here](https://docs.pola.rs/api/python/stable/reference/datatypes.html) is a full list of all data types Polars supports.
43 | """
44 | )
45 | return
46 |
47 |
48 | @app.cell(hide_code=True)
49 | def _(mo):
50 | mo.md(
51 | r"""
52 | ## Series
53 |
54 | A series is a 1-dimensional data structure that can hold only one data type.
55 | """
56 | )
57 | return
58 |
59 |
60 | @app.cell
61 | def _(pl):
62 | s = pl.Series("emojis", ["😀", "🤣", "🥶", "💀", "🤖"])
63 | s
64 | return (s,)
65 |
66 |
67 | @app.cell(hide_code=True)
68 | def _(mo):
69 | mo.md(r"""Unless specified, Polars infers the datatype from the supplied values.""")
70 | return
71 |
72 |
73 | @app.cell
74 | def _(pl):
75 | s1 = pl.Series("friends", ["Евгений", "अभिषेक", "秀良", "Federico", "Bob"])
76 | s2 = pl.Series("uints", [0x00, 0x01, 0x10, 0x11], dtype=pl.UInt8)
77 | s1.dtype, s2.dtype
78 | return s1, s2
79 |
80 |
81 | @app.cell(hide_code=True)
82 | def _(mo):
83 | mo.md(
84 | r"""
85 | ## Dataframe
86 |
87 | A dataframe is a 2-dimensional data structure that contains uniquely named series and can hold multiple data types. Dataframes are more commonly used for data manipulation using the functionality of Polars.
88 |
89 | The snippet below shows how to create a dataframe from a dictionary of lists:
90 | """
91 | )
92 | return
93 |
94 |
95 | @app.cell
96 | def _(pl):
97 | data = pl.DataFrame(
98 | {
99 | "Product ID": ["L51172", "M22586", "L51639", "L50250", "M20109"],
100 | "Type": ["L", "M", "L", "L", "M"],
101 | "Air temperature": [302.3, 300.8, 302.6, 300, 303.4], # (K)
102 | "Machine Failure": [False, True, False, False, True]
103 | }
104 | )
105 | data
106 | return (data,)
107 |
108 |
109 | @app.cell(hide_code=True)
110 | def _(mo):
111 | mo.md(
112 | r"""
113 | ### Inspecting a dataframe
114 |
115 | Polars has various functions to explore the data in a dataframe. We will use the dataframe `data` defined above in our examples. Alongside we can also see a view of the dataframe rendered by `marimo` as the cells are executed.
116 |
117 | ///note
118 | We can also use `marimo`'s built in data-inspection elements/features such as [`mo.ui.dataframe`](https://docs.marimo.io/api/inputs/dataframe/#marimo.ui.dataframe) & [`mo.ui.data_explorer`](https://docs.marimo.io/api/inputs/data_explorer/). For more check out our Polars tutorials at [`marimo learn`](https://marimo-team.github.io/learn/)!
119 | """
120 | )
121 | return
122 |
123 |
124 | @app.cell(hide_code=True)
125 | def _(mo):
126 | mo.md(
127 | """
128 | #### Head
129 |
130 | The function `head` shows the first rows of a dataframe. Unless specified, it shows the first 5 rows.
131 | """
132 | )
133 | return
134 |
135 |
136 | @app.cell
137 | def _(data):
138 | data.head(3)
139 | return
140 |
141 |
142 | @app.cell(hide_code=True)
143 | def _(mo):
144 | mo.md(
145 | r"""
146 | #### Glimpse
147 |
148 | The function `glimpse` is an alternative to `head` to view the first few columns, but displays each line of the output corresponding to a single column. That way, it makes inspecting wider dataframes easier.
149 | """
150 | )
151 | return
152 |
153 |
154 | @app.cell
155 | def _(data):
156 | print(data.glimpse(return_as_string=True))
157 | return
158 |
159 |
160 | @app.cell(hide_code=True)
161 | def _(mo):
162 | mo.md(
163 | r"""
164 | #### Tail
165 |
166 | The `tail` function, just like its name suggests, shows the last rows of a dataframe. Unless the number of rows is specified, it will show the last 5 rows.
167 | """
168 | )
169 | return
170 |
171 |
172 | @app.cell
173 | def _(data):
174 | data.tail(3)
175 | return
176 |
177 |
178 | @app.cell(hide_code=True)
179 | def _(mo):
180 | mo.md(
181 | r"""
182 | #### Sample
183 |
184 | `sample` can be used to show a specified number of randomly selected rows from the dataframe. Unless the number of rows is specified, it will show a single row. `sample` does not preserve order of the rows.
185 | """
186 | )
187 | return
188 |
189 |
190 | @app.cell
191 | def _(data):
192 | import random
193 |
194 | random.seed(42) # For reproducibility.
195 |
196 | data.sample(3)
197 | return (random,)
198 |
199 |
200 | @app.cell(hide_code=True)
201 | def _(mo):
202 | mo.md(
203 | r"""
204 | #### Describe
205 |
206 | The function `describe` describes the summary statistics for all columns of a dataframe.
207 | """
208 | )
209 | return
210 |
211 |
212 | @app.cell
213 | def _(data):
214 | data.describe()
215 | return
216 |
217 |
218 | @app.cell(hide_code=True)
219 | def _(mo):
220 | mo.md(
221 | r"""
222 | ## Schema
223 |
224 | A schema is a mapping showing the datatype corresponding to every column of a dataframe. The schema of a dataframe can be viewed using the attribute `schema`.
225 | """
226 | )
227 | return
228 |
229 |
230 | @app.cell
231 | def _(data):
232 | data.schema
233 | return
234 |
235 |
236 | @app.cell(hide_code=True)
237 | def _(mo):
238 | mo.md(r"""Since a schema is a mapping, it can be specified in the form of a Python dictionary. Then this dictionary can be used to specify the schema of a dataframe on definition. If not specified or the entry is `None`, Polars infers the datatype from the contents of the column. Note that if the schema is not specified, it will be inferred automatically by default.""")
239 | return
240 |
241 |
242 | @app.cell
243 | def _(pl):
244 | pl.DataFrame(
245 | {
246 | "Product ID": ["L51172", "M22586", "L51639", "L50250", "M20109"],
247 | "Type": ["L", "M", "L", "L", "M"],
248 | "Air temperature": [302.3, 300.8, 302.6, 300, 303.4], # (K)
249 | "Machine Failure": [False, True, False, False, True]
250 | },
251 | schema={"Product ID": pl.String, "Type": pl.String, "Air temperature": None, "Machine Failure": None},
252 | )
253 | return
254 |
255 |
256 | @app.cell(hide_code=True)
257 | def _(mo):
258 | mo.md(r"""Sometimes the automatically inferred schema is enough for some columns, but we might wish to override the inference of only some columns. We can specify the schema for those columns using `schema_overrides`.""")
259 | return
260 |
261 |
262 | @app.cell
263 | def _(pl):
264 | pl.DataFrame(
265 | {
266 | "Product ID": ["L51172", "M22586", "L51639", "L50250", "M20109"],
267 | "Type": ["L", "M", "L", "L", "M"],
268 | "Air temperature": [302.3, 300.8, 302.6, 300, 303.4], # (K)
269 | "Machine Failure": [False, True, False, False, True]
270 | },
271 | schema_overrides={"Air temperature": pl.Float32},
272 | )
273 | return
274 |
275 |
276 | @app.cell(hide_code=True)
277 | def _(mo):
278 | mo.md(
279 | r"""
280 | ### References
281 |
282 | 1. Polars documentation ([link](https://docs.pola.rs/api/python/stable/reference/datatypes.html))
283 | """
284 | )
285 | return
286 |
287 |
288 | @app.cell(hide_code=True)
289 | def _():
290 | import marimo as mo
291 | import polars as pl
292 | return mo, pl
293 |
294 |
295 | if __name__ == "__main__":
296 | app.run()
297 |
--------------------------------------------------------------------------------
/polars/12_aggregations.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.13"
3 | # dependencies = [
4 | # "marimo",
5 | # "polars==1.23.0",
6 | # ]
7 | # ///
8 |
9 | import marimo
10 |
11 | __generated_with = "0.12.9"
12 | app = marimo.App(width="medium")
13 |
14 |
15 | @app.cell
16 | def _():
17 | import marimo as mo
18 | return (mo,)
19 |
20 |
21 | @app.cell(hide_code=True)
22 | def _(mo):
23 | mo.md(
24 | r"""
25 | # Aggregations
26 | _By [Joram Mutenge](https://www.udemy.com/user/joram-mutenge/)._
27 |
28 | In this notebook, you'll learn how to perform different types of aggregations in Polars, including grouping by categories and time. We'll analyze sales data from a clothing store, focusing on three product categories: hats, socks, and sweaters.
29 | """
30 | )
31 | return
32 |
33 |
34 | @app.cell
35 | def _():
36 | import polars as pl
37 |
38 | df = (pl.read_csv('https://raw.githubusercontent.com/jorammutenge/learn-rust/refs/heads/main/sample_sales.csv', try_parse_dates=True)
39 | .rename(lambda col: col.replace(' ','_').lower())
40 | )
41 | df
42 | return df, pl
43 |
44 |
45 | @app.cell(hide_code=True)
46 | def _(mo):
47 | mo.md(
48 | r"""
49 | ## Grouping by category
50 | ### With single category
51 | Let's find out how many of each product category we sold.
52 | """
53 | )
54 | return
55 |
56 |
57 | @app.cell
58 | def _(df, pl):
59 | (df
60 | .group_by('category')
61 | .agg(pl.sum('quantity'))
62 | )
63 | return
64 |
65 |
66 | @app.cell(hide_code=True)
67 | def _(mo):
68 | mo.md(
69 | r"""
70 | It looks like we sold more sweaters. Maybe this was a winter season.
71 |
72 | Let's add another aggregate to see how much was spent on the total units for each product.
73 | """
74 | )
75 | return
76 |
77 |
78 | @app.cell
79 | def _(df, pl):
80 | (df
81 | .group_by('category')
82 | .agg(pl.sum('quantity'),
83 | pl.sum('ext_price'))
84 | )
85 | return
86 |
87 |
88 | @app.cell(hide_code=True)
89 | def _(mo):
90 | mo.md(r"""We could also write aggregate code for the two columns as a single line.""")
91 | return
92 |
93 |
94 | @app.cell
95 | def _(df, pl):
96 | (df
97 | .group_by('category')
98 | .agg(pl.sum('quantity','ext_price'))
99 | )
100 | return
101 |
102 |
103 | @app.cell(hide_code=True)
104 | def _(mo):
105 | mo.md(r"""Actually, the way we've been writing the aggregate lines is syntactic sugar. Here's a longer way of doing it as shown in the [Polars documentation](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.dataframe.group_by.GroupBy.agg.html).""")
106 | return
107 |
108 |
109 | @app.cell
110 | def _(df, pl):
111 | (df
112 | .group_by('category')
113 | .agg(pl.col('quantity').sum(),
114 | pl.col('ext_price').sum())
115 | )
116 | return
117 |
118 |
119 | @app.cell(hide_code=True)
120 | def _(mo):
121 | mo.md(
122 | r"""
123 | ### With multiple categories
124 | We can also group by multiple categories. Let's find out how many items we sold in each product category for each SKU. This more detailed aggregation will produce more rows than the previous DataFrame.
125 | """
126 | )
127 | return
128 |
129 |
130 | @app.cell
131 | def _(df, pl):
132 | (df
133 | .group_by('category','sku')
134 | .agg(pl.sum('quantity'))
135 | )
136 | return
137 |
138 |
139 | @app.cell(hide_code=True)
140 | def _(mo):
141 | mo.md(
142 | r"""
143 | Aggregations when grouping data are not limited to sums. You can also use functions like [`max`, `min`, `median`, `first`, and `last`](https://docs.pola.rs/user-guide/expressions/aggregation/#basic-aggregations).
144 |
145 | Let's find the largest sale quantity for each product category.
146 | """
147 | )
148 | return
149 |
150 |
151 | @app.cell
152 | def _(df, pl):
153 | (df
154 | .group_by('category')
155 | .agg(pl.max('quantity'))
156 | )
157 | return
158 |
159 |
160 | @app.cell(hide_code=True)
161 | def _(mo):
162 | mo.md(
163 | r"""
164 | Let's make the aggregation more interesting. We'll identify the first customer to purchase each item, along with the quantity they bought and the amount they spent.
165 |
166 | **Note:** To make this work, we'll have to sort the date from earliest to latest.
167 | """
168 | )
169 | return
170 |
171 |
172 | @app.cell
173 | def _(df, pl):
174 | (df
175 | .sort('date')
176 | .group_by('category')
177 | .agg(pl.first('account_name','quantity','ext_price'))
178 | )
179 | return
180 |
181 |
182 | @app.cell(hide_code=True)
183 | def _(mo):
184 | mo.md(
185 | r"""
186 | ## Grouping by time
187 | Since `datetime` is a special data type in Polars, we can perform various group-by aggregations on it.
188 |
189 | Our dataset spans a two-year period. Let's calculate the total dollar sales for each year. We'll do it the naive way first so you can appreciate grouping with time.
190 | """
191 | )
192 | return
193 |
194 |
195 | @app.cell
196 | def _(df, pl):
197 | (df
198 | .with_columns(year=pl.col('date').dt.year())
199 | .group_by('year')
200 | .agg(pl.sum('ext_price').round(2))
201 | )
202 | return
203 |
204 |
205 | @app.cell(hide_code=True)
206 | def _(mo):
207 | mo.md(
208 | r"""
209 | We had more sales in 2014.
210 |
211 | Now let's perform the above operation by grouping with time. This requires sorting the dataframe first.
212 | """
213 | )
214 | return
215 |
216 |
217 | @app.cell
218 | def _(df, pl):
219 | (df
220 | .sort('date')
221 | .group_by_dynamic('date', every='1y')
222 | .agg(pl.sum('ext_price'))
223 | )
224 | return
225 |
226 |
227 | @app.cell(hide_code=True)
228 | def _(mo):
229 | mo.md(
230 | r"""
231 | The beauty of grouping with time is that it allows us to resample the data by selecting whatever time interval we want.
232 |
233 | Let's find out what the quarterly sales were for 2014
234 | """
235 | )
236 | return
237 |
238 |
239 | @app.cell
240 | def _(df, pl):
241 | (df
242 | .filter(pl.col('date').dt.year() == 2014)
243 | .sort('date')
244 | .group_by_dynamic('date', every='1q')
245 | .agg(pl.sum('ext_price'))
246 | )
247 | return
248 |
249 |
250 | @app.cell(hide_code=True)
251 | def _(mo):
252 | mo.md(
253 | r"""
254 | Here's an interesting question we can answer that takes advantage of grouping by time.
255 |
256 | Let's find the hour of the day where we had the most sales in dollars.
257 | """
258 | )
259 | return
260 |
261 |
262 | @app.cell
263 | def _(df, pl):
264 | (df
265 | .sort('date')
266 | .group_by_dynamic('date', every='1h')
267 | .agg(pl.max('ext_price'))
268 | .filter(pl.col('ext_price') == pl.col('ext_price').max())
269 | )
270 | return
271 |
272 |
273 | @app.cell(hide_code=True)
274 | def _(mo):
275 | mo.md(r"""Just for fun, let's find the median number of items sold in each SKU and the total dollar amount in each SKU every six days.""")
276 | return
277 |
278 |
279 | @app.cell
280 | def _(df, pl):
281 | (df
282 | .sort('date')
283 | .group_by_dynamic('date', every='6d')
284 | .agg(pl.first('sku'),
285 | pl.median('quantity'),
286 | pl.sum('ext_price'))
287 | )
288 | return
289 |
290 |
291 | @app.cell(hide_code=True)
292 | def _(mo):
293 | mo.md(r"""Let's rename the columns to clearly indicate the type of aggregation performed. This will help us identify the aggregation method used on a column without needing to check the code.""")
294 | return
295 |
296 |
297 | @app.cell
298 | def _(df, pl):
299 | (df
300 | .sort('date')
301 | .group_by_dynamic('date', every='6d')
302 | .agg(pl.first('sku'),
303 | pl.median('quantity').alias('median_qty'),
304 | pl.sum('ext_price').alias('total_dollars'))
305 | )
306 | return
307 |
308 |
309 | @app.cell(hide_code=True)
310 | def _(mo):
311 | mo.md(
312 | r"""
313 | ## Grouping with over
314 |
315 | Sometimes, we may want to perform an aggregation but also keep all the columns and rows of the dataframe.
316 |
317 | Let's assign a value to indicate the number of times each customer visited and bought something.
318 | """
319 | )
320 | return
321 |
322 |
323 | @app.cell
324 | def _(df, pl):
325 | (df
326 | .with_columns(buy_freq=pl.col('account_name').len().over('account_name'))
327 | )
328 | return
329 |
330 |
331 | @app.cell(hide_code=True)
332 | def _(mo):
333 | mo.md(r"""Finally, let's determine which customers visited the store the most and bought something.""")
334 | return
335 |
336 |
337 | @app.cell
338 | def _(df, pl):
339 | (df
340 | .with_columns(buy_freq=pl.col('account_name').len().over('account_name'))
341 | .filter(pl.col('buy_freq') == pl.col('buy_freq').max())
342 | .select('account_name','buy_freq')
343 | .unique()
344 | )
345 | return
346 |
347 |
348 | @app.cell(hide_code=True)
349 | def _(mo):
350 | mo.md(r"""There's more you can do with aggregations in Polars such as [sorting with aggregations](https://docs.pola.rs/user-guide/expressions/aggregation/#sorting). We hope that in this notebook, we've armed you with the tools to get started.""")
351 | return
352 |
353 |
354 | if __name__ == "__main__":
355 | app.run()
356 |
--------------------------------------------------------------------------------
/polars/README.md:
--------------------------------------------------------------------------------
1 | # Learn Polars
2 |
3 | _🚧 This collection is a work in progress. Please help us add notebooks!_
4 |
5 | This collection of marimo notebooks is designed to teach you the basics of data wrangling using a Python library called Polars.
6 |
7 | **Help us build this course! ⚒️**
8 |
9 | We're seeking contributors to help us build these notebooks. Every contributor will be acknowledged as an author in this README and in their contributed notebooks. Head over to the [tracking issue](https://github.com/marimo-team/learn/issues/40) to sign up for a planned notebook or propose your own.
10 |
11 | **Running notebooks.** To run a notebook locally, use
12 |
13 | ```bash
14 | uvx marimo edit
15 | ```
16 |
17 | You can also open notebooks in our online playground by appending marimo.app/ to a notebook's URL:
18 |
19 | [https://marimo.app/github.com/marimo-team/learn/blob/main/polars/01_why_polars.py](https://marimo.app/github.com/marimo-team/learn/blob/main/polars/01_why_polars.py).
20 |
21 | **Thanks to all our notebook authors!**
22 |
23 | * [Koushik Khan](https://github.com/koushikkhan)
24 | * [Péter Gyarmati](https://github.com/peter-gy)
25 | * [Joram Mutenge](https://github.com/jorammutenge)
26 | * [etrotta](https://github.com/etrotta)
27 | * [Debajyoti Das](https://github.com/debajyotid2)
28 |
--------------------------------------------------------------------------------
/probability/01_sets.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.11.0"
11 | app = marimo.App()
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | r"""
18 | # Sets
19 |
20 | Probability is the study of "events", assigning numerical values to how likely
21 | events are to occur. For example, probability lets us quantify how likely it is for it to rain or shine on a given day.
22 |
23 |
24 | Typically we reason about _sets_ of events. In mathematics,
25 | a set is a collection of elements, with no element included more than once.
26 | Elements can be any kind of object.
27 |
28 | For example:
29 |
30 | - ☀️ Weather events: $\{\text{Rain}, \text{Overcast}, \text{Clear}\}$
31 | - 🎲 Die rolls: $\{1, 2, 3, 4, 5, 6\}$
32 | - 🪙 Pairs of coin flips = $\{ \text{(Heads, Heads)}, \text{(Heads, Tails)}, \text{(Tails, Tails)} \text{(Tails, Heads)}\}$
33 |
34 | Sets are the building blocks of probability, and will arise frequently in our study.
35 | """
36 | )
37 | return
38 |
39 |
40 | @app.cell(hide_code=True)
41 | def _(mo):
42 | mo.md(r"""## Set operations""")
43 | return
44 |
45 |
46 | @app.cell(hide_code=True)
47 | def _(mo):
48 | mo.md(r"""In Python, sets are made with the `set` function:""")
49 | return
50 |
51 |
52 | @app.cell
53 | def _():
54 | A = set([2, 3, 5, 7])
55 | A
56 | return (A,)
57 |
58 |
59 | @app.cell
60 | def _():
61 | B = set([0, 1, 2, 3, 5, 8])
62 | B
63 | return (B,)
64 |
65 |
66 | @app.cell(hide_code=True)
67 | def _(mo):
68 | mo.md(
69 | r"""
70 | Below we explain common operations on sets.
71 |
72 | _**Try it!** Try modifying the definitions of `A` and `B` above, and see how the results change below._
73 |
74 | The **union** $A \cup B$ of sets $A$ and $B$ is the set of elements in $A$, $B$, or both.
75 | """
76 | )
77 | return
78 |
79 |
80 | @app.cell
81 | def _(A, B):
82 | A | B
83 | return
84 |
85 |
86 | @app.cell(hide_code=True)
87 | def _(mo):
88 | mo.md(r"""The **intersection** $A \cap B$ is the set of elements in both $A$ and $B$""")
89 | return
90 |
91 |
92 | @app.cell
93 | def _(A, B):
94 | A & B
95 | return
96 |
97 |
98 | @app.cell(hide_code=True)
99 | def _(mo):
100 | mo.md(r"""The **difference** $A \setminus B$ is the set of elements in $A$ that are not in $B$.""")
101 | return
102 |
103 |
104 | @app.cell
105 | def _(A, B):
106 | A - B
107 | return
108 |
109 |
110 | @app.cell(hide_code=True)
111 | def _(mo):
112 | mo.md(
113 | """
114 | ### 🎬 An interactive example
115 |
116 | Here's a simple example that classifies TV shows into sets by genre, and uses these sets to recommend shows to a user based on their preferences.
117 | """
118 | )
119 | return
120 |
121 |
122 | @app.cell(hide_code=True)
123 | def _(mo):
124 | viewer_type = mo.ui.radio(
125 | options={
126 | "I like action and drama!": "New Viewer",
127 | "I only like action shows": "Action Fan",
128 | "I only like dramas": "Drama Fan",
129 | },
130 | value="I like action and drama!",
131 | label="Which genre do you prefer?",
132 | )
133 | return (viewer_type,)
134 |
135 |
136 | @app.cell(hide_code=True)
137 | def _(viewer_type):
138 | viewer_type
139 | return
140 |
141 |
142 | @app.cell
143 | def _():
144 | action_shows = {"Stranger Things", "The Witcher", "Money Heist"}
145 | drama_shows = {"The Crown", "Money Heist", "Bridgerton"}
146 | return action_shows, drama_shows
147 |
148 |
149 | @app.cell
150 | def _(action_shows, drama_shows):
151 | recommendations = {
152 | "New Viewer": action_shows | drama_shows, # Union for new viewers
153 | "Action Fan": action_shows - drama_shows, # Unique action shows
154 | "Drama Fan": drama_shows - action_shows, # Unique drama shows
155 | }
156 | return (recommendations,)
157 |
158 |
159 | @app.cell(hide_code=True)
160 | def _(mo, recommendations, viewer_type):
161 | result = recommendations[viewer_type.value]
162 |
163 | explanation = {
164 | "New Viewer": "You get everything to explore!",
165 | "Action Fan": "Pure action, no drama!",
166 | "Drama Fan": "Drama-focused selections!",
167 | }
168 |
169 | mo.md(f"""
170 | **🎬 Recommended shows.** Based on your preference for **{viewer_type.value}**,
171 | we recommend:
172 |
173 | {", ".join(result)}
174 |
175 | **Why these shows?**
176 | {explanation[viewer_type.value]}
177 | """)
178 | return explanation, result
179 |
180 |
181 | @app.cell(hide_code=True)
182 | def _(mo):
183 | mo.md("""
184 | ### Exercise
185 |
186 | Given these sets:
187 |
188 | - A = {🎮, 📱, 💻}
189 |
190 | - B = {📱, 💻, 🖨️}
191 |
192 | - C = {💻, 🖨️, ⌨️}
193 |
194 | Can you:
195 |
196 | 1. Find all elements that are in A or B
197 |
198 | 2. Find elements common to all three sets
199 |
200 | 3. Find elements in A that aren't in C
201 |
202 |
203 |
204 | Check your answers!
205 |
206 | 1. A ∪ B = {🎮, 📱, 💻, 🖨️}
207 | 2. A ∩ B ∩ C = {💻}
208 | 3. A - C = {🎮, 📱}
209 |
210 |
211 | """)
212 | return
213 |
214 |
215 | @app.cell(hide_code=True)
216 | def _(mo):
217 | mo.md(
218 | r"""
219 | ## 🧮 Set properties
220 |
221 | Here are some important properties of the set operations:
222 |
223 | 1. **Commutative**: $A \cup B = B \cup A$
224 | 2. **Associative**: $(A \cup B) \cup C = A \cup (B \cup C)$
225 | 3. **Distributive**: $A \cup (B \cap C) = (A \cup B) \cap (A \cup C)$
226 | """
227 | )
228 | return
229 |
230 |
231 | @app.cell(hide_code=True)
232 | def _(mo):
233 | mo.md(
234 | r"""
235 | ## Set builder notation
236 |
237 | To compactly describe the elements in a set, we can use **set builder notation**, which specifies conditions that must be true for elements to be in the set.
238 |
239 | For example, here is how to specify the set of positive numbers less than 10:
240 |
241 | \[
242 | \{x \mid 0 < x < 10 \}
243 | \]
244 |
245 | The predicate to the right of the vertical bar $\mid$ specifies conditions that must be true for an element to be in the set; the expression to the left of $\mid$ specifies the value being included.
246 |
247 | In Python, set builder notation is called a "set comprehension."
248 | """
249 | )
250 | return
251 |
252 |
253 | @app.cell
254 | def _():
255 | def predicate(x):
256 | return x > 0 and x < 10
257 | return (predicate,)
258 |
259 |
260 | @app.cell
261 | def _(predicate):
262 | set(x for x in range(100) if predicate(x))
263 | return
264 |
265 |
266 | @app.cell(hide_code=True)
267 | def _(mo):
268 | mo.md("""**Try it!** Try modifying the `predicate` function above and see how the set changes.""")
269 | return
270 |
271 |
272 | @app.cell(hide_code=True)
273 | def _(mo):
274 | mo.md("""
275 | ## Summary
276 |
277 | You've learned:
278 |
279 | - Basic set operations
280 | - Set properties
281 | - Real-world applications
282 |
283 | In the next lesson, we'll define probability from the ground up, using sets.
284 |
285 | Remember: In probability, every event is a set, and every set can be an event!
286 | """)
287 | return
288 |
289 |
290 | @app.cell
291 | def _():
292 | import marimo as mo
293 | return (mo,)
294 |
295 |
296 | if __name__ == "__main__":
297 | app.run()
298 |
--------------------------------------------------------------------------------
/probability/02_axioms.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.11"
3 | # dependencies = [
4 | # "marimo",
5 | # "matplotlib==3.10.0",
6 | # "numpy==2.2.2",
7 | # ]
8 | # ///
9 |
10 | import marimo
11 |
12 | __generated_with = "0.11.2"
13 | app = marimo.App(width="medium")
14 |
15 |
16 | @app.cell
17 | def _():
18 | import marimo as mo
19 | return (mo,)
20 |
21 |
22 | @app.cell(hide_code=True)
23 | def _(mo):
24 | mo.md(
25 | r"""
26 | # Axioms of Probability
27 |
28 | Probability theory is built on three fundamental axioms, known as the [Kolmogorov axioms](https://en.wikipedia.org/wiki/Probability_axioms). These axioms form
29 | the mathematical foundation for all of probability theory[1 ](https://chrispiech.github.io/probabilityForComputerScientists/en/part1/probability).
30 |
31 | Let's explore each axiom and understand why they make intuitive sense:
32 | """
33 | )
34 | return
35 |
36 |
37 | @app.cell(hide_code=True)
38 | def _(mo):
39 | mo.md(
40 | r"""
41 | ## The Three Axioms
42 |
43 | | Axiom | Mathematical Form | Meaning |
44 | |-------|------------------|----------|
45 | | **Axiom 1** | $0 \leq P(E) \leq 1$ | All probabilities are between 0 and 1 |
46 | | **Axiom 2** | $P(S) = 1$ | The probability of the sample space is 1 |
47 | | **Axiom 3** | $P(E \cup F) = P(E) + P(F)$ | For mutually exclusive events, probabilities add |
48 |
49 | where the set $S$ is the sample space (all possible outcomes), and $E$ and $F$ are sets that represent events. The notation $P(E)$ denotes the probability of $E$, which you can interpret as the chance that something happens. $P(E) = 0$ means that the event cannot happen, while $P(E) = 1$ means the event will happen no matter what; $P(E) = 0.5$ means that $E$ has a 50% chance of happening.
50 |
51 | For an example, when rolling a fair six-sided die once, the sample space $S$ is the set of die faces ${1, 2, 3, 4, 5, 6}$, and there are many possible events; we'll see some examples below.
52 | """
53 | )
54 | return
55 |
56 |
57 | @app.cell(hide_code=True)
58 | def _(mo):
59 | mo.md(
60 | r"""
61 | ## Understanding Through Examples
62 |
63 | Let's explore these axioms using a simple experiment: rolling a fair six-sided die.
64 | We'll use this to demonstrate why each axiom makes intuitive sense.
65 | """
66 | )
67 | return
68 |
69 |
70 | @app.cell
71 | def _(event):
72 | event
73 | return
74 |
75 |
76 | @app.cell(hide_code=True)
77 | def _(mo):
78 | # Create an interactive widget to explore different events
79 |
80 | event = mo.ui.dropdown(
81 |
82 | options=[
83 |
84 | "Rolling an even number (2,4,6)",
85 |
86 | "Rolling an odd number (1,3,5)",
87 |
88 | "Rolling a prime number (2,3,5)",
89 |
90 | "Rolling less than 4 (1,2,3)",
91 |
92 | "Any possible roll (1,2,3,4,5,6)",
93 |
94 | ],
95 |
96 | value="Rolling an even number (2,4,6)",
97 |
98 | label="Select an event"
99 |
100 | )
101 | return (event,)
102 |
103 |
104 | @app.cell(hide_code=True)
105 | def _(event, mo, np, plt):
106 | # Define the probabilities for each event
107 | event_map = {
108 | "Rolling an even number (2,4,6)": [2, 4, 6],
109 | "Rolling an odd number (1,3,5)": [1, 3, 5],
110 | "Rolling a prime number (2,3,5)": [2, 3, 5],
111 | "Rolling less than 4 (1,2,3)": [1, 2, 3],
112 | "Any possible roll (1,2,3,4,5,6)": [1, 2, 3, 4, 5, 6],
113 | }
114 |
115 | # Get outcomes directly from the event value
116 | outcomes = event_map[event.value]
117 | prob = len(outcomes) / 6
118 |
119 | # Visualize the probability
120 | dice = np.arange(1, 7)
121 | colors = ['#1f77b4' if d in outcomes else '#d3d3d3' for d in dice]
122 |
123 | fig, ax = plt.subplots(figsize=(8, 2))
124 | ax.bar(dice, np.ones_like(dice), color=colors)
125 | ax.set_xticks(dice)
126 | ax.set_yticks([])
127 | ax.set_title(f"P(Event) = {prob:.2f}")
128 |
129 | # Add explanation
130 | explanation = mo.md(f"""
131 | **Event**: {event.value}
132 |
133 | **Probability**: {prob:.2f}
134 |
135 | **Favorable outcomes**: {outcomes}
136 |
137 | This example demonstrates:
138 |
139 | - Axiom 1: The probability is between 0 and 1
140 |
141 | - Axiom 2: For the sample space, P(S) = 1
142 |
143 | - Axiom 3: The probability is the sum of individual outcome probabilities
144 | """)
145 |
146 | mo.hstack([plt.gcf(), explanation])
147 | return ax, colors, dice, event_map, explanation, fig, outcomes, prob
148 |
149 |
150 | @app.cell(hide_code=True)
151 | def _(mo):
152 | mo.md(
153 | r"""
154 | ## Why These Axioms Matter
155 |
156 | These axioms are more than just rules - they provide the foundation for all of probability theory:
157 |
158 | 1. **Non-negativity** (Axiom 1) makes intuitive sense: you can't have a negative number of occurrences
159 | in any experiment.
160 |
161 | 2. **Normalization** (Axiom 2) ensures that something must happen - the total probability must be 1.
162 |
163 | 3. **Additivity** (Axiom 3) lets us build complex probabilities from simple ones, but only for events
164 | that can't happen together (mutually exclusive events).
165 |
166 | From these simple rules, we can derive all the powerful tools of probability theory that are used in
167 | statistics, machine learning, and other fields.
168 | """
169 | )
170 | return
171 |
172 |
173 | @app.cell(hide_code=True)
174 | def _(mo):
175 | mo.md(
176 | r"""
177 | ## 🤔 Test Your Understanding
178 |
179 | Consider rolling two dice. Which of these statements follow from the axioms?
180 |
181 |
182 | 1. P(sum is 13) = 0
183 |
184 | ✅ Correct! This follows from Axiom 1. Since no combination of dice can sum to 13,
185 | the probability must be non-negative but can be 0.
186 |
187 |
188 |
189 | 2. P(sum is 7) + P(sum is not 7) = 1
190 |
191 | ✅ Correct! This follows from Axioms 2 and 3. These events are mutually exclusive and cover
192 | the entire sample space.
193 |
194 |
195 |
196 | 3. P(first die is 6 or second die is 6) = P(first die is 6) + P(second die is 6)
197 |
198 | ❌ Incorrect! This doesn't follow from Axiom 3 because the events are not mutually exclusive -
199 | you could roll (6,6).
200 |
201 | """
202 | )
203 | return
204 |
205 |
206 | @app.cell
207 | def _():
208 | import numpy as np
209 | import matplotlib.pyplot as plt
210 | return np, plt
211 |
212 |
213 | if __name__ == "__main__":
214 | app.run()
215 |
--------------------------------------------------------------------------------
/probability/03_probability_of_or.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # "matplotlib",
6 | # "matplotlib-venn"
7 | # ]
8 | # ///
9 |
10 | import marimo
11 |
12 | __generated_with = "0.11.2"
13 | app = marimo.App(width="medium")
14 |
15 |
16 | @app.cell
17 | def _():
18 | import marimo as mo
19 | return (mo,)
20 |
21 |
22 | @app.cell
23 | def _():
24 | import matplotlib.pyplot as plt
25 | from matplotlib_venn import venn2
26 | import numpy as np
27 | return np, plt, venn2
28 |
29 |
30 | @app.cell(hide_code=True)
31 | def _(mo):
32 | mo.md(
33 | r"""
34 | # Probability of Or
35 |
36 | When calculating the probability of either one event _or_ another occurring, we need to be careful about how we combine probabilities. The method depends on whether the events can happen together[1 ](https://chrispiech.github.io/probabilityForComputerScientists/en/part1/prob_or/).
37 |
38 | Let's explore how to calculate $P(E \cup F)$, i.e. $P(E \text{ or } F)$, in different scenarios.
39 | """
40 | )
41 | return
42 |
43 |
44 | @app.cell(hide_code=True)
45 | def _(mo):
46 | mo.md(
47 | r"""
48 | ## Mutually Exclusive Events
49 |
50 | Two events $E$ and $F$ are **mutually exclusive** if they cannot occur simultaneously.
51 | In set notation, this means:
52 |
53 | $E \cap F = \emptyset$
54 |
55 | For example:
56 |
57 | - Rolling an even number (2,4,6) vs rolling an odd number (1,3,5)
58 | - Drawing a heart vs drawing a spade from a deck
59 | - Passing vs failing a test
60 |
61 | Here's a Python function to check if two sets of outcomes are mutually exclusive:
62 | """
63 | )
64 | return
65 |
66 |
67 | @app.cell
68 | def _():
69 | def are_mutually_exclusive(event1, event2):
70 | return len(event1.intersection(event2)) == 0
71 |
72 | # Example with dice rolls
73 | even_numbers = {2, 4, 6}
74 | odd_numbers = {1, 3, 5}
75 | prime_numbers = {2, 3, 5, 7}
76 | return are_mutually_exclusive, even_numbers, odd_numbers, prime_numbers
77 |
78 |
79 | @app.cell
80 | def _(are_mutually_exclusive, even_numbers, odd_numbers):
81 | are_mutually_exclusive(even_numbers, odd_numbers)
82 | return
83 |
84 |
85 | @app.cell
86 | def _(are_mutually_exclusive, even_numbers, prime_numbers):
87 | are_mutually_exclusive(even_numbers, prime_numbers)
88 | return
89 |
90 |
91 | @app.cell(hide_code=True)
92 | def _(mo):
93 | mo.md(
94 | r"""
95 | ## Or with Mutually Exclusive Events
96 |
97 | For mutually exclusive events, the probability of either event occurring is simply the sum of their individual probabilities:
98 |
99 | $P(E \cup F) = P(E) + P(F)$
100 |
101 | This extends to multiple events. For $n$ mutually exclusive events $E_1, E_2, \ldots, E_n$:
102 |
103 | $P(E_1 \cup E_2 \cup \cdots \cup E_n) = \sum_{i=1}^n P(E_i)$
104 |
105 | Let's implement this calculation:
106 | """
107 | )
108 | return
109 |
110 |
111 | @app.cell
112 | def _():
113 | def prob_union_mutually_exclusive(probabilities):
114 | return sum(probabilities)
115 |
116 | # Example: Rolling a die
117 | # P(even) = P(2) + P(4) + P(6)
118 | p_even_mutually_exclusive = prob_union_mutually_exclusive([1/6, 1/6, 1/6])
119 | print(f"P(rolling an even number) = {p_even_mutually_exclusive}")
120 |
121 | # P(prime) = P(2) + P(3) + P(5)
122 | p_prime_mutually_exclusive = prob_union_mutually_exclusive([1/6, 1/6, 1/6])
123 | print(f"P(rolling a prime number) = {p_prime_mutually_exclusive}")
124 | return (
125 | p_even_mutually_exclusive,
126 | p_prime_mutually_exclusive,
127 | prob_union_mutually_exclusive,
128 | )
129 |
130 |
131 | @app.cell(hide_code=True)
132 | def _(mo):
133 | mo.md(
134 | r"""
135 | ## Or with Non-Mutually Exclusive Events
136 |
137 | When events can occur together, we need to use the **inclusion-exclusion principle**:
138 |
139 | $P(E \cup F) = P(E) + P(F) - P(E \cap F)$
140 |
141 | Why subtract $P(E \cap F)$? Because when we add $P(E)$ and $P(F)$, we count the overlap twice!
142 |
143 | For example, consider calculating $P(\text{prime or even})$ when rolling a die:
144 |
145 | - Prime numbers: {2, 3, 5}
146 | - Even numbers: {2, 4, 6}
147 | - The number 2 is counted twice unless we subtract its probability
148 |
149 | Here's how to implement this calculation:
150 | """
151 | )
152 | return
153 |
154 |
155 | @app.cell
156 | def _():
157 | def prob_union_general(p_a, p_b, p_intersection):
158 | """Calculate probability of union for any two events"""
159 | return p_a + p_b - p_intersection
160 |
161 | # Example: Rolling a die
162 | # P(prime or even)
163 | p_prime_general = 3/6 # P(prime) = P(2,3,5)
164 | p_even_general = 3/6 # P(even) = P(2,4,6)
165 | p_intersection = 1/6 # P(intersection) = P(2)
166 |
167 | result = prob_union_general(p_prime_general, p_even_general, p_intersection)
168 | print(f"P(prime or even) = {p_prime_general} + {p_even_general} - {p_intersection} = {result}")
169 | return (
170 | p_even_general,
171 | p_intersection,
172 | p_prime_general,
173 | prob_union_general,
174 | result,
175 | )
176 |
177 |
178 | @app.cell(hide_code=True)
179 | def _(mo):
180 | mo.md(
181 | r"""
182 | ### Extension to Three Events
183 |
184 | For three events, the inclusion-exclusion principle becomes:
185 |
186 | $P(E_1 \cup E_2 \cup E_3) = P(E_1) + P(E_2) + P(E_3)$
187 | $- P(E_1 \cap E_2) - P(E_1 \cap E_3) - P(E_2 \cap E_3)$
188 | $+ P(E_1 \cap E_2 \cap E_3)$
189 |
190 | The pattern is:
191 |
192 | 1. Add individual probabilities
193 | 2. Subtract probabilities of pairs
194 | 3. Add probability of triple intersection
195 | """
196 | )
197 | return
198 |
199 |
200 | @app.cell(hide_code=True)
201 | def _(mo):
202 | mo.md(r"""### Interactive example:""")
203 | return
204 |
205 |
206 | @app.cell
207 | def _(event_type):
208 | event_type
209 | return
210 |
211 |
212 | @app.cell(hide_code=True)
213 | def _(mo):
214 | # Create a dropdown to select the type of events to visualize
215 | event_type = mo.ui.dropdown(
216 | options=[
217 | "Mutually Exclusive Events (Rolling Odd vs Even)",
218 | "Non-Mutually Exclusive Events (Prime vs Even)",
219 | "Three Events (Less than 3, Even, Prime)"
220 | ],
221 | value="Mutually Exclusive Events (Rolling Odd vs Even)",
222 | label="Select Event Type"
223 | )
224 | return (event_type,)
225 |
226 |
227 | @app.cell(hide_code=True)
228 | def _(event_type, mo, plt, venn2):
229 | # Define the events and their probabilities
230 | events_data = {
231 | "Mutually Exclusive Events (Rolling Odd vs Even)": {
232 | "sets": (round(3/6, 2), round(3/6, 2), 0), # (odd, even, intersection)
233 | "labels": ("Odd\n{1,3,5}", "Even\n{2,4,6}"),
234 | "title": "Mutually Exclusive Events: Odd vs Even Numbers",
235 | "explanation": r"""
236 | ### Mutually Exclusive Events
237 |
238 | $P(\text{Odd}) = \frac{3}{6} = 0.5$
239 |
240 | $P(\text{Even}) = \frac{3}{6} = 0.5$
241 |
242 | $P(\text{Odd} \cap \text{Even}) = 0$
243 |
244 | $P(\text{Odd} \cup \text{Even}) = P(\text{Odd}) + P(\text{Even}) = 1$
245 |
246 | These events are mutually exclusive because a number cannot be both odd and even.
247 | """
248 | },
249 | "Non-Mutually Exclusive Events (Prime vs Even)": {
250 | "sets": (round(2/6, 2), round(2/6, 2), round(1/6, 2)), # (prime-only, even-only, intersection)
251 | "labels": ("Prime\n{3,5}", "Even\n{4,6}"),
252 | "title": "Non-Mutually Exclusive: Prime vs Even Numbers",
253 | "explanation": r"""
254 | ### Non-Mutually Exclusive Events
255 |
256 | $P(\text{Prime}) = \frac{3}{6} = 0.5$ (2,3,5)
257 |
258 | $P(\text{Even}) = \frac{3}{6} = 0.5$ (2,4,6)
259 |
260 | $P(\text{Prime} \cap \text{Even}) = \frac{1}{6}$ (2)
261 |
262 | $P(\text{Prime} \cup \text{Even}) = \frac{3}{6} + \frac{3}{6} - \frac{1}{6} = \frac{5}{6}$
263 |
264 | These events overlap because 2 is both prime and even.
265 | """
266 | },
267 | "Three Events (Less than 3, Even, Prime)": {
268 | "sets": (round(1/6, 2), round(2/6, 2), round(1/6, 2)), # (less than 3, even, intersection)
269 | "labels": ("<3\n{1,2}", "Even\n{2,4,6}"),
270 | "title": "Complex Example: Numbers < 3 and Even Numbers",
271 | "explanation": r"""
272 | ### Complex Event Interaction
273 |
274 | $P(x < 3) = \frac{2}{6}$ (1,2)
275 |
276 | $P(\text{Even}) = \frac{3}{6}$ (2,4,6)
277 |
278 | $P(x < 3 \cap \text{Even}) = \frac{1}{6}$ (2)
279 |
280 | $P(x < 3 \cup \text{Even}) = \frac{2}{6} + \frac{3}{6} - \frac{1}{6} = \frac{4}{6}$
281 |
282 | The number 2 belongs to both sets, requiring the inclusion-exclusion principle.
283 | """
284 | }
285 | }
286 |
287 | # Get data for selected event type
288 | data = events_data[event_type.value]
289 |
290 | # Create visualization
291 | plt.figure(figsize=(10, 5))
292 | v = venn2(subsets=data["sets"],
293 | set_labels=data["labels"])
294 | plt.title(data["title"])
295 |
296 | # Display explanation alongside visualization
297 | mo.hstack([
298 | plt.gcf(),
299 | mo.md(data["explanation"])
300 | ])
301 | return data, events_data, v
302 |
303 |
304 | @app.cell(hide_code=True)
305 | def _(mo):
306 | mo.md(
307 | r"""
308 | ## 🤔 Test Your Understanding
309 |
310 | Consider rolling a six-sided die. Which of these statements are true?
311 |
312 |
313 | 1. P(even or less than 3) = P(even) + P(less than 3)
314 |
315 | ❌ Incorrect! These events are not mutually exclusive (2 is both even and less than 3).
316 | We need to use the inclusion-exclusion principle.
317 |
318 |
319 |
320 | 2. P(even or greater than 4) = 4/6
321 |
322 | ✅ Correct! {2,4,6} ∪ {5,6} = {2,4,5,6}, so probability is 4/6.
323 |
324 |
325 |
326 | 3. P(prime or odd) = 5/6
327 |
328 | ✅ Correct! {2,3,5} ∪ {1,3,5} = {1,2,3,5}, so probability is 5/6.
329 |
330 | """
331 | )
332 | return
333 |
334 |
335 | @app.cell(hide_code=True)
336 | def _(mo):
337 | mo.md(
338 | """
339 | ## Summary
340 |
341 | You've learned:
342 |
343 | - How to identify mutually exclusive events
344 | - The addition rule for mutually exclusive events
345 | - The inclusion-exclusion principle for overlapping events
346 | - How to extend these concepts to multiple events
347 |
348 | In the next lesson, we'll explore **conditional probability** - how the probability
349 | of one event changes when we know another event has occurred.
350 | """
351 | )
352 | return
353 |
354 |
355 | if __name__ == "__main__":
356 | app.run()
357 |
--------------------------------------------------------------------------------
/probability/06_probability_of_and.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # "matplotlib",
6 | # "matplotlib-venn"
7 | # ]
8 | # ///
9 |
10 | import marimo
11 |
12 | __generated_with = "0.11.4"
13 | app = marimo.App(width="medium")
14 |
15 |
16 | @app.cell
17 | def _():
18 | import marimo as mo
19 | return (mo,)
20 |
21 |
22 | @app.cell
23 | def _():
24 | import matplotlib.pyplot as plt
25 | from matplotlib_venn import venn2
26 | return plt, venn2
27 |
28 |
29 | @app.cell(hide_code=True)
30 | def _(mo):
31 | mo.md(
32 | r"""
33 | # Probability of And
34 | _This notebook is a computational companion to the book ["Probability for Computer Scientists"](https://chrispiech.github.io/probabilityForComputerScientists/en/part1/prob_and/), by Stanford professor Chris Piech._
35 |
36 | When calculating the probability of both events occurring together, we need to consider whether the events are independent or dependent.
37 | Let's explore how to calculate $P(E \cap F)$, i.e. $P(E \text{ and } F)$, in different scenarios.
38 | """
39 | )
40 | return
41 |
42 |
43 | @app.cell(hide_code=True)
44 | def _(mo):
45 | mo.md(
46 | r"""
47 | ## And with Independent Events
48 |
49 | Two events $E$ and $F$ are **independent** if knowing one event occurred doesn't affect the probability of the other.
50 | For independent events:
51 |
52 | $P(E \text{ and } F) = P(E) \cdot P(F)$
53 |
54 | For example:
55 |
56 | - Rolling a 6 on one die and getting heads on a coin flip
57 | - Drawing a heart from a deck, replacing it, and drawing another heart
58 | - Getting a computer error on Monday vs. Tuesday
59 |
60 | Here's a Python function to calculate probability for independent events:
61 | """
62 | )
63 | return
64 |
65 |
66 | @app.cell
67 | def _():
68 | def calc_independent_prob(p_e, p_f):
69 | return p_e * p_f
70 |
71 | # Example 1: Rolling a die and flipping a coin
72 | p_six = 1/6 # P(rolling a 6)
73 | p_heads = 1/2 # P(getting heads)
74 | p_both = calc_independent_prob(p_six, p_heads)
75 | print(f"Example 1: P(rolling 6 AND getting heads) = {p_six:.3f} × {p_heads:.3f} = {p_both:.3f}")
76 | return calc_independent_prob, p_both, p_heads, p_six
77 |
78 |
79 | @app.cell
80 | def _(calc_independent_prob):
81 | # Example 2: Two independent system components failing
82 | p_cpu_fail = 0.05 # P(CPU failure)
83 | p_disk_fail = 0.03 # P(disk failure)
84 | p_both_fail = calc_independent_prob(p_cpu_fail, p_disk_fail)
85 | print(f"Example 2: P(both CPU and disk failing) = {p_cpu_fail:.3f} × {p_disk_fail:.3f} = {p_both_fail:.3f}")
86 | return p_both_fail, p_cpu_fail, p_disk_fail
87 |
88 |
89 | @app.cell(hide_code=True)
90 | def _(mo):
91 | mo.md(
92 | r"""
93 | ## And with Dependent Events
94 |
95 | For dependent events, we use the **chain rule**:
96 |
97 | $P(E \text{ and } F) = P(E) \cdot P(F|E)$
98 |
99 | where $P(F|E)$ is the probability of $F$ occurring given that $E$ has occurred.
100 |
101 | For example:
102 |
103 | - Drawing two hearts without replacement
104 | - Getting two consecutive heads in poker
105 | - System failures in connected components
106 |
107 | Let's implement this calculation:
108 | """
109 | )
110 | return
111 |
112 |
113 | @app.cell
114 | def _():
115 | def calc_dependent_prob(p_e, p_f_given_e):
116 | return p_e * p_f_given_e
117 |
118 | # Example 1: Drawing two hearts without replacement
119 | p_first_heart = 13/52 # P(first heart)
120 | p_second_heart = 12/51 # P(second heart | first heart)
121 | p_both_hearts = calc_dependent_prob(p_first_heart, p_second_heart)
122 | print(f"Example 1: P(two hearts) = {p_first_heart:.3f} × {p_second_heart:.3f} = {p_both_hearts:.3f}")
123 | return calc_dependent_prob, p_both_hearts, p_first_heart, p_second_heart
124 |
125 |
126 | @app.cell
127 | def _(calc_dependent_prob):
128 | # Example 2: Drawing two aces without replacement
129 | p_first_ace = 4/52 # P(first ace)
130 | p_second_ace = 3/51 # P(second ace | first ace)
131 | p_both_aces = calc_dependent_prob(p_first_ace, p_second_ace)
132 | print(f"Example 2: P(two aces) = {p_first_ace:.3f} × {p_second_ace:.3f} = {p_both_aces:.3f}")
133 | return p_both_aces, p_first_ace, p_second_ace
134 |
135 |
136 | @app.cell(hide_code=True)
137 | def _(mo):
138 | mo.md(
139 | r"""
140 | ## Multiple Events
141 |
142 | For multiple independent events:
143 |
144 | $P(E_1 \text{ and } E_2 \text{ and } \cdots \text{ and } E_n) = \prod_{i=1}^n P(E_i)$
145 |
146 | For dependent events:
147 |
148 | $P(E_1 \text{ and } E_2 \text{ and } \cdots \text{ and } E_n) = P(E_1) \cdot P(E_2|E_1) \cdot P(E_3|E_1,E_2) \cdots P(E_n|E_1,\ldots,E_{n-1})$
149 |
150 | Let's visualize these probabilities:
151 | """
152 | )
153 | return
154 |
155 |
156 | @app.cell(hide_code=True)
157 | def _(mo):
158 | mo.md(r"""### Interactive example""")
159 | return
160 |
161 |
162 | @app.cell
163 | def _(event_type):
164 | event_type
165 | return
166 |
167 |
168 | @app.cell(hide_code=True)
169 | def _(mo):
170 | event_type = mo.ui.dropdown(
171 | options=[
172 | "Independent AND (Die and Coin)",
173 | "Dependent AND (Sequential Cards)",
174 | "Multiple AND (System Components)"
175 | ],
176 | value="Independent AND (Die and Coin)",
177 | label="Select AND Probability Scenario"
178 | )
179 | return (event_type,)
180 |
181 |
182 | @app.cell(hide_code=True)
183 | def _(event_type, mo, plt, venn2):
184 | # Define the events and their probabilities
185 | events_data = {
186 | "Independent AND (Die and Coin)": {
187 | "sets": (0.33, 0.17, 0.08), # (die, coin, intersection)
188 | "labels": ("Die\nP(6)=1/6", "Coin\nP(H)=1/2"),
189 | "title": "Independent Events: Rolling a 6 AND Getting Heads",
190 | "explanation": r"""
191 | ### Independent Events: Die Roll and Coin Flip
192 |
193 | $P(\text{Rolling 6}) = \frac{1}{6} \approx 0.17$
194 |
195 | $P(\text{Getting Heads}) = \frac{1}{2} = 0.5$
196 |
197 | $P(\text{6 and Heads}) = \frac{1}{6} \times \frac{1}{2} = \frac{1}{12} \approx 0.08$
198 |
199 | These events are independent because the outcome of the die roll
200 | doesn't affect the coin flip, and vice versa.
201 | """,
202 | },
203 | "Dependent AND (Sequential Cards)": {
204 | "sets": (
205 | 0.25,
206 | 0.24,
207 | 0.06,
208 | ), # (first heart, second heart, intersection)
209 | "labels": ("First\nP(H₁)=13/52", "Second\nP(H₂|H₁)=12/51"),
210 | "title": "Dependent Events: Drawing Two Hearts",
211 | "explanation": r"""
212 | ### Dependent Events: Drawing Hearts
213 |
214 | $P(\text{First Heart}) = \frac{13}{52} = 0.25$
215 |
216 | $P(\text{Second Heart}|\text{First Heart}) = \frac{12}{51} \approx 0.24$
217 |
218 | $P(\text{Both Hearts}) = \frac{13}{52} \times \frac{12}{51} \approx 0.06$
219 |
220 | These events are dependent because drawing the first heart
221 | changes the probability of drawing the second heart.
222 | """,
223 | },
224 | "Multiple AND (System Components)": {
225 | "sets": (0.05, 0.03, 0.0015), # (CPU fail, disk fail, intersection)
226 | "labels": ("CPU\nP(C)=0.05", "Disk\nP(D)=0.03"),
227 | "title": "Independent System Failures",
228 | "explanation": r"""
229 | ### System Component Failures
230 |
231 | $P(\text{CPU Failure}) = 0.05$
232 |
233 | $P(\text{Disk Failure}) = 0.03$
234 |
235 | $P(\text{Both Fail}) = 0.05 \times 0.03 = 0.0015$
236 |
237 | Component failures are typically independent in **well-designed systems**,
238 | meaning one component's failure doesn't affect the other's probability of failing.
239 | """,
240 | },
241 | }
242 |
243 | # Get data for selected event type
244 | data = events_data[event_type.value]
245 |
246 | # Create visualization
247 | plt.figure(figsize=(10, 5))
248 | v = venn2(subsets=data["sets"], set_labels=data["labels"])
249 | plt.title(data["title"])
250 |
251 | # Display explanation alongside visualization
252 | mo.hstack([plt.gcf(), mo.md(data["explanation"])])
253 | return data, events_data, v
254 |
255 |
256 | @app.cell(hide_code=True)
257 | def _(mo):
258 | mo.md(
259 | r"""
260 | ## 🤔 Test Your Understanding
261 |
262 | Which of these statements about AND probability are true?
263 |
264 |
265 | 1. The probability of getting two sixes in a row with a fair die is 1/36
266 |
267 | ✅ True! Since die rolls are independent events:
268 | P(two sixes) = P(first six) × P(second six) = 1/6 × 1/6 = 1/36
269 |
270 |
271 |
272 | 2. When drawing cards without replacement, P(two kings) = 4/52 × 4/52
273 |
274 | ❌ False! This is a dependent event. The correct calculation is:
275 | P(two kings) = P(first king) × P(second king | first king) = 4/52 × 3/51
276 |
277 |
278 |
279 | 3. If P(A) = 0.3 and P(B) = 0.4, then P(A and B) must be 0.12
280 |
281 | ❌ False! P(A and B) = 0.12 only if A and B are independent events.
282 | If they're dependent, we need P(B|A) to calculate P(A and B).
283 |
284 |
285 |
286 | 4. The probability of rolling a six AND getting tails is (1/6 × 1/2)
287 |
288 | ✅ True! These are independent events, so we multiply their individual probabilities:
289 | P(six and tails) = P(six) × P(tails) = 1/6 × 1/2 = 1/12
290 |
291 | """
292 | )
293 | return
294 |
295 |
296 | @app.cell(hide_code=True)
297 | def _(mo):
298 | mo.md(
299 | """
300 | ## Summary
301 |
302 | You've learned:
303 |
304 | - How to identify independent vs dependent events
305 | - The multiplication rule for independent events
306 | - The chain rule for dependent events
307 | - How to extend these concepts to multiple events
308 |
309 | In the next lesson, we'll explore **law of total probability** in more detail, building on our understanding of various topics.
310 | """
311 | )
312 | return
313 |
314 |
315 | if __name__ == "__main__":
316 | app.run()
317 |
--------------------------------------------------------------------------------
/probability/README.md:
--------------------------------------------------------------------------------
1 | # Learn probability
2 |
3 | This collection of marimo notebooks teaches the fundamentals of probability,
4 | with an emphasis on computation with Python.
5 |
6 | Much of the structure and many explanations here are adapted from Chris Piech's [Probability for Computer Scientists](https://chrispiech.github.io/probabilityForComputerScientists/en/index.html) course reader.
7 |
8 |
9 | **Running notebooks.** To run a notebook locally, use
10 |
11 | ```bash
12 | uvx marimo edit
13 | ```
14 |
15 | For example, run the numbers tutorial with
16 |
17 | ```bash
18 | uvx marimo edit https://github.com/marimo-team/learn/blob/main/probability/01_sets.py
19 | ```
20 |
21 | You can also open notebooks in our online playground by appending `marimo.app/`
22 | to a notebook's URL: [marimo.app/https://github.com/marimo-team/learn/blob/main/probability/01_sets.py](https://marimo.app/https://github.com/marimo-team/learn/blob/main/probability/01_sets.py).
23 |
24 | **Thanks to all our notebook authors!**
25 |
26 | * [Srihari Thyagarajan](https://github.com/Haleshot)
27 |
--------------------------------------------------------------------------------
/python/001_numbers.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App()
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 🔢 Numbers
19 |
20 | This tutorial provides a brief overview of working with numbers.
21 |
22 | ## Number Types
23 |
24 | Python has several types of numbers:
25 |
26 | ```python
27 | integer = 42 # whole numbers (int)
28 | decimal = 3.14 # floating-point numbers (float)
29 | complex_num = 2 + 3j # complex numbers
30 | ```
31 |
32 | Below is an example number we'll use to explore operations.
33 | """
34 | )
35 | return
36 |
37 |
38 | @app.cell
39 | def _():
40 | number = 42
41 | return (number,)
42 |
43 |
44 | @app.cell(hide_code=True)
45 | def _(mo):
46 | mo.md(
47 | """
48 | ## Basic mathematical operations
49 |
50 | Python supports all standard mathematical operations.
51 |
52 | Try changing the value of `number` above and watch how the results change.
53 | """
54 | )
55 | return
56 |
57 |
58 | @app.cell
59 | def _(number):
60 | number + 10 # Addition
61 | return
62 |
63 |
64 | @app.cell
65 | def _(number):
66 | number - 5 # Subtraction
67 | return
68 |
69 |
70 | @app.cell
71 | def _(number):
72 | number * 3 # Multiplication
73 | return
74 |
75 |
76 | @app.cell
77 | def _(number):
78 | number / 2 # Division (always returns float)
79 | return
80 |
81 |
82 | @app.cell(hide_code=True)
83 | def _(mo):
84 | mo.md("""Python also has special division operators and power operations.""")
85 | return
86 |
87 |
88 | @app.cell
89 | def _(number):
90 | number // 5 # Floor division (rounds down)
91 | return
92 |
93 |
94 | @app.cell
95 | def _(number):
96 | number % 5 # Modulus (remainder)
97 | return
98 |
99 |
100 | @app.cell
101 | def _(number):
102 | number**2 # Exponentiation
103 | return
104 |
105 |
106 | @app.cell(hide_code=True)
107 | def _(mo):
108 | mo.md(
109 | """
110 | ## Type conversion
111 |
112 | You can convert between different number types. Try changing these values!
113 | """
114 | )
115 | return
116 |
117 |
118 | @app.cell
119 | def _():
120 | decimal_number = 3.14
121 | return (decimal_number,)
122 |
123 |
124 | @app.cell
125 | def _(decimal_number):
126 | int(decimal_number) # Convert to integer (truncates decimal part)
127 | return
128 |
129 |
130 | @app.cell
131 | def _(number):
132 | float(number) # Convert to "float" or decimal
133 | return
134 |
135 |
136 | @app.cell(hide_code=True)
137 | def _(mo):
138 | mo.md(
139 | """
140 | ## Built-in math functions
141 | Python provides many useful built-in functions for working with numbers:
142 | """
143 | )
144 | return
145 |
146 |
147 | @app.cell
148 | def _(number):
149 | abs(-number) # Absolute value
150 | return
151 |
152 |
153 | @app.cell
154 | def _():
155 | round(3.14159, 2) # Round to 2 decimal places
156 | return
157 |
158 |
159 | @app.cell
160 | def _():
161 | max(1, 5, 3, 7, 2) # Find maximum value
162 | return
163 |
164 |
165 | @app.cell
166 | def _():
167 | min(1, 5, 3, 7, 2) # Find minimum value
168 | return
169 |
170 |
171 | @app.cell(hide_code=True)
172 | def _(mo):
173 | mo.md(
174 | """
175 | ## Advanced operations
176 |
177 | For more complex mathematical operations, use Python's [math module](https://docs.python.org/3/library/math.html).
178 | """
179 | )
180 | return
181 |
182 |
183 | @app.cell
184 | def _():
185 | import math
186 | return (math,)
187 |
188 |
189 | @app.cell
190 | def _(math):
191 | math.sqrt(16)
192 | return
193 |
194 |
195 | @app.cell
196 | def _(math):
197 | math.sin(math.pi/2)
198 | return
199 |
200 |
201 | @app.cell
202 | def _(math):
203 | math.cos(0)
204 | return
205 |
206 |
207 | @app.cell
208 | def _(math):
209 | math.pi, math.e
210 | return
211 |
212 |
213 | @app.cell
214 | def _(math):
215 | math.log10(100)
216 | return
217 |
218 |
219 | @app.cell
220 | def _(math):
221 | math.log(math.e)
222 | return
223 |
224 |
225 | @app.cell(hide_code=True)
226 | def _(mo):
227 | mo.md("""
228 | ## Next steps:
229 |
230 | - Practice different mathematical operations
231 | - Experiment with type conversions
232 | - Try out the math module functions
233 |
234 | Keep calculating! 🧮✨
235 | """)
236 | return
237 |
238 |
239 | @app.cell
240 | def _():
241 | import marimo as mo
242 | return (mo,)
243 |
244 |
245 | if __name__ == "__main__":
246 | app.run()
247 |
--------------------------------------------------------------------------------
/python/002_strings.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App(width="medium")
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 🎭 Strings
19 |
20 | This notebook introduces **strings**, which are containers for text.
21 |
22 | ## Creating strings
23 | Create strings by wrapping text in quotes:
24 |
25 | ```python
26 | # Use double quotes
27 | greeting = "Hello, Python!"
28 |
29 | # or single quotes
30 | name = 'Alice'
31 |
32 | # or triple quotes
33 | multiline_string = \"""
34 | Dear, Alice,
35 | Nice to meet you.
36 | Sincerely,
37 | Bob.
38 | \"""
39 | ```
40 |
41 | Below is an example string.
42 | """
43 | )
44 | return
45 |
46 |
47 | @app.cell
48 | def _():
49 | text = "Python is amazing!"
50 | text
51 | return (text,)
52 |
53 |
54 | @app.cell(hide_code=True)
55 | def _(mo):
56 | mo.md(
57 | """
58 | ## Essential string operations
59 |
60 | Here are some methods for working with strings.
61 |
62 | Tip: Try changing the value of `text` above, and watch how the
63 | computed values below change.
64 | """
65 | )
66 | return
67 |
68 |
69 | @app.cell
70 | def _(text):
71 | # the `len` method returns the number of characters in the string.
72 | len(text)
73 | return
74 |
75 |
76 | @app.cell
77 | def _(text):
78 | text.upper()
79 | return
80 |
81 |
82 | @app.cell
83 | def _(text):
84 | text.lower()
85 | return
86 |
87 |
88 | @app.cell
89 | def _(text):
90 | text.title()
91 | return
92 |
93 |
94 | @app.cell(hide_code=True)
95 | def _(mo):
96 | mo.md("""Use string methods and the `in` operator to find things in strings.""")
97 | return
98 |
99 |
100 | @app.cell
101 | def _(text):
102 | # Returns the index of "is" in the string
103 | text.find("is")
104 | return
105 |
106 |
107 | @app.cell
108 | def _(text):
109 | "Python" in text
110 | return
111 |
112 |
113 | @app.cell
114 | def _(text):
115 | "Javascript" in text
116 | return
117 |
118 |
119 | @app.cell(hide_code=True)
120 | def _(mo):
121 | mo.md(
122 | """
123 | ## Inserting values in strings
124 |
125 | Modern Python uses f-strings to insert values into strings. For example,
126 | check out how the next cell greets you (and notice the `f''''`)!
127 |
128 | **Try it!** Enter your name in `my_name` below, then run the cell.
129 | """
130 | )
131 | return
132 |
133 |
134 | @app.cell
135 | def _():
136 | my_name = ""
137 | return (my_name,)
138 |
139 |
140 | @app.cell
141 | def _(my_name):
142 | f"Hello, {my_name}!"
143 | return
144 |
145 |
146 | @app.cell(hide_code=True)
147 | def _(mo):
148 | mo.md(
149 | """
150 | ## Working with parts of strings
151 | You can access any part of a string using its position (index):
152 | """
153 | )
154 | return
155 |
156 |
157 | @app.cell
158 | def _(text):
159 | first_letter = text[0]
160 | first_letter
161 | return (first_letter,)
162 |
163 |
164 | @app.cell
165 | def _(text):
166 | last_letter = text[-1]
167 | last_letter
168 | return (last_letter,)
169 |
170 |
171 | @app.cell
172 | def _(text):
173 | first_three = text[0:3]
174 | first_three
175 | return (first_three,)
176 |
177 |
178 | @app.cell
179 | def _(text):
180 | last_two = text[-2:]
181 | last_two
182 | return (last_two,)
183 |
184 |
185 | @app.cell(hide_code=True)
186 | def _(mo):
187 | mo.md(
188 | """
189 | ## Other helpful string methods
190 |
191 | Finally, here are some other helpful string methods. Feel free to try them out on your own strings by modifying the value of `sentence` below.
192 | """
193 | )
194 | return
195 |
196 |
197 | @app.cell
198 | def _():
199 | sentence = " python is fun "
200 | sentence
201 | return (sentence,)
202 |
203 |
204 | @app.cell
205 | def _(sentence):
206 | # Remove extra spaces
207 | sentence.strip()
208 | return
209 |
210 |
211 | @app.cell
212 | def _(sentence):
213 | # Split into a list of words
214 | sentence.split()
215 | return
216 |
217 |
218 | @app.cell
219 | def _(sentence):
220 | sentence.replace("fun", "awesome")
221 | return
222 |
223 |
224 | @app.cell
225 | def _():
226 | "123".isdigit(), "abc".isdigit()
227 | return
228 |
229 |
230 | @app.cell
231 | def _():
232 | "123".isalpha(), "abc".isalpha()
233 | return
234 |
235 |
236 | @app.cell
237 | def _():
238 | "Python3".isalnum()
239 | return
240 |
241 |
242 | @app.cell(hide_code=True)
243 | def _(mo):
244 | mo.md(
245 | r"""
246 | ## Next steps
247 |
248 | For a full primer on strings, check out the [official documentation](https://docs.python.org/3/library/string.html).
249 | """
250 | )
251 | return
252 |
253 |
254 | @app.cell
255 | def _():
256 | import marimo as mo
257 | return (mo,)
258 |
259 |
260 | if __name__ == "__main__":
261 | app.run()
262 |
--------------------------------------------------------------------------------
/python/003_collections.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App(width="medium")
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 📦 Collections
19 |
20 | A "collection" is a type of variable that holds multiple values.
21 |
22 | ## Lists
23 | Lists are ordered, mutable sequences. Create them using square brackets:
24 |
25 | ```python
26 | fruits = ["apple", "banana", "orange"]
27 | numbers = [1, 2, 3, 4, 5]
28 | mixed = [1, "hello", 3.14, True]
29 | ```
30 |
31 | Below is an example list we'll use to explore operations.
32 | """
33 | )
34 | return
35 |
36 |
37 | @app.cell
38 | def _():
39 | sample_list = [1, 2, 3, 4, 5]
40 | return (sample_list,)
41 |
42 |
43 | @app.cell(hide_code=True)
44 | def _(mo):
45 | mo.md(
46 | """
47 | ## List operations
48 |
49 | Here are common operations you can perform on lists.
50 |
51 | Try changing the values in `sample_list` above and watch the results change.
52 | """
53 | )
54 | return
55 |
56 |
57 | @app.cell
58 | def _(sample_list):
59 | len(sample_list) # List length
60 | return
61 |
62 |
63 | @app.cell
64 | def _(sample_list):
65 | extended_list = sample_list + [6] # Concatenate two lists
66 | extended_list
67 | return (extended_list,)
68 |
69 |
70 | @app.cell
71 | def _(extended_list):
72 | extended_list[0] # Access first element
73 | return
74 |
75 |
76 | @app.cell
77 | def _(extended_list):
78 | extended_list[-1] # Access last element
79 | return
80 |
81 |
82 | @app.cell(hide_code=True)
83 | def _(mo):
84 | mo.md(
85 | """
86 | ## Tuples
87 |
88 | Tuples are immutable sequences. They're like lists that can't be changed after creation:
89 | """
90 | )
91 | return
92 |
93 |
94 | @app.cell
95 | def _():
96 | coordinates = (10, 20)
97 | return (coordinates,)
98 |
99 |
100 | @app.cell
101 | def _(coordinates):
102 | x, y = coordinates # Tuple unpacking
103 | x
104 | return x, y
105 |
106 |
107 | @app.cell(hide_code=True)
108 | def _(mo):
109 | mo.md("""#### Tuple concatenation""")
110 | return
111 |
112 |
113 | @app.cell
114 | def _():
115 | tuple1 = (1, 2, 3)
116 | tuple2 = (4, 5, 6)
117 |
118 | tuple3 = tuple1 + tuple2
119 | tuple3
120 | return tuple1, tuple2, tuple3
121 |
122 |
123 | @app.cell(hide_code=True)
124 | def _(mo):
125 | mo.md(
126 | """
127 | ## Dictionaries
128 |
129 | Dictionaries store key-value pairs. They're perfect for mapping relationships:
130 | """
131 | )
132 | return
133 |
134 |
135 | @app.cell
136 | def _():
137 | person = {"name": "John Doe", "age": 25, "city": "New York"}
138 | return (person,)
139 |
140 |
141 | @app.cell
142 | def _(person):
143 | person["name"] # Access value by key
144 | return
145 |
146 |
147 | @app.cell
148 | def _(person):
149 | person.keys() # Get all keys
150 | return
151 |
152 |
153 | @app.cell
154 | def _(person):
155 | person.values() # Get all values
156 | return
157 |
158 |
159 | @app.cell(hide_code=True)
160 | def _(mo):
161 | mo.md(
162 | """
163 | ## Sets
164 |
165 | Sets are unordered collections of unique elements:
166 | """
167 | )
168 | return
169 |
170 |
171 | @app.cell
172 | def _():
173 | numbers_set = {1, 2, 3, 3, 2, 1} # Duplicates are removed
174 | return (numbers_set,)
175 |
176 |
177 | @app.cell
178 | def _(numbers_set):
179 | numbers_set | {4} # Add a new element
180 | return
181 |
182 |
183 | @app.cell
184 | def _():
185 | {1, 2, 3} & {3, 4, 5} # Find common elements
186 | return
187 |
188 |
189 | @app.cell(hide_code=True)
190 | def _(mo):
191 | mo.md(
192 | """
193 | ## Collection methods and operations
194 |
195 | Here are some common operations across collections:
196 |
197 | ```python
198 | # Lists
199 | my_list = [1, 2, 3]
200 | my_list.insert(0, 0) # Insert at position
201 | my_list.remove(2) # Remove first occurrence
202 | my_list.sort() # Sort in place
203 | sorted_list = sorted(my_list) # Return new sorted list
204 |
205 | # Dictionaries
206 | my_dict = {"a": 1}
207 | my_dict.update({"b": 2}) # Add new key-value pairs
208 | my_dict.get("c", "Not found") # Safe access with default
209 |
210 | # Sets
211 | set_a = {1, 2, 3}
212 | set_b = {3, 4, 5}
213 | set_a.union(set_b) # Combine sets
214 | set_a.difference(set_b) # Elements in A but not in B
215 | ```
216 | """
217 | )
218 | return
219 |
220 |
221 | @app.cell(hide_code=True)
222 | def _(mo):
223 | mo.md(
224 | r"""
225 | ## Documentation
226 |
227 | See the official [Python tutorial on data structures](https://docs.python.org/3/tutorial/datastructures.html) for more in-depth information.
228 | """
229 | )
230 | return
231 |
232 |
233 | @app.cell
234 | def _():
235 | import marimo as mo
236 | return (mo,)
237 |
238 |
239 | if __name__ == "__main__":
240 | app.run()
241 |
--------------------------------------------------------------------------------
/python/004_conditional_logic.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App()
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 🔄 Conditional logic
19 |
20 | This tutorial teaches you how to how to make **decisions** in your code, using
21 | Python's conditional statements.
22 |
23 | ## If Statements
24 | The foundation of decision-making in Python:
25 | ```python
26 | if condition:
27 | # code to run if condition is True
28 | elif another_condition:
29 | # code to run if another_condition is True
30 | else:
31 | # code to run if no conditions are True
32 | ```
33 | Let's explore with some examples:
34 | """
35 | )
36 | return
37 |
38 |
39 | @app.cell(hide_code=True)
40 | def _(mo):
41 | mo.md("""**Try it!** Try changing the value of `42` below, and see how the output changes.""")
42 | return
43 |
44 |
45 | @app.cell
46 | def _():
47 | number = 42
48 | return (number,)
49 |
50 |
51 | @app.cell(hide_code=True)
52 | def _(mo):
53 | mo.md(
54 | r"""
55 | Compare numbers using operators like
56 |
57 | - `>`
58 | - `>=`
59 | - `<`
60 | - `<=`
61 | - `==` (note the two equal signs!)
62 | """
63 | )
64 | return
65 |
66 |
67 | @app.cell
68 | def _(mo, number):
69 | if number > 42:
70 | result = "Greater than 42"
71 | elif number == 42:
72 | result = "Equal to 42!"
73 | else:
74 | result = "Less than 42"
75 | mo.md(result)
76 | return (result,)
77 |
78 |
79 | @app.cell(hide_code=True)
80 | def _(mo):
81 | mo.md(
82 | r"""
83 | ### Interactive decision making
84 | **Try it!** Try changing the conditions below and see how the results change:
85 | """
86 | )
87 | return
88 |
89 |
90 | @app.cell(hide_code=True)
91 | def _(mo, threshold, value):
92 | mo.hstack([value, threshold], justify="start")
93 | return
94 |
95 |
96 | @app.cell(hide_code=True)
97 | def _(mo):
98 | value = mo.ui.number(value=25, start=0, stop=100, label="Enter a number")
99 | threshold = mo.ui.slider(value=50, start=0, stop=100, label="Set threshold")
100 | return threshold, value
101 |
102 |
103 | @app.cell(hide_code=True)
104 | def _(mo, threshold, value):
105 | if value.value > threshold.value:
106 | decision = f"{value.value} is greater than {threshold.value}"
107 | elif value.value == threshold.value:
108 | decision = f"{value.value} is equal to {threshold.value}"
109 | else:
110 | decision = f"{value.value} is less than {threshold.value}"
111 |
112 | mo.hstack(
113 | [
114 | mo.md(f"**Decision**: {decision}"),
115 | mo.md(
116 | f"**Threshold cleared?**: {'✅' if value.value >= threshold.value else '❌'}"
117 | ),
118 | ],
119 | justify="space-around",
120 | )
121 | return (decision,)
122 |
123 |
124 | @app.cell(hide_code=True)
125 | def _(mo):
126 | mo.md(
127 | r"""
128 | ## Boolean operations
129 | Python uses boolean operators to combine conditions:
130 |
131 | - `and`: Both conditions must be True
132 |
133 | - `or`: At least one condition must be True
134 |
135 | - `not`: Inverts the condition
136 | """
137 | )
138 | return
139 |
140 |
141 | @app.cell(hide_code=True)
142 | def _(mo):
143 | _text = mo.md("""
144 | - Try different combinations of age and ID status
145 | - Notice how both conditions must be True to allow voting
146 | - Experiment with edge cases (exactly 18, no ID, etc.)
147 | """)
148 | mo.accordion({"💡 Experiment Tips": _text})
149 | return
150 |
151 |
152 | @app.cell(hide_code=True)
153 | def _(age, has_id, mo):
154 | mo.hstack([age, has_id], justify="start")
155 | return
156 |
157 |
158 | @app.cell(hide_code=True)
159 | def _(mo):
160 | age = mo.ui.number(value=18, start=0, stop=120, label="Age")
161 | has_id = mo.ui.switch(value=True, label="Has ID")
162 | return age, has_id
163 |
164 |
165 | @app.cell(hide_code=True)
166 | def _(age, has_id, mo):
167 | can_vote = age.value >= 18 and has_id.value
168 |
169 | explanation = f"""
170 | ### Voting eligibility check
171 |
172 | Current Status:
173 |
174 | - Age: {age.value} years old
175 |
176 | - Has ID: {"Yes" if has_id.value else "No"}
177 |
178 | - Can Vote: {"Yes ✅" if can_vote else "No ❌"}
179 |
180 | Reason: {
181 | "Both age and ID requirements met"
182 | if can_vote
183 | else "Missing " + ("required age" if age.value < 18 else "valid ID")
184 | }
185 | """
186 |
187 | mo.md(explanation)
188 | return can_vote, explanation
189 |
190 |
191 | @app.cell(hide_code=True)
192 | def _(mo):
193 | mo.md(r"""**Try it!** Write Python code that computes whether an individual can vote.""")
194 | return
195 |
196 |
197 | @app.cell
198 | def _():
199 | my_age = 18
200 | return (my_age,)
201 |
202 |
203 | @app.cell
204 | def _():
205 | has_an_id = False
206 | return (has_an_id,)
207 |
208 |
209 | @app.cell(hide_code=True)
210 | def _(mo):
211 | mo.md(
212 | """
213 | ## Complex conditions
214 | Combine multiple conditions for more sophisticated logic:
215 | ```python
216 | # Multiple conditions
217 | if (age >= 18 and has_id) or has_special_permission:
218 | print("Access granted")
219 |
220 | # Nested conditions
221 | if age >= 18:
222 | if has_id:
223 | print("Full access")
224 | else:
225 | print("Limited access")
226 | ```
227 | """
228 | )
229 | return
230 |
231 |
232 | @app.cell(hide_code=True)
233 | def _(humidity, mo, temp, wind):
234 | mo.hstack([temp, humidity, wind])
235 | return
236 |
237 |
238 | @app.cell(hide_code=True)
239 | def _(mo):
240 | temp = mo.ui.number(value=25, start=-20, stop=50, label="Temperature (°C)")
241 | humidity = mo.ui.slider(value=60, start=0, stop=100, label="Humidity (%)")
242 | wind = mo.ui.number(value=10, start=0, stop=100, label="Wind Speed (km/h)")
243 | return humidity, temp, wind
244 |
245 |
246 | @app.cell(hide_code=True)
247 | def _(humidity, mo, temp, wind):
248 | def get_weather_advice():
249 | conditions = []
250 |
251 | if temp.value > 30:
252 | conditions.append("🌡️ High temperature")
253 | elif temp.value < 10:
254 | conditions.append("❄️ Cold temperature")
255 |
256 | if humidity.value > 80:
257 | conditions.append("💧 High humidity")
258 | elif humidity.value < 30:
259 | conditions.append("🏜️ Low humidity")
260 |
261 | if wind.value > 30:
262 | conditions.append("💨 Strong winds")
263 |
264 | return conditions
265 |
266 |
267 | conditions = get_weather_advice()
268 |
269 | message = f"""
270 | ### Weather analysis
271 |
272 | Current Conditions:
273 |
274 | - Temperature: {temp.value}°C
275 |
276 | - Humidity: {humidity.value}%
277 |
278 | - Wind Speed: {wind.value} km/h
279 |
280 | Alerts: {", ".join(conditions) if conditions else "No special alerts"}
281 | """
282 |
283 | mo.md(message)
284 | return conditions, get_weather_advice, message
285 |
286 |
287 | @app.cell(hide_code=True)
288 | def _(mo):
289 | mo.md("""
290 | ## Next steps
291 |
292 | - Practice combining multiple conditions
293 | - Explore nested if statements
294 | - Try creating your own complex decision trees
295 |
296 | Keep coding! 🎯✨
297 | """)
298 | return
299 |
300 |
301 | @app.cell
302 | def _():
303 | import marimo as mo
304 | return (mo,)
305 |
306 |
307 | if __name__ == "__main__":
308 | app.run()
309 |
--------------------------------------------------------------------------------
/python/005_loops.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App()
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 🔄 Loops
19 |
20 | Let's learn how Python helps us repeat tasks efficiently with loops.
21 |
22 | A "loop" is a way to execute a block of code multiple times. Python has two
23 | main types of loops:
24 |
25 | ```python
26 | # For loop: when you know how many times to repeat
27 | for i in range(5):
28 | print(i)
29 |
30 | # While loop: when you don't know how many repetitions
31 | while condition:
32 | do_something()
33 | ```
34 |
35 | Let's start with a simple list to explore loops. Feel free to modify this list and see how the subsequent outputs change.
36 | """
37 | )
38 | return
39 |
40 |
41 | @app.cell
42 | def _():
43 | sample_fruits = ["apple", "banana", "orange", "grape"]
44 | return (sample_fruits,)
45 |
46 |
47 | @app.cell(hide_code=True)
48 | def _(mo):
49 | mo.md(
50 | """
51 | ## The for loop
52 |
53 | The for loop is perfect for iterating over sequences.
54 | Try changing the `sample_fruits` list above and see how the output changes.
55 | """
56 | )
57 | return
58 |
59 |
60 | @app.cell
61 | def _(sample_fruits):
62 | for _fruit in sample_fruits:
63 | print(f"I like {_fruit}s!")
64 | return
65 |
66 |
67 | @app.cell(hide_code=True)
68 | def _(mo):
69 | mo.md(
70 | """
71 | ### Getting the position of an item
72 |
73 | When you need both the item and its position, use `enumerate()`:
74 | """
75 | )
76 | return
77 |
78 |
79 | @app.cell
80 | def _(sample_fruits):
81 | for _idx, _fruit in enumerate(sample_fruits):
82 | print(f"{_idx + 1}. {_fruit}")
83 | return
84 |
85 |
86 | @app.cell(hide_code=True)
87 | def _(mo):
88 | mo.md(
89 | """
90 | ### Iterating over a range of numbers
91 |
92 | `range()` is a powerful function for generating sequences of numbers:
93 | """
94 | )
95 | return
96 |
97 |
98 | @app.cell
99 | def _():
100 | print("range(5):", list(range(5)))
101 | print("range(2, 5):", list(range(2, 5)))
102 | print("range(0, 10, 2):", list(range(0, 10, 2)))
103 | return
104 |
105 |
106 | @app.cell
107 | def _():
108 | for _i in range(5):
109 | print(_i)
110 | return
111 |
112 |
113 | @app.cell(hide_code=True)
114 | def _(mo):
115 | mo.md(
116 | """
117 | ## The `while` loop
118 |
119 | While loops continue as long as a condition is `True`.
120 | """
121 | )
122 | return
123 |
124 |
125 | @app.cell
126 | def _():
127 | _count = 0
128 | while _count < 5:
129 | print(f"The count is {_count}")
130 | _count += 1
131 | return
132 |
133 |
134 | @app.cell(hide_code=True)
135 | def _(mo):
136 | mo.md(
137 | """
138 | ## Controlling loop execution
139 |
140 | Python provides several ways to control loop execution:
141 |
142 | - `break`: exit the loop immediately
143 |
144 | - `continue`: skip to the next iteration
145 |
146 | These can be used with both `for` and `while` loops.
147 | """
148 | )
149 | return
150 |
151 |
152 | @app.cell
153 | def _():
154 | for _i in range(1, 6):
155 | if _i == 4:
156 | print("Breaking out of the loop.")
157 | break
158 | print(_i)
159 | return
160 |
161 |
162 | @app.cell
163 | def _():
164 | for _i in range(1, 6):
165 | if _i == 3:
166 | continue
167 | print(_i)
168 | return
169 |
170 |
171 | @app.cell(hide_code=True)
172 | def _(mo):
173 | mo.md(
174 | """
175 | ## Practical loop patterns
176 |
177 | Here are some common patterns you'll use with loops:
178 |
179 | ```python
180 | # Pattern 1: Accumulator
181 | value = 0
182 | for num in [1, 2, 3, 4, 5]:
183 | value += num
184 |
185 | # Pattern 2: Search
186 | found = False
187 | for item in items:
188 | if condition:
189 | found = True
190 | break
191 |
192 | # Pattern 3: Filter
193 | filtered = []
194 | for item in items:
195 | if condition:
196 | filtered.append(item)
197 | ```
198 | """
199 | )
200 | return
201 |
202 |
203 | @app.cell(hide_code=True)
204 | def _(mo):
205 | mo.md(
206 | r"""
207 | ## Next steps
208 |
209 | Check out the official [Python docs on loops and control flow](https://docs.python.org/3/tutorial/controlflow.html).
210 | """
211 | )
212 | return
213 |
214 |
215 | @app.cell
216 | def _():
217 | import marimo as mo
218 | return (mo,)
219 |
220 |
221 | if __name__ == "__main__":
222 | app.run()
223 |
--------------------------------------------------------------------------------
/python/006_dictionaries.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App()
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 📚 Dictionaries
19 |
20 | Dictionaries are collections of key-value pairs, with each key associated with a value. The keys are unique, meaning they show up only once.
21 |
22 | ## Creating dictionaries
23 | Here are a few ways to create dictionaries:
24 |
25 | ```python
26 | simple_dict = {"name": "Alice", "age": 25}
27 | empty_dict = dict()
28 | from_pairs = dict([("a", 1), ("b", 2)])
29 | ```
30 |
31 | Below is a sample dictionary we'll use to explore operations.
32 | """
33 | )
34 | return
35 |
36 |
37 | @app.cell
38 | def _():
39 | sample_dict = {
40 | "name": "Python",
41 | "type": "programming language",
42 | "year": 1991,
43 | "creator": "Guido van Rossum",
44 | "is_awesome": True,
45 | }
46 | return (sample_dict,)
47 |
48 |
49 | @app.cell(hide_code=True)
50 | def _(mo):
51 | mo.md(
52 | """
53 | ## Operations
54 |
55 | Let's explore how to work with dictionaries.
56 |
57 | **Try it!** Try modifying the `sample_dict` above and watch how the results change!
58 | """
59 | )
60 | return
61 |
62 |
63 | @app.cell(hide_code=True)
64 | def _(mo):
65 | mo.md(
66 | r"""
67 | ### Accessing values by key
68 |
69 | Access values by key using square brackets, like below
70 | """
71 | )
72 | return
73 |
74 |
75 | @app.cell
76 | def _(sample_dict):
77 | sample_dict['name'], sample_dict['year']
78 | return
79 |
80 |
81 | @app.cell(hide_code=True)
82 | def _(mo):
83 | mo.md(r"""If you're not sure if a dictionary has a given key, use `get()`:""")
84 | return
85 |
86 |
87 | @app.cell
88 | def _(sample_dict):
89 | sample_dict.get("version", "Not specified"), sample_dict.get("type", "Unknown")
90 | return
91 |
92 |
93 | @app.cell(hide_code=True)
94 | def _(mo):
95 | mo.md(
96 | """
97 | ## Enumerating dictionary contents
98 |
99 | Python dictionaries come with helpful methods to enumerate keys, values, and pairs.
100 | """
101 | )
102 | return
103 |
104 |
105 | @app.cell
106 | def _(sample_dict):
107 | print(list(sample_dict.keys()))
108 | return
109 |
110 |
111 | @app.cell
112 | def _(sample_dict):
113 | print(list(sample_dict.values()))
114 | return
115 |
116 |
117 | @app.cell
118 | def _(sample_dict):
119 | print(list(sample_dict.items()))
120 | return
121 |
122 |
123 | @app.cell
124 | def _():
125 | def demonstrate_modification():
126 | _dict = {"a": 1, "b": 2}
127 | print("Original:", _dict)
128 |
129 | # Adding/updating
130 | _dict.update({"c": 3, "b": 22})
131 | print("After update:", _dict)
132 |
133 | # Removing
134 | _removed = _dict.pop("b")
135 | print(f"Removed {_removed}, Now:", _dict)
136 |
137 |
138 | demonstrate_modification()
139 | return (demonstrate_modification,)
140 |
141 |
142 | @app.cell(hide_code=True)
143 | def _(mo):
144 | mo.md(
145 | """
146 | ## Dictionary comprehension
147 |
148 | Create dictionaries efficiently with dictionary comprehensions:
149 | """
150 | )
151 | return
152 |
153 |
154 | @app.cell
155 | def _():
156 | print({x: x**2 for x in range(5)})
157 | return
158 |
159 |
160 | @app.cell
161 | def _():
162 | print({x: x**2 for x in range(5) if x % 2 == 0})
163 | return
164 |
165 |
166 | @app.cell(hide_code=True)
167 | def _(mo):
168 | mo.md(
169 | """
170 | ## Nested dictionaries
171 |
172 | Dictionaries can contain other dictionaries, creating complex data structures:
173 | """
174 | )
175 | return
176 |
177 |
178 | @app.cell
179 | def _():
180 | nested_data = {
181 | "users": {
182 | "alice": {
183 | "age": 25,
184 | "email": "alice@example.com",
185 | "interests": ["python", "data science"],
186 | },
187 | "bob": {
188 | "age": 30,
189 | "email": "bob@example.com",
190 | "interests": ["web dev", "gaming"],
191 | },
192 | }
193 | }
194 | return (nested_data,)
195 |
196 |
197 | @app.cell
198 | def _(mo, nested_data):
199 | mo.md(f"Alice's age: {nested_data['users']['alice']['age']}")
200 | return
201 |
202 |
203 | @app.cell
204 | def _(mo, nested_data):
205 | mo.md(f"Bob's interests: {nested_data['users']['bob']['interests']}")
206 | return
207 |
208 |
209 | @app.cell(hide_code=True)
210 | def _(mo):
211 | mo.md(
212 | """
213 | ## Common dictionary patterns
214 |
215 | Here are some useful patterns when working with dictionaries:
216 |
217 | ```python
218 | # Pattern 1: Counting items
219 | counter = {}
220 | for item in items:
221 | counter[item] = counter.get(item, 0) + 1
222 |
223 | # Pattern 2: Grouping data
224 | groups = {}
225 | for item in _items:
226 | key = get_group_key(item)
227 | groups.setdefault(key, []).append(item)
228 |
229 | # Pattern 3: Caching/Memoization
230 | cache = {}
231 | def expensive_function(arg):
232 | if arg not in cache:
233 | cache[arg] = compute_result(arg)
234 | return cache[arg]
235 | ```
236 | """
237 | )
238 | return
239 |
240 |
241 | @app.cell
242 | def _():
243 | import marimo as mo
244 | return (mo,)
245 |
246 |
247 | if __name__ == "__main__":
248 | app.run()
249 |
--------------------------------------------------------------------------------
/python/007_advanced_collections.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App()
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 🔄 Advanced collections
19 |
20 | This tutorials hows advanced patterns for working with collections.
21 |
22 | ## Lists of dictionaries
23 |
24 | A common pattern in data handling is working with lists of dictionaries:
25 | this is helpful for representing structured data like records or entries.
26 | """
27 | )
28 | return
29 |
30 |
31 | @app.cell
32 | def _():
33 | # Sample data: List of user records
34 | users_data = [
35 | {"id": 1, "name": "Alice", "skills": ["Python", "SQL"]},
36 | {"id": 2, "name": "Bob", "skills": ["JavaScript", "HTML"]},
37 | {"id": 3, "name": "Charlie", "skills": ["Python", "Java"]}
38 | ]
39 | return (users_data,)
40 |
41 |
42 | @app.cell(hide_code=True)
43 | def _(mo):
44 | mo.md(
45 | """
46 | Let's explore common operations on structured data.
47 |
48 | **Try it!** Try modifying the `users_data` above and see how the results
49 | change!
50 | """
51 | )
52 | return
53 |
54 |
55 | @app.cell
56 | def _(users_data):
57 | # Finding users with specific skills
58 | python_users = [
59 | user["name"] for user in users_data if "Python" in user["skills"]
60 | ]
61 | print("Python developers:", python_users)
62 | return (python_users,)
63 |
64 |
65 | @app.cell(hide_code=True)
66 | def _(mo):
67 | mo.md(
68 | """
69 | ## Nested data structures
70 |
71 | Python collections can be nested in various ways to represent complex data:
72 | """
73 | )
74 | return
75 |
76 |
77 | @app.cell
78 | def _():
79 | # Complex nested structure
80 | project_data = {
81 | "web_app": {
82 | "frontend": ["HTML", "CSS", "React"],
83 | "backend": {
84 | "languages": ["Python", "Node.js"],
85 | "databases": ["MongoDB", "PostgreSQL"]
86 | }
87 | },
88 | "mobile_app": {
89 | "platforms": ["iOS", "Android"],
90 | "technologies": {
91 | "iOS": ["Swift", "SwiftUI"],
92 | "Android": ["Kotlin", "Jetpack Compose"]
93 | }
94 | }
95 | }
96 | return (project_data,)
97 |
98 |
99 | @app.cell
100 | def _(project_data):
101 | # Nested data accessing
102 | backend_langs = project_data["web_app"]["backend"]["languages"]
103 | print("Backend languages:", backend_langs)
104 |
105 | ios_tech = project_data["mobile_app"]["technologies"]["iOS"]
106 | print("iOS technologies:", ios_tech)
107 | return backend_langs, ios_tech
108 |
109 |
110 | @app.cell(hide_code=True)
111 | def _(mo):
112 | mo.md(
113 | """
114 | ### Example: data transformation
115 |
116 | Let's explore how to transform and reshape collection data:
117 | """
118 | )
119 | return
120 |
121 |
122 | @app.cell
123 | def _():
124 | # Data-sample for transformation
125 | sales_data = [
126 | {"date": "2024-01", "product": "A", "units": 100},
127 | {"date": "2024-01", "product": "B", "units": 150},
128 | {"date": "2024-02", "product": "A", "units": 120},
129 | {"date": "2024-02", "product": "B", "units": 130}
130 | ]
131 | return (sales_data,)
132 |
133 |
134 | @app.cell
135 | def _(sales_data):
136 | # Transform to product-based structure
137 | product_sales = {}
138 | for sale in sales_data:
139 | if sale["product"] not in product_sales:
140 | product_sales[sale["product"]] = []
141 | product_sales[sale["product"]].append({
142 | "date": sale["date"],
143 | "units": sale["units"]
144 | })
145 |
146 | print("Sales by product:", product_sales)
147 | return product_sales, sale
148 |
149 |
150 | @app.cell(hide_code=True)
151 | def _(mo):
152 | mo.md(
153 | """
154 | ## More collection utilities
155 |
156 | Python's `collections` module provides specialized container datatypes:
157 |
158 | ```python
159 | from collections import defaultdict, Counter, deque
160 |
161 | # defaultdict - dictionary with default factory
162 | word_count = defaultdict(int)
163 | for word in words:
164 | word_count[word] += 1
165 |
166 | # Counter - count hashable objects
167 | colors = Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])
168 | print(colors.most_common(2)) # Top 2 most common colors
169 |
170 | # deque - double-ended queue
171 | history = deque(maxlen=10) # Only keeps last 10 items
172 | history.append(item)
173 | ```
174 | """
175 | )
176 | return
177 |
178 |
179 | @app.cell
180 | def _():
181 | from collections import Counter
182 |
183 | # Example using Counter
184 | programming_languages = [
185 | "Python", "JavaScript", "Python", "Java",
186 | "Python", "JavaScript", "C++", "Java"
187 | ]
188 |
189 | language_count = Counter(programming_languages)
190 | print("Language frequency:", dict(language_count))
191 | print("Most common language:", language_count.most_common(1))
192 | return Counter, language_count, programming_languages
193 |
194 |
195 | @app.cell(hide_code=True)
196 | def _(mo):
197 | mo.md(
198 | """
199 | ## Next steps
200 |
201 | For a reference on the `collections` module, see [the official Python
202 | docs](https://docs.python.org/3/library/collections.html).
203 | """
204 | )
205 | return
206 |
207 |
208 | @app.cell
209 | def _():
210 | import marimo as mo
211 | return (mo,)
212 |
213 |
214 | if __name__ == "__main__":
215 | app.run()
216 |
--------------------------------------------------------------------------------
/python/008_functions.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App()
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 🧩 Functions
19 |
20 | This tutorial is about an important topic: **functions.**
21 |
22 | A function is a reusable block of code, similar in spirit to a mathematical function. Each function has a **name**, and accepts some number of **arguments**. These arguments are used in the function "body" (its block of code), and each function can **return** values.
23 |
24 | **Example.** Below is an example function.
25 | """
26 | )
27 | return
28 |
29 |
30 | @app.cell
31 | def _():
32 | def greet(your_name):
33 | return f"Hello, {your_name}!"
34 | return (greet,)
35 |
36 |
37 | @app.cell(hide_code=True)
38 | def _(mo):
39 | mo.md(r"""The keyword `def` starts the function definition. The function's **name** is `greet`. It accepts one **argument** called `your_name`. It then creates a string and **returns** it.""")
40 | return
41 |
42 |
43 | @app.cell(hide_code=True)
44 | def _(mo):
45 | mo.md(
46 | """
47 | In the next cell, we **call** the function with a value and assign its return value to a variable.
48 |
49 | **Try it!** Try changing the input to the function.
50 | """
51 | )
52 | return
53 |
54 |
55 | @app.cell
56 | def _(greet):
57 | greeting = greet(your_name="")
58 | greeting
59 | return (greeting,)
60 |
61 |
62 | @app.cell(hide_code=True)
63 | def _(mo):
64 | mo.md(
65 | """
66 | **Why use functions?** Functions help you:
67 |
68 | - Break down complex problems
69 | - Create reusable code blocks
70 | - Improve code readability
71 | """
72 | )
73 | return
74 |
75 |
76 | @app.cell(hide_code=True)
77 | def _(mo):
78 | mo.md(
79 | """
80 | ## Default parameters
81 | Make your functions more flexible by providing default values.
82 | """
83 | )
84 | return
85 |
86 |
87 | @app.cell
88 | def _():
89 | def create_profile(name, age=18):
90 | return f"{name} is {age} years old"
91 | return (create_profile,)
92 |
93 |
94 | @app.cell
95 | def _(create_profile):
96 | # Example usage
97 | example_name = "Alex"
98 | example_profile = create_profile(example_name)
99 | example_profile
100 | return example_name, example_profile
101 |
102 |
103 | @app.cell(hide_code=True)
104 | def _(mo):
105 | mo.md("""You can also create functions that reference variables outside the function body. This is called 'closing over' variables""")
106 | return
107 |
108 |
109 | @app.cell
110 | def _():
111 | base_multiplier = 2
112 |
113 | def multiplier(x):
114 | """
115 | Create a function that multiplies input by a base value.
116 |
117 | This demonstrates how functions can 'close over'
118 | values from their surrounding scope.
119 | """
120 | return x * base_multiplier
121 | return base_multiplier, multiplier
122 |
123 |
124 | @app.cell
125 | def _(multiplier):
126 | print([multiplier(num) for num in [1, 2, 3]])
127 | return
128 |
129 |
130 | @app.cell(hide_code=True)
131 | def _(mo):
132 | mo.md(
133 | """
134 | ## Returning multiple values
135 |
136 | Functions can return multiple values: just separate the values to return by
137 | commas. Check out the next cell for an example.
138 | """
139 | )
140 | return
141 |
142 |
143 | @app.cell
144 | def _():
145 | def weather_analysis(temp):
146 | """
147 | Analyze weather based on temperature.
148 |
149 | Args:
150 | temp (float): Temperature in Celsius
151 |
152 | Returns:
153 | tuple: Weather status, recommendation, warning level
154 | """
155 | if temp <= 0:
156 | return "Freezing", "Wear heavy coat", "High"
157 | elif 0 < temp <= 15:
158 | return "Cold", "Layer up", "Medium"
159 | elif 15 < temp <= 25:
160 | return "Mild", "Comfortable clothing", "Low"
161 | else:
162 | return "Hot", "Stay hydrated", "High"
163 | return (weather_analysis,)
164 |
165 |
166 | @app.cell
167 | def _():
168 | temperature = 25
169 | return (temperature,)
170 |
171 |
172 | @app.cell
173 | def _(temperature, weather_analysis):
174 | status, recommendation, warning_level = weather_analysis(temperature)
175 | status, recommendation, warning_level
176 | return recommendation, status, warning_level
177 |
178 |
179 | @app.cell
180 | def _():
181 | import marimo as mo
182 | return (mo,)
183 |
184 |
185 | if __name__ == "__main__":
186 | app.run()
187 |
--------------------------------------------------------------------------------
/python/009_modules.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App()
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 🧩 Using modules
19 |
20 | A `module` in Python is a Python file that defines functions and variables. Modules can be `imported` into other Python files, letting you reuse their
21 | functions and variables.
22 |
23 | We have already seen some modules in previous tutorials, including the `math`
24 | module. Python comes with many other modules built-in.
25 | """
26 | )
27 | return
28 |
29 |
30 | @app.cell(hide_code=True)
31 | def _(mo):
32 | mo.md(
33 | """
34 | ## The Python standard library
35 |
36 | Python's "standard library" provides many modules, for many kinds of tasks.
37 |
38 | ```python
39 | # String manipulation
40 | import string
41 |
42 | # Operating system interactions
43 | import os
44 |
45 | # Date and time handling
46 | import datetime
47 |
48 | # Mathematical operations
49 | import math
50 | ```
51 |
52 | See the [Python standard library documentation](https://docs.python.org/3/library/) for a full reference
53 | """
54 | )
55 | return
56 |
57 |
58 | @app.cell(hide_code=True)
59 | def _(mo):
60 | mo.md("""### Example""")
61 | return
62 |
63 |
64 | @app.cell
65 | def _():
66 | import string
67 | import os
68 | import datetime
69 | import math
70 |
71 | # Example of using imported modules
72 | def demonstrate_standard_library_usage():
73 | # String module: get all punctuation
74 | punctuation_example = string.punctuation
75 |
76 | # OS module: get current working directory
77 | current_dir = os.getcwd()
78 |
79 | # Datetime module: get current date
80 | today = datetime.date.today()
81 |
82 | # Math module: calculate square root
83 | sqrt_example = math.sqrt(16)
84 |
85 | return {
86 | "Punctuation": punctuation_example,
87 | "Current Directory": current_dir,
88 | "Today's Date": today,
89 | "Square Root Example": sqrt_example
90 | }
91 |
92 | # Run the demonstration
93 | module_usage_examples = demonstrate_standard_library_usage()
94 | module_usage_examples
95 | return (
96 | datetime,
97 | demonstrate_standard_library_usage,
98 | math,
99 | module_usage_examples,
100 | os,
101 | string,
102 | )
103 |
104 |
105 | @app.cell(hide_code=True)
106 | def _(mo):
107 | mo.md(
108 | """
109 | ## Import syntax
110 |
111 | You can import entire modules, and access their functions and variables using dot notation (`math.sqrt`). Or you can import specific members:
112 |
113 | ```python
114 | # Import entire module
115 | import math
116 |
117 | # Import specific functions
118 | from math import sqrt, pow
119 |
120 | # Import with alias
121 | import math as m
122 | ```
123 | """
124 | )
125 | return
126 |
127 |
128 | @app.cell
129 | def _():
130 | def demonstrate_import_strategies():
131 | """
132 | Demonstrate different import strategies using the math module
133 | """
134 | # Strategy 1: Import entire module
135 | import math
136 | entire_module_result = math.sqrt(25)
137 |
138 | # Strategy 2: Import specific functions
139 | from math import pow, sqrt
140 | specific_import_result = pow(2, 3)
141 |
142 | # Strategy 3: Import with alias
143 | import math as m
144 | alias_result = m.sqrt(16)
145 |
146 | return {
147 | "Entire Module Import": entire_module_result,
148 | "Specific Function Import": specific_import_result,
149 | "Alias Import": alias_result
150 | }
151 |
152 | # Run the import strategy demonstration
153 | import_strategy_examples = demonstrate_import_strategies()
154 | import_strategy_examples
155 | return demonstrate_import_strategies, import_strategy_examples
156 |
157 |
158 | @app.cell(hide_code=True)
159 | def _(mo):
160 | mo.md(
161 | """
162 | ## Third-party packages
163 |
164 | In addition to Python's standard library, there are hundreds of thousands of
165 | modules available for free on the Python Package index.
166 |
167 | These are distributed as Python "packages", and include packages for
168 | manipulating arrays of numbers, creating web applications, and more. `marimo`
169 | itself is a third-party package!
170 |
171 | For installing packages on your machine, we recommend using the [`uv` package manager](https://docs.astral.sh/uv/).
172 | """
173 | )
174 | return
175 |
176 |
177 | @app.cell
178 | def _():
179 | import marimo as mo
180 | return (mo,)
181 |
182 |
183 | if __name__ == "__main__":
184 | app.run()
185 |
--------------------------------------------------------------------------------
/python/010_exceptions.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.10"
3 | # dependencies = [
4 | # "marimo",
5 | # ]
6 | # ///
7 |
8 | import marimo
9 |
10 | __generated_with = "0.10.19"
11 | app = marimo.App()
12 |
13 |
14 | @app.cell(hide_code=True)
15 | def _(mo):
16 | mo.md(
17 | """
18 | # 🛡️ Handling errors
19 |
20 | Sometimes things go wrong in programs. When that happens, Python raises `exceptions` to tell you what went amiss. For example, maybe you divided by 0:
21 | """
22 | )
23 | return
24 |
25 |
26 | @app.cell
27 | def _():
28 | 1 / 0
29 | return
30 |
31 |
32 | @app.cell(hide_code=True)
33 | def _(mo):
34 | mo.md(
35 | """
36 | That's a lot of red! The outputs above are Python telling you that
37 | something went wrong — in this case, we tried dividing a number by 0.
38 |
39 | Python provides tools to catch and handle exceptions: the `try/except`
40 | block. This is demonstrated in the next couple cells.
41 | """
42 | )
43 | return
44 |
45 |
46 | @app.cell
47 | def _():
48 | # Try changing the value of divisor below, and see how the output changes.
49 | divisor = 0
50 | return (divisor,)
51 |
52 |
53 | @app.cell
54 | def _(divisor):
55 | try:
56 | print(1 / divisor)
57 | except ZeroDivisionError as e:
58 | print("Something went wrong!", e)
59 | return
60 |
61 |
62 | @app.cell(hide_code=True)
63 | def _(mo):
64 | mo.md(
65 | """
66 | Python has many types of Exceptions besides `ZeroDivisionError`. If you
67 | don't know what kind of exception you're handling, catch the generic
68 | `Exception` type:
69 |
70 | ```python
71 | try:
72 | ...
73 | except Exception:
74 | ...
75 | ```
76 | """
77 | )
78 | return
79 |
80 |
81 | @app.cell(hide_code=True)
82 | def _(error_types):
83 | error_types
84 | return
85 |
86 |
87 | @app.cell(hide_code=True)
88 | def _(mo):
89 | # Choose error type
90 | error_types = mo.ui.dropdown(
91 | value="ZeroDivisionError",
92 | options=[
93 | "ZeroDivisionError",
94 | "TypeError",
95 | "ValueError",
96 | "IndexError",
97 | "KeyError"
98 | ],
99 | label="Learn about ..."
100 | )
101 | return (error_types,)
102 |
103 |
104 | @app.cell(hide_code=True)
105 | def _(error_types, mo):
106 | # Error explanation
107 | error_explanations = {
108 | "ZeroDivisionError": """
109 | ### 🚫 ZeroDivisionError
110 | - Occurs when you try to divide by zero
111 | - Mathematical impossibility
112 | - Example:
113 | ```python
114 | x = 10 / 0 # Triggers ZeroDivisionError
115 | ```
116 | """,
117 | "TypeError": """
118 | ### 🔀 TypeError
119 | - Happens when an operation is applied to an inappropriate type
120 | - Mixing incompatible types
121 | - Example:
122 | ```python
123 | "2" + 3 # Can't add string and integer
124 | ```
125 | """,
126 | "ValueError": """
127 | ### 📊 ValueError
128 | - Raised when a function receives an argument of correct type
129 | but inappropriate value
130 | - Example:
131 | ```python
132 | int("hello") # Can't convert non-numeric string to int
133 | ```
134 | """,
135 | "IndexError": """
136 | ### 📑 IndexError
137 | - Occurs when trying to access a list index that doesn't exist
138 | - Going beyond list boundaries
139 | - Example:
140 | ```python
141 | my_list = [1, 2, 3]
142 | print(my_list[5]) # Only has indices 0, 1, 2
143 | ```
144 | """,
145 | "KeyError": """
146 | ### 🗝️ KeyError
147 | - Raised when trying to access a dictionary key that doesn't exist
148 | - Example:
149 | ```python
150 | my_dict = {"a": 1, "b": 2}
151 | print(my_dict["c"]) # "c" key doesn't exist
152 | ```
153 | """
154 | }
155 |
156 | mo.md(error_explanations.get(error_types.value, "Select an error type"))
157 | return (error_explanations,)
158 |
159 |
160 | @app.cell(hide_code=True)
161 | def _(mo):
162 | mo.md(
163 | """
164 | ## Handling multiple exception types
165 |
166 | Catch and handle different types of errors specifically:
167 |
168 | ```python
169 | def complex_function(x, y):
170 | try:
171 | # Potential errors: TypeError, ZeroDivisionError
172 | result = x / y
173 | return int(result)
174 | except TypeError:
175 | return "Type mismatch!"
176 | except ZeroDivisionError:
177 | return "No division by zero!"
178 | except ValueError:
179 | return "Conversion error!"
180 | finally:
181 | # The `finally` block always runs, regardless if there
182 | # was an error or not
183 | ...
184 |
185 | ```
186 | """
187 | )
188 | return
189 |
190 |
191 | @app.cell(hide_code=True)
192 | def _(finally_input):
193 | finally_input
194 | return
195 |
196 |
197 | @app.cell(hide_code=True)
198 | def _(mo):
199 | # Finally Block Demonstration
200 | finally_input = mo.ui.switch(
201 | label="Throw an error?",
202 | value=True
203 | )
204 | return (finally_input,)
205 |
206 |
207 | @app.cell
208 | def _(finally_input, mo):
209 | def simulate_resource_management():
210 | try:
211 | # Simulating a resource-intensive operation
212 | if not finally_input.value:
213 | return "🟢 Resource processing successful"
214 | else:
215 | raise Exception("Simulated failure")
216 | except Exception as e:
217 | return f"🔴 Error: {e}"
218 | finally:
219 | return "📦 Resource cleanup completed"
220 |
221 |
222 | _result = simulate_resource_management()
223 |
224 | mo.md(f"""
225 | ### Example: the finally clause
226 |
227 | **Scenario**: {"Normal operation" if not finally_input.value else "An exception was raised"}
228 |
229 | **Result**: {_result}
230 |
231 | Notice how the `finally` block always runs, ensuring cleanup!
232 | """)
233 | return (simulate_resource_management,)
234 |
235 |
236 | @app.cell
237 | def _():
238 | import marimo as mo
239 | return (mo,)
240 |
241 |
242 | if __name__ == "__main__":
243 | app.run()
244 |
--------------------------------------------------------------------------------
/python/README.md:
--------------------------------------------------------------------------------
1 | # Learn Python
2 |
3 | This collection of marimo notebooks is designed to teach you the basics
4 | of the Python programming language.
5 |
6 | **Running notebooks.** To run a notebook locally, use
7 |
8 | ```bash
9 | uvx marimo edit
10 | ```
11 |
12 | For example, run the numbers tutorial with
13 |
14 | ```bash
15 | uvx marimo edit https://github.com/marimo-team/learn/blob/main/python/001_numbers.py
16 | ```
17 |
18 | You can also open notebooks in our online playground by appending `marimo.app/`
19 | to a notebook's URL: [marimo.app/https://github.com/marimo-team/learn/blob/main/python/001_numbers.py](https://marimo.app/https://github.com/marimo-team/learn/blob/main/python/001_numbers.py).
20 |
21 | **Thanks to all our notebook authors!**
22 |
23 | * [Srihari Thyagarajan](https://github.com/Haleshot)
24 |
--------------------------------------------------------------------------------
/scripts/build.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import subprocess
5 | import argparse
6 | import json
7 | import datetime
8 | import markdown
9 | from datetime import date
10 | from pathlib import Path
11 | from typing import Dict, List, Any, Optional, Tuple
12 |
13 | from jinja2 import Environment, FileSystemLoader
14 |
15 |
16 | def export_html_wasm(notebook_path: str, output_dir: str, as_app: bool = False) -> bool:
17 | """Export a single marimo notebook to HTML format.
18 |
19 | Args:
20 | notebook_path: Path to the notebook to export
21 | output_dir: Directory to write the output HTML files
22 | as_app: If True, export as app instead of notebook
23 |
24 | Returns:
25 | bool: True if export succeeded, False otherwise
26 | """
27 | # Create directory for the output
28 | os.makedirs(output_dir, exist_ok=True)
29 |
30 | # Determine the output path (preserving directory structure)
31 | rel_path = os.path.basename(os.path.dirname(notebook_path))
32 | if rel_path != os.path.dirname(notebook_path):
33 | # Create subdirectory if needed
34 | os.makedirs(os.path.join(output_dir, rel_path), exist_ok=True)
35 |
36 | # Determine output filename (same as input but with .html extension)
37 | output_filename = os.path.basename(notebook_path).replace(".py", ".html")
38 | output_path = os.path.join(output_dir, rel_path, output_filename)
39 |
40 | # Run marimo export command
41 | mode = "--mode app" if as_app else "--mode edit"
42 | cmd = f"marimo export html-wasm {mode} {notebook_path} -o {output_path} --sandbox"
43 | print(f"Exporting {notebook_path} to {rel_path}/{output_filename} as {'app' if as_app else 'notebook'}")
44 | print(f"Running command: {cmd}")
45 |
46 | try:
47 | result = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True)
48 | print(f"Successfully exported {notebook_path} to {output_path}")
49 | return True
50 | except subprocess.CalledProcessError as e:
51 | print(f"Error exporting {notebook_path}: {e}")
52 | print(f"Command output: {e.output}")
53 | return False
54 |
55 |
56 | def get_course_metadata(course_dir: Path) -> Dict[str, Any]:
57 | """Extract metadata from a course directory.
58 |
59 | Reads the README.md file to extract title and description.
60 |
61 | Args:
62 | course_dir: Path to the course directory
63 |
64 | Returns:
65 | Dict: Dictionary containing course metadata (title, description)
66 | """
67 | readme_path = course_dir / "README.md"
68 | title = course_dir.name.replace("_", " ").title()
69 | description = ""
70 | description_html = ""
71 |
72 | if readme_path.exists():
73 | with open(readme_path, "r", encoding="utf-8") as f:
74 | content = f.read()
75 |
76 | # Try to extract title from first heading
77 | title_match = content.split("\n")[0]
78 | if title_match.startswith("# "):
79 | title = title_match[2:].strip()
80 |
81 | # Extract description from content after first heading
82 | desc_content = "\n".join(content.split("\n")[1:]).strip()
83 | if desc_content:
84 | # Take first paragraph as description, preserve markdown formatting
85 | description = desc_content.split("\n\n")[0].strip()
86 | # Convert markdown to HTML
87 | description_html = markdown.markdown(description)
88 |
89 | return {
90 | "title": title,
91 | "description": description,
92 | "description_html": description_html
93 | }
94 |
95 |
96 | def organize_notebooks_by_course(all_notebooks: List[str]) -> Dict[str, Dict[str, Any]]:
97 | """Organize notebooks by course.
98 |
99 | Args:
100 | all_notebooks: List of paths to notebooks
101 |
102 | Returns:
103 | Dict: A dictionary where keys are course directories and values are
104 | metadata about the course and its notebooks
105 | """
106 | courses = {}
107 |
108 | for notebook_path in sorted(all_notebooks):
109 | # Parse the path to determine course
110 | # The first directory in the path is the course
111 | path_parts = Path(notebook_path).parts
112 |
113 | if len(path_parts) < 2:
114 | print(f"Skipping notebook with invalid path: {notebook_path}")
115 | continue
116 |
117 | course_id = path_parts[0]
118 |
119 | # If this is a new course, initialize it
120 | if course_id not in courses:
121 | course_metadata = get_course_metadata(Path(course_id))
122 |
123 | courses[course_id] = {
124 | "id": course_id,
125 | "title": course_metadata["title"],
126 | "description": course_metadata["description"],
127 | "description_html": course_metadata["description_html"],
128 | "notebooks": []
129 | }
130 |
131 | # Extract the notebook number and name from the filename
132 | filename = Path(notebook_path).name
133 | basename = filename.replace(".py", "")
134 |
135 | # Extract notebook metadata
136 | notebook_title = basename.replace("_", " ").title()
137 |
138 | # Try to extract a sequence number from the start of the filename
139 | # Match patterns like: 01_xxx, 1_xxx, etc.
140 | import re
141 | number_match = re.match(r'^(\d+)(?:[_-]|$)', basename)
142 | notebook_number = number_match.group(1) if number_match else None
143 |
144 | # If we found a number, remove it from the title
145 | if number_match:
146 | notebook_title = re.sub(r'^\d+\s*[_-]?\s*', '', notebook_title)
147 |
148 | # Calculate the HTML output path (for linking)
149 | html_path = f"{course_id}/{filename.replace('.py', '.html')}"
150 |
151 | # Add the notebook to the course
152 | courses[course_id]["notebooks"].append({
153 | "path": notebook_path,
154 | "html_path": html_path,
155 | "title": notebook_title,
156 | "display_name": notebook_title,
157 | "original_number": notebook_number
158 | })
159 |
160 | # Sort notebooks by number if available, otherwise by title
161 | for course_id, course_data in courses.items():
162 | # Sort the notebooks list by number and title
163 | course_data["notebooks"] = sorted(
164 | course_data["notebooks"],
165 | key=lambda x: (
166 | int(x["original_number"]) if x["original_number"] is not None else float('inf'),
167 | x["title"]
168 | )
169 | )
170 |
171 | return courses
172 |
173 |
174 | def generate_clean_tailwind_landing_page(courses: Dict[str, Dict[str, Any]], output_dir: str) -> None:
175 | """Generate a clean tailwindcss landing page with green accents.
176 |
177 | This generates a modern, minimal landing page for marimo notebooks using tailwindcss.
178 | The page is designed with clean aesthetics and green color accents using Jinja2 templates.
179 |
180 | Args:
181 | courses: Dictionary of courses metadata
182 | output_dir: Directory to write the output index.html file
183 | """
184 | print("Generating clean tailwindcss landing page")
185 |
186 | index_path = os.path.join(output_dir, "index.html")
187 | os.makedirs(output_dir, exist_ok=True)
188 |
189 | # Load Jinja2 template
190 | current_dir = Path(__file__).parent
191 | templates_dir = current_dir / "templates"
192 | env = Environment(loader=FileSystemLoader(templates_dir))
193 | template = env.get_template('index.html')
194 |
195 | try:
196 | with open(index_path, "w", encoding="utf-8") as f:
197 | # Render the template with the provided data
198 | rendered_html = template.render(
199 | courses=courses,
200 | current_year=datetime.date.today().year
201 | )
202 | f.write(rendered_html)
203 |
204 | print(f"Successfully generated clean tailwindcss landing page at {index_path}")
205 |
206 | except IOError as e:
207 | print(f"Error generating clean tailwindcss landing page: {e}")
208 |
209 |
210 | def main() -> None:
211 | parser = argparse.ArgumentParser(description="Build marimo notebooks")
212 | parser.add_argument(
213 | "--output-dir", default="_site", help="Output directory for built files"
214 | )
215 | parser.add_argument(
216 | "--course-dirs", nargs="+", default=None,
217 | help="Specific course directories to build (default: all directories with .py files)"
218 | )
219 | args = parser.parse_args()
220 |
221 | # Find all course directories (directories containing .py files)
222 | all_notebooks: List[str] = []
223 |
224 | # Directories to exclude from course detection
225 | excluded_dirs = ["scripts", "env", "__pycache__", ".git", ".github", "assets"]
226 |
227 | if args.course_dirs:
228 | course_dirs = args.course_dirs
229 | else:
230 | # Automatically detect course directories (any directory with .py files)
231 | course_dirs = []
232 | for item in os.listdir("."):
233 | if (os.path.isdir(item) and
234 | not item.startswith(".") and
235 | not item.startswith("_") and
236 | item not in excluded_dirs):
237 | # Check if directory contains .py files
238 | if list(Path(item).glob("*.py")):
239 | course_dirs.append(item)
240 |
241 | print(f"Found course directories: {', '.join(course_dirs)}")
242 |
243 | for directory in course_dirs:
244 | dir_path = Path(directory)
245 | if not dir_path.exists():
246 | print(f"Warning: Directory not found: {dir_path}")
247 | continue
248 |
249 | notebooks = [str(path) for path in dir_path.rglob("*.py")
250 | if not path.name.startswith("_") and "/__pycache__/" not in str(path)]
251 | all_notebooks.extend(notebooks)
252 |
253 | if not all_notebooks:
254 | print("No notebooks found!")
255 | return
256 |
257 | # Export notebooks sequentially
258 | successful_notebooks = []
259 | for nb in all_notebooks:
260 | # Determine if notebook should be exported as app or notebook
261 | # For now, export all as notebooks
262 | if export_html_wasm(nb, args.output_dir, as_app=False):
263 | successful_notebooks.append(nb)
264 |
265 | # Organize notebooks by course (only include successfully exported notebooks)
266 | courses = organize_notebooks_by_course(successful_notebooks)
267 |
268 | # Generate landing page using Tailwind CSS
269 | generate_clean_tailwind_landing_page(courses, args.output_dir)
270 |
271 | # Save course data as JSON for potential use by other tools
272 | courses_json_path = os.path.join(args.output_dir, "courses.json")
273 | with open(courses_json_path, "w", encoding="utf-8") as f:
274 | json.dump(courses, f, indent=2)
275 |
276 | print(f"Build complete! Site generated in {args.output_dir}")
277 | print(f"Successfully exported {len(successful_notebooks)} out of {len(all_notebooks)} notebooks")
278 |
279 |
280 | if __name__ == "__main__":
281 | main()
282 |
--------------------------------------------------------------------------------
/scripts/preview.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import subprocess
5 | import argparse
6 | import webbrowser
7 | import time
8 | import sys
9 | from pathlib import Path
10 |
11 | def main():
12 | parser = argparse.ArgumentParser(description="Build and preview marimo notebooks site")
13 | parser.add_argument(
14 | "--port", default=8000, type=int, help="Port to run the server on"
15 | )
16 | parser.add_argument(
17 | "--no-build", action="store_true", help="Skip building the site (just serve existing files)"
18 | )
19 | parser.add_argument(
20 | "--output-dir", default="_site", help="Output directory for built files"
21 | )
22 | args = parser.parse_args()
23 |
24 | # Store the current directory
25 | original_dir = os.getcwd()
26 |
27 | try:
28 | # Build the site if not skipped
29 | if not args.no_build:
30 | print("Building site...")
31 | build_script = Path("scripts/build.py")
32 | if not build_script.exists():
33 | print(f"Error: Build script not found at {build_script}")
34 | return 1
35 |
36 | result = subprocess.run(
37 | [sys.executable, str(build_script), "--output-dir", args.output_dir],
38 | check=False
39 | )
40 | if result.returncode != 0:
41 | print("Warning: Build process completed with errors.")
42 |
43 | # Check if the output directory exists
44 | output_dir = Path(args.output_dir)
45 | if not output_dir.exists():
46 | print(f"Error: Output directory '{args.output_dir}' does not exist.")
47 | return 1
48 |
49 | # Change to the output directory
50 | os.chdir(args.output_dir)
51 |
52 | # Open the browser
53 | url = f"http://localhost:{args.port}"
54 | print(f"Opening {url} in your browser...")
55 | webbrowser.open(url)
56 |
57 | # Start the server
58 | print(f"Starting server on port {args.port}...")
59 | print("Press Ctrl+C to stop the server")
60 |
61 | # Use the appropriate Python executable
62 | subprocess.run([sys.executable, "-m", "http.server", str(args.port)])
63 |
64 | return 0
65 | except KeyboardInterrupt:
66 | print("\nServer stopped.")
67 | return 0
68 | except Exception as e:
69 | print(f"Error: {e}")
70 | return 1
71 | finally:
72 | # Always return to the original directory
73 | os.chdir(original_dir)
74 |
75 | if __name__ == "__main__":
76 | sys.exit(main())
77 |
--------------------------------------------------------------------------------
/scripts/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Marimo Learn - Interactive Python Notebooks
7 |
8 |
9 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
Interactive Python Learning with marimo
31 |
Explore our collection of interactive notebooks for Python, data science, and machine learning.
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
Why Learn with Marimo ?
50 |
51 |
52 |
57 |
Interactive Learning
58 |
Learn by doing with interactive notebooks that run directly in your browser.
59 |
60 |
61 |
66 |
Practical Examples
67 |
Real-world examples and applications to reinforce your understanding.
68 |
69 |
70 |
75 |
Comprehensive Curriculum
76 |
From Python basics to advanced machine learning concepts.
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
Explore Our Courses
86 |
87 | {% for course_id, course in courses.items() %}
88 | {% set notebooks = course.get('notebooks', []) %}
89 | {% set notebook_count = notebooks|length %}
90 |
91 | {% if notebook_count > 0 %}
92 | {% set title = course.get('title', course_id|replace('_', ' ')|title) %}
93 |
94 |
95 |
96 |
97 |
{{ title }}
98 |
99 | {% if course.get('description_html') %}
100 | {{ course.get('description_html')|safe }}
101 | {% endif %}
102 |
103 |
104 |
{{ notebook_count }} notebooks:
105 |
106 | {% for notebook in notebooks %}
107 | {% set notebook_title = notebook.get('title', notebook.get('path', '').split('/')[-1].replace('.py', '').replace('_', ' ').title()) %}
108 |
109 |
110 | {{ notebook_title }}
111 |
112 |
113 | {% endfor %}
114 |
115 |
116 |
117 |
118 | {% endif %}
119 | {% endfor %}
120 |
121 |
122 |
123 |
124 |
125 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
© {{ current_year }} marimo. All rights reserved.
144 |
145 |
157 |
158 |
159 |
160 |
161 |
162 |
173 |
174 |
175 |
--------------------------------------------------------------------------------