{"spec_id":"chernoff-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nchernoff-basic: Chernoff Faces for Multivariate Data\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport seaborn as sns\nfrom sklearn.datasets import load_wine\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"\n\n# Load wine dataset (13 chemical measurements per wine sample)\nwine = load_wine()\nfeature_names = wine.feature_names\ndata = wine.data\ntarget = wine.target\n\n# Select 5 wines from each class (3 classes = 15 total)\nnp.random.seed(42)\nindices = []\nfor cls in range(3):\n    class_indices = np.where(target == cls)[0]\n    selected = np.random.choice(class_indices, size=5, replace=False)\n    indices.extend(selected)\nindices = np.array(indices)\n\nsubset_data = data[indices]\nsubset_target = target[indices]\nclass_names = [\"Class 1\", \"Class 2\", \"Class 3\"]\n\n# Select 4 key features for face mapping\nselected_features = [0, 6, 9, 12]  # Alcohol, phenols, malic acid, proline\nselected_feature_names = [feature_names[i].replace(\" \", \"\\n\") for i in selected_features]\ndata_subset = subset_data[:, selected_features]\n\n# Within-species normalization for faces\nnormalized_data = np.zeros((15, 4))\nfor cls in range(3):\n    class_mask = subset_target == cls\n    class_subset = data_subset[class_mask]\n    for feat_idx in range(4):\n        feat_min = class_subset[:, feat_idx].min()\n        feat_max = class_subset[:, feat_idx].max()\n        feat_range = feat_max - feat_min if feat_max > feat_min else 1.0\n        normalized_data[class_mask, feat_idx] = (class_subset[:, feat_idx] - feat_min) / feat_range\n\n# Set seaborn theme\nsns.set_style(\"white\")\nsns.set_context(\"poster\", font_scale=1.0)\nsns.set_theme(\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"text.color\": INK,\n        \"axes.labelcolor\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n    }\n)\n\n# Okabe-Ito palette - first series is always brand green\nokabe_ito = [BRAND, \"#C475FD\", \"#4467A3\"]\nface_colors = [okabe_ito[t] for t in subset_target]\n\n# Create figure\nfig = plt.figure(figsize=(16, 9), facecolor=PAGE_BG)\n\n# Grid layout: faces only (3 rows x 5 cols)\ngs = fig.add_gridspec(3, 5, hspace=0.35, wspace=0.25)\n\n\ndef hex_to_rgb(h):\n    return tuple(int(h.lstrip(\"#\")[i : i + 2], 16) / 255.0 for i in (0, 2, 4))\n\n\n# Draw Chernoff faces\nfor idx in range(15):\n    row = idx // 5\n    col = idx % 5\n    ax = fig.add_subplot(gs[row, col])\n\n    features = normalized_data[idx]\n    color = face_colors[idx]\n\n    # Map features to facial characteristics\n    # Feature 0 (alcohol): face width\n    # Feature 1 (phenols): face height\n    # Feature 2 (malic acid): eye size\n    # Feature 3 (proline): mouth curvature\n\n    face_width = 0.45 + features[0] * 0.55\n    face_height = 0.55 + features[1] * 0.55\n    eye_size = 0.04 + features[2] * 0.12\n    mouth_curve = -0.35 + features[3] * 0.7\n\n    # Draw face outline\n    face = mpatches.Ellipse(\n        (0.5, 0.5), face_width, face_height, facecolor=color, edgecolor=INK, linewidth=2.5, alpha=0.9\n    )\n    ax.add_patch(face)\n\n    # Draw eyes\n    eye_y = 0.58\n    eye_spacing = 0.11 + features[1] * 0.05\n\n    # Left eye\n    left_eye = mpatches.Ellipse(\n        (0.5 - eye_spacing, eye_y), eye_size * 1.6, eye_size, facecolor=ELEVATED_BG, edgecolor=INK, linewidth=2\n    )\n    ax.add_patch(left_eye)\n    left_pupil = mpatches.Circle((0.5 - eye_spacing, eye_y), eye_size * 0.35, facecolor=INK)\n    ax.add_patch(left_pupil)\n\n    # Right eye\n    right_eye = mpatches.Ellipse(\n        (0.5 + eye_spacing, eye_y), eye_size * 1.6, eye_size, facecolor=ELEVATED_BG, edgecolor=INK, linewidth=2\n    )\n    ax.add_patch(right_eye)\n    right_pupil = mpatches.Circle((0.5 + eye_spacing, eye_y), eye_size * 0.35, facecolor=INK)\n    ax.add_patch(right_pupil)\n\n    # Draw eyebrows\n    eyebrow_angle = -0.12 + features[2] * 0.24\n    eyebrow_y = eye_y + eye_size + 0.05\n\n    ax.plot(\n        [0.5 - eye_spacing - 0.05, 0.5 - eye_spacing + 0.05],\n        [eyebrow_y + eyebrow_angle, eyebrow_y - eyebrow_angle],\n        color=INK,\n        linewidth=3.5,\n        solid_capstyle=\"round\",\n    )\n    ax.plot(\n        [0.5 + eye_spacing - 0.05, 0.5 + eye_spacing + 0.05],\n        [eyebrow_y - eyebrow_angle, eyebrow_y + eyebrow_angle],\n        color=INK,\n        linewidth=3.5,\n        solid_capstyle=\"round\",\n    )\n\n    # Draw nose\n    nose_size = 0.03 + features[0] * 0.025\n    color_rgb = hex_to_rgb(color)\n    nose_color = tuple(c * 0.7 for c in color_rgb)\n    nose = mpatches.Polygon(\n        [[0.5, 0.50], [0.5 - nose_size, 0.40], [0.5 + nose_size, 0.40]],\n        facecolor=nose_color,\n        edgecolor=INK,\n        linewidth=1.5,\n    )\n    ax.add_patch(nose)\n\n    # Draw mouth\n    mouth_width = 0.08 + features[0] * 0.05\n    mouth_x = np.linspace(0.5 - mouth_width, 0.5 + mouth_width, 25)\n    mouth_y = 0.30 + mouth_curve * ((mouth_x - 0.5) ** 2) * 18\n    ax.plot(mouth_x, mouth_y, color=INK_SOFT, linewidth=3.5, solid_capstyle=\"round\")\n\n    # Set axis properties\n    ax.set_xlim(0, 1)\n    ax.set_ylim(0, 1)\n    ax.set_aspect(\"equal\")\n    ax.axis(\"off\")\n\n    # Add label below face\n    class_idx = subset_target[idx]\n    sample_num = (idx % 5) + 1\n    ax.text(\n        0.5,\n        -0.05,\n        f\"{class_names[class_idx]} #{sample_num}\",\n        ha=\"center\",\n        va=\"top\",\n        fontsize=11,\n        fontweight=\"bold\",\n        color=INK,\n    )\n\n# Add overall title\nfig.suptitle(\"chernoff-basic · seaborn · anyplot.ai\", fontsize=24, fontweight=\"medium\", y=0.98, color=INK)\n\n# Add legend for classes\nlegend_handles = [mpatches.Patch(color=okabe_ito[i], label=class_names[i], ec=INK, lw=1.5) for i in range(3)]\nfig.legend(\n    handles=legend_handles,\n    loc=\"lower right\",\n    fontsize=14,\n    frameon=True,\n    bbox_to_anchor=(0.98, 0.02),\n    title=\"Wine Class\",\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    title_fontsize=14,\n)\n\n# Add feature mapping explanation\nfeature_text = \"Face Width ← Alcohol  |  Face Height ← Phenols  |  Eye Size ← Malic Acid  |  Mouth Curve ← Proline\"\nfig.text(0.5, 0.01, feature_text, ha=\"center\", fontsize=12, style=\"italic\", color=INK_SOFT)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}