Matplotlib: changing datetime ticks makes plot disappear - python

im having serious trouble modifying how and which x-axis labels are presented in my plot.
I have a datetime index and want to reduce the number of xticks been shown and remove the year from it. Should be simple, right?! But, for some reason, the plot disappears after i set major formatter and locator. Here is a working example:
import datetime
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import pandas as pd
teste = pd.DataFrame(index=pd.date_range('2019-01-01','2019-12-31',freq='2D'),columns=['A','B','C'])
teste['A']=.4
teste['B']=.5
teste['C']=.1
for col in teste.columns:
variation = np.random.rand(len(teste))
teste[col]+=variation/10.0
teste['total']=teste.sum(axis=1)
for col in teste.columns:
teste[col]/=teste['total']
ax = plt.figure(figsize=(24,10)).add_axes([0,0,1,1])
teste.drop('total',axis=1).plot(kind='bar',stacked='True',ax=ax,width=1,colormap='coolwarm')
ax.tick_params(labelsize=14)
ax.set_xlabel('')
ax.set_title('Teste',fontsize=28)
ax.set_ylabel('Share (%)',fontsize=22)
ax.tick_params(axis='both',labelsize=20)
ax.legend(bbox_to_anchor=(1.05, 1),fontsize=22, loc='upper left', borderaxespad=0.)
As you can see, the xticks are unreadable. But when i try to format:
ax = plt.figure(figsize=(24,10)).add_axes([0,0,1,1])
teste.drop('total',axis=1).plot(kind='bar',stacked='True',ax=ax,width=1,colormap='RdBu')
ax.xaxis_date()
ax.xaxis.set_major_locator(mdates.DayLocator(interval=10))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d/%m"))
ax.xaxis.set_minor_formatter(mdates.DateFormatter("%d/%m"))
ax.set_xlim(teste.index[0],teste.index[-1])
ax.margins(0)
ax.tick_params(labelsize=14)
ax.set_xlabel('')
ax.set_title('Teste',fontsize=28)
ax.set_ylabel('Share (%)',fontsize=22)
ax.tick_params(axis='both',labelsize=20)
ax.legend(bbox_to_anchor=(1.05, 1),fontsize=22, loc='upper left', borderaxespad=0.)
The plot vanishes. What am i doing wrong? I`ve tried everything. plt.MaxNLocator(N=10) also doesn't work. It spreads the first N points all over the axis, completely disregarding where it actually should be.
Any help would be greatly appreciated.
Thanks in advance,
Edit: #Trenton McKinney:
Removing ax.set_xlim(teste.index[0],teste.index[-1]) makes the plot appear but without the xticks.

I used the method shown on the Matplotlib website: Stacked Bar Graph
With a bar plot, every bar has a location [0, ..., n]
ind selects the locs to label
dates are the names of the selected ticks
ax = plt.figure(figsize=(24,10)).add_axes([0,0,1,1])
teste.drop('total',axis=1).plot(kind='bar',stacked='True',ax=ax,width=1,colormap='RdBu')
# locations of tick marks to label
ind = np.arange(0, len(teste.index)+1, 10)
# label for ticks
dates = teste.index.date[0::10] # %y-%m-%d format
# dates = teste.index.strftime('%d/%m')[0::10] # %d/%m format
# set the xticks
plt.xticks(ind, dates)
# only used to show locs and labels if you're having trouble
# locs, labels = plt.xticks()
# label_t = [x.get_text() for x in labels]
# formatting
ax.margins(0)
ax.tick_params(labelsize=14)
ax.set_xlabel('')
ax.set_title('Teste',fontsize=28)
ax.set_ylabel('Share (%)',fontsize=22)
ax.tick_params(axis='both',labelsize=20)
ax.legend(bbox_to_anchor=(1.05, 1),fontsize=22, loc='upper left', borderaxespad=0.)
plt.show()
Optionally
fig, ax = plt.subplots(figsize=(20, 8))
p1 = ax.bar(teste.index, teste.A)
p2 = ax.bar(teste.index, teste.B, bottom=teste.A)
p3 = ax.bar(teste.index, teste.C, bottom=teste.A+teste.B)
ax.xaxis_date()
ax.xaxis.set_major_locator(mdates.DayLocator(interval=10))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d/%m"))
ax.set_xlim(teste.index[0],teste.index[-1])
plt.xticks(rotation=45, ha='right') # or (rotation=90, ha='center')
plt.show()

Related

Changing order of items in combined sns python graph

I need a plot graph with three different axes. So far, I adapted one script I have found on the web. Unfortunately, I have noticed that my data are not ordered properly. At first, I sort my dataframe by a column named 'a', but in the final figure, only this column seems to be sorted. I would like to order all of them. When I print dataframe after sorting everything seems to be fine.
I will really appreciate any help.
Here is my dataframe after I have sorted it based on column 'a' and here is final graph, where only area of catchments is sorted, but names of catchments, mean elevation and mean slope of catchments are not sorted properly.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
from matplotlib.ticker import PercentFormatter
data1 = 'path to my .xlsx file'
df = pd.read_excel(data1, 'catchments_basic')# loading data
df_sorted = df.sort_values(by=['a'], ascending=False)
def make_patch_spines_invisible(ax):
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
sns.set(style="white", rc={"lines.linewidth": 3})
fig, ax = plt.subplots(figsize=(15,10))
fig.subplots_adjust(right=0.75)
ax1 = ax.twinx()
ax2 = ax.twinx()
# Offset the right spine of par2. The ticks and label have already been
# placed on the right by twinx above.
ax2.spines["right"].set_position(("axes", 1.1))
# Having been created by twinx, par2 has its frame off, so the line of its
# detached spine is invisible. First, activate the frame but make the patch
# and spines invisible.
make_patch_spines_invisible(ax2)
# Second, show the right spine.
ax2.spines["right"].set_visible(True)
host = sns.barplot(x=df_sorted['Catchment'],
y=df_sorted["a"],
color='#004488',
label="area",
ax=ax)
par1 = sns.lineplot(x=df_sorted['Catchment'],
y=df_sorted["b"],
color='r',
marker="o",
label="mean elevation",
ax=ax1)
par2 = sns.lineplot(x=df_sorted['Catchment'],
y=df_sorted["c"],
color='g',
marker="o",
label="mean slope",
ax=ax2)
host.set_xlim(-1, 20)
host.set_ylim(0, 1000)
par1.set_ylim(0, 1000)
par2.set_ylim(0, 100)
host.set_xlabel("river catchment")
host.set_ylabel("area [$km^2$]")
par1.set_ylabel("mean elevation [m n. m.]")
par2.set_ylabel("mean slope [%]")
host.yaxis.label.set_color(color='#004488')
par1.yaxis.label.set_color(color='r')
par2.yaxis.label.set_color(color='g')
tkw = dict(size=4, width=1.5)
host.tick_params(axis='y', colors='#004488', **tkw)
host.tick_params(axis='x', colors='black', **tkw)
par1.tick_params(axis='y', colors='r', **tkw)
par2.tick_params(axis='y', colors='g', **tkw)
host.tick_params(axis='povodie', **tkw)
ax2.yaxis.set_major_formatter(PercentFormatter(decimals=0))
for tick in host.get_xticklabels():
tick.set_rotation(45)
host.set_title('Area, mean altitude and mean slope in selected river catchments', family='Arial', size=12, weight='bold')
host.grid(linestyle="dotted", color='black')
host.legend(loc='upper left')
par1.legend(loc='upper center')
par2.legend(loc='upper right')
save_results_to = 'path where I want to save figure
plt.tight_layout(pad=2)
plt.savefig(save_results_to + 'basic_characteristics_bar_line_combination.png', dpi = 300)
plt.show()
print ('done')
you should change sort parameter in sns.lineplot to False

Why is Seaborn plotting two legends, how do I remove one and fix the other?

When I run the code shown below I get a figure containing 2 legends. I can't figure out why two are being plotted and I havent been able to remove one of them. My aim is to keep the legend that is outside of the figure, remove the one thats inside the figure and also somehow stop the weird cropping that is cutting off the right side of the legend outside the figure.
I had a previous question asking something similar, but that issue was solved by using seaborns scatterplot instead of the relplot. Sadly neither of the answers that worked in that question work here. If this problem is arising out of an "uncoventional" way of plotting the type of figure I'm trying to make, then please let me know. Doing it properly is better than hacking your way to the solution...
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
#setup
sns.set(font_scale=2)
sns.set_context('poster')
#figure and axes
fig = plt.figure(figsize=(20,20))
axs = {i:fig.add_subplot(330+i) for i in range(1,10)}
#create random data
r = np.random.randint
N=10
df = pd.DataFrame(columns=['No.','x1','x2','x3','y1','y2','y3'])
for i in range(N):
df.loc[i] = i+1,r(50,high=100),r(50,high=100),r(50,high=100),r(50,high=100),r(50,high=100),r(50,high=100)
#create axes labels
x_labels = ['x1','x2','x3']
y_labels = ['y1','y2','y3']
xy_labels = [(x,y) for y in y_labels for x in x_labels ]
#plot on axes
for i,(x_label,y_label) in enumerate(xy_labels):
if i ==0:#if statement so only one of the plots has legend='full'
a = sns.scatterplot(
data=df,
x=x_label,
y=y_label,
legend='full', #create the legend
ax=axs[i+1],
hue='No.',
palette=sns.color_palette("hls", N)
)
fig.legend(bbox_to_anchor=(1, 0.7), loc=2, borderaxespad=0.) #Move the legend outside the plot
a.legend_.remove() #attempt to remove the legend
else:
a = sns.scatterplot(
data=df,
x=x_label,
y=y_label,
legend=False,
ax=axs[i+1],
hue='No.',
palette=sns.color_palette("hls", N)
)
#remove axes labels from specific plots
if i not in [0,3,6]: axs[i+1].set_ylabel('')
if i not in [6,7,8]: axs[i+1].set_xlabel('')
#add line plots and set limits
for ax in axs.values():
sns.lineplot(x=range(50,100),y=range(50,100), ax=ax, linestyle='-')
ax.set_xlim([50,100])
ax.set_ylim([50,100])
fig.tight_layout()
You can add legend=False in the last part of your code.
#setup
sns.set(font_scale=2)
sns.set_context('poster')
#figure and axes
fig = plt.figure(figsize=(20,20))
axs = {i:fig.add_subplot(330+i) for i in range(1,10)}
#create axes labels
x_labels = ['x1','x2','x3']
y_labels = ['y1','y2','y3']
xy_labels = [(x,y) for y in y_labels for x in x_labels ]
#plot on axes
for i,(x_label,y_label) in enumerate(xy_labels):
if i ==0:#if statement so only one of the plots has legend='full'
a = sns.scatterplot(
data=df,
x=x_label,
y=y_label,
legend='full', #create the legend
ax=axs[i+1],
hue='No.',
palette=sns.color_palette("hls", N)
)
fig.legend(bbox_to_anchor=(1, 0.7), loc=2, borderaxespad=0.) #Move the legend outside the plot
a.legend_.remove() #attempt to remove the legend
else:
a = sns.scatterplot(
data=df,
x=x_label,
y=y_label,
legend=False,
ax=axs[i+1],
hue='No.',
palette=sns.color_palette("hls", N)
)
#remove axes labels from specific plots
if i not in [0,3,6]: axs[i+1].set_ylabel('')
if i not in [6,7,8]: axs[i+1].set_xlabel('')
#add line plots and set limits
for ax in axs.values():
sns.lineplot(x=range(50,100),y=range(50,100), ax=ax, linestyle='-', legend=False)
ax.set_xlim([50,100])
ax.set_ylim([50,100])
fig.tight_layout()
Result:

plot changes not affecting all subplots [duplicate]

Im trying to plot a scatter matrix. I'm building on the example given in this thread Is there a function to make scatterplot matrices in matplotlib?. Here I have just modified the code slightly to make the axis visible for all the subplots. The modified code is given below
import itertools
import numpy as np
import matplotlib.pyplot as plt
def main():
np.random.seed(1977)
numvars, numdata = 4, 10
data = 10 * np.random.random((numvars, numdata))
fig = scatterplot_matrix(data, ['mpg', 'disp', 'drat', 'wt'],
linestyle='none', marker='o', color='black', mfc='none')
fig.suptitle('Simple Scatterplot Matrix')
plt.show()
def scatterplot_matrix(data, names, **kwargs):
"""Plots a scatterplot matrix of subplots. Each row of "data" is plotted
against other rows, resulting in a nrows by nrows grid of subplots with the
diagonal subplots labeled with "names". Additional keyword arguments are
passed on to matplotlib's "plot" command. Returns the matplotlib figure
object containg the subplot grid."""
numvars, numdata = data.shape
fig, axes = plt.subplots(nrows=numvars, ncols=numvars, figsize=(8,8))
fig.subplots_adjust(hspace=0.05, wspace=0.05)
for ax in axes.flat:
# Hide all ticks and labels
ax.xaxis.set_visible(True)
ax.yaxis.set_visible(True)
# # Set up ticks only on one side for the "edge" subplots...
# if ax.is_first_col():
# ax.yaxis.set_ticks_position('left')
# if ax.is_last_col():
# ax.yaxis.set_ticks_position('right')
# if ax.is_first_row():
# ax.xaxis.set_ticks_position('top')
# if ax.is_last_row():
# ax.xaxis.set_ticks_position('bottom')
# Plot the data.
for i, j in zip(*np.triu_indices_from(axes, k=1)):
for x, y in [(i,j), (j,i)]:
axes[x,y].plot(data[x], data[y], **kwargs)
# Label the diagonal subplots...
for i, label in enumerate(names):
axes[i,i].annotate(label, (0.5, 0.5), xycoords='axes fraction',
ha='center', va='center')
# Turn on the proper x or y axes ticks.
for i, j in zip(range(numvars), itertools.cycle((-1, 0))):
axes[j,i].xaxis.set_visible(True)
axes[i,j].yaxis.set_visible(True)
fig.tight_layout()
plt.xticks(rotation=45)
fig.show()
return fig
main()
I cant seem to be able to rotate the x-axis text of all the subplots. As it can be seen, i have tried the plt.xticks(rotation=45) trick. But this seems to perform the rotation for the last subplot alone.
Just iterate through the axes tied to the figure, set the active axes to the iterated object, and modify:
for ax in fig.axes:
matplotlib.pyplot.sca(ax)
plt.xticks(rotation=90)
plt only acts on the current active axes. You should bring it inside your last loop where you set some of the labels visibility to True:
# Turn on the proper x or y axes ticks.
for i, j in zip(range(numvars), itertools.cycle((-1, 0))):
axes[j,i].xaxis.set_visible(True)
axes[i,j].yaxis.set_visible(True)
for tick in axes[i,j].get_xticklabels():
tick.set_rotation(45)
for tick in axes[j,i].get_xticklabels():
tick.set_rotation(45)
for ax in fig.axes:
ax.tick_params(labelrotation=90)

How to shrink plot on x-axis in matplotlib?

I've seen a couple examples but the plots are constructed differently and I don't see how to make the syntax work. Here's my code:
pdf_file = PdfPages(sys.argv[1].split('.')[0] + "_graphs.pdf")
for i in range(0, len(list_of_data)):
biorep = int(list_of_figure_key[i].split('.')[1])
construct = int(list_of_figure_key[i].split('.')[0].split('_')[1])
plot(time, list_of_data[i], color=color_dict[construct], linestyle=linestyle_dict[biorep], label=list_of_figure_key[i] )
xlabel('time (hours)', fontsize=9)
ylabel(ReadType, fontsize=9)
xlim(min(time),max(time))
legend(fontsize=8, loc='center left', bbox_to_anchor=(1, .5))
pdf_file.savefig()
It produces a beautiful figure but the legend is much too long and goes off the edge of the page. I'd like to shrink the plot on the x-axis so the legend will fit as a 2-column legend.
Figure can be seen here: http://i.imgur.com/mvgzIhj.jpg
Thanks in advance!
You can make a two-column legend using the ncol legend attribute. You can shrink the width of the plot by drawing the axis on the plot and fixing its size:
from matplotlib import pyplot as plt
fig = plt.figure() # initialize figure
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # add axis
To make this work with your code, something like this should work:
# import pyplot
from matplotlib import pyplot as plt
# set up filename to save it
pdf_file = PdfPages(sys.argv[1].split('.')[0] + "_graphs.pdf")
# set up axis object
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
# plot your data
for i in range(0, len(list_of_data)):
biorep = int(list_of_figure_key[i].split('.')[1])
construct = int(list_of_figure_key[i].split('.')[0].split('_')[1])
ax.plot(time, list_of_data[i], color=color_dict[construct],
linestyle=linestyle_dict[biorep], label=list_of_figure_key[i] )
# modify axis limits and legend
ax.set_xlabel('time (hours)', fontsize=9)
ax.set_ylabel(ReadType, fontsize=9)
ax.set_xlim(min(time),max(time))
ax.legend(fontsize=8, loc='upper left', bbox_to_anchor=(1, .5), ncol=2)
# save final figure
plt.savefig(pdf_file)
In your code, you were remaking the legend, the limits and the legend at each iteration of the for-loop, as well as saving and then overwriting the pdf image. This isn't necessary -- you can just do it once at the end.
For more legend tips, this post is handy. This one is also helpful.

matplotlib: empty area appears on the figure when plotting many subplots

I am plotting a few dozen of subplots with matplotlib. At the bottom of the figure, between the last row of plots and the legend, an empty area appears. The empty area grows larger when I add more subplots. Any idea how to get rid of this empty space?
Here's the working code:
import textwrap
import matplotlib.pyplot as plt
from collections import OrderedDict
rlen = 31 # number of plots
figsize = (11, 3) # matrix of subplots
fig = plt.figure(figsize=(figsize[1]*4, figsize[0]*4))
plots = []
for f_ind in range(rlen):
plots.append(fig.add_subplot(figsize[0], figsize[1], f_ind))
fig.subplots_adjust(wspace=0.5, hspace=0.5)
for ax in plots:
atitle = 'Aaa bbb ccc ' * 10
ax.set_title('\n'.join(textwrap.wrap(atitle, 45)), fontsize=10)
ax.plot(range(10), range(10), 'o', color='red', label='LABEL_1')
revlist = list(reversed(range(10)))
ax.plot(revlist, range(10), 'o', color='blue', label='LABEL_2')
ax.set_xlabel('Train set size', fontsize=9)
ax.set_ylabel('Accuracy (%)', fontsize=9)
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
lgd = fig.legend(by_label.values(), by_label.keys(), loc='lower center', ncol=4)
fig.savefig('ZZZ.png', dpi=fig.dpi, bbox_extra_artists=(lgd,), bbox_inches='tight')
You can also view the example result here. Thanks!
You can use the bottom keyword for subplots_adjust to set the point to which the subplots should be drawn with respect to the figure.
So for example:
fig.subplots_adjust(wspace=0.5, hspace=0.5, bottom=0)
The legend will then be close to the 'lowest' subplots. Here is a crop from the bottom of the resulting image:
Besides bottom, there are also left, right and top keywords.

Categories

Resources