Opened 14 years ago
Last modified 10 years ago
#4529 closed enhancement
Implement plots with logarithmic scale — at Version 25
Reported by: | ronanpaixao | Owned by: | ronanpaixao |
---|---|---|---|
Priority: | major | Milestone: | sage-5.2 |
Component: | graphics | Keywords: | plot log scale |
Cc: | Merged in: | ||
Authors: | Reviewers: | ||
Report Upstream: | N/A | Work issues: | |
Branch: | Commit: | ||
Dependencies: | #12974 | Stopgaps: |
Description (last modified by )
Attached is a patch which introduces log scale to Graphics()
class.
Apply the following patches in the specified order. SAGE_ROOT
is the directory where the sage installation is present.
cd SAGE_ROOT/devel/sage ../../sage -hg qimport -P http://trac.sagemath.org/sage_trac/raw-attachment/ticket/12974/trac_12974-fix_graphics_attributes_and_reorder_args.patch ../../sage -hg qimport -P http://trac.sagemath.org/sage_trac/raw-attachment/ticket/12974/trac_12974-refactor_and_whitespace_cleanups.patch ../../sage -hg qimport -P http://trac.sagemath.org/sage_trac/raw-attachment/ticket/4529/trac_4529-add_logscale_to_Graphics.patch ../../sage -hg qimport -P http://trac.sagemath.org/sage_trac/raw-attachment/ticket/4529/trac_4529-add_docs_eg_to_some_user_facing_functions.patch ../../sage -b
OLD DISCUSSION BELOW :)
Currently plot() has no option to use logarithmic scales.
One workaround is to use matplotlib directly, with its semilogy(), semilogx() and loglog() functions, but that wouldn't produce plots with the customisations implemented in sage. Another workaround is messing with the plot figure like:
import pylab p=plot(x,marker='.') f=pylab.figure() f.gca().set_xscale('log') p.save(figure=f)
But that creates two problems:
- The first problem is that the adaptive choosing of points just considers linear scale, so the points get too much spaced apart in the beginning of the plot and too close in the end.
- The second problem relates to the axis, which, for the same reason, isn't located right.
Also, this requires the user to know how to deal with figures, which is not directly exposed by sage.
There are some possibilities to fix that:
- Make plot() detect if the figure changes the scales and modify the adaptive algorithm and the axis codes accordingly
- Create a kwarg to tell plot() to implement the scale-change internally
- Create other functions to use loglog(), semilogx() and semilogy()
- Many (or all) of the above together, since they aren't mutually exclusive
From what I noticed, Mathematica implements the separate functions way, but it may be better to fix the issue in plot() itself and if the other functions are wanted, just make it so that they call plot() with the correct arguments
Change History (29)
Changed 14 years ago by
comment:1 Changed 14 years ago by
- Milestone changed from sage-wishlist to sage-duplicate/invalid/wontfix
- Resolution set to duplicate
- Status changed from new to closed
comment:2 Changed 14 years ago by
- Resolution duplicate deleted
- Status changed from closed to reopened
comment:3 Changed 14 years ago by
- Owner changed from somebody to ronanpaixao
- Status changed from reopened to new
comment:4 Changed 14 years ago by
- Milestone changed from sage-duplicate/invalid/wontfix to sage-3.2.1
comment:5 Changed 13 years ago by
See #1431 for a way to solve this.
comment:6 Changed 11 years ago by
- Report Upstream set to N/A
comment:7 Changed 11 years ago by
Here are some comments about an API. How about adding a show keyword that specifies the scale of the axes.
scale='log'
-- a string specifies the scale of the vertical axis
scale=('log','linear')
-- a tuple or list of two strings specifies scales for both axes
We'd probably like some way to pass in arguments to the scale, since different scales have different options. This looks ugly: scale=( ('log', {'base': 2}), 'linear')
comment:8 Changed 11 years ago by
We could also do something like xscale='log'
or xscale=('log',{'base': 2})
and similarly for yscale. I don't like using x and y, though, since the variables in the plot might not be x and y.
comment:9 Changed 11 years ago by
My sense is that the API should look like the tick marks API.
Here are some comments Jason made on sage-support about this.
> To change the scale, you can modify the plot afterwards, but I am > running into some sort of problem doing it: > sage: p=plot(e^x,(x,0,10)) > sage: m=p.matplotlib() > sage: from matplotlib.backends.backend_agg import FigureCanvasAgg > sage: m.set_canvas(FigureCanvasAgg(m)) > sage: m.gca().set_yscale('log') > sage: m.savefig('test.png') It seems something was wrong with the plot in the above example, or something. Anyways, starting with: p=plot(x,(x,1,10)) works fine. To do #4529, I'd suggest adding a keyword to show that defines the scales of the x and y axes. I've added some comments to the ticket.
How was I not cc:ed on this ticket before? ;-)
comment:10 Changed 11 years ago by
Also, #5128 would appear to be slightly related.
comment:11 Changed 11 years ago by
The error with e^x
is
MaskError: Cannot convert masked element to a Python int.
but seems to be related to there being something other than linearity involved. Linear functions work, anything with ^
or **
or sin
doesn't.
--> 154 self._renderer.draw_text_image(font.get_image(), int(x), int(y) + 1, angle, gc)
is the problem - it's converting one of the elements, which is supposed to be skipped (masked, right?) to an int.
comment:12 Changed 11 years ago by
logplots.py has a new class LogGraphics
that I implemented and have been using for the past few months. Integrating it with Graphics seemed quite a painful process, so I had to go this direction and make my own class. Currently, it handles many but not all of the arguments that the Graphics
class supports. In addition it uses matplotlib.plt
to do the log plot; otherwise I ran into all sorts of problems with matplotlib (like the ones mentioned in earlier comments).
In engineering, we often need logarithmic plots and the logarithmic plots sometimes is of the form that the x-axis decreases as we go towards the right (for example if we plot decreasing probabilities on the x-axis). This LogGraphics
takes this into account and makes sure that if a list of x-axis points with decreasing values along the higher indices of the list, then it plots the graph with a decreasing x-axis.
comment:13 Changed 11 years ago by
Sorry, I meant matplotlib.pyplot
in the above comment.
comment:14 Changed 10 years ago by
- Status changed from new to needs_review
comment:15 Changed 10 years ago by
I am not sure if this needs to be set to "needs_review". The main thing it is lacking is that it doesn't inherit the Graphics
class, and hence the set of plot options it supports is much less.
On the other hand, I did try to make it inherit the Graphics
class but then I ran into a big hurdle: the variables in the Graphics
class are defined with double underscore __
and so even after I inherit it, I need to use (IMHO ugly) setters and getters in order to access those variables. I tried to overcome this limitation by inheriting Graphics
in the class LogGraphics
and defining a separate (and mostly identical) __init__
in LogGraphics
but then the methods wouldn't work. Since I needed to rewrite almost everything, I decided to just rewrite everything from scratch.
One thing that I plan to do is change all the variables in the Graphics
class to be defined with a single _
and see how it works out. Perhaps then it might be possible to integrate this patch better and consequently have access to all the methods (and hence plot options) available to the plot
command.
comment:16 Changed 10 years ago by
Hmm, that's odd that you had to do this. Here are two "Sage-ic" ideas.
- Have it inherit from
GraphicPrimitive
. That is how most classes are done, and hopefully (?) would work. - Have this be a
show
option, or something like that. In principle, we wouldn't even want the graphic itself to be plotted logarithmically; one could imagine wanting to have aLine
or other plot and then to view it with log, semilog, reverse log, or regular scales. See Jason's wise comment:7.
comment:17 Changed 10 years ago by
GraphicsPrimitive
is unfortunately too crude for most purposes. All the important functions are defined inGraphics
.- I did try using
_render_on_subplot
fromline.py
but ran into the same problem as you described in comment:9. It simply doesn't work since matplotlib fails to re-render the line in log scale. Actually I ran into many more problems; I just can't remember what else. The most painless method seemed to be to use pyplot (or even pylab, but we can avoid importing that since we don't need numpy).
That said, it is IMHO better to have logarithmic plots as a separate class. For instance, it doesn't make much sense to "add" plots with different scalings (and I also raise an error in the class I created).
comment:18 follow-up: ↓ 19 Changed 10 years ago by
- Status changed from needs_review to needs_work
I'm currently cleaning up tickets marked needs_review which have no patches attached, which includes this one, so back to needs_work this goes.
comment:19 in reply to: ↑ 18 Changed 10 years ago by
Replying to kini:
I'm currently cleaning up tickets marked needs_review which have no patches attached, which includes this one, so back to needs_work this goes.
That's fine. I am actually working on a patch which
- modifies the
Graphics
class to have all the attributes start with a single underscore._
instead of.__
. This is already working and passes all doctects at least indevel/sage/sage/plot
- inherits the
Graphics
class and introduces logarithmic plots in a separate file. This is in progress and I hope I have a patch to attach to this ticket soon.
comment:20 Changed 10 years ago by
Awesome. Just yesterday I had a feature request for log-log and semilog plots!
comment:21 Changed 10 years ago by
- Dependencies set to 12974
- Description modified (diff)
I added a patch to Graphics
class which introduces log plots. Some salient points
- I had to "disable" some tick formatting for log plots because matplotlib wasn't behaving well with the formatting that is done in
Graphics().maptplotlib()
(ex. the error in comment:11, out of memory error, etc) - The patch in this ticket relies on the patches in #12974 which is mostly a cleanup of the
Graphics
class. - In trying to implement my own class, I started to look at each of the matplotlib functions more carefully, and found out the reason(s) why setting the scale wasn't working (see point 1.). The result is that I could implement log scale right inside
Graphics
by carefully weeding out the corner cases. I hope I got all the corner cases.
Todo:
- A patch to
plot()
and other functions will take more time to implement. :( - Probably need to make sure that user does not specify tick formatters and locators which don't behave well with log plots.
- Feedback is welcome! I need to know if I missed something.
Example code:
p = plot(exp, 1, 10) p.set_scale('loglog') p.show() xd=range(-5,5); yd=[10**_ for _ in xd]; p=list_plot(zip(xd, yd),plotjoined=True) p.set_yscale('log', 2) # Set only y-axis to log and with base of log being 2. p.show()
comment:22 follow-up: ↓ 23 Changed 10 years ago by
Hmm.. there is still a problem if I modify the Graphics
class. It becomes impossible to add 2D and 3D graphics.
comment:23 in reply to: ↑ 22 Changed 10 years ago by
Replying to ppurka:
Hmm.. there is still a problem if I modify the
Graphics
class. It becomes impossible to add 2D and 3D graphics.
It was a silly thing. I just needed to reorder the check for ._*scale
to after the check for Graphics3d
in __add__()
. The updated patch now passes all doctests in sage/plot
! Also, SHOW_OPTIONS, matplotlib()
have two extra arguments: scale
, base
which are identical in behavior to the arguments in set_scale()
. So, now it is possible to do this:
p = plot(exp, 1, 10) p.show(scale=('loglog', 2))
comment:24 Changed 10 years ago by
- Dependencies changed from 12974 to #12974
comment:25 Changed 10 years ago by
- Description modified (diff)
- Status changed from needs_work to needs_review
Added another patch to other user facing functions. Actually most of the functions in sage.plot.*
work when scale=...
is passed as an argument while calling the function. Using the log scale makes sense in only a couple of them though, and I have added some documentation and examples for the cases I think are pertinent.
Finally, I added some extra functions {loglog,semilogx,semilogy}_plot
, and the corresponding list_plots. I think this should undergo a review now.
These set of patches passes all doctests in devel/sage/sage/plot
.
"Wrong" sage plot with log scale