Accidental overlay of graphs in matplotlib - python

It looks like the datapoints in the first graph accidentally overlays the second graph. The code I'm running is being run several times and it when I first have a short period and the second time I run it I have a longer period while the datapoints in the short period is also part of the longer period.
So is there a way to clean the plot before you start building a graph?
You can see the code for building the graph here:
def create_graph(self, device):
# 800 and 355 pixels.
ticks = 5
width = 8
height = 3.55
dpi = 100
bgcolor = '#f3f6f6'
font = {
'size': 16,
'family': 'Arial'
}
plt.rc('font', **font)
# size of figure and setting background color
fig = plt.gcf()
fig.set_size_inches(width, height)
fig.set_facecolor(bgcolor)
# axis color, no ticks and bottom line in grey color.
ax = plt.axes(axisbg=bgcolor, frameon=True)
ax.xaxis.set_ticks_position('none')
ax.spines['bottom'].set_color('#aabcc2')
ax.yaxis.set_ticks_position('none')
# removing all but bottom spines
for key, sp in ax.spines.items():
if key != 'bottom':
sp.set_visible(False)
# setting amounts of ticks on y axis
yloc = plt.MaxNLocator(ticks)
ax.yaxis.set_major_locator(yloc)
x_no_ticks = 8
# Deciding how many ticks we want on the graph
locator = AutoDateLocator(maxticks=x_no_ticks)
formatter = AutoDateFormatter(locator)
# Formatter always chooses the most granular since we have granular dates
# either change format or round dates depending on how granular
# we want them to be for different date ranges.
formatter.scaled[1/(24.*60.)] = '%d/%m %H:%M'
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(formatter)
# turns off small ticks
plt.tick_params(axis='x',
which='both',
bottom='on',
top='off',
pad=10)
# Can't seem to set label color differently, changing tick_params color changes labels.
ax.xaxis.label.set_color('#FFFFFF')
# setting dates in x-axis automatically triggers use of AutoDateLocator
x = [datetime.fromtimestamp(point['x']) for point in device['data']]
y = [point['y'] for point in device['data']]
plt.plot(x, y, color='#53b4d4', linewidth=2)
# pick values for y-axis
y_ticks_values = np.array([point['y'] for point in device['data']])
y_ticks = np.linspace(y_ticks_values.min(), y_ticks_values.max(), ticks)
y_ticks = np.round(y_ticks, decimals=2)
plt.yticks(y_ticks, [str(val) + self.extract_unit(device) for val in y_ticks])
# plt.ylim(ymin=0.1) # Only show values of a certain threshold.
plt.tight_layout()
buf = io.BytesIO()
plt.savefig(buf,
format='png',
facecolor=fig.get_facecolor(),
dpi=dpi)

You have to add plt.close() after plt.savefig(). So the figure won't be caught by the next plt.gcf() call.

Related

Legend position for figures with variable size

My plot function creates horizontal bars per year for data with different size. I have to change the figure size for each set of subplots.
I need to place my two legends on lower center of each figure below the x axis label. The positions need to vary depending on the figure size and remain consistent. So for all produced figures, the legends would look like this figure.
Find a snippet of my dataframe here. I have tried to simplify the code as much as I could and I know the plot is missing some element, but I just want to get to my question's answer, not to create a perfect plot here. I understand probably I need to create a variable for my anchor bounding box but I don't know how. Here is my code:
def plot_bars(data,ax):
""" Plots a single chart of work plan for a specific routeid
data: dataframe with section length and year
Returns: None"""
ax.barh(df['year'], df['sec_len'] , left = df['sec_begin'])
ax.set_yticklabels('')
def plot_fig(df):
# Draw the plots
ax_set = df[['routeid','num_bars']].drop_duplicates('routeid')
route_set = ax_set['routeid'].values
h_ratios = ax_set['num_bars'].values
len_ratio = h_ratios.sum()/BARS_PER_PAGE # Global constant set to 40 based on experiencing
fig, axes = plt.subplots(len(route_set), 1, squeeze=False, sharex=True
, gridspec_kw={'height_ratios':h_ratios}
, figsize=(10.25,7.5*len_ratio))
for i, r in enumerate(route_set):
plot_bars(df[df['routeid']==r], axes[i,0])
plt.xlabel('Section length')
## legends
fig.legend(labels=['Legend2'], loc=8, bbox_to_anchor=(0.5, -0.45))
fig.legend( labels=['Legend1'], loc = 8, bbox_to_anchor=(0.5, -0.3))
## Title
fig.suptitle('title', fontsize=16, y=1)
fig.subplots_adjust(hspace=0, top = 1-0.03/len_ratio)
for df in df_list:
plot_fig(df)
The problem is when the figure size changes, the legends move as in these pictures:
here
here
I think the problem boils down to having the correct relative position with respect to the xlabel, so are right that you need to calculate the bbox_to_anchor using the position of the xlabel and the height/width of the axes. Something like this:
fig, (ax, ax1) = plt.subplots(nrows=2, figsize=(5, 4), gridspec_kw={'height_ratios':[4, 1]})
ax.plot(range(10), range(10), label="myLabel")
ax.set_xlabel("xlabel")
x, y = ax.xaxis.get_label().get_position() # position of xlabel
h, w = ax.bbox.height, ax.bbox.width # height and width of the Axes
leg_pos = [x + 0 / w, y - 55 / h] # this needs to be adjusted according to your needs
fig.legend(loc="lower center", bbox_to_anchor=leg_pos, bbox_transform=ax.transAxes)
plt.show()

Draw grid line on SecondaryAxis - Matplotlib

The question
I am trying to draw grid lines from the ticks of my SecondaryAxis with
ax2.grid(color=color,linestyle='--')
nothing shows up on the figure, I believe I am in the same situation as for Format SecondaryAxis ticklabels Matplotlib, aren't I ?
However, does anybody have a workaround for the issue without reversing the scales ? I mean by reversing the scale is to have the percentages scale on the main axis and the normal scale on the secondary axis.
The full code
import matplotlib.pyplot as plt
import numpy as np
#generate dummy load duration curve
dur=2500
load = np.random.normal(60,30,dur+1)
load[::-1].sort()
x=range(0,dur+1)
perticks = np.linspace(0,1,11)
xticks = perticks*dur
# get yticks from xticks
yticks = np.interp(xticks, range(0,dur+1), load)
print(yticks)
# create figure object with axe object
fig, ax1 = plt.subplots(figsize=(16, 8))
ax1.plot(x, load)
#create second axis
ax2 = ax1.secondary_yaxis('right')
# label and color of the secondaryaxis
perlabels = ['0%', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%']
color ='tab:blue'
ax2.set_yticks(yticks)
ax2.set_yticklabels(labels=perlabels)
ax2.tick_params(axis='y', color=color, labelcolor=color)
# draw grid lines on the secondaryaxis
ax2.grid(color=color,linestyle='--')
# do the same for x axis
ax3 = ax1.secondary_xaxis('top')
ax3.set_xticks(xticks)
ax3.set_xticklabels(labels=perlabels)
ax3.tick_params(axis='x', color=color, labelcolor=color)
ax3.grid(color=color,linestyle='--')
The output
I did some digging on this topic, and opened an issue on GitHub. Here's what I found out:
The SecondaryAxis is "quite new thing", added in matplotlib 3.1.0. (May 2019). Even the v.3.3.3 docs say that the secondary_xaxis() method is experimental.
The SecondaryAxis inherits from _AxesBase, which is an "implementation detail". It is not supposed (as of v.3.3.3) to work as Axes object, and the SecondaryAxis.grid() is not supposed to draw anything (like _AxesBase.grid() does). Although, I agree it is misleading that there is a non-working method.
Therefore, at the time of writing, .grid() is only assumed to work on primaxy axes.
Making the blue axis primary
Since .grid() only works on non-secondary axis, you make the primary axis blue, and move it to top & right.
Code
# Take the x and y-ticks for transfering them to secondary axis
xticks_orig = ax1.get_xticks()
yticks_orig = ax1.get_yticks()
# Make the primary axis blue since we want to draw grid on it
ax1.xaxis.tick_top()
ax1.yaxis.tick_right()
ax1.set_xticks(xticks)
ax1.set_yticks(yticks)
ax1.set_yticklabels(labels=perlabels)
ax1.set_xticklabels(labels=perlabels)
ax1.tick_params(axis="both", color=color, labelcolor=color)
ax1.grid(color=color, linestyle="--")
# Draw the black secondary axis
ax2 = ax1.secondary_yaxis("left")
ax3 = ax1.secondary_xaxis("bottom")
ax2.set_yticks(yticks_orig)
ax3.set_xticks(xticks_orig)
Adding grid lines manually
You could add the grid lines also manually, like this
xlim = ax1.get_xlim()
for y in ax2.get_yticks():
ax1.plot(xlim, (y, y), ls="--", color=color, lw=0.5)
ax1.set_xlim(xlim)
ylim = ax1.get_ylim()
for x in ax3.get_xticks():
ax1.plot((x, x), ylim, ls="--", color=color, lw=0.5)
ax1.set_ylim(ylim)
The output would look like this:
The difference here is now we draw lines on the figure which look like the grid lines.
To add on np8's answer, you can also use axvline to draw the lines. This has the advantage that you do not need to keep track of the y limits manually:
for x in ax2.get_xticks():
ax1.axvline(x, color=color, zorder=-1, linestyle="--", linewidth=0.5)
Note also that you will need to appropriately transform the x-coordinate to match the transform you do from ax1 to ax2.
Also, in my case I first had to render the canvas in order for the tick labels to be generated:
fig1.canvas.draw()

Changing the size of labels of plots in python

I am using GetDist for plotting contours in jupyter. I am wondering how to change the size of numbers in axes and labels of parameters.
There are some lines in the code containing labels as follows:
a,b,c = np.genfromtxt('data/data.txt',unpack=True)
names = ['H','M','z']
labels = ['H','M','z']
samples0 = MCSamples(samples=[a,b,c],names = names, labels = labels)
g.triangle_plot([samples0],['H','M','z'],legend_labels=['Summation of data'], legend_loc='upper right',filled=True)
The problem is when the number of parameter goes up, the plot should be smaller to placed in a printed paper and then we cannot see numbers and parameters' labels.
Thank you
I found the answer which is tricky
g.settings.axes_fontsize = 20
g.settings.lab_fontsize = 30
g.settings.x_label_rotation=47
g.settings.legend_fontsize = 40
by the use of g.setting in GetDist we can customize the plots.
you can use the plot.legend(loc=2, prop={'size': 6}) to increase the legend size This takes a dictionary of keywords corresponding to matplotlib.font_manager.FontProperties properties. more about legends
1). if you want to increase the size of the plotting data according to x values this would be helpful.
# yvalues is the y value list
widthscale = len(yvalues)/4
figsize = (8*widthscale,6) # fig size in inches (width,height)
figure = pylab.figure(figsize = figsize) # set the figsize
if you want increase them without dynamically you can use plot.rc function
eg.
import matplotlib.pyplot as plt
SMALL_SIZE = 8
MEDIUM_SIZE = 10
BIGGER_SIZE = 12
plt.rc('font', size=SMALL_SIZE) # controls default text sizes
plt.rc('axes', titlesize=SMALL_SIZE) # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title
2).second option would be
plt.rcParams["axes.labelsize"] = 22

or directly control the size of the label
ax.set_xlabel("some label", fontsize=22)
To control the legend's fontsize you can use rcParams
plt.rcParams["legend.fontsize"] = 22

or directly specify the size in the legend
ax.legend(fontsize=22)
You can change the font size of the labels to adjust them so they are more visible. If you can edit your question to include an MCVE by adding some dummy data and your plotting code, it will be much easier to provide more specific help.

How do I make my colour bar for Cartopy have a specific range set by me?

I am comparing various missions and I want the colour bar to have a max and min set by me. I am not sure how to do this, any help with this? The missions themselves will keep within a range but I want to set it, so it is easily comparable. Some missions are different and some arent so I dont want the computer to set the max and min automatically.
crs_latlon = ccrs.PlateCarree()
def make_plot(projection_name, projection_crs):
ax = plt.axes(projection=projection_crs)
#ax.set_extent((-65.0, -58, 40, 47.7), crs=crs_latlon)
ax.set_extent((-65.0, -62, 43, 45.5), crs=crs_latlon)
#Add coastlines and meridians/parallels (Cartopy-specific).
plt.gca().coastlines('10m')
ax.gridlines(crs=crs_latlon, linestyle='-')
# Add a title, legend, and display.
ax.set_title(''.join(("Mission #13: Attenuation Coeffiecient - ",
projection_name)))
cb = plt.scatter(avglonlist, avglatlist, c=klist, cmap=coolwarm)
plt.colorbar(cb, cmap=coolwarm, orientation='vertical',ticklocation='auto')
#plt.colorbar.ColorbarBase(ax=ax, cmap = coolwarm, orientation='vertical', ticklocation='auto',
# norm=plt.colors.Normalize(vmin=0, vmax=1))
iplt.show()
def main():
# Demonstrate with two different display projections.
make_plot('Equidistant Cylindrical', ccrs.PlateCarree())
graph_my_latandk(avglatlist,klist)
if __name__ == '__main__':
main()
I guess you have to create your own color bar like here:
import matplotlib.pylab as plt
from matplotlib import colorbar, colors
fig = plt.figure(figsize=(8, 3))
ax = fig.add_axes([.05, .05, .05, .7]) # position of colorbar
cbar = colorbar.ColorbarBase(ax, cmap=plt.get_cmap('coolwarm'),
norm=colors.Normalize(vmin=-.5, vmax=1.5)) # set min, max of colorbar
cbar.set_clim(-.5, .5) # set limits of color map
plt.show()
vmin, vmax allow to set limits of colorbar but clim allow to set limits of color map.
If you pass vmin and vmax to scatter, you can set the colour range of the scatter plot and the colourbar will bet set accordingly.
e.g.
cb = plt.scatter(avglonlist, avglatlist, c=klist, cmap=coolwarm, vmin=-4, vmax=2)
plt.colorbar(cb, cmap=coolwarm, orientation='vertical',ticklocation='auto')

Adjusting tick settings on Seaborn heatmap

I've been trying to adjust the tick settings for a heat map through several different methods with no success. The only method that actually changes the settings of the plot is plt.xticks(np.arange(217, 8850, 85)) but even when using several different intervals for this method the data is skewed greatly to the right.
When the tick labels aren't clumped together (for example using plt.xticks(np.arange(217, 8850, 500))) the last tick mark on the end of the axis is no where near the 8850 max I need to show all the data.
I'm trying to adjust these tick settings on both the x and y in order to view the full range of data (Xmax: 8848 Xmin: 7200, Ymax: 8848 Ymin:217) with intervals that allow the tick labels to be readable.
Images of Heatmap:
First image is with plt.xticks(np.arange(217, 8850, 500)):
Second image is with plt.xticks(np.arange(217, 8850, 85)):
Third is original Heatmap:
color = 'seismic'
success_rate = (m['Ascents'] / ((m['Ascents']) + (m['Failed_Attempts'])))*100
success_rate.fillna(0).astype(float)
mm['success_rate'] = success_rate
mm['success_rate'].round(2)
vm = mm.pivot("Height(m)", "Prominence(m)", "success_rate")
cPreference = sns.heatmap(vm, vmax = 100, cmap = color, cbar_kws= {'label': 'Success Rate of Climbs (%)'})
cPreference = cPreference.invert_yaxis()
"""Methods I've Tried"""
plt.xticks(np.arange(217, 8850, 1000)) """<< Only line that actually makes visible changes but data is skewed greatly"""
cPreference.xaxis.set_ticks(np.arange(mm["Height(m)"].min(), mm["Height(m)"].max(), (mm["Height(m)"].max() - \
mm["Height(m)"].min()) / 10))
cPreference.yaxis.set_ticks(np.arange(mm["Prominence(m)"].min(), mm["Prominence(m)"].max(), (mm["Prominence(m)"].max() \
- mm["Prominence(m)"].min()) / 10))
sns.set_style("ticks", {"xtick.major.size" : 8, "ytick.major.size" : 8})
plt.title("What is a good Mountain to Climb?")
sns.plt.show()
cPreference = sns.heatmap(vm, vmax = 100, cmap = color, >>> xticklabels = 10, yticklabels = 5 <<<, cbar_kws={'label': 'Success Rate of Climbs (%)'})
By setting xticklabels or yticklabels equal to an integer it will still plot the same column but it will only display every nth item in that column.
you can specify the behavior of the tick positioning manually by setting a custom tick locator, for example
from matplotlib import ticker
tick_locator = ticker.MaxNLocator(10)
ax.xaxis.set_major_locator(tick_locator)
here is the link to the documentation for the many options

Categories