Eric Blum
Eric Blum

Reputation: 784

compressing several elif statements

I've written some code to find how close the diffs of two time series are to each other and do this by finding the distance from the expected difference locations from the nearest actual change and assigning a score based on how far off it is.

In that code I have something that looks like this:

nearest_change = np.abs(actual_changes[actual_changes == change].index - time).min()

minutes_off = nearest_change.seconds/60
if minutes_off < 15:
  sum += 1
elif minutes_off < 30:
  sum += .8
elif minutes_off < 45:
  sum += .6
elif minutes_off < 60:
  sum += .4
elif minutes_off < 65:
  sum += .2

return sum / count

Is there a more pythonic and concise way to achieve this sort of scoring?

Upvotes: 0

Views: 377

Answers (3)

Mark Siddorn
Mark Siddorn

Reputation: 66

You can loop through each threshold and increment by 0.2 each time.

thresholds = [15, 30, 45, 60, 65]
for time_diff in thresholds:
    if minutes_off < time_diff:
        sum += .2
    else:
        break

Depending on how many you need, dynamically creating the threshold list might be worth it or not.

Upvotes: 1

Chris_Rands
Chris_Rands

Reputation: 41168

If your mappings are discrete and can't be described by a simple formula then you could create them with an OrderedDict:

from collections import OrderedDict
d = OrderedDict([(15,1),(30,.8),(45,.6),(60,.4),(65,.2)])
for key,value in d.items():
    if minutes_off < key:
            s += value
            break

Note, I renamed sum, as s to avoid clash with Python built-in function.

Upvotes: 1

CodenameLambda
CodenameLambda

Reputation: 1496

Yes, there is. I'd guess this one is better, although it uses more code to archieve the same effect and is slower. But it does avoid repetition.

class ScoreAccessDict(dict):
    def __init__(self, *args, f, **kwargs):
        self.f = f
        super().__init__(*args, **kwargs)

    def __getitem__(self, item):
        iterator = iter(self.keys())

        try:
            best = next(iterator)
        except StopIteration:
            raise KeyError(item)

        for k in iterator:
            if self.f(best, item) < self.f(k, item):
                best = k

        return super().__getitem__(best)

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

and to use it:

nearest_change = np.abs(actual_changes[actual_changes == change].index - time).min()

minutes_off = nearest_change.seconds / 60  # spaces between operands and the operator

d = ScoreAccessDict({15: 1, 30: 0.8, 45: 0.6, 60: 0.4, 65: 0.2, float('inf'): 0}, f = lambda x, y: x if x < y else -x)

return s + d[minutes_off]  # sum shouldn't be shadowed

But judging from your code, the values added should be calculated using a continuous function anyway. And that would be the most pythonic way.

Upvotes: 0

Related Questions