Parasite axis appearing at two positions and overlapping - python

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.

Related

How to toggle points on and off on matplotlib 3D scatter plot?

SOLVED (see below)
On 2D matpotlib scatter plot I can turn on and off points by accessing _offsets property of scatter plot object and setting it's .mask attribute True/False for indexes of those points we want to show/hide like this:
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import random
def TogglePoints(event, plot):
plot._offsets.mask = [ [1, 1], [1, 1], [1, 1], [0, 0], [0, 0], [0, 0] ]
plot.figure.canvas.draw()
x = [random.randint(-10, 10) for n in range(6)]
y = [random.randint(-10, 10) for n in range(6)]
ax = plt.axes()
sc = ax.scatter(x, y, marker='o', s=20, alpha=1)
ax_button = plt.axes([0.81, 0.01, 0.1, 0.05])
button= Button(ax_button, "Toggle")
button.on_clicked(lambda event: TogglePoints(event, sc))
plt.show()
When you click the "Toggle" button on the figure, points with indexes 0, 1, 2 will disappear. You can make them re-appear by setting _offsets.mask back to False and re-drawing plot.
This is what I want to achieve with matplotlib 3D scatter plot.
Using _offsets.mask = [ [1, 1], [1, 1], [1, 1], [0, 0], [0, 0], [0, 0] ] on 3D scatter plot doesn't seem to work.
Actually it alters type of underlying array from MaskedArray to numpy.ndarray for some reason (see: Numpy MaskedArray in matplotlib 3D scatter plot, turns into ndarray when called by PyQt5 button click).
I know that 3D scatter plots have _offsets3d property. However I don't know how I can use it to show/hide points on the plot. Or maybe there's some other way ?
Does anyone know how I can do that ?
Thanks to this post:
Get working alpha value of scatter points in mpl_toolkits.basemap.Basemap
I've found a workaround that serves my purpose.
It concerns setting alpha values of points with set_facecolors().
So the working code now looks like this:
...
import pandas as pd #added
def TogglePointsOFF(event, plot):
for n in range(3): # n = index of point
fc_colors[n, 3] = 0 # 4th value is alpha
plot.set_facecolors(fc_colors)
plot.figure.canvas.draw()
def TogglePointsON(event, plot):
for n in range(3): # n = index of point
fc_colors[n, 3] = 1 # 4th value is alpha
plot.set_facecolors(fc_colors)
plot.figure.canvas.draw()
#I've put it into DataFrame() so you can better see
df = pd.DataFrame()
df['label'] = ["data_"+str(n) for n in range(6)]
df['id'] = [1, 1, 1, 2, 2, 2]
['x'] = [random.randint(-10, 10) for n in range(6)]
['y'] = [random.randint(-10, 10) for n in range(6)]
['z'] = [random.randint(-10, 10) for n in range(6)]
colors = {1:'red', 2:'blue'} # to map colors with df 'id'
#plot points colored according to value of df['id']
ax = plt.axes()
sc = ax.scatter(df['x'], df['y'], df['z'], c=df['id'].map(colors), marker='o', s=20, depthshade=False)
global fc_colors #yeah yeah globals...
face_colors = sc._face_colors
ax_button = plt.axes([0.81, 0.01, 0.1, 0.05])
ax_button_1 = plt.axes([0.68, 0.01, 0.12, 0.05])
button= Button(ax_button, "OFF")
button_1= Button(ax_button_1, "ON")
button.on_clicked(lambda event: TogglePointsOFF(event, sc))
button_1.on_clicked(lambda event: TogglePointsON(event, sc))
plt.show()
Clicking buttons "ON" and "OFF" will hide/show group of points based on index.
I've tried using set_alpha() and passing iterable of alpha values like: [0, 0, 0, 1, 1, 1] however it seemed to work on random points and set alpha of incorrect points.
Also getting face_colors from get_facecolors() seemed to get colors with random index alignment. This may be connected why passing iterable with alpha values to set_alpha() didn't work. That's why I take colors of points from: sc._face_colors .
Thank you for your time.
WARNING! Be advised.
This doesn't work when you use any 'official' colormap like this:
sc = ax.scatter(df['x'], df['y'], df['z'], cmap='tab10, vmin=10, vmax=10, marker='o', s=20, depthshade=False)
For setting alpha of points as described above you have to "kind-off" make you own colormap mapping like it was done here:
c=df['id'].map(colors)
or, use Normalizer object to map any colormap to some custom values like this:
from matplotlib.colors import Normalize #added
#let's assume we have some score values coresponding with data points:
score = [random.uniform(0.101, 100.123) for n in range(6)]
#but we can use any iterable with numbers
norm = Normalize(min(score), max(score)
cmap = matplotlib.cm.get_cmap('Spectral') #get some built in colormap
colors = cmap(norm(score))
#now you can use colors as 'c' parameter:
sc = ax.scatter(df['x'], df['y'], df['z'], c=colors, marker='o', s=20, depthshade=False)
Remember! don't put any alpha parameter and use depthshade=False to prevent fading of points in the back of the plot.
I hope you found this usefull.
Keep scrolling.

python - making the additional yaxis (parasite axis) visible on my graph

these are my codes, mostly taken from the example here https://matplotlib.org/2.0.2/examples/axes_grid/demo_parasite_axes2.html
the output graph is shown below
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
sns.set()
import numpy as np
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(left=.15)
par1 = host.twinx()
par2 = host.twinx()
host.tick_params(width=0)
par1.tick_params(width=0)
par2.tick_params(width=0)
offset = -60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["left"] = new_fixed_axis(loc="left", axes=par2,offset=(offset, 0))
par2.set_ylabel("V3")
par2.axis["left"].toggle(all=True)
xx = np.random.randint(1,10,100)
yy = np.random.randint(-100,10,100)
p1 = host.scatter(xx,yy)
ax = plt.gca()
ax.invert_yaxis()
rolling_period = 3
plt.xlabel('V2')
plt.ylabel('V1')
x1, y1 = [250, 0], [0, 0]
x2, y2 = [0, 0], [-30, 0]
plt.plot(x1,y1,x2,y2,color='black')
plt.xlim([-50, 250])
plt.ylim([0, -30])
plt.tick_params(axis='x',which='both',bottom=False,top=False)
p2, = par1.plot([0, 1, 2], [0, 3, 2], linestyle = 'None', label="V3")
plt.draw()
plt.show()
as you can see if I use seaborn I cant see the axis line for the parasite axis (the additional y axis on the left)
however if I remove the seaborn I do see that, but I do need to use seaborn for visualisation purpose so how do I fix the problem?
the graph without seaborn
Seaborn sets axis line colors to white. Just reset it to black by:
par2.axis['left'].line.set_ec((0, 0, 0, 1))

Rotate x axis labels in Matplotlib parasite plot

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)

How to add counts of points as a label in a sparse scatter plot

I have sparse scatter plot to visualize the comparison of predicted vs actual values. The range of the values are 1-4 and there are no decimal points.
I have tried plotly so far with hte following code (but I can also use a matplotlib solution):
my_scatter = go.Scatter(
x = y_actual, y = y_pred, mode = 'markers',
marker = dict(color = 'rgb(240, 189, 89)', opacity=0.5)
)
This prints the graph nicely (see below). I use opacity to see the density at each point. I.e. if two points lie on top of each other, the point will be shown in darker color. However, this is not explanatory enough. Is it possible to add the counts at each point as a label? There are some overlaps at certain intersections. I want to display how many points intersects. Can this be done automatically using matplotlib or plotly?
This answer uses matplotlib.
To answer the initial question first: You need to find out how often the data produces a point at a given coordinate to be able to annotate the points. If all values are integers this can easily be done using a 2d histogram. Out of the hstogram one would then select only those bins where the count value is nonzero and annotate the respective values in a loop:
x = [3, 0, 1, 2, 2, 0, 1, 3, 3, 3, 4, 1, 4, 3, 0]
y = [1, 0, 4, 3, 2, 1, 4, 0, 3, 0, 4, 2, 3, 3, 1]
import matplotlib.pyplot as plt
import numpy as np
x = np.array(x)
y = np.array(y)
hist, xbins,ybins = np.histogram2d(y,x, bins=range(6))
X,Y = np.meshgrid(xbins[:-1], ybins[:-1])
X = X[hist != 0]; Y = Y[hist != 0]
Z = hist[hist != 0]
fig, ax = plt.subplots()
ax.scatter(x,y, s=49, alpha=0.4)
for i in range(len(Z)):
ax.annotate(str(int(Z[i])), xy=(X[i],Y[i]), xytext=(4,0),
textcoords="offset points" )
plt.show()
You may then decide not to plot all points but the result from the histogramming which offers the chance to change the color and size of the scatter points,
ax.scatter(X,Y, s=(Z*20)**1.4, c = Z/Z.max(), cmap="winter_r", alpha=0.4)
Since all values are integers, you may also opt for an image plot,
fig, ax = plt.subplots()
ax.imshow(hist, cmap="PuRd")
for i in range(len(Z)):
ax.annotate(str(int(Z[i])), xy=(X[i],Y[i]), xytext=(0,0), color="w",
ha="center", va="center", textcoords="offset points" )
Without the necesity to calculate the number of occurances, another option is to use a hexbin plot. This gives slightly inaccurate positions of the dots, du to the hexagonal binning, but I still wanted to mention this option.
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
x = np.array(x)
y = np.array(y)
fig, ax = plt.subplots()
cmap = plt.cm.PuRd
cmaplist = [cmap(i) for i in range(cmap.N)]
cmaplist[0] = (1.0,1.0,1.0,1.0)
cmap = matplotlib.colors.LinearSegmentedColormap.from_list('mcm',cmaplist, cmap.N)
ax.hexbin(x,y, gridsize=20, cmap=cmap, linewidth=0 )
plt.show()

matplotlib naming the tick marks on x axis 1, 2, 4, 8, 16, etc

Using the same code from a previous question, this sample generates the graph below:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
data = (0, 1890,865, 236, 6, 1, 2, 0 , 0, 0, 0 ,0 ,0 ,0, 0, 0)
ind = range(len(data))
width = 0.9 # the width of the bars: can also be len(x) sequence
p1 = plt.bar(ind, data, width)
plt.xlabel('Duration 2^x')
plt.ylabel('Count')
plt.title('DBFSwrite')
plt.axis([0, len(data), -1, max(data)])
ax = plt.gca()
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
plt.savefig('myfig')
Instead of the tick labels being 0, 2, 4, 6, 8...I would rather have them be labeled at every mark, and proceed with the value of 2^x: 1, 2, 4, 8, 16, etc. How can I do that? And then even better, could I have the label centered under the bar, instead of at the left edge?
One way of achieving this is to make use of a Locator and a Formatter. This makes it possible to use the plot interactively without "losing" tickmarks. In this case I'd recommend MultipleLocator and FuncFormatter as seen in example below.
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FuncFormatter
data = (0, 1890,865, 236, 6, 1, 2, 0 , 0, 0, 0 ,0 ,0 ,0, 0, 0)
ind = range(len(data))
width = 0.9 # the width of the bars: can also be len(x) sequence
# Add `aling='center'` to center bars on ticks
p1 = plt.bar(ind, data, width, align='center')
plt.xlabel('Duration 2^x')
plt.ylabel('Count')
plt.title('DBFSwrite')
plt.axis([0, len(data), -1, max(data)])
ax = plt.gca()
# Place tickmarks at every multiple of 1, i.e. at any integer
ax.xaxis.set_major_locator(MultipleLocator(1))
# Format the ticklabel to be 2 raised to the power of `x`
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, pos: int(2**x)))
# Make the axis labels rotated for easier reading
plt.gcf().autofmt_xdate()
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
plt.savefig('myfig')
xticks() is what you want:
# return locs, labels where locs is an array of tick locations and
# labels is an array of tick labels.
locs, labels = xticks()
# set the locations of the xticks
xticks( arange(6) )
# set the locations and labels of the xticks
xticks( arange(5), ('Tom', 'Dick', 'Harry', 'Sally', 'Sue') )
So, to have the ticks at 2^x for x in 1..4, do as follows:
tick_values = [2**x for x in arange(1,5)]
xticks(tick_values,[("%.0f" % x) for x in tick_values])
To have the labels centered instead of left of the bars, use the align='center' when calling bar.
Here's the result:

Categories