ENH: Allow bar and barh plots to use index values for x position.
State: closed

_Note_: This is my first attempt to contribute code to pandas, so any correction is more than welcome.


This proposal adds a new parameter to the bar and barh plots, `positioning`, with the values:
- sequential: The index values are added sequentially (`x=0, 1, 2, 3...`) and only the tick label shows the value.
- numerical: The X axis positions will be related to the real index values.
- categorical: The X axis ticks includes all the possible categories, even if are not all used in the index.

With these modes it is possible to create richer bar plots, useful to compare results from different samples.

## Details

With the current bar and barh plots is difficult to create a graph where the categorical axis (x for bar) follows the real values
of the input data.
For example, if we have 10 machines that are not always active and we are collecting the RAM and CPU usage we can have
a data set like:
```python
data = pd.DataFrame({
    'machine': [1, 6, 4, 2, 10],
    'cpu': [0.9, 0.8, 0.8, 0.9, 0.7],
    'ram': [0.6, 0.7, 0.2, 0.9, 1.0]
})

data.plot.bar(x='machine', rot=0)
```
![image](https://user-images.githubusercontent.com/1187546/127787243-6bbcb568-a51f-48dd-8181-88ad5ed379fb.png)

This can be misleading and difficult to compare with data from other sample:

```python
other = pd.DataFrame({
    'machine': [2, 5, 4, 8, 10],
    'cpu': [0.7, 0.3, 0.8, 0.7, 0.8],
    'ram': [0.5, 0.9, 0.6, 0.6, 0.8]
})

fig, axes = plt.subplots(2, 1)
data.plot.bar(x='machine', ax=axes[0], ylabel='day 1', rot=0)
other.plot.bar(x='machine', ax=axes[1], ylabel='day 2', rot=0)
fig.suptitle('Difficult to correlate plots')
```
![image](https://user-images.githubusercontent.com/1187546/127787490-637e1504-4733-46b9-8537-ab51998017ec.png)

This proposal introduces the possibility to interpret the x values according to the numerical value, solving this issue:

```python
fig, axes = plt.subplots(2, 1, sharex='col')
data.plot.bar(x='machine', ax=axes[0], ylabel='day 1', rot=0, positioning="numerical")
other.plot.bar(x='machine', ax=axes[1], ylabel='day 2', rot=0, positioning="numerical")
axes[1].set_xlim(0.5, 10.5)
fig.suptitle('Aligned plot')
```

![image](https://user-images.githubusercontent.com/1187546/127787686-ed3a3bcc-76c7-4039-8d66-56b953aaf862.png)

When the X axis is a categorical data it is possible to also include all the missing labels in the X-axis,
so it is easier to compare graphs generated in different moments.

```python
machines = ['OP1', 'OP2', 'OP3', 'OP4', 'OP5', 'SP1', 'SP2', 'SP3', 'SP4', 'SP5']
day_1 = pd.DataFrame({'machine': ['OP1', 'SP1', 'OP4', 'OP2', 'SP5'], 'cpu': [0.9, 0.8, 0.8, 0.9, 0.7], 'ram': [0.6, 0.7, 0.2, 0.9, 1.0]})
day_1['machine'] = pd.Categorical(day_1['machine'], machines)
day_2 = pd.DataFrame({
    'machine': ['OP2', 'OP5', 'OP4', 'SP3', 'SP4'],
    'cpu': [0.7, 0.3, 0.8, 0.7, 0.8],
    'ram': [0.5, 0.9, 0.6, 0.6, 0.8]
})

day_2['machine'] = pd.Categorical(day_2['machine'], machines)
fig, axes = plt.subplots(2, 1, sharex='col')
day_1.plot.bar(x='machine', ax=axes[0], ylabel='day 1', rot=0, positioning="categorical")
day_2.plot.bar(x='machine', ax=axes[1], ylabel='day 2', rot=0, positioning="categorical")
fig.suptitle('Plot with Categories')
```
![image](https://user-images.githubusercontent.com/1187546/127787905-298e2935-a49b-4282-8c9f-1b55fbd667bd.png)


## Other changes

I also factorized the documentation of bar and barh so they share the same examples. In the current version of the docs there are subtle differences between the bar and barh pages, I tried to unify them.

## Open Points

- Not convinced at all with the name of the flag and the values, for sure someone can find better alternative
- Current code works well with "absolute" index like float, integer categorical or timestamp. It does not work with relative index like period, timedelta or date offsets. Sometimes is fair enough (like using  MonthEnd) but in other cases like TimeDelta the problem arises in the different way matplotlib converts the width parameter of bar.
- I added some tests, but I need to add additional ones to check proper behaviour with error bars and stacked plots.
- The code can be better organized, but requires to modify the content of some of the BarPlot class properties between `__init__` and the call to `_make_plot`. I preferred to keep backwards compatibility, even if this class looks internal to pandas.

_NOTE_: I renamed the parameter from `x_mode` to `positioning`. Still not the best, but at least do not produce confusion for the  barh plots (where it really position the Y axis). Still not perfect, as there is also the parameter `position` that might be confusing.


- [x] tests added / passed
- [x] Ensure all linting tests pass, see [here](https://pandas.pydata.org/pandas-docs/dev/development/contributing.html#code-standards) for how to run them
- [ ] whatsnew entry


Labels:
name: Enhancement
name: Visualization
description: plotting
name: Stale

Comments:
Hi @killerrex - has this enhancement been discussed anywhere?
Hi @MarcoGorelli - No, is the first time I presented it.

I was following the contributing to pandas guide and I understood that for an enhanced proposal I was requested to do it with a PR.

From your question I deduce I understood incorrectly and I missed a lot of important steps.

Shall I cancel the PR and open a issue with the description of the proposal so it is properly reviewed and improved or discarded if not interesting?

Thanks and sorry for the noise.
It's fine, sometimes people just open pull requests, I just wanted to understand if it had been discussed already

This feature looks nice, and we'll see what others say, but to be honest I'd be hesistant to add more features to pandas.plotting, as it's already the part of pandas which probably gets the least attention
cc @tacaswell any thoughts here
Not having looked at the code, this seems like a nice feature to add.
This pull request is stale because it has been open for thirty days with no activity. Please update or respond to this comment if you're still interested in working on this.
@killerrex Does by any chance your pull request address this kind of behavior? https://stackoverflow.com/q/69415032/554319
> @killerrex Does by any chance your pull request address this kind of behavior? https://stackoverflow.com/q/69415032/554319

Almost... to be precise, it cover it when the index is not datetime, I am trying to solve it when it is a datetime.

One of the origins of my modification was to plot in the same axis bars and a line without having to do too many strange things.

So, for example:
```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


n = 21
rng = np.random.default_rng(112233)
x = np.linspace(100, 300, n)

# Linear part, 2 different lines
y1 = 3.0 + 0.25 * rng.integers(-1, 1, size=n, endpoint=True).cumsum()
y2 = 2.0 + 0.45 * rng.integers(-1, 1, size=n, endpoint=True).cumsum()
df_lin = pd.DataFrame({'y1': y1, 'y2': y2}, index=x)

# Stats of 3 different fields
zv = {f'z{k}': k * rng.random(n) for k in range(1, 4)}
df_bar = pd.DataFrame(zv, index=x)

fig, axes = plt.subplots(2, 1, figsize=(12, 8))

# Up plot, with the normal behaviour of pandas 1.3
df_bar.plot.bar(ax=axes[0])
df_lin.plot.line(ax=axes[0], ls=':')
axes[0].set_ylabel('Default positioning')
# Down, with my change to be "Numerical
df_bar.plot.bar(ax=axes[1], positioning='numerical')
df_lin.plot.line(ax=axes[1], ls=':')
axes[1].set_ylabel('positioning=numerical')

plt.show()
```

Creates the following plot:
![image](https://user-images.githubusercontent.com/1187546/135775184-60b9eec9-8ad1-45b6-b9a5-621058330d30.png)

As you can see, in the top axis the lines are not plotted... because they are really 100 units to the right. Even if the X axis says 100, 110... the real values of that coordinates are 0, 1, 2...

In the lower plot you can see it with my modification (I renamed the flag from x_mode to positioning) where the lines are plotted in the expected positions.

I am trying to solve it also for datetime like indexes, but the problem I face is that I need to follow a similar path to what was done in the LinePlot with the `_ts_plot`. To do this properly I need to break the contents of some of the BarPlot class members between the call init and the call to generate in the plot function of [pandas/plotting/_matplotlib/__init__.py](dev/pandas/blob/562df33db7e747187183e96dff4a44f2f68f4c1d/pandas/plotting/_matplotlib/__init__.py#L58)

Let me see if I managed to put it in place something, even if it is just duplicating the code to see if it works and if it is useful I will clean it.

umm why do you need a kwarg at all? if you have categorical data isn't this by-definition?
This pull request is stale because it has been open for thirty days with no activity. Please update or respond to this comment if you're still interested in working on this.
closing as stale. 
This would be a very interesting feature. I am currently working with time series of non-equispaced samples. Because of the nature of the data, it is convenient to represent it by bars rather than a continuous line. However, with the current mode of operation, the bars appear equispaced, falsifying the time x-axis.