I am experimenting with matplotlib at the moment. Some time ago I used Excel VBA code to produce images such as the one attached.
You will notice it is not presented in a scientific/research style but rather as if produced by a school-student on graph paper - with three different grid-line styles.
Is there a fairly straightforward way to achieve this sort of thing with matplotlib?
Yes, you can use spines for this.
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
import numpy as np
fig = plt.figure(1)
ax = fig.add_subplot(111)
# set up axis
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# draw curve
x = np.arange(-2.5,2.5,0.01)
line, = ax.plot(x, x**2)
#set bounds
ax.set_ybound(-1,7)
# create grid
#ax.xaxis.set_major_locator(MultipleLocator(1))
#ax.xaxis.set_minor_locator(MultipleLocator(0.2))
#ax.yaxis.set_major_locator(MultipleLocator(1))
#ax.yaxis.set_minor_locator(MultipleLocator(0.2))
#ax.xaxis.grid(True,'minor')
#ax.yaxis.grid(True,'minor')
#ax.xaxis.grid(True,'major',linewidth=2)
#ax.yaxis.grid(True,'major',linewidth=2)
#adjust grid on the 2s
#for idx,loc in enumerate(ax.xaxis.get_majorticklocs()):
#if loc !=0 and loc % 2 == 0: ax.get_xgridlines()[idx].set_c('r')
#for idx,loc in enumerate(ax.yaxis.get_majorticklocs()):
#if loc !=0 and loc % 2 == 0: ax.get_ygridlines()[idx].set_c('r')
## THIS IS THE EDIT
ax.xaxis.set_minor_locator(MultipleLocator(0.2))
ax.yaxis.set_minor_locator(MultipleLocator(0.2))
ax.xaxis.grid(True,'minor',linewidth=2)
ax.yaxis.grid(True,'minor',linewidth=2)
minor_grid_lines = [tick.gridline for tick in ax.xaxis.get_minor_ticks()]
for idx,loc in enumerate(ax.xaxis.get_minorticklocs()):
if loc % 2.0 == 0: minor_grid_lines[idx].set_c('r' )
elif loc % 1.0 == 0: minor_grid_lines[idx].set_c('g' )
else: minor_grid_lines[idx].set_c( 'b' )
plt.show()
This is a modified version of the accepted answer above.
Maybe somebody will find this helpful
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
import numpy as np
from matplotlib.ticker import FormatStrFormatter
_fontsize_legend = 10
_fontsize = 15
DP = 2
fig = plt.figure(figsize=(12, 12), dpi=100, facecolor='w', edgecolor='k')
##fig = plt.figure()
fig.canvas.draw()
ax = plt.gca()
# set up axis
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# draw curve
x = np.arange(-2.5,2.5,0.01)
line, = ax.plot(x, x**2)
#set bounds
ax.set_ybound(-1,7)
## THIS IS THE EDIT
ax.xaxis.set_major_locator(MultipleLocator(1/4))
ax.yaxis.set_major_locator(MultipleLocator(1/4))
ax.xaxis.grid(True,'major',linewidth=2/DP,linestyle='-',color='#d7d7d7',zorder=0)
ax.yaxis.grid(True,'major',linewidth=2/DP,linestyle='-',color='#d7d7d7')
ax.xaxis.set_minor_locator(MultipleLocator( (1/4) / 5 ))
ax.yaxis.set_minor_locator(MultipleLocator( (1/4) / 5 ))
ax.xaxis.grid(True,'minor',linewidth=0.5/DP,linestyle='-',color='#d7d7d7')
ax.yaxis.grid(True,'minor',linewidth=0.5/DP,linestyle='-',color='#d7d7d7')
ax.set_axisbelow(True)
ax.set_aspect('equal')
##ax.axhline(linewidth=0)
##ax.axvline(linewidth=0)
ax.xaxis.set_major_formatter(FormatStrFormatter('%i'))
xticks = ax.xaxis.get_major_ticks()
for i,l in enumerate(xticks):
if not (i - 1) % 4 == 0:
xticks[i].label1.set_visible(False)
else:
xticks[i].label1.set_fontsize(_fontsize)
ax.yaxis.set_major_formatter(FormatStrFormatter('%i'))
yticks = ax.yaxis.get_major_ticks()
for i,l in enumerate(yticks):
if not (i - 1) % 4 == 0:
yticks[i].label1.set_visible(False)
else:
yticks[i].label1.set_fontsize(_fontsize)
figManager = plt.get_current_fig_manager()
figManager.window.showMaximized()
plt.show()
Just another thought - I have also tried to do it all with the minor gridlines (apart from anything else it will help my understanding), but it's not enumerating properly, no doubt due to the get_minorticklocs and ax.get_xgridlines. Sorry, and thanks in advance...
Geddes
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
import numpy as np
fig = plt.figure(1)
ax = fig.add_subplot(111)
# set up axis
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# draw curve
x = np.arange(-2.5,2.5,0.01)
line, = ax.plot(x, x**2)
#set bounds
ax.set_ybound(-1,7)
# create grid
ax.xaxis.set_minor_locator(MultipleLocator(0.2))
ax.yaxis.set_minor_locator(MultipleLocator(0.2))
ax.xaxis.grid(True,'minor',linewidth=2)
ax.yaxis.grid(True,'minor',linewidth=2)
#adjust grid on the 2s
for idx,loc in enumerate(ax.xaxis.get_minorticklocs()):
if loc % 2 == 0: ax.get_xgridlines()[idx].set_color('r')
if loc % 1 == 0: ax.get_xgridlines()[idx].set_color('g')
if loc % 0.2 == 0: ax.get_xgridlines()[idx].set_color('b')
for idx,loc in enumerate(ax.yaxis.get_majorticklocs()):
if loc % 2 == 0: ax.get_ygridlines()[idx].set_c('b')
plt.savefig('spines3.png',dpi=300)
Related
Could you change my code in this way so as to have these 2 plots alongside, means in 1 row and 2 column (subplot nrows=1, ncols=2) ? Currently I have these charts in 2 separate cells, and I want to have them in 1.
my code:
First plot:
from yellowbrick.classifier import (PrecisionRecallCurve)
fig, ax = plt.subplots(figsize=(10, 6))
viz = PrecisionRecallCurve(DecisionTreeClassifier(max_depth=4))
viz.fit(X_train_model_2, y_train_model_2)
print(viz.score(X_test_model_2, y_test_model_2))
viz.ax.set(title="Krzywa precyzja-czułość klasyfikatora drzewa losowego",
xlabel="Czułość",
ylabel="Precyzja")
ax.legend(("Binarna krzywa precyzja-czułość",
"Średnia precyzja = {:0.2f}".format(viz.score(X_test_model_2,y_test_model_2))),
frameon=True,
loc="lower left")
plt.show()
Second plot:
import scikitplot as skplt
fig, ax = plt.subplots(figsize=(10, 6))
y_probas = decision_tree.predict_proba(X_test_model_2)
skplt.metrics.plot_cumulative_gain(y_test_model_2,
y_probas,
ax=ax)
ax.set(title="Krzywa skumulowanych zysków",
xlabel="Odsetek próbek",
ylabel="Zysk")
ax.legend(("Klasa 0",
"Klasa 1",
"Krzywa odniesienia"),
frameon=True,
loc="lower right")
plt.show()
Maybe that helps:
from yellowbrick.classifier.prcurve import PrecisionRecallCurve
import scikitplot as skplt
import numpy as np
import sklearn
import sklearn.tree
import matplotlib.pyplot as plt
#generate some test data
X = np.arange(200)+np.random.normal(0,10,200)
y = np.array([True if (x <100) and (x > 50) else False for x in X])
X = X.reshape(-1,1)
X_train_model_2 = []
y_train_model_2 = []
X_test_model_2 = []
y_test_model_2 = []
X_train_model_2,X_test_model_2,y_train_model_2,y_test_model_2=
sklearn.model_selection.train_test_split(
X, y,
test_size=0.4,
random_state=0)
fig, (ax1, ax2) = plt.subplots(1,2) #1 row, 2 columns
viz = PrecisionRecallCurve(sklearn.tree.DecisionTreeClassifier(max_depth=4),
ax = ax1) #set the axis to plot one (ax1)
decision_tree = viz.fit(X_train_model_2, y_train_model_2)
print(viz.score(X_test_model_2, y_test_model_2))
#Set the attributes for plot one
ax1.set(title="Krzywa precyzja-czułość klasyfikatora drzewa losowego",
xlabel="Czułość",
ylabel="Precyzja")
ax1.legend(("Binarna krzywa precyzja-czułość",
"Średnia precyzja {:0.2f}".format(viz.score(X_test_model_2,y_test_model_2))),
frameon=True,
loc="lower left")
y_probas = decision_tree.predict_proba(X_test_model_2)
skplt.metrics.plot_cumulative_gain(y_test_model_2,
y_probas,
ax=ax2) #set the axis to plot two (ax2)
#Set the attributes for plot two
ax2.set(title="Krzywa skumulowanych zysków",
xlabel="Odsetek próbek",
ylabel="Zysk")
ax2.legend(("Klasa 0",
"Klasa 1",
"Krzywa odniesienia"),
frameon=True,
loc="lower right")
#Show the whole plot
plt.show()
I'm trying to visualize a name co-occurrence matrix. This version works okay:
import pandas as pd
import numpy as np
import string
import matplotlib.pyplot as plt
n = 10
names = ['Long Name ' + suffix for suffix in string.ascii_uppercase[:n]]
df = pd.DataFrame(np.random.randint(0, 100, size=(n,n)),
columns=names, index=names)
fig = plt.figure()
ax = plt.gca()
im = ax.matshow(df, interpolation='none')
fig.colorbar(im)
ax.set_xticks(np.arange(n))
ax.set_xticklabels(names)
ax.set_yticks(np.arange(n))
ax.set_yticklabels(names)
ax.xaxis.set_ticks_position("bottom")
plt.setp(ax.get_xticklabels(), rotation=45,
ha="right", rotation_mode="anchor")
for (i,j), z in np.ndenumerate(df):
if z != 0:
ax.text(j, i, str(z), ha="center", va="center")
ax.set_title("Name Co-Occurrences")
fig.tight_layout()
plt.show()
The problem is that the actual matrix I have is fairly large, so I would like to display the names both on the top and the bottom. I've tried to do so by using twiny:
import pandas as pd
import numpy as np
import string
import matplotlib.pyplot as plt
n = 10
names = ['Long Name ' + suffix for suffix in string.ascii_uppercase[:n]]
df = pd.DataFrame(np.random.randint(0, 100, size=(n,n)),
columns=names, index=names)
fig = plt.figure()
botax = plt.gca()
im = botax.matshow(df, interpolation='none')
fig.colorbar(im)
topax = botax.twiny()
for ax, ha, pos in zip([topax, botax], ["left", "right"], ["top", "bottom"]):
ax.set_xticks(np.arange(n))
ax.set_xticklabels(names)
ax.set_yticks(np.arange(n))
ax.set_yticklabels(names)
ax.xaxis.set_ticks_position(pos)
plt.setp(ax.get_xticklabels(), rotation=45,
ha=ha, va="center", rotation_mode="anchor")
for (i,j), z in np.ndenumerate(df):
if z != 0:
botax.text(j, i, str(z), ha="center", va="center")
botax.set_title("Name Co-Occurrences")
fig.tight_layout()
plt.show()
Unfortunately the top labels aren't aligned correctly and I don't know why:
In order to label both, bottom and top of an axes, there is no need for a twin axes. This may make this all a bit easier. You can instead just turn the bottom and top ticks and labels on, and then rotate and align them separately.
import pandas as pd
import numpy as np
import string
import matplotlib.pyplot as plt
n = 10
names = ['Long Name ' + suffix for suffix in string.ascii_uppercase[:n]]
df = pd.DataFrame(np.random.randint(0, 100, size=(n,n)),
columns=names, index=names)
fig = plt.figure()
ax = plt.gca()
im = ax.matshow(df, interpolation='none')
fig.colorbar(im)
ax.set_xticks(np.arange(n))
ax.set_xticklabels(names)
ax.set_yticks(np.arange(n))
ax.set_yticklabels(names)
# Set ticks on both sides of axes on
ax.tick_params(axis="x", bottom=True, top=True, labelbottom=True, labeltop=True)
# Rotate and align bottom ticklabels
plt.setp([tick.label1 for tick in ax.xaxis.get_major_ticks()], rotation=45,
ha="right", va="center", rotation_mode="anchor")
# Rotate and align top ticklabels
plt.setp([tick.label2 for tick in ax.xaxis.get_major_ticks()], rotation=45,
ha="left", va="center",rotation_mode="anchor")
ax.set_title("Name Co-Occurrences", pad=55)
fig.tight_layout()
plt.show()
You will have to first set the aspect ratio of the upper x-axis to be the same as that of the lower x-axis. How to do this has been answered here. Then you can use y=1.3 to lift the title a bit upward so that it does not overlap with the upper x-axis tick labels.
topax = botax.twiny()
aspect0 = botax.get_aspect()
if aspect0 == 'equal':
aspect0 = 1.0
dy = np.abs(np.diff(botax.get_ylim()))
dx = np.abs(np.diff(botax.get_xlim()))
aspect = aspect0 / (float(dy) / dx)
topax.set_aspect(aspect)
for ax, ha, pos in zip([topax, botax], ["left", "right"], ["top", "bottom"]):
ax.set_xticks(np.arange(n))
.....
.....
botax.set_title("Name Co-Occurrences", y=1.3)
I want to add labels to my plot which consists of sub subplots. Here is what I want (I added the outer labels with GIMP)
And this is what I actually get:
Here is the code that produces the last plot:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
plots = 16
subplots = 9
fig = plt.figure(figsize=(8, 8))
wh_plots = int(np.sqrt(plots))
wh_subplots = int(np.sqrt(subplots))
outer_grid = gridspec.GridSpec(wh_plots, wh_plots, wspace=0.1, hspace=0.1)
for p in range(plots):
inner_grid = gridspec.GridSpecFromSubplotSpec(wh_subplots, wh_subplots, subplot_spec=outer_grid[p], wspace=0.05, hspace=0.05)
for s in range(subplots):
ax = plt.Subplot(fig, inner_grid[s])
ax.imshow(np.random.rand(10,10), cmap="magma", interpolation="none")
ax.set_xticks([])
ax.set_yticks([])
fig.add_subplot(ax)
if (p+1) > 12 and s == 7:
ax.set_xlabel("sub_xlabel")
if (p) % 4 == 0 and s == 3:
ax.set_ylabel("sub_ylabel")
all_axes = fig.get_axes()
plt.show()
My questions:
How can I get the "xlabel" and "ylabel" as seen in the first plot?
Is there a better way to label the subplots (sub_xlabel / sub_ylabel)
compared to what I did?
if (p+1) > 12 and s == 7:
ax.set_xlabel("sub_xlabel")
if (p) % 4 == 0 and s == 3:
ax.set_ylabel("sub_ylabel")
It works, but it doesn't look right.
You can add these lines before plt.show():
fig.text(0.5, 0.04, 'xlabel', ha='center', fontsize=18)
fig.text(0.04, 0.5, 'ylabel', va='center', rotation='vertical', fontsize=18)
I'm trying to plot a polar chart using matplotlib. How can I add ticked scale markers on the perimeter line ?
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(30, 30))
ax = plt.subplot(111, polar=True)
ax.set_rmax(1)
plt.show()
grades, have no ticks
should have markers like this (dismiss the colored data ):
polar plot
I've tried with set_xticks, tickslabel, thethagrid. But I can't find a solution.
Please help.
Seems that there is no radial tick marks implemented in matplotlib (only tick labels). If you don't mind, you might want to think about creating them yourself. For example:
import numpy as np
import matplotlib.pyplot as plt
ax = plt.subplot(111, polar=True)
ax.xaxis.get_gridlines()[2].set_linestyle('-')
# Make ticks
tick_length = 0.5
start_theta = np.pi * 0.5
for i in range(0, 42, 2):
end_r = np.sqrt(i ** 2 + tick_length ** 2)
if i == 0:
end_theta = 0
else:
end_theta = start_theta - np.arctan(tick_length / i)
ax.plot([start_theta, end_theta], [i, end_r], color='k')
ax.set_rmax(40)
ax.set_rticks(range(0, 41, 10))
ax.set_rlabel_position(90)
for t in ax.yaxis.get_major_ticks():
t.label1.set_va('center')
t.label1.set_ha('right')
plt.show()
I am trying to plot several data which, in some cases, occupies the entire plot.
The default option, from version 2, should be 'best', which tries to find the best position to place the legend inside the plot.
Is there a way to extend the option to be able to place the legend outside the plot if the space is insufficient?
Otherwise, is there an option for matplotlib (without taking the max of all the series and add a manual padding) to automatically add an ylim padding and give space to the legend and be placed inside the plot?
The main idea is to avoid manual tweaking of the plots, having several plots to be created automatically.
A simple MWE is in the following:
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import scipy as sc
import matplotlib.pyplot as plt
plt.close('all')
x = sc.linspace(0, 1, 50)
y = sc.array([sc.ones(50)*0.5, x, x**2, (1-x), (1-x**2)]).T
fig = plt.figure('Fig')
ax = fig.add_subplot(111)
lines = ax.plot(x, y)
leg = ax.legend([lines[0], lines[1], lines[2], lines[3], lines[4]],
[r'$\mathrm{line} = 0.5$', r'$\mathrm{line} = x$', r'$\mathrm{line} = x^2$',
r'$\mathrm{line} = 1-x$',r'$\mathrm{line} = 1-x^2$'], ncol=2)
fig.tight_layout()
There is no automatic way to place the legend at "the best" position outside the axes.
inside the plot
You may decide to always leave enough space inside the axes, such that the legend doesn't overlap with anything. To this end you can use ax.margins. e.g.
ax.margins(y=0.25)
will produce 25% margin on both ends of the y axis, enough space to host the legend if it has 3 columns.
You may then decide to always use the same location, e.g. loc="upper center" for a consistent result among all plots.
The drawback of this is that it depends on figure size and that it adds a (potentially undesired) margin at the other end of the axis as well. If you can live with that margin, a way to automatically determine the needed margin would be the following:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms
x = np.linspace(0, 1, 50)
y = np.array([np.ones(50)*0.5, x, x**2, (1-x), (1-x**2)]).T
fig = plt.figure('Fig')
ax = fig.add_subplot(111)
lines = ax.plot(x, y)
def legend_adjust(legend, ax=None ):
if ax == None: ax =plt.gca()
ax.figure.canvas.draw()
bbox = legend.get_window_extent().transformed(ax.transAxes.inverted() )
print bbox.height
ax.margins(y = 2.*bbox.height)
leg = plt.legend(handles=[lines[0], lines[1], lines[2], lines[3], lines[4]],
labels= [r'$\mathrm{line} = 0.5$', r'$\mathrm{line} = x$', r'$\mathrm{line} = x^2$',
r'$\mathrm{line} = 1-x$',r'$\mathrm{line} = 1-x^2$'], loc="upper center",
ncol=2)
legend_adjust(leg)
plt.show()
If setting the limits is fine with you, you may also adapt the limits themselves:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms
x = np.linspace(0, 1, 50)
y = np.array([np.ones(50)*0.5, x, x**2, (1-x), (1-x**2)]).T
fig = plt.figure('Fig')
ax = fig.add_subplot(111)
lines = ax.plot(x, y)
def legend_adjust(legend, ax=None, pad=0.05 ):
if ax == None: ax =plt.gca()
ax.figure.canvas.draw()
bbox = legend.get_window_extent().transformed(ax.transAxes.inverted() )
ymin, ymax = ax.get_ylim()
ax.set_ylim(ymin, ymax+(ymax-ymin)*(1.+pad-bbox.y0))
leg = plt.legend(handles=[lines[0], lines[1], lines[2], lines[3], lines[4]],
labels= [r'$\mathrm{line} = 0.5$', r'$\mathrm{line} = x$', r'$\mathrm{line} = x^2$',
r'$\mathrm{line} = 1-x$',r'$\mathrm{line} = 1-x^2$'], loc="upper center",
ncol=2)
legend_adjust(leg)
plt.show()
out of the plot
Otherwise you may decide to always put the legend out of the plot. Some techniques are collected in this answer.
Of special interest may be to place the legend outside the figure without changing the figuresize, as detailed in this question: Creating figure with exact size and no padding (and legend outside the axes)
Adapting it to this case would look like:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms
x = np.linspace(0, 1, 50)
y = np.array([np.ones(50)*0.5, x, x**2, (1-x), (1-x**2)]).T
fig = plt.figure('Fig')
ax = fig.add_subplot(111)
lines = ax.plot(x, y)
def legend(ax=None, x0=1,y0=1, direction = "v", padpoints = 3,**kwargs):
if ax == None: ax =plt.gca()
otrans = ax.figure.transFigure
t = ax.legend(bbox_to_anchor=(x0,y0), loc=1, bbox_transform=otrans,**kwargs)
plt.tight_layout()
ax.figure.canvas.draw()
plt.tight_layout()
ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0]
trans2=matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans)+\
ax.figure.transFigure.inverted()
tbox = t.get_window_extent().transformed(trans2 )
bbox = ax.get_position()
if direction=="v":
ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox.y0-bbox.y0])
else:
ax.set_position([bbox.x0, bbox.y0,tbox.x0-bbox.x0, bbox.height])
legend(handles=[lines[0], lines[1], lines[2], lines[3], lines[4]],
labels= [r'$\mathrm{line} = 0.5$', r'$\mathrm{line} = x$', r'$\mathrm{line} = x^2$',
r'$\mathrm{line} = 1-x$',r'$\mathrm{line} = 1-x^2$'],
y0=0.8, direction="h", borderaxespad=0.2)
plt.show()