TransWikia.com

Change number of decimals on a triplot with matplotlib on only one tick of a subplot

Stack Overflow Asked on December 27, 2021

I am plotting what we call a triplot, i.e joint sitribution with couples of parameters which represent covariance and posterior distribution. It consists of different subplots.

Initially, on a subplot on y-axis of triplot, I get the following default xticks :

enter image description here

What I would like to do is to replace mean xtick value 0.0000 by 0.00.

For this and from my research, I found (with keeping Latex fonts) :

## If we are on third line of triplot
if (m == 3):
  ax.yaxis.set_major_formatter(FormatStrFormatter(r'$%.2f$')) (1)

The issue is that I have now the following subplot :

enter image description here

As you can see, the two other xtick values are also affected by the format %.2f whereas I just want to use this format for the mean (0.00).

How can I keep the limits ±0.1864 and change only the number of decimals for 0.0000 to display only 0.00 ?

I think I have to select the right xtick (what I call the "mean[m]" in my code indexed by "m" where "m" represents the line of triplot subplots (and here with a column index equal to 0 = first column) along all the y-axis but I don’t know how to locate it on this axis.

If someone could see how to change only this value by adding/modifying some options to the (1) instruction line, I would be grateful.

UPDATE 1: Thanks for your answers. Actually, I am using the tool GetDist (with examples on GetDist examples).

Initially, I thought that, by taking 2 decimals instead of 4 for "w_a" parameter (label on the left on above figures), the "w_a" label would be shifted to the right.

Indeed, I show you below the entire triangular plot where you can see this label "w_a" is misaligned compared to all other labels, both on Ox and Oy axis : on Ox axis, "w_a" is under aligned and on Oy, it is over left aligned, since the "-0.1864" and "0.1864" (mostly I think since the sign minus in front of 0.1864).

Do you think there may be a trick/solution to align all the labels with a constant space between them and the Ox and Oy axis ? I mean independently of the fact that I take 2 or 4 decimals for the 3 xticks on each box.

triangular plot

UPDATE 2: I have found a solution that we can qualify of "tricky" or "dirty" solution.

I played with set_label_coords on xaxis and yaxis and set manually the padding which gives a good rendering.

g = plots.get_subplot_plotter()
g.settings.constrained_layout = True

# Call triplot
g.triangle_plot([matrix1, matrix2],
                names,
                filled = True,
                legend_labels = ['1240', '1560'],
                legend_loc = 'upper right',
                contour_colors = ['red','darkblue'],
                line_args = [{'lw':2, 'color':'red'},
                {'lw':2, 'color':'darkblue'}],
                )

# Browse each subplot of triangle
for m in range(0,7):
  for n in range(0,m+1):
    ax = g.subplots[m,n]

    # First joint disctibution : row = 1 (second line) and column = 0 (first column)
    if (m == 1 and n == 0):
      x0, y0 = ax.yaxis.get_label().get_position()

    if (m != n):
      if (m == 3 and n == 0):
        # shift on the left w_a label
        ax.yaxis.set_label_coords(x0-0.55, y0)

        # Joint disctibution : row = 3 (fourth line) and column = 0 (first column)
        # 0.0 for w_a
        ax.yaxis.set_major_formatter(FuncFormatter(lambda x,_: '$0.0$' if x == 0 else f'${x:.4f}$'))
       
      # Joint disctibution : row = 6 (seventh line) and column = 3 (fourth column)

      if (m == 6 and n == 3):
        # shift on bottom w_a label
        ax.xaxis.set_label_coords(x0+0.5, y0-1)
      elif (n == 3):
        ax.xaxis.set_major_formatter(FuncFormatter(lambda x,_: '$0.0$' if x == 0 else f'${x:.4f}$'))

Here is the result :

Tricky solution

As you can see, this is a fine-tuned solution since I have to iteratively change the padding to get in agreement with all the other labels from a global alignment point of view.

My original idea was to store the coordinates of the first label on each axis and apply these coordinates to all other labels on each side subplots.

But unfortunately, I get for each side subplots for y-axis :

x0 = 0, y0 = 0.5

and for x-axis : x0 = 0.5 and y0 = 0

So, If I apply these coordinates, the labels are over on x-axis/y-axis and I am obliged to shift horizontally (for y-axis) and vertically (for x-axis) the labels.

But the ideal would be to have an automatic way to align all the labels at the same space between labels and axis. I keep on my research.

UPDATE 3: I tried to apply the solution given by @JohanCwith the following code snippet at the end of my script :

for ax in g.fig.axes:
    ylabel = ax.get_ylabel()
    if len(ylabel) > 0:
        ylbl = ax.yaxis.get_label()
        ax.yaxis.labelpad = ylbl.get_position()[0] + 30

Here the results :

@JohanCsuggestion

As you can see, an extra padding is appearing for "w_a" parameter compared to all the others labels.

I think that extra-padding is simply caused by the value -0.1864 on the y-axis of subplot corresponding to "w_a" label, especially by the sign "minus" which pushes the label to the left, always compared to the others labels which have 4 decimals without sign "minus".

I don’t know how to force the label "w_a" to be located like the other ones (this is your code snippet).

Is there an alternative way to do this forcing ?

PS : maybe if I can’t force the same location for "w_a" parameter**, I could include in the @JohanC solution, i.e in the for ax in g.fig.axes: a condition dedicated for "w_a" by making decrease the labelpad and then set it roughly to the same labelpad than the other labels.

UPDATE 4 : Following the advices of JohanC, I dit at the end of my script :

for ax in g.fig.axes:
    ylabel = ax.get_ylabel()
    if len(ylabel) > 0:
        ylbl = ax.yaxis.get_label()
        ax.yaxis.labelpad = ylbl.get_position()[0] + 30

# Redraw the plot
plt.draw()
# Save triplot
g.export('Final_Figure.pdf')

But the Final_figure.pdf is not changing the shift of "w_a" (left shift for Oy axis and bottom shift for Ox axis), below the result :

Always shift with "w_a" parameter

I don’t know currently what to try anything else.

2 Answers

You can use a custom tick formatter. Jakevdp also has a nice example including expressions with π as ticks.

from matplotlib import pyplot as plt
from matplotlib.ticker import FuncFormatter
import numpy as np

def custom_tick_formatter(x, pos):
    'The two args are the value and tick position'
    return f'${x:.2f}$' if abs(x) < 1e-5 else f'${x:.4f}$'

fig, ax = plt.subplots()
t = np.arange(1, 51)
ax.scatter(t * np.cos(t) / 200, t * np.sin(t) / 200)
ax.yaxis.set_major_formatter(FuncFormatter(custom_tick_formatter))
ax.set_yticks([-0.1864, 0, 0.1864])
plt.show()

example plot

About your new question, it seems just setting the position of the ylabel gets overridden when the plot is redrawn. Setting the padding of the ylabel depending on its position seems to work well. Try to add this at the end of your code (maybe the value 30 needs to change in your situation):

for ax in g.fig.axes:
    ylabel = ax.get_ylabel()
    if len(ylabel) > 0:
        ylbl = ax.yaxis.get_label()
        # print(ylabel, ylbl.get_position())
        # ax.set_ylabel(ylabel, labelpad=...)
        ax.yaxis.labelpad = ylbl.get_position()[0] - 30
plt.show()

Answered by JohanC on December 27, 2021

You could use a FuncFormatter:

from matplotlib.ticker import FuncFormatter

ax.yaxis.set_major_formatter(FuncFormatter(lambda x,_: '0.00' if x == 0 else f'{x:.4f}'))

As for your updated (actual) question: you could use align_labels().

Example:

from getdist import plots, MCSamples
import getdist
import matplotlib.pyplot as plt
import numpy as np
ndim = 4
nsamp = 1000
np.random.seed(10)
A = np.random.rand(ndim,ndim)
cov = np.dot(A, A.T)
samps = np.random.multivariate_normal([0]*ndim, cov, size=nsamp)
samps[:,1]-=100000
A = np.random.rand(ndim,ndim)
cov = np.dot(A, A.T)
samps2 = np.random.multivariate_normal([0]*ndim, cov, size=nsamp)
names = ["x%s"%i for i in range(ndim)]
labels =  ["x_%s"%i for i in range(ndim)]
samples = MCSamples(samples=samps,names = names, labels = labels)
samples2 = MCSamples(samples=samps2,names = names, labels = labels, label='Second set')
g = plots.get_subplot_plotter()
g.settings.axis_tick_x_rotation=45
g.triangle_plot([samples, samples2], filled=True)

enter image description here

Then after g.fig.align_labels(): enter image description here

(this is retained in the pdf saved with g.export('Final_Figure.pdf') or g.fig.savefig('Final_Figure.pdf')).

Answered by Stef on December 27, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP