Rotate x axis labels in Matplotlib parasite plot - python

After Thomas very helpfully fixed my issues making two parasite sub plots in this question, I'm now trying to rotate the x axis labels on the subplots.
Unfortunately, my modification to the example code here seems to have no effect on the x axis labels:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)
par1 = host.twinx()
par2 = host.twinx()
offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",
axes=par2,
offset=(offset, 0))
par1.axis["right"].toggle(all=True)
par2.axis["right"].toggle(all=True)
host.set_xlim(0, 2)
host.set_ylim(0, 2)
host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")
p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], label="Velocity")
par1.set_ylim(0, 4)
par2.set_ylim(1, 65)
host.legend()
host.axis["left"].label.set_color(p1.get_color())
par1.axis["right"].label.set_color(p2.get_color())
par2.axis["right"].label.set_color(p3.get_color())
plt.xticks(rotation = 45) #<- The only change from the example
plt.draw()
plt.show()
gives un-rotated x axis labels in:
Although I've shown the plt.xticks(rotation = 45), I've also tried other ways that work with "conventional" matplotlib plots without success. Does anyone know if there is a way to do this, or am I just dealing with too much of a niche case? Maybe I should just figure out a way to live with using sub plots and no parasite axes?
Thanks a lot,
Alex

There are two ways to produce parasite axes:
Using the mpl_toolkits axisartist and axes_grid1 package, examples are
demo_parasite_axes
demo_parasite_axes2
this stackoverflow answer
Using usual subplots and their spines. Examples are
multiple_yaxis_with_spines
this stackoverflow answer
Here you are using the first approach, which may be a bit unintuitive due to it using the special axes provided by the axisartist toolkit.
Solution 1:
Use the usual subplots approach for which all the usual ways of rotating ticklabels work just fine, e.g.
plt.setp(ax.get_xticklabels(), rotation=90)
Solution 2:
In case you want to stick with the mpl_toolkits approach you need to obtain the ticklabels from the axis via axis["right"].major_ticklabels,
plt.setp(par2.axis["bottom"].major_ticklabels, rotation=-135)

Related

Vertical and Horizontal figures on one plot

I would like to create sth like the following graph in matplotlib:
I have x = [0, 1, ..., 10], and for each x I have values from range [0, 60]. Lets say that the black line is the quantile of values for a given i from range x. For selected i I want to add horizontally histogram (with parameter density = True) like in the picture with the possibility to control the width of this histogram (in the picture it goes from 2 to 5 but I would like to set fixed width). How can I do that?
Yes, this is relatively straightforward with inset_axes:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.random.randn(100)
ax.plot(x)
ylim = ax.get_ylim()
histax = ax.inset_axes([0.3, 0, 0.2, 1], transform=ax.transAxes)
histax.hist(x, orientation='horizontal', alpha=0.5 )
histax.set_facecolor('none')
histax.set_ylim(ylim)
plt.show()
You will probably want to clean up the axes etc, but that is the general idea.

Changing axis ticks in Matplotlib with multiple connected Boxplots

I am plotting a convergence graph and to show deviations from the mean I am using connected boxplots:
For some reason Matplotlib forces ticks for each boxplot and I cannot seem to get them removed. My code for the current plot looks something like this:
label = ["" for i in range(160)]
no_labels = int(np.floor(len(label)/20))
for i in range(no_labels):
label[i*20] = str(i*no_samples/no_labels)
# Weird behaviour for the last label so adding it manually
label[-1] = no_samples
fig = plt.figure(figsize=(10,5))
ax = fig.add_axes([0,0,1,1])
ax.set_xlabel("Samples", labelpad=10)
ax.set_ylabel("Error (MSE)", labelpad=10)
ax.set_ylim(0, 0.11)
ax.boxplot(data, flierprops=flyprops, showcaps=False,
boxprops=colorprops, whiskerprops={'color' : 'tab:blue'},
labels=label, patch_artist=True)
I have tried multiple ways of manipulating axis ticks which are available in MPL.
1) Trying to let MPL do the work:
ax.xaxis.set_major_locator(MultipleLocator(20))
2) Trying to set ticks manually: ax.set_xticks([list_of_ticks])
3) Tried a workaround
ax.xaxis.set_minor_locator(MultipleLocator(20))
# Removing major ticks, setting minor ticks
ax.xaxis.set_tick_params(which='major', size=0, width=2, direction='in')
ax.yaxis.set_tick_params(which='major', size=5, width=2, direction='in')
None of these seemed to work and I am unsure why. I think it may have something to do with my label variable but if I don't include it in this way MPL with include an axis lable for every entry which is a mess.
How can I set axis ticks once every 1000 entries in a connected boxplots figure?`
Edit: The input data is a numpy array of shape (15, 160) s.t. there are 160 boxplots plotted of 15 samples each. Example data for 5 boxplots of 3 samples each would look like:
np.random.rand(3,5)
>>> array([[0.05942481, 0.03408175, 0.84021109, 0.27531937, 0.62428798],
[0.24658313, 0.77910387, 0.2161348 , 0.39101172, 0.14038211],
[0.40694432, 0.22979738, 0.87056873, 0.788295 , 0.29337562]])
The main issue seems to be that the ticks need to be updated after drawing the main plot, never before.
(Having ax = fig.add_axes([0, 0, 1, 1]) is also quite unusual to work with. The standard way is fig, ax = plt.subplots(figsize=(10, 5)) which lets matplotlib a bit of flexibility for the whitespace around the plot.)
The code of the question has some missing variables and data, but the following toy example should create something similar:
import numpy as np
import matplotlib.pyplot as plt
no_samples = 8000
x = np.linspace(0, no_samples, 160)
no_labels = int(np.floor(len(x) / 20))
label = [f'{i * no_samples / no_labels:.0f}' for i in range(no_labels+1)]
fig = plt.figure(figsize=(10, 5))
ax = fig.add_axes([0.1, 0.1, 0.85, 0.85])
N = 100
data = np.random.normal(np.tile(100 / (x+1000), N), 0.001).reshape(N, -1)
flyprops = {'markersize':0.01}
colorprops = None
ax.boxplot(data, flierprops=flyprops, showcaps=False,
boxprops=colorprops, whiskerprops={'color': 'tab:blue'},
patch_artist=True)
ax.set_xlabel("Samples", labelpad=10)
ax.set_ylabel("Error (MSE)", labelpad=10)
ax.set_ylim(0, 0.11)
ax.set_xticks(range(0, len(x)+1, 20))
ax.set_xticklabels(label)
plt.show()
Here is an example of setting the tick marks:
import matplotlib.pyplot as plt
import numpy as np
data=np.random.rand(3,50)
fig = plt.figure(figsize=(10,5))
ax = fig.add_axes([0,0,1,1])
ax.set_xlabel("Samples", labelpad=10)
ax.set_ylabel("Error (MSE)", labelpad=10)
ax.boxplot(data,
showcaps=False,
whiskerprops={'color' : 'tab:blue'},
patch_artist=True
)
plt.xticks([10, 20, 30, 40, 50],
["10", "20", "30", "40", "50"])
EDIT:
You can also avoid messing with strings and set the marks like this:
N=50
plt.xticks(np.linspace(0, N, num=6), np.linspace(0, N, num=6))
See here and this example.
Simple ticks can be acheived in a similar mannar as here (note data as transposed numpy array) using
import numpy as np
import matplotlib.pyplot as plt
data = np.array([ np.random.rand(100) for i in range(3) ]).T
plt.boxplot(data)
plt.xticks([1, 2, 3], ['mon', 'tue', 'wed'])

Scatterplot in matplotlib with legend and randomized point order

I'm trying to build a scatterplot of a large amount of data from multiple classes in python/matplotlib. Unfortunately, it appears that I have to choose between having my data randomised and having legend labels. Is there a way I can have both (preferably without manually coding the labels?)
Minimum reproducible example:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
X = np.random.normal(0, 1, [5000, 2])
Y = np.random.normal(0.5, 1, [5000, 2])
data = np.concatenate([X,Y])
classes = np.concatenate([np.repeat('X', X.shape[0]),
np.repeat('Y', Y.shape[0])])
Plotting with randomized points:
plot_idx = np.random.permutation(data.shape[0])
colors = pd.factorize(classes)
fig, ax = plt.subplots()
ax.scatter(data[plot_idx, 0],
data[plot_idx, 1],
c=colors[plot_idx],
label=classes[plot_idx],
alpha=0.4)
plt.legend()
plt.show()
This gives me the wrong legend.
Plotting with the correct legend:
from matplotlib import cm
unique_classes = np.unique(classes)
colors = cm.Set1(np.linspace(0, 1, len(unique_classes)))
for i, class in enumerate(unique_classes):
ax.scatter(data[classes == class, 0],
data[classes == class, 1],
c=colors[i],
label=class,
alpha=0.4)
plt.legend()
plt.show()
But now the points are not randomized and the resulting plot is not representative of the data.
I'm looking for something that would give me a result like I get as follows in R:
library(ggplot2)
X <- matrix(rnorm(10000, 0, 1), ncol=2)
Y <- matrix(rnorm(10000, 0.5, 1), ncol=2)
data <- as.data.frame(rbind(X, Y))
data$classes <- rep(c('X', 'Y'), times=nrow(X))
plot_idx <- sample(nrow(data))
ggplot(data[plot_idx,], aes(x=V1, y=V2, color=classes)) +
geom_point(alpha=0.4, size=3)
You need to create the legend manually. This is not a big problem though. You can loop over the labels and create a legend entry for each. Here one may use a Line2D with a marker similar to the scatter as handle.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
X = np.random.normal(0, 1, [5000, 2])
Y = np.random.normal(0.5, 1, [5000, 2])
data = np.concatenate([X,Y])
classes = np.concatenate([np.repeat('X', X.shape[0]),
np.repeat('Y', Y.shape[0])])
plot_idx = np.random.permutation(data.shape[0])
colors,labels = pd.factorize(classes)
fig, ax = plt.subplots()
sc = ax.scatter(data[plot_idx, 0],
data[plot_idx, 1],
c=colors[plot_idx],
alpha=0.4)
h = lambda c: plt.Line2D([],[],color=c, ls="",marker="o")
plt.legend(handles=[h(sc.cmap(sc.norm(i))) for i in range(len(labels))],
labels=list(labels))
plt.show()
Alternatively you can use a special scatter handler, as shown in the quesiton Why doesn't the color of the points in a scatter plot match the color of the points in the corresponding legend? but that seems a bit overkill here.
It's a bit of a hack, but you can save the axis limits, set the labels by drawing points well outside the limits of the plot, and then resetting the axis limits as follows:
plot_idx = np.random.permutation(data.shape[0])
color_idx, unique_classes = pd.factorize(classes)
colors = cm.Set1(np.linspace(0, 1, len(unique_classes)))
fig, ax = plt.subplots()
ax.scatter(data[plot_idx, 0],
data[plot_idx, 1],
c=colors[color_idx[plot_idx]],
alpha=0.4)
xlim = ax.get_xlim()
ylim = ax.get_ylim()
for i in range(len(unique_classes)):
ax.scatter(xlim[1]*10,
ylim[1]*10,
c=colors[i],
label=unique_classes[i])
ax.set_xlim(xlim)
ax.set_ylim(ylim)
plt.legend()
plt.show()

Parasite axis appearing at two positions and overlapping

using this example as guidance:
http://matplotlib.org/examples/axes_grid/demo_parasite_axes2.html
I tried to make a plot with multiple axes using twiny in stead of twinx.
However, one of the axes - par2 appears both in top and bottom location, overlapping par1 on top. What am I doing wrong?
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(bottom=0.2)
par1 = host.twiny()
par2 = host.twiny()
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["bottom"] = new_fixed_axis(loc="bottom",
axes=par2,
offset=(0, -40))
par2.axis["bottom"].toggle(all=True)
host.set_xlim(0, 2)
host.set_ylim(0, 2)
host.set_ylabel("Distance")
host.set_xlabel("Density")
par1.set_xlabel("Temperature")
par2.set_xlabel("Velocity")
p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par1.plot([0, 3, 2], [0, 1, 2], label="Temperature")
p3, = par2.plot([50, 30, 15], [0, 1, 2], label="Velocity")
par1.set_xlim(0, 4)
par2.set_xlim(1, 65)
host.legend()
host.axis["bottom"].label.set_color(p1.get_color())
par1.axis["top"].label.set_color(p2.get_color())
par2.axis["bottom"].label.set_color(p3.get_color())
plt.draw()
plt.show()
The difference between the Matplotlib website example and your code is that you're adding the parasite axes adjacent to the "normal" axis location (i.e., the bottom) whereas the example is adding them adjacent to the "abnormal" or twinned axis.
The par2 = host.twiny() call sets the par2 top axis to be visible and this isn't undone by the new_fixed_axis() call, so you need to switch this off using,
par2.axis["top"].toggle(all=False)
Note that, weirdly, you can also achieve this by replacing the top axis rather than the bottom one:
par2.axis["top"] = new_fixed_axis(loc="bottom",
axes=par2,
offset=(0, -40))
But that's probably a whole world of confusion waiting to happen.

Changing a Matplotlib Rectangle to Circle in a Legend

Consider the following plotting code:
plt.figure(figsize=(10,6))
for k in range(nruns):
plt.plot(Thing1['Data'][:,k],color='Grey',alpha=0.10)
plt.plot(Thing2[:,1],Thing2[:,4],'ko')
a = plt.Rectangle((0, 0), 1, 1, fc="Grey",alpha=0.50)
b = plt.Rectangle((0, 0), 1, 1, fc="Black", alpha=1.00)
plt.legend([a,b], ["Thing1","Thing2"],loc=2,fontsize='small')
plt.xlabel("Time",fontsize=16)
plt.ylabel("Hijinks",fontsize=16)
plt.show()
I'd really like "b" to be a circle, rather than a rectangle. But I'm rather horrid at matplotlib code, and especially the use of proxy artists. Any chance there's a straightforward way to do this?
You're very close. You just need to use a Line2D artist and set its properties like ususal:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10,6))
fakexy = (0, 0)
a = plt.Rectangle(fakexy, 1, 1, fc="Grey",alpha=0.50)
b = plt.Line2D(fakexy, fakexy, linestyle='none', marker='o', markerfacecolor="Black", alpha=1.00)
ax.legend([a, b], ["Thing1", "Thing2"], loc='upper left', fontsize='small')
ax.set_xlabel("Time", fontsize=16)
ax.set_ylabel("Hijinks", fontsize=16)
I get:
There is a much easier way to do this with newer versions of matplotlib.
from pylab import *
p1 = Rectangle((0, 0), 1, 1, fc="r")
p2 = Circle((0, 0), fc="b")
p3 = plot([10,20],'g--')
legend([p1,p2,p3], ["Red Rectangle","Blue Circle","Green-dash"])
show()
Note this is not my work. This is obtained from Matplotlib, legend with multiple different markers with one label.

Categories