{"spec_id":"parallel-categories-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nparallel-categories-basic: Basic Parallel Categories Plot\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 63/100 | Updated: 2026-05-13\n\"\"\"\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.path import Path\n\n\n# Set seaborn style for consistent aesthetics\nsns.set_style(\"whitegrid\")\nsns.set_context(\"talk\", font_scale=1.2)\n\n# Use seaborn's colorblind-safe palette for accessibility\nclass_palette = sns.color_palette(\"colorblind\", 3)\nclass_colors = {\"First\": class_palette[0], \"Second\": class_palette[1], \"Third\": class_palette[2]}\n\n# Data - Titanic-style dataset with categorical dimensions\nnp.random.seed(42)\n\nn_samples = 500\ndata = {\n    \"Class\": np.random.choice([\"First\", \"Second\", \"Third\"], n_samples, p=[0.25, 0.25, 0.50]),\n    \"Sex\": np.random.choice([\"Male\", \"Female\"], n_samples, p=[0.55, 0.45]),\n    \"Age Group\": np.random.choice([\"Child\", \"Adult\", \"Senior\"], n_samples, p=[0.15, 0.70, 0.15]),\n    \"Embarked\": np.random.choice([\"Southampton\", \"Cherbourg\", \"Queenstown\"], n_samples, p=[0.70, 0.20, 0.10]),\n}\n\n# Create survival based on realistic patterns\nsurvival_prob = np.zeros(n_samples)\nfor i in range(n_samples):\n    p = 0.3\n    if data[\"Class\"][i] == \"First\":\n        p += 0.35\n    elif data[\"Class\"][i] == \"Second\":\n        p += 0.15\n    if data[\"Sex\"][i] == \"Female\":\n        p += 0.25\n    if data[\"Age Group\"][i] == \"Child\":\n        p += 0.15\n    survival_prob[i] = min(p, 0.95)\n\ndata[\"Outcome\"] = np.where(np.random.random(n_samples) < survival_prob, \"Survived\", \"Lost\")\n\ndf = pd.DataFrame(data)\n\n# Aggregate data for parallel categories\ndimensions = [\"Class\", \"Sex\", \"Age Group\", \"Embarked\", \"Outcome\"]\ndim_orders = {\n    \"Class\": [\"First\", \"Second\", \"Third\"],\n    \"Sex\": [\"Female\", \"Male\"],\n    \"Age Group\": [\"Child\", \"Adult\", \"Senior\"],\n    \"Embarked\": [\"Southampton\", \"Cherbourg\", \"Queenstown\"],\n    \"Outcome\": [\"Survived\", \"Lost\"],\n}\n\n# Create figure - removed inset to reduce crowding\nfig, ax = plt.subplots(figsize=(16, 9))\n\n# Calculate positions for each dimension\nn_dims = len(dimensions)\nx_positions = np.linspace(0.10, 0.90, n_dims)\ndim_width = 0.025\n\n# Track category positions and heights\ncategory_positions = {}\n\n# Draw category bars and labels\nfor dim_idx, dim in enumerate(dimensions):\n    x_pos = x_positions[dim_idx]\n    categories = dim_orders[dim]\n    counts = df[dim].value_counts()\n    total = counts.sum()\n    heights = {cat: counts.get(cat, 0) / total for cat in categories}\n\n    y_start, y_end = 0.08, 0.88\n    y_range = y_end - y_start\n    current_y = y_start\n\n    for cat in categories:\n        height = heights[cat] * y_range\n        category_positions[(dim, cat)] = (x_pos, current_y, height)\n\n        # Draw category rectangle\n        rect = mpatches.FancyBboxPatch(\n            (x_pos - dim_width / 2, current_y),\n            dim_width,\n            height,\n            boxstyle=\"round,pad=0.003,rounding_size=0.008\",\n            facecolor=\"#3a3a3a\",\n            edgecolor=\"white\",\n            linewidth=1.5,\n            zorder=10,\n        )\n        ax.add_patch(rect)\n\n        # Place all category labels outside bars for better readability\n        label_y = current_y + height / 2\n        if dim_idx == 0:\n            # First dimension: labels on left\n            ax.text(\n                x_pos - dim_width / 2 - 0.015, label_y, cat, ha=\"right\", va=\"center\", fontsize=13, fontweight=\"bold\"\n            )\n        elif dim_idx == n_dims - 1:\n            # Last dimension: labels on right\n            ax.text(x_pos + dim_width / 2 + 0.015, label_y, cat, ha=\"left\", va=\"center\", fontsize=13, fontweight=\"bold\")\n        elif dim_idx == n_dims - 2:\n            # Embarked dimension: labels on right to avoid ribbon overlap\n            ax.text(x_pos + dim_width / 2 + 0.015, label_y, cat, ha=\"left\", va=\"center\", fontsize=11, fontweight=\"bold\")\n        else:\n            # Other middle dimensions: labels on left\n            ax.text(\n                x_pos - dim_width / 2 - 0.012, label_y, cat, ha=\"right\", va=\"center\", fontsize=12, fontweight=\"bold\"\n            )\n\n        current_y += height\n\n# Draw ribbons connecting categories\nfor i in range(n_dims - 1):\n    dim1, dim2 = dimensions[i], dimensions[i + 1]\n    x1, x2 = x_positions[i], x_positions[i + 1]\n    flow_counts = df.groupby([dim1, dim2]).size().reset_index(name=\"count\")\n\n    cat1_current = {cat: category_positions[(dim1, cat)][1] for cat in dim_orders[dim1]}\n    cat2_current = {cat: category_positions[(dim2, cat)][1] for cat in dim_orders[dim2]}\n\n    total_count = len(df)\n    y_range = 0.80\n\n    for _, row in flow_counts.iterrows():\n        cat1, cat2, count = row[dim1], row[dim2], row[\"count\"]\n        ribbon_height = (count / total_count) * y_range\n\n        y1_bottom = cat1_current[cat1]\n        y2_bottom = cat2_current[cat2]\n        y1_top = y1_bottom + ribbon_height\n        y2_top = y2_bottom + ribbon_height\n\n        cat1_current[cat1] = y1_top\n        cat2_current[cat2] = y2_top\n\n        x_mid = (x1 + x2) / 2\n\n        verts = [\n            (x1 + dim_width / 2, y1_bottom),\n            (x_mid, y1_bottom),\n            (x_mid, y2_bottom),\n            (x2 - dim_width / 2, y2_bottom),\n            (x2 - dim_width / 2, y2_top),\n            (x_mid, y2_top),\n            (x_mid, y1_top),\n            (x1 + dim_width / 2, y1_top),\n            (x1 + dim_width / 2, y1_bottom),\n        ]\n\n        codes = [\n            Path.MOVETO,\n            Path.CURVE3,\n            Path.CURVE3,\n            Path.LINETO,\n            Path.LINETO,\n            Path.CURVE3,\n            Path.CURVE3,\n            Path.LINETO,\n            Path.CLOSEPOLY,\n        ]\n\n        path = Path(verts, codes)\n\n        # Color by Class category\n        first_cat = df.loc[(df[dim1] == cat1) & (df[dim2] == cat2), \"Class\"].mode()\n        color = class_colors.get(first_cat.iloc[0], class_palette[0]) if len(first_cat) > 0 else class_palette[0]\n\n        patch = mpatches.PathPatch(path, facecolor=color, edgecolor=\"white\", linewidth=0.3, alpha=0.55, zorder=5)\n        ax.add_patch(patch)\n\n# Add dimension labels at the top\nfor dim_idx, dim in enumerate(dimensions):\n    ax.text(\n        x_positions[dim_idx],\n        0.94,\n        dim,\n        ha=\"center\",\n        va=\"bottom\",\n        fontsize=17,\n        fontweight=\"bold\",\n        color=class_palette[0],\n    )\n\n# Legend for Class colors\nlegend_patches = [\n    mpatches.Patch(color=class_colors[\"First\"], alpha=0.7, label=\"First Class\"),\n    mpatches.Patch(color=class_colors[\"Second\"], alpha=0.7, label=\"Second Class\"),\n    mpatches.Patch(color=class_colors[\"Third\"], alpha=0.7, label=\"Third Class\"),\n]\nax.legend(\n    handles=legend_patches,\n    loc=\"lower center\",\n    fontsize=12,\n    framealpha=0.9,\n    edgecolor=\"gray\",\n    ncol=3,\n    bbox_to_anchor=(0.5, -0.02),\n)\n\n# Style adjustments\nax.set_xlim(0, 1)\nax.set_ylim(0, 1.02)\nax.set_aspect(\"auto\")\nax.axis(\"off\")\n\n# Title\nax.set_title(\"parallel-categories-basic · seaborn · pyplots.ai\", fontsize=24, fontweight=\"bold\", pad=15)\n\nplt.savefig(\"plot.png\", dpi=300, bbox_inches=\"tight\", facecolor=\"white\")\n"}