Context: My question comes from needing to iterate different np.arange
arrays. The main motivation is to calculate all the possible combinations between the arrays, let's say a
, b
, c
, x
, y
, z
where the last 3 (x
, y
, z
) can be arrays OR floats.
The reason behind is that every combination can be evaluated then in equations. Floats OR values are considered because x
, y
, z
represent parameters (that can be fixed or considered to have errors) and a
, b
, c
are variables. This is useful when wanting to evaluate a set of differential equations solutions. In a mathematical context some would say "the raison d'être is to explore the space of parameters".
To calculate every possible combination I know I can iterate using for-loops. The problem arises when considering the exceptions: as x
, y
, z
can be lists "L" or numbers "N", I find the cases:
#Different combinations:
options=[['N',1],['L',2]]
for i in options:
for j in options:
for k in options:
print(i[0]+j[0]+k[0]+' w/ total combinations: '+str(2*2*2*i[1]*j[1]*k[1]))
which are NNN, NNL, NLL, NLN, LNL, LLN, LNN, LLL. I've also calculated the length of the resulting possibilities, for the case of len()=2
arrays.
What is the code doing?: calculating every possibility. If there's a number instead of a list (or array) it's not iterated but appended constantly through every possibility for the other variables that do "change".
But the problem is: considering each case separatedly is unefficent and messy code. Question: how could I do it in a more general and compacted way?
You can check the code here:
import random
import numpy as np
import numbers
a = np.random.uniform(1,10,2)
b = np.random.uniform(1,10,2)
c = np.random.uniform(1,10,2)
w=[36] #this is a len(w)=1 list only to manage exceptions, but is equivalent to have a number instead of a list/array.
x = np.random.normal(2,1e-1,2) #This could be a float
y = np.random.normal(3,1e-1,2) #This could be a float
z = np.random.normal(4,1e-1,2) #This could be a float
#Now interate
def iter_func(args):
returned_array = [] #this is the output list-of-lists with all the combinations.
a,b,c,x,y,z = args #numbers or arrays; variables or parameters (w/ or w/o errors in a mathematical context)
for avals in a:
for bvals in b:
for cvals in c:
if isinstance(x,np.ndarray)==True:
for xvals in x:
if isinstance(y,np.ndarray)==True:
for yvals in y:
if isinstance(z,np.ndarray)==True:
for zvals in z:
returned_array.append([avals,bvals,cvals,xvals,yvals,zvals])
elif isinstance(z,numbers.Real)==True or isinstance(z,list)==True and len(z)==1:
returned_array.append([avals,bvals,cvals,xvals,yvals,z])
elif isinstance(y,numbers.Real)==True or isinstance(y,list)==True and len(y)==1:
returned_array.append([avals,bvals,cvals,xvals,y,z])
elif isinstance(x,numbers.Real)==True or isinstance(x,list)==True and len(x)==1:
if isinstance(y,np.ndarray)==True:
for yvals in y:
if isinstance(z,np.ndarray)==True:
for zvals in z:
returned_array.append([avals,bvals,cvals,x,yvals,zvals])
elif isinstance(z,numbers.Real)==True or isinstance(z,list)==True and len(z)==1:
returned_array.append([avals,bvals,cvals,x,yvals,z])
elif isinstance(y,numbers.Real)==True or isinstance(y,list)==True and len(y)==1:
if isinstance(z,np.ndarray)==True:
for zvals in z:
returned_array.append([avals,bvals,cvals,x,y,zvals])
elif isinstance(z,numbers.Real)==True or isinstance(z,list)==True and len(z)==1:
returned_array.append([avals,bvals,cvals,x,y,z])
return returned_array
#Now I'm testing different possibilities
#Case test: last 3 are lists of numbers
returned_array = iter_func([a,b,c,w,w,w])
print('total combinations: '+str(len(returned_array)))
for i in returned_array:
print(i)
print('\n')
#Case 1: NNN
returned_array = iter_func([a,b,c,w[0],w[0],w[0]])
print('total combinations: '+str(len(returned_array)))
for i in returned_array:
print(i)
print('\n')
#Case 2: NNL
returned_array = iter_func([a,b,c,w[0],w[0],z])
print('total combinations: '+str(len(returned_array)))
for i in returned_array:
print(i)
print('\n')
#Case 3: NLN
returned_array = iter_func([a,b,c,w[0],y,w[0]])
print('total combinations: '+str(len(returned_array)))
for i in returned_array:
print(i)
print('\n')
#Case 4: NLL
returned_array = iter_func([a,b,c,w[0],y,z])
print('total combinations: '+str(len(returned_array)))
for i in returned_array:
print(i)
print('\n')
#Case 5: LNN
returned_array = iter_func([a,b,c,x,w[0],w[0]])
print('total combinations: '+str(len(returned_array)))
for i in returned_array:
print(i)
print('\n')
#Case 6: LNL
returned_array = iter_func([a,b,c,x,w[0],z])
print('total combinations: '+str(len(returned_array)))
for i in returned_array:
print(i)
print('\n')
#Case 7: LLN
returned_array = iter_func([a,b,c,x,y,w[0]])
print('total combinations: '+str(len(returned_array)))
for i in returned_array:
print(i)
print('\n')
#Case 8: LLL
returned_array = iter_func([a,b,c,x,y,z])
print('total combinations: '+str(len(returned_array)))
for i in returned_array:
print(i)
print('\n')
with output:
total combinations: 8
[2.8336495994988162, 5.295916749347686, 4.929950792386235, [36], [36], [36]]
[2.8336495994988162, 5.295916749347686, 9.493663046965622, [36], [36], [36]]
[2.8336495994988162, 9.869611330430935, 4.929950792386235, [36], [36], [36]]
[2.8336495994988162, 9.869611330430935, 9.493663046965622, [36], [36], [36]]
[2.741768057298594, 5.295916749347686, 4.929950792386235, [36], [36], [36]]
[2.741768057298594, 5.295916749347686, 9.493663046965622, [36], [36], [36]]
[2.741768057298594, 9.869611330430935, 4.929950792386235, [36], [36], [36]]
[2.741768057298594, 9.869611330430935, 9.493663046965622, [36], [36], [36]]
total combinations: 8
[2.8336495994988162, 5.295916749347686, 4.929950792386235, 36, 36, 36]
[2.8336495994988162, 5.295916749347686, 9.493663046965622, 36, 36, 36]
[2.8336495994988162, 9.869611330430935, 4.929950792386235, 36, 36, 36]
[2.8336495994988162, 9.869611330430935, 9.493663046965622, 36, 36, 36]
[2.741768057298594, 5.295916749347686, 4.929950792386235, 36, 36, 36]
[2.741768057298594, 5.295916749347686, 9.493663046965622, 36, 36, 36]
[2.741768057298594, 9.869611330430935, 4.929950792386235, 36, 36, 36]
[2.741768057298594, 9.869611330430935, 9.493663046965622, 36, 36, 36]
total combinations: 16
[2.8336495994988162, 5.295916749347686, 4.929950792386235, 36, 36, 3.8501788450044114]
[2.8336495994988162, 5.295916749347686, 4.929950792386235, 36, 36, 3.895647492990764]
[2.8336495994988162, 5.295916749347686, 9.493663046965622, 36, 36, 3.8501788450044114]
[2.8336495994988162, 5.295916749347686, 9.493663046965622, 36, 36, 3.895647492990764]
[2.8336495994988162, 9.869611330430935, 4.929950792386235, 36, 36, 3.8501788450044114]
[2.8336495994988162, 9.869611330430935, 4.929950792386235, 36, 36, 3.895647492990764]
[2.8336495994988162, 9.869611330430935, 9.493663046965622, 36, 36, 3.8501788450044114]
[2.8336495994988162, 9.869611330430935, 9.493663046965622, 36, 36, 3.895647492990764]
[2.741768057298594, 5.295916749347686, 4.929950792386235, 36, 36, 3.8501788450044114]
[2.741768057298594, 5.295916749347686, 4.929950792386235, 36, 36, 3.895647492990764]
[2.741768057298594, 5.295916749347686, 9.493663046965622, 36, 36, 3.8501788450044114]
[2.741768057298594, 5.295916749347686, 9.493663046965622, 36, 36, 3.895647492990764]
[2.741768057298594, 9.869611330430935, 4.929950792386235, 36, 36, 3.8501788450044114]
[2.741768057298594, 9.869611330430935, 4.929950792386235, 36, 36, 3.895647492990764]
[2.741768057298594, 9.869611330430935, 9.493663046965622, 36, 36, 3.8501788450044114]
[2.741768057298594, 9.869611330430935, 9.493663046965622, 36, 36, 3.895647492990764]
Q: is this called "variable-depth for-loops", "nested for-loops"? Any comment on this would be highly appreciated.
I'm not very familiar with Python but Q: I think it could be done in a simplier way. What would you suggest? Any hint? Maybe a way to do it with pandas, numpy or in a big list comprehension? (I do not really know if possible). I apologize if the question is vague. I'd be happy if you suggest anything to make the question clearer.