🐩 Poodle Palette — Genotype → Phenotype Tutor¶
Welcome! This notebook lets you toggle five coat-colour loci and instantly see the phenotype (or carrier notes) for any Poodle genotype:
- B — black vs. brown pigment
- E — pigment switch (ee = red/cream)
- K — dominant black / brindle / pattern-free
- A — sable, phantom, agouti, recessive black (shown when K = kyky)
- S — solid vs. parti spotting
Click around in the widget below, or feed the function a full genotype string to watch the colours—and the carrier caveats—unfold. Enjoy the genetics journey!
In [ ]:
# 📦 Setup
# If running locally, make sure pandas and matplotlib are installed.
# In Google Colab these come pre‑installed.
import pandas as pd
import matplotlib.pyplot as plt
# Display plots inline
%matplotlib inline
🎓 2. Genetic Background — quick‐reference table¶
Locus | Key alleles* | Dominance order → | What the locus controls | Notes for Poodles |
---|---|---|---|---|
B (TYRP1) | B = black • b = brown | BB ≈ Bb ≫ bb | Eumelanin pigment (black vs. brown) | bb turns nose/lips brown and lightens any black areas (inc. phantom points). |
E (MC1R) | E = allows pigment • e = recessive red | EE ≈ Ee ≫ ee | Switch that blocks/permits black/brown coat colour | ee hides all K/A patterns; dog may carry them. |
K (CBD103) | KB = dominant black • Kbr = brindle • ky = pattern-free | KB ≫ Kbr ≫ ky | Overrides or reveals A-locus patterns | Kbr shows brindle unless coat is ee or a/a. |
A (ASIP) | ay = sable • at = phantom • aw = agouti • a = recessive black | ay ≫ at ≫ aw ≫ a | Determines banding/point patterns only when K = ky/ky or Kbr (non-a/a) | In ee kyky dogs, top A allele is “carried” rather than shown. |
S (MITF) | S = solid • sp = parti | SS ≫ Ssp ≫ spsp | White-spotting (parti) layer on top of base colour | Ssp dogs carry parti; spsp shows full parti pattern. |
* For simplicity we list the most common poodle alleles. Other rare modifiers (D-locus silvering, I-locus intensity, etc.) are beyond this demo.
Rule of thumb:
- E sets the stage (ee hides everything).
- K decides if patterns are blocked (KB), brindled (Kbr), or revealed (kyky).
- A supplies the actual pattern—if K lets it through.
- B colours the eumelanin (black vs. brown).
- S sprinkles white on top if homozygous sp.
Carriers (Kbr, Ssp, ay/at/aw in ee dogs) are noted in the phenotype read-out but not visually expressed.
In [ ]:
## 3. Imports + Helper Dictionaries
In [ ]:
# 📦 Standard data-science imports
import pandas as pd
import matplotlib.pyplot as plt
# 📚 Teaching widgets (for the interactive allele picker we’ll add later)
from IPython.display import HTML
import ipywidgets as widgets
# 🖼️ Show charts inside the notebook
%matplotlib inline
# ────────────────────────────────────────────────────────────────
# Helper dictionaries for quick look-ups at each locus
# These will be used by predict_colour() in the next section.
# B-locus (black vs. brown pigment)
B_LOCUS = {
"BB": "Black",
"Bb": "Black",
"bb": "Brown"
}
# E-locus (extension / red-cream override)
E_LOCUS = {
"EE": "Allows black/brown",
"Ee": "Allows black/brown",
"ee": "Red/Cream (blocks black)"}
# Spotting / Parti – dominant solid S, recessive parti sp
S_LOCUS = {
"SS": "solid",
"Ssp": "solid (carrier)",
"spsp": "parti"
}
# K-locus hierarchy KB > Kbr > ky
K_HIERARCHY = ["KB", "Kbr", "ky"] # first one present wins
# A-locus hierarchy ay > at > aw > a
A_HIERARCHY = ["ay", "at", "aw", "a"]
In [ ]:
## 4. Function `predict_colour()`
In [ ]:
# ── dominance tables ───────────────────────────────────────────────
K_HIERARCHY = ["Kbr", "KB"]
A_HIERARCHY = ["ay", "at", "aw", "a"]
A_LABELS = {"ay": "Sable", "at": "Phantom", "aw": "Agouti"}
def predict_colour(b_pair: str, e_pair: str,
k_pair: str,
a1: str, a2: str,
s_pair: str) -> str:
"""Return plain-English phenotype plus carrier notes."""
# --- 0. quick flags ---------------------------------------------------
recessive_red = (e_pair == "ee") # ee dog
carries_red = (e_pair in ("Ee", "eE")) # heterozygous
brown_base = (b_pair == "bb") # brown dog
carries_brown = (b_pair in ("Bb", "bB")) # heterozygous
# --- 1. base coat colour ---------------------------------------------
if recessive_red:
base = "Red/Cream" + (" (brown nose)" if brown_base else "")
else:
base = "Brown" if brown_base else "Black"
# --- 2. K-locus winner -----------------------------------------------
if "Kbr" in k_pair:
k_dom = "Kbr"
elif "KB" in k_pair:
k_dom = "KB"
else:
k_dom = "ky"
# --- 3. dominant A allele --------------------------------------------
dom_a = next(a for a in A_HIERARCHY if a in (a1 + a2))
# --- 4. pattern / carrier phrases ------------------------------------
pattern = ""
if recessive_red:
if k_dom == "Kbr":
pattern = "carries Brindle"
elif k_dom == "ky" and dom_a != "a":
pattern = f"carries {A_LABELS[dom_a]}"
else:
if k_dom == "KB":
pattern = ""
elif k_dom == "Kbr":
pattern = "" if dom_a == "a" else "Brindle"
else: # kyky
pattern = A_LABELS.get(dom_a, "")
# --- 5. spotting notes -----------------------------------------------
spot_prefix = "Parti" if s_pair.lower().endswith("spsp") else ""
spot_suffix = "carries Parti" if s_pair.lower() == "ssp" else ""
# --- 6. red & brown carrier notes ------------------------------------
red_suffix = "carries Red" if carries_red else ""
brown_suffix = "carries Brown" if carries_brown else ""
# --- 7. assemble ------------------------------------------------------
parts = [spot_prefix, base, pattern, spot_suffix, red_suffix, brown_suffix]
return " ".join(p for p in parts if p)
In [ ]:
## 5. Bulk Conversion of Your CSV
In [ ]:
## 6. Interactive Playground
In [ ]:
def show_interactive():
# --- B locus ----------------------------------------------------------
b_btn = widgets.ToggleButtons(
options=[("BB (black)", "BB"),
("Bb (black)", "Bb"),
("bb (brown)", "bb")],
description="B locus:",
)
# --- E locus ----------------------------------------------------------
e_btn = widgets.ToggleButtons(
options=[("EE (permits)", "EE"),
("Ee (permits)", "Ee"),
("ee (red/cream)", "ee")],
description="E locus:",
)
# --- K locus ----------------------------------------------------------
k_btn = widgets.ToggleButtons(
options=[("KB/KB", "KBKB"),
("Kbr/ky", "Kbrky"),
("ky/ky", "kyky")],
description="K locus:",
)
# --- A locus (two alleles) -------------------------------------------
a1_btn = widgets.ToggleButtons(
description="A-1:",
options=[("ay", "ay"), ("at", "at"), ("aw", "aw"), ("a", "a")],
)
a2_btn = widgets.ToggleButtons(
description="A-2:",
options=[("ay", "ay"), ("at", "at"), ("aw", "aw"), ("a", "a")],
)
# --- Spotting ---------------------------------------------------------
s_btn = widgets.ToggleButtons(
description="S locus:",
options=[("SS (solid)", "SS"),
("Ssp (carrier)", "Ssp"),
("sp/sp (parti)", "spsp")],
)
# --------- live output area ------------------------------------------
out = widgets.Output()
def update(_=None):
colour = predict_colour(
b_btn.value,
e_btn.value,
k_btn.value,
a1_btn.value,
a2_btn.value,
s_btn.value
)
with out:
out.clear_output()
display(HTML(f"<h3>{colour}</h3>"))
# fire once at start
update()
# listen for *any* change
for w in (b_btn, e_btn, k_btn, a1_btn, a2_btn, s_btn):
w.observe(update, names="value")
display(b_btn, e_btn, k_btn, a1_btn, a2_btn, s_btn, out)
# run it
show_interactive()
ToggleButtons(description='B locus:', options=(('BB (black)', 'BB'), ('Bb (black)', 'Bb'), ('bb (brown)', 'bb'…
ToggleButtons(description='E locus:', options=(('EE (permits)', 'EE'), ('Ee (permits)', 'Ee'), ('ee (red/cream…
ToggleButtons(description='K locus:', options=(('KB/KB', 'KBKB'), ('Kbr/ky', 'Kbrky'), ('ky/ky', 'kyky')), val…
ToggleButtons(description='A-1:', options=(('ay', 'ay'), ('at', 'at'), ('aw', 'aw'), ('a', 'a')), value='ay')
ToggleButtons(description='A-2:', options=(('ay', 'ay'), ('at', 'at'), ('aw', 'aw'), ('a', 'a')), value='ay')
ToggleButtons(description='S locus:', options=(('SS (solid)', 'SS'), ('Ssp (carrier)', 'Ssp'), ('sp/sp (parti)…
Output()
In [ ]:
## 7. Visual Summary
In [ ]:
## 8. Further Reading