Ejemplo n.º 1
0
    def setUp(self):
        self.test_outflow_data = pd.DataFrame({
            "compartment_duration": [1, 1, 2, 2.5, 10],
            "total_population": [4, 2, 2, 4, 3],
            "outflow_to": ["jail", "prison", "jail", "prison", "prison"],
            "compartment": ["test"] * 5,
        })

        self.historical_data = pd.DataFrame({
            2015: {
                "jail": 2,
                "prison": 2
            },
            2016: {
                "jail": 1,
                "prison": 0
            },
            2017: {
                "jail": 1,
                "prison": 1
            },
        })

        self.compartment_policies = []

        self.test_transition_table = CompartmentTransitions(
            self.test_outflow_data)
        self.test_transition_table.initialize(self.compartment_policies)
Ejemplo n.º 2
0
    def test_reallocate_outflow_preserves_total_population(self):
        compartment_policies = [
            SparkPolicy(
                policy_fn=partial(
                    CompartmentTransitions.reallocate_outflow,
                    reallocation_df=pd.DataFrame({
                        "outflow": ["jail", "jail"],
                        "affected_fraction": [0.25, 0.25],
                        "new_outflow": ["prison", "treatment"],
                    }),
                    reallocation_type="+",
                    retroactive=True,
                ),
                sub_population={"sub_group": "test_population"},
                spark_compartment="test_compartment",
                apply_retroactive=True,
            )
        ]

        compartment_transitions = CompartmentTransitions(self.test_data)
        compartment_transitions.initialize(compartment_policies)

        assert_series_equal(
            compartment_transitions.transition_dfs["before"].sum(axis=1),
            compartment_transitions.transition_dfs["after_retroactive"].sum(
                axis=1),
        )
Ejemplo n.º 3
0
    def test_extend_table_extends_table(self):
        """make sure CompartmentTransitions.extend_table is actually adding empty rows"""

        compartment_transitions = CompartmentTransitions(self.test_data)
        compartment_transitions.extend_tables(15)
        self.assertEqual(
            set(compartment_transitions.transition_dfs["before"].index),
            set(range(1, 16)),
        )
Ejemplo n.º 4
0
class TestShellCompartment(unittest.TestCase):
    """Test the ShellCompartment class runs correctly"""
    def setUp(self):
        self.test_outflow_data = pd.DataFrame({
            "compartment_duration": [1, 1, 2, 2.5, 10],
            "total_population": [4, 2, 2, 4, 3],
            "outflow_to": ["jail", "prison", "jail", "prison", "prison"],
            "compartment": ["test"] * 5,
        })

        self.historical_data = pd.DataFrame({
            2015: {
                "jail": 2,
                "prison": 2
            },
            2016: {
                "jail": 1,
                "prison": 0
            },
            2017: {
                "jail": 1,
                "prison": 1
            },
        })

        self.compartment_policies = []

        self.test_transition_table = CompartmentTransitions(
            self.test_outflow_data)
        self.test_transition_table.initialize(self.compartment_policies)

    def test_all_edges_fed_to(self):
        """ShellCompartments require edges to the compartments defined in the outflows_data"""
        starting_ts = 2015
        policy_ts = 2018
        test_shell_compartment = ShellCompartment(
            self.test_outflow_data,
            starting_ts=starting_ts,
            policy_ts=policy_ts,
            tag="test_shell",
            constant_admissions=True,
            policy_list=[],
        )
        test_full_compartment = FullCompartment(
            self.historical_data,
            self.test_transition_table,
            starting_ts=starting_ts,
            policy_ts=policy_ts,
            tag="test_compartment",
        )
        with self.assertRaises(ValueError):
            test_shell_compartment.initialize_edges(
                [test_shell_compartment, test_full_compartment])
Ejemplo n.º 5
0
    def test_non_retroactive_policy_cannot_affect_retroactive_table(self):
        compartment_policies = [
            SparkPolicy(
                policy_fn=CompartmentTransitions.test_retroactive_policy,
                sub_population={"compartment": "test_compartment"},
                spark_compartment="test_compartment",
                apply_retroactive=False,
            )
        ]

        compartment_transitions = CompartmentTransitions(self.test_data)
        with self.assertRaises(ValueError):
            compartment_transitions.initialize(compartment_policies)
Ejemplo n.º 6
0
    def test_results_independent_of_data_order(self):

        compartment_policies = [
            SparkPolicy(
                policy_fn=CompartmentTransitions.test_retroactive_policy,
                sub_population={"compartment": "test_compartment"},
                spark_compartment="test_compartment",
                apply_retroactive=True,
            ),
            SparkPolicy(
                policy_fn=CompartmentTransitions.test_non_retroactive_policy,
                sub_population={"compartment": "test_compartment"},
                spark_compartment="test_compartment",
                apply_retroactive=False,
            ),
        ]
        compartment_transitions_default = CompartmentTransitions(
            self.test_data)
        compartment_transitions_shuffled = CompartmentTransitions(
            self.test_data.sample(frac=1))

        compartment_transitions_default.initialize(compartment_policies)

        compartment_transitions_shuffled.initialize(compartment_policies)

        self.assertEqual(compartment_transitions_default,
                         compartment_transitions_shuffled)
Ejemplo n.º 7
0
    def test_preserve_normalized_outflow_behavior_preserves_normalized_outflow_behavior(
        self, ):
        compartment_policies = [
            SparkPolicy(
                policy_fn=CompartmentTransitions.test_retroactive_policy,
                sub_population={"compartment": "test_compartment"},
                spark_compartment="test_compartment",
                apply_retroactive=True,
            ),
            SparkPolicy(
                policy_fn=partial(
                    CompartmentTransitions.
                    preserve_normalized_outflow_behavior,
                    outflows=["prison"],
                    state="after_retroactive",
                    before_state="before",
                ),
                sub_population={"compartment": "test_compartment"},
                spark_compartment="test_compartment",
                apply_retroactive=True,
            ),
        ]

        compartment_transitions = CompartmentTransitions(self.test_data)
        compartment_transitions.initialize(compartment_policies)

        baseline_transitions = CompartmentTransitions(self.test_data)
        baseline_transitions.initialize([])

        assert_series_equal(
            baseline_transitions.transition_dfs["after_retroactive"]["prison"],
            compartment_transitions.transition_dfs["after_retroactive"]
            ["prison"],
        )
Ejemplo n.º 8
0
    def test_alternate_transitions_data_equal_to_differently_instantiated_transition_table(
        self, ):
        alternate_data = self.test_data.copy()
        alternate_data.compartment_duration *= 2
        alternate_data.total_population = 10 - alternate_data.total_population

        policy_function = SparkPolicy(
            policy_fn=partial(
                CompartmentTransitions.use_alternate_transitions_data,
                alternate_historical_transitions=alternate_data,
                retroactive=False,
            ),
            spark_compartment="test_compartment",
            sub_population={"sub_group": "test_population"},
            apply_retroactive=False,
        )

        compartment_transitions = CompartmentTransitions(self.test_data)
        compartment_transitions.initialize([policy_function])

        alternate_data_transitions = CompartmentTransitions(alternate_data)
        alternate_data_transitions.initialize([])

        assert_frame_equal(
            compartment_transitions.transition_dfs["after_non_retroactive"],
            alternate_data_transitions.transition_dfs["after_non_retroactive"],
        )
Ejemplo n.º 9
0
 def test_rejects_remaining_as_outflow(self):
     """Tests that compartment transitions won't accept 'remaining' as an outflow"""
     broken_test_data = self.test_data.copy()
     broken_test_data.loc[broken_test_data["outflow_to"] == "jail",
                          "outflow_to"] = "remaining"
     with self.assertRaises(ValueError):
         CompartmentTransitions(broken_test_data)
Ejemplo n.º 10
0
    def test_rejects_data_with_negative_populations_or_durations(self):
        negative_duration_data = pd.DataFrame({
            "compartment_duration": [1, -1, 2, 2.5, 10],
            "total_population": [4, 2, 2, 4, 3],
            "outflow_to": ["jail", "prison", "jail", "prison", "prison"],
            "compartment": ["test_compartment"] * 5,
        })

        negative_population_data = pd.DataFrame({
            "compartment_duration": [1, 1, 2, 2.5, 10],
            "total_population": [4, 2, 2, -4, 3],
            "outflow_to": ["jail", "prison", "jail", "prison", "prison"],
            "compartment": ["test_compartment"] * 5,
        })

        with self.assertRaises(ValueError):
            CompartmentTransitions(negative_duration_data)

        with self.assertRaises(ValueError):
            CompartmentTransitions(negative_population_data)
Ejemplo n.º 11
0
    def test_chop_technicals_chops_correctly(self):
        """
        Make sure CompartmentTransitions.chop_technical_revocations zeros technicals after the correct duration and
            that table sums to the same amount (i.e. total population shifted but not removed)
        """
        compartment_policies = [
            SparkPolicy(
                policy_fn=partial(
                    CompartmentTransitions.chop_technical_revocations,
                    technical_outflow="prison",
                    release_outflow="jail",
                    retroactive=False,
                ),
                sub_population={"sub_group": "test_population"},
                spark_compartment="test_compartment",
                apply_retroactive=False,
            )
        ]

        compartment_transitions = CompartmentTransitions(self.test_data)
        compartment_transitions.initialize(compartment_policies)

        baseline_transitions = CompartmentTransitions(self.test_data)
        baseline_transitions.initialize([])

        # check total population was preserved
        assert_series_equal(
            compartment_transitions.transition_dfs["after_non_retroactive"].
            iloc[0],
            baseline_transitions.transition_dfs["after_non_retroactive"].
            iloc[0],
        )

        # check technicals chopped
        compartment_transitions.unnormalize_table("after_non_retroactive")
        self.assertTrue(
            (compartment_transitions.transition_dfs["after_non_retroactive"].
             loc[3:, "prison"] == 0).all())
        self.assertTrue(
            compartment_transitions.transition_dfs["after_non_retroactive"].
            loc[1, "prison"] != 0)
Ejemplo n.º 12
0
 def test_normalize_transitions_requires_generated_transition_table(self):
     compartment_transitions = CompartmentTransitions(self.test_data)
     # manually initializing without the self._generate_transition_table()
     compartment_transitions.transition_dfs = {
         "before":
         pd.DataFrame(
             {
                 outflow: np.zeros(10)
                 for outflow in compartment_transitions.outflows
             },
             index=range(1, 11),
         ),
         "transitory":
         pd.DataFrame(),
         "after_retroactive":
         pd.DataFrame(),
         "after_non_retroactive":
         pd.DataFrame(),
     }
     with self.assertRaises(ValueError):
         compartment_transitions.normalize_transitions(
             state="after_retroactive")
Ejemplo n.º 13
0
    def test_normalize_transitions_requires_non_normalized_before_table(self):
        """Tests that transitory transitions table rejects a pre-normalized 'before' table"""
        transitions_table = CompartmentTransitions(self.test_data)
        transitions_table.transition_dfs["after"] = deepcopy(
            transitions_table.transition_dfs["before"])
        transitions_table.normalize_transitions(state="before")

        with self.assertRaises(ValueError):
            transitions_table.normalize_transitions(
                state="after",
                before_table=transitions_table.transition_dfs["before"])
Ejemplo n.º 14
0
    def test_apply_reduction_with_trivial_reductions_doesnt_change_transition_table(
        self, ):

        policy_mul = partial(
            CompartmentTransitions.apply_reduction,
            reduction_df=pd.DataFrame({
                "outflow": ["prison"] * 2,
                "affected_fraction": [0, 0.5],
                "reduction_size": [0.5, 0],
            }),
            reduction_type="*",
            retroactive=False,
        )

        policy_add = partial(
            CompartmentTransitions.apply_reduction,
            reduction_df=pd.DataFrame({
                "outflow": ["prison"] * 2,
                "affected_fraction": [0, 0.5],
                "reduction_size": [0.5, 0],
            }),
            reduction_type="+",
            retroactive=False,
        )

        compartment_policies = [
            SparkPolicy(policy_mul, "test_compartment",
                        {"sub_group": "test_population"}, False),
            SparkPolicy(policy_add, "test_compartment",
                        {"sub_group": "test_population"}, False),
        ]

        compartment_transitions = CompartmentTransitions(self.test_data)
        compartment_transitions.initialize(compartment_policies)

        assert_frame_equal(
            compartment_transitions.transition_dfs["before"],
            compartment_transitions.transition_dfs["after_non_retroactive"],
        )
Ejemplo n.º 15
0
    def test_unnormalized_table_inverse_of_normalize_table(self):
        compartment_transitions = CompartmentTransitions(self.test_data)
        original_before_table = compartment_transitions.transition_dfs[
            "before"].copy()
        # 'normalize' table (in the classical mathematical sense) to match scale of unnormalized table
        original_before_table /= original_before_table.sum().sum()

        compartment_transitions.normalize_transitions("before")
        compartment_transitions.unnormalize_table("before")
        assert_frame_equal(
            pd.DataFrame(original_before_table),
            pd.DataFrame(compartment_transitions.transition_dfs["before"]),
        )
Ejemplo n.º 16
0
    def test_apply_reduction_matches_example_by_hand(self):
        compartment_transitions = CompartmentTransitions(self.test_data)
        compartment_policy = [
            SparkPolicy(
                policy_fn=partial(
                    CompartmentTransitions.apply_reduction,
                    reduction_df=pd.DataFrame({
                        "outflow": ["prison"],
                        "affected_fraction": [0.25],
                        "reduction_size": [0.5],
                    }),
                    reduction_type="+",
                    retroactive=True,
                ),
                sub_population={"sub_group": "test_population"},
                spark_compartment="test_compartment",
                apply_retroactive=True,
            )
        ]

        expected_result = pd.DataFrame(
            {
                "jail": [4, 2, 0, 0, 0, 0, 0, 0, 0, 0],
                "prison": [2, 0.5, 3.5, 0, 0, 0, 0, 0, 0.375, 2.625],
            },
            index=range(1, 11),
            dtype=float,
        )
        expected_result.index.name = "compartment_duration"
        expected_result.columns.name = "outflow_to"
        expected_result /= expected_result.sum().sum()

        compartment_transitions.initialize(compartment_policy)
        compartment_transitions.unnormalize_table("after_retroactive")
        assert_frame_equal(
            round(compartment_transitions.transition_dfs["after_retroactive"],
                  8),
            round(expected_result, 8),
        )
Ejemplo n.º 17
0
class TestFullCompartment(unittest.TestCase):
    """Test the FullCompartment runs correctly"""

    def setUp(self):
        self.test_supervision_data = pd.DataFrame(
            {
                "compartment_duration": [1, 1, 2, 2.5, 10],
                "total_population": [4, 2, 2, 4, 3],
                "outflow_to": ["jail", "prison", "jail", "prison", "prison"],
                "compartment": ["test"] * 5,
            }
        )
        self.test_incarceration_data = pd.DataFrame(
            {
                "compartment_duration": [1, 1, 2, 2.5, 10],
                "total_population": [4, 2, 2, 4, 3],
                "outflow_to": [
                    "supervision",
                    "release",
                    "supervision",
                    "release",
                    "release",
                ],
                "compartment": ["test"] * 5,
            }
        )

        self.compartment_policies = []

        self.incarceration_transition_table = CompartmentTransitions(
            self.test_incarceration_data
        )
        self.incarceration_transition_table.initialize(self.compartment_policies)

        self.release_transition_table = CompartmentTransitions(
            self.test_supervision_data
        )
        self.release_transition_table.initialize(self.compartment_policies)

        self.historical_data = pd.DataFrame(
            {
                2015: {"jail": 2, "prison": 2},
                2016: {"jail": 1, "prison": 0},
                2017: {"jail": 1, "prison": 1},
            }
        )

    def test_step_forward_fails_without_initialized_edges(self):
        """Tests that step_forward() needs the initialize_edges() to have been run"""
        rel_compartment = FullCompartment(
            self.historical_data, self.release_transition_table, 2015, 2018, "release"
        )
        with self.assertRaises(ValueError):
            rel_compartment.step_forward()

    def test_all_edges_fed_to(self):
        """Tests that all edges in self.edges are included in self.transition_tables"""
        rel_compartment = FullCompartment(
            self.historical_data, self.release_transition_table, 2015, 2018, "release"
        )
        test_compartment = FullCompartment(
            self.historical_data,
            self.incarceration_transition_table,
            2015,
            2018,
            "test_compartment",
        )
        compartment_list = [rel_compartment, test_compartment]
        for compartment in compartment_list:
            compartment.initialize_edges(compartment_list)

        for compartment in compartment_list:
            compartment.step_forward()
Ejemplo n.º 18
0
    def _initialize_transition_tables(
        cls,
        transitions_data: pd.DataFrame,
        compartments_architecture: Dict[str, str],
        policy_list: List[SparkPolicy],
    ) -> Tuple[Dict[str, CompartmentTransitions], Dict[str,
                                                       List[SparkPolicy]]]:
        """Create and initialize all transition tables and store shell policies."""
        # Initialize a default transition class for each compartment to represent the no-policy scenario
        transitions_per_compartment = {}
        unused_transitions_data = transitions_data
        for compartment in compartments_architecture:
            compartment_type = compartments_architecture[compartment]
            compartment_duration_data = transitions_data[
                transitions_data["compartment"] == compartment]
            unused_transitions_data = unused_transitions_data.drop(
                compartment_duration_data.index)

            if compartment_duration_data.empty:
                if compartment_type != "shell":
                    raise ValueError(
                        f"Transition data missing for compartment {compartment}. Data is required for all "
                        "disaggregtion axes. Even the 'release' compartment needs transition data even if "
                        "it's just outflow to 'release'")
            else:
                if compartment_type == "full":
                    transition_class = CompartmentTransitions(
                        compartment_duration_data)
                elif compartment_type == "shell":
                    raise ValueError(
                        f"Cannot provide transitions data for shell compartment \n "
                        f"{compartment_duration_data}")
                else:
                    raise ValueError(
                        f"unrecognized transition table type {compartment_type}"
                    )

                transitions_per_compartment[compartment] = transition_class

        if len(unused_transitions_data) > 0:
            warn(
                f"Some transitions data not fed to a compartment: {unused_transitions_data}",
                Warning,
            )

        # Create a transition object for each compartment and year with policies applied and store shell policies
        shell_policies = dict()
        for compartment in compartments_architecture:
            # Select any policies that are applicable for this compartment
            compartment_policies = SparkPolicy.get_compartment_policies(
                policy_list, compartment)

            # add to the dict compartment -> transition class with policies applied
            if compartment in transitions_per_compartment:
                transitions_per_compartment[compartment].initialize(
                    compartment_policies)

            # add shell policies to dict that gets passed to initialization
            else:
                shell_policies[compartment] = compartment_policies

        return transitions_per_compartment, shell_policies
Ejemplo n.º 19
0
 def test_normalize_transitions_requires_initialized_transition_table(self):
     with self.assertRaises(ValueError):
         compartment_transitions = CompartmentTransitions(self.test_data)
         compartment_transitions.normalize_transitions(
             state="after_retroactive")