{"spec_id":"histogram-density","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nhistogram-density: Density Histogram\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-11\n\"\"\"\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom scipy import stats\n\n\n# Data - Generate bimodal distribution to show density histogram features\nnp.random.seed(42)\n# Reaction times from two conditions: baseline and fatigued\nbaseline_times = np.random.normal(loc=250, scale=30, size=350)\nfatigued_times = np.random.normal(loc=380, scale=45, size=150)\nreaction_times = np.concatenate([baseline_times, fatigued_times])\n\n# Compute histogram data manually for density normalization\nnum_bins = 25\nbins = np.linspace(reaction_times.min() - 10, reaction_times.max() + 10, num_bins + 1)\ncounts, bin_edges = np.histogram(reaction_times, bins=bins, density=True)\nbin_width = bin_edges[1] - bin_edges[0]\n\n# Create DataFrame with bin ranges for proper bar rendering\nhist_df = pd.DataFrame(\n    {\n        \"bin_start\": bin_edges[:-1],\n        \"bin_end\": bin_edges[1:],\n        \"Density\": counts,\n        \"bin_center\": (bin_edges[:-1] + bin_edges[1:]) / 2,\n    }\n)\n\n# Create density histogram using rect mark for proper filled bars\nhistogram = (\n    alt.Chart(hist_df)\n    .mark_rect(color=\"#306998\", opacity=0.75, stroke=\"#1a3a5c\", strokeWidth=1.5)\n    .encode(\n        x=alt.X(\"bin_start:Q\", scale=alt.Scale(domain=[bins.min(), bins.max()]), title=\"Reaction Time (ms)\"),\n        x2=\"bin_end:Q\",\n        y=alt.Y(\"Density:Q\", scale=alt.Scale(domain=[0, counts.max() * 1.1]), title=\"Density (probability per ms)\"),\n        tooltip=[\n            alt.Tooltip(\"bin_center:Q\", title=\"Bin Center\", format=\".0f\"),\n            alt.Tooltip(\"Density:Q\", title=\"Density\", format=\".5f\"),\n        ],\n    )\n)\n\n# Create KDE overlay for theoretical density reference\nkde = stats.gaussian_kde(reaction_times, bw_method=0.15)\nx_kde = np.linspace(reaction_times.min() - 20, reaction_times.max() + 20, 300)\ny_kde = kde(x_kde)\n\nkde_df = pd.DataFrame({\"Reaction Time (ms)\": x_kde, \"Density\": y_kde})\n\nkde_line = alt.Chart(kde_df).mark_line(color=\"#FFD43B\", strokeWidth=4).encode(x=\"Reaction Time (ms):Q\", y=\"Density:Q\")\n\n# Combine histogram and KDE\nchart = (\n    alt.layer(histogram, kde_line)\n    .properties(\n        width=1600,\n        height=900,\n        title=alt.Title(\"histogram-density · altair · pyplots.ai\", fontSize=28, anchor=\"middle\", color=\"#333333\"),\n    )\n    .configure_axis(labelFontSize=18, titleFontSize=22, gridColor=\"#cccccc\", gridOpacity=0.3)\n    .configure_view(strokeWidth=0)\n)\n\n# Save as PNG (1600 × 900 × 3 = 4800 × 2700 px)\nchart.save(\"plot.png\", scale_factor=3.0)\n\n# Save interactive HTML version\nchart.save(\"plot.html\")\n"}