Finding where a value lands between two numbers in Python - python

I have a problem where I need to determine where a value lands between other values. This is an awful long question...but its a convoluted problem (at least to me).
The simplest presentation of the problem can be seen with the following data:
I have a value of 24.0. I need to determine where that value lands within six 'ranges'. The ranges are: 10, 20, 30, 40, 50, 60. I need to calculate where along the ranges, the value lands. I can see that it lands between 20 and 30. A simple if statement can find that for me.
My if statement for checking if the value is between 20 and 30 would be:
if value >=20 and value <=30:
Pretty simple stuff.
What I'm having trouble with is when I try to rank the output.
As an example, let's say that each range value is given an integer representation. 10 =1, 20=2, 30=3, 40=4, 50=5, 60=6, 70=7. Additionally, lets say that if the value is less than the midpoint between two values, it is assigned the rank output of the lower value. For example, my value of 24 is between 20 and 30 so it should be ranked as a "2".
This in and of itself is fairly straightforward with this example, but using real world data, I have ranges and values like the following:
Value = -13 with Ranges = 5,35,30,25,-25,-30,-35
Value = 50 with Ranges = 5,70,65,60,40,35,30
Value = 6 with Ranges = 1,40,35,30,5,3,0
Another wrinkle - the orders of the ranges matter. In the above, the first range number equates to a ranking of 1, the second to a ranking of 2, etc as I mentioned a few paragraphs above.
The negative numbers in the range values were causing trouble until I decided to use a percentile ranking which gets rid of the negative values all together. To do this, I am using an answer from Map each list value to its corresponding percentile like this:
y=[stats.percentileofscore(x, a, 'rank') for a in x]
where x is the ranges AND the value I'm checking. Running the value=6 values above through this results in y being:
x = [1, 40, 35, 30, 5, 3, 0, 6]
y=[stats.percentileofscore(x, a, 'rank') for a in x]
Looking at "y", we see it as:
[25.0, 100.0, 87.5, 75.0, 50.0, 37.5, 12.5, 62.5]
What I need to do now is compare that last value (62.5) with the other values to see what the final ranking will be (rankings of 1 through 7) according to the following ranking map:
1=25.0
2=100.0
3=87.5
4=75.0
5=50.0
6=37.5
7=12.5
If the value lies between two of the values, it should be assigned the lower rank. In this example, the 62.5 value would have a final ranking value of 4 because it sits between 75.0 (rank=4) and 50.0 (rank=5).
If I take 'y' and break it out and use those values in multiple if/else statements it works for some but not all (the -13 example does not work correctly).
My question is this:
How can I programmatically analyze any value/range set to find the final ranking without building an enormous if/elif structure? Here are a few sample sets. Rankings are in order of presentation below (first value in Ranges =1 , second = 2, etc etc)
Value = -13 with Ranges = 5, 35, 30, 25, -25, -30, -35 --> Rank = 4
Value = 50 with Ranges = 5, 70, 65, 60, 40, 35, 30 --> Rank = 4
Value = 6 with Ranges = 1, 40, 35, 30, 5, 3,0 --> Rank = 4
Value = 24 with Ranges = 10, 20, 30, 40, 50, 60, 70 --> Rank = 2
Value = 2.26 with Ranges = 0.1, 0.55, 0.65, 0.75, 1.75, 1.85, 1.95 --> Rank = 7
Value = 31 with Ranges = 10, 20, 30, 40, 60, 70, 80 --> Rank = 3
I may be missing something very easy within python to do this...but I've bumped my head on this wall for a few days with no progress.
Any help/pointers are appreciated.

def checker(term):
return term if term >= 0 else abs(term)+1e10
l1, v1 = [5, 35, 30, 25, -25, -30, -35], -13 # Desired: 4
l2, v2 = [5, 70, 65, 60, 40, 35, 30], 50 # Desired: 4
l3, v3 = [1, 40, 35, 30, 5, 3, 0], 6 # Desired: 4
l4, v4 = [10, 20, 30, 40, 50, 60, 70], 24 # Desired: 2
l5, v5 = [0.1, 0.55, 0.65, 0.75, 1.75, 1.85, 1.95], 2.26 # Desired: 7
l6, v6 = [10, 20, 30, 40, 60, 70, 80], 31 # Desired: 3
Result:
>>> print(*(sorted(l_+[val], key=checker).index(val) for
... l_, val in zip((l1,l2,l3,l4,l5,l6),(v1,v2,v3,v4,v5,v6))), sep='\n')
4
4
4
2
7
3

Taking the first example of -13.
y = [5, 35, 30, 25, -25, -30, -35]
value_to_check = -13
max_rank = len(y) # Default value in case no range found (as per 2.26 value example)
for ii in xrange(len(y)-1,0,-1):
if (y[ii] <= value_to_check <= y[ii-1]) or (y[ii] >= value_to_check >= y[ii-1]):
max_rank = ii
break
>>> max_rank
4
In function form:
def get_rank(y, value_to_check):
max_rank = len(y) # Default value in case no range found (as per 2.26 value example)
for ii in xrange(len(y)-1,0,-1):
if (y[ii] <= value_to_check <= y[ii-1]) or (y[ii] >= value_to_check >= y[ii-1]):
max_rank = ii
break
return max_rank
When you call:
>>> get_rank(y, value_to_check)
4

This correctly finds the answer for all your data:
def get_rank(l,n):
mindiff = float('inf')
minindex = -1
for i in range(len(l) - 1):
if l[i] <= n <= l[i + 1] or l[i + 1] <= n <= l[i]:
diff = abs(l[i + 1] - l[i])
if diff < mindiff:
mindiff = diff
minindex = i
if minindex != -1:
return minindex + 1
if n > max(l):
return len(l)
return 1
>>> test()
[5, 35, 30, 25, -25, -30, -35] -13 Desired: 4 Actual: 4
[5, 70, 65, 60, 40, 35, 30] 50 Desired: 4 Actual: 4
[1, 40, 35, 30, 5, 3, 0] 6 Desired: 4 Actual: 4
[10, 20, 30, 40, 50, 60, 70] 24 Desired: 2 Actual: 2
[0.1, 0.55, 0.65, 0.75, 1.75, 1.85, 1.95] 2.26 Desired: 7 Actual: 7
[10, 20, 30, 40, 60, 70, 80] 31 Desired: 3 Actual: 3
For completeness, here is my test() function, but you only need get_rank for what you are doing:
>>> def test():
lists = [[[5, 35, 30, 25, -25, -30, -35],-13,4],[[5, 70, 65, 60, 40, 35, 30],50,4],[[1, 40, 35, 30, 5, 3,0],6,4],[[10, 20, 30, 40, 50, 60, 70],24,2],[[0.1, 0.55, 0.65, 0.75, 1.75, 1.85, 1.95],2.26,7],[[10, 20, 30, 40, 60, 70, 80],31,3]]
for l,n,desired in lists:
print l,n,'Desired:',desired,'Actual:',get_rank(l,n)

Related

Knapsack with constraint same value

I am Solving a Multiple Knapsacks Problem in python :
The problem is to pack a subset of the items into five bins, each of which has a maximum capacity of 100, so that the total packed value is a maximum.
data = {}
data['weights'] = [
48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36
]
data['values'] = [
10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25
]
assert len(data['weights']) == len(data['values'])
data['num_items'] = len(data['weights'])
data['all_items'] = range(data['num_items'])
data['bin_capacities'] = [100, 100, 100, 100, 100]
data['num_bins'] = len(data['bin_capacities'])
data['all_bins'] = range(data['num_bins'])
The data includes the following:
weights: A vector containing the weights of the items.
values: A vector containing the values of the items.
capacities: A vector containing the capacities of the bins.
The following code declares the MIP solver.
solver = pywraplp.Solver.CreateSolver('SCIP')
if solver is None:
print('SCIP solver unavailable.')
return
The following code creates the variables for the problem.
# x[i, b] = 1 if item i is packed in bin b.
x = {}
for i in data['all_items']:
for b in data['all_bins']:
x[i, b] = solver.BoolVar(f'x_{i}_{b}')
The following code defines the constraints for the problem:
Each x[(i, j)] is a 0-1 variable, where i is an item and j is a bin. In the solution, x[(i, j)] will be 1 if item i is placed in bin j, and 0 otherwise.
# Each item is assigned to at most one bin.
for i in data['all_items']:
solver.Add(sum(x[i, b] for b in data['all_bins']) <= 1)
# The amount packed in each bin cannot exceed its capacity.
for b in data['all_bins']:
solver.Add(
sum(x[i, b] * data['weights'][i]
for i in data['all_items']) <= data['bin_capacities'][b])
# Maximize total value of packed items.
objective = solver.Objective()
for i in data['all_items']:
for b in data['all_bins']:
objective.SetCoefficient(x[i, b], data['values'][i])
objective.SetMaximization()
I Try to add another contraint which consist that all items in the same bag should have the same weight, but I struggle to do it in python . Can you help me to code it?
Thanks
Just a sketch. Think about it... Maybe fix it (it's just an idea).
What you have: Assignment-matrix A items <-> bins
item 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0
1
2
3
4
<=1 <=1 <=1 ...
bin
What you should add: Assignment-matrix B item-classes <-> bins
Item-class: set of all items of same weight
e.g.:
import numpy as np
weights = np.array([48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36])
unique_weights = set(weights)
partition = [np.where(weights == i)[0] for i in unique_weights]
# [array([ 3, 4, 8, 13, 14]), array([ 2, 6, 7, 12]), array([0, 5]), array([9]), array([ 1, 10, 11])]
Additional assignment-matrix:
item-class 0 1 2 3 4
0 <=1
1 <=1
2 <=1
3 <=1
4 <=1
bin
Then: Link/Channel those
Sum of assigned items to bin of class C is 0 if class C is not UNIQUELY assigned to this bin, unbounded (big-M) otherwise.
Something like:
for b in range(n_bins):
for c in range(n_partitions):
sum(A[b, all_indices_of_items_in_class(c)]) <= B[b, c] * len(all_indices_of_items_in_class(c))
Remarks
Obviously, this is more of some addition to the status quo.
It might make more sense to not model A as big boolean-mat, but just introduce cardinality-constraints (how much identical items are picked) as we already have variables expressing "what" we pick.

Seperate array into three new arrays using inequalities in Python

I am trying to split an array into three new arrays using inequalities.
This will give you an idea of what I am trying to achieve:
measurement = [1, 5, 10, 13, 40, 43, 60]
for x in measurement:
if 0 < x < 6:
small = measurement
elif 6 < x < 15:
medium = measurement
else
large = measurement
Intended Output:
small = [1, 5]
medium = [10, 13]
large = [40, 43, 60]
If your array is sorted, you can do :
measurement = [1, 5, 10, 13, 40, 43, 60]
one_third = len(measurement) // 3
two_third = (2 * len(measurement)) // 3
small = measurement[:one_third]
medium = measurement[one_third : two_thirds]
large = measurement[two_thirds:]
You could easily generalize to any number of split with a loop. Not sure if you wanted explicitly those inequalities or just split with the array in three. If its the first one, my answer is not right
You can use numpy:
arr = np.array(measurement)
small = arr[(arr>0)&(arr<6)] # array([1, 5])
medium = arr[(arr>6)&(arr<15)] # array([10, 13])
large = arr[(arr>15)] # array([40, 43, 60])
You can also use dictionary:
d = {'small':[], 'medium':[], 'large':[]}
for x in measurement:
if 0 < x < 6:
d['small'].append(x)
elif 6 < x < 15:
d['medium'].append(x)
else:
d['large'].append(x)
Output:
{'small': [1, 5], 'medium': [10, 13], 'large': [40, 43, 60]}
With the bisect module you can do something along these lines:
from bisect import bisect
breaks=[0,6,15,float('inf')]
buckets={}
m = [1, 5, 10, 13, 40, 43, 60]
for e in m:
buckets.setdefault(breaks[bisect(breaks, e)], []).append(e)
You then have a dict of lists matching what you are looking for:
>>> buckets
{6: [1, 5], 15: [10, 13], inf: [40, 43, 60]}
You can also form tuples of your break points and list that will become a dict to form the sub lists:
m = [1, 5, 10, 13, 40, 43, 60]
buckets=[('small',[]), ('medium',[]), ('large',[]), ('other',[])]
breaks=[(0,6),(6,15),(15,float('inf'))]
for x in m:
buckets[
next((i for i,t in enumerate(breaks) if t[0]<=x<t[1]), -1)
][1].append(x)
>>> dict(buckets)
{'small': [1, 5], 'medium': [10, 13], 'large': [40, 43, 60], 'other': []}

How to print the sum of the current and previous element in a list

I am trying to iterate through a list of numbers and print the sum of the current element and the previous element using python. For example,
Given numbers = [5,10,15,20,25,30,30], the output should be 5, 15, 25, 35, 45, 55, 60,. This is the following code that I have tried, it is very close to the answer but the first element is wrong.
numbers = [5, 10, 15, 20, 25, 30, 30]
i = 0
for x in range(1, 8):
print(numbers[i] + numbers[i - 1], end=", ")
i += 1
I am getting the output 35, 15, 25, 35, 45, 55, 60,. What am I doing wrong?
You can pair adjacent items of numbers by zipping it with itself but padding one with a 0, so that you can iterate through the pairs to output the sums in a list comprehension:
[a + b for a, b in zip([0] + numbers, numbers)]
or by mapping the pairs to the sum function:
list(map(sum, zip([0] + numbers, numbers)))
Both would return:
[5, 15, 25, 35, 45, 55, 60]
You are starting at index 0, where it seems your intended output starts at index 1:
Here is a better solution:
numbers = [5, 10, 15, 20, 25, 30, 30]
for i in range(len(numbers)):
if i == 0:
print(numbers[i])
else:
print(numbers[i - 1] + numbers[i])
Outputs:
5
15
25
35
45
55
60
This should work:
numbers = [5, 10, 15, 20, 25, 30, 30]
output = [numbers[i]+numbers[i-1] if i > 0 else numbers[i] for i in range(len(numbers))]
print(output)
You are starting at i = 0, so the first number you are adding is the 0 and the -1 (the last one, in this case). That's why you are getting the 35 (5+30).
This list comprehension works:
numbers = [5, 10, 15, 20, 25, 30, 30]
output = [value + numbers[i-1] if i else value for i, value in enumerate(numbers)]
print(output)
>>> [5, 15, 25, 35, 45, 55, 60]
Cheat, and add a [0] at the start to prevent the first sum to be wrong.
You'll run into problems at the end, though, because then the list in the enumerate is one item longer than the original, so also clip off its last number:
print ([a+numbers[index] for index,a in enumerate([0]+numbers[:-1])])
Result:
[5, 15, 25, 35, 45, 55, 60]
If you want to see how it works, print the original numbers before addition:
>>> print ([(a,numbers[index]) for index,a in enumerate([0]+numbers[:-1])])
[(0, 5), (5, 10), (10, 15), (15, 20), (20, 25), (25, 30), (30, 30)]
The enumerate loops over the changed list [0, 5, 15, .. 55], where everything is shifted up a place, but numbers[index] still returns the correct index from the original list. Adding them up yields the correct result.

Find largest value from multiple colums in each group of row index in Python, arrange those values diagonally in matrix, and find determinant

I am new to Python. I want to find the largest values from all the columns for repetitive row indexes (i.e. 5 to 130), and also show its row and column index label in output.The largest values should be absolute. (Irrespective of + or - sign). There should not be duplicates for row indexes in different groups.
After finding largest from each group,I want to arrange those values diagonally in square matrix. Then fill the remaining array with the corresponding values of indexes for each group from the main dataframe and find its Determinant.
df=pd.DataFrame(
{'0_deg': [43, 50, 45, -17, 5, 19, 11, 32, 36, 41, 19, 11, 32, 36, 1, 19, 7, 1, 36, 10],
'10_deg': [47, 41, 46, -18, 4, 16, 12, 34, -52, 31, 16, 12, 34, -71, 2, 9, 52, 34, -6, 9],
'20_deg': [46, 43, -56, 29, 6, 14, 13, 33, 43, 6, 14, 13, 37, 43, 3, 14, 13, 25, 40, 8],
'30_deg': [-46, 16, -40, -11, 9, 15, 33, -39, -22, 21, 15, 63, -39, -22, 4, 6, 25, -39, -22, 7]
}, index=[5, 10, 12, 101, 130, 5, 10, 12, 101, 130, 5, 10, 12, 101, 130, 5, 10, 12, 101, 130]
)
Data set :
Expected Output:
My code is showing only till output 1.
Actual Output:
Code:
df = pd.read_csv ('Matrixfile.csv')
df = df.set_index('Number')
def f(x):
x1 = x.abs().stack()
x2 = x.stack()
x = x2.iloc[np.argsort(-x1)].head(1)
return x
groups = (df.index == 5).cumsum()
df1 = df.groupby(groups).apply(f).reset_index(level=[1,2])
df1.columns = ['Number','Angle','Value']
print (df1)
df1.to_csv('Matrix_OP.csv', encoding='utf-8', index=True)
I am not sure about #piRSquared output from what I understood from your question. There might be some errors in there, for instance, in group 2, max(abs(values)) = 52 (underline in red in picture) but 41 is displayed on left...
Here is a less elegant way of doing it but maybe easier for you to understand :
import numpy as np
# INPUT
data_dict ={'0_deg': [43, 50, 45, -17, 5, 19, 11, 32, 36, 41, 19, 11, 32, 36, 1, 19, 7, 1, 36, 10],
'10_deg': [47, 41, 46, -18, 4, 16, 12, 34, -52, 31, 16, 12, 34, -71, 2, 9, 52, 34, -6, 9],
'20_deg': [46, 43, -56, 29, 6, 14, 13, 33, 43, 6, 14, 13, 37, 43, 3, 14, 13, 25, 40, 8],
'30_deg': [-46, 16, -40, -11, 9, 15, 33, -39, -22, 21, 15, 63, -39, -22, 4, 6, 25, -39, -22, 7],
}
# Row idx of a group in this list
idx = [5, 10, 12, 101, 130]
# Getting some dimensions and sorting the data
row_idx_length = len(idx)
group_length = len(data_dict['0_deg'])
number_of_groups = len(data_dict.keys())
idx = idx*number_of_groups
data_arr = np.zeros((group_length,number_of_groups),dtype=np.int32)
#
col = 0
keys = []
for key in sorted(data_dict):
data_arr[:,col] = data_dict[key]
keys.append(key)
col+=1
def get_extrema_value_group(arr):
# function to find absolute extrema value of a 2d array
extrema = 0
for i in range(0, len(arr)):
max_value = max(arr[i])
min_value = min(arr[i])
if (abs(min_value) > max_value) and (abs(extrema) < abs(min_value)):
extrema = min_value
elif (abs(min_value) < max_value) and (abs(extrema) < max_value):
extrema = max_value
return extrema
# For output 1
max_values = []
for i in range(0,row_idx_length*number_of_groups,row_idx_length):
# get the max value for the current group
value = get_extrema_value_group(data_arr[i:i+row_idx_length])
# get the row and column idx associated with the max value
idx_angle_number = np.nonzero(abs(data_arr[i:i+row_idx_length,:])==value)
print('Group number : ' + str(i//row_idx_length+1))
print('Number : '+ str(idx[idx_angle_number[0][0]]))
print('Angle : '+ keys[idx_angle_number[1][0]])
print('Absolute extrema value : ' + str(value))
print('------')
max_values.append(value)
# Arrange those values diagonally in square matrix for output 2
A = np.diag(max_values)
print('A = ' + str(A))
# Fill A with desired values
for i in range(0,number_of_groups,1):
A[i,0] = data_arr[i*row_idx_length+2,2] # 20 deg 12
A[i,1:3] = data_arr[i*row_idx_length+3,1] # x2 : 10 deg 101
A[i,3] = data_arr[i*row_idx_length+1,1] # 10 deg 10
# Final output
# replace the diagonal of A with max values
# get the idx of diag
A_di = np.diag_indices(number_of_groups)
# replace with max values
A[A_di] = max_values
print ('A = ' + str(A))
# Compute determinant of A
det_A = np.linalg.det(A)
print ('det(A) = '+str(det_A))
Output 1:
Group number : 1
Number : 12
Angle : 20_deg
Absolute extrema value : -56
------
Group number : 2
Number : 101
Angle : 10_deg
Absolute extrema value : -52
------
Group number : 3
Number : 101
Angle : 10_deg
Absolute extrema value : -71
------
Group number : 4
Number : 10
Angle : 10_deg
Absolute extrema value : 52
------
Output 2 :
A = [[-56 0 0 0]
[ 0 -52 0 0]
[ 0 0 -71 0]
[ 0 0 0 52]]
Output 3 :
A = [[-56 -18 -18 41]
[ 33 -52 -52 12]
[ 37 -71 -71 12]
[ 25 -6 -6 52]]
det(A) = -5.4731330578761246e-11

Finding max and min indices in lists in Python

I have a list that looks like:
trial_lst = [0.5, 3, 6, 40, 90, 130.8, 129, 111, 8, 9, 0.01, 9, 40, 90, 130.1, 112, 108, 90, 77, 68, 0.9, 8, 40, 90, 92, 130.4]
The list represents a series of experiments, each with a minimum and a maximum index. For example, in the list above, the minimum and maximum would be as follows:
Experiment 1:
Min: 0.5
Max: 130.8
Experiment 2:
Min: 0.01
Max: 130.1
Experiment 3:
Min: 0.9
Max: 103.4
I obtained the values for each experiment above because I know that each
experiment starts at around zero (such as 0.4, 0.001, 0.009, etc.) and ends at around 130 (130, 131.2, 130.009, etc.). You can imagine a nozzle turning on and off. When it turns on, the pressure rises and as it's turned off, the pressure dips. I am trying to calculate the minimum and maximum values for each experiment.
What I've tried so far is iterating through the list to first mark each index as max, but I can't seem to get that right.
Here is my code. Any suggestions on how I can change it?
for idx, item in enumerate(trial_lst):
if idx > 0:
prev = trial_lst[idx-1]
curr = item
if prev > curr:
result.append((curr, "max"))
else:
result.append((curr, ""))
I am looking for a manual way to do this, no libraries.
Use the easiest way ( sort your list or array first ):
trial_lst = [0.5, 3, 6, 40, 90, 130.8, 129, 111, 8, 9, 0.01, 9, 40, 90, 130.1, 112, 108, 90, 77, 68, 0.9, 8, 40, 90, 92, 130.4]
trial_lst.sort(key=float)
for count, items in enumerate(trial_lst):
counter = count + 1
last_object = (counter, trial_lst[count], trial_lst[(len(trial_lst)-1) - count])
print( last_object )
You can easily get the index of the minimum value using the following:
my_list.index(min(my_list))
Here is an interactive demonstration which may help:
>>> trial_lst = [0.5, 3, 6, 40, 90, 130.8, 129, 111, 8, 9, 0.01, 9, 40, 90, 130.1, 112, 108, 90, 77, 68, 0.9, 8, 40, 90, 92, 130.4]
Use values below 1 to identify where one experiment ends and another begins
>>> indices = [x[0] for x in enumerate(map(lambda x:x<1, trial_lst)) if x[1]]
Break list into sublists at those values
>>> sublists = [trial_lst[i:j] for i,j in zip([0]+indices, indices+[None])[1:]]
Compute max/min for each sublist
>>> for i,l in enumerate(sublists):
... print "Experiment", i+1
... print "Min", min(l)
... print "Max", max(l)
... print
...
Experiment 1
Min 0.5
Max 130.8
Experiment 2
Min 0.01
Max 130.1
Experiment 3
Min 0.9
Max 130.4

Categories

Resources