Search code examples
pandasmatplotlibxticks

Pandas plot - unable to set xticks


I am trying to plot a stacked bar chart using pandas dataframe plot method. The x values are floats and I am trying to set xticks at integer intervals with no success. Here is my code

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt

data = [[0.0, 4.9, 19.9, 8.1, 0.0], [0.12, 4.5, 27.0, 10.1, 0.0], 
        [0.24, 2.8, 9.3, 4.6, 0.0], [0.36, 2.2, 9.1, 2.7, 0.0], 
        [0.48, 2.2, 7.3, 3.1, 0.0], [1.0, 4.9, 26.4, 11.2, 0.0], 
        [1.12, 5.9, 25.3, 8.7, 0.0], [1.24, 3.7, 13.3, 7.1, 0.0], 
        [1.3599999999999999, 3.0, 9.5, 3.4, 0.0], [1.48, 3.2, 8.9, 4.7, 0.0], 
        [2.0, 8.4, 24.8, 20.2, 0.0], [2.12, 9.5, 19.3, 22.5, 0.0], 
        [2.24, 5.7, 11.8, 6.6, 0.0], [2.36, 4.1, 11.1, 5.1, 0.0], 
        [2.48, 5.4, 8.0, 4.6, 0.0]]
df = pd.DataFrame(columns = ['xval', 'y1', 'y2', 'y3', 'y4'], data = data)
fig, ax = plt.subplots()
df.plot(x = 'xval', kind = 'bar', ax = ax, stacked = True)
xticks = [x for x in df['xval'] if int(x) == x]
ticklabel = ['{:.1f}'.format(x) for x in xticks]
ax.set_xticks(xticks, labels = ticklabel)
ax.grid()
fig.savefig('mychart.png')

enter image description here


Solution

  • import pandas as pd
    from matplotlib import pyplot as plt
    
    data = [
        [0.0, 4.9, 19.9, 8.1, 0.0],
        [0.12, 4.5, 27.0, 10.1, 0.0],
        [0.24, 2.8, 9.3, 4.6, 0.0],
        [0.36, 2.2, 9.1, 2.7, 0.0],
        [0.48, 2.2, 7.3, 3.1, 0.0],
        [1.0, 4.9, 26.4, 11.2, 0.0],
        [1.12, 5.9, 25.3, 8.7, 0.0],
        [1.24, 3.7, 13.3, 7.1, 0.0],
        [1.3599999999999999, 3.0, 9.5, 3.4, 0.0],
        [1.48, 3.2, 8.9, 4.7, 0.0],
        [2.0, 8.4, 24.8, 20.2, 0.0],
        [2.12, 9.5, 19.3, 22.5, 0.0],
        [2.24, 5.7, 11.8, 6.6, 0.0],
        [2.36, 4.1, 11.1, 5.1, 0.0],
        [2.48, 5.4, 8.0, 4.6, 0.0],
    ]
    df = pd.DataFrame(columns=["xval", "y1", "y2", "y3", "y4"], data=data)
    fig, ax = plt.subplots()
    df.plot(x="xval", kind="bar", ax=ax, stacked=True)
    xticks, ticklabels = zip(*[(i, x) for i, x in enumerate(df["xval"]) if x.is_integer()])
    ax.set_xticks(xticks, ticklabels)
    ax.grid()
    fig.savefig("mychart.png")
    

    plot with integer xticks

    It seems to me that the issue with the original approach is that the x-axis isn't actually using some real number line (since the bars wouldn't be equally spaced if something like that was happening). You can see this in action by reordering the rows of the data and seeing that the bars and labels will move around, regardless of whether things are sorted (easiest to see without the xticks-rewriting code).

    Thus, the xticks should actually be the ordinal numbers of the relevant bars. In this case, the "0.0", "1.0", and "2.0" labels should be at bars 0, 5, and 10, respectively (not 0, 1, and 2).