How to set the legends outside of the graphics in OpenTURNS?

159 Views Asked by At

I want to create a contour plot of the log-likelihood of a Gaussian sample with OpenTURNS. Each contour maps to a function value, which is indicated by the legend of the plot. The problem is that the legend somewhat hides the contours: how to setup the plot so that the legend does not hide the content?

Here is an example. I create a Normal distribution corresponding to the height of man with age between 20 and 79 years old (see "Statistical Abstract of the United States U.S. Census Bureau." Table 209. 2012). Then I generate a sample from this distribution. I define the log-likelihood function, which takes the vector (mu, sigma) on input and returns a one-dimensional vector containing the log-likelihood on output. I can use the draw method of this function to create the contour plot.

import openturns as ot
import openturns.viewer as otv

mu = 1.763
sigma = 0.0680
N = ot.Normal(mu, sigma)
sample_size = 100
sample = N.getSample(sample_size)

def loglikelihood_gauss(X):
    """Compute the log-likelihood of a Gaussian sample."""
    mu, sigma = X
    N = ot.Normal(mu, sigma)
    log_pdf = N.computeLogPDF(sample)
    sample_size = sample.getSize()
    log_likelihood = log_pdf.computeMean() * sample_size
    return log_likelihood

# Contour plot
logLikelihoodFunction = ot.PythonFunction(2, 1, loglikelihood_gauss)
ot.ResourceMap_SetAsUnsignedInteger("Contour-DefaultLevelsNumber", 5)
graph = logLikelihoodFunction.draw([1.65, 0.04], [1.85, 0.15], [50]*2)
graph.setXTitle(r"$\mu$")
graph.setYTitle(r"$\sigma$")
graph.setTitle("Log-Likelihood.")
view = otv.View(graph)

This produces:

Log-Likelihood of a gaussian sample

How to setup the plot so that the legend does not hide the contours?

3

There are 3 best solutions below

0
On

The first trick is to use the bbox_to_anchor option of the legend matplotlib function. This is possible because all OpenTURNS graphics are from Matplotlib at the Python layer. The second trick is to get the figure from the View object, based on the getFigure method. Without further code, this produces a second legend which is at the correct location, but duplicates the legend. The third trick is to hide the old legend by setting the content to empty strings.

logLikelihoodFunction = ot.PythonFunction(2, 1, loglikelihood_gauss)
ot.ResourceMap_SetAsUnsignedInteger("Contour-DefaultLevelsNumber", 5)
graph = logLikelihoodFunction.draw([1.65, 0.04], [1.85, 0.15], [50]*2)
graph.setXTitle(r"$\mu$")
graph.setYTitle(r"$\sigma$")
graph.setTitle("Log-Likelihood.")
legends = graph.getLegends()
graph.setLegendPosition("")
view = otv.View(graph)
figure = view.getFigure()
figure.legend(legends, bbox_to_anchor=(1.1, 0.9))

The log-likelihood contour plot with a legend outside of the box

When saving the figure, the legend may be cropped. It might be necessary to use the bbox_inches option:

figure.savefig("filename.png", bbox_inches="tight")
0
On

Very nice functionality! I'am often embarrassed with graphs which legend is too big. You can improve your script by using:

graph.setLegendPosition('')

instead of:

graph.setLegends([""] * 5)

It avoids you to count the number of drawables you have in the graph.

0
On

Unfortunately it doesn't work well if you mix different kind of drawables:

import openturns as ot
import openturns.viewer as otv

f = ot.SymbolicFunction(["x", "y"], ["x^4+y^4-8*(x^2+y^2)"])
graph = f.draw([-4.0]*2, [4.0]*2)
graph.setLegendPosition("bottomleft")
graph.add(ot.Curve([[-4.0]*2, [4.0]*2], "red", "dashed", 1.0, "line"))
graph.add(ot.Cloud([[0.0]*2], "black", "fcircle", "point"))
legends = graph.getLegends()
view = otv.View(graph)
figure = view.getFigure()
figure.legend(legends, bbox_to_anchor=(1.0, 1.0))

On the resulting figure you see that the new legend has interverted the labels of the point and the line with the labels of the first two isovalues. The correct way to get the legend labels is:

_, legends = view._ax[0].get_legend_handles_labels()