# Normalised histogram in Observable Plot

**URL:** <https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576>\
**Category:** Help\
**Created:** [December 7, 2023, 11:17am UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576 "2023-12-07T11:17:28Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![wirhabenzeit](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/wirhabenzeit/32/8298_2.png) [@wirhabenzeit](https://talk.observablehq.com/u/wirhabenzeit)\
**Post date:** [December 7, 2023, 11:17am UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576/1 "2023-12-07T11:17:28Z")

</div>

I am drawing a histogram like this

```auto
Plot.plot({
  marks: [
    Plot.rectY(data, Plot.binX({y: "count"},{x: "x"}))
  ]
})

```

and it works well. However, I’d like to rescale the histogram to represent a probability distribution (the total area of all bars should be 1).

As far as I can tell `Plot.binX` does not support this. So I tried `Plot.transformX` but it’s not clear how to do it because I need to normalise by the bin-width which I do not know how to access.

Any hints?

---

<div class="post-metadata">

**Author:** ![Fil](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/fil/32/207_2.png) [@Fil](https://talk.observablehq.com/u/Fil)\
**Post date:** [December 7, 2023, 11:36am UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576/2 "2023-12-07T11:36:40Z")

</div>

try changing y to `"proportion"`?

---

<div class="post-metadata">

**Author:** ![wirhabenzeit](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/wirhabenzeit/32/8298_2.png) [@wirhabenzeit](https://talk.observablehq.com/u/wirhabenzeit)\
**Post date:** [December 7, 2023, 12:31pm UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576/3 "2023-12-07T12:31:28Z")

</div>

I tried but it does not work, what I get is this:

 ![Screenshot 2023-12-07 at 13.26.33](https://canada1.discourse-cdn.com/flex030/uploads/observablehq/original/2X/7/7e117220d6089911d7d06ebffcc060d294f92a50.png)

The total area looks to be something like 0.15. According to the documentation

> _proportion_ - the sum proportional to the overall total

it sounds as if `proportion` is summing the values in each bin and dividing by the total sum. What I am looking for is the analogue of `density=True` in [matplotlib-hist](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html):

> If `True`, draw and return a probability density: each bin will display the bin’s raw count divided by the total number of counts _and the bin width_ (`density = counts /(sum(counts) * np.diff(bins))`), so that the area under the histogram integrates to 1 (`np.sum(density * np.diff(bins)) == 1`).

---

<div class="post-metadata">

**Author:** ![mcmcclur](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/mcmcclur/32/2364_2.png) [@mcmcclur](https://talk.observablehq.com/u/mcmcclur)\
**Post date:** [December 7, 2023, 1:16pm UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576/4 "2023-12-07T13:16:46Z")

</div>

The following reducer should accomplish what you want:

```
Plot.binX({
  y: (a, bin) => {
    return a.length / pts.length / (bin.x2 - bin.x1);
  }
}

```

Here [it is in action](https://observablehq.com/embed/0158051e7985f87b):

https://observablehq.com/embed/0158051e7985f87b?cells=normalized_histogram

Note that the data consists of 5000 points generated by `d3.randomNormal()` and the curve is a graph of the standard normal, illustrating that the technique works.

---

<div class="post-metadata">

**Author:** ![wirhabenzeit](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/wirhabenzeit/32/8298_2.png) [@wirhabenzeit](https://talk.observablehq.com/u/wirhabenzeit)\
**Post date:** [December 7, 2023, 1:48pm UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576/5 "2023-12-07T13:48:02Z")

</div>

Thanks, yes this is exactly what I am trying to achieve.

Is there a more automatic way than dividing by the number of points? I want to do this in more complicated situations, with facets or different color groups, so for each group the normalising factor will be different.

I forked your notebook here [https://observablehq.com/d/0ef7cf5eae234601](https://observablehq.com/d/0ef7cf5eae234601) for a simple example.

---

<div class="post-metadata">

**Author:** ![mcmcclur](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/mcmcclur/32/2364_2.png) [@mcmcclur](https://talk.observablehq.com/u/mcmcclur)\
**Post date:** [December 7, 2023, 1:51pm UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576/6 "2023-12-07T13:51:30Z")

</div>

> [@wirhabenzeit](#):
>
> Is there a more automatic way than dividing by the number of points?

Not that I see; the options to `bin` are documented here:

> **[Bin transform | Observable Plot](https://observablehq.com/plot/transforms/bin#bin-options)**
>
> The JavaScript library for exploratory data visualization

@fil would be the expert on the latest and greatest, though.

---

<div class="post-metadata">

**Author:** ![Fil](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/fil/32/207_2.png) [@Fil](https://talk.observablehq.com/u/Fil)\
**Post date:** [December 7, 2023, 2:49pm UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576/7 "2023-12-07T14:49:36Z")

</div>

For facets we have `proportion-facet`, but I realize this is not what you’re looking for exactly, since you really want to normalize by series (facet + z).

One way to do the normalization by _facet_ is to divide all the heights by the width of the _x_ interval, using a map transform, as I do in the first chart of [https://observablehq.com/@fil/plot-normalized-histograms](https://observablehq.com/@fil/plot-normalized-histograms)

 ![chart1](https://canada1.discourse-cdn.com/flex030/uploads/observablehq/original/2X/b/bca6f73ff83e91e5972aaf2329679efd19226883.png)

Normalizing each series (including not only _fy_, but also _z_) means you should not stack the series. It seems to me that this is in contradiction with the use of bars, which are inherently stacking/summable (unless you make them semi-transparent…). However, it seems like a reasonable ask if you want to represent the probability densities with a line mark, as I do in the second chart.

 ![chart2](https://canada1.discourse-cdn.com/flex030/uploads/observablehq/original/2X/2/25522c4b4181b8e134ee232b7d6805c530ef025b.png)

Or a semi-transparent area mark, for aesthetics:

 ![chart 3](https://canada1.discourse-cdn.com/flex030/uploads/observablehq/original/2X/2/2f322c8e89ff6a5e35c174ff10fb7fe5db58199d.png)

We would have to see how to implement a native normalization to area=1, if it’s possible; it happens somewhere around [group.js#L422](https://github.com/observablehq/plot/blob/6fdb955450909cdc2ced7e52f1383b5ec5edc7c2/src/transforms/group.js#L422).

Suggestions are welcome! I can see in the source code that we hesitate to remove proportion-facet, maybe there could be a more general mechanism to cover all these cases.

---

<div class="post-metadata">

**Author:** ![Fil](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/fil/32/207_2.png) [@Fil](https://talk.observablehq.com/u/Fil)\
**Post date:** [December 7, 2023, 5:13pm UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576/8 "2023-12-07T17:13:34Z")

</div>

Tracking issue: [Density reducer · Issue #1940 · observablehq/plot · GitHub](https://github.com/observablehq/plot/issues/1940)

---

<div class="post-metadata">

**Author:** ![wirhabenzeit](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/wirhabenzeit/32/8298_2.png) [@wirhabenzeit](https://talk.observablehq.com/u/wirhabenzeit)\
**Post date:** [December 7, 2023, 6:32pm UTC](https://talk.observablehq.com/t/normalised-histogram-in-observable-plot/8576/9 "2023-12-07T18:32:15Z")

</div>

Thanks a lot! The `mapY` approach works well for my case!
