diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e7e5c98..99a085b4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog -### 41.4.7 [#1212](https://github.com/openfisca/openfisca-core/pull/1212) +## 41.5.0 [#1212](https://github.com/openfisca/openfisca-core/pull/1212) + +#### New features + +- Introduce `VectorialAsofDateParameterNodeAtInstant` + - It is a parameter node of the legislation at a given instant which has been vectorized along some date. + - Vectorized parameters allow requests such as parameters.housing_benefit[date], where date is a `numpy.datetime64` vector + +### 41.4.7 [#1211](https://github.com/openfisca/openfisca-core/pull/1211) #### Technical changes diff --git a/openfisca_core/parameters/__init__.py b/openfisca_core/parameters/__init__.py index a5ac18044..33a1f06c6 100644 --- a/openfisca_core/parameters/__init__.py +++ b/openfisca_core/parameters/__init__.py @@ -41,6 +41,7 @@ from .parameter_scale_bracket import ParameterScaleBracket from .parameter_scale_bracket import ParameterScaleBracket as Bracket from .values_history import ValuesHistory +from .vectorial_asof_date_parameter_node_at_instant import VectorialAsofDateParameterNodeAtInstant from .vectorial_parameter_node_at_instant import VectorialParameterNodeAtInstant __all__ = [ @@ -63,5 +64,6 @@ "ParameterScaleBracket", "Bracket", "ValuesHistory", + "VectorialAsofDateParameterNodeAtInstant", "VectorialParameterNodeAtInstant", ] diff --git a/openfisca_core/parameters/parameter_node_at_instant.py b/openfisca_core/parameters/parameter_node_at_instant.py index 9dc0abee8..40317f584 100644 --- a/openfisca_core/parameters/parameter_node_at_instant.py +++ b/openfisca_core/parameters/parameter_node_at_instant.py @@ -41,6 +41,10 @@ def __getattr__(self, key): def __getitem__(self, key): # If fancy indexing is used, cast to a vectorial node if isinstance(key, numpy.ndarray): + # If fancy indexing is used wit a datetime64, cast to a vectorial node supporting datetime64 + if numpy.issubdtype(key.dtype, numpy.datetime64): + return parameters.VectorialAsofDateParameterNodeAtInstant.build_from_node(self)[key] + return parameters.VectorialParameterNodeAtInstant.build_from_node(self)[key] return self._children[key] diff --git a/openfisca_core/parameters/vectorial_asof_date_parameter_node_at_instant.py b/openfisca_core/parameters/vectorial_asof_date_parameter_node_at_instant.py new file mode 100644 index 000000000..8b7dd0fdd --- /dev/null +++ b/openfisca_core/parameters/vectorial_asof_date_parameter_node_at_instant.py @@ -0,0 +1,66 @@ +import numpy + +from openfisca_core.parameters.parameter_node_at_instant import ParameterNodeAtInstant +from openfisca_core.parameters.vectorial_parameter_node_at_instant import VectorialParameterNodeAtInstant + + +class VectorialAsofDateParameterNodeAtInstant(VectorialParameterNodeAtInstant): + """ + Parameter node of the legislation at a given instant which has been vectorized along some date. + Vectorized parameters allow requests such as parameters.housing_benefit[date], where date is a np.datetime64 type vector + """ + + @staticmethod + def build_from_node(node): + VectorialParameterNodeAtInstant.check_node_vectorisable(node) + subnodes_name = node._children.keys() + # Recursively vectorize the children of the node + vectorial_subnodes = tuple([ + VectorialAsofDateParameterNodeAtInstant.build_from_node(node[subnode_name]).vector + if isinstance(node[subnode_name], ParameterNodeAtInstant) + else node[subnode_name] + for subnode_name in subnodes_name + ]) + # A vectorial node is a wrapper around a numpy recarray + # We first build the recarray + recarray = numpy.array( + [vectorial_subnodes], + dtype=[ + (subnode_name, subnode.dtype if isinstance(subnode, numpy.recarray) else 'float') + for (subnode_name, subnode) in zip(subnodes_name, vectorial_subnodes) + ] + ) + return VectorialAsofDateParameterNodeAtInstant(node._name, recarray.view(numpy.recarray), node._instant_str) + + def __getitem__(self, key): + # If the key is a string, just get the subnode + if isinstance(key, str): + key = numpy.array([key], dtype='datetime64[D]') + return self.__getattr__(key) + # If the key is a vector, e.g. ['1990-11-25', '1983-04-17', '1969-09-09'] + elif isinstance(key, numpy.ndarray): + assert numpy.issubdtype(key.dtype, numpy.datetime64) + names = list(self.dtype.names) # Get all the names of the subnodes, e.g. ['before_X', 'after_X', 'after_Y'] + values = numpy.asarray([value for value in self.vector[0]]) + names = [ + name + for name in names + if not name.startswith("before") + ] + names = [ + numpy.datetime64( + "-".join(name[len("after_"):].split("_")) + ) + for name in names + ] + conditions = sum([ + name <= key + for name in names + ]) + result = values[conditions] + + # If the result is not a leaf, wrap the result in a vectorial node. + if numpy.issubdtype(result.dtype, numpy.record) or numpy.issubdtype(result.dtype, numpy.void): + return VectorialAsofDateParameterNodeAtInstant(self._name, result.view(numpy.recarray), self._instant_str) + + return result diff --git a/setup.py b/setup.py index 8135f9d88..ceb862e0d 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,7 @@ setup( name="OpenFisca-Core", - version="41.4.7", + version="41.5.0", author="OpenFisca Team", author_email="contact@openfisca.org", classifiers=[ diff --git a/tests/core/parameters_date_indexing/__init__.py b/tests/core/parameters_date_indexing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/parameters_date_indexing/full_rate_age.yaml b/tests/core/parameters_date_indexing/full_rate_age.yaml new file mode 100644 index 000000000..fa9377fec --- /dev/null +++ b/tests/core/parameters_date_indexing/full_rate_age.yaml @@ -0,0 +1,121 @@ +description: Full rate age +full_rate_age_by_birthdate: + description: Full rate age by birthdate + before_1951_07_01: + description: Born before 01/07/1951 + year: + description: Year + values: + 1983-04-01: + value: 65.0 + month: + description: Month + values: + 1983-04-01: + value: 0.0 + after_1951_07_01: + description: Born after 01/07/1951 + year: + description: Year + values: + 2011-07-01: + value: 65.0 + 1983-04-01: + value: null + month: + description: Month + values: + 2011-07-01: + value: 4.0 + 1983-04-01: + value: null + after_1952_01_01: + description: Born after 01/01/1952 + year: + description: Year + values: + 2011-07-01: + value: 65.0 + 1983-04-01: + value: null + month: + description: Month + values: + 2012-01-01: + value: 9.0 + 2011-07-01: + value: 8.0 + 1983-04-01: + value: null + after_1953_01_01: + description: Born after 01/01/1953 + year: + description: Year + values: + 2011-07-01: + value: 66.0 + 1983-04-01: + value: null + month: + description: Month + values: + 2012-01-01: + value: 2.0 + 2011-07-01: + value: 0.0 + 1983-04-01: + value: null + after_1954_01_01: + description: Born after 01/01/1954 + year: + description: Year + values: + 2011-07-01: + value: 66.0 + 1983-04-01: + value: null + month: + description: Month + values: + 2012-01-01: + value: 7.0 + 2011-07-01: + value: 4.0 + 1983-04-01: + value: null + after_1955_01_01: + description: Born after 01/01/1955 + year: + description: Year + values: + 2012-01-01: + value: 67.0 + 2011-07-01: + value: 66.0 + 1983-04-01: + value: null + month: + description: Month + values: + 2012-01-01: + value: 0.0 + 2011-07-01: + value: 8.0 + 1983-04-01: + value: null + after_1956_01_01: + description: Born after 01/01/1956 + year: + description: Year + values: + 2011-07-01: + value: 67.0 + 1983-04-01: + value: null + month: + description: Month + values: + 2011-07-01: + value: 0.0 + 1983-04-01: + value: null diff --git a/tests/core/parameters_date_indexing/full_rate_required_duration.yml b/tests/core/parameters_date_indexing/full_rate_required_duration.yml new file mode 100644 index 000000000..af394ec56 --- /dev/null +++ b/tests/core/parameters_date_indexing/full_rate_required_duration.yml @@ -0,0 +1,162 @@ +description: Required contribution duration for full rate +contribution_quarters_required_by_birthdate: + description: Contribution quarters required by birthdate + before_1934_01_01: + description: before 1934 + values: + 1983-01-01: + value: 150.0 + after_1934_01_01: + description: '1934-01-01' + values: + 1994-01-01: + value: 151.0 + 1983-01-01: + value: null + after_1935_01_01: + description: '1935-01-01' + values: + 1994-01-01: + value: 152.0 + 1983-01-01: + value: null + after_1936_01_01: + description: '1936-01-01' + values: + 1994-01-01: + value: 153.0 + 1983-01-01: + value: null + after_1937_01_01: + description: '1937-01-01' + values: + 1994-01-01: + value: 154.0 + 1983-01-01: + value: null + after_1938_01_01: + description: '1938-01-01' + values: + 1994-01-01: + value: 155.0 + 1983-01-01: + value: null + after_1939_01_01: + description: '1939-01-01' + values: + 1994-01-01: + value: 156.0 + 1983-01-01: + value: null + after_1940_01_01: + description: '1940-01-01' + values: + 1994-01-01: + value: 157.0 + 1983-01-01: + value: null + after_1941_01_01: + description: '1941-01-01' + values: + 1994-01-01: + value: 158.0 + 1983-01-01: + value: null + after_1942_01_01: + description: '1942-01-01' + values: + 1994-01-01: + value: 159.0 + 1983-01-01: + value: null + after_1943_01_01: + description: '1943-01-01' + values: + 1994-01-01: + value: 160.0 + 1983-01-01: + value: null + after_1949_01_01: + description: '1949-01-01' + values: + 2009-01-01: + value: 161.0 + 1983-01-01: + value: null + after_1950_01_01: + description: '1950-01-01' + values: + 2009-01-01: + value: 162.0 + 1983-01-01: + value: null + after_1951_01_01: + description: '1951-01-01' + values: + 2009-01-01: + value: 163.0 + 1983-01-01: + value: null + after_1952_01_01: + description: '1952-01-01' + values: + 2009-01-01: + value: 164.0 + 1983-01-01: + value: null + after_1953_01_01: + description: '1953-01-01' + values: + 2012-01-01: + value: 165.0 + 1983-01-01: + value: null + after_1955_01_01: + description: '1955-01-01' + values: + 2013-01-01: + value: 166.0 + 1983-01-01: + value: null + after_1958_01_01: + description: '1958-01-01' + values: + 2015-01-01: + value: 167.0 + 1983-01-01: + value: null + after_1961_01_01: + description: '1961-01-01' + values: + 2015-01-01: + value: 168.0 + 1983-01-01: + value: null + after_1964_01_01: + description: '1964-01-01' + values: + 2015-01-01: + value: 169.0 + 1983-01-01: + value: null + after_1967_01_01: + description: '1967-01-01' + values: + 2015-01-01: + value: 170.0 + 1983-01-01: + value: null + after_1970_01_01: + description: '1970-01-01' + values: + 2015-01-01: + value: 171.0 + 1983-01-01: + value: null + after_1973_01_01: + description: '1973-01-01' + values: + 2015-01-01: + value: 172.0 + 1983-01-01: + value: null diff --git a/tests/core/parameters_date_indexing/test_date_indexing.py b/tests/core/parameters_date_indexing/test_date_indexing.py new file mode 100644 index 000000000..1b19c72ba --- /dev/null +++ b/tests/core/parameters_date_indexing/test_date_indexing.py @@ -0,0 +1,38 @@ +import numpy +import os + + +from openfisca_core.tools import assert_near +from openfisca_core.parameters import ParameterNode +from openfisca_core.model_api import * # noqa + +LOCAL_DIR = os.path.dirname(os.path.abspath(__file__)) + +parameters = ParameterNode(directory_path=LOCAL_DIR) + + +def get_message(error): + return error.args[0] + + +def test_on_leaf(): + parameter_at_instant = parameters.full_rate_required_duration('1995-01-01') + birthdate = numpy.array(['1930-01-01', '1935-01-01', '1940-01-01', '1945-01-01'], dtype='datetime64[D]') + assert_near(parameter_at_instant.contribution_quarters_required_by_birthdate[birthdate], [150, 152, 157, 160]) + + +def test_on_node(): + birthdate = numpy.array(['1950-01-01', '1953-01-01', '1956-01-01', '1959-01-01'], dtype='datetime64[D]') + parameter_at_instant = parameters.full_rate_age('2012-03-01') + node = parameter_at_instant.full_rate_age_by_birthdate[birthdate] + assert_near(node.year, [65, 66, 67, 67]) + assert_near(node.month, [0, 2, 0, 0]) + + +# def test_inhomogenous(): +# birthdate = numpy.array(['1930-01-01', '1935-01-01', '1940-01-01', '1945-01-01'], dtype = 'datetime64[D]') +# parameter_at_instant = parameters..full_rate_age('2011-01-01') +# parameter_at_instant.full_rate_age_by_birthdate[birthdate] +# with pytest.raises(ValueError) as error: +# parameter_at_instant.full_rate_age_by_birthdate[birthdate] +# assert "Cannot use fancy indexing on parameter node '.full_rate_age.full_rate_age_by_birthdate'" in get_message(error.value) diff --git a/tests/core/parameters_fancy_indexing/coefficient_de_minoration.yaml b/tests/core/parameters_fancy_indexing/coefficient_de_minoration.yaml new file mode 100644 index 000000000..9894ae64a --- /dev/null +++ b/tests/core/parameters_fancy_indexing/coefficient_de_minoration.yaml @@ -0,0 +1,135 @@ +description: Coefficient de minoration ARRCO +coefficient_minoration_en_fonction_distance_age_annulation_decote_en_annee: + description: Coefficient de minoration à l'Arrco en fonction de la distance à l'âge d'annulation de la décote (en année) + '-10': + description: '-10' + values: + 1965-01-01: + value: 0.43 + 1957-05-15: + value: null + '-9': + description: '-9' + values: + 1965-01-01: + value: 0.5 + 1957-05-15: + value: null + '-8': + description: '-8' + values: + 1965-01-01: + value: 0.57 + 1957-05-15: + value: null + '-7': + description: '-7' + values: + 1965-01-01: + value: 0.64 + 1957-05-15: + value: null + '-6': + description: '-6' + values: + 1965-01-01: + value: 0.71 + 1957-05-15: + value: null + '-5': + description: '-5' + values: + 1965-01-01: + value: 0.78 + 1957-05-15: + value: 0.75 + '-4': + description: '-4' + values: + 1965-01-01: + value: 0.83 + 1957-05-15: + value: 0.8 + '-3': + description: '-3' + values: + 1965-01-01: + value: 0.88 + 1957-05-15: + value: 0.85 + '-2': + description: '-2' + values: + 1965-01-01: + value: 0.92 + 1957-05-15: + value: 0.9 + '-1': + description: '-1' + values: + 1965-01-01: + value: 0.96 + 1957-05-15: + value: 0.95 + '0': + description: '0' + values: + 1965-01-01: + value: 1.0 + 1957-05-15: + value: 1.05 + '1': + description: '1' + values: + 1965-01-01: + value: null + 1957-05-15: + value: 1.1 + '2': + description: '2' + values: + 1965-01-01: + value: null + 1957-05-15: + value: 1.15 + '3': + description: '3' + values: + 1965-01-01: + value: null + 1957-05-15: + value: 1.2 + '4': + description: '4' + values: + 1965-01-01: + value: null + 1957-05-15: + value: 1.25 + metadata: + order: + - '-10' + - '-9' + - '-8' + - '-7' + - '-6' + - '-5' + - '-4' + - '-3' + - '-2' + - '-1' + - '0' + - '1' + - '2' + - '3' + - '4' +metadata: + order: + - coefficient_minoration_en_fonction_distance_age_annulation_decote_en_annee + reference: + 1965-01-01: Article 18 de l'annexe A de l'Accord national interprofessionnel de retraite complémentaire du 8 décembre 1961 + 1957-05-15: Accord du 15/05/1957 pour la création de l'UNIRS + description_en: Penalty for early retirement ARRCO +documentation: | + Note: Le coefficient d'abattement (ou de majoration avant 1965) constitue une multiplication des droits de pension à l'arrco par le coefficient en question. Par exemple, un individu partant en retraite à 60 ans en 1960 touchait 75% de sa pension. A partir de 1983, une double condition d'âge et de durée d'assurance est instaurée: un individu ayant validé une durée égale à la durée d'assurance cible(voir onglet Trim_tx_plein_RG) partira sans abbattement, même s'il n'a pas atteint l'âge d'annulation de la décôte dans le régime général (voir onglet Age_ann_dec_RG). + Note : le coefficient de minoration est linéaire en nombre de trimestres, e.g. il est de 0,43 à AAD - 10 ans, de 0,4475 à AAD - 9 ans et 3 trimestres, de 0,465 à AAD - 9 ans et 2 trimestres, etc.