优化Pandas DataFrame关联映射代码运行性能的技术咨询
Got it, let's fix that slow loop right away—your current approach is dragging because it's doing repeated, expensive DataFrame operations (like where, dropna, and row-wise lookups) for every single row in your 8500-row dataset. Those small overheads add up to a minute of runtime easily. Here's how to cut that down to seconds:
Step 1: Standardize Dirty Data First
Since both DataFrames have messy name values, we need a consistent cleaning function to normalize them. This ensures that minor differences (like extra spaces, capitalization, or camel case) don't break our mappings.
import pandas as pd def clean_name(raw_name): # Handle empty/NaN values first if pd.isna(raw_name) or str(raw_name).strip() == "": return "" # Normalize: strip whitespace, lowercase, collapse multiple spaces cleaned = str(raw_name).strip().lower() cleaned = ' '.join(cleaned.split()) # Replace multiple spaces with single space # Add extra rules here if needed (e.g., remove special characters, fix common typos) return cleaned
Step 2: Build a Fast Lookup Mapping
Instead of searching the first DataFrame every time, create a dictionary that maps normalized (email, cleaned_name) pairs to their corresponding ID. Dictionary lookups are O(1)—instant compared to row-wise DataFrame queries.
# Clean and prep the first DataFrame for mapping first_df['cleaned_name'] = first_df['name'].apply(clean_name) first_df['email'] = first_df['email'].str.lower() # Normalize emails too # Drop duplicates to avoid ambiguous mappings (adjust 'keep' as per your business rules) first_df_deduped = first_df.drop_duplicates(subset=['email', 'cleaned_name'], keep='first') # Create the lookup dictionary id_mapping = first_df_deduped.set_index(['email', 'cleaned_name'])['ID'].to_dict()
Step 3: Vectorize the Mapping Process
Now apply the same cleaning to the second DataFrame, then use the dictionary to map values in bulk. We'll use apply here (still row-wise, but way faster than your original loop) or even better, use Pandas' built-in merge for full vectorization.
Option 1: Dictionary Mapping (Simple & Fast)
# Clean the second DataFrame's columns second_df['cleaned_Name'] = second_df['Name'].apply(clean_name) second_df['Email'] = second_df['Email'].str.lower() second_df['cleaned_To_Name'] = second_df['To_Name'].apply(clean_name) second_df['To'] = second_df['To'].str.lower() # Map From and To IDs in one go second_df['From'] = second_df.apply( lambda row: id_mapping.get((row['Email'], row['cleaned_Name'])), axis=1 ) second_df['To'] = second_df.apply( lambda row: id_mapping.get((row['To'], row['cleaned_To_Name'])), axis=1 ) # Build your final output DataFrame output_df = second_df[['Id', 'From', 'To']].rename(columns={'Id': 'ID'})
Option 2: Pandas Merge (Even Faster for Large Datasets)
For truly massive datasets, Pandas' merge operations (implemented in C) are faster than Python-level apply. Here's how to use it:
# Prep the first DataFrame for merging first_merge = first_df_deduped[['email', 'cleaned_name', 'ID']].rename(columns={'ID': 'From'}) # Clean the second DataFrame and merge to get 'From' IDs second_df_clean = second_df.copy() second_df_clean['cleaned_Name'] = second_df_clean['Name'].apply(clean_name) second_df_clean['Email'] = second_df_clean['Email'].str.lower() merged_from = pd.merge( second_df_clean, first_merge, left_on=['Email', 'cleaned_Name'], right_on=['email', 'cleaned_name'], how='left' ) # Merge again to get 'To' IDs first_merge_to = first_df_deduped[['email', 'cleaned_name', 'ID']].rename(columns={'ID': 'To'}) merged_from['cleaned_To_Name'] = merged_from['To_Name'].apply(clean_name) merged_from['To'] = merged_from['To'].str.lower() final_merged = pd.merge( merged_from, first_merge_to, left_on=['To', 'cleaned_To_Name'], right_on=['email', 'cleaned_name'], how='left' ) # Final output output_df = final_merged[['Id', 'From', 'To']].rename(columns={'Id': 'ID'})
Key Notes
- Handle Missing Mappings: Use
fillna()to replaceNaNvalues inFrom/Towith a default (like 0 or -1) if needed, depending on your use case. - Duplicate Handling: The
drop_duplicatesstep ensures each (email, cleaned_name) pair maps to exactly one ID—adjustkeep='last'or add logic to resolve duplicates if your data requires it. - Speed Gain: Both methods should reduce runtime from ~1 minute to a few seconds (or less, depending on your hardware). Merge will be fastest for datasets larger than 10k rows.
内容的提问来源于stack exchange,提问作者Marcus Willems

