1

I am trying to make a bar chart of negative values where the baseline x-axis is at -10 instead of 0 and the values, because they are all -10<x<0, extend up from the baseline.

If I plot it as is, the bars extend downward:

import matplotlib.pyplot as plt
vals = [-4, -6, -8, -6, -5]
plt.bar(range(len(vals)), vals)

enter image description here

I could fake it in some sense by adding 10 to the data, but then I would have to remove the y-tick values, and I need to keep them.

new_vals = [val + 10 for val in vals]
plt.yticks([])
plt.bar(range(len(new_vals)), new_vals)

enter image description here

So how can I make the second image with the y-ticks of the first image and, preferably, without "faking" any of the data?

jss367
  • 4,759
  • 14
  • 54
  • 76
  • 1
    https://stackoverflow.com/a/11250884/8033585 shows how to achieve this. Instead of adding 10, consider changing sign of data. Get the labels and then change their sign. – AGN Gazer Jun 24 '18 at 15:33
  • Using `fig, ax = plt.subplots()` and `ax.set_yticklabels(range(-10, 0))` per your link solved it for me, although I still have to fake the data. Also, had I just flipped the sign it would change the shape of the graph, which isn't what I'm looking for. – jss367 Jun 24 '18 at 15:41
  • This is not "faking data": you really just want to plot `data - (-10)` i.e. `data + 10`. That's how shifting your baseline works. – Andras Deak -- Слава Україні Jun 24 '18 at 15:46

2 Answers2

0

Following https://matplotlib.org/gallery/ticks_and_spines/custom_ticker1.html example, you can also do like this:

from matplotlib.ticker import FuncFormatter
def neg_tick(x, pos):
    return '%.1f' % (-x if x else 0) # avoid negative zero (-0.0) labels

formatter = FuncFormatter(neg_tick)
fig, ax = plt.subplots()
ax.yaxis.set_major_formatter(formatter)
plt.bar(range(len(vals)), [-v for v in vals]) # or -numpy.asarray(vals)
# or, let Python enumerate bars:
# plt.bar(*zip(*enumerate(-v for v in vals)))
plt.show()

You cannot not "fake" data. Bar charts plot bars up for positive data and down for negative. If you want things differently, you need to trick bar chart somehow.

enter image description here

IMO, the above solution is better than https://stackoverflow.com/a/11250884/8033585 because it does not need the trick with drawing canvas, changing labels, etc.


If you do want to have "reverted" bar length (as it is in your example), then you can do the following:

from matplotlib.ticker import FuncFormatter
import numpy as np

# shifted up:
vals = np.asarray(vals)
minval = np.amin(vals)
minval += np.sign(minval) # "add" 1 so that "lowest" bar is still drawn
def neg_tick(x, pos):
    return '%.1f' % (x + minval if x != minval else 0)

formatter = FuncFormatter(neg_tick)
fig, ax = plt.subplots()
ax.yaxis.set_major_formatter(formatter)
plt.bar(*zip(*enumerate(-minval + vals)))
plt.show()

enter image description here

AGN Gazer
  • 8,025
  • 2
  • 27
  • 45
0

Try matplotlib.axes.Axes.invert_yaxis

from matplotlib import pyplot as plt
vals = [-4, -6, -8, -6, -5]
plt.bar(range(len(vals)), vals)

enter image description here

fig, ax1 = plt.subplots(1,1)
ax1.bar(range(len(vals)), vals)
ax1.invert_yaxis()

enter image description here

iayork
  • 6,420
  • 8
  • 44
  • 49
  • This changes the shape of the data, which isn't what I'm looking for. But otherwise, this is a good solution. – jss367 Jun 24 '18 at 16:29