evalrx 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- evalrx/__init__.py +139 -0
- evalrx/agent_assets/__init__.py +2 -0
- evalrx/agent_assets/skills/README.md +28 -0
- evalrx/agent_assets/skills/eval-chart-style/SKILL.md +172 -0
- evalrx/agent_assets/skills/evalrx-report-ui/SKILL.md +116 -0
- evalrx/agent_assets/skills/nature-figure/LICENSE +201 -0
- evalrx/agent_assets/skills/nature-figure/README.md +412 -0
- evalrx/agent_assets/skills/nature-figure/SKILL.md +60 -0
- evalrx/agent_assets/skills/nature-figure/manifest.yaml +59 -0
- evalrx/agent_assets/skills/nature-figure/references/api.md +436 -0
- evalrx/agent_assets/skills/nature-figure/references/backend-selection.md +100 -0
- evalrx/agent_assets/skills/nature-figure/references/chart-types.md +281 -0
- evalrx/agent_assets/skills/nature-figure/references/common-patterns.md +350 -0
- evalrx/agent_assets/skills/nature-figure/references/demos.md +65 -0
- evalrx/agent_assets/skills/nature-figure/references/design-theory.md +439 -0
- evalrx/agent_assets/skills/nature-figure/references/figure-contract.md +93 -0
- evalrx/agent_assets/skills/nature-figure/references/figure-legend-conventions.md +71 -0
- evalrx/agent_assets/skills/nature-figure/references/nature-2026-observations.md +112 -0
- evalrx/agent_assets/skills/nature-figure/references/qa-contract.md +119 -0
- evalrx/agent_assets/skills/nature-figure/references/r-template-index.md +66 -0
- evalrx/agent_assets/skills/nature-figure/references/r-workflow.md +161 -0
- evalrx/agent_assets/skills/nature-figure/references/tutorials.md +251 -0
- evalrx/agent_assets/skills/nature-figure/static/core/contract.md +29 -0
- evalrx/agent_assets/skills/nature-figure/static/core/stance.md +37 -0
- evalrx/agent_assets/skills/nature-figure/static/fragments/backend/python.md +37 -0
- evalrx/agent_assets/skills/nature-figure/static/fragments/backend/r.md +44 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/SKILL.md +213 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/assets/analysis_report_template.md +53 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/references/model_selection.md +72 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.R +130 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.py +150 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.R +181 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.py +186 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.R +149 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.py +177 -0
- evalrx/agent_assets/skills.py +27 -0
- evalrx/agent_runtime/__init__.py +78 -0
- evalrx/agent_runtime/_docker_runner.py +89 -0
- evalrx/agent_runtime/cli_runtime.py +103 -0
- evalrx/agent_runtime/cli_transcript.py +138 -0
- evalrx/agent_runtime/cli_types.py +68 -0
- evalrx/agent_runtime/codegen/__init__.py +5 -0
- evalrx/agent_runtime/codegen/runner.py +94 -0
- evalrx/agent_runtime/experiment_harness.py +117 -0
- evalrx/agent_runtime/factory.py +102 -0
- evalrx/agent_runtime/json_shape.py +44 -0
- evalrx/agent_runtime/judges/__init__.py +28 -0
- evalrx/agent_runtime/judges/agy.py +179 -0
- evalrx/agent_runtime/judges/autodetect.py +135 -0
- evalrx/agent_runtime/judges/claude.py +159 -0
- evalrx/agent_runtime/judges/codex.py +120 -0
- evalrx/agent_runtime/providers/__init__.py +21 -0
- evalrx/agent_runtime/providers/antigravity.py +31 -0
- evalrx/agent_runtime/providers/base.py +145 -0
- evalrx/agent_runtime/providers/claude_code.py +49 -0
- evalrx/agent_runtime/providers/codex.py +37 -0
- evalrx/agent_runtime/providers/gemini_cli.py +26 -0
- evalrx/agent_runtime/providers/kimi_cli.py +27 -0
- evalrx/agent_runtime/providers/opencode.py +27 -0
- evalrx/agent_runtime/providers/registry.py +58 -0
- evalrx/agent_runtime/sandbox.py +517 -0
- evalrx/agent_runtime/skill_audit.py +143 -0
- evalrx/agent_runtime/skills/__init__.py +19 -0
- evalrx/agent_runtime/skills/installer.py +68 -0
- evalrx/agent_runtime/skills/prompt_policy.py +86 -0
- evalrx/agent_runtime/skills/resolver.py +19 -0
- evalrx/analysis/__init__.py +132 -0
- evalrx/analysis/adjudicate.py +154 -0
- evalrx/analysis/analysis_module.py +361 -0
- evalrx/analysis/api.py +171 -0
- evalrx/analysis/case_studio.py +651 -0
- evalrx/analysis/cli.py +114 -0
- evalrx/analysis/dashboard.py +350 -0
- evalrx/analysis/eval_case_matrix.py +118 -0
- evalrx/analysis/eval_viz_theme.py +833 -0
- evalrx/analysis/explore_run.py +333 -0
- evalrx/analysis/explorer.py +1276 -0
- evalrx/analysis/failure_modes.py +607 -0
- evalrx/analysis/fused_pipeline.py +489 -0
- evalrx/analysis/holdout.py +300 -0
- evalrx/analysis/hypothesis_agent.py +230 -0
- evalrx/analysis/narration.py +177 -0
- evalrx/analysis/operationalize.py +442 -0
- evalrx/analysis/plain_language.py +42 -0
- evalrx/analysis/planner.py +283 -0
- evalrx/analysis/probe_search.py +203 -0
- evalrx/analysis/profile.py +268 -0
- evalrx/analysis/prompts/__init__.py +0 -0
- evalrx/analysis/prompts/explorer.py +417 -0
- evalrx/analysis/prompts/failure_modes.py +33 -0
- evalrx/analysis/prompts/holdout.py +27 -0
- evalrx/analysis/prompts/hypothesis_agent.py +78 -0
- evalrx/analysis/prompts/run_codebase.py +47 -0
- evalrx/analysis/prompts/stats_agent.py +72 -0
- evalrx/analysis/prompts/stats_tool_generator.py +43 -0
- evalrx/analysis/result_marker.py +47 -0
- evalrx/analysis/run_codebase.py +242 -0
- evalrx/analysis/run_view.py +205 -0
- evalrx/analysis/stage_views.py +93 -0
- evalrx/analysis/stats_agent.py +944 -0
- evalrx/analysis/stats_tool_agent.py +261 -0
- evalrx/analysis/stats_tool_generator.py +415 -0
- evalrx/analysis/stats_tools.py +1153 -0
- evalrx/analysis/trajectory_records.py +193 -0
- evalrx/analysis/workbench.py +431 -0
- evalrx/analyzers/__init__.py +42 -0
- evalrx/analyzers/agent/__init__.py +25 -0
- evalrx/analyzers/agent/counterfactual.py +84 -0
- evalrx/analyzers/agent/first_error_judge.py +96 -0
- evalrx/analyzers/agent/ignored_obs.py +81 -0
- evalrx/analyzers/agent/loop_detect.py +79 -0
- evalrx/analyzers/agent/reliability.py +165 -0
- evalrx/analyzers/agent/tool_shap.py +225 -0
- evalrx/analyzers/agent/trajectory_rubric.py +168 -0
- evalrx/analyzers/attention/__init__.py +19 -0
- evalrx/analyzers/attention/relative_attn.py +610 -0
- evalrx/analyzers/attention/rollout.py +73 -0
- evalrx/analyzers/attention/sink.py +56 -0
- evalrx/analyzers/attention/summary.py +190 -0
- evalrx/analyzers/attribution/__init__.py +6 -0
- evalrx/analyzers/attribution/generic_attn.py +31 -0
- evalrx/analyzers/attribution/gradcam.py +30 -0
- evalrx/analyzers/base.py +12 -0
- evalrx/analyzers/geometry/__init__.py +6 -0
- evalrx/analyzers/geometry/cka.py +70 -0
- evalrx/analyzers/geometry/linear_probe.py +157 -0
- evalrx/analyzers/hallucination/__init__.py +9 -0
- evalrx/analyzers/hallucination/chair.py +78 -0
- evalrx/analyzers/hallucination/opera.py +29 -0
- evalrx/analyzers/hallucination/pope.py +119 -0
- evalrx/analyzers/hallucination/selfcheck.py +155 -0
- evalrx/analyzers/hallucination/vcd.py +29 -0
- evalrx/analyzers/lens/__init__.py +7 -0
- evalrx/analyzers/lens/layer_contrast.py +133 -0
- evalrx/analyzers/lens/logit_lens.py +138 -0
- evalrx/analyzers/lens/tuned_lens.py +30 -0
- evalrx/analyzers/patching/__init__.py +5 -0
- evalrx/analyzers/patching/causal_trace.py +30 -0
- evalrx/analyzers/perturbation/__init__.py +23 -0
- evalrx/analyzers/perturbation/_shapley.py +54 -0
- evalrx/analyzers/perturbation/context_shap.py +174 -0
- evalrx/analyzers/perturbation/cot_faithfulness.py +239 -0
- evalrx/analyzers/perturbation/format_sensitivity.py +237 -0
- evalrx/analyzers/perturbation/mm_shap.py +146 -0
- evalrx/analyzers/perturbation/modality_ablation.py +196 -0
- evalrx/analyzers/perturbation/perturbation_battery.py +274 -0
- evalrx/analyzers/perturbation/prompt_contrast.py +265 -0
- evalrx/analyzers/perturbation/rise.py +94 -0
- evalrx/analyzers/perturbation/vl_shap.py +102 -0
- evalrx/analyzers/reasoning/__init__.py +33 -0
- evalrx/analyzers/reasoning/_text.py +328 -0
- evalrx/analyzers/reasoning/answer_extraction_audit.py +327 -0
- evalrx/analyzers/reasoning/arith_audit.py +226 -0
- evalrx/analyzers/reasoning/contamination.py +214 -0
- evalrx/analyzers/reasoning/knowledge_split.py +253 -0
- evalrx/analyzers/reasoning/self_repair.py +246 -0
- evalrx/analyzers/reasoning/step_rollout_value.py +216 -0
- evalrx/analyzers/reasoning/termination_audit.py +258 -0
- evalrx/analyzers/uncertainty/__init__.py +18 -0
- evalrx/analyzers/uncertainty/calibration.py +174 -0
- evalrx/analyzers/uncertainty/coverage_gap.py +199 -0
- evalrx/analyzers/uncertainty/entropy.py +90 -0
- evalrx/analyzers/uncertainty/logprob_entropy.py +69 -0
- evalrx/analyzers/uncertainty/self_consistency.py +204 -0
- evalrx/analyzers/uncertainty/verbalized_conf.py +64 -0
- evalrx/cli.py +411 -0
- evalrx/config.py +77 -0
- evalrx/contract/__init__.py +179 -0
- evalrx/contract/common.py +452 -0
- evalrx/contract/emit.py +948 -0
- evalrx/contract/export.py +237 -0
- evalrx/contract/m1.py +325 -0
- evalrx/contract/m2.py +317 -0
- evalrx/contract/m3.py +165 -0
- evalrx/contract/m4.py +130 -0
- evalrx/contract/m5.py +292 -0
- evalrx/contract/methodology.py +76 -0
- evalrx/contract/pre_m1.py +58 -0
- evalrx/contract/typescript.py +140 -0
- evalrx/core/__init__.py +85 -0
- evalrx/core/analyzer.py +174 -0
- evalrx/core/capability.py +54 -0
- evalrx/core/case.py +443 -0
- evalrx/core/experiment.py +106 -0
- evalrx/core/model.py +198 -0
- evalrx/core/pipeline.py +42 -0
- evalrx/core/registry.py +142 -0
- evalrx/core/result.py +64 -0
- evalrx/core/spec.py +173 -0
- evalrx/core/tokentype.py +165 -0
- evalrx/core/tool.py +92 -0
- evalrx/datasets/__init__.py +41 -0
- evalrx/datasets/base.py +68 -0
- evalrx/datasets/gui_os.py +52 -0
- evalrx/datasets/llm_qa.py +57 -0
- evalrx/datasets/pure_qa.py +12 -0
- evalrx/datasets/vlm_qa.py +695 -0
- evalrx/datasets/web_search_qa.py +52 -0
- evalrx/eval_agent/__init__.py +341 -0
- evalrx/eval_agent/_tools.py +81 -0
- evalrx/eval_agent/ab_runner.py +50 -0
- evalrx/eval_agent/agentic/__init__.py +43 -0
- evalrx/eval_agent/agentic/actions.py +216 -0
- evalrx/eval_agent/agentic/board.py +107 -0
- evalrx/eval_agent/agentic/loop.py +190 -0
- evalrx/eval_agent/agentic/tools.py +538 -0
- evalrx/eval_agent/checkpoint.py +57 -0
- evalrx/eval_agent/cli_agent.py +59 -0
- evalrx/eval_agent/cli_skills.py +5 -0
- evalrx/eval_agent/evolution.py +396 -0
- evalrx/eval_agent/git_manager.py +215 -0
- evalrx/eval_agent/hypothesis.py +172 -0
- evalrx/eval_agent/label_quarantine.py +209 -0
- evalrx/eval_agent/legacy.py +530 -0
- evalrx/eval_agent/log_schema.py +497 -0
- evalrx/eval_agent/loop.py +2159 -0
- evalrx/eval_agent/loop_reports.py +116 -0
- evalrx/eval_agent/model_instrumentation.py +282 -0
- evalrx/eval_agent/narration.py +193 -0
- evalrx/eval_agent/nl_runner.py +460 -0
- evalrx/eval_agent/orchestrator.py +61 -0
- evalrx/eval_agent/preregister.py +93 -0
- evalrx/eval_agent/prompts/__init__.py +1 -0
- evalrx/eval_agent/prompts/agentic.py +46 -0
- evalrx/eval_agent/prompts/case_discovery.py +25 -0
- evalrx/eval_agent/prompts/diagnosis.py +125 -0
- evalrx/eval_agent/prompts/experiment_writer.py +265 -0
- evalrx/eval_agent/prompts/explore_step.py +37 -0
- evalrx/eval_agent/prompts/fix_agent.py +257 -0
- evalrx/eval_agent/prompts/hypothesis_tester.py +15 -0
- evalrx/eval_agent/prompts/nl_runner.py +38 -0
- evalrx/eval_agent/prompts/probe_agent.py +25 -0
- evalrx/eval_agent/prompts/probe_candidate_generator.py +14 -0
- evalrx/eval_agent/prompts/probe_generator.py +35 -0
- evalrx/eval_agent/prompts/whitebox_probe_generator.py +38 -0
- evalrx/eval_agent/report.py +58 -0
- evalrx/eval_agent/run_context.py +354 -0
- evalrx/eval_agent/run_log.schema.json +1215 -0
- evalrx/eval_agent/run_logger_v2.py +1764 -0
- evalrx/eval_agent/run_metadata.py +208 -0
- evalrx/eval_agent/stages/__init__.py +56 -0
- evalrx/eval_agent/stages/case_discovery.py +293 -0
- evalrx/eval_agent/stages/diagnosis.py +1017 -0
- evalrx/eval_agent/stages/experiment_writer.py +1634 -0
- evalrx/eval_agent/stages/fix_agent.py +3916 -0
- evalrx/eval_agent/stages/fix_internals.py +499 -0
- evalrx/eval_agent/stages/fix_pipeline.py +725 -0
- evalrx/eval_agent/stages/fix_tiers.py +187 -0
- evalrx/eval_agent/stages/fix_tools.py +1034 -0
- evalrx/eval_agent/stages/hypothesis_tester.py +1014 -0
- evalrx/eval_agent/stages/probe.py +439 -0
- evalrx/eval_agent/stages/probe_agent.py +1079 -0
- evalrx/eval_agent/stages/probe_candidate_generator.py +128 -0
- evalrx/eval_agent/stages/probe_generator.py +326 -0
- evalrx/eval_agent/stages/probe_search_agent.py +106 -0
- evalrx/eval_agent/stages/protocol.py +112 -0
- evalrx/eval_agent/stages/repair_catalog.py +273 -0
- evalrx/eval_agent/stages/surgery.py +524 -0
- evalrx/eval_agent/stages/whitebox_probe_generator.py +351 -0
- evalrx/eval_agent/store.py +231 -0
- evalrx/logging_utils.py +112 -0
- evalrx/models/__init__.py +161 -0
- evalrx/models/_discover.py +101 -0
- evalrx/models/agent.py +380 -0
- evalrx/models/backends/__init__.py +58 -0
- evalrx/models/backends/api.py +169 -0
- evalrx/models/backends/base.py +57 -0
- evalrx/models/backends/gemini_compat.py +579 -0
- evalrx/models/backends/hf_local.py +2074 -0
- evalrx/models/backends/openai_compat.py +301 -0
- evalrx/models/backends/vllm_offline.py +116 -0
- evalrx/models/base.py +24 -0
- evalrx/models/blackbox/__init__.py +4 -0
- evalrx/models/blackbox/agent.py +31 -0
- evalrx/models/blackbox/base.py +29 -0
- evalrx/models/blackbox/gemini.py +279 -0
- evalrx/models/blackbox/llm_api.py +17 -0
- evalrx/models/blackbox/vlm_api.py +17 -0
- evalrx/models/compose.py +66 -0
- evalrx/models/inference.py +88 -0
- evalrx/models/paper_methods/__init__.py +8 -0
- evalrx/models/paper_methods/aad.py +53 -0
- evalrx/models/paper_methods/ifcd.py +204 -0
- evalrx/models/paper_methods/pai.py +164 -0
- evalrx/models/paper_methods/tcd.py +202 -0
- evalrx/models/paper_methods/vcd.py +45 -0
- evalrx/models/paper_methods/vicrop.py +137 -0
- evalrx/models/toolcodec.py +143 -0
- evalrx/models/tools/__init__.py +20 -0
- evalrx/models/tools/perception.py +300 -0
- evalrx/models/tools/visual.py +174 -0
- evalrx/models/whitebox/__init__.py +26 -0
- evalrx/models/whitebox/agent.py +31 -0
- evalrx/models/whitebox/base.py +24 -0
- evalrx/models/whitebox/qwen.py +61 -0
- evalrx/models/whitebox/qwen2_5_omni.py +29 -0
- evalrx/models/whitebox/qwen2_audio.py +25 -0
- evalrx/models/whitebox/qwen_omni.py +53 -0
- evalrx/models/whitebox/qwen_vl.py +62 -0
- evalrx/observability/__init__.py +21 -0
- evalrx/observability/envelope.py +122 -0
- evalrx/observability/outbox.py +111 -0
- evalrx/observability/tracer.py +882 -0
- evalrx/reporting/__init__.py +28 -0
- evalrx/reporting/case_study.py +947 -0
- evalrx/reporting/compiler.py +587 -0
- evalrx/reporting/dynamic.py +1882 -0
- evalrx/reporting/html_report.py +2225 -0
- evalrx/reporting/langfuse_exporter.py +38 -0
- evalrx/reporting/langfuse_source.py +155 -0
- evalrx/reporting/model.py +151 -0
- evalrx/reporting/run_events.py +184 -0
- evalrx/reporting/server.py +557 -0
- evalrx/reporting/stages.py +58 -0
- evalrx/reporting/static_export.py +142 -0
- evalrx/reporting/web_dist/index.html +146 -0
- evalrx/specs.py +727 -0
- evalrx/stats/__init__.py +47 -0
- evalrx/stats/api.py +192 -0
- evalrx/stats/bootstrap.py +86 -0
- evalrx/stats/ebh.py +27 -0
- evalrx/stats/evalue.py +98 -0
- evalrx/stats/friedman.py +138 -0
- evalrx/stats/mcnemar.py +40 -0
- evalrx/stats/multiplicity.py +159 -0
- evalrx/stats/subset_sampling.py +55 -0
- evalrx/term_links.py +43 -0
- evalrx/viz/__init__.py +7 -0
- evalrx/viz/labels.py +77 -0
- evalrx/viz/prompts.py +39 -0
- evalrx/viz/renderer.py +590 -0
- evalrx/viz/schema.py +36 -0
- evalrx/viz/style.py +134 -0
- evalrx-0.1.2.dist-info/METADATA +532 -0
- evalrx-0.1.2.dist-info/RECORD +339 -0
- evalrx-0.1.2.dist-info/WHEEL +5 -0
- evalrx-0.1.2.dist-info/entry_points.txt +3 -0
- evalrx-0.1.2.dist-info/licenses/LICENSE +121 -0
- evalrx-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# API Reference — Nature Figure Making
|
|
2
|
+
|
|
3
|
+
Conventions, constants, and reusable code blocks. Implement in your script or adapt as needed.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Constants
|
|
8
|
+
|
|
9
|
+
### PALETTE
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
# NOTE (EvalRX adaptation): the key names and family semantics are the
|
|
13
|
+
# original skill's; the VALUES are synced to the CVD-validated EvalRX
|
|
14
|
+
# dataviz palette so agent-drawn figures, host plotly charts, and host
|
|
15
|
+
# spec-rendered PNGs share ONE palette. See the notice in README.md.
|
|
16
|
+
PALETTE = {
|
|
17
|
+
"blue_main": "#2a78d6",
|
|
18
|
+
"blue_secondary": "#6da7ec",
|
|
19
|
+
"green_1": "#7ec07e",
|
|
20
|
+
"green_2": "#4ca74b",
|
|
21
|
+
"green_3": "#008300",
|
|
22
|
+
"red_1": "#ee9999",
|
|
23
|
+
"red_2": "#e97675",
|
|
24
|
+
"red_strong": "#e34948",
|
|
25
|
+
"neutral_light": "#c3c2b7",
|
|
26
|
+
"neutral_mid": "#898781",
|
|
27
|
+
"neutral_dark": "#52514e",
|
|
28
|
+
"neutral_black": "#0b0b0b",
|
|
29
|
+
"gold": "#eda100",
|
|
30
|
+
"teal": "#1baf7a",
|
|
31
|
+
"violet": "#4a3aa7",
|
|
32
|
+
"magenta":"#e87ba4",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Slots 1-5 are validated as a categorical set (worst adjacent CVD dE 13.3
|
|
36
|
+
# with direct labels as relief); the 6th slot is muted ink for a
|
|
37
|
+
# reference/background series only -- never a data series that must be
|
|
38
|
+
# told apart by hue.
|
|
39
|
+
DEFAULT_COLORS = [
|
|
40
|
+
PALETTE["blue_main"],
|
|
41
|
+
PALETTE["green_3"],
|
|
42
|
+
PALETTE["red_strong"],
|
|
43
|
+
PALETTE["teal"],
|
|
44
|
+
PALETTE["violet"],
|
|
45
|
+
PALETTE["neutral_mid"],
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
PALETTE_NMI_PASTEL = {
|
|
49
|
+
"baseline_dark": "#484878",
|
|
50
|
+
"baseline_mid": "#7884B4",
|
|
51
|
+
"baseline_soft": "#B4C0E4",
|
|
52
|
+
"ours_tiny": "#E4E4F0",
|
|
53
|
+
"ours_base": "#E4CCD8",
|
|
54
|
+
"ours_large": "#F0C0CC",
|
|
55
|
+
"bg_lilac": "#E0E0F0",
|
|
56
|
+
"bg_aqua": "#E0F0F0",
|
|
57
|
+
"bg_peach": "#F0E0D0",
|
|
58
|
+
"neutral_light": "#D8D8D8",
|
|
59
|
+
"neutral_mid": "#A8A8A8",
|
|
60
|
+
"neutral_dark": "#606060",
|
|
61
|
+
"delta_up": "#2E9E44",
|
|
62
|
+
"delta_down": "#E53935",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
DEFAULT_COLORS_NMI_PASTEL = [
|
|
66
|
+
PALETTE_NMI_PASTEL["baseline_dark"],
|
|
67
|
+
PALETTE_NMI_PASTEL["baseline_mid"],
|
|
68
|
+
PALETTE_NMI_PASTEL["baseline_soft"],
|
|
69
|
+
PALETTE_NMI_PASTEL["ours_tiny"],
|
|
70
|
+
PALETTE_NMI_PASTEL["ours_base"],
|
|
71
|
+
PALETTE_NMI_PASTEL["ours_large"],
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
PALETTE_NATURE_IMAGING = {
|
|
75
|
+
"bg": "#000000",
|
|
76
|
+
"context": "#B8B8B8",
|
|
77
|
+
"cyan": "#22D7E6",
|
|
78
|
+
"magenta": "#FF2AD4",
|
|
79
|
+
"white": "#FFFFFF",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
PALETTE_NATURE_MATERIAL = {
|
|
83
|
+
"aqua": "#77D7D1",
|
|
84
|
+
"teal": "#33B5A5",
|
|
85
|
+
"lilac": "#B9A7E8",
|
|
86
|
+
"violet": "#7C6CCF",
|
|
87
|
+
"callout_red": "#E53935",
|
|
88
|
+
"neutral": "#D9D9D9",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
PALETTE_NATURE_CLINICAL = {
|
|
92
|
+
"baseline": "#272727",
|
|
93
|
+
"week6": "#E28E2C",
|
|
94
|
+
"week13": "#D24B40",
|
|
95
|
+
"week26": "#5B8FD6",
|
|
96
|
+
"year1": "#7BAA5B",
|
|
97
|
+
"year2": "#C45AD6",
|
|
98
|
+
"group_band": "#F2E6D9",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
PALETTE_NATURE_GENOMICS = {
|
|
102
|
+
"neutral_light": "#D8D8D8",
|
|
103
|
+
"neutral_mid": "#8F8F8F",
|
|
104
|
+
"wave1": "#D9544D",
|
|
105
|
+
"wave2": "#5B7FCA",
|
|
106
|
+
"wave3": "#B89BD9",
|
|
107
|
+
"outline": "#4D4D4D",
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Use `DEFAULT_COLORS` when color itself carries explicit semantic meaning (`hero`, `baseline`, `positive variant`).
|
|
112
|
+
Use `DEFAULT_COLORS_NMI_PASTEL` when several compared methods belong to one or two related families and the page
|
|
113
|
+
should feel visually unified.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## MANDATORY font + SVG rules (always first, no exceptions)
|
|
118
|
+
|
|
119
|
+
These three lines are **non-negotiable** and must appear at the top of every script,
|
|
120
|
+
before any figure is created. They guarantee editable text in SVG output:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
plt.rcParams['font.family'] = 'sans-serif'
|
|
124
|
+
plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Liberation Sans']
|
|
125
|
+
plt.rcParams['svg.fonttype'] = 'none' # keeps text as <text> nodes, not paths
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Why `svg.fonttype = 'none'`**: matplotlib's default (`'path'`) converts every
|
|
129
|
+
glyph to a bezier path, making text unselectable, unsearchable, and impossible to
|
|
130
|
+
re-align in Illustrator / Inkscape. With `'none'`, text stays as SVG `<text>` elements
|
|
131
|
+
and font substitution happens at render time.
|
|
132
|
+
|
|
133
|
+
**Output format**: always save as `.svg` (primary). PNG/PDF are optional secondary
|
|
134
|
+
exports. Never use `.png` alone when the figure contains text that may need adjustment.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## apply_publication_style()
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
def apply_publication_style(font_size=16, axes_linewidth=2.5, use_tex=False):
|
|
142
|
+
"""Apply Nature-style rcParams. Call once before creating any figures."""
|
|
143
|
+
# ── MANDATORY: editable SVG text ──────────────────────────────────────────
|
|
144
|
+
plt.rcParams['font.family'] = 'sans-serif'
|
|
145
|
+
plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Liberation Sans']
|
|
146
|
+
plt.rcParams['svg.fonttype'] = 'none'
|
|
147
|
+
# ── Layout & style ────────────────────────────────────────────────────────
|
|
148
|
+
plt.rcParams['font.size'] = font_size
|
|
149
|
+
plt.rcParams['axes.spines.right'] = False
|
|
150
|
+
plt.rcParams['axes.spines.top'] = False
|
|
151
|
+
plt.rcParams['axes.linewidth'] = axes_linewidth
|
|
152
|
+
plt.rcParams['legend.frameon'] = False
|
|
153
|
+
if use_tex:
|
|
154
|
+
plt.rcParams['text.usetex'] = True
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Presets:**
|
|
158
|
+
- Large bar panels: `apply_publication_style(font_size=24, axes_linewidth=3)`
|
|
159
|
+
- Compact figures: `apply_publication_style(font_size=15, axes_linewidth=2)`
|
|
160
|
+
- Dense journal-width multi-panels: `apply_publication_style(font_size=8, axes_linewidth=1)`
|
|
161
|
+
- LaTeX labels: `apply_publication_style(use_tex=True)`
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## is_dark(hex_color, threshold=128)
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
def is_dark(hex_color, threshold=128):
|
|
169
|
+
"""Return True if hex color is dark (use white text on it)."""
|
|
170
|
+
c = hex_color.lstrip('#')
|
|
171
|
+
r, g, b = int(c[0:2], 16), int(c[2:4], 16), int(c[4:6], 16)
|
|
172
|
+
return (0.299*r + 0.587*g + 0.114*b) < threshold
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## add_panel_label(ax, label, ...)
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
def add_panel_label(ax, label, x=-0.06, y=1.02, fontsize=14,
|
|
181
|
+
color='black', fontweight='bold'):
|
|
182
|
+
"""Place a Nature-style panel label near the top-left edge."""
|
|
183
|
+
ax.text(
|
|
184
|
+
x, y, label,
|
|
185
|
+
transform=ax.transAxes,
|
|
186
|
+
fontsize=fontsize,
|
|
187
|
+
fontweight=fontweight,
|
|
188
|
+
color=color,
|
|
189
|
+
ha='left',
|
|
190
|
+
va='bottom',
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
For dark image plates, move the label inside the panel and switch to white:
|
|
195
|
+
`add_panel_label(ax, 'a', x=0.01, y=0.98, color='white')`
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## style_dark_image_ax(ax, ...)
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
def style_dark_image_ax(ax, facecolor='black'):
|
|
203
|
+
"""Prepare an axes for microscopy / rendering plates."""
|
|
204
|
+
ax.set_facecolor(facecolor)
|
|
205
|
+
ax.set_xticks([])
|
|
206
|
+
ax.set_yticks([])
|
|
207
|
+
for spine in ax.spines.values():
|
|
208
|
+
spine.set_visible(False)
|
|
209
|
+
return ax
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## make_grouped_bar(ax, categories, series, labels, ...)
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
def make_grouped_bar(ax, categories, series, labels,
|
|
218
|
+
ylabel='Value', colors=None,
|
|
219
|
+
annotate=False, bar_width=0.8,
|
|
220
|
+
error_kw=None):
|
|
221
|
+
"""
|
|
222
|
+
Grouped bar chart.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
ax : matplotlib Axes
|
|
227
|
+
categories : list[str] — x-axis category names (length K)
|
|
228
|
+
series : list[array] — one array per group (each length K)
|
|
229
|
+
labels : list[str] — legend label per group
|
|
230
|
+
ylabel : str
|
|
231
|
+
colors : list[str] | None — defaults to DEFAULT_COLORS; override with
|
|
232
|
+
DEFAULT_COLORS_NMI_PASTEL for unified-family figures
|
|
233
|
+
annotate : bool — print value above each bar
|
|
234
|
+
bar_width : float — total width for all bars in one category
|
|
235
|
+
error_kw : dict — passed to ax.bar as error_kw
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
list[BarContainer]
|
|
240
|
+
"""
|
|
241
|
+
import numpy as np
|
|
242
|
+
if colors is None:
|
|
243
|
+
colors = DEFAULT_COLORS
|
|
244
|
+
if error_kw is None:
|
|
245
|
+
error_kw = {'elinewidth': 2, 'capthick': 2, 'capsize': 10}
|
|
246
|
+
n_groups = len(series)
|
|
247
|
+
n_cats = len(categories)
|
|
248
|
+
w = bar_width / n_groups
|
|
249
|
+
x = np.arange(n_cats)
|
|
250
|
+
containers = []
|
|
251
|
+
for i, (vals, label, color) in enumerate(zip(series, labels, colors)):
|
|
252
|
+
offset = (i - (n_groups - 1) / 2) * w
|
|
253
|
+
bars = ax.bar(x + offset, vals, width=w, label=label,
|
|
254
|
+
color=color, edgecolor='black', linewidth=1.5,
|
|
255
|
+
error_kw=error_kw)
|
|
256
|
+
containers.append(bars)
|
|
257
|
+
if annotate:
|
|
258
|
+
for bar, val in zip(bars, vals):
|
|
259
|
+
ax.text(bar.get_x() + bar.get_width() / 2,
|
|
260
|
+
bar.get_height() + 0.01,
|
|
261
|
+
f'{val:.2f}', ha='center', va='bottom', fontsize=10)
|
|
262
|
+
ax.set_xticks(x)
|
|
263
|
+
ax.set_xticklabels(categories)
|
|
264
|
+
ax.set_ylabel(ylabel)
|
|
265
|
+
ax.legend()
|
|
266
|
+
return containers
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## make_trend(ax, x, y_series, labels, ...)
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
def make_trend(ax, x, y_series, labels,
|
|
275
|
+
colors=None, ylabel=None, xlabel=None,
|
|
276
|
+
show_shadow=False, shadow_alpha=0.15,
|
|
277
|
+
lw=2.5, marker='o', markersize=8):
|
|
278
|
+
"""
|
|
279
|
+
Multi-line trend plot.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
x : array-like — shared x values
|
|
284
|
+
y_series : list[array] — one 1D array per line
|
|
285
|
+
labels : list[str]
|
|
286
|
+
show_shadow : bool — fill_between ± std if y_series contains 2D arrays (rows=runs)
|
|
287
|
+
"""
|
|
288
|
+
import numpy as np
|
|
289
|
+
if colors is None:
|
|
290
|
+
colors = DEFAULT_COLORS
|
|
291
|
+
for y, label, color in zip(y_series, labels, colors):
|
|
292
|
+
y = np.asarray(y)
|
|
293
|
+
if y.ndim == 2:
|
|
294
|
+
mean, std = y.mean(0), y.std(0)
|
|
295
|
+
else:
|
|
296
|
+
mean, std = y, None
|
|
297
|
+
ax.plot(x, mean, color=color, lw=lw, marker=marker,
|
|
298
|
+
markersize=markersize, label=label)
|
|
299
|
+
if show_shadow and std is not None:
|
|
300
|
+
ax.fill_between(x, mean - std, mean + std,
|
|
301
|
+
color=color, alpha=shadow_alpha)
|
|
302
|
+
if ylabel:
|
|
303
|
+
ax.set_ylabel(ylabel)
|
|
304
|
+
if xlabel:
|
|
305
|
+
ax.set_xlabel(xlabel)
|
|
306
|
+
ax.legend()
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## make_forest_plot(ax, labels, estimates, ci_low, ci_high, ...)
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
def make_forest_plot(ax, labels, estimates, ci_low, ci_high,
|
|
315
|
+
colors=None, ref=0.0, xlabel=None, xlim=None,
|
|
316
|
+
marker='o', markersize=5, lw=1.5):
|
|
317
|
+
"""
|
|
318
|
+
Minimal forest plot helper for Nature-style clinical/statistical panels.
|
|
319
|
+
"""
|
|
320
|
+
import numpy as np
|
|
321
|
+
y = np.arange(len(labels))[::-1]
|
|
322
|
+
if colors is None:
|
|
323
|
+
colors = ['#B64342'] * len(labels)
|
|
324
|
+
for yi, est, lo, hi, color in zip(y, estimates, ci_low, ci_high, colors):
|
|
325
|
+
ax.plot([lo, hi], [yi, yi], color=color, lw=lw)
|
|
326
|
+
ax.plot(est, yi, marker=marker, ms=markersize, color=color)
|
|
327
|
+
ax.axvline(ref, color='#767676', linestyle='--', linewidth=1.2, alpha=0.8)
|
|
328
|
+
ax.set_yticks(y)
|
|
329
|
+
ax.set_yticklabels(labels)
|
|
330
|
+
if xlabel:
|
|
331
|
+
ax.set_xlabel(xlabel)
|
|
332
|
+
if xlim is not None:
|
|
333
|
+
ax.set_xlim(xlim)
|
|
334
|
+
ax.spines['right'].set_visible(False)
|
|
335
|
+
ax.spines['top'].set_visible(False)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Use pale `ax.axhspan(...)` bands behind contiguous label groups when you need the
|
|
339
|
+
clinical-triptych look from `Nature`.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## make_heatmap(ax, matrix, ...)
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
def make_heatmap(ax, matrix, x_labels=None, y_labels=None,
|
|
347
|
+
cmap='magma', cbar_label=None, annotate=False,
|
|
348
|
+
fmt='{:.2f}', fontsize=12):
|
|
349
|
+
"""
|
|
350
|
+
2D heatmap with optional colorbar and cell annotations.
|
|
351
|
+
"""
|
|
352
|
+
import numpy as np
|
|
353
|
+
import matplotlib as mpl
|
|
354
|
+
im = ax.imshow(matrix, cmap=cmap, aspect='auto')
|
|
355
|
+
if cbar_label:
|
|
356
|
+
cbar = ax.figure.colorbar(im, ax=ax)
|
|
357
|
+
cbar.set_label(cbar_label)
|
|
358
|
+
if x_labels:
|
|
359
|
+
ax.set_xticks(range(len(x_labels)))
|
|
360
|
+
ax.set_xticklabels(x_labels, rotation=30, ha='right')
|
|
361
|
+
if y_labels:
|
|
362
|
+
ax.set_yticks(range(len(y_labels)))
|
|
363
|
+
ax.set_yticklabels(y_labels)
|
|
364
|
+
if annotate:
|
|
365
|
+
norm = mpl.colors.Normalize(vmin=matrix.min(), vmax=matrix.max())
|
|
366
|
+
cm_obj = plt.get_cmap(cmap)
|
|
367
|
+
for (i, j), val in np.ndenumerate(matrix):
|
|
368
|
+
r, g, b, _ = cm_obj(norm(val))
|
|
369
|
+
lum = 0.299*r + 0.587*g + 0.114*b
|
|
370
|
+
color = 'white' if lum < 0.5 else 'black'
|
|
371
|
+
ax.text(j, i, fmt.format(val), ha='center', va='center',
|
|
372
|
+
fontsize=fontsize, color=color)
|
|
373
|
+
ax.set_frame_on(False)
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## finalize_figure(fig, out_path, ...)
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
def finalize_figure(fig, out_path, formats=None, dpi=300,
|
|
382
|
+
pad=2, bbox_inches=None, close=True):
|
|
383
|
+
"""
|
|
384
|
+
Apply tight_layout and save figure.
|
|
385
|
+
|
|
386
|
+
Parameters
|
|
387
|
+
----------
|
|
388
|
+
out_path : str — path without extension, or with extension
|
|
389
|
+
formats : list — e.g. ['png', 'pdf']. If None, uses extension of out_path.
|
|
390
|
+
dpi : int — 300 standard, 600 for dense bar panels
|
|
391
|
+
pad : float — tight_layout pad (2 default, 1 for compact multi-panel)
|
|
392
|
+
"""
|
|
393
|
+
import os
|
|
394
|
+
from pathlib import Path
|
|
395
|
+
fig.tight_layout(pad=pad)
|
|
396
|
+
base = Path(out_path)
|
|
397
|
+
os.makedirs(base.parent, exist_ok=True)
|
|
398
|
+
if formats is None:
|
|
399
|
+
formats = [base.suffix.lstrip('.') or 'png']
|
|
400
|
+
base = base.with_suffix('')
|
|
401
|
+
saved = []
|
|
402
|
+
for fmt in formats:
|
|
403
|
+
p = str(base) + f'.{fmt}'
|
|
404
|
+
kw = {}
|
|
405
|
+
if bbox_inches is not None:
|
|
406
|
+
kw['bbox_inches'] = bbox_inches
|
|
407
|
+
fig.savefig(p, dpi=dpi, **kw)
|
|
408
|
+
saved.append(p)
|
|
409
|
+
if close:
|
|
410
|
+
plt.close(fig)
|
|
411
|
+
return saved
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Validation Rules
|
|
417
|
+
|
|
418
|
+
- `make_grouped_bar`: `len(categories)` must equal length of each array in `series`.
|
|
419
|
+
- `make_trend`: each array in `y_series` must have same length as `x`.
|
|
420
|
+
- `make_heatmap`: `matrix` must be 2D; `x_labels` length = `matrix.shape[1]`; `y_labels` length = `matrix.shape[0]`.
|
|
421
|
+
- `finalize_figure`: supported formats — `png`, `pdf`, `svg`, `eps`, `jpg`, `tif`.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Conventions
|
|
426
|
+
|
|
427
|
+
- Save outputs under `./figures/` (or path given by user); `finalize_figure` creates parent dirs.
|
|
428
|
+
- In headless / batch runs, set non-interactive backend before importing pyplot:
|
|
429
|
+
```python
|
|
430
|
+
import matplotlib
|
|
431
|
+
matplotlib.use('Agg')
|
|
432
|
+
import matplotlib.pyplot as plt
|
|
433
|
+
```
|
|
434
|
+
- Always `plt.close(fig)` after saving to free memory.
|
|
435
|
+
- For multi-panel figures, prefer one baseline family plus one hero family; reserve green/red for delta cues.
|
|
436
|
+
- When color roles, resolution, or layout are underspecified and would change the figure, confirm with user before finalizing.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Backend Selection
|
|
2
|
+
|
|
3
|
+
At the start of a figure task, ask the user to choose **Python or R** if they have
|
|
4
|
+
not already specified a backend. This is a blocking gate: stop after asking and wait
|
|
5
|
+
for the user's answer. Do not infer Python just because the task involves simulation,
|
|
6
|
+
NumPy-like data, or custom layout, and do not infer R just because the task is biological
|
|
7
|
+
or omics-adjacent.
|
|
8
|
+
|
|
9
|
+
Use the decision table only in either of these cases:
|
|
10
|
+
|
|
11
|
+
- the user explicitly asks you to recommend or choose the backend;
|
|
12
|
+
- the user provides an unambiguous language-specific workflow or file, such as an `.R`
|
|
13
|
+
script, RDS object, Python notebook, or existing Python plotting code.
|
|
14
|
+
|
|
15
|
+
## Quick decision table
|
|
16
|
+
|
|
17
|
+
| Recommend R when | Recommend Python when |
|
|
18
|
+
|---|---|
|
|
19
|
+
| The user brings R scripts, RData/RDS, Seurat objects, DESeq2/limma outputs, survival models, or ggplot templates | The data pipeline is already Python, NumPy/Pandas arrays, PyTorch/TensorFlow outputs, image arrays, or simulation output |
|
|
20
|
+
| The target plot is `ggplot2`, `patchwork`, `ComplexHeatmap`, `ggtree`, `circlize`, `survminer`, `maftools`, or Seurat/UMAP-heavy | The target plot needs low-level custom layout, Matplotlib patches, image plates, subplot mosaics, or custom drawing primitives |
|
|
21
|
+
| The user provides an R template collection or an existing R plotting workflow | The user wants a self-contained script with matplotlib/seaborn/statsmodels and no R dependency |
|
|
22
|
+
| Heatmap annotations are biologically rich and multi-layered | Image panels and quantitative panels need tight pixel/axis control |
|
|
23
|
+
|
|
24
|
+
If either backend can do the job, honor the user's preference. Do not switch
|
|
25
|
+
backends for aesthetics alone.
|
|
26
|
+
|
|
27
|
+
## Backend exclusivity rule
|
|
28
|
+
|
|
29
|
+
Backend choice is not just a syntax preference; it defines the graphics engine for
|
|
30
|
+
the entire deliverable. Once Python or R has been selected, use that backend for
|
|
31
|
+
all of the following:
|
|
32
|
+
|
|
33
|
+
- plotting scripts;
|
|
34
|
+
- mock/simulated data examples that include plotting;
|
|
35
|
+
- preview PNG/TIFF files;
|
|
36
|
+
- SVG/PDF/TIFF exports;
|
|
37
|
+
- visual QA renders and final layout checks.
|
|
38
|
+
|
|
39
|
+
Do not generate a substitute preview or export with the non-selected backend. For
|
|
40
|
+
example, if the user selected R and `Rscript` is missing, do not use Python/matplotlib
|
|
41
|
+
to approximate the figure. If the user selected Python and `matplotlib` or another
|
|
42
|
+
required Python plotting package is missing, do not use R/ggplot2/ComplexHeatmap to
|
|
43
|
+
approximate the figure. Stop, report the selected-backend blocker, and provide the
|
|
44
|
+
selected-backend script plus install/run instructions or request permission to install
|
|
45
|
+
the selected-backend dependencies.
|
|
46
|
+
|
|
47
|
+
The non-selected language is allowed only for non-visual utility work, such as
|
|
48
|
+
listing files, checking CSV dimensions, decompressing an archive, or converting a
|
|
49
|
+
data file before the selected backend draws the figure. It must not import plotting
|
|
50
|
+
libraries, open graphics devices, save image/vector files, or decide visual layout.
|
|
51
|
+
|
|
52
|
+
## Default stacks
|
|
53
|
+
|
|
54
|
+
### R
|
|
55
|
+
|
|
56
|
+
- Core plotting: `ggplot2`
|
|
57
|
+
- Multi-panel assembly: `patchwork`
|
|
58
|
+
- Heatmaps: `ComplexHeatmap`, `circlize`
|
|
59
|
+
- Direct labels: `ggrepel`
|
|
60
|
+
- Survival/clinical: `survival`, `survminer`, `forestplot`, `ggplot2`
|
|
61
|
+
- Single-cell/omics: `Seurat`, `SingleCellExperiment`, `ComplexHeatmap`, `ggtree`
|
|
62
|
+
- Export: `svglite`, `grDevices::cairo_pdf`, `ragg`
|
|
63
|
+
|
|
64
|
+
### Python
|
|
65
|
+
|
|
66
|
+
- Core plotting: `matplotlib`
|
|
67
|
+
- Statistical plots: `seaborn`
|
|
68
|
+
- Layout: `subplot_mosaic`, `GridSpec`
|
|
69
|
+
- Tables/model output: `pandas`, `numpy`, `statsmodels`
|
|
70
|
+
- Images: `matplotlib.imshow`, `skimage`, `tifffile` when needed
|
|
71
|
+
- Export: `fig.savefig(... .svg/.pdf/.tiff)`, `svg.fonttype='none'`,
|
|
72
|
+
`pdf.fonttype=42`
|
|
73
|
+
|
|
74
|
+
## Mixed workflow rule
|
|
75
|
+
|
|
76
|
+
Use the selected plotting backend for final assembly and all visual output. A mixed
|
|
77
|
+
workflow is reasonable only when the non-selected language performs non-visual data
|
|
78
|
+
preparation and the selected backend assembles the figure. In that case:
|
|
79
|
+
|
|
80
|
+
1. Export clean source data as CSV/TSV with stable column names.
|
|
81
|
+
2. Assemble the final figure in the selected backend.
|
|
82
|
+
3. Keep the source-data file next to the plotting script.
|
|
83
|
+
4. Do not stitch, preview, QA-render, or export final image/vector outputs from the
|
|
84
|
+
non-selected backend unless the user explicitly changes the selected backend.
|
|
85
|
+
|
|
86
|
+
## Recommendation language
|
|
87
|
+
|
|
88
|
+
Use direct language:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
For this figure I recommend R because the main burden is ComplexHeatmap-style
|
|
92
|
+
omics annotation and patchwork assembly. I will still keep the export contract
|
|
93
|
+
SVG/PDF/TIFF with editable text.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
For this figure I recommend Python because the key panel is a custom image plate
|
|
98
|
+
with quantitative overlays and a subplot_mosaic layout. Matplotlib gives tighter
|
|
99
|
+
control over the raster and vector layers.
|
|
100
|
+
```
|