Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib: Draw a vertical arrow in a log-log plot

I am not able to draw a simple, vertical arrow in the following log-log plot:

#!/usr/bin/python2

import matplotlib.pyplot as plt
import matplotlib as mpl

plt.yscale('log')
plt.xscale('log')
plt.ylim((1e-20,1e-10))
plt.xlim((1e-12,1))

plt.arrow(0.00006666, 1e-20, 0, 1e-8 - 1e-20, length_includes_head=True)

plt.savefig('test.pdf')

It just doesn't show. From the documentation it appears as if all the arguments, like width, height and so on relate to the scale of the axis. This is very counter-intuitive. I tried using twin() of the axisartist package to define an axis on top of mine with limits (0,1), (0,1) to have more control over the arrow's parameters, but I couldn't figure out how to have a completely independent axis on top of the primary one.

Any ideas?

like image 402
janoliver Avatar asked Aug 02 '12 16:08

janoliver


People also ask

What does Loglog signify in pandas plot () function?

loglog() Function. The Axes. errorbar() function in axes module of matplotlib library is used to make a plot with log scaling on both the x and y axis. Syntax: Axes.loglog(self, *args, **kwargs)


3 Answers

I know this thread has been dead for a long time now, but I figure posting my solution might be helpful for anyone else trying to figure out how to draw arrows on log-scale plots efficiently.

As an alternative to what others have already posted, you could use a transformation object to input the arrow coordinates not in the scale of the original axes but in the (linear) scale of the "axes coordinates". What I mean by axes coordinates are those that are normalized to [0,1] (horizontal range) by [0,1] (vertical range), where the point (0,0) would be the bottom-left corner and the point (1,1) would be the top-right, and so on. Then you could simply include an arrow by:

plt.arrow(0.1, 0.1, 0.9, 0.9, transform=plot1.transAxes, length_includes_head=True)

This gives an arrow that spans diagonally over 4/5 of the plot's horizontal and vertical range, from the bottom-left to the top-right (where plot1 is the subplot name).

If you want to do this in general, where exact coordinates (x0,y0) and (x1,y1) in the log-space can be specified for the arrow, this is not too difficult if you write two functions fx(x) and fy(y) that transform from the original coordinates to these "axes" coordinates. I've given an example of how the original code posted by the OP could be modified to implement this below (apologies for not including the images the code produces, I don't have the required reputation yet).

#!/usr/bin/python3

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

# functions fx and fy take log-scale coordinates to 'axes' coordinates
ax = 1E-12 # [ax,bx] is range of horizontal axis 
bx = 1E0
def fx(x):
    return (np.log(x) - np.log(ax))/(np.log(bx) - np.log(ax))

ay = 1E-20 # [ay,by] is range of vertical axis
by = 1E-10
def fy(y):
    return (np.log(y) - np.log(ay))/(np.log(by) - np.log(ay))

plot1 = plt.subplot(111)
plt.xscale('log')
plt.yscale('log')
plt.xlim(ax, bx)
plt.ylim(ay, by)

# transformed coordinates for arrow from (1E-10,1E-18) to (1E-4,1E-16)
x0 = fx(1E-10)
y0 = fy(1E-18)
x1 = fx(1E-4) - fx(1E-10)
y1 = fy(1E-16) - fy(1E-18)

plt.arrow(
          x0, y0, x1, y1, # input transformed arrow coordinates 
          transform = plot1.transAxes, # tell matplotlib to use axes coordinates   
          facecolor = 'black',
          length_includes_head=True
         )

plt.grid(True)
plt.savefig('test.pdf')
like image 136
antibrane Avatar answered Sep 27 '22 21:09

antibrane


I was looking for an answer to this question, and found a useful answer! You can specify any "mathtext" character (matplotlib's version of LaTeX) as a marker. Try: plt.plot(x,y, 'ko', marker=r'$\downarrow$', markersize=20)

This will plot a downward pointing, black arrow at position (x,y) that looks good on any plot (even log-log). See: matplotlib.org/users/mathtext.html#mathtext-tutorial for more symbols you can use.

like image 45
Ben Avatar answered Sep 27 '22 23:09

Ben


Subplots approach

After creating the subplots do the following

  • Align the positions
  • Use set_axis_off() to turn the axis off (ticks, labels, etc)
  • Draw the arrow!

So a few lines gets whats you want!

E.g.

#!/usr/bin/python2

import matplotlib.pyplot as plt

hax = plt.subplot(1,2,1)
plt.yscale('log')
plt.xscale('log')
plt.ylim((1e-20,1e-10))
plt.xlim((1e-12,1))

hax2 = plt.subplot(1,2,2)
plt.arrow(0.1, 1, 0, 1, length_includes_head=True)

hax.set_position([0.1, 0.1, 0.8, 0.8])
hax2.set_position([0.1, 0.1, 0.8, 0.8])

hax2.set_axis_off()

plt.savefig('test.pdf')

Rescale data

Alternatively a possibly easier approach, though the axis labels may be tricky, is to rescale the data.

i.e.

import numpy 

# Other import commands and data input

plt.plot(numpy.log10(x), numpy.log10(y))) 

Not a great solution, but a decent result if you can handle the tick labels!

like image 24
jmetz Avatar answered Sep 27 '22 22:09

jmetz