Distribution Analysis
This page documents how to use scripts/notebooks/distribution_analysis.ipynb to visualise and compare model prediction outputs across splits and models.
Summary
The distribution analysis notebook loads per-video prediction JSONs and evaluation reports produced by the MooVision pipeline, and generates a series of diagnostic charts for comparing YOLO and Seq-NMS model behaviour across three experimental splits (POSTWEAN, WEAN, Random).
The notebook produces nine figures grouped by analysis type:
| Figure | Type | Description |
|---|---|---|
| Fig 1 | Histogram | Confidence score distribution per model and split |
| Fig 2 | Bar chart | Predicted event count per video |
| Fig 3 | Histogram | Event duration distribution per model and split |
| Fig 4 | Scatter | Confidence vs event duration |
| Fig 5 | Dot plot | Temporal density of events within videos — POSTWEAN |
| Fig 6 | Dot plot | Temporal density of events within videos — WEAN |
| Fig 7 | Dot plot | Temporal density of events within videos — Random |
| Fig 8 | Heatmap | Event-level confusion matrix by model and split |
All charts render inline — nothing is saved to disk.
Inputs (Data requirements)
The notebook requires two sets of outputs produced by the MooVision training and evaluation pipeline.
Prediction JSONs
Per-video prediction JSON files output by baseline.py or clipping.py. These must be organised into the following directory structure under results/metadata/:
results/
└── metadata/
├── period_based/
│ ├── POSTWEAN/
│ │ ├── yolo/
│ │ │ └── *.json
│ │ └── seq-nms/
│ │ └── *.json
│ └── WEAN/
│ ├── yolo/
│ │ └── *.json
│ └── seq-nms/
│ └── *.json
└── random/
├── yolo/
│ └── *.json
└── seq-nms/
└── *.json
Each prediction JSON must conform to the MooVision output schema and contain the following fields:
{
"identifier": "video_name.mp4",
"video_path": "/path/to/video",
"total_duration_sec": 120.0,
"cross_sucking_detected": true,
"num_events": 3,
"events": [
{
"start_sec": 10.5,
"end_sec": 18.2,
"duration_sec": 7.7,
"avg_confidence": 0.43
}
]
}
Evaluation Reports
Per-split evaluation JSON files output by the evaluation pipeline. These must be located in results/evaluation/ and follow the naming convention:
evaluation_report_{split}_{model}.json
For example:
results/
└── evaluation/
├── evaluation_report_period_based_postwean_yolo.json
├── evaluation_report_period_based_postwean_seq_nms.json
├── evaluation_report_period_based_wean_yolo.json
├── evaluation_report_period_based_wean_seq_nms.json
├── evaluation_report_random_yolo.json
└── evaluation_report_random_seq_nms.json
Each evaluation JSON must contain the following structure:
{
"event_level": {
"true_positives": 9,
"false_positives": 193,
"false_negatives": 44,
"precision": 0.044,
"recall": 0.17,
"f2": 0.11
},
"sequence_level": {
"avg_temporal_iou": 0.68
},
"frame_level": {
"avg_bbox_iou": 0.52
}
}
Usage
Basic Usage
Open and run the notebook from the project root:
uv run jupyter notebook scripts/notebooks/distribution_analysis.ipynb
Or with JupyterLab:
uv run jupyter lab scripts/notebooks/distribution_analysis.ipynb
Run all cells in order using Kernel → Restart & Run All. Charts will render inline after each figure cell.
Vegafusion Note
The notebook uses vegafusion as the default Altair data transformer for handling large prediction dataframes (Figures 1–7). Figures 8 and 9 switch to the default transformer automatically since they use pre-aggregated data. Do not manually re-enable vegafusion after the Figure 8 cell or the charts will fail to render.
Adding a New Split
To add a new split, define its prediction JSON paths and add an entry to the SPLITS dictionary at the top of the notebook:
SPLITS = {
"POSTWEAN": {
"YOLO": YOLO_MODEL_OUTPUT_DIR_PERIOD_POST,
"Seq-NMS": SEQ_MODEL_OUTPUT_DIR_PERIOD_POST,
},
"MY_NEW_SPLIT": { # add your split here
"YOLO": Path("results/metadata/my_split/yolo"),
"Seq-NMS": Path("results/metadata/my_split/seq-nms"),
},
}
Also update SPLIT_ORDER and MODEL_ORDER to include the new split label so facet ordering is preserved.
Adding a New Model
To add a new model alongside YOLO and Seq-NMS, add its path to each relevant split entry in SPLITS and update MODEL_ORDER:
MODEL_ORDER = [f"{m} — {s}" for s in SPLIT_ORDER for m in ["YOLO", "Seq-NMS", "MY_MODEL"]]
Outputs
The notebook does not write any files to disk. All figures render inline in the notebook. To export a figure for use in the report:
fig5.save("reports/figures/fig5_temporal_density_postwean.html") # interactive
fig9.save("reports/figures/fig9_confusion_matrix.png", scale_factor=2.0) # static
Note: PNG export requires the vl-convert-python package:
uv add vl-convert-python
Figure Reference
Figure 1 — Confidence Score Distribution
Six-panel histogram of avg_confidence per predicted event, arranged as YOLO (top row) vs Seq-NMS (bottom row) across POSTWEAN, WEAN, and Random splits. Seq-NMS is expected to show lower and more spread confidence than YOLO due to the sequence linking step chaining lower-confidence frame detections.
Figure 2 — Predicted Events per Video
Bar chart of predicted event count per video, sorted descending, coloured by pen. Uniform tall bars across all videos indicate the model is over-detecting rather than concentrating detections on genuine cross-sucking videos.
Figure 3 — Event Duration Distribution
Histogram of event duration in seconds. YOLO tends to produce a small number of very long events; Seq-NMS fragments these into many shorter events. A spike at short durations alongside a long tail suggests the short cluster is mostly false positives.
Figure 4 — Confidence vs Event Duration
Scatter plot of avg_confidence against duration_sec, coloured by pen. Events in the short + low-confidence region are the strongest false positive candidates. Events in the long + high-confidence region are the strongest true positive candidates for manual review.
Figures 5–7 — Temporal Density of Events Within Videos
Dot plots showing where within each video events fire. Dot size encodes duration, dot colour encodes confidence. Genuine cross-sucking bouts are expected to appear as clusters of large, high-confidence dots within a narrow time window. Sparse dots scattered uniformly across the full timeline suggest the model is not detecting real behavioural bouts.
Figure 8 — Event-Level Confusion Matrix
Faceted heatmap showing TP, FP, and FN counts for each model and split. True negatives are excluded as they are not well-defined at the event level for continuous video. The top-right cell (Actual CS, Predicted No CS) shows the false negative count and is typically the dominant cell, reflecting the near-zero recall observed across all splits.