Conditional operators¶
Conditional operator (operator IfElse
) *¶
Operator IfElse
provides a simple way to conditionally apply an
operator. The condition can be a fixed condition, a expression (a string) that
will be evaluated in a population’s local namespace or a user-defined function
when it is applied to the population.
The first case is used to control the execution of certain operators depending
on user input. For example, Example IfElseFixed determines
whether or not some outputs should be given depending on a variable verbose
.
Note that the applicability of the conditional operators are determined by the
IfElse
operator and individual opearators. That is to say, the
parameters begin
, step
, end
, at
, and reps
of operators in
ifOps
and elseOps
are only honored when operator IfElse
is
applied.
Example: A conditional opeartor with fixed condition
>>> import simuPOP as sim
>>> pop = sim.Population(size=1000, loci=1)
>>> verbose = True
>>> pop.evolve(
... initOps=[
... sim.InitSex(),
... sim.InitGenotype(freq=[0.5, 0.5]),
... ],
... matingScheme=sim.RandomMating(),
... postOps=sim.IfElse(verbose,
... ifOps=[
... sim.Stat(alleleFreq=0),
... sim.PyEval(r"'Gen: %3d, allele freq: %.3f\n' % (gen, alleleFreq[0][1])",
... step=5)
... ],
... begin=10),
... gen = 30
... )
Gen: 10, allele freq: 0.483
Gen: 15, allele freq: 0.455
Gen: 20, allele freq: 0.481
Gen: 25, allele freq: 0.481
30
now exiting runScriptInteractively...
When a string is specified, it will be considered as an expression and be evaluated in a population’s namespace. The return value will be used to determine if an operator should be executed. For example, you can re-introduce a mutant if it gets lost in the population, output a warning when certain condition is met, or record the occurance of certain events in a population. For example, Example IfElse records the number of generations the frequency of an allele goes below 0.4 and beyong 0.6 before it gets lost or fixed in the population. Note that a list of else-operators can also be executed when the condition is not met.
Example: A conditional opeartor with dynamic condition
>>> import simuPOP as sim
>>> simu = sim.Simulator(
... sim.Population(size=1000, loci=1),
... rep=4)
>>> simu.evolve(
... initOps=[
... sim.InitSex(),
... sim.InitGenotype(freq=[0.5, 0.5]),
... sim.PyExec('below40, above60 = 0, 0')
... ],
... matingScheme=sim.RandomMating(),
... postOps=[
... sim.Stat(alleleFreq=0),
... sim.IfElse('alleleFreq[0][1] < 0.4',
... sim.PyExec('below40 += 1')),
... sim.IfElse('alleleFreq[0][1] > 0.6',
... sim.PyExec('above60 += 1')),
... sim.IfElse('len(alleleFreq[0]) == 1',
... sim.PyExec('stoppedAt = gen')),
... sim.TerminateIf('len(alleleFreq[0]) == 1')
... ]
... )
(892, 1898, 4001, 2946)
>>> for pop in simu.populations():
... print('Overall: %4d, below 40%%: %4d, above 60%%: %4d' % \
... (pop.dvars().stoppedAt, pop.dvars().below40, pop.dvars().above60))
...
Overall: 891, below 40%: 20, above 60%: 515
Overall: 1897, below 40%: 1039, above 60%: 51
Overall: 4000, below 40%: 2878, above 60%: 0
Overall: 2945, below 40%: 198, above 60%: 1731
now exiting runScriptInteractively...
In the last case, a user-defined function can be specified. This function should
accept parameter pop
when the operator is applied to a population, and one
or more parameters pop
, off
, dad
and mom
when it is applied
during-mating. The later could be used to apply different during-mating
operators for different types of parents or offspring. For example, Example
pedigreeMatingAgeStructured in Chapter 6
uses a CloneGenoTransmitter
when only one parent is available (when
parameter mom
is None
), and a MendelianGenoTransmitter
when two
parents are available.
Conditionally terminate an evolutionary process (operator TerminateIf
)¶
Operator TerminateIf
has been described and used in several examples
such as Example simuGen, expression and
IfElse. This operator accept an Python expression and terminate
the evolution of the population being applied if the expression is evaluated to
be True
. This operator is well suited for situations where the number of
generations to evolve cannot be determined in advance.
If a TerminateIf
operator is applied to the offspring generation, the
evolutionary cycle is considered to be completed. If the evolution is terminated
before mating, the evolutionary cycle is condered to be incomplete. Such a
difference can be important if the number of generations that have been involved
is important for your analysis.
A less-known feature of operator TerminateIf
is its ability to
terminate the evolution of all replicates, using parameter stopAll=True
. For
example, Example TerminateIf terminates the evolution of
all populations when one of the populations gets fixed. The return value of
simu.evolve
shows that some populations have evolved one generation less
than the population being fixed.
Example: Terminate the evolution of all populations in a simulator
>>> import simuPOP as sim
>>> simu = sim.Simulator(
... sim.Population(size=100, loci=1),
... rep=10)
>>> simu.evolve(
... initOps=[
... sim.InitSex(),
... sim.InitGenotype(freq=[0.5, 0.5]),
... ],
... matingScheme=sim.RandomMating(),
... postOps=[
... sim.Stat(alleleFreq=0),
... sim.TerminateIf('len(alleleFreq[0]) == 1', stopAll=True)
... ]
... )
(88, 88, 88, 88, 87, 87, 87, 87, 87, 87)
>>>
now exiting runScriptInteractively...
Conditional removal of individuals (operator DiscardIf
)¶
Operator DiscardIf
accepts a fixed condition or probability, or a
condition or a Python function that returns either True
/False
or a
probability to remove an individual. When it is applied during mating, it will
evaluate the condition or call the function for each offspring, and discard the
offspring if the return value of the expression or function is True, or remove
at a probability if the return value is a number between 0 and 1. The python
expression accepts information fields as variables so operator
DiscardIf
('age > 80'
) will discard all individuals with age > 80,
and DiscardIf
('1-fitness'
) will remove individuals according to 1
minus their fitness. Optionally, the offspring itself can be used in the
expression if parameter exposeInd is used to set the variable name of the
offspring.
Alternatively, a Python function can be passed to this operator. This function
should be defined with parameters pop
, off
, mom
, dad
or names of
information fields. For example, DiscardIf
(lambda age: age > 80
)
will remove individuals with age > 80.
A constant expression is also allowed in this operator. A fixed condition or
number is acceptable so DiscardIf
(0.1
) will randomly remove 10%
of all individuals. Although it does not make sense to use DiscardIf
(True
) because all offspring will be discarded, it is quite useful to use
this operator in the context of DiscardIf
(True, subPops=[(0, 0)]
)
to remove all individuals in a virtual subpopulation. If virtual subpopulation
(0, 0)
is defined as all individuals with age > 80, the last method achieves
the same effect as the first two methods.
Example DiscardIf demonstrates an interesting application of this operator. This example evolves a population for one generation. Instead of keeping all offspring, it keeps only 500 affected and 500 unaffected offspring. This is achieved by defining virtual subpopulations by affection status and range, and discard the first 500 offspring if they are unaffected, and the last 500 offspring if they are affected.
Example: Use operator DiscardIf to generate case control samples
>>> import simuPOP as sim
>>> pop = sim.Population(size=500, loci=1)
>>> pop.setVirtualSplitter(sim.ProductSplitter([
... sim.AffectionSplitter(),
... sim.RangeSplitter([[0,500], [500, 1000]]),
... ])
... )
>>> pop.evolve(
... initOps=[
... sim.InitSex(),
... sim.InitGenotype(freq=[0.5, 0.5]),
... ],
... matingScheme=sim.RandomMating(
... ops=[
... sim.MendelianGenoTransmitter(),
... sim.MaPenetrance(loci=0, penetrance=[0, 0.01, 0.1]),
... sim.DiscardIf(True, subPops=[
... (0, 'Unaffected, Range [0, 500)'),
... (0, 'Affected, Range [500, 1000)')])
... ],
... subPopSize=1000,
... ),
... gen = 1
... )
1
>>> sim.stat(pop, numOfAffected=True)
>>> print(pop.dvars().numOfAffected, pop.dvars().numOfUnaffected)
500 500
now exiting runScriptInteractively...