Exemplo n.º 1
0
    def test_unsupported_versions_to_json(self, unsupported_version):
        counterfactual_explanations = CounterfactualExplanations(
            cf_examples_list=[],
            local_importance=None,
            summary_importance=None,
            version=unsupported_version)

        with pytest.raises(UserConfigValidationException) as ucve:
            counterfactual_explanations.to_json()

        assert "Unsupported serialization version {}".format(unsupported_version) in str(ucve)
Exemplo n.º 2
0
    def test_unsupported_versions_from_json(self, unsupported_version):
        json_str = json.dumps({'metadata': {'version': unsupported_version}})
        with pytest.raises(UserConfigValidationException) as ucve:
            CounterfactualExplanations.from_json(json_str)

        assert "Incompatible version {} found in json input".format(unsupported_version) in str(ucve)

        json_str = json.dumps({'metadata': {'versio': unsupported_version}})
        with pytest.raises(UserConfigValidationException) as ucve:
            CounterfactualExplanations.from_json(json_str)

        assert "No version field in the json input" in str(ucve)
Exemplo n.º 3
0
 def test_no_counterfactuals_found_summary_importance(
         self, desired_class, sample_custom_query_10, total_CFs, version):
     counterfactual_explanations = self.exp.global_feature_importance(
         query_instances=sample_custom_query_10,
         desired_class=desired_class,
         total_CFs=total_CFs)
     counterfactual_explanations.cf_examples_list[0].final_cfs_df = None
     counterfactual_explanations.cf_examples_list[
         0].final_cfs_df_sparse = None
     counterfactual_explanations.cf_examples_list[9].final_cfs_df = None
     counterfactual_explanations.cf_examples_list[
         9].final_cfs_df_sparse = None
     self.verify_counterfactual_explanations(
         counterfactual_explanations,
         None,
         sample_custom_query_10.shape[0],
         version,
         local_importance_available=True,
         summary_importance_available=True)
     counterfactual_explanations_as_json = counterfactual_explanations.to_json(
     )
     recovered_counterfactual_explanations = CounterfactualExplanations.from_json(
         counterfactual_explanations_as_json)
     self.verify_counterfactual_explanations(
         recovered_counterfactual_explanations,
         None,
         sample_custom_query_10.shape[0],
         version,
         local_importance_available=True,
         summary_importance_available=True)
     assert counterfactual_explanations == recovered_counterfactual_explanations
Exemplo n.º 4
0
    def test_summary_importance_output(self, desired_class,
                                       sample_custom_query_10, total_CFs,
                                       version):
        counterfactual_explanations = self.exp.global_feature_importance(
            query_instances=sample_custom_query_10,
            desired_class=desired_class,
            total_CFs=total_CFs)

        self.verify_counterfactual_explanations(
            counterfactual_explanations,
            total_CFs,
            sample_custom_query_10.shape[0],
            version,
            local_importance_available=True,
            summary_importance_available=True)

        json_output = counterfactual_explanations.to_json()
        assert json_output is not None
        assert json.loads(json_output).get('metadata').get(
            'version') == version

        recovered_counterfactual_explanations = CounterfactualExplanations.from_json(
            json_output)
        self.verify_counterfactual_explanations(
            counterfactual_explanations,
            total_CFs,
            sample_custom_query_10.shape[0],
            version,
            local_importance_available=True,
            summary_importance_available=True)

        assert recovered_counterfactual_explanations == counterfactual_explanations
Exemplo n.º 5
0
    def test_counterfactual_explanations_output(self, desired_class,
                                                sample_custom_query_1,
                                                total_CFs, version):
        counterfactual_explanations = self.exp.generate_counterfactuals(
            query_instances=sample_custom_query_1,
            desired_class=desired_class,
            total_CFs=total_CFs)

        self.verify_counterfactual_explanations(counterfactual_explanations,
                                                total_CFs,
                                                sample_custom_query_1.shape[0],
                                                version)

        json_output = counterfactual_explanations.to_json()
        assert json_output is not None
        assert json.loads(json_output).get('metadata').get(
            'version') == version

        recovered_counterfactual_explanations = CounterfactualExplanations.from_json(
            json_output)
        self.verify_counterfactual_explanations(counterfactual_explanations,
                                                total_CFs,
                                                sample_custom_query_1.shape[0],
                                                version)

        assert recovered_counterfactual_explanations == counterfactual_explanations
Exemplo n.º 6
0
    def test_sorted_summary_importance_counterfactual_explanations(self):

        unsorted_summary_importance = {
            "age": 0.985,
            "workclass": 0.65,
            "education": 0.915,
            "occupation": 0.95,
            "hours_per_week": 0.985,
            "gender": 0.67,
            "marital_status": 0.655,
            "race": 0.41
        }

        sorted_summary_importance = {
            "age": 0.985,
            "hours_per_week": 0.985,
            "occupation": 0.95,
            "education": 0.915,
            "gender": 0.67,
            "marital_status": 0.655,
            "workclass": 0.65,
            "race": 0.41
        }

        counterfactual_explanations = CounterfactualExplanations(
            cf_examples_list=[],
            local_importance=None,
            summary_importance=unsorted_summary_importance)

        assert unsorted_summary_importance == counterfactual_explanations.summary_importance
        assert sorted_summary_importance == counterfactual_explanations.summary_importance

        assert list(unsorted_summary_importance.keys()) != list(counterfactual_explanations.summary_importance.keys())
        assert list(sorted_summary_importance.keys()) == list(counterfactual_explanations.summary_importance.keys())
Exemplo n.º 7
0
    def test_sorted_local_importance_counterfactual_explanations(self):

        unsorted_local_importance = [{
            "age": 0.985,
            "workclass": 0.65,
            "education": 0.915,
            "occupation": 0.95,
            "hours_per_week": 0.985,
            "gender": 0.67,
            "marital_status": 0.655,
            "race": 0.41
        }, {
            "age": 0.985,
            "workclass": 0.65,
            "education": 0.915,
            "occupation": 0.95,
            "hours_per_week": 0.985,
            "gender": 0.67,
            "marital_status": 0.655,
            "race": 0.41
        }]

        sorted_local_importance = [{
            "age": 0.985,
            "hours_per_week": 0.985,
            "occupation": 0.95,
            "education": 0.915,
            "gender": 0.67,
            "marital_status": 0.655,
            "workclass": 0.65,
            "race": 0.41
        }, {
            "age": 0.985,
            "hours_per_week": 0.985,
            "occupation": 0.95,
            "education": 0.915,
            "gender": 0.67,
            "marital_status": 0.655,
            "workclass": 0.65,
            "race": 0.41
        }]

        counterfactual_explanations = CounterfactualExplanations(
            cf_examples_list=[],
            local_importance=unsorted_local_importance,
            summary_importance=None)

        for index in range(0, len(unsorted_local_importance)):
            assert unsorted_local_importance[
                index] == counterfactual_explanations.local_importance[index]
            assert sorted_local_importance[
                index] == counterfactual_explanations.local_importance[index]

        for index in range(0, len(unsorted_local_importance)):
            assert list(unsorted_local_importance[index].keys()) != \
                list(counterfactual_explanations.local_importance[index].keys())
            assert list(sorted_local_importance[index].keys()) == \
                list(counterfactual_explanations.local_importance[index].keys())
Exemplo n.º 8
0
    def generate_counterfactuals(self,
                                 query_instances,
                                 total_CFs,
                                 desired_class="opposite",
                                 desired_range=None,
                                 permitted_range=None,
                                 features_to_vary="all",
                                 stopping_threshold=0.5,
                                 posthoc_sparsity_param=0.1,
                                 posthoc_sparsity_algorithm="linear",
                                 verbose=False,
                                 **kwargs):
        """General method for generating counterfactuals.

        :param query_instances: Input point(s) for which counterfactuals are to be generated. This can be a dataframe with one or more rows.
        :param total_CFs: Total number of counterfactuals required.

        :param desired_class: Desired counterfactual class - can take 0 or 1. Default value is "opposite" to the outcome class of query_instance for binary classification.
        :param desired_range: For regression problems. Contains the outcome range to generate counterfactuals in.
        :param permitted_range: Dictionary with feature names as keys and permitted range in list as values. Defaults to the range inferred from training data. If None, uses the parameters initialized in data_interface.
        :param features_to_vary: Either a string "all" or a list of feature names to vary.
        :param stopping_threshold: Minimum threshold for counterfactuals target class probability.
        :param posthoc_sparsity_param: Parameter for the post-hoc operation on continuous features to enhance sparsity.
        :param posthoc_sparsity_algorithm: Perform either linear or binary search. Takes "linear" or "binary". Prefer binary search when a feature range is large (for instance, income varying from 10k to 1000k) and only if the features share a monotonic relationship with predicted outcome in the model.
        :param verbose: Whether to output detailed messages.
        :param sample_size: Sampling size
        :param random_seed: Random seed for reproducibility
        :param kwargs: Other parameters accepted by specific explanation method

        :returns: A CounterfactualExplanations object that contains the list of
        counterfactual examples per query_instance as one of its attributes.
        """
        if total_CFs <= 0:
            raise UserConfigValidationException(
                "The number of counterfactuals generated per query instance (total_CFs) should be a positive integer."
            )
        cf_examples_arr = []
        query_instances_list = []
        if isinstance(query_instances, pd.DataFrame):
            for ix in range(query_instances.shape[0]):
                query_instances_list.append(query_instances[ix:(ix + 1)])
        elif isinstance(query_instances, Iterable):
            query_instances_list = query_instances
        for query_instance in tqdm(query_instances_list):
            res = self._generate_counterfactuals(
                query_instance,
                total_CFs,
                desired_class=desired_class,
                desired_range=desired_range,
                permitted_range=permitted_range,
                features_to_vary=features_to_vary,
                stopping_threshold=stopping_threshold,
                posthoc_sparsity_param=posthoc_sparsity_param,
                posthoc_sparsity_algorithm=posthoc_sparsity_algorithm,
                verbose=verbose,
                **kwargs)
            cf_examples_arr.append(res)
        return CounterfactualExplanations(cf_examples_list=cf_examples_arr)
Exemplo n.º 9
0
    def test_serialization_deserialization_counterfactual_explanations_class(
            self):

        counterfactual_explanations = CounterfactualExplanations(
            cf_examples_list=[],
            local_importance=None,
            summary_importance=None)
        assert counterfactual_explanations.cf_examples_list is not None
        assert len(counterfactual_explanations.cf_examples_list) == 0
        assert counterfactual_explanations.summary_importance is None
        assert counterfactual_explanations.local_importance is None
        assert counterfactual_explanations.metadata is not None
        assert counterfactual_explanations.metadata['version'] is not None
        assert counterfactual_explanations.metadata['version'] == '1.0'

        counterfactual_explanations_as_json = counterfactual_explanations.to_json(
        )
        recovered_counterfactual_explanations = CounterfactualExplanations.from_json(
            counterfactual_explanations_as_json)
        assert counterfactual_explanations == recovered_counterfactual_explanations
Exemplo n.º 10
0
    def test_empty_counterfactual_explanations_object(self, version):

        counterfactual_explanations = CounterfactualExplanations(
            cf_examples_list=[],
            local_importance=None,
            summary_importance=None,
            version=version)
        self.verify_counterfactual_explanations(counterfactual_explanations, None,
                                                0, version)

        counterfactual_explanations_as_json = counterfactual_explanations.to_json()
        assert counterfactual_explanations_as_json is not None

        recovered_counterfactual_explanations = CounterfactualExplanations.from_json(
            counterfactual_explanations_as_json)

        self.verify_counterfactual_explanations(recovered_counterfactual_explanations, None,
                                                0, version)

        assert counterfactual_explanations == recovered_counterfactual_explanations
Exemplo n.º 11
0
    def test_KD_tree_counterfactual_explanations_output(
            self, desired_range, sample_custom_query_2, total_CFs):
        counterfactual_explanations = self.exp_regr.generate_counterfactuals(
            query_instances=sample_custom_query_2,
            total_CFs=total_CFs,
            desired_range=desired_range)

        assert counterfactual_explanations is not None
        json_str = counterfactual_explanations.to_json()
        assert json_str is not None

        recovered_counterfactual_explanations = CounterfactualExplanations.from_json(
            json_str)
        assert recovered_counterfactual_explanations is not None
        assert counterfactual_explanations == recovered_counterfactual_explanations
Exemplo n.º 12
0
 def test_no_counterfactuals_found(self, desired_class,
                                   sample_custom_query_1, total_CFs,
                                   version):
     counterfactual_explanations = self.exp.generate_counterfactuals(
         query_instances=sample_custom_query_1, desired_class=desired_class,
         total_CFs=total_CFs)
     counterfactual_explanations.cf_examples_list[0].final_cfs_df = None
     counterfactual_explanations.cf_examples_list[0].final_cfs_df_sparse = None
     self.verify_counterfactual_explanations(counterfactual_explanations, None,
                                             sample_custom_query_1.shape[0], version)
     counterfactual_explanations_as_json = counterfactual_explanations.to_json()
     recovered_counterfactual_explanations = CounterfactualExplanations.from_json(
         counterfactual_explanations_as_json)
     self.verify_counterfactual_explanations(recovered_counterfactual_explanations, None,
                                             sample_custom_query_1.shape[0], version)
     assert counterfactual_explanations == recovered_counterfactual_explanations
Exemplo n.º 13
0
    def load_result(self, data_directory_path):
        metadata_file_path = (data_directory_path /
                              (_CommonSchemaConstants.METADATA + '.json'))

        if metadata_file_path.exists():
            with open(metadata_file_path, 'r') as result_file:
                metadata = json.load(result_file)

            if metadata['version'] == _SchemaVersions.V1:
                cf_schema_keys = _V1SchemaConstants.ALL
            else:
                cf_schema_keys = _V2SchemaConstants.ALL

            counterfactual_examples_dict = {}
            for counterfactual_examples_key in cf_schema_keys:
                result_path = (data_directory_path /
                               (counterfactual_examples_key + '.json'))
                with open(result_path, 'r') as result_file:
                    counterfactual_examples_dict[
                        counterfactual_examples_key] = json.load(result_file)

            counterfactuals_json_str = json.dumps(counterfactual_examples_dict)
            self.counterfactual_obj = \
                CounterfactualExplanations.from_json(counterfactuals_json_str)
        else:
            self.counterfactual_obj = None

        result_path = (data_directory_path /
                       (CounterfactualConfig.HAS_COMPUTATION_FAILED + '.json'))
        with open(result_path, 'r') as result_file:
            self.has_computation_failed = json.load(result_file)

        result_path = (data_directory_path /
                       (CounterfactualConfig.FAILURE_REASON + '.json'))
        with open(result_path, 'r') as result_file:
            self.failure_reason = json.load(result_file)

        result_path = (data_directory_path /
                       (CounterfactualConfig.IS_COMPUTED + '.json'))
        with open(result_path, 'r') as result_file:
            self.is_computed = json.load(result_file)
Exemplo n.º 14
0
    def feature_importance(self,
                           query_instances,
                           cf_examples_list=None,
                           total_CFs=10,
                           local_importance=True,
                           global_importance=True,
                           desired_class="opposite",
                           desired_range=None,
                           permitted_range=None,
                           features_to_vary="all",
                           stopping_threshold=0.5,
                           posthoc_sparsity_param=0.1,
                           posthoc_sparsity_algorithm="linear",
                           **kwargs):
        """ Estimate feature importance scores for the given inputs.

        :param query_instances: A list of inputs for which to compute the
                                feature importances. These can be provided as a dataframe.
        :param cf_examples_list: If precomputed, a list of counterfactual
                                 examples for every input point. If cf_examples_list is provided, then
                                 all the following parameters are ignored.
        :param total_CFs: The number of counterfactuals to generate per input
                          (default is 10)
        :param other_parameters: These are the same as the generate_counterfactuals method.

        :returns: An object of class CounterfactualExplanations that includes
                  the list of counterfactuals per input, local feature importances per
                  input, and the global feature importance summarized over all inputs.
        """
        if cf_examples_list is None:
            cf_examples_list = self.generate_counterfactuals(
                query_instances,
                total_CFs,
                desired_class=desired_class,
                desired_range=desired_range,
                permitted_range=permitted_range,
                features_to_vary=features_to_vary,
                stopping_threshold=stopping_threshold,
                posthoc_sparsity_param=posthoc_sparsity_param,
                posthoc_sparsity_algorithm=posthoc_sparsity_algorithm,
                **kwargs).cf_examples_list
        allcols = self.data_interface.categorical_feature_names + self.data_interface.continuous_feature_names
        summary_importance = None
        local_importances = None
        if global_importance:
            summary_importance = {}
            # Initializing importance vector
            for col in allcols:
                summary_importance[col] = 0

        if local_importance:
            local_importances = [{} for _ in range(len(cf_examples_list))]
            # Initializing local importance for the ith query instance
            for i in range(len(cf_examples_list)):
                for col in allcols:
                    local_importances[i][col] = 0

        # Summarizing the found counterfactuals
        for i in range(len(cf_examples_list)):
            cf_examples = cf_examples_list[i]
            org_instance = cf_examples.test_instance_df

            if cf_examples.final_cfs_df_sparse is not None:
                df = cf_examples.final_cfs_df_sparse
            else:
                df = cf_examples.final_cfs_df
            for index, row in df.iterrows():
                for col in self.data_interface.continuous_feature_names:
                    if not np.isclose(org_instance[col].iat[0], row[col]):
                        if summary_importance is not None:
                            summary_importance[col] += 1
                        if local_importances is not None:
                            local_importances[i][col] += 1
                for col in self.data_interface.categorical_feature_names:
                    if org_instance[col].iat[0] != row[col]:
                        if summary_importance is not None:
                            summary_importance[col] += 1
                        if local_importances is not None:
                            local_importances[i][col] += 1

            if local_importances is not None:
                for col in allcols:
                    local_importances[i][col] /= (
                        cf_examples_list[0].final_cfs_df.shape[0])
        if summary_importance is not None:
            for col in allcols:
                summary_importance[col] /= (
                    cf_examples_list[0].final_cfs_df.shape[0] *
                    len(cf_examples_list))
        return CounterfactualExplanations(
            cf_examples_list,
            local_importance=local_importances,
            summary_importance=summary_importance)
Exemplo n.º 15
0
    def generate_counterfactuals(self,
                                 query_instance,
                                 total_CFs,
                                 desired_class="opposite",
                                 proximity_weight=0.5,
                                 diversity_weight=1.0,
                                 categorical_penalty=0.1,
                                 algorithm="DiverseCF",
                                 features_to_vary="all",
                                 permitted_range=None,
                                 yloss_type="hinge_loss",
                                 diversity_loss_type="dpp_style:inverse_dist",
                                 feature_weights="inverse_mad",
                                 optimizer="tensorflow:adam",
                                 learning_rate=0.05,
                                 min_iter=500,
                                 max_iter=5000,
                                 project_iter=0,
                                 loss_diff_thres=1e-5,
                                 loss_converge_maxiter=1,
                                 verbose=False,
                                 init_near_query_instance=True,
                                 tie_random=False,
                                 stopping_threshold=0.5,
                                 posthoc_sparsity_param=0.1,
                                 posthoc_sparsity_algorithm="linear",
                                 limit_steps_ls=10000):
        """Generates diverse counterfactual explanations

        :param query_instance: Test point of interest. A dictionary of feature names and values or a single row dataframe.
        :param total_CFs: Total number of counterfactuals required.
        :param desired_class: Desired counterfactual class - can take 0 or 1. Default value is "opposite" to the
                              outcome class of query_instance for binary classification.
        :param proximity_weight: A positive float. Larger this weight, more close the counterfactuals are to the
                                 query_instance.
        :param diversity_weight: A positive float. Larger this weight, more diverse the counterfactuals are.
        :param categorical_penalty: A positive float. A weight to ensure that all levels of a categorical variable sums to 1.

        :param algorithm: Counterfactual generation algorithm. Either "DiverseCF" or "RandomInitCF".
        :param features_to_vary: Either a string "all" or a list of feature names to vary.
        :param permitted_range: Dictionary with continuous feature names as keys and permitted min-max range in list as values.
                               Defaults to the range inferred from training data. If None, uses the parameters initialized in
                               data_interface.
        :param yloss_type: Metric for y-loss of the optimization function. Takes "l2_loss" or "log_loss" or "hinge_loss".
        :param diversity_loss_type: Metric for diversity loss of the optimization function.
                                    Takes "avg_dist" or "dpp_style:inverse_dist".
        :param feature_weights: Either "inverse_mad" or a dictionary with feature names as keys and
                                corresponding weights as values. Default option is "inverse_mad" where the weight
                                for a continuous feature is the inverse of the Median Absolute Devidation (MAD) of
                                the feature's values in the training set; the weight for a categorical feature is
                                equal to 1 by default.
        :param optimizer: Tensorflow optimization algorithm. Currently tested only with "tensorflow:adam".

        :param learning_rate: Learning rate for optimizer.
        :param min_iter: Min iterations to run gradient descent for.
        :param max_iter: Max iterations to run gradient descent for.
        :param project_iter: Project the gradients at an interval of these many iterations.
        :param loss_diff_thres: Minimum difference between successive loss values to check convergence.
        :param loss_converge_maxiter: Maximum number of iterations for loss_diff_thres to hold to declare convergence.
                                      Defaults to 1, but we assigned a more conservative value of 2 in the paper.
        :param verbose: Print intermediate loss value.
        :param init_near_query_instance: Boolean to indicate if counterfactuals are to be initialized near query_instance.
        :param tie_random: Used in rounding off CFs and intermediate projection.
        :param stopping_threshold: Minimum threshold for counterfactuals target class probability.
        :param posthoc_sparsity_param: Parameter for the post-hoc operation on continuous features to enhance sparsity.
        :param posthoc_sparsity_algorithm: Perform either linear or binary search. Takes "linear" or "binary".
                                           Prefer binary search when a feature range is large
                                           (for instance, income varying from 10k to 1000k) and only if the features
                                           share a monotonic relationship with predicted outcome in the model.
        :param limit_steps_ls: Defines an upper limit for the linear search step in the posthoc_sparsity_enhancement


        :return: A CounterfactualExamples object to store and visualize the resulting counterfactual explanations
                 (see diverse_counterfactuals.py).

        """

        # check feature MAD validity and throw warnings
        if feature_weights == "inverse_mad":
            self.data_interface.get_valid_mads(display_warnings=True,
                                               return_mads=False)

        # check permitted range for continuous features
        if permitted_range is not None:
            # if not self.data_interface.check_features_range(permitted_range):
            #     raise ValueError(
            #         "permitted range of features should be within their original range")
            # else:
            self.data_interface.permitted_range = permitted_range
            self.minx, self.maxx = self.data_interface.get_minx_maxx(
                normalized=True)
            self.cont_minx = []
            self.cont_maxx = []
            for feature in self.data_interface.continuous_feature_names:
                self.cont_minx.append(
                    self.data_interface.permitted_range[feature][0])
                self.cont_maxx.append(
                    self.data_interface.permitted_range[feature][1])

        if ([
                total_CFs, algorithm, features_to_vary, yloss_type,
                diversity_loss_type, feature_weights, optimizer
        ] != (self.cf_init_weights + self.loss_weights +
              self.optimizer_weights)):
            self.do_cf_initializations(total_CFs, algorithm, features_to_vary)
            self.do_loss_initializations(yloss_type, diversity_loss_type,
                                         feature_weights)
            self.do_optimizer_initializations(optimizer)
        """
        Future Support: We have three main components in our tensorflow graph:
        (1) initialization of tf.variables
        (2) defining ops for loss function initializations, and
        (3) defining ops for optimizer initializations.

        Need to define methods to delete some nodes from a tensorflow graphs or update
        variables/ops in a tensorflow graph dynamically, so that only those components
        corresponding to the variables that are updated change.
        """
        # check if hyperparameters are to be updated
        if not collections.Counter([proximity_weight, diversity_weight, categorical_penalty]) == \
                collections.Counter(self.hyperparameters):
            self.update_hyperparameters(proximity_weight, diversity_weight,
                                        categorical_penalty)

        final_cfs_df, test_instance_df, final_cfs_df_sparse = self.find_counterfactuals(
            query_instance, limit_steps_ls, desired_class, learning_rate,
            min_iter, max_iter, project_iter, loss_diff_thres,
            loss_converge_maxiter, verbose, init_near_query_instance,
            tie_random, stopping_threshold, posthoc_sparsity_param,
            posthoc_sparsity_algorithm)

        counterfactual_explanations = exp.CounterfactualExamples(
            data_interface=self.data_interface,
            final_cfs_df=final_cfs_df,
            test_instance_df=test_instance_df,
            final_cfs_df_sparse=final_cfs_df_sparse,
            posthoc_sparsity_param=posthoc_sparsity_param,
            desired_class=desired_class)

        return CounterfactualExplanations(
            cf_examples_list=[counterfactual_explanations])
Exemplo n.º 16
0
    def generate_counterfactuals(self, query_instances, total_CFs,
                                 desired_class="opposite", desired_range=None,
                                 permitted_range=None, features_to_vary="all",
                                 stopping_threshold=0.5, posthoc_sparsity_param=0.1,
                                 proximity_weight=0.2, sparsity_weight=0.2, diversity_weight=5.0,
                                 categorical_penalty=0.1,
                                 posthoc_sparsity_algorithm="linear", verbose=False, **kwargs):
        """General method for generating counterfactuals.

        :param query_instances: Input point(s) for which counterfactuals are to be generated.
                                This can be a dataframe with one or more rows.
        :param total_CFs: Total number of counterfactuals required.
        :param desired_class: Desired counterfactual class - can take 0 or 1. Default value
                              is "opposite" to the outcome class of query_instance for binary classification.
        :param desired_range: For regression problems. Contains the outcome range to
                              generate counterfactuals in. This should be a list of two numbers in
                              ascending order.
        :param permitted_range: Dictionary with feature names as keys and permitted range in list as values.
                                Defaults to the range inferred from training data.
                                If None, uses the parameters initialized in data_interface.
        :param features_to_vary: Either a string "all" or a list of feature names to vary.
        :param stopping_threshold: Minimum threshold for counterfactuals target class probability.
        :param proximity_weight: A positive float. Larger this weight, more close the counterfactuals are to the
                                 query_instance. Used by ['genetic', 'gradientdescent'],
                                 ignored by ['random', 'kdtree'] methods.
        :param sparsity_weight: A positive float. Larger this weight, less features are changed from the query_instance.
                                Used by ['genetic', 'kdtree'], ignored by ['random', 'gradientdescent'] methods.
        :param diversity_weight: A positive float. Larger this weight, more diverse the counterfactuals are.
                                 Used by ['genetic', 'gradientdescent'], ignored by ['random', 'kdtree'] methods.
        :param categorical_penalty: A positive float. A weight to ensure that all levels of a categorical variable sums to 1.
                                 Used by ['genetic', 'gradientdescent'], ignored by ['random', 'kdtree'] methods.
        :param posthoc_sparsity_param: Parameter for the post-hoc operation on continuous features to enhance sparsity.
        :param posthoc_sparsity_algorithm: Perform either linear or binary search. Takes "linear" or "binary".
                                           Prefer binary search when a feature range is large (for instance,
                                           income varying from 10k to 1000k) and only if the features share a
                                           monotonic relationship with predicted outcome in the model.
        :param verbose: Whether to output detailed messages.
        :param sample_size: Sampling size
        :param random_seed: Random seed for reproducibility
        :param kwargs: Other parameters accepted by specific explanation method

        :returns: A CounterfactualExplanations object that contains the list of
                  counterfactual examples per query_instance as one of its attributes.
        """
        self._validate_counterfactual_configuration(
            query_instances=query_instances,
            total_CFs=total_CFs,
            desired_class=desired_class,
            desired_range=desired_range,
            permitted_range=permitted_range, features_to_vary=features_to_vary,
            stopping_threshold=stopping_threshold, posthoc_sparsity_param=posthoc_sparsity_param,
            posthoc_sparsity_algorithm=posthoc_sparsity_algorithm, verbose=verbose,
            kwargs=kwargs
        )

        cf_examples_arr = []
        query_instances_list = []
        if isinstance(query_instances, pd.DataFrame):
            for ix in range(query_instances.shape[0]):
                query_instances_list.append(query_instances[ix:(ix+1)])
        elif isinstance(query_instances, Iterable):
            query_instances_list = query_instances

        for query_instance in tqdm(query_instances_list):
            self.data_interface.set_continuous_feature_indexes(query_instance)
            res = self._generate_counterfactuals(
                query_instance, total_CFs,
                desired_class=desired_class,
                desired_range=desired_range,
                permitted_range=permitted_range,
                features_to_vary=features_to_vary,
                stopping_threshold=stopping_threshold,
                posthoc_sparsity_param=posthoc_sparsity_param,
                posthoc_sparsity_algorithm=posthoc_sparsity_algorithm,
                verbose=verbose,
                **kwargs)
            cf_examples_arr.append(res)

        self._check_any_counterfactuals_computed(cf_examples_arr=cf_examples_arr)

        return CounterfactualExplanations(cf_examples_list=cf_examples_arr)