| import gradio as gr
|
|
|
| from controllers.estimation.graphical_controller import run_graphical_analysis
|
| from controllers.utils.downloads import figure_to_png
|
|
|
|
|
| def build(state):
|
|
|
| ALL_MEAN_ESTIMATORS = [
|
| "Sample Mean",
|
| "Geometric Mean",
|
| "Harmonic Mean",
|
| "Interquartile Mean",
|
| "Trimmed Mean",
|
| "Winsorized Mean",
|
| "Weighted Mean",
|
| ]
|
|
|
| ALL_DEVIATION_ESTIMATORS = [
|
| "Deviation (1 ddof)",
|
| "Range (bias corrected)",
|
| "IQR (bias corrected)",
|
| "MAD (bias corrected)",
|
| "AAD (bias corrected)",
|
| ]
|
|
|
|
|
|
|
|
|
| def update_estimator_dropdowns(column):
|
| df = state.filtered_df if state.filtered_df is not None else state.df
|
|
|
| if df is None or column is None or column not in df.columns:
|
| return gr.update(), gr.update()
|
|
|
| data = df[column].dropna()
|
|
|
| mean_choices = ALL_MEAN_ESTIMATORS.copy()
|
| if (data <= 0).any():
|
| mean_choices = [
|
| m
|
| for m in mean_choices
|
| if m not in ("Geometric Mean", "Harmonic Mean")
|
| ]
|
|
|
| deviation_choices = ALL_DEVIATION_ESTIMATORS.copy()
|
| if len(data) > 25:
|
| deviation_choices = [
|
| d
|
| for d in deviation_choices
|
| if d != "Range (bias corrected)"
|
| ]
|
|
|
| return (
|
| gr.update(choices=mean_choices),
|
| gr.update(choices=deviation_choices),
|
| )
|
|
|
| gr.Markdown("## 📊 Graphical Analysis")
|
|
|
|
|
|
|
|
|
| with gr.Row():
|
| refresh_button = gr.Button("🔄 Refresh Numeric Columns")
|
|
|
| column_dropdown = gr.Dropdown(
|
| label="Select Numeric Column",
|
| choices=[],
|
| interactive=True,
|
| elem_classes=["data-selector"],
|
| elem_id="custom_dropdown",
|
| )
|
|
|
| graph_type_dropdown = gr.Dropdown(
|
| label="Select Graph",
|
| choices=[
|
| "Histogram",
|
| "Empirical Probability Mass Function",
|
| "Empirical Cumulative Distribution Function (ECDF)",
|
| ],
|
| value="Histogram",
|
| interactive=True,
|
| )
|
|
|
|
|
|
|
|
|
| with gr.Row() as histo_main_row:
|
| histo_add_kde = gr.Checkbox(
|
| label="Add KDE", value=True, interactive=True
|
| )
|
| histo_add_data = gr.Checkbox(
|
| label="Show data", value=False, interactive=True
|
| )
|
| histo_add_normal = gr.Checkbox(
|
| label="Add Normal Density", value=False, interactive=True
|
| )
|
| histo_add_ci = gr.Checkbox(
|
| label="Add Confidence Interval", value=False, interactive=True
|
| )
|
| histo_add_pi = gr.Checkbox(
|
| label="Add Prediction Interval", value=False, interactive=True
|
| )
|
|
|
|
|
| with gr.Row(visible=False) as interval_choice_row:
|
| histo_choose_ci = gr.Radio(
|
| label="Confidence Interval",
|
| choices=["Mean", "Median", "Both"],
|
| value="Both",
|
| interactive=True,
|
| )
|
| histo_choose_pi = gr.Radio(
|
| label="Prediction Interval",
|
| choices=["Mean", "Median", "IQR", "Bootstrap"],
|
| value="Mean",
|
| interactive=True,
|
| )
|
|
|
| ci_pi_conf_level = gr.Textbox(
|
| label="Confidence level (e.g. 0.95)",
|
| value="0.95",
|
| interactive=True,
|
| )
|
|
|
|
|
|
|
|
|
| with gr.Row(visible=False) as ecdf_row:
|
| ecdf_add_conf = gr.Checkbox(
|
| label="Add CI for the ECDF",
|
| value=True,
|
| interactive=True,
|
| )
|
| ecdf_conf_level = gr.Textbox(
|
| label="Confidence level (e.g. 0.95)",
|
| value="0.95",
|
| interactive=True,
|
| visible=True,
|
| )
|
| ecdf_add_normal = gr.Checkbox(
|
| label="Add Normal CDF", value=False, interactive=True
|
| )
|
|
|
|
|
|
|
|
|
| with gr.Row(visible=False) as estimator_row:
|
| mean_select = gr.Dropdown(
|
| label="Mean Estimator",
|
| choices=ALL_MEAN_ESTIMATORS,
|
| value="Sample Mean",
|
| )
|
|
|
| trim_alpha = gr.Textbox(
|
| label="Trimmed Mean α",
|
| value="0.1",
|
| visible=False,
|
| )
|
|
|
| winsor_limits = gr.Textbox(
|
| label="Winsorized Limits (e.g. 0.1, 0.1)",
|
| value="0.1, 0.1",
|
| visible=False,
|
| )
|
|
|
| weights_column = gr.Dropdown(
|
| label="Weights Column",
|
| choices=[],
|
| visible=False,
|
| elem_classes=["data-selector"],
|
| elem_id="custom_dropdown",
|
| )
|
|
|
| median_select = gr.Dropdown(
|
| label="Median Estimator",
|
| choices=["Sample Median"],
|
| value="Sample Median",
|
| )
|
|
|
| sigma_select = gr.Dropdown(
|
| label="Deviation Estimator",
|
| choices=ALL_DEVIATION_ESTIMATORS,
|
| value="Deviation (1 ddof)",
|
| )
|
|
|
| def toggle_mean_params(mean_est):
|
| return (
|
| gr.update(visible=mean_est == "Trimmed Mean"),
|
| gr.update(visible=mean_est == "Winsorized Mean"),
|
| gr.update(visible=mean_est == "Weighted Mean"),
|
| )
|
|
|
| mean_select.change(
|
| toggle_mean_params,
|
| inputs=mean_select,
|
| outputs=[trim_alpha, winsor_limits, weights_column],
|
| )
|
|
|
| with gr.Row(visible=False) as bootstrap_row:
|
| boots_mean = gr.Checkbox(label="Bootstrap Mean", value=False)
|
| boots_median = gr.Checkbox(label="Bootstrap Median", value=False)
|
| boots_sigma = gr.Checkbox(label="Bootstrap Deviation", value=False)
|
| boots_pi = gr.Checkbox(label="Bootstrap Prediction", value=False)
|
|
|
| with gr.Row(visible=False) as bootstrap_samples_row:
|
| bootstrap_samples = gr.Slider(
|
| label="Bootstrap samples",
|
| minimum=100,
|
| maximum=5000,
|
| step=100,
|
| value=1000,
|
| )
|
|
|
| with gr.Row(visible=False) as normal_mu_row:
|
| normal_mu_source = gr.Radio(
|
| label="Normal μ based on",
|
| choices=["Mean-based CI", "Median-based CI"],
|
| value="Mean-based CI",
|
| interactive=True,
|
| )
|
|
|
|
|
|
|
|
|
| with gr.Column(elem_id="column_centered"):
|
| run_button = gr.Button(
|
| "🚀 Run Graphical Analysis",
|
| elem_id="run_button",
|
| )
|
|
|
| with gr.Row(visible=False) as download_row:
|
| filename_input = gr.Textbox(
|
| label="Filename (without extension)",
|
| placeholder="e.g. histogram",
|
| )
|
| download_button = gr.Button("🖼️ Download Figure as PNG")
|
| download_file = gr.File(label="Download link will appear here")
|
|
|
| output_plot = gr.Plot(visible=False)
|
|
|
|
|
|
|
|
|
|
|
| def refresh_columns():
|
| numeric_cols = state.numeric_cols or []
|
| return (
|
| gr.update(choices=numeric_cols),
|
| gr.update(choices=numeric_cols),
|
| )
|
|
|
| def toggle_graph_type(graph_type):
|
| is_hist = graph_type in [
|
| "Histogram",
|
| "Empirical Probability Mass Function",
|
| ]
|
| return (
|
| gr.update(visible=is_hist),
|
| gr.update(visible=not is_hist),
|
| )
|
|
|
| def update_estimator_block(
|
| graph_type, add_normal, add_ci, add_pi, ecdf_add_normal_val
|
| ):
|
| is_hist = graph_type in [
|
| "Histogram",
|
| "Empirical Probability Mass Function",
|
| ]
|
| is_ecdf = (
|
| graph_type == "Empirical Cumulative Distribution Function (ECDF)"
|
| )
|
|
|
|
|
| if is_hist:
|
| any_flag = add_normal or add_ci or add_pi
|
| intervals_flag = add_ci or add_pi
|
| estimator_visible = any_flag
|
| bootstrap_visible = any_flag
|
| interval_row_visible = intervals_flag
|
| choose_ci_visible = add_ci
|
| choose_pi_visible = add_pi
|
| normal_mu_visible = add_normal
|
| ci_pi_visible = intervals_flag
|
| boots_pi_visible = add_pi
|
|
|
| elif is_ecdf:
|
| any_flag = ecdf_add_normal_val
|
| estimator_visible = any_flag
|
| bootstrap_visible = any_flag
|
| interval_row_visible = False
|
| choose_ci_visible = False
|
| choose_pi_visible = False
|
| normal_mu_visible = ecdf_add_normal_val
|
| ci_pi_visible = False
|
| boots_pi_visible = False
|
| else:
|
|
|
| estimator_visible = False
|
| bootstrap_visible = False
|
| interval_row_visible = False
|
| choose_ci_visible = False
|
| choose_pi_visible = False
|
| normal_mu_visible = False
|
| ci_pi_visible = False
|
| boots_pi_visible = False
|
|
|
| return (
|
| gr.update(visible=estimator_visible),
|
| gr.update(visible=bootstrap_visible),
|
| gr.update(visible=interval_row_visible),
|
| gr.update(visible=choose_ci_visible),
|
| gr.update(visible=choose_pi_visible),
|
| gr.update(visible=normal_mu_visible),
|
| gr.update(visible=boots_pi_visible),
|
| )
|
|
|
| def toggle_bootstrap_samples(
|
| boots_mean_val,
|
| boots_median_val,
|
| boots_sigma_val,
|
| boots_pi_val,
|
| ):
|
| show = any(
|
| [boots_mean_val, boots_median_val, boots_sigma_val, boots_pi_val]
|
| )
|
| return gr.update(visible=show)
|
|
|
|
|
|
|
|
|
| refresh_button.click(
|
| refresh_columns,
|
| outputs=[column_dropdown, weights_column],
|
| )
|
|
|
| column_dropdown.change(
|
| fn=update_estimator_dropdowns,
|
| inputs=column_dropdown,
|
| outputs=[mean_select, sigma_select],
|
| )
|
|
|
| graph_type_dropdown.change(
|
| fn=toggle_graph_type,
|
| inputs=graph_type_dropdown,
|
| outputs=[histo_main_row, ecdf_row],
|
| )
|
|
|
| ecdf_add_conf.change(
|
| fn=lambda check: gr.update(visible=check),
|
| inputs=ecdf_add_conf,
|
| outputs=ecdf_conf_level,
|
| )
|
|
|
|
|
| for comp in (
|
| graph_type_dropdown,
|
| histo_add_normal,
|
| histo_add_ci,
|
| histo_add_pi,
|
| ecdf_add_normal,
|
| ):
|
| comp.change(
|
| fn=update_estimator_block,
|
| inputs=[
|
| graph_type_dropdown,
|
| histo_add_normal,
|
| histo_add_ci,
|
| histo_add_pi,
|
| ecdf_add_normal,
|
| ],
|
| outputs=[
|
| estimator_row,
|
| bootstrap_row,
|
| interval_choice_row,
|
| histo_choose_ci,
|
| histo_choose_pi,
|
| normal_mu_row,
|
| boots_pi,
|
| ],
|
| )
|
|
|
|
|
| for comp in (boots_mean, boots_median, boots_sigma, boots_pi):
|
| comp.change(
|
| fn=toggle_bootstrap_samples,
|
| inputs=[boots_mean, boots_median, boots_sigma, boots_pi],
|
| outputs=bootstrap_samples_row,
|
| )
|
|
|
|
|
|
|
|
|
| def on_run(
|
| column,
|
| graph_type,
|
| histo_kde,
|
| histo_data,
|
| histo_ci,
|
| histo_ci_choice,
|
| histo_pi,
|
| histo_pi_choice,
|
| ci_pi_conf_level_text,
|
| histo_normal,
|
| mean_est,
|
| trim_alpha_text,
|
| winsor_text,
|
| weights_col,
|
| median_est,
|
| sigma_est,
|
| normal_mu_src,
|
| boots_mean_val,
|
| boots_median_val,
|
| boots_sigma_val,
|
| boots_pi_val,
|
| boot_samples_val,
|
| ecdf_add_conf_val,
|
| ecdf_conf_level_text,
|
| ecdf_add_normal_val,
|
| ):
|
| df = state.filtered_df if state.filtered_df is not None else state.df
|
| if df is None:
|
| raise gr.Error(
|
| "No data loaded. Please load data in the Data tab first."
|
| )
|
|
|
| if column is None:
|
| raise gr.Error("Please select a numeric column.")
|
|
|
|
|
| try:
|
| ci_pi_level = float(ci_pi_conf_level_text)
|
| except Exception:
|
| raise gr.Error(
|
| "Confidence level for CI/PI must be numeric, e.g. 0.95."
|
| )
|
|
|
|
|
| try:
|
| ecdf_level = float(ecdf_conf_level_text)
|
| except Exception:
|
| raise gr.Error(
|
| "ECDF confidence level must be numeric, e.g. 0.95."
|
| )
|
|
|
| try:
|
| fig = run_graphical_analysis(
|
| df=df,
|
| column=column,
|
| graph_type=graph_type,
|
| add_kde=histo_kde,
|
| add_data=histo_data,
|
| add_normal=histo_normal,
|
| add_ci=histo_ci,
|
| ci_choice=histo_ci_choice,
|
| add_pi=histo_pi,
|
| pi_choice=histo_pi_choice,
|
| mean_estimator=mean_est,
|
| median_estimator=median_est,
|
| sigma_estimator=sigma_est,
|
| trim_param=trim_alpha_text,
|
| winsor_limits=winsor_text,
|
| weights_col=weights_col,
|
| normal_mu_source=normal_mu_src,
|
| bootstrap_mean=boots_mean_val,
|
| bootstrap_median=boots_median_val,
|
| bootstrap_sigma=boots_sigma_val,
|
| bootstrap_prediction=boots_pi_val,
|
| bootstrap_samples=int(boot_samples_val),
|
| ci_pi_conf_level=ci_pi_level,
|
| ecdf_add_conf=ecdf_add_conf_val,
|
| ecdf_conf_level=ecdf_level,
|
| ecdf_add_normal=ecdf_add_normal_val,
|
| )
|
| except ValueError as e:
|
| raise gr.Error(str(e))
|
|
|
|
|
| return (
|
| gr.update(value=fig, visible=True),
|
| gr.update(visible=True),
|
| None,
|
| )
|
|
|
|
|
| run_button.click(
|
| fn=on_run,
|
| inputs=[
|
| column_dropdown,
|
| graph_type_dropdown,
|
| histo_add_kde,
|
| histo_add_data,
|
| histo_add_ci,
|
| histo_choose_ci,
|
| histo_add_pi,
|
| histo_choose_pi,
|
| ci_pi_conf_level,
|
| histo_add_normal,
|
| mean_select,
|
| trim_alpha,
|
| winsor_limits,
|
| weights_column,
|
| median_select,
|
| sigma_select,
|
| normal_mu_source,
|
| boots_mean,
|
| boots_median,
|
| boots_sigma,
|
| boots_pi,
|
| bootstrap_samples,
|
| ecdf_add_conf,
|
| ecdf_conf_level,
|
| ecdf_add_normal,
|
| ],
|
| outputs=[output_plot, download_row, download_file],
|
| )
|
|
|
| def on_download(fig, filename):
|
| if fig is None:
|
| return None
|
| name = (filename or "graphical_analysis").strip()
|
| base = name or "graphical_analysis"
|
| return figure_to_png(fig, base)
|
|
|
| download_button.click(
|
| fn=on_download,
|
| inputs=[output_plot, filename_input],
|
| outputs=download_file,
|
| )
|
|
|