# python random with a skew

I am trying to create a very simple evolution algorithm for a creature simulator, what I would like to have is, both creatures have a trait and a dominance level, both noted by ints. their child's trait will be a random number between creature A's trait and creature B's trait then skewed to the more dominant. So if A has a trait of 5 and dominance of 2 and B has a trait of 10 and a dominance of 7, the skew will be -5 so it skews more towards B. Their child is more likely to have a trait of 8 than 6. Is there a good way to do this? I visualise it ending up like this: A5-6--7---8----9-----10B

I can't figure out how much the skew should be until I am able to test the results, so for the time being it's kind of arbitrary.

Thank you everyone for taking the time to help me.

my solution will give you stronger skew for bigger dominance differences:
it is using the fact that raising a value between 0 and 1 to a positive power will skew the result within the same range.
set the fine-tuning, as you see fit.

``````import random
a_trait = 5
a_dominance = 2
b_trait = 10
b_dominance = 7

target = random.random()
skew = abs(a_dominance - b_dominance) + 1
fine_tuning_multiplier = 0.3
skewed_target = target ** (skew * fine_tuning_multiplier)

trait_delta = abs(a_trait - b_trait)
target_trait = trait_delta * (1 - skewed_target)

if a_dominance > b_dominance:
val = b_trait - target_trait
else:
val = a_trait + target_trait

print "val:", int(round(val))
``````

note that in this solution the traits must be sorted. (i.e. a_trait < b_trait)

40 consecutive runs resulted in:
`8 8 9 8 9 10 6 7 7 10 8 8 9 9 10 9 9 7 10 6 10 7 10 9 10 10 7 8 8 7 10 5 6 6 9 9 10 10 6 6`

From what I see, you need a weighted random selection, from `range(parent_a.trait, parent_b.trait)`.

To distribute the weights linearly, the simplest thing you could do is construct a range of floats in such a way that the `sum()` of the range is `1.0`, and each step would represent a probability of choosing that one step.

Example:

``````trait_delta = abs(pacent_a.trait - trait_b.trait)
trait_delta_sum = trait_delta * (trait_delta+1.) / 2.
weights = [x/trait_delta_sum for x in range(1, trait_delta+1)]
``````

As I said above, the sum of the weights should be equal to one:

``````assert sum(weights) == 1.
``````

Then generate a cumulative sum of the weights:

``````cumulative_weihts = [sum(weights[:x]) for x in range(trait_delta)]
``````

Now just pick a random number, and find the index of the largest number that is smaller than said random number:

``````rand = random.random()
shift = sum[-1 for x in cumulative_weightn if r<x])
``````

Finally, use the skew as an offset from the trait with the bigger dominance:

``````if parent_a.dominance > parent_b.dominance:
trait = parent_a.dominance + skew
else:
trait = parent_b.dominance + skew
``````

Sure, that's quite simple to do.

Just take the sum of both dominance values and take a `random.randrange()` value of these.

If that value is lower than the dominance value of parent A, you picked that parent's trait, otherwise it's parent B's trait you picked:

``````import random

if random.randrange(parentA.dominance + parentB.dominance) < parentA.dominance:
trait = parentA.trait
else:
trait = parentB.trait
``````

In other words, it's a very simple weighted random selection between two options.

For your specific example, the sum of the dominances is 9, so the `randrange()` value is one between 0 and 8; if 0 or 1 is picked, parent A's trait is selected, if 2, 3, 4, 5, 6, 7 or 8 is picked then parent B's trait is selected instead.

If instead you are talking about picking a trait on a range from parent A's trait value up to and including parent B's trait value where traits are treated as a range, then your dominance values are used to 'pull' a trait value towards one or the other parent.

It basically comes down to a tug-of-war between the parents. With balanced dominance, the trait picked would, on average, come down to the average value of the two traits. But with one parent dominating over the other, the trait value picked 'shifts' towards the dominating parent.

This translates in a chance that the values below the midpoint are picked for parent A have a chance of parentA.dominance in (parentA.dominance + parentB.dominance), while the values above the midpoint have a chance of parentB.dominance in (parentA.dominance + parentB.dominance) of being picked.

``````import random

pick = random.random()

# sort parents by trait; smallest trait first
parents = sorted((parentA, parentB), key=attrgetter('trait'))
average = (parents.trait + parents.trait) / 2.0
weights = []
slots = (parents.trait - parents.trait + 1.0) * (parents.dominance + parents.dominance)
for i in range(parents.trait, parents.trait + 1):
if i <= average:
weights.append(sum(weights) + (parents.dominance / slots))
else:
weights.append(sum(weights) + (parents.dominance / slots))
if weights[-1] > pick:
return i
``````

A quick demo picking traits between parent A and parent B using the above calculation:

``````>>> for i in range(40): print pickTrait(),
...
9 9 9 10 6 9 8 10 7 9 9 8 8 8 9 8 9 9 7 5 8 8 9 5 9 9 10 10 10 10 5 5 9 9 8 9 10 7 8 9
``````
• I may have not phrased my question clearly enough, unfortunatly this is not the whole answer, I am not just taking A's trait or B's trait I am taking a number from within the range of their two values. and that random number will be dependent on the difference between the two parents dominance. – EasilyBaffled Mar 7 '13 at 21:37
• @EasilyBaffled: Well, what does it mean to have a trait of 5 more than a trait of 10? Are traits a range, are they expressible as floating point values, or are they discreet (e.g. 5 is something completely different than 6, etc.)? You can 'overlay' the weights of the parents onto a range of values between 5 and 10 (inclusive) but you need to be more specific if that has any meaning at all. – Martijn Pieters Mar 7 '13 at 21:40
• @MartijnPieters I think the OP simply wants a weighted random integer from `randrange` of `[parentA.trait:parentB.trait]`, but weighted by the dominance (I assume a linear weight distribution). – Attila O. Mar 7 '13 at 21:44
• @AttilaO.: Perhaps, that is what I am trying to assertain. I'd like to hear that from the OP however. – Martijn Pieters Mar 7 '13 at 21:45
• @MartijnPieters traits of the parents are assigned randomly from a range of ints from 2 to 10. So 5 is average 10 is the best. Dominance is decided from a relation to the trait, which is why B has a higher dominance than A. If you want something more applicable, think of hight. I my dad is tall(6'3") my mom is short(5'1") I am 5'8" I am closer to my mom's hight, so it would stand to reason that her genes were dominant. Attila did a good job of summarizing it. – EasilyBaffled Mar 7 '13 at 22:04

If I understand your question correctly, you want a probability distribution function somewhat like below. The y axis is your dominance value and the x axis is the trait value. To generate a value in this distribution, you basically take the area under the curve, 12.5 in this case. Choose a uniform random number between 0 and 12.5, fill in that much area starting from the left side, and see where it falls on the x axis. For example, say you chose 4.5. The area under the curve between 5 and 8 is 4.5, so your child's trait is 8.

If my math is right, given `a` as the value between 0 and 12.5 you chose, `d1` and `d2` are the respective dominances, and `t1` and `t2` are the respective traits, your child's trait is given by:

``````t1 + sqrt(2*a*(d2-d1)/(t2-t1))
``````

You can do this by generating two random numbers.

Say you have trait A. The first 'parent' has a value of 2. The second a value of 8. You thus want a range of 2 to 8. Normally you'd generate a random number between 0 and 1.0, and multiply that by (8 - 2) and add it to 2, like:

``````N = (rand() * (8-2)) + 2;
``````

Simple enough, but it doesnt trend in a particular direction.

Instead, generate two random numbers. Lets say you want the 8 (the high value) to be the 'dominant' trait. Generate two random values between 0 and 1.0, and use the highest of the two when calculating the new child's value. That will 'trend' you towards 8.

If you wanted the low value to be dominant, do the same, using the lower of the two randomly generated numbers.