按特定约束合并多个pandas DataFrame的实现问询
I see you've tried basic merge/join/concat and manual approaches without hitting your exact requirements—let's build a solution that handles the custom value differentiation, row/column additions, and readability you need. We'll break this into two core parts: merging df1 and df2 with your value-splitting rule, then incorporating df3.
Step 1: Prep df1 and df2 for Merging
First, we need to align their ID column names since df1 uses ID and df2 uses PERSID—this ensures we can properly match rows:
import pandas as pd import numpy as np # Recreate your original DataFrames (fixed the typo in df2's index setup) df1 = pd.DataFrame({'ID': [20016, 50048, 13478, 68493, 57483], 'Sex': ['F', 'M', 'F', 'F', 'M'], 'Var1': [3, 3, 3, 3, 2], 'Var2': [2, 3, np.nan, 3, 2], 'Var3': [-0.25, 0, 4, np.nan, 0.14]}).set_index('ID').reset_index() df2 = pd.DataFrame({'PERSID': [20016, 50048, 13478, 68493, 57483, 45623], 'Sex': ['F', 'M', 'F', 'F', 'M', 'M'], 'Var1': [3, 1, 3, 3, 2, np.nan], 'Var2': [3, 3, np.nan, 3, 2, 0], 'Var3': [-0.25, 0, 4, np.nan, 0.14, 0.28]}).rename(columns={'PERSID': 'ID'}).reset_index(drop=True)
Step 2: Merge df1 and df2 with Value Differentiation
We'll use an outer merge to keep all rows, then process each column to split values into _x (from df1) and _y (from df2) only when they differ. We'll also keep a single column for values that match across both DataFrames:
# Outer merge on ID, add suffixes to distinguish df1/df2 columns merged = pd.merge(df1, df2, on='ID', how='outer', suffixes=('_x', '_y'), indicator=True) # Helper function to clean up columns: retain single column if values match, else keep _x/_y def process_column(base_col): col_x = f"{base_col}_x" col_y = f"{base_col}_y" # Compare values, treating NaNs as equal (since pandas sees NaN != NaN) values_match = merged[col_x].fillna('TEMP_NAN') == merged[col_y].fillna('TEMP_NAN') # Create a base column for matching values merged[base_col] = np.where(values_match, merged[col_x], pd.NA) # Drop the base column if all values are missing (meaning no matches) if merged[base_col].isna().all(): merged.drop(base_col, axis=1, inplace=True) else: # Clear _x/_y values where they match the base column merged.loc[values_match, [col_x, col_y]] = pd.NA # Apply the helper to all shared columns for col in ['Sex', 'Var1', 'Var2', 'Var3']: process_column(col) # Clean up the merge indicator and reorder columns to keep _x/_y pairs adjacent merged.drop('_merge', axis=1, inplace=True) # Build a readable column order: ID first, then grouped base/_x/_y columns column_order = ['ID'] for base_col in ['Sex', 'Var1', 'Var2', 'Var3']: if f"{base_col}_x" in merged.columns: column_order.append(f"{base_col}_x") if base_col in merged.columns: column_order.append(base_col) if f"{base_col}_y" in merged.columns: column_order.append(f"{base_col}_y") # Remove duplicate entries (in case base column was kept) column_order = list(dict.fromkeys(column_order)) merged = merged[column_order]
This will produce exactly the expected output you shared for df1 + df2, with matching values in a single column and differing values split into _x/_y pairs.
Step 3: Incorporate df3
Now we'll merge the combined df1/df2 result with df3. Since df3 has a different structure, we'll treat its SUBJECT column as the ID (adjust this if SUBJECT isn't meant to map to ID—use pd.concat([merged, df3], axis=1) if they're entirely unrelated):
# Recreate df3 df3 = pd.DataFrame({ 'SUBJECT': [200, 6700, 6702, '1330__201805', '4786__201805'], 'Var4': [1640.345, 14236.430, 1345.400, 345.750, np.nan], 'Var5': [345.0, 1713.0, np.nan, 335.0, 0.0], 'Var6': [-0.250000, -0.050735, 0.034450, 0.140000, np.nan] }).rename(columns={'SUBJECT': 'ID'}) # Full outer merge to preserve all rows and columns, fill gaps with NaN final_merged = pd.merge(merged, df3, on='ID', how='outer') # Reorder columns to keep the original grouped pairs first, then df3's columns final_columns = merged.columns.tolist() + [col for col in df3.columns if col != 'ID'] final_merged = final_merged[final_columns]
Key Details
- We use
fillna('TEMP_NAN')to correctly compare NaN values (since pandas doesn't recognizeNaN == NaNas True) - Columns are intentionally ordered to keep
_xand_ypairs next to each other for readability - The final merge with
df3adds all its rows and columns without losing any existing data, filling missing cells with NaN as required
内容的提问来源于stack exchange,提问作者mExitus

