6

I want to make a categorical plot in python to represent the range of several variables. I thought maybe I would use a bar plot and set ranges for the bars. Here is my bar plot

import matplotlib.pyplot as plt
import numpy as np

data = {'a':.3, 'b': .3, 'c': .3, 'd': .1,'e':.1,'f':.1}

names = list(data.keys())    
values = list(data.values())

plt.bar(names,values)
plt.show()

Barplot produced from given code: enter image description here Please tell me how set the value of the bars to something like range(.3,.4). In other words, I want the bars to go from say (.3 to .4 to represent a range) instead of (0 to .3 the way it currently is).

I know using a barplot to represent a range is odd, and if someone has another way for me to do this please add your code. I don't really care as long as I get a plot which represents ranges of at least 6 variables in which I can easily and manually change the value of the ranges.

Nazim Kerimbekov
  • 4,712
  • 8
  • 34
  • 58
codes4toads
  • 65
  • 1
  • 1
  • 5
  • https://matplotlib.org/3.1.1/gallery/misc/table_demo.html#sphx-glr-gallery-misc-table-demo-py use this example to get selective colouring. –  Dec 18 '19 at 15:17

2 Answers2

3

One approach would be to use a LineCollection and set the linewidth to resemble a bar plot.

To use a LineCollection you need to provide a set of x values that plot each range provided in data. To create the y values associated with your range, I used np.arange. Since np.arange can only count integers, I multiplied each value in data 10 and divided the result by 10 to get back to the input data.

data = {'a':(.3,.4), 'b': (.3,.4), 'c': (.3,.4), 'd': (0,.1),'e':(0,.1),'f':(0,.1)}

names = list(data.keys())    
values = list(data.values())

values = [np.arange(a[0]*10, (a[1]+0.1)*10,1)/10 for a in values]
xs = [np.ones(a.shape) for a in values]

xs = [i*a for i,a in zip(range(len(xs)), xs)]

verts = []
for x,y in zip(xs, values):
    verts.append(list(zip(x.tolist(), y.tolist())))

lc = LineCollection(verts, linewidths=40)

fig,ax = plt.subplots()
ax.add_collection(lc)
ax.set_xlim(-0.5,len(values) )
plt.xticks(np.arange(len(values)), names)

enter image description here

dubbbdan
  • 2,650
  • 1
  • 25
  • 43
  • I really like this but I get this error when I try to change the range on all of them. ValueError Traceback (most recent call last) in () 10 values = [np.arange(a[0]*10, (a[1]+0.1)*10,1)/10 for a in values] 11 xs = [np.ones(a.shape) for a in values] ---> 12 xs = (np.arange(len(values))+1)* xs 13 14 verts = [] ValueError: operands could not be broadcast together with shapes (6,) (6,2) – codes4toads Dec 18 '19 at 16:57
  • What did you change the ranges to? – dubbbdan Dec 18 '19 at 20:36
  • changed them to (.3,.4),(.3,.4),(.3,.4),(0,.1),(0,.1),(0,.1), and got rid of 'g'. Something odd was happening where I could change 5 of 6 parameters to what I wanted and the last one when I went to change it would give me the error. thanks again! – codes4toads Dec 18 '19 at 21:35
  • sorry about that, I have updated my answer with the changed values. This solution is more complex than `ax.vline`, but it will plot exactly the data you want. I also see that the bars don't stop exactly at 0.4 on @Fourier's post. – dubbbdan Dec 18 '19 at 21:53
3

Updated answer

To solve the issue with axvline, the ratio of width (pixels) versus the range has to be taken into account. I feel that this is a hack.

Therefore, here is a solution that uses axvspan.

import numpy as np
from matplotlib import pyplot as plt

data = {'a':(0,.3), 'b': (0.2,.3), 'c': (.1,.3), 'd': (0.3,0.4),'e':(.1,.2),'f':(.1,.4)}

width = 0.5 # adjust to your liking

fig, ax = plt.subplots()

for i, values in enumerate(data.values()):

    ymin, ymax = values

    ax.axvspan(xmin=i-width/2, xmax=i+width/2, ymin=ymin,ymax=ymax)

# to illustrate that ranges are properly drawn
ax.grid(True)

#add ticks 
ax.set_xticks(np.arange(0,len(data)))    
ax.set_xticklabels(data.keys())

Output:

enter image description here

Original answer

Another option would be to use axvline:

from matplotlib import pyplot as plt

data = {'a':(0,.3), 'b': (0.2,.3), 'c': (.1,.3), 'd': (0.3,0.4),'e':(.1,.2),'f':(.1,.4)}

for key, values in data.items():
    ymin, ymax = values
    plt.axvline(x=key, ymin=ymin, ymax=ymax, lw=5)

Output:

enter image description here

By adjusting lw you can approach a bar plot if required.

Fourier
  • 2,795
  • 3
  • 25
  • 39
  • 1
    I like this solution a lot but the line goes a little above .4 on the plot.. any way to fix this? seems like an odd problem – codes4toads Dec 18 '19 at 16:58