maartenbreddels commited on
Commit
0449aa7
β€’
0 Parent(s):

initial commit

Browse files
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+
7
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
+
9
+ COPY . .
10
+
11
+ CMD ["solara", "run", "pages", "--host=0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Drawdata Sklearn
3
+ emoji: 🌍
4
+ colorFrom: indigo
5
+ colorTo: gray
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # drawdata - demo using solara
12
+
13
+ [Drawdata](https://github.com/koaning/drawdata) is an ipywidget library build using [anywidget](https://github.com/manzt/anywidget).
14
+
15
+ All ipywidgets (and also anywidget) run in the Jupyter notebook, but also in [Solara server](https://solara.dev/).
16
+
17
+ Solara server can run Python code and notebooks, and display [regular widgets](https://solara.dev/docs/tutorial/ipywidgets),
18
+ or for more production quality code, [solara components](https://solara.dev/docs/fundamentals/components).
19
+
20
+ ## Demo 1 - Original with classic widgets
21
+
22
+ This demonstrates solara server can execute the ([almost unmodified](https://github.com/widgetti/solara/issues/539)) [notebook](https://github.com/probabl-ai/youtube-appendix/blob/main/04-drawing-data/notebook.ipynb).
23
+
24
+ Since ipywidgets lacks lifetime management, the widgets in this tab are kept alive and reused when moved back to this tab (try drawing, and switch tabs).
25
+
26
+ This demo shows how drawdata can be used exploring how different sklearn algorithm perform on datasets draw by hand ([YouTube video](https://www.youtube.com/watch?v=STPv0jSAQEk))
27
+
28
+
29
+ ## Demo 2 - Fancy demo using components
30
+
31
+ For larger apps and dynamic UI's it pays off using [components](https://solara.dev/docs/fundamentals/components) and creating elements
32
+ instead of directly creating widgets. This makes it possible to create very dynamic UIs (try changing the layout using the top right button in the app bar)
33
+ and have proper life cycle management (widgets get cleaned up when changing tabs, freeing memory - very important in larger applications)
pages/00-readme.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import solara
2
+ from pathlib import Path
3
+
4
+
5
+ HERE = Path(__file__).parent
6
+
7
+
8
+ @solara.component
9
+ def Page():
10
+ md_text = (HERE.parent / "README.md").read_text()
11
+ # take out front matter
12
+ md_text = md_text.rpartition("---")[2]
13
+ solara.Markdown(md_text)
14
+ solara.Title("Draw data widget demo with Solara β˜€οΈ")
15
+
16
+
17
+
18
+ @solara.component
19
+ def Layout(children):
20
+ solara.AppLayout(children=children)
pages/01-original-with-widgets.ipynb ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 7,
6
+ "id": "eea9f1b8-4240-4a68-a412-2b4071b2c04a",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "from drawdata import ScatterWidget"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 8,
16
+ "id": "207dbcfd-e731-4758-8035-d1f429aa10d4",
17
+ "metadata": {},
18
+ "outputs": [],
19
+ "source": [
20
+ "widget = ScatterWidget()"
21
+ ]
22
+ },
23
+ {
24
+ "cell_type": "code",
25
+ "execution_count": 13,
26
+ "id": "b77030f4-c895-4a39-96ce-b79d6a8a6d69",
27
+ "metadata": {},
28
+ "outputs": [],
29
+ "source": [
30
+ "import matplotlib.pyplot as plt\n",
31
+ "from IPython.core.display import HTML\n",
32
+ "from sklearn.linear_model import LogisticRegression\n",
33
+ "from sklearn.inspection import DecisionBoundaryDisplay\n",
34
+ "from sklearn.tree import DecisionTreeClassifier\n",
35
+ "\n",
36
+ "import matplotlib.pylab as plt \n",
37
+ "import numpy as np\n",
38
+ "import ipywidgets"
39
+ ]
40
+ },
41
+ {
42
+ "cell_type": "code",
43
+ "execution_count": null,
44
+ "id": "b9d36c79-3d1d-4084-a1a6-43f3b95c06fe",
45
+ "metadata": {},
46
+ "outputs": [
47
+ {
48
+ "data": {
49
+ "application/vnd.jupyter.widget-view+json": {
50
+ "model_id": "6b21ddf1e80a4a34a786547610d52aa0",
51
+ "version_major": 2,
52
+ "version_minor": 0
53
+ },
54
+ "text/plain": [
55
+ "HBox(children=(ScatterWidget(), Output()))"
56
+ ]
57
+ },
58
+ "execution_count": 17,
59
+ "metadata": {},
60
+ "output_type": "execute_result"
61
+ }
62
+ ],
63
+ "source": [
64
+ "widget = ScatterWidget()\n",
65
+ "output = ipywidgets.Output()\n",
66
+ "\n",
67
+ "\n",
68
+ "@output.capture(clear_output=True)\n",
69
+ "def on_change(change):\n",
70
+ " df = widget.data_as_pandas\n",
71
+ " if len(df) and (df['color'].nunique() > 1):\n",
72
+ " X = df[['x', 'y']].values\n",
73
+ " y = df['color']\n",
74
+ " display(HTML(\"<br><br><br>\"))\n",
75
+ " fig = plt.figure(figsize=(12, 12));\n",
76
+ " classifier = DecisionTreeClassifier().fit(X, y)\n",
77
+ " disp = DecisionBoundaryDisplay.from_estimator(\n",
78
+ " classifier, X, \n",
79
+ " ax=fig.add_subplot(111),\n",
80
+ " response_method=\"predict_proba\" if len(np.unique(df['color'])) == 2 else \"predict\",\n",
81
+ " xlabel=\"x\", ylabel=\"y\",\n",
82
+ " alpha=0.5,\n",
83
+ " );\n",
84
+ " disp.ax_.scatter(X[:, 0], X[:, 1], c=y, edgecolor=\"k\");\n",
85
+ " # plt.title(f\"{classifier.__class__.__name__}\");\n",
86
+ " disp.ax_.set_title(f\"{classifier.__class__.__name__}\");\n",
87
+ " # plt.show();\n",
88
+ " import solara\n",
89
+ " solara.display(solara.FigureMatplotlib(fig))\n",
90
+ "widget.observe(on_change, names=[\"data\"])\n",
91
+ "on_change(None)\n",
92
+ "page = ipywidgets.HBox([widget, output])\n",
93
+ "page"
94
+ ]
95
+ },
96
+ {
97
+ "cell_type": "markdown",
98
+ "id": "5f9bb7ce-8a7f-4879-9ddf-5b256bb0ff64",
99
+ "metadata": {},
100
+ "source": [
101
+ "\n",
102
+ "p<br><br><br><br><br><br><br><br><br><br><br><br>"
103
+ ]
104
+ }
105
+ ],
106
+ "metadata": {
107
+ "kernelspec": {
108
+ "display_name": "Python 3 (ipykernel)",
109
+ "language": "python",
110
+ "name": "python3"
111
+ },
112
+ "language_info": {
113
+ "codemirror_mode": {
114
+ "name": "ipython",
115
+ "version": 3
116
+ },
117
+ "file_extension": ".py",
118
+ "mimetype": "text/x-python",
119
+ "name": "python",
120
+ "nbconvert_exporter": "python",
121
+ "pygments_lexer": "ipython3",
122
+ "version": "3.11.6"
123
+ }
124
+ },
125
+ "nbformat": 4,
126
+ "nbformat_minor": 5
127
+ }
pages/02-fancy-with-solara.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from traitlets import Dict
3
+ import solara
4
+ import solara.lab
5
+ import matplotlib.pyplot as plt
6
+
7
+ # needed for solara up to version 1.28
8
+ plt.switch_backend("module://matplotlib_inline.backend_inline")
9
+ # title = "solara"
10
+
11
+ from sklearn.linear_model import LogisticRegression
12
+ from sklearn.inspection import DecisionBoundaryDisplay
13
+ from sklearn.tree import DecisionTreeClassifier
14
+
15
+ import matplotlib.pylab as plt
16
+ import numpy as np
17
+ import pandas as pd
18
+
19
+ from drawdata import ScatterWidget
20
+
21
+ drawdata: solara.Reactive[List[Dict]] = solara.reactive([])
22
+
23
+
24
+ @solara.component
25
+ def ClassifierDraw(classifier, X, y, response_method="predict_proba", figsize=(8, 8)):
26
+ fig = plt.figure(figsize=figsize)
27
+ disp = DecisionBoundaryDisplay.from_estimator(
28
+ classifier,
29
+ X,
30
+ # not sure why this was needed, otherwise i get a blank plot
31
+ ax=fig.add_subplot(111),
32
+ response_method=response_method,
33
+ xlabel="x",
34
+ ylabel="y",
35
+ alpha=0.5,
36
+ )
37
+ disp.ax_.scatter(X[:, 0], X[:, 1], c=y, edgecolor="k")
38
+ plt.title(f"{classifier.__class__.__name__}")
39
+ plt.close()
40
+ solara.FigureMatplotlib(fig)
41
+
42
+
43
+ @solara.component
44
+ def DecisionTreeClassifierDraw(df):
45
+ criterion = solara.use_reactive("gini")
46
+ splitter = solara.use_reactive("best")
47
+ with solara.Row():
48
+ solara.ToggleButtonsSingle(value=criterion, values=["gini", "entropy", "log_loss"])
49
+ solara.ToggleButtonsSingle(value=splitter, values=["best", "random"])
50
+ X = df[["x", "y"]].values
51
+ y = df["color"]
52
+ classifier = DecisionTreeClassifier(criterion=criterion.value, splitter=splitter.value).fit(X, y)
53
+ ClassifierDraw(classifier, X, y, "predict_proba" if len(np.unique(df["color"])) == 2 else "predict")
54
+
55
+
56
+ @solara.component
57
+ def LogisticRegressionDraw(df):
58
+ penalty = solara.use_reactive("l2")
59
+ solver = solara.use_reactive("lbfgs")
60
+ l1_ratio = solara.use_reactive(0.5)
61
+ with solara.Row():
62
+ solara.ToggleButtonsSingle(value=penalty, values=["l1", "l2", "elasticnet", "none"])
63
+ solara.ToggleButtonsSingle(value=solver, values=["newton-cg", "lbfgs", "liblinear", "sag", "saga"])
64
+ if penalty.value == "elasticnet":
65
+ solara.FloatSlider("l1_ratio", value=l1_ratio, min=0, max=1, step=0.1)
66
+ X = df[["x", "y"]].values
67
+ y = df["color"]
68
+ try:
69
+ classifier = LogisticRegression(penalty=penalty.value, solver=solver.value, l1_ratio=l1_ratio.value).fit(X, y)
70
+ except ValueError as e:
71
+ solara.Error(str(e))
72
+ else:
73
+ ClassifierDraw(classifier, X, y, "predict_proba" if len(np.unique(df["color"])) == 2 else "predict")
74
+
75
+
76
+ @solara.component
77
+ def Page():
78
+ vertical = solara.use_reactive(True)
79
+ solara.AppBarTitle("Draw Data with Solara demo")
80
+ df = pd.DataFrame(drawdata.value) if drawdata.value else None
81
+
82
+ with solara.AppBar():
83
+ # TODO: doesn't work, ScatterWidget does not update when data is updated (read only?)
84
+ # solara.Button(icon_name="mdi-delete", on_click=lambda: drawdata.set([]), icon=True)
85
+ # demo how solara can dynamically change the layout
86
+ solara.Button(icon_name="mdi-align-vertical-top" if vertical.value else "mdi-align-horizontal-left", on_click=lambda: vertical.set(not vertical.value), icon=True)
87
+
88
+ # if solara.lab.theme.dark_effective:
89
+ # plt.style.use('dark_background')
90
+ # else:
91
+ # plt.style.use('default')
92
+ with solara.Column() if vertical.value else solara.Row():
93
+ # with solara, we don't just create the widget, but an element that describes it
94
+ # and instead of observe, we have on_<trait> callbacks
95
+ # Note: if we store the data in the reactive var (drawdata), we keep the drawing
96
+ # on hot reload.
97
+ ScatterWidget.element(data=drawdata.value, on_data=drawdata.set)
98
+ # downside of using elements and components: we cannot call method on the widget
99
+ # so we need to re-create the dataframe ourselves
100
+ with solara.lab.Tabs():
101
+ with solara.lab.Tab("classifier"):
102
+ if df is not None and (df["color"].nunique() > 1):
103
+ with solara.Column(style={"max-height": "500px", "padding-top": "0px"}):
104
+ with solara.lab.Tabs():
105
+ with solara.lab.Tab("DecisionTreeClassifier"):
106
+ DecisionTreeClassifierDraw(df)
107
+ with solara.lab.Tab("LogisticRegressionDraw"):
108
+ LogisticRegressionDraw(df)
109
+ else:
110
+ with solara.Column(style={"justify-content": "center"}) if not vertical.value else solara.Row():
111
+ solara.Info("Choose at least two colors to draw a decision boundary.")
112
+ with solara.lab.Tab("table view"):
113
+ if df is not None:
114
+ with solara.FileDownload(data=lambda: df.to_csv(), filename="drawdata.csv"):
115
+ solara.Button("download as csv", icon_name="mdi-download", outlined=True, color="primary")
116
+ solara.DataFrame(df)
117
+
118
+
119
+ # in the notebook:
120
+ Page()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ solara==1.28
2
+ drawdata==0.3.0
3
+ matplotlib
4
+ scikit-learn
5
+ pandas
6
+ numpy