基于Artist和Song列合并两个Pandas DataFrame及拼写匹配问题
Hey there! Let's work through your two main problems: getting the merge to work correctly without duplicate columns, and handling spelling mismatches between your DataFrames.
1. Fixing the Basic Merge Problem
Your original merge code had a syntax error that caused those Artist_x/Artist_y columns. When you use on='Artist' and 'Song', Python evaluates that as just 'Song' (since non-empty strings are truthy, 'Artist' and 'Song' returns the second string). That means you were only merging on the Song column—so Pandas renames the duplicate Artist columns from each DataFrame to avoid confusion.
Here's the corrected approach to merge on both columns, get your desired column order, and fill mismatched values with 0:
import pandas as pd # Correct merge: use a list to specify multiple join keys merged = pd.merge(dfMap, dfBB, on=['Artist', 'Song'], how='outer') # Reorder columns to match your desired structure desired_cols = ['Artist', 'Song', 'SongId', 'ArtistId', 'Rank', 'Year'] merged = merged[desired_cols] # Fill any NaN values (from unmatched rows) with 0 merged = merged.fillna(0)
This will give you exactly the column structure you want, with 0s in Rank/Year for rows from dfMap that don't have a match in dfBB, and 0s in SongId/ArtistId for rows from dfBB that don't match dfMap.
2. Handling Spelling/Formatting Errors with Fuzzy Matching
For cases where artist or song names have minor spelling differences (like "Taylor Swift" vs "Tayler Swift" or "Hello" vs "Hello!"), you'll need fuzzy matching to find similar entries. We'll use rapidfuzz (a maintained, faster alternative to fuzzywuzzy) for this.
Step 1: Preprocess Text
First, standardize the text in both DataFrames to reduce false mismatches (e.g., lowercase, remove special characters, trim spaces):
from rapidfuzz import process, fuzz def clean_text(text): if pd.isna(text): return "" # Lowercase, trim spaces, remove common special chars return text.lower().strip().replace("'", "").replace("-", " ").replace(",", "") # Apply cleaning to both DataFrames dfBB['Artist_clean'] = dfBB['Artist'].apply(clean_text) dfBB['Song_clean'] = dfBB['Song'].apply(clean_text) dfMap['Artist_clean'] = dfMap['Artist'].apply(clean_text) dfMap['Song_clean'] = dfMap['Song'].apply(clean_text)
Step 2: Fuzzy Match & Merge
Next, we'll match each row in dfMap to the closest entry in dfBB based on the cleaned artist/song names, using a similarity threshold (adjust this based on how strict you want matches to be):
def get_best_match(row, target_df): # Create a combined key for matching (artist + song) target_keys = target_df['Artist_clean'] + " | " + target_df['Song_clean'] current_key = row['Artist_clean'] + " | " + row['Song_clean'] # Find the closest match (token_sort_ratio ignores word order, great for names like "The Beatles" vs "Beatles, The") match, score, _ = process.extractOne(current_key, target_keys, scorer=fuzz.token_sort_ratio) # Only accept matches with a similarity score >= 80 (adjust as needed) if score >= 80: matched_row = target_df[target_keys == match].iloc[0] return pd.Series([matched_row['Rank'], matched_row['Year']]) else: # No good match found, return 0s return pd.Series([0, 0]) # Add Rank and Year to dfMap via fuzzy matching dfMap[['Rank', 'Year']] = dfMap.apply(get_best_match, target_df=dfBB, axis=1) # Finalize the DataFrame with your desired columns final_df = dfMap[['Artist', 'Song', 'SongId', 'ArtistId', 'Rank', 'Year']]
Notes on Fuzzy Matching:
- Adjust the similarity threshold (80 in the example) based on your data: use a higher value (like 90) for stricter matches, or lower (like 70) if you have more variation.
- If you want to match on artist first then song (instead of combining them), you can modify the function to first find matching artists, then match songs within those artists—this can improve accuracy.
- For large datasets, this apply-based approach might be slow. You can optimize by using vectorized operations or libraries like
fuzzywuzzy-processfor bulk matching.
内容的提问来源于stack exchange,提问作者joe borg

