🐩 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:

  1. E sets the stage (ee hides everything).
  2. K decides if patterns are blocked (KB), brindled (Kbr), or revealed (kyky).
  3. A supplies the actual pattern—if K lets it through.
  4. B colours the eumelanin (black vs. brown).
  5. 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