def lambda_handler(event, context): if 'body' in event.keys(): event = json.loads(event["body"]) bucket = "districtr" state = event["state"].lower().replace(" ", "_") units = event["units"].lower().replace(" ", "") # district = event["dist_id"] plan_assignment = event["assignment"] keys = set(plan_assignment.keys()) key = "dual_graphs/{}_{}.json".format(state, units) try: data = s3.get_object(Bucket=bucket, Key=key) g = json_graph.adjacency_graph(json.load(data['Body'])) graph = Graph(g) assignment = { n: plan_assignment[n] if n in keys else -1 for n in graph.nodes() } part = GeographicPartition(graph, assignment) return {'statusCode': 200, 'body': json.dumps(plan_evaluation(part))} except Exception as e: print(e) return { "error": "This state/units ({}, {}) is not supported".format( event["state"], event["units"]) }
def test_pa_freeze(): from gerrychain import ( GeographicPartition, Graph, MarkovChain, proposals, updaters, constraints, accept, ) import hashlib from gerrychain.proposals import recom from functools import partial graph = Graph.from_json("docs/user/PA_VTDs.json") my_updaters = {"population": updaters.Tally("TOTPOP", alias="population")} initial_partition = GeographicPartition(graph, assignment="CD_2011", updaters=my_updaters) ideal_population = sum( initial_partition["population"].values()) / len(initial_partition) # We use functools.partial to bind the extra parameters (pop_col, pop_target, epsilon, node_repeats) # of the recom proposal. proposal = partial(recom, pop_col="TOTPOP", pop_target=ideal_population, epsilon=0.02, node_repeats=2) pop_constraint = constraints.within_percent_of_ideal_population( initial_partition, 0.02) chain = MarkovChain( proposal=proposal, constraints=[pop_constraint], accept=accept.always_accept, initial_state=initial_partition, total_steps=100, ) result = "" for count, partition in enumerate(chain): result += str(list(sorted(partition.population.values()))) result += str(len(partition.cut_edges)) result += str(count) + "\n" assert hashlib.sha256(result.encode()).hexdigest( ) == "3bef9ac8c0bfa025fb75e32aea3847757a8fba56b2b2be6f9b3b952088ae3b3c"
def run_GerryChain_heuristic(G, population_deviation, k, iterations): my_updaters = {"population": updaters.Tally("TOTPOP", alias="population")} start = recursive_tree_part( G, range(k), sum(G.nodes[i]["TOTPOP"] for i in G.nodes()) / k, "TOTPOP", population_deviation / 2, 1) initial_partition = GeographicPartition(G, start, updaters=my_updaters) proposal = partial(recom, pop_col="TOTPOP", pop_target=sum(G.nodes[i]["TOTPOP"] for i in G.nodes()) / k, epsilon=population_deviation / 2, node_repeats=2) compactness_bound = constraints.UpperBound( lambda p: len(p["cut_edges"]), 1.5 * len(initial_partition["cut_edges"])) pop_constraint = constraints.within_percent_of_ideal_population( initial_partition, population_deviation / 2) my_chain = MarkovChain(proposal=proposal, constraints=[pop_constraint, compactness_bound], accept=accept.always_accept, initial_state=initial_partition, total_steps=iterations) min_cut_edges = sum(G[i][j]['edge_length'] for i, j in G.edges) print("In GerryChain heuristic, current # of cut edges: ", end='') print(min_cut_edges, ",", sep='', end=' ') for partition in my_chain: current_cut_edges = sum(G[i][j]['edge_length'] for i, j in partition["cut_edges"]) print(current_cut_edges, ",", sep='', end=' ') if current_cut_edges < min_cut_edges: best_partition = partition min_cut_edges = current_cut_edges print("Best heuristic solution has # cut edges =", min_cut_edges) return ([[i for i in G.nodes if best_partition.assignment[i] == j] for j in range(k)], min_cut_edges)
def run_chain(adj_graph, elections, total_steps=100): my_updaters = { "population": updaters.Tally("population"), "cut_edges": cut_edges, "elections": lambda x: [elec.alias for elec in elections], "expected_seat_share": expected_seat_share_updater } election_updaters = {election.name: election for election in elections} my_updaters.update(election_updaters) initial_partition = GeographicPartition(adj_graph, assignment="initial_plan", updaters=my_updaters) ideal_population = sum( initial_partition["population"].values()) / len(initial_partition) proposal = partial(recom, pop_col="population", pop_target=ideal_population, epsilon=0.01, node_repeats=2) pop_constraint = constraints.within_percent_of_ideal_population( initial_partition, 0.01) chain = MarkovChain(proposal=proposal, constraints=[ pop_constraint, ], accept=accept.always_accept, initial_state=initial_partition, total_steps=total_steps) return pd.DataFrame({ ix: { "cut_edges": len(partition["cut_edges"]), "expected_R_seats": partition["expected_seat_share"] } for ix, partition in enumerate(chain) }).T
## ## ## ## ## ## ## ## ## ## ## ## Initial partition ## ## ## ## ## ## ## ## ## ## ## print("Creating seed plan") ################################################################## ######## ! if using a random map as the initial partition ################################################################## # NUM_DISTRICTS=34 # EPS=0.05 total_pop = sum(dat[POP_COL]) ideal_pop = total_pop / NUM_DISTRICTS cddict = recursive_tree_part(graph=graph, parts=range(NUM_DISTRICTS), pop_target=ideal_pop, pop_col=POP_COL, epsilon=EPS) init_partition = GeographicPartition(graph, assignment=cddict, updaters=mo_updaters) # init_partition["USSEN16"].efficiency_gap() #PRES EG = -0.08, USSEN EG = -0.18 ################################################################## ######## ! if using the enacted state senate map as the initial partition ################################################################## # init_partition = GeographicPartition(graph, assignment="SLDUST", updaters=mo_updaters) # init_partition["USSEN16"].efficiency_gap() # ideal_pop = sum(init_partition['population'].values()) / len(init_partition) # # show stuff about the initial partition # init_partition.graph # init_partition.graph.nodes[0] # init_partition['population'] # for district, pop in init_partition["population"].items():
def plan_evaluation(partition): graph = partition.graph # Naming constants county_column = "COUNTY" municipality_column = "TOWN" # Contiguity split_districts = [] for district in partition.parts.keys(): if district != -1: part_contiguous = nx.is_connected(partition.subgraphs[district]) if not part_contiguous: split_districts.append(district) contiguity = (len(split_districts) == 0) # Calculate cut edges cut_edges = updaters.cut_edges(partition) # Polsby Popper try: polsbypopper = [ v for _k, v in metrics.polsby_popper(partition).items() ] polsbypopper_stats = { 'max': max(polsbypopper), 'min': min(polsbypopper), 'mean': statistics.mean(polsbypopper), 'median': statistics.median(polsbypopper) } if len(polsbypopper) > 1: polsbypopper_stats['variance'] = statistics.variance(polsbypopper) except: polsbypopper = [] polsbypopper_stats = "Polsby Popper unavailable for this geometry." # county splits try: county_splits = updaters.county_splits("county_splits", county_column)(partition) county_response = {} counties = set([graph.nodes[n][county_column] for n in graph.nodes]) county_response['num_counties'] = len(counties) try: county_partition = GeographicPartition( graph, county_column, {"population": updaters.Tally('TOTPOP', alias="population")}) county_pop_dict = { c: county_partition.population[c] for c in counties } county_response['population'] = county_pop_dict except KeyError: county_response['population'] = -1 split_list = {} splits = 0 num_split = 0 for c in counties: s = county_splits[c].contains # this is caused by a bug in some states in gerrychain I think if -1 in s: s.remove(-1) if len(s) > 1: num_split += 1 split_list[c] = list(s) splits += len(s) - 1 county_response['splits'] = splits county_response['split_list'] = split_list except: county_response = -1 # municipality splits try: municipality_splits = updaters.county_splits( "muni_splits", municipality_column)(partition) muni_response = {} munis = set([graph.nodes[n][municipality_column] for n in graph.nodes]) muni_response['num_counties'] = len(munis) try: muni_partition = GeographicPartition( graph, municipality_column, {"population": updaters.Tally('TOTPOP', alias="population")}) muni_pop_dict = {m: muni_partition.population[m] for m in munis} muni_response['population'] = muni_pop_dict muni_response['num_counties'] = len(munis) except KeyError: muni_response['population'] = -1 split_list = {} splits = 0 num_split = 0 for m in munis: s = municipality_splits[m].contains # this is caused by a bug in some states in gerrychain I think if -1 in s: s.remove(-1) if len(s) > 1: num_split += 1 split_list[m] = list(s) splits += len(s) - 1 muni_response['splits'] = splits muni_response['split_list'] = split_list except: muni_response = -1 # Build Response dictionary response = { 'cut_edges': len(cut_edges), 'contiguity': contiguity, 'split': split_districts, 'polsbypopper': polsbypopper_stats, 'pp_scores': polsbypopper, 'num_units': len(graph.nodes), 'counties': county_response, 'municipalities': muni_response } return response
def chain(iterations): idef = random.randint(1, 10000) graph = Graph.from_json("./PA_VTD.json") election = Election("SEN12", {"Dem": "USS12D", "Rep": "USS12R"}) initial_partition = GeographicPartition(graph, assignment="2011_PLA_1", updaters={ "cut_edges": cut_edges, "population": Tally("TOT_POP", alias="population"), "SEN12": election }) ideal_population = sum( initial_partition["population"].values()) / len(initial_partition) # We use functools.partial to bind the extra parameters (pop_col, pop_target, epsilon, node_repeats) # of the recom proposal. proposal = partial(recom, pop_col="TOT_POP", pop_target=ideal_population, epsilon=0.02, node_repeats=2) chain = MarkovChain(proposal=proposal, constraints=[republican_constraint], accept=contiguous, initial_state=initial_partition, total_steps=iterations + 100) count = 0 metrics = [] boundary_nodes = [] boundary_weighted = [] for partition in chain.with_progress_bar(): mm = mean_median(partition["SEN12"]) p = pp(partition) bias = partisan_bias(partition["SEN12"]) gini = partisan_gini(partition["SEN12"]) gap = efficiency_gap(partition["SEN12"]) cut = len(partition["cut_edges"]) if count >= 100: metrics.append((mm, p, bias, gini, gap, cut)) nodes = [0] * 8921 bnodes = [0] * 8921 for edge in partition["cut_edges"]: nodes[edge[0]] = 1 nodes[edge[1]] = 1 bnodes[edge[0]] += 1 bnodes[edge[1]] += 1 boundary_nodes.append(nodes) boundary_weighted.append(bnodes) if count % 100 == 0: print(idef, count, mm, p, bias, gini, gap, cut, partition["SEN12"].wins("Rep")) count += 1 return metrics, boundary_nodes, boundary_weighted
def chain(iterations): idef = random.randint(1, 10000) graph = Graph.from_json("../data/PA_init/PA_VTD.json") election = Election("SEN12", {"Dem": "USS12D", "Rep": "USS12R"}) initial_partition = GeographicPartition( graph, assignment="2011_PLA_1", updaters={ "cut_edges": cut_edges, "population": Tally("TOT_POP", alias="population"), "SEN12": election } ) ideal_population = sum(initial_partition["population"].values()) / len(initial_partition) # We use functools.partial to bind the extra parameters (pop_col, pop_target, epsilon, node_repeats) # of the recom proposal. proposal = partial(recom, pop_col="TOT_POP", pop_target=ideal_population, epsilon=0.02, node_repeats=2 ) chain = MarkovChain( proposal=proposal, constraints=[], accept=contiguous, initial_state=initial_partition, total_steps=85*iterations + 17000 ) count = 0 metrics = [] boundary_nodes = [] boundary_weighted = [] for partition in chain.with_progress_bar(): mm = mean_median(partition["SEN12"]) p = pp(partition) bias = partisan_bias(partition["SEN12"]) gini = partisan_gini(partition["SEN12"]) gap = efficiency_gap(partition["SEN12"]) cut = len(partition["cut_edges"]) if count >= 17000: if count % 85 == 0: metrics.append((mm, p, bias, gini, gap, cut, partition["SEN12"].wins("Rep"))) nodes = [0]*8921 bnodes = [0]*8921 for edge in partition["cut_edges"]: nodes[edge[0]] = 1 nodes[edge[1]] = 1 bnodes[edge[0]] += 1 bnodes[edge[1]] += 1 boundary_nodes.append(nodes) boundary_weighted.append(bnodes) assign = {i: partition.assignment[i] for i in partition.assignment} shape["CD"] = shape.index.map(assign) this_map = shape.dissolve(by='CD') this_map.plot(color='white', edgecolor='black') plt.axis('off') fig = plt.gcf() fig.set_size_inches((15,9), forward=False) fig.savefig("../images/PA_neutral/" + str(idef) + str(int((count-17000)/85)+1) + ".png", dpi=600, bbox_inches="tight", pad_inches=0) plt.close() if count % 8500 == 0: print(idef, count, mm, p, bias, gini, gap, cut, partition["SEN12"].wins("Rep")) else: if count%1000 == 0: print(idef, "Mixing...... Iteration", count, "/17000") count += 1 return metrics, boundary_nodes, boundary_weighted, idef
#Use ReCom to build gerrychain and plot. elections = [ Election("PRES16", { "Dem": "G16DPRS", "Rep": "G16RPRS" }), Election("SEN12", { "Dem": "G18DSEN", "Rep": "G18RSEN" }) ] my_updaters = {"population": updaters.Tally("TOTPOP", alias="population")} election_updaters = {election.name: election for election in elections} my_updaters.update(election_updaters) initial_partition = GeographicPartition(graph, assignment="CD_16", updaters=my_updaters) # The ReCom proposal needs to know the ideal population for the districts so that # we can improve speed by bailing early on unbalanced partitions. ideal_population = sum( initial_partition["population"].values()) / len(initial_partition) # We use functools.partial to bind the extra parameters (pop_col, pop_target, epsilon, node_repeats) # of the recom proposal. proposal = partial(recom, pop_col="TOTPOP", pop_target=ideal_population, epsilon=0.03, node_repeats=2) compactness_bound = constraints.UpperBound(
Election( election_names[i], { "Dem": election_columns[i][0], "Rep": election_columns[i][1] }, ) for i in range(num_elections) ] election_updaters = {election.name: election for election in elections} updaters.update(election_updaters) # ---- Past Districting Plan ----- districting_partition = GeographicPartition(graph, district_assignment_col, updaters) compactness_ref = len(districting_partition["cut_edges"]) county_splits_ref = calc_splits(districting_partition["county_splits"]) racial_vap_ref = districting_partition["racial_demographics"] # ---- Chain Run ------ # population calculation pop = 0 for n in graph.nodes: pop = pop + graph.nodes[n][pop_col] # --- Create random initial partition -------
nlist = list(g.nodes()) n = len(nlist) shuffle = False num_steps = 10 pos = nx.kamada_kawai_layout(g) if shape: pos = {node: (c_x[node], c_y[node]) for node in g.nodes} unique_id = 0 for n in nlist: g.node[n]["PP_ID"] = unique_id unique_id += 1 pp_dic = GeographicPartition(g, "PP_ID", {"pp": polsby_popper}) bg_pp_list = list(pp_dic["pp"].values()) k = 2 for n in nlist: if bg_pp_list[n] > 0.8: g.node[n]["cddict"] = 0 elif bg_pp_list[n] < 0.1: g.node[n]["cddict"] = 1 else: g.node[n]["cddict"] = random.choice([0, 1]) nx.draw(g, pos=pos, node_color=[g.node[x]["cddict"] for x in nlist], node_size=25)
] # Configure our updaters (everything we want to compute for each plan in the ensemble). # Population updater, for computing how close to equality the district # populations are. "TOT_POP" is the population column from our shapefile. my_updaters = {"population": updaters.Tally("TOT_POP", alias="population")} # Election updaters, for computing election results using the vote totals # from our shapefile. election_updaters = {election.name: election for election in elections} my_updaters.update(election_updaters) # Instantiate the initial state of our Markov chain, using the 2011 districting plan. initial_partition = GeographicPartition(graph, assignment="2011_PLA_1", updaters=my_updaters) # GeographicPartition comes with built-in ``area`` and ``perimeter`` updaters. # ## Setting up the Markov chain # The recom proposal needs to know the ideal population for the districts so that # we can improve speed by bailing early on unbalanced partitions. ideal_population = sum( initial_partition["population"].values()) / len(initial_partition) # We use functools.partial to bind the extra parameters (pop_col, pop_target, epsilon, node_repeats) # of the recom proposal. proposal = partial(recom, pop_col="TOT_POP",
election.name: election for election in election_functions } my_updaters.update(election_updaters) #initial partition####################################################### step_Num = 0 total_population = state_gdf[tot_pop].sum() ideal_population = total_population / num_districts if start_map == 'new_seed': start_map = recursive_tree_part(graph, range(num_districts), ideal_population, tot_pop, pop_tol, 3) initial_partition = GeographicPartition(graph=graph, assignment=start_map, updaters=my_updaters) proposal = partial(recom, pop_col=tot_pop, pop_target=ideal_population, epsilon=pop_tol, node_repeats=3) #constraints ############################################ def inclusion(partition): """ Returns 'True' if proposed plan has greater than or equal to the number of Black, Latino and distinct effective districts as the enacted map. """
def run_GerryChain_heuristic(G, population_deviation, k, threshold, iterations): my_updaters = {"population": updaters.Tally("TOTPOP", alias="population")} start = recursive_tree_part( G, range(k), sum(G.nodes[i]["TOTPOP"] for i in G.nodes()) / k, "TOTPOP", population_deviation / 2, 1) initial_partition = GeographicPartition(G, start, updaters=my_updaters) proposal = partial(recom, pop_col="TOTPOP", pop_target=sum(G.nodes[i]["TOTPOP"] for i in G.nodes()) / k, epsilon=population_deviation / 2, node_repeats=2) compactness_bound = constraints.UpperBound( lambda p: len(p["cut_edges"]), 1.5 * len(initial_partition["cut_edges"])) pop_constraint = constraints.within_percent_of_ideal_population( initial_partition, population_deviation / 2) my_chain = MarkovChain(proposal=proposal, constraints=[pop_constraint, compactness_bound], accept=accept.always_accept, initial_state=initial_partition, total_steps=iterations) min_cut_edges = sum(G[i][j]['edge_length'] for i, j in G.edges) print( "In GerryChain heuristic, current # of cut edges and minority districts: ", end='') print(min_cut_edges, ",", sep='', end=' ') max_of_minority_districts = -1 all_maps = [] pareto_frontier = [] obj_vals = [] for partition in my_chain: current_cut_edges = sum(G[i][j]['edge_length'] for i, j in partition["cut_edges"]) number_minority_district = 0 for district in range(k): total_pop_district = 0 total_pop_minority = 0 for node in partition.graph: if partition.assignment[node] == district: total_pop_district += G.node[node]["VAP"] total_pop_minority += G.node[node]["BVAP"] #total_pop_minority += G.node[node]["HVAP"] #total_pop_minority += G.node[node]["AMINVAP"] #total_pop_minority += G.node[node]["ASIANVAP"] #total_pop_minority += G.node[node]["NHPIVAP"] if (total_pop_minority > threshold * total_pop_district): number_minority_district += 1 if number_minority_district > max_of_minority_districts: max_of_minority_districts = number_minority_district if current_cut_edges < min_cut_edges: min_cut_edges = current_cut_edges print((current_cut_edges, number_minority_district), ",", sep='', end=' ') obj_vals.append([current_cut_edges, number_minority_district]) all_maps.append( [partition, current_cut_edges, number_minority_district]) print("Best heuristic solution has # cut edges =", min_cut_edges) print("Best heuristic solution has # minority districts =", max_of_minority_districts) all_maps.sort(key=lambda x: x[1]) all_maps.sort(key=lambda x: x[2], reverse=True) pareto_frontier.append(all_maps[0]) least_number_of_cut_edges = all_maps[0][1] for i in range(1, len(all_maps)): if all_maps[i][1] < least_number_of_cut_edges: pareto_frontier.append(all_maps[i]) least_number_of_cut_edges = all_maps[i][1] print("Pareto Frontier: ", pareto_frontier) optimal_maps = [] optimal_cut_edges = [] optimal_minority_districts = [] for plan in pareto_frontier: optimal_maps.append( [[i for i in G.nodes if plan[0].assignment[i] == j] for j in range(k)]) optimal_cut_edges.append(plan[1]) optimal_minority_districts.append(plan[2]) return (optimal_maps, optimal_cut_edges, optimal_minority_districts, obj_vals)
Election( election_names[i], { "Dem": election_columns[i][0], "Rep": election_columns[i][1] }, ) for i in range(num_elections) ] election_updaters = {election.name: election for election in elections} updaters.update(election_updaters) # ---- Proposed Partition ----- prop_partition = GeographicPartition(graph, district_assignment_col, updaters) # ---- Geographic Metrics ------ # County Splits num_county_splits = calc_splits(prop_partition["county_splits"]) upper_county = len(prop_partition["county_splits"]) county_partition = GeographicPartition(graph, county_assignment_col, updaters) if cong: threshold_pop = cong_factor * cong_seat_pop else: total_pop = 0 for n in graph.nodes: total_pop = total_pop + graph.nodes[n][pop_col]
Election( election_names[i], { "Dem": election_columns[i][0], "Rep": election_columns[i][1] }, ) for i in range(num_elections) ] election_updaters = {election.name: election for election in elections} updaters.update(election_updaters) # ---- Proposed Partition ----- initial_partition = GeographicPartition(graph, district_assignment_col, updaters) # ---- Chain Run ------ pop = 0 for n in graph.nodes: pop = pop + graph.nodes[n][pop_col] proposal = partial(recom, pop_col=pop_col, pop_target=pop / num_dist, epsilon=0.05, node_repeats=3) compactness_bound = constraints.UpperBound(lambda p: len(p["cut_edges"]),
def main(graph_json, shp, n_steps, output_dir, prefix, seed, pop_col, pop_tol, plan_col, reproject, election): os.makedirs(output_dir, exist_ok=True) has_geometry = False if not shp and not graph_json: print('Specify a shapefile or a NetworkX-format graph ' 'JSON file.', file=sys.stderr) sys.exit(1) elif shp and not graph_json: gdf = gpd.read_file(shp) if reproject: gdf = reprojected(gdf) graph = Graph.from_geodataframe(gdf) has_geometry = True elif graph_json and not shp: graph = Graph.from_json(graph_json) else: graph = Graph.from_json(graph_json) gdf = gpd.read_file(shp) if reproject: gdf = reprojected(gdf) print('Appending geometries from shapefile to graph...') graph.geometry = gdf.geometry # TODO: is this always valid? has_geometry = True my_updaters = {'population': updaters.Tally(pop_col, alias='population')} if election: election_up = Election('election', { 'Democratic': election[0], 'Republican': election[1] }) my_updaters['election'] = election_up initial_state = GeographicPartition(graph, assignment=plan_col, updaters=my_updaters) normal_chain = RecomChain(graph=graph, total_steps=n_steps, initial_state=initial_state, pop_col=pop_col, pop_tol=pop_tol, reversible=False, seed=seed) reversible_chain = RecomChain(graph=graph, total_steps=n_steps, initial_state=initial_state, pop_col=pop_col, pop_tol=pop_tol, reversible=True, seed=seed) normal_plans = [plan for plan in tqdm(normal_chain)] reversible_plans = [plan for plan in tqdm(reversible_chain)] cut_edges_fig(output_dir, prefix, normal_plans, reversible_plans) longest_boundary_fig(output_dir, prefix, normal_plans, reversible_plans) if has_geometry: demo_plans(output_dir, '_'.join([prefix, 'recom']), normal_plans, n_steps, n_steps // 25) demo_plans(output_dir, '_'.join([prefix, 'reversible_recom']), reversible_plans, n_steps, n_steps // 25) if election: election_hists(output_dir, 'dem_vote_share', 'election', 'Democratic', normal_plans, reversible_plans) acceptance_stats(output_dir, '_'.join([prefix, 'recom']), normal_plans) acceptance_stats(output_dir, '_'.join([prefix, 'reversible_recom']), reversible_plans)
county_col = "COUNTYFP10" pop_col = "TOT_POP" uid = "GEOID10" graph = Graph.from_geodataframe(df,ignore_errors=True) print("made graph") graph.add_data(df,list(df)) graph = nx.relabel_nodes(graph, df[uid]) counties = (set(list(df[county_col]))) countydict = dict(graph.nodes(data=county_col)) starting_partition = GeographicPartition( graph, assignment="GOV", updaters={ "polsby_popper" : polsby_popper, "cut_edges": cut_edges, "population": Tally(pop_col, alias="population"), } ) county_edge_count = {} for i in counties: county_graph = graph.subgraph([n for n,v in graph.nodes(data = True) if v[county_col] == i]) total_edges = len(county_graph.edges()) county_edge_count[i] = total_edges def county_splits_dict(partition): """ From a partition, generates a dictionary of counter dictionaries.