Feat/remove ballots with cand - #389
Conversation
peterrrock2
left a comment
There was a problem hiding this comment.
Looking good so far!
There was a problem hiding this comment.
Fixtures
Test all attrs for CleanProfile independently
Test chaining
Test idempotent
| no_rank_altr_idxs = { | ||
| idx for idx, c in zip(idxs, cleaned_rows) if all(x == tilde or x == empty for x in c) | ||
| } | ||
| no_rank_altr_idxs = no_rank_altr_idxs - unaltr_idxs |
There was a problem hiding this comment.
Document change in PR description, and update the doc string in the CleanProfile class. Please add examples in that doc string so we can more easily determine the meaning of these.
TODO:
Add issue where we use a dataclass to descriptively partition the possible state space for these alterations -- users should make the decision on what they car about
example
@dataclass(slots = true)
class CleaningIndexDeltas:
dropped: set or list or tuple --> entire row removed index does not exist in child
remove_empty_ranking: TypedDict { trailing_only: <object>, internal_only: <object>, both_trailing_and_internal: <object>}
subset_previous_ballot: TypedDict {strict_subset_no_gaps ([A, B, {C, D}, E] -> [B, {C,D}]), strict_subset_gaps, weak_subset ([A, B, {C,D}, E] -> [B, C])
... and so onThis is a loose sketch
| A removed ballot's ranking is considered empty after cleaning and recorded in the | ||
| ``no_rank_altr_idxs`` of the returned ``CleanedRankProfile``. |
There was a problem hiding this comment.
The current paradigm (which is not great) records dropped ballots as gaps in the index (compared to the parent profile)
| with that integer candidate. | ||
| """ | ||
| if not isinstance(profile, RankProfile): | ||
| raise ProfileError("Profile must be a RankProfile.") |
There was a problem hiding this comment.
| raise ProfileError("Profile must be a RankProfile.") | |
| raise TypeError("Profile must be a RankProfile.") |
| if isinstance(removed, Candidate) and not isinstance(removed, bool): | ||
| removed = [removed] | ||
| elif isinstance(removed, list): | ||
| if any(not isinstance(cand, (str, int)) or isinstance(cand, bool) for cand in removed): | ||
| raise TypeError("Candidates must be strings or integers within removed.") | ||
| else: | ||
| raise TypeError("removed must be a str/int candidate or a list of candidates.") |
There was a problem hiding this comment.
I think that we have a function that checks if something is a valid candidate that we should use here
| ranking_cols = [f"Ranking_{i}" for i in range(1, profile.max_ranking_length + 1)] | ||
| ballots_to_remove = profile._df[ranking_cols].isin(cand_ids).any(axis=1) | ||
| cleaned_df = profile.df[~ballots_to_remove] | ||
| removed_ballot_idxs = set(profile.df[ballots_to_remove].index) |
There was a problem hiding this comment.
| removed_ballot_idxs = set(profile.df[ballots_to_remove].index) |
Closes #381
Summary
remove_ballots_with_cand_rank_profileis added as a cleaning function to remove ballots containing a certain candidate or one from a set of candidates. Currently, specific candidate(s) can be removed from a ballot, but this supports workflows where ballots with an invalid write-in marker like"overvote"or"undervote"can be removed entirely.Changes
remove_ballots_with_cand_rank_profileis added torank_profiles_cleaning.py. The ballots are removed using the candidates' integer IDs within internaldf. The removed ballots are considered empty and recorded inno_rank_altr_idxsof the returnedCleanedRankProfile.no_rank_altr_idxsis modified to include ballots where all rank slots arefrozsenset()orfrozenset("~"). This accounts for ballots that are now empty after cleaning by removing candidates and replacing them with empty frozensets.unaltr_idxsare removed fromno_rank_altr_idxsto correctly distinguish which ballots were already empty and are empty after cleaningis_equiv_to_condensedupdated to return True if nofrozenset()in ranking or only trailing ones alongside anyfrozenset("~").is_equiv_for_remove_and_condenseupdated to useis_equiv_to_condensedTesting
test_remove_ballots_with_cand_rank_profile.pyadded with tests for ballots withremovedcandidates with and without tiestest_clean_ranked_profile.pyandtest_remove_cand_ranked_profile.pyupdated to account for the changes tono_rank_altr_idxsandunaltr_idxsnow including originally empty ballotstest_condense_ranked_profileupdated to account for changes tois_equiv_to_condensedto include originally empty ballots