Python List Comprehensions Help

That’s because you can’t use an else condition there (e.g. you can’t append a zero to the list with that syntax).

In which case, I suppose if it works, then it works, though I would probably add some parenthesis to make it clearer what you’re trying to do.

totals = [(a/b) if b else 0 for a,b in zip(net,eff)]

The difference is what the if..else statement is doing. In the first one, the comprehension looks at b, if it is not 0 it appends the result of (a/b), if it is 0 then it moves on to the next iteration. In the second one, the comprehension looks at b if it is not 0 it appends the result of (a/b), if it is 0 then it appends 0.

Throw this in script console to see the difference.

totals = [a/b for a,b in zip(net,eff) if b]

print totals

totals = []
for a,b in zip(net,eff):
    if b:
        totals.append(a/b)

print totals

totals = [(a/b) if b else 0 for a,b in zip(net,eff)]

print totals

totals = []

for a,b in zip(net,eff):
    if b:
        totals.append(a/b)
    else:
        totals.append(0)
3 Likes

To make it clearer what the difference between [x if a else y for x in iterable] and [x for x in iterable if a], i’d suggest putting it like this:

comprehension = [a/b for a, b in zip(net, eff) if b]

loop = []
for a, b in zip(net, eff):
    if b:
        loop.append(a/b)

comprehension == loop


comprehension = [(a/b) if b else 0 for a, b in zip(net, eff)]

loop = []
for a, b in zip(net, eff):
    loop.append(a/b if b else 0)

comprehension == loop

In the first case, the condition is a filter, it determines whether anything is appended.
In the second case, the condition is a ‘processing’, it determines what is appended.

2 Likes

Thanks
I used this:

def divideZero(a,b):
		return 0 if (a and b)==0 else a/b 

That’s… a weird way to do it.

Why are you comparing (a and b) to 0 ? In this case, it works, because dividing 0 returns 0.
But let’s check this step by step:

  • (a and b)
    This returns 1 if both a and b are non-zero.
  • (a and b) == 0
    Left expression compared to 0, which means the whole thing will return false if either a or b are 0.
  • 0 if (a and b)==0 else a/b
    if either a or b are 0, return 0, otherwise return a/b

It does what you think it does, but… probably not how you thought it did it.

you could write it like this

# you don't need to compare to 0, a and b will be evaluated as a boolean
return 0 if a and b else a/b
# checking only if b is 0
return 0 if b == 0 else a/b
# which can also be shortened, as 0 will evaluate to false
return a/b if b else 0
# or the way it was suggested earlier, though I don't quite like using try/catch for this
try:
    return a/b
except ZeroDivisionError:
    return 0

edit: To be clear, what I thought was weird is the (a and b) part.

1 Like

Yeah, I had to think about all of that when I read it as well.

I would probably just avoid the inline if, were I going to go to the trouble of writing a function, and just write it like this:

def handleDivdeByZero(a,b):
    if b:
        return a/b
    return 0

Also, as a note, I don’t really like divideZero as a function name because it is too ambiguous as to what the function actually does. But that’s just me, as you were.

1 Like

I used if (a and b) !=0 because it is easy for me to read.
I used the inline because it is the fastest format I read about, though faster as if a and b.
Don’t you have to address both when a is zero and b is zero, not just b?


This new problem hurts my head.

vals=system.dataset.toPyDataSet(value)
def handleDivideZero(a,b):
		return 0 if (a and b)==0 else a/b 
infeed= list(vals[0])[1::5]  
infeed= sum(infeed)
outfeed= list(vals[0])[2::5]
outfeed=sum(outfeed)
uptime=list(vals[0])[3::5]#*1.0/60
mV=list(vals[0])[5::5]	
rateXUptime=[a*b for a,b in zip(uptime,mV)]
rateXUptime=sum(rateXUptime)
uptime=sum(uptime)*1.0/60
downtime=list(vals[0])[4::5]
downtime=sum(downtime)*1.0/60	
duration=uptime+downtime		
avail=	handleDivideZero(float(uptime),float(duration))
perf= 	handleDivideZero(float(Infeed),rateXUptime)
qual=	handleDivideZero(Outfeed,float(Infeed))
value="{:.1%}".format(avail*perf*qual)
return value

I need eliminate all the values in lists when either the infeed or outfeed for a machine are less than 100 for that machine.

The parameters are paired in a groups of 5 columns per machine, so that is why I step by 5.
Got them from a historian binding that is property bound on this label where I calculate OEE.

Do I get the lists, then zip them together to do the excluding easier?
Is that like for sets of zips(a,b)?
Then when I have them zipped, make a new list excluding the ones when infeed and outfeed are less than 100?

Has to be a better way. Is there?

0/x is always 0, there's no error there. Which is also why your line works: you return 0 when a == 0, which is... not wrong.

2 Likes

There's a piece of code missing:

1 Like

Thanks, edited it.

Why are you doing this division ?
You’re not using the result - at least not in the code you posted.

1 Like

I will edit it more.

I didn’t think I needed all of it. Probably riddled with errors and typos now.
Probably I should have used the four zips, did the new lists, and gone that route.

I assume this is also supposed to be in a for loop? Otherwise, step by 5 doesn’t make any since.

1 Like

I didn’t use any for loops.

It was working great. I have to remove machines that aren’t making over 100 on infeed or outfeed.
(Sometimes they get outfeed as they can drop in partials effectively.)

I usually don’t use enumerate for example though, so I thought that there would be a good way that I could learn to do the excluding.

Why not ?
I mean, [::5] will make a new list with every fifth item, which might be what he wants to do.
Speaking of what he wants to do… I have no idea, as usual :X

@zacharyw.larson , I think you need to be more explicit about what you’re trying to do ;p

I’m going home for today, I’ll check your issue tomorrow if it’s not solved yet.

2 Likes

I need to eliminate all the values in lists when either the infeed or outfeed for a machine are less than 100 for that machine.

The parameters are paired in groups of 5 columns per machine, so that is why I step by 5.
Got them from a historian binding that is property bound on this label where I calculate OEE.

Column 0 is the timestamp.
Columns [1::5] all infeeds.
Columns [5::5] all mVs.

I think for today I can just map them with four zips and I can probably figure it out.
I am not sure how to address them once I have zipped them, though I think that way would work.
There is probably some way though with enumerate that might be faster and simpler.

update

infeed2=	list(vals[0])[1::5]	#0 element is for timestamp, 5 params from each machine there after
outfeed2=	list(vals[0])[2::5]	
uptime2=	list(vals[0])[3::5]
downtime2=	list(vals[0])[4::5]
mV2=		list(vals[0])[5::5]	
bigList= zip(infeed2,outfeed2,uptime2,downtime2,mV2)
	
value=bigList

I did not realize it would be just one zip.
Probably easier to explain what I am trying to do if I can get it working in a clumsy way.

update

qual2=[a/b if (a<100 and b<100) for a,b,c,d,e in zip(infeed2,outfeed2,uptime2,downtime2,maxV2)]

This didn't work, parsing error. I am so bad at nested list comprehensions.

qual2=[[a[0]/a[1] if (a<100 and a<100) for a in b]    for b in bigList]

This did not work, parse error. I will go to the script console to work with lists in lists this afternoon. I think this was talked about in this thread before I re-reading it.

If you're using elements, you won't need the 'b' pat of the comprehension.

qual2=[a[0]/a[1] if (a[0]<100 and a[1]<100) for a in bigList]
1 Like
infeed=[1000,2000,3000,0,5000,90]
outfeed=[1001,2001,0,4001,91,6001]
param3=[1.12,1.22,1.32,0,1.52,1.62]
param4=[3.1,3.2,3.3,3.4,3.5,3.6]
param5=[104,204,304,404,504,604]
bigList= zip(infeed, outfeed, 
param3,param4,param5)

print bigList
test=[a[0]/a[1] if (a[0]>100 and a[1]>100) for a in bigList]
print test

'#desired output [almost 1, almsot 1, ~100, ~1/100]'
#desired output [almost 1, almsot 1]

I got a no viable alternative after if.
Also, I had “less than” before, but I need “greater than”.

It also just occurred to me that it is easier to explain what I want to get in terms of desired output and in formatting that is used in the script console.

1 Like

What is the expected output if either are not above 100?

Thanks for the sample values, btw. That helps. :slight_smile:

1 Like

I want to omit the values when a value of infeed or outfeed are below 100, and I think I messed up my example haha.

#desired output [almost 1, almost 1]

Edited the post.

I would approach this differently.

Comprehensions aren’t always the answer, even when they can accomplish a task.

When I have a list of things that needs to be grouped I use this function (note that you can use the zip syntax directly, but I find a function name more readable):

def grouper(n,iterable):
    return zip(*[iter(iterable)] * n)

This will take a list of elements, group them to a length of n and return a list of tuples of those groups.

Then to accomplish what (I believe) your code is doing, I would write it like this:

def doCalcs():
 
	vals = system.dataset.toPyDataSet(value)
	
	sums = {'infeed':0,'outfeed':0,'uptime':0,'rateXUptime':0,'downtime':0}
	
	for infeed,outfeed,uptime,downtime,mV in grouper(5,vals[1:]):
		if infeed >= 100 and outfeed >= 100:
			sums['infeed'] += infeed
			sums['outfeed'] += outfeed
			sums['uptime'] += uptime / 60.0
			sums['rateXUptime'] += uptime * mV
			sums['doentime'] += downtime / 60.0
	
	duration = sums['uptime'] + sums['downtime']
	
	avail = sums['uptime']/duration if duration else 0
	perf = sums['infeed']/sums['rateXUptime'] if sums['rateXUptime'] else 0
	qual = sums['outfeed']/sums['infeed'] if sums['infeed'] else 0
	
	return "{:.1%}".format(avail*perf*qual)
2 Likes