Question

I'm doing some pyplotting of generic data and converting it from a power value to a dB value. Due to the system these values come from, 0 is used as a 'the useful data ends here' indicator (the nature of the mathematics, not a defined value).

My usual way of dealing with these is wrapping the conversion in a try/except and returning a default 'low' value, eg

def f(value):
    try:
        return convert(value)
    except ValueError:
        return -140 #implementation specific, don't worry

This is fine and dandy for 90% of use within my framework, except when it comes to graphing.

I'm lazy so what I do at the minute is:

pl.plot(xvals,map(f,yvals))

This draws the data correctly, and when the data ends, plunges off a cliff, which is the expected behaviour. But what I would like to happen is for the graph to just end when it meets a ValueError exception and do away with f() altogether.

Other than breaking the map up into a loop, anyone got any brilliant ideas?

UPDATES:

I'm using Pylab / MatplotLib "Endpoint" is execution dependent; sometimes the above doesn't matter atall as there are no 'bad' values. This is all in an effort for me to be lazy and use matplotlibs graph scaling instead of resetting dynamic ylim's based on the min of the ydata (which I don't do atm, just ylim(-140) in this case.)

Vaguely Important Update On Answer: unutbu's answer is what I'll actually be using for my implementation, due to (not mentioned in the question dependencies), as raising a StopIteration in this regularly used function wreaks havoc with unrelated-to-the-question control logic, without putting all of those other instances in try-excepts; sometimes -inf makes more sense than you would think.

Thanks to everyone for being awesomely fast, and I apologise to unutbu for the QuestionFail.

Was it helpful?

Solution

Perhaps there's some trick in the plotting library, but a much better options seems not generating such data to begin with. It's not that map saves you thirty lines of code...

Use itertools.takewhile(lambda y: y != NO_VALUE, (f(y) for y in yvals)) (and wrap it in a call to list if the plotting library requires a list instead of an iterable).

Edit: I had an even better idea: In the wrapper, add

except ValueError:
    raise StopIteration

That's the exception signaling "end of iterale", and map respects it.

OTHER TIPS

If you are using matplotlib, then it implies you have numpy installed.

Since you are converting to dB, it sounds like you might be taking a log. In that case, np.log(0) = -inf.

You can mask nans and infs with the numpy function np.ma.masked_invalid, and matplotlib can plot masked arrays. For example,

import matplotlib.pyplot as plt
import numpy as np

xvals=np.arange(100)
yvals=np.cumsum(np.random.random(100))
yvals[-10:]=0
yvals=np.log(yvals)

print(yvals[-10:])
# [-Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf -Inf]

yvals=np.ma.masked_invalid(yvals)
plt.plot(xvals,yvals)
plt.show()

yields enter image description here

Notice that the graph ends with xval equal to 89, since the last 10 values of yval are masked.

You're needlessly limiting yourself by refusing to use a looping construct.

In your situation you want to stop iterating over data when a certain value is reached, that is exactly the purpose of forloops and breaks

yvals_ = []
for y in yvals:
    y_ = f(y)
    if y_ == -140:
        break
    else:
        yvals_.append(y_)

p1.plot(xvals[:len(yvals_)],yvals_)

It seems like you have data, and you don't want to plot the last point. So what about not plotting it?

pl.plot(xvals[:-1], map(f, yvals)[:-1])
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top