The general problem that you describe doesn't have a solution because you're trying to make a map from a 2D surface to a 1D line that preserves all distances, and this isn't possible. If there was a particular region that you wanted to compare to all others, you could then put all the others around a circle so their distance match the distance to this region (but then the distance between these other regions will be distorted).

But you can certainly do better than just random in approximating the distance. Here's an approach: The first step would be to do multiple random arrangements and then pick the best of these. The next improvement would be to optimize each of these arrangements against some cost function by moving the regions around in small steps until they settle into a local minimum, and then pick the best of these local minima. The results of this is shown in the plots below, and the Python code is further down.

```
import pylab as px
import numpy as nx
import numpy.random as rand
rand.seed(1)
rt2 = nx.sqrt(2)
N = 10 # number of brain regions
# make brain region locations r=1
regions = []
s = 2.
while len(regions)<N:
p = 2*s*rand.rand(2)-s
if nx.sqrt(px.dot(p,p))<s:
regions.append(p)
regions = nx.array(regions)
#px.figure()
px.subplot(2,2,1)
for i in range(len(regions)):
px.text(regions[i,0], regions[i,1], `i`, fontsize=15)
px.xlim(-1.1*s, 1.1*s)
px.ylim(-1.1*s, 1.1*s)
px.title("inital positions")
# precalc distance matrix for future comparisons
dm = nx.zeros((N,N), dtype=nx.float)
for i in range(N):
for j in range(N):
dm[i,j] = nx.sqrt(nx.sum((regions[i,:]-regions[j,:])**2))
def randomize_on_circle(n):
"""return array of n random angles"""
return 2*nx.pi*rand.rand(n)
def cost_fcn(d_target, d_actual): # cost for distances not matching
return abs(d_target-d_actual)
def calc_cost(angles):
"""calc cost for the given arrangement """
c = 0.
for i in range(N-1):
for j in range(i, N):
# sqrt(...) is distance between two pts on a circle (I think)
c += cost_fcn(dm[j, i], rt2*nx.sqrt(1-nx.cos(angles[i]-angles[j])))
return c
def optimize_step(a, shift=2*nx.pi/360):
"""try shifting all points a bit cw and ccw, and return the most beneficial"""
max_benefit, ref_cost = None, None
best_i, best_shift = None, None
for imove in range(N): # loop through the regions and try moving each one
cost0 = calc_cost(a)
for da in (shift, -shift):
a_temp = nx.array(a)
a_temp[imove] += da
cost = calc_cost(a_temp)
benefit = cost0 - cost # benefit if moving lowers the cost
if max_benefit is None or benefit > max_benefit:
max_benefit, best_i, best_shift, ref_cost = benefit, imove, da, cost
return max_benefit, best_i, best_shift, ref_cost
lowest_cost, best_angles = None, None
cost_initials, cost_plateaus = [], []
for i in range(30): # loop though 20 randomized placements on the circle
angles = randomize_on_circle(N)
costs = []
benefits = []
# optimize each original arrangement by shifting placements one-by-one in small steps
count_benefits_neg = 0
count_total, max_total = 0, 2000
while count_benefits_neg < 10: # better to do a variable step size
b, i, s, c = optimize_step(angles)
angles[i] += s
costs.append(c)
benefits.append(b)
if b < 0:
count_benefits_neg += 1
count_total += 1
if count_total > max_total:
print count_total, b, costs[-20:], benefits[-20]
raise "not finding an equilibrium"
if lowest_cost is None or c < lowest_cost:
lowest_cost = c
best_angles = nx.array(angles)
cost_graph = costs[:]
benefit_graph = nx.array(benefits)
cost_plateaus.append(c)
cost_initials.append(costs[0])
px.subplot(2, 2, 2)
px.plot(cost_graph, 'o') # make sure the cost is leveling off
px.title("cost evoloution of best")
px.subplot(2, 2, 3)
px.plot(cost_initials, 'o')
px.plot(cost_plateaus, 'd')
px.title("initial and final costs")
px.subplot(2, 2, 4)
for i in range(len(best_angles)):
px.text(nx.cos(best_angles[i]), nx.sin(best_angles[i]), `i`, fontsize=15)
px.xlim(-1.2, 1.2)
px.ylim(-1.2, 1.2)
px.title("positioned on circle")
px.show()
```

Interestingly, this seems to have resulted in far things being far-ish, and near things being near-ish, but with the mid-range orders messed up, so maybe this will do what you want? (This also illustrates the fundamental problem in going from 2D to 1D. For example, on the circle the 4 wants to be further from the 9, but it can't do that without getting closer to the other numbers, whereas in 2D it could just go out to the side.)

You'll probably want to modify `cost_fnc`

which specifies the penalty for having the distances of points on the circle not match the distance from the 2D arrangement. Changing this in a way to increase the costs for large errors (say a quadradic), or to emphasise a cost for the large distance being right, say `d_target*(abs(d_actual-d_target))`

, etc, might help.

Also, changing the size of the circle relative to the size of the 2D data will change the look of this quite a lot, and you probably will want to circle somewhat smaller than the data, as I've done here, as this will spread the points around the circle more. (Here the circle has R=1, so just scale the data appropriately.) Also note that this will make a quantitative assessment of the cost to be not very meaningful as the best arrangements never get very to very low cost since some regions can never be as far apart as in the 2D data.

The point of running multiple random starts is that the evolving arrangement can get stuck in local minima. This technique seems to be useful: settling helps in getting the distance right and the costs down (plot #3, blue dots=initial random, diamonds=local minimum) and it helps some initial arrangements much more than others, so it's good to try multiple initial arrangements. Also, since a number of these seem to settle to around 15 it gives some confidence that this arrangement might be representative.