matplotlib bar chart with labels: space out the bars - matplotlib

When I run this code in Jupyter notebook the labels overlap and are unreadable.
y = [72, 21, 114, 52, 114, 12, 101, 16, 68, 118]
x = np.arange(len(y))
columns = ['MAHC_A', 'MAHC_B', 'MAHC_C', 'MAHC_D', 'MAHC_E', 'MAHC_F','MAHC_G', 'MAHC_H', 'MAHC_I', 'MAHC_J']
fig, ax = plt.subplots()
ax.bar(x, y, width=bar_width)
ax.set_xticks(x)
ax.set_xticklabels(xlabels)
plt.show()
Is there a way to space these out?

There are several options:
Make the figure larger in the horizontal direction.
fig, ax = plt.subplots(figsize=(10,4))
Make the fontsize smaller
ax.set_xticklabels(columns, fontsize=8)
Rotate the labels, such that they won't overlap anymore.
ax.set_xticklabels(columns, rotation=45)
Or, of course any combination of those.

Related

How to enlarge a Matplotlib group bar diagram

I have a group bar chart that I would like to scale.
I am running matplotlib in a Jupyter Notebook and the bar chart is very squashed. I would like to make the axis bigger but can't get it to work in a group bar chart. If I could make it wider it would be much more readable. But if I just increase "width" then the bars start to overlap each other.
The second problem is what to do about the labels. How can the labels be printed to three decimal places?
Note: I recognise that the the values plotted are orders of magnitude different so you cannot really read the small values. Ordinarily you would not combine these onto a single chart - but this is a class exercise to demonstrating why you would not do it so I expect that.
Here is the self-contained code to demonstrate the problem:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
labels = ['0-9', '10-19', '20-29', '30-39', '40-49', '50-59', '60-69', '70-79', '80-89', '90-99']
t3=[1.2833333333333332, 1.6970588235294117, 1.7189655172413794, 1.8090163934426229, 1.44140625, 1.5763157894736846, 1.3685185185185187, 1.430120481927711, 1.5352941176470587, 1.9]
tt4= [116.33333333333333, 106.0, 106.93103448275862, 109.47540983606558, 98.734375, 99.84210526315789, 96.72839506172839, 99.40963855421687, 104.94117647058823, 203.0]
tsh= [1.2833333333333332, 1.6970588235294117, 1.7189655172413794, 1.8090163934426229, 1.44140625, 1.5763157894736846, 1.3685185185185187, 1.430120481927711, 1.5352941176470587, 1.9]
hypo_count= [2, 15, 55, 58, 59, 69, 72, 74, 33, 1]
x = np.arange(len(labels)) # the label locations
width = 0.2 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(x, t3, width, label='T3 avg')
rects2 = ax.bar(x+(width), tt4, width, label='TT4 avg')
rects3 = ax.bar(x+(width*2), tsh, width, label='TSH avg')
rects4 = ax.bar(x+(width*3), hypo_count, width, label='# Hypothyroid +ve')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_title('Age Bracket')
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend()
# Print the value on top of each bar
ax.bar_label(rects1, padding=3)
ax.bar_label(rects2, padding=3)
ax.bar_label(rects3, padding=3)
ax.bar_label(rects4, padding=3)
fig.tight_layout()
plt.show()

Matplotlib / Seaborn: Make a vertical distplot and a barplot share the Y axis

I'm trying to create a plot with two subplots (one row, two columns), in which a vertical distplot and a vertical barplot (both from seaborn) share the Y axis. The result should look somewhat like an asymmetric violin plot.
The data for the bar plot is of this form:
In[8]: barplot_data[0:5]
Out[8]:
[{'time': 0, 'val': 171.19374169863295},
{'time': 50, 'val': 2313.8459788903383},
{'time': 100, 'val': 1518.687964071397},
{'time': 150, 'val': 1355.8373488876694},
{'time': 200, 'val': 1558.7682098705088}]
I.e., for every time step (in steps of 50), I know the height of the bar. The data for the dist plot is of the form:
In[9]: distplot_data[0:5]
Out[9]: [605, 477, 51, 337, 332]
I.e., a series of time points of which I'd like the distribution to be drawn.
Here's how I create the bar plot in the right subplot:
barplot_df = pd.DataFrame(barplot_data)
fig, axes = plt.subplots(1, 2, sharex=False, sharey=True, squeeze=False)
left_ax = axes[0][0]
right_ax = axes[0][1]
sns.barplot(y='time', x='val',
data=barplot_df,
orient='h',
ax = right_ax)
The result is pretty much what I want on the right side:
Similarly, I can put the dist plot on the left side:
fig, axes = plt.subplots(1, 2, sharex=False, sharey=True, squeeze=False)
left_ax = axes[0][0]
right_ax = axes[0][1]
sns.distplot(distplot_data, ax=left_ax, vertical=True)
This also works. I think it's kind of strange that the direction of the Y axis is reversed, but whatever:
However, now I'm just trying to plot them both into the same figure and it wreaks havoc on the dist plot:
fig, axes = plt.subplots(1, 2, sharex=False, sharey=True, squeeze=False)
left_ax = axes[0][0]
right_ax = axes[0][1]
sns.barplot(y='time', x='val',
data=barplot_df,
orient='h',
ax = right_ax)
sns.distplot(distplot_data, ax=left_ax, vertical=True)
I can only imagine that this is because of the axis of the distplot somehow being distorted or something? Does someone know what's going on here?

matplotlib line plot dont show vertical lines in step function

I do have a plot that only consists of horizontal lines at certain values when I have a signal, otherwise none. So, I am looking for a way to plot this without the vertical lines. there may be gaps between the lines when there is no signal and I dont want the lines to connect nor do I want a line falling off to 0. Is there a way to plot this like that in matplotlib?
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
axes = self.figure.add_subplot(111)
axes.plot(df.index, df["x1"], lw=1.0, c=self.getColour('g', i), ls=ls)
The plot you are looking for is Matplotlib's plt.hlines(y, xmin, xmax).
For example:
import matplotlib.pyplot as plt
y = range(1, 11)
xmin = range(10)
xmax = range(1, 11)
colors=['blue', 'green', 'red', 'yellow', 'orange', 'purple',
'cyan', 'magenta', 'pink', 'black']
fig, ax = plt.subplots(1, 1)
ax.hlines(y, xmin, xmax, colors=colors)
plt.show()
Yields a plot like this:
See the Matplotlib documentation for more details.

Get desired wspace and subplots appropriately sized?

I'm trying to make a plot with one panel up top (colspan = 2) and two plots below, with a controlled amount of space between them. I'd like the bounds of the plots to be in alignment. Here's what I'm starting with:
import cartopy
from matplotlib import pyplot
from matplotlib.gridspec import GridSpec
gs = GridSpec(2, 2, height_ratios=[2, 1], hspace=0, wspace=0)
ax0 = pyplot.subplot(gs[0, :], projection=cartopy.crs.LambertConformal())
ax0.add_feature(cartopy.feature.COASTLINE)
ax0.set_extent([-120, -75, 20, 52], cartopy.crs.Geodetic())
ax1 = pyplot.subplot(gs[1, 0], projection=cartopy.crs.LambertConformal())
ax1.add_feature(cartopy.feature.COASTLINE)
ax1.set_extent([-90, -75, 20, 30], cartopy.crs.Geodetic())
ax2 = pyplot.subplot(gs[1, 1], projection=cartopy.crs.LambertConformal())
ax2.add_feature(cartopy.feature.COASTLINE)
ax2.set_extent([-90, -75, 20, 30], cartopy.crs.Geodetic())
pyplot.show()
First problem is that the wspace=0 parameter doesn't take.
Second problem is (at least this is my guess on how to proceed) calculating a height ratio that will make the width of the upper subplot equal the combined width of the lower subplots (plus any wspace).

matplotlib + wxpython not sizing correctly with legend

I have a matplotlib figure embedded in a wxpython frame with a few sizers. Everything works fine until I include a legend but then the sizers don't seem to be working with the legend.
Even when I resize the window by dragging at the corner, the main figure changes size, but only the edge of the legend is ever shown.
That is, note that the legend is not visible in the wxFrame.
import wx
import matplotlib as mpl
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
from random import shuffle
class PlotFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, title="Plot", size=(-1, -1))
self.main_panel = wx.Panel(self, -1)
self.plot_panel = PlotPanel(self.main_panel)
s0 = wx.BoxSizer(wx.VERTICAL)
s0.Add(self.main_panel, 1, wx.EXPAND)
self.SetSizer(s0)
self.s0 = s0
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.main_sizer.Add(self.plot_panel, 1, wx.EXPAND)
self.main_panel.SetSizer(self.main_sizer)
class PlotPanel(wx.Panel):
def __init__(self, parent, id = -1, dpi = None, **kwargs):
wx.Panel.__init__(self, parent, id=id, **kwargs)
self.figure = mpl.figure.Figure(dpi=dpi, figsize=(2,2))
self.canvas = Canvas(self, -1, self.figure)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas,1,wx.EXPAND)
self.SetSizer(sizer)
sizer.SetMinSize((600, 500))
self.sizer = sizer
def test(plot_panel):
axes = plot_panel.figure.gca()
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
return legend
if __name__=="__main__":
app = wx.PySimpleApp()
frame = PlotFrame()
legend = test(frame.plot_panel)
frame.Fit()
print "legend frame pre show: ", legend.get_frame()
frame.Show(True)
print "legend frame post show:", legend.get_frame()
frame.Fit()
app.MainLoop()
Edit:
For a solution to be useful to me, I would like it to look good when the figure is automatically drawn by the program, so adjustment parameters can be hard coded in the program, or, for example, on a window resize event, but not adjusted by hand for each plot. The main things that I expect to change here are: 1) the lengths of the labels (from, say, 1 to 25 characters), 2) the windows size (usually by the user dragging around the corner, and 3) the number of points and lines. (Also, if it matters, eventually, I'll want to have dates on the bottom axis.)
I've put the legend outside of the axes so that it won't cover any data points, and I'd prefer that it stay to the right of the axes.
I'm using Python 2.6.6, wxPython 2.8.12.1, and matplotlib 1.1.0 and am stuck with these for now.
It is re-sizing correctly, you just didn't tell it to do what you want it to do.
The problem is this line:
axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
Pretty sure the bbox_to_anchor kwarg is over-ridding the loc kwarg and you are pegging the bottom left of the legend to (1.05, 0.5) in axes units. If the axes expands to fill your window, the left edge of the legend will always be 5% of the width axes to the right of the right edge of you axes, hence always out of view.
You either need to put your legend someplace else or shrink your axes (in figure fraction).
option 1 move the legend:
axes.legend(bbox_to_anchor=(0.5, 0.5)) #find a better place this is in the center
option 2 move the axes + resize the figure:
axes.set_position([.1, .1, .5, .8]) # units are in figure fraction
set_position
fig = figure()
axes = fig.add_subplot(111)
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
# adjust the figure size (in inches)
fig.set_size_inches(fig.get_size_inches() * np.array([1.5, 1]), forward=True)
# and the axes size (in figure fraction)
# to (more-or-less) preserve the aspect ratio of the original axes
# and show the legend
pos = np.array(axes.get_position().bounds)
pos[2] = .66
axes.set_position(pos)
option 3: automate option 2
fig = figure() # use plt to set this up for demo purposes
axes = fig.add_subplot(111) # add a subplot
# control paramters
left_pad = .05
right_pad = .05
# plot data
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
# set axes labels
axes.set_xlabel('test x')
axes.set_ylabel('test y')
# make the legend
legend = axes.legend(loc='center left', bbox_to_anchor=(1 + left_pad, 0.5))
# function to 'squeeze' the legend into place
def squeeze_legend(event):
fig.tight_layout()
right_frac = 1 - legend.get_window_extent().width / fig.get_window_extent().width - left_pad - right_pad
fig.subplots_adjust(right=right_frac)
fig.canvas.draw()
# call it so the first draw is right
squeeze_legend()
# use the resize event call-back to make sure it works even if the window is re-sized
fig.canvas.mpl_connect('resize_event', squeeze_legend)