Date axis formatting doesn't work in bar plot

I am trying to format a date x-axis in a stacked bar chart.
I want to place date xticks every 2 years and show year only in order to make them look clean. However, the formatting codes below don’t work.
How can I achieve my goal?

datelist = pd.date_range(dt.datetime(2000,1,1), periods=20,freq='Q').tolist()
bar_series = ['series0', 'series1', 'series2']
values = np.random.randn(20, 3)

df = pd.DataFrame(values,index=datelist,columns=bar_series)

fig = plt.figure(figsize=(10,10))
ax1 = fig.add_subplot(111)

import matplotlib.dates as mdates

ax1.xaxis_date()
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
ax1.xaxis.set_major_locator(mdates.YearLocator(2))

df.plot.bar(ax=ax1,stacked=True)
plt.show()

This code gives:

Hi,
It seemed to be an easy solution, but took me about 2 hours to solve this intriguing problems. I could not see at first that you were trying to format a direct plot from Pandas, using Matplot´s instructions - I really do not know if we have a large range of formatting in Pandas in terms of plotting. Anyway I did an alternative (few minor adjustments in your code) :

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

plt.style.use(“ggplot”)

datelist = pd.date_range(“2000-01-01”, periods=20,freq=‘Q’)

bar_series = [‘series0’, ‘series1’, ‘series2’]
values = np.random.randn(20, 3)

df = pd.DataFrame(values,index=datelist,columns=bar_series)

fig = plt.figure(figsize=(10,10))
ax1 = fig.add_subplot(111)

import matplotlib.dates as mdates

ax1.xaxis.set_major_locator(mdates.YearLocator())
ax1.xaxis.set_major_formatter(mdates.DateFormatter(’%Y’))

p1 = plt.bar(df.index, df[“series0”], width = 50 )
p2 = plt.bar(df.index, df[“series1”], width = 50 )
p3 = plt.bar(df.index, df[“series2”], width = 50 )

plt.legend((p1[0], p2[0], p3[0]), (‘series0’, ‘series1’, “series2”))

plt.show()

It gives you :

2 Likes

So the reason you’ll have to do something like @El_Uatu’s solution is that pandas datetime conversion does not generate something that matplotlib’s date formatters can understand.

1 Like

The codes you suggest gives a plane bar chart, rather than a “stacked” bar chart.
I tried multiple ways to plot a stacked bar chart, but the only successful way of achieving both a stacked bar chart and the formatted date axis is this:

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

plt.style.use('fivethirtyeight')

datelist = pd.date_range("2000-01-01", periods=20,freq='Q')

bar_series = ['series0', 'series1', 'series2']
values = np.random.randint(low=-10,high=10,size=(20, 3))

df = pd.DataFrame(values,index=datelist,columns=bar_series)

fig = plt.figure(figsize=(10,10))
ax1 = fig.add_subplot(111)

import matplotlib.dates as mdates

ax1.xaxis.set_major_locator(mdates.YearLocator(2))
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))

ax1.bar(df.index, df["series0"], width = 50, color='lightgray',label='series1')
ax1.bar(df.index, df["series1"], width = 50,bottom=df['series0'] ,color='gray',label='series2')
ax1.bar(df.index, df["series2"], width = 50,bottom=df['series0']+df['series1'] ,color='black',label='series2')

df['Sum'] = df.sum(axis=1)
ax1.plot(df['Sum'],color='deepskyblue',label='sum')

ax1.legend()
plt.show()

print(df)

series0  series1  series2  Sum
2000-03-31        8       -6      -10   -8
2000-06-30       -2        3        6    7
2000-09-30       -3      -10       -7  -20
2000-12-31        2        4       -4    2
2001-03-31        8        3       -5    6
2001-06-30       -2        9        4   11
2001-09-30        9        4      -10    3
2001-12-31        9        1       -6    4
2002-03-31        7       -8       -4   -5
2002-06-30       -4        2        0   -2
2002-09-30        6        8       -3   11
2002-12-31       -8        5       -9  -12
2003-03-31       -9        5       -4   -8
2003-06-30        5        5        4   14
2003-09-30       -7        5       -6   -8
2003-12-31       -3        0        4    1
2004-03-31       -3        0        8    5
2004-06-30        3        6        0    9
2004-09-30       -1       -8       -7  -16
2004-12-31       -2       -9        0  -11

I know it meets the technical requirements, but it gives odd results…(i.e. the sums are not equal to the heights of the df[‘series2’], etc.)

You are right, I did not pay attention for the stacking.

A very blunt approach for plotting directly from Pandas might be :

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

plt.style.use(‘ggplot’)

datelist = pd.date_range(‘2000-01-01’, periods=20, freq=‘Q’)
bar_series = [‘series0’, ‘series1’, ‘series2’]
values = np.random.randn(20, 3)

datelist = datelist.astype(str)
datelist = [datelist[ x][0:4] for x in range(len(datelist))]

df = pd.DataFrame(values,index=datelist,columns=bar_series)

fig = plt.figure(figsize=(10,10))
ax1 = fig.add_subplot(111)

df.plot.bar(ax=ax1,stacked=True)

plt.xticks(rotation=45)
plt.show()

Although some extra x-axis formatting might be needed, gives you :

1 Like

I think this is a shorter version of what you’re both trying to do:

fig, ax = plt.subplots(figsize=(15,5), constrained_layout=True)

bottom = 0
for c in df.columns:
  ax.bar(df.index, df[c], bottom=bottom, width=50, label=c)
  bottom+=df[c]

ax.xaxis.set_major_locator(mdates.YearLocator(2))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
ax.legend()

plt.show()
1 Like

@El_Uatu, @story645
I think I can plot what I wanted to plot now.
Thank you for your great replies!

2 Likes