HugBot commited on
Commit
88ffb40
Β·
verified Β·
1 Parent(s): c872458

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -129
app.py CHANGED
@@ -1,10 +1,13 @@
1
  # -*- coding: utf-8 -*-
2
 
 
3
  import numpy as np
4
  import matplotlib.pyplot as plt
5
- import gradio as gr
 
 
6
 
7
- # ── style ──────────────────────────────────────────────────────────────────
8
  BG = '#f8fafc'
9
  PANEL = '#ffffff'
10
  GRID = '#e2e8f0'
@@ -16,7 +19,7 @@ C_TEXT = '#1e293b'
16
  C_DIM = '#64748b'
17
  C_BORDER= '#cbd5e1'
18
 
19
- plt.rcParams.update({
20
  'figure.facecolor': BG, 'axes.facecolor': PANEL,
21
  'axes.edgecolor': C_BORDER, 'axes.labelcolor': C_DIM,
22
  'xtick.color': C_DIM, 'ytick.color': C_DIM,
@@ -24,137 +27,158 @@ C_BORDER= '#cbd5e1'
24
  'text.color': C_TEXT, 'font.family': 'monospace',
25
  })
26
 
27
- EP_RANGE = np.linspace(0, 15, 600)
28
 
29
- def calc_paf(episodes, incidence, zscore, rr):
30
  return 1 - 1 / np.exp(episodes * incidence * zscore * rr)
31
 
32
- # ── plot function ──────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def draw(ep, inc, zs, rr):
34
  paf_curve = calc_paf(EP_RANGE, inc, zs, rr) * 100
35
  cur_paf = calc_paf(ep, inc, zs, rr)
36
  exponent = ep * inc * zs * rr
37
 
38
- fig, axes = plt.subplots(1, 2, figsize=(15, 6),
39
- gridspec_kw={'width_ratios': [3, 1]})
40
- fig.patch.set_facecolor(BG)
41
-
42
- # ── left: curve ───────────────────────────────────────────────────────
43
- ax = axes[0]
44
- ax.set_facecolor(PANEL)
45
- for spine in ax.spines.values():
46
- spine.set_color(C_BORDER)
47
-
48
- # shadow + main curve
49
- ax.plot(EP_RANGE, paf_curve, color=ACCENT, linewidth=5, alpha=0.15)
50
- ax.plot(EP_RANGE, paf_curve, color=ACCENT, linewidth=2.2)
51
-
52
- # crosshairs
53
- ax.axvline(ep, color=ACCENT, linestyle='--', linewidth=1.2, alpha=0.6)
54
- ax.axhline(cur_paf*100, color=ACCENT, linestyle='--', linewidth=1.2, alpha=0.3)
55
-
56
- # dot at current point
57
- ax.scatter([ep], [cur_paf*100], color=ACCENT, s=80, zorder=5,
58
- edgecolors='white', linewidths=1.5)
59
-
60
- # shaded area under curve up to current episode
61
- mask = EP_RANGE <= ep
62
- ax.fill_between(EP_RANGE[mask], paf_curve[mask], color=ACCENT, alpha=0.08)
63
-
64
- ax.set_xlim(0, 15)
65
- ax.set_ylim(0, 102)
66
- ax.set_xlabel('Episodes', fontsize=11, labelpad=8)
67
- ax.set_ylabel('PAF (%)', fontsize=11, labelpad=8)
68
- ax.set_title('Population Attributable Fraction',
69
- color=C_TEXT, fontsize=13, pad=12, fontweight='normal')
70
- ax.grid(True, linewidth=0.8)
71
-
72
- # annotation
73
- ax.annotate(
74
- f' {cur_paf*100:.1f}%',
75
- xy=(ep, cur_paf*100),
76
- xytext=(min(ep + 1, 13), cur_paf*100 + 4),
77
- color=ACCENT, fontsize=15, fontweight='bold',
78
- arrowprops=dict(arrowstyle='->', color=ACCENT, lw=1.2)
79
- )
80
-
81
- # formula watermark
82
- ax.text(0.98, 0.05,
83
- 'PAF = 1 βˆ’ exp(βˆ’ep Γ— inc Γ— Ξ”z Γ— RR)',
84
- transform=ax.transAxes, fontsize=11,
85
- color=C_DIM, ha='right', va='bottom')
86
-
87
- # ── right: breakdown panel ────────────────────────────────────────────
88
- ax2 = axes[1]
89
- ax2.set_facecolor('#f1f5f9')
90
- for spine in ax2.spines.values():
91
- spine.set_color(C_BORDER)
92
- ax2.set_xticks([]); ax2.set_yticks([])
93
-
94
- rows = [
95
- ('episodes', f'{ep:.2f}', ACCENT),
96
- ('incidence', f'{inc:.3f}', C_INC),
97
- ('Ξ”z / ep.', f'{zs:.3f}', C_ZS),
98
- ('RR', f'{rr:.3f}', C_RR),
99
- ('─' * 10, '─' * 6, C_BORDER),
100
- ('exponent', f'{exponent:.4f}', '#475569'),
101
- ('exp(βˆ’x)', f'{1/np.exp(exponent):.4f}', '#475569'),
102
- ('─' * 10, '─' * 6, C_BORDER),
103
- ('PAF', f'{cur_paf*100:.2f}%', ACCENT),
104
- ]
105
-
106
- ax2.text(0.5, 0.97, 'BREAKDOWN', transform=ax2.transAxes,
107
- ha='center', fontsize=13, color=ACCENT, fontweight='bold')
108
-
109
- y_start = 0.88
110
- for i, (k, v, c) in enumerate(rows):
111
- y = y_start - i * 0.095
112
- ax2.text(0.08, y, k, transform=ax2.transAxes,
113
- fontsize=11, color=C_DIM, va='center')
114
- ax2.text(0.92, y, v, transform=ax2.transAxes,
115
- fontsize=11, color=c, va='center', ha='right', fontweight='bold')
116
-
117
- ax2.set_title('Parameters', color=C_TEXT, fontsize=13, pad=8)
118
-
119
- plt.tight_layout(pad=1.5)
120
- return fig
121
-
122
- # ── Gradio UI ───────────────────────────���─────────────────────────────────
123
- with gr.Blocks(theme=gr.themes.Soft(), title="PAF Visualizer") as demo:
124
-
125
- gr.HTML("""
126
- <div style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:10px;
127
- padding:12px 20px;margin-bottom:12px;font-family:monospace;">
128
- <span style="color:#1e293b;font-size:20px;font-weight:600;">
129
- Population Attributable Fraction Visualizer
130
- </span><br>
131
- <span style="color:#64748b;font-size:13px;">
132
- PAF = 1 βˆ’ exp(βˆ’episodes Γ— incidence Γ— Ξ”z Γ— RR)
133
- </span>
134
- </div>""")
135
-
136
- with gr.Row():
137
- with gr.Column():
138
- ep = gr.Slider(0, 15, value=4.2, step=0.1, label="Episodes")
139
- inc = gr.Slider(0.01, 1, value=0.3, step=0.01, label="Pathogen Incidence")
140
- zs = gr.Slider(0.01, 2, value=0.2, step=0.01, label="Ξ”z per Episode")
141
- rr = gr.Slider(1.01, 5, value=1.5, step=0.05, label="Relative Risk (RR)")
142
-
143
- reset_btn = gr.Button("Reset Defaults", variant="secondary")
144
-
145
- plot = gr.Plot(label="")
146
-
147
- # live update on any slider change
148
- for slider in [ep, inc, zs, rr]:
149
- slider.change(fn=draw, inputs=[ep, inc, zs, rr], outputs=plot)
150
-
151
- # reset button
152
- def reset():
153
- return 5.0, 0.3, 1.2, 2.5
154
-
155
- reset_btn.click(fn=reset, outputs=[ep, inc, zs, rr])
156
-
157
- # draw on load
158
- demo.load(fn=draw, inputs=[ep, inc, zs, rr], outputs=plot)
159
-
160
- demo.launch()
 
1
  # -*- coding: utf-8 -*-
2
 
3
+
4
  import numpy as np
5
  import matplotlib.pyplot as plt
6
+ import ipywidgets as widgets
7
+ from ipywidgets import FloatSlider, VBox, HBox, Layout, HTML
8
+ from IPython.display import display
9
 
10
+ # ── style ──────────────────────────────────────────────────────────────────
11
  BG = '#f8fafc'
12
  PANEL = '#ffffff'
13
  GRID = '#e2e8f0'
 
19
  C_DIM = '#64748b'
20
  C_BORDER= '#cbd5e1'
21
 
22
+ plt.rcParams.update({
23
  'figure.facecolor': BG, 'axes.facecolor': PANEL,
24
  'axes.edgecolor': C_BORDER, 'axes.labelcolor': C_DIM,
25
  'xtick.color': C_DIM, 'ytick.color': C_DIM,
 
27
  'text.color': C_TEXT, 'font.family': 'monospace',
28
  })
29
 
30
+ EP_RANGE = np.linspace(0, 15, 600)
31
 
32
+ def calc_paf(episodes, incidence, zscore, rr):
33
  return 1 - 1 / np.exp(episodes * incidence * zscore * rr)
34
 
35
+ def make_slider(desc, mn, mx, val, step, color):
36
+ return FloatSlider(
37
+ value=val, min=mn, max=mx, step=step,
38
+ description=desc, continuous_update=True,
39
+ layout=Layout(width='520px'),
40
+ style={'description_width': '160px', 'handle_color': color}
41
+ )
42
+
43
+ # ── widgets ────────────────────────────────────────────────────────────────
44
+ s_ep = make_slider('Episodes', 0, 15, 4.2, 0.1, ACCENT)
45
+ s_inc = make_slider('Pathogen Incidence', 0.01, 1, 0.3, 0.01, C_INC)
46
+ s_zs = make_slider('Ξ”z per Episode', 0.01, 2, 0.2, 0.01, C_ZS)
47
+ s_rr = make_slider('Relative Risk (RR)', 1.01, 5, 1.5, 0.05, C_RR)
48
+
49
+ btn_reset = widgets.Button(
50
+ description='Reset Defaults',
51
+ layout=Layout(width='160px', height='34px'),
52
+ style={'button_color': '#e2e8f0'}
53
+ )
54
+
55
+ info_box = HTML()
56
+ out = widgets.Output()
57
+
58
+ # ── plot function ──────────────────────────────────────────────────────────
59
  def draw(ep, inc, zs, rr):
60
  paf_curve = calc_paf(EP_RANGE, inc, zs, rr) * 100
61
  cur_paf = calc_paf(ep, inc, zs, rr)
62
  exponent = ep * inc * zs * rr
63
 
64
+ with out:
65
+ out.clear_output(wait=True)
66
+ fig, axes = plt.subplots(1, 2, figsize=(15, 6),
67
+ gridspec_kw={'width_ratios': [3, 1]})
68
+ fig.patch.set_facecolor(BG)
69
+
70
+ # ── left: curve ───────────────────────────────────────────────────
71
+ ax = axes[0]
72
+ ax.set_facecolor(PANEL)
73
+ for spine in ax.spines.values():
74
+ spine.set_color(C_BORDER)
75
+
76
+ # shadow effect
77
+ ax.plot(EP_RANGE, paf_curve, color=ACCENT, linewidth=5, alpha=0.15)
78
+ ax.plot(EP_RANGE, paf_curve, color=ACCENT, linewidth=2.2)
79
+
80
+ # crosshairs
81
+ ax.axvline(ep, color=ACCENT, linestyle='--', linewidth=1.2, alpha=0.6)
82
+ ax.axhline(cur_paf*100, color=ACCENT, linestyle='--', linewidth=1.2, alpha=0.3)
83
+
84
+ # dot at current point
85
+ ax.scatter([ep], [cur_paf*100], color=ACCENT, s=80, zorder=5, edgecolors='white', linewidths=1.5)
86
+
87
+ # shaded area under curve up to current episode
88
+ mask = EP_RANGE <= ep
89
+ ax.fill_between(EP_RANGE[mask], paf_curve[mask], color=ACCENT, alpha=0.08)
90
+
91
+ ax.set_xlim(0, 15)
92
+ ax.set_ylim(0, 102)
93
+ ax.set_xlabel('Episodes', fontsize=11, labelpad=8)
94
+ ax.set_ylabel('PAF (%)', fontsize=11, labelpad=8)
95
+ ax.set_title('Population Attributable Fraction',
96
+ color=C_TEXT, fontsize=13, pad=12, fontweight='normal')
97
+ ax.grid(True, linewidth=0.8)
98
+
99
+ # annotation
100
+ ax.annotate(
101
+ f' {cur_paf*100:.1f}%',
102
+ xy=(ep, cur_paf*100),
103
+ xytext=(min(ep + 1, 13), cur_paf*100 + 4),
104
+ color=ACCENT, fontsize=15, fontweight='bold',
105
+ arrowprops=dict(arrowstyle='->', color=ACCENT, lw=1.2)
106
+ )
107
+
108
+ # formula watermark
109
+ ax.text(0.98, 0.05,
110
+ 'PAF = 1 βˆ’ exp(βˆ’ep Γ— inc Γ— Ξ”z Γ— RR)',
111
+ transform=ax.transAxes, fontsize=15,
112
+ color=C_DIM, ha='right', va='bottom')
113
+
114
+ # ��─ right: breakdown panel ────────────────────────────────────────
115
+ ax2 = axes[1]
116
+ ax2.set_facecolor('#f1f5f9')
117
+ for spine in ax2.spines.values():
118
+ spine.set_color(C_BORDER)
119
+ ax2.set_xticks([]); ax2.set_yticks([])
120
+
121
+ rows = [
122
+ ('episodes', f'{ep:.2f}', ACCENT),
123
+ ('incidence', f'{inc:.3f}', C_INC),
124
+ ('Ξ”z / ep.', f'{zs:.3f}', C_ZS),
125
+ ('RR', f'{rr:.3f}', C_RR),
126
+ ('─' * 10, '─' * 6, C_BORDER),
127
+ ('exponent', f'{exponent:.4f}', '#475569'),
128
+ ('exp(βˆ’x)', f'{1/np.exp(exponent):.4f}','#475569'),
129
+ ('─' * 10, '─' * 6, C_BORDER),
130
+ ('PAF', f'{cur_paf*100:.2f}%', ACCENT),
131
+ ]
132
+
133
+ ax2.text(0.5, 0.97, 'BREAKDOWN', transform=ax2.transAxes,
134
+ ha='center', fontsize=15, color=ACCENT, fontweight='bold')
135
+
136
+ y_start = 0.88
137
+ for i, (k, v, c) in enumerate(rows):
138
+ y = y_start - i * 0.095
139
+ ax2.text(0.08, y, k, transform=ax2.transAxes,
140
+ fontsize=12, color=C_DIM, va='center')
141
+ ax2.text(0.92, y, v, transform=ax2.transAxes,
142
+ fontsize=12, color=c, va='center', ha='right', fontweight='bold')
143
+
144
+ ax2.set_title('Parameters', color=C_TEXT, fontsize=15, pad=8)
145
+
146
+ plt.tight_layout(pad=1.5)
147
+ plt.show()
148
+
149
+ info_box.value = f"""
150
+ <div style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;
151
+ padding:8px 18px;font-family:monospace;font-size:15px;
152
+ color:#64748b;display:inline-block;margin-top:6px;">
153
+ episodes&nbsp;<b style="color:{ACCENT}">{ep:.2f}</b> &nbsp;Β·&nbsp;
154
+ incidence&nbsp;<b style="color:{C_INC}">{inc:.3f}</b> &nbsp;Β·&nbsp;
155
+ Ξ”z&nbsp;<b style="color:{C_ZS}">{zs:.3f}</b> &nbsp;Β·&nbsp;
156
+ RR&nbsp;<b style="color:{C_RR}">{rr:.3f}</b> &nbsp;&nbsp;β†’&nbsp;&nbsp;
157
+ <b style="color:{ACCENT};font-size:15px;">PAF = {cur_paf*100:.2f}%</b>
158
+ </div>"""
159
+
160
+ def on_change(change):
161
+ draw(s_ep.value, s_inc.value, s_zs.value, s_rr.value)
162
+
163
+ def on_reset(b):
164
+ s_ep.value=5.0; s_inc.value=0.3; s_zs.value=1.2; s_rr.value=2.5
165
+
166
+ for s in [s_ep, s_inc, s_zs, s_rr]:
167
+ s.observe(on_change, names='value')
168
+ btn_reset.on_click(on_reset)
169
+
170
+ # ── layout & display ───────────────────────────────────────────────────────
171
+ header = HTML("""
172
+ <div style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:10px;
173
+ padding:12px 20px;margin-bottom:12px;font-family:monospace;">
174
+ <span style="color:#1e293b;font-size:18px;">Population Attributable Fraction Visualizer</span>
175
+ </div>""")
176
+
177
+ controls = VBox([
178
+ HBox([s_ep, s_inc]),
179
+ HBox([s_zs, s_rr]),
180
+ HBox([btn_reset, info_box])
181
+ ], layout=Layout(padding='10px'))
182
+
183
+ display(VBox([header, controls, out]))
184
+ draw(s_ep.value, s_inc.value, s_zs.value, s_rr.value)