A fraud analyst may need to turn thousands of transactions into a graph before investigating shared devices, IP addresses, or payment cards. Parallelism lets independent pieces of that preparation run at the same time, reducing the time spent waiting for a usable graph. It does not replace the later step of combining their results. section 9.3.2
Two graph-analysis jobs show where the work can be split:
- For each transaction, prepare relationships among its users, IP addresses, devices, phone numbers, and cards. Process transaction rows concurrently, then aggregate the prepared relationships into a NetworkX graph. An edge’s weight counts how many transactions linked its entities. section 9.3.2
- After Louvain community detection, analyze communities concurrently. A ThreadPoolExecutor submits each community’s node list to a function that calculates size, density, and counts by node type; the collected results become a pandas table. section 9.3.3
Check the graph before interpreting it
One run reports 14,813 transactions processed for edge preparation, followed by 14,906 nodes and 30,567 edges. These progress and final-count checks establish what was built before analysts inspect dense or unusual communities; they do not, by themselves, establish that a cluster is fraudulent. section 9.3.2 section 9.3.3
Listing 9.5 Parallel analysis of communities and displaying top results
community_analysis_data = [] # To store results from all communities
if 'all_community_node_lists_sorted' in locals() and all_community_node_lists_sorted:
print(f"\nAnalyzing {len(all_community_node_lists_sorted)} communities in parallel (max_workers=8)...")
with ThreadPoolExecutor(max_workers=8) as executor:
future_to_original_index = {
executor.submit(analyze_community_details_func, nodes_list): i
for i, nodes_list in enumerate(all_community_node_lists_sorted)
}
processed_count = 0
sorted_community_ids = list(sorted(communities_dict_nodes.keys()))
for future_item in as_completed(future_to_original_index):
original_list_index = future_to_original_index[future_item]
try:
density_val, size_val, type_counts_dict = future_item.result()
actual_community_id_for_row = list(sorted(communities_dict_
nodes.keys()))[original_list_index]
analysis_row_dict = {'Community ID': actual_community_id_for_row,
'Size': size_val,
'Density': density_val}
analysis_row_dict.update(type_counts_dict)
community_analysis_data.append(analysis_row_dict)
processed_count +=1
if processed_count % 500 == 0: # Progress update
print(f"Analyzed {processed_count} communities...")
except Exception as ex:
print(f"Error analyzing community (original list index {original_list_index}): {ex}")
if community_analysis_data:
community_details_df = pd.DataFrame(community_analysis_data)
# Ensure all node type columns exist, filling with 0 if a type is not in a community
for node_type_key in node_types_and_columns.keys():
if node_type_key not in community_details_df.columns:
community_details_df[node_type_key] = 0
community_details_df[node_type_key] = community_details_df[node_type_key].fillna(0).astype(int)
community_details_df_sorted = community_details_df.sort_values(by='Size', ascending=False)
print("\nTop 5 largest communities with their characteristics:")
# Define the desired order of columns for display
display_cols_ordered = ['Community ID', 'Size', 'Density'] + list(node_types_and_columns.keys())
# Filter for columns that actually exist in the DataFrame to prevent errors
existing_display_cols_ordered = [col for col in display_cols_ordered if col in community_details_df_sorted.columns]
display(community_details_df_sorted.head()[existing_display_cols_
ordered])
else:
print("No community analysis results to display.")
else:
print("Node lists for communities not available. Skipping detailed community analysis.")
community_details_df_sorted = pd.DataFrame() # Define for subsequent cells not to fail
The listing shows community jobs submitted with ThreadPoolExecutor(max_workers=8), then collected as they complete while retaining their original indices. section 9.3.3
The same pattern applies at pipeline scale, but the unit of work changes. Quantitative research can distribute work by date range, asset universe, or feature group; orchestration must still manage dependencies, retries, and observable outputs. In a nightly customer-data pipeline, Airflow can run clickstream and ledger extraction simultaneously, but the join must wait for both streams. The batch runs after ledger reconciliation so the resulting user-level data uses finalized financial records. Production use also brings data-quality and privacy controls beyond simply adding workers. section 10.3.1 section 13.1.2




































