This notebook aim to show easily how periods work in OpenFisca.
We need to initialize a simulation.
from openfisca_core.simulation_builder import SimulationBuilder
from openfisca_france import FranceTaxBenefitSystem
tax_benefit_system = FranceTaxBenefitSystem()
TEST_CASE = {
'individus': {
'parent1': {
'age': {'2015': 30},
'salaire_de_base': {'2015': 50000}
},
'enfant1': {
'age': {'2015': 12}
},
'enfant2': {
'age': {'2015': 18}
}
},
'familles': {
'famille1': {
'parents': ['parent1'],
'enfants': ['enfant1', 'enfant2']
}
},
'menages': {
'menage1': {
'personne_de_reference': ['parent1'],
'enfants': ['enfant1', 'enfant2']
}
},
'foyers_fiscaux': {
'foyer_fiscal1': {
'declarants': ['parent1'],
'personnes_a_charge': ['enfant1', 'enfant2']
}
}
}
simulation_builder = SimulationBuilder()
simulation = simulation_builder.build_from_entities(tax_benefit_system, TEST_CASE)
A simulation's variable is calculated for a specific period.
This specific time interval have to be given when calling a variable to avoid computation problem.
Some variables are computed over the month.
For exemple the variable af
(family allowance).
If called on an annual basis, an error is displayed:
try:
simulation.calculate('af', '2015')
except Exception as e:
print(e)
Unable to compute variable 'af' for period 2015: 'af' must be computed for a whole month. You can use the ADD option to sum 'af' over the requested period, or change the requested period to 'period.first_month'.
This error:
Unable to compute variable 'af' for period 2015: 'af' must be computed for a whole month. You can use the ADD option to sum 'af' over the requested period, or change the requested period to 'period.first_month'.
means exactly the fact that af
are computed for a month, therefore its '2015' calculation period should be changed for a specific month.
When called on its period (monthly), the result of the variable's formula will be returned:
simulation.calculate('af', '2015-01') # calculate variable af for January 2015
array([129.99], dtype=float32)
Other formulas only works on a annual basis, thus a monthly call will not work, e.g irpp
(income tax) :
try:
simulation.calculate('irpp', period = '2015-01')
except Exception as e:
print(e)
Unable to compute variable 'irpp' for period 2015-01: 'irpp' must be computed for a whole year. You can use the DIVIDE option to get an estimate of irpp by dividing the yearly value by 12, or change the requested period to 'period.this_year'.
It must be called on an annual basis :
simulation.calculate('irpp', period = '2015') # calculate variable irpp for 2015
array([-2401.], dtype=float32)
WARNING : this demonstration shows the necessity of being aware over which kind of period each measure you want to compute is based on.
If you've forgotten over which kind of period (year or month) your variable is defined in the legislation, you may check in the legislation explorer.
Example for the irpp, a tax with annual definition, you will find the period on the website page, e.g.:
irpp
(...)
This variable applies to the entity foyer_fiscal.
It has a definition period of a year.
Specific period calls insure that no errors are made by the user.
If a annual based variable is asked to be computed monthly, the software returns an error.
But there is solutions to get the result of a given variable on another kind of period, a annual based variable over a month for example.
The calculate_ad
method has the same behavior as calculat
, except that it sum all variables given their instant of calculus over a period lenght.
simulation.calculate_add("af", "2015")
array([1559.88], dtype=float32)
This result is equivalent to do sum all monthly calculate over the period.
annual_af = 0 # create annual_af equals to 0
for month in range(1,13): # [1,2,...,11,12]
annual_af += simulation.calculate("af", '2015-{}'.format(month)) # add recursively af for all month in 2014
annual_af
array([1559.88], dtype=float32)
We thus see that annual_af is equal to simulation.calculate_add('af', "2014").
We can test that:
simulation.calculate_add('af','2015') == annual_af
array([ True])
simulation.calculate_divide("irpp", "2015-01")
array([-200.08333], dtype=float32)
simulation.calculate("irpp", "2015") / 12
array([-200.08333], dtype=float32)
We can also use more exotic period by using the class periods.
Actually when we do: simulation.calculate('irpp','2015')
, the '2015' string is converted into an object period.
A more explicit way to do this, is: simulation.calculate('irpp', periods.period('2015'))
from openfisca_core import periods # openfisca_core is the architecture of OpenFisca
periods.period('2015')
Period(('year', Instant((2015, 1, 1)), 1))
print(simulation.calculate('irpp', periods.period('2015')))
print(simulation.calculate('irpp', periods.period('2015')) == simulation.calculate('irpp', 2015))
[-2401.] [ True]
We can look at periods docstring to understand how it works:
print(periods.period.__doc__)
Return a new period, aka a triple (unit, start_instant, size). >>> period('2014') Period((YEAR, Instant((2014, 1, 1)), 1)) >>> period('year:2014') Period((YEAR, Instant((2014, 1, 1)), 1)) >>> period('2014-2') Period((MONTH, Instant((2014, 2, 1)), 1)) >>> period('2014-02') Period((MONTH, Instant((2014, 2, 1)), 1)) >>> period('month:2014-2') Period((MONTH, Instant((2014, 2, 1)), 1)) >>> period('year:2014-2') Period((YEAR, Instant((2014, 2, 1)), 1))
A Period
object is initialized with three parameters:
periods.Period('day', periods.Instant((2014, 3, 01)), 32)
would be a period going from the first of March to the first of April.
period = periods.Period(('day', periods.Instant((2014, 3, 1)), 32))
period.unit, period.start, period.size
('day', Instant((2014, 3, 1)), 32)
If we want to calculate the af
(family allowance) from April to July.
af_april_to_july = simulation.calculate_add(
'af',
period = periods.Period(('month', periods.Instant((2014, 3, 1)), 4))
)
print(af_april_to_july)
[519.18]
A simplificated version of period declaration exists with the symbol: ":"
.
month:year-month:n
means n months beginning at the month, year.
Example with af_april_to_july
:
af_april_to_july = simulation.calculate_add('af', period = periods.period("month:2014-04:4"))
print(af_april_to_july)
[519.96]
A simplified instant declaration of instant also exists
periods.instant("2014-01")
Instant((2014, 1, 1))
Computing over several years needs to rethink how we've declared the simulation:
the starting simulation has, as input data period
, one year, 2015
, so it won't be able to compute anything outside this time interval.
print('Income tax on 2014', simulation.calculate('irpp', '2014'))
print('Income tax on 2015', simulation.calculate('irpp', '2015'))
print('Income tax on 2016', simulation.calculate('irpp', '2016'))
Income tax on 2014 [0.] Income tax on 2015 [-2401.] Income tax on 2016 [0.]
The idea is to change the period over which those input data apply and stretch their values using the syntax shown previously.
# We have to initialise a new simulation
TEST_CASE_OVER_YEARS = {
'individus': {
'parent1': {
'date_naissance': {'ETERNITY': 1975},
'salaire_de_base': {'year:2014:3': 50000 * 3} # three years starting in 2014
# multiplication by 3 is need so salaire de base is 50000 € for each of the three years
},
'enfant1': {
'date_naissance': {'ETERNITY': 2001} # a birth date doesn't change over time
},
'enfant2': {
'date_naissance': {'ETERNITY': 1999}
}
},
'familles': {
'famille1': {
'parents': ['parent1'],
'enfants': ['enfant1', 'enfant2']
}
},
'menages': {
'menage1': {
'personne_de_reference': ['parent1'],
'enfants': ['enfant1', 'enfant2']
}
},
'foyers_fiscaux': {
'foyer_fiscal1': {
'declarants': ['parent1'],
'personnes_a_charge': ['enfant1', 'enfant2']
}
}
}
simulation_builder = SimulationBuilder()
simulation_over_years = simulation_builder.build_from_entities(tax_benefit_system, TEST_CASE_OVER_YEARS)
print('Income tax on 2014', simulation_over_years.calculate('irpp', '2014'))
print('Income tax on 2015', simulation_over_years.calculate('irpp', '2015'))
print('Income tax on 2016', simulation_over_years.calculate('irpp', '2016'))
Income tax on 2014 [-2413.] Income tax on 2015 [-2401.] Income tax on 2016 [-2391.]
WARNING : handling the age
We've changed the variable age
to date_naissance
(birth date) in order that the individuals get older. If we've kept the age
they would have the same age for the entire period.
Now we can compute the irpp
with the function previously used : calculate_add
simulation_over_years.calculate_add('irpp', period = 'year:2014-01:2') # for 2014 and 2015
array([-4814.], dtype=float32)
It takes in consideration the legislation change between the two years.
Therefore it equals the sum of irpp
for each year but not the double of irpp
for one year.
print(simulation_over_years.calculate('irpp', '2015') + simulation_over_years.calculate('irpp', '2014'))
print(simulation_over_years.calculate_add('irpp', '2014') * 2)
[-4814.] [-4826.]
These formulas are still working for variables defined on a monthly basis.
simulation_over_years.calculate_add('af', 'month:2015-01:2')
array([0.], dtype=float32)
You might want to make evolve some given values over the years.
The tool for it will be to use a Python dictionnary.
For example, if you want to give a wage evolution:
TEST_CASE_MULTIPLE_YEARS = {
'individus': {
'parent1': {
'date_naissance': {'ETERNITY': 1975},
'salaire_de_base': {
'2014': 50000,
'2015': 50500,
'2016': 51000
}
},
'enfant1': {
'date_naissance': {'ETERNITY': 2001}
},
'enfant2': {
'date_naissance': {'ETERNITY': 1999}
}
},
'familles': {
'famille1': {
'parents': ['parent1'],
'enfants': ['enfant1', 'enfant2']
}
},
'menages': {
'menage1': {
'personne_de_reference': ['parent1'],
'enfants': ['enfant1', 'enfant2']
}
},
'foyers_fiscaux': {
'foyer_fiscal1': {
'declarants': ['parent1'],
'personnes_a_charge': ['enfant1', 'enfant2']
}
}
}
simulation_builder = SimulationBuilder()
simulation_multiple_years = simulation_builder.build_from_entities(tax_benefit_system, TEST_CASE_MULTIPLE_YEARS)
simulation_multiple_years.calculate_add('irpp', 'year:2014:3') # for 2014, 2015 and 2016
array([-7363.], dtype=float32)
Command line to get the Notebook's Table of Contents:
%%javascript
$.getScript('https://kmahelona.github.io/ipython_notebook_goodies/ipython_notebook_toc.js')