Mate Allocation — Help

← back to the app

How to prepare your data, run an allocation, and use the results.

1 · What the app does

It assigns male and female broodstock to mating tanks so that, within each tank, the males and females are as unrelated as possible. That keeps inbreeding out of the next generation. Animals that don't fit are put in tank 0, which simply means "not used this time".

Relatedness comes from a genomic relationship matrix (GRM) built from SNP genotypes. The workflow is two steps:

  genotypes.csv + sex.csv
            │
            ▼   prepare_grm.R   (R, on your computer)
     grm_labelled.csv
            │
            ▼   this web app    (in your browser)
   allocation CSV · closest-pairs CSV · PDF report

Privacy: the R script runs locally and the web app runs entirely inside your browser. No genotype or kinship data is ever uploaded anywhere, and the app works with the network disconnected.

2 · Step 1 — prepare your data in R

You need R with the rrBLUP package. Install it once:

install.packages("rrBLUP")

Input file 1 — genotypes.csv

One row per animal, one column per SNP. The first column is the animal ID; the rest are genotypes coded 0, 1 or 2 (count of one allele). Missing values may be blank, NA, or any number outside 0–2.

id,snp001,snp002,snp003,...
BR0001,0,1,2,...
BR0002,1,1,0,...
BR0003,2,,1,...      <- blank = missing

Input file 2 — sex.csv

Two columns named id and sex. Accepts M/F, Male/Female, or 1/2 (1 = male, 2 = female). Row order does not matter — animals are matched by ID.

id,sex
BR0001,F
BR0002,Male
BR0003,1

Running it

Open prepare_grm.R, edit the SETTINGS block at the top so the file names and output path match yours, then run:

Rscript prepare_grm.R

It prints a short report of what it did, for example:

read 240 animals x 150 SNPs from genotypes.csv
QC: dropped 1 animal(s) below call rate 0.50, 2 without sex; 237 remain
QC: dropped 3 SNP(s) below call rate 0.50; 147 remain
GRM: 237 x 237   diagonal mean 0.991   off-diagonal range -0.42 .. 0.77
wrote grm_labelled.csv

What the quality control does

Only two filters are applied, both adjustable at the top of the script:

Animals without a usable sex are dropped too, because the app needs both sexes. There is deliberately no minor-allele-frequency filter: rrBLUP::A.mat produces an identical matrix with or without monomorphic markers, so it would change nothing.

Sanity checks worth doing

Already have a relationship matrix?

If you already have a GRM (or a pedigree-based A matrix) you can skip R entirely — just format it as the labelled CSV described in the next section. The comment block at the bottom of prepare_grm.R also shows how to feed in genotypes you already hold in R (for example from an .Rdata file).

3 · Step 2 — load it into the app

The kinship file (required)

This is what prepare_grm.R writes. Header row is id,sex, followed by every animal ID; each data row is the animal's ID, its sex, then its relatedness to every animal in the same order as the header.

id,sex,BR0001,BR0002,BR0003
BR0001,F,0.9951,0.4327,-0.0212
BR0002,M,0.4327,1.0043,0.0155
BR0003,M,-0.0212,0.0155,0.9977

The app checks the file as it loads and refuses anything ambiguous — mismatched row/column order, an unrecognised sex, or a non-numeric cell (which would otherwise be silently treated as "completely unrelated" and produce a wrong allocation).

The constraints file (optional)

Only needed in Advanced mode. It is a separate file so you can change your mind without re-running R. List only the animals you care about; unlisted animals are unconstrained. Only id is required.

id,fix_tank,exclude,family
BR0007,2,,F12     <- must go in tank 2; family F12
BR0019,,1,F12     <- must NOT be used (tank 0)
BR0044,,,F12      <- free, but kept apart from other F12 animals

Constraints are never allowed to block a run. If they can't all be met — a family with more members than you have tanks, or more animals pinned to a tank than it holds — the app warns you first, then produces the best allocation it can and tells you exactly what it could not satisfy.

4 · Settings and what they mean

Simple or Advanced

Simple (the default) gives every tank the same capacity — set the number of tanks and how many males and females each holds. Advanced lets each tank have its own male and female counts, and adds the constraints file. Switching to Advanced starts from your Simple numbers, so nothing is lost; switching back warns before discarding per-tank differences. Both modes use the same optimiser.

SettingWhat it doesGuidance
TanksHow many mating tanks to fill.Your actual tank count.
Males / females per tankCapacity of each tank. Anything not placed goes to tank 0. Asking for more than you have is allowed — you'll be warned and tanks are under-filled.
Within-sex threshold Same-sex pairs below this are ignored by the optimiser, so it concentrates on male–female relatedness. 1 ignores all same-sex relatedness (matches the historical pipeline). 0.5 also keeps close same-sex kin apart. This is a breeding decision.
RestartsHow many independent attempts; the best is kept. This is the knob that matters. 20 is a sensible default; raising it is cheap.
IterationsSwap steps within each attempt. Leave at 1200. The search settles after a couple of hundred steps, so more iterations achieve nothing — see the note below.
SeedMakes the run repeatable. The same seed and settings always reproduce the same allocation. Change it to explore alternatives.

Why restarts rather than iterations? The search is a downhill one: it stops when no single swap improves things. Testing on this data showed a run settles after roughly 100–200 iterations and then never improves, so extra iterations are wasted effort. Independent restarts explore genuinely different starting points. At an equal compute budget, 20 restarts × 1000 iterations beat 1 restart × 20000.

5 · Reading the results

The headline number

Max opposite-sex kinship (any tank) is what the optimiser minimises: the single most-related male–female pair anywhere in your tanks. It is the fairest way to compare two runs. Lower is better; compare runs on this number, not on which animal ended up in which tank (the search is random, so membership shifts between seeds even when quality is the same).

There is no universal "good" value — it depends on how related your broodstock is. Compare against your own baseline: run once, note the number, and see whether more restarts or a different seed beats it.

Closest pairs to check

The most practical table on the page. It ranks the male–female pairs that ended up sharing a tank, most related first. Everything not on this list is less related than the entries shown, so if the top few are acceptable to you, the whole allocation is. Values at or above 0.25 (roughly a half-sib) are highlighted, and pairs sharing a family label are flagged.

Kinship after allocation

A heat map of every animal against every other, ordered by tank, with black lines at the tank boundaries. Squares on the diagonal are tanks. What you want is for the male–female blocks inside each tank to look "cool" (unrelated). Use the colour selector if you prefer a colour-blind-friendly or greyscale palette; the legend shows the scale, which is shared by every heat map so colours mean the same thing everywhere.

Within a single tank

One tank at a time with the animal IDs shown, males then females, split by a black line. The two blocks on the diagonal are male–male and female–female (expected to contain relatives — that's fine). The off-diagonal rectangle is the male–female block: that's the one that should be cool.

Kinship distributions

Three histograms on a shared scale:

Constraint report

Shown when a constraints file was used, e.g. "3/3 pinned animals in their tank · 2/2 excluded kept out · 40 family conflicts unavoidable — tank 1: BR0001 + BR0002". Unavoidable conflicts are named so you can decide whether to accept them, add a tank, or relax a constraint.

6 · The files you can download, and what to use them for

FileUse it for
Allocation CSV
MateAllocated_…csv
The working document. Animal → tank number, so staff know which animal goes where. Tank 0 = not used. Includes the constraint columns when you supplied a constraints file.
Closest pairs CSV
ClosestPairs_…csv
The review list — hand it to whoever signs off the crossing plan.
Sorted kinship matrix CSV
GRM_…sorted_withID_…csv
The full relatedness matrix reordered by tank, for your own analysis or to archive alongside the decision.
PDF report
MateAllocation_report_…pdf
The record. A summary sheet (settings, per-tank allocation, constraint outcome, conflicting pairs) followed by all the figures. Attach this to the breeding file.

Reproducibility

Every file name carries the seed and date, and both CSVs begin with # comment lines recording exactly what produced them — input files, tank layout, threshold, restarts, iterations, seed, and the result achieved. Re-running with the same settings and seed reproduces the same allocation.

Read one back in R without the comments getting in the way:

alloc <- read.csv("MateAllocated_....csv", comment.char = "#")
prov  <- grep("^#", readLines("MateAllocated_....csv"), value = TRUE)   # the settings

Excel shows the comment lines as ordinary text rows above the table.

7 · Troubleshooting

"Why are so many animals in tank 0?"
Tank 0 holds everyone who didn't fit. If you have 240 animals and ask for 5 tanks of 16+16, only 160 are placed — the other 80 land in tank 0. Increase the tanks or the per-tank numbers to use more animals.

"I ran it twice and got different tanks."
Expected — the search is random. Fix the seed to repeat a run exactly, and compare runs by the reported max kinship rather than by tank membership.

"My constraints weren't all satisfied."
Some sets are impossible: a family of 10 cannot be spread across 5 tanks without two members sharing one. The app tells you up front, and the result names the conflicts. Add tanks, split the family label, or accept the listed conflicts.

"The file won't load."
The message names the problem. Common causes: the header doesn't start with id,sex,; the number of ID columns doesn't match the number of data rows; a row's ID doesn't match the header in that position; or a cell is blank or non-numeric. Re-export from prepare_grm.R to be sure of the format.

"The Cancel button is greyed out."
You opened the page directly from disk (file://), where the calculation runs on the main thread and can't be interrupted. Serve the folder over HTTP instead and Cancel works immediately:

cd webapp
python3 -m http.server 8777
# then open http://localhost:8777

"Nothing happens / the page looks stale after an update."
Your browser cached the old files. Do a hard reload (Shift + reload).

"Is my data sent anywhere?"
No. Everything is computed in your browser; the page makes no network requests and works offline. Nothing leaves your machine unless you download a file yourself.