{"spec_id":"line-impurity-comparison","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nline-impurity-comparison: Gini Impurity vs Entropy Comparison\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\nimport re\nimport sys\n\n\n# Prevent importing local pygal.py file\nsys.path = [p for p in sys.path if not p.endswith(\"/implementations/python\")]\n\nimport cairosvg\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Data — 200 points for smooth curves across full [0, 1] probability range\np = np.linspace(0, 1, 200)\ngini = 2 * p * (1 - p)\n\nwith np.errstate(divide=\"ignore\", invalid=\"ignore\"):\n    entropy = -p * np.log2(p) - (1 - p) * np.log2(1 - p)\nentropy = np.nan_to_num(entropy, nan=0.0)\n\n_font = \"Helvetica, Arial, sans-serif\"\n\n# Imprint palette — first series is brand green, second is lavender\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(\n        \"#009E73\",  # 1: Gini — Imprint green (first categorical series)\n        \"#C475FD\",  # 2: Entropy — Imprint lavender\n        INK_MUTED,  # 3: vertical guide line — muted neutral\n        \"#009E73\",  # 4: Gini peak dot — matches Gini\n        \"#C475FD\",  # 5: Entropy peak dot — matches Entropy\n    ),\n    opacity=\"1\",\n    opacity_hover=\"1\",\n    stroke_opacity=\"1\",\n    stroke_opacity_hover=\"1\",\n    stroke_width=2.5,\n    guide_stroke_color=INK_MUTED,\n    guide_stroke_dasharray=\"3, 5\",\n    major_guide_stroke_color=INK_SOFT,\n    major_guide_stroke_dasharray=\"0\",\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    value_label_font_size=48,\n    tooltip_font_size=32,\n    font_family=_font,\n    label_font_family=_font,\n    major_label_font_family=_font,\n    legend_font_family=_font,\n    title_font_family=_font,\n    value_font_family=_font,\n    value_label_font_family=_font,\n)\n\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    title=\"line-impurity-comparison · python · pygal · anyplot.ai\",\n    x_title=\"Probability p\",\n    y_title=\"Impurity measure\",\n    style=custom_style,\n    show_dots=False,\n    fill=False,\n    show_y_guides=True,\n    show_x_guides=False,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=2,\n    legend_box_size=24,\n    truncate_legend=-1,\n    xrange=(0, 1),\n    range=(0, 1.05),\n    y_labels=[0, 0.2, 0.4, 0.6, 0.8, 1.0],\n    show_y_labels=True,\n    x_label_rotation=0,\n    margin=80,\n    margin_bottom=120,\n    margin_left=160,\n    margin_right=80,\n    margin_top=80,\n    interpolate=\"cubic\",\n    print_values=False,\n    print_zeroes=False,\n    print_labels=True,\n    x_value_formatter=lambda x: f\"{x:.1f}\",\n    js=[],\n)\n\n# Convert to pygal XY point lists\ngini_points = list(zip(p.tolist(), gini.tolist(), strict=True))\nentropy_points = list(zip(p.tolist(), entropy.tolist(), strict=True))\n\n# Series 1: Gini impurity — Imprint green, first categorical series\nchart.add(\"Gini: 2p(1−p)\", gini_points, stroke_style={\"width\": 5})\n\n# Series 2: Shannon entropy (normalized to [0,1]) — Imprint lavender\nchart.add(\"Entropy: −p log₂p − (1−p) log₂(1−p)\", entropy_points, stroke_style={\"width\": 5})\n\n# Series 3: Vertical guide at p=0.5 (both maxima occur here)\nchart.add(None, [(0.5, 0.0), (0.5, 1.05)], stroke_style={\"width\": 1.5, \"dasharray\": \"8, 6\"})\n\n# Series 4: Gini peak annotation dot at (0.5, 0.5)\nchart.add(\n    None, [{\"value\": (0.5, 0.5), \"label\": \"Gini peak = 0.50\"}], stroke_style={\"width\": 0}, show_dots=True, dots_size=10\n)\n\n# Series 5: Entropy peak annotation dot at (0.5, 1.0)\nchart.add(\n    None,\n    [{\"value\": (0.5, 1.0), \"label\": \"Entropy peak = 1.00\"}],\n    stroke_style={\"width\": 0},\n    show_dots=True,\n    dots_size=10,\n)\n\n\ndef _render_png(chart, filepath):\n    \"\"\"Render chart to PNG, softening the default hard rectangular axis border.\"\"\"\n    svg_bytes = chart.render()\n    svg_str = svg_bytes.decode(\"utf-8\")\n    # Extract chart id to scope the injected rule precisely\n    m = re.search(r'id=\"(chart-[^\"]+)\"', svg_str)\n    chart_id = f\"#{m.group(1)} \" if m else \"\"\n    # Soften the default axis border lines (left/bottom frame) to INK_MUTED at 45% opacity.\n    # .axis .line targets the axis spine paths; more-specific .axis .major.line continues\n    # to use the theme INK color for the major grid anchors, both at reduced opacity.\n    border_css = (\n        f\"\\n      {chart_id}.axis .line {{\"\n        f\" stroke: {INK_MUTED} !important;\"\n        f\" stroke-opacity: 0.45 !important; }}\"\n        f\"\\n      {chart_id}.axis .major.line {{\"\n        f\" stroke: {INK_MUTED} !important;\"\n        f\" stroke-opacity: 0.45 !important; }}\"\n        f\"\\n    \"\n    )\n    svg_str = svg_str.replace(\"</style>\", border_css + \"</style>\", 1)\n    cairosvg.svg2png(bytestring=svg_str.encode(\"utf-8\"), write_to=filepath, output_width=3200, output_height=1800)\n\n\n_render_png(chart, f\"plot-{THEME}.png\")\nchart.render_to_file(f\"plot-{THEME}.svg\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}