I think you need to graduate from Excel. It technically has linear programming but I trust that less than writing out code to accomplish the same task.
When you say
greater than
how much greater than? Constraint solvers essentially always talk in greater-or-equal, not greater-than; so either use greater-or-equal or choose some epsilon minimum distance between the two constraint terms.
You can frame this as a mixed-integer linear programming problem that attempts to mutate the figure matrix until the constraints are obeyed while minimizing the number of mutations necessary:
import pandas as pd
import pulp
df = pd.DataFrame({
'rank': range(1, 6),
'lxly': (-22.32, -8.67, -1.51, 1.31, 5.47),
'lxhy': (-20.18, -3.87, -1.79, 2.31, 6.35),
'hxly': ( -9.79, -6.30, -0.64, 3.41, 6.98),
'hxhy': ( -0.73, -4.27, -0.34, 4.24, 7.35),
}).set_index('rank')
df.columns.name = 'bucket'
long = df.stack().rename('figure').to_frame()
# 1 if the figure changes, 0 if the figure stays the same
long['change'] = pulp.LpVariable.matrix(
name='change', indices=long.index, cat=pulp.LpBinary,
)
# what will be used instead of the original figure
long['replacement'] = pulp.LpVariable.matrix(
name='replacement', indices=long.index, cat=pulp.LpContinuous,
)
replacements = long['replacement'].unstack('bucket')
# Minimize the number of outlier changes needed
prob = pulp.LpProblem(name='smoothing', sense=pulp.LpMinimize)
prob.setObjective(pulp.lpSum(long['change']))
# Big-M room for change on the figure
M = 2*long['figure'].abs().max()
for (rank, bucket), row in long.iterrows():
# The replacement can only be used for the figure if change=1
prob.addConstraint(
name=f'lower_r{rank}_{bucket}',
constraint=row['replacement'] >= row['figure'] - M*row['change'],
)
prob.addConstraint(
name=f'upper_r{rank}_{bucket}',
constraint=row['replacement'] <= row['figure'] + M*row['change'],
)
# Minimum change for "greater than"
epsilon = 0.1
for rank, row in replacements.iterrows():
# OP original bucket pair constraints
prob.addConstraint(
name=f'r{rank}_pair_a', constraint=row['lxhy'] >= row['lxly'] + epsilon,
)
prob.addConstraint(
name=f'r{rank}_pair_b', constraint=row['hxhy'] >= row['hxly'] + epsilon,
)
prob.addConstraint(
name=f'r{rank}_pair_c', constraint=row['hxly'] >= row['lxly'] + epsilon,
)
# constraint D is nonsense
for bucket, col in replacements.items():
for i_start in range(len(col) - 1):
select = slice(i_start, i_start + 2)
first, second = col.iloc[select]
rank_first, rank_second = col.index[select]
prob.addConstraint(
name=f'{bucket}_r_{rank_first}_{rank_second}',
constraint=first + epsilon <= second,
)
print(prob)
prob.solve()
assert prob.status == pulp.LpStatusOptimal
long['change'] = long['change'].apply(pulp.value)
long['replacement'] = long['replacement'].apply(pulp.value)
print('Outlier changes:')
print(long.loc[long['change'] > 0.5, ['figure', 'replacement']])
(It's also possible to, in one line, import that data frame from Excel.)
The output is:
Outlier changes:
figure replacement
rank bucket
1 hxhy -0.73 -9.69
3 lxly -1.51 -8.57