[{"data":1,"prerenderedAt":852},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-two-excel-files-on-common-column-python":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-two-excel-files-on-common-column-python":844},{"id":4,"title":5,"body":6,"description":837,"extension":838,"meta":839,"navigation":70,"path":840,"seo":841,"stem":842,"__hash__":843},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-two-excel-files-on-common-column-python\u002Findex.md","Merge Two Excel Files on a Common Column in Python",{"type":7,"value":8,"toc":831},"minimark",[9,13,32,37,261,265,329,333,347,403,409,445,451,534,547,721,725,728,819,827],[10,11,5],"h1",{"id":12},"merge-two-excel-files-on-a-common-column-in-python",[14,15,16,17,21,22,25,26,31],"p",{},"To merge two Excel files on a shared column in Python, load both workbooks into pandas DataFrames with ",[18,19,20],"code",{},"pd.read_excel()",", then align them using ",[18,23,24],{},"pd.merge()",". This pattern handles dtype coercion automatically, preserves row alignment for left joins, and integrates cleanly into automated reporting pipelines. For broader strategies on ",[27,28,30],"a",{"href":29},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002F","Merging and Joining Excel DataFrames",", follow the production-ready implementation below.",[33,34,36],"h3",{"id":35},"core-implementation","Core Implementation",[38,39,44],"pre",{"className":40,"code":41,"language":42,"meta":43,"style":43},"language-python shiki shiki-themes github-light github-dark","import pandas as pd\n\n# 1. Load workbooks (explicit engine prevents silent fallbacks)\ndf_primary = pd.read_excel(\"sales_Q3.xlsx\", engine=\"openpyxl\")\ndf_lookup = pd.read_excel(\"product_catalog.xlsx\", engine=\"openpyxl\")\n\n# 2. Merge on shared key with collision-safe suffixes\nmerged = pd.merge(\n df_primary,\n df_lookup,\n on=\"product_sku\",\n how=\"left\",\n suffixes=(\"_sales\", \"_catalog\")\n)\n\n# 3. Export result\nmerged.to_excel(\"merged_sales_report.xlsx\", index=False, engine=\"openpyxl\")\n","python","",[18,45,46,65,72,79,110,133,138,144,155,161,167,181,194,215,220,225,231],{"__ignoreMap":43},[47,48,51,55,59,62],"span",{"class":49,"line":50},"line",1,[47,52,54],{"class":53},"szBVR","import",[47,56,58],{"class":57},"sVt8B"," pandas ",[47,60,61],{"class":53},"as",[47,63,64],{"class":57}," pd\n",[47,66,68],{"class":49,"line":67},2,[47,69,71],{"emptyLinePlaceholder":70},true,"\n",[47,73,75],{"class":49,"line":74},3,[47,76,78],{"class":77},"sJ8bj","# 1. Load workbooks (explicit engine prevents silent fallbacks)\n",[47,80,82,85,88,91,95,98,102,104,107],{"class":49,"line":81},4,[47,83,84],{"class":57},"df_primary ",[47,86,87],{"class":53},"=",[47,89,90],{"class":57}," pd.read_excel(",[47,92,94],{"class":93},"sZZnC","\"sales_Q3.xlsx\"",[47,96,97],{"class":57},", ",[47,99,101],{"class":100},"s4XuR","engine",[47,103,87],{"class":53},[47,105,106],{"class":93},"\"openpyxl\"",[47,108,109],{"class":57},")\n",[47,111,113,116,118,120,123,125,127,129,131],{"class":49,"line":112},5,[47,114,115],{"class":57},"df_lookup ",[47,117,87],{"class":53},[47,119,90],{"class":57},[47,121,122],{"class":93},"\"product_catalog.xlsx\"",[47,124,97],{"class":57},[47,126,101],{"class":100},[47,128,87],{"class":53},[47,130,106],{"class":93},[47,132,109],{"class":57},[47,134,136],{"class":49,"line":135},6,[47,137,71],{"emptyLinePlaceholder":70},[47,139,141],{"class":49,"line":140},7,[47,142,143],{"class":77},"# 2. Merge on shared key with collision-safe suffixes\n",[47,145,147,150,152],{"class":49,"line":146},8,[47,148,149],{"class":57},"merged ",[47,151,87],{"class":53},[47,153,154],{"class":57}," pd.merge(\n",[47,156,158],{"class":49,"line":157},9,[47,159,160],{"class":57}," df_primary,\n",[47,162,164],{"class":49,"line":163},10,[47,165,166],{"class":57}," df_lookup,\n",[47,168,170,173,175,178],{"class":49,"line":169},11,[47,171,172],{"class":100}," on",[47,174,87],{"class":53},[47,176,177],{"class":93},"\"product_sku\"",[47,179,180],{"class":57},",\n",[47,182,184,187,189,192],{"class":49,"line":183},12,[47,185,186],{"class":100}," how",[47,188,87],{"class":53},[47,190,191],{"class":93},"\"left\"",[47,193,180],{"class":57},[47,195,197,200,202,205,208,210,213],{"class":49,"line":196},13,[47,198,199],{"class":100}," suffixes",[47,201,87],{"class":53},[47,203,204],{"class":57},"(",[47,206,207],{"class":93},"\"_sales\"",[47,209,97],{"class":57},[47,211,212],{"class":93},"\"_catalog\"",[47,214,109],{"class":57},[47,216,218],{"class":49,"line":217},14,[47,219,109],{"class":57},[47,221,223],{"class":49,"line":222},15,[47,224,71],{"emptyLinePlaceholder":70},[47,226,228],{"class":49,"line":227},16,[47,229,230],{"class":77},"# 3. Export result\n",[47,232,234,237,240,242,245,247,251,253,255,257,259],{"class":49,"line":233},17,[47,235,236],{"class":57},"merged.to_excel(",[47,238,239],{"class":93},"\"merged_sales_report.xlsx\"",[47,241,97],{"class":57},[47,243,244],{"class":100},"index",[47,246,87],{"class":53},[47,248,250],{"class":249},"sj4cs","False",[47,252,97],{"class":57},[47,254,101],{"class":100},[47,256,87],{"class":53},[47,258,106],{"class":93},[47,260,109],{"class":57},[33,262,264],{"id":263},"critical-configuration-notes","Critical Configuration Notes",[266,267,268,288,309,319],"ul",{},[269,270,271,275,276,279,280,283,284,287],"li",{},[272,273,274],"strong",{},"Dependencies:"," Requires ",[18,277,278],{},"pandas>=1.5.0"," and ",[18,281,282],{},"openpyxl>=3.1.0",". Install via ",[18,285,286],{},"pip install pandas openpyxl",".",[269,289,290,293,294,297,298,301,302,305,306,287],{},[272,291,292],{},"Key Alignment:"," The ",[18,295,296],{},"on"," parameter is strictly case-sensitive. Mismatched dtypes (",[18,299,300],{},"object"," vs ",[18,303,304],{},"int64",") or trailing whitespace cause silent empty joins. Normalize keys first: ",[18,307,308],{},"df[\"product_sku\"] = df[\"product_sku\"].astype(str).str.strip()",[269,310,311,314,315,318],{},[272,312,313],{},"Memory Limits:"," ",[18,316,317],{},"read_excel()"," loads entire sheets into RAM. For files >500MB or 1M+ rows, convert to Parquet\u002FCSV first or switch to Polars.",[269,320,321,324,325,328],{},[272,322,323],{},"Python Version:"," Optimized for Python 3.8+. EOL versions lack modern ",[18,326,327],{},"pathlib"," integration and stable Excel I\u002FO.",[33,330,332],{"id":331},"targeted-fallbacks-for-edge-cases","Targeted Fallbacks for Edge Cases",[14,334,335,338,339,342,343,346],{},[272,336,337],{},"Different Column Names","\nUse ",[18,340,341],{},"left_on","\u002F",[18,344,345],{},"right_on"," and drop the duplicate post-join:",[38,348,350],{"className":40,"code":349,"language":42,"meta":43,"style":43},"merged = pd.merge(df_primary, df_lookup, left_on=\"sku_id\", right_on=\"ProductCode\", how=\"inner\").drop(columns=[\"ProductCode\"])\n",[18,351,352],{"__ignoreMap":43},[47,353,354,356,358,361,363,365,368,370,372,374,377,379,382,384,387,390,393,395,398,400],{"class":49,"line":50},[47,355,149],{"class":57},[47,357,87],{"class":53},[47,359,360],{"class":57}," pd.merge(df_primary, df_lookup, ",[47,362,341],{"class":100},[47,364,87],{"class":53},[47,366,367],{"class":93},"\"sku_id\"",[47,369,97],{"class":57},[47,371,345],{"class":100},[47,373,87],{"class":53},[47,375,376],{"class":93},"\"ProductCode\"",[47,378,97],{"class":57},[47,380,381],{"class":100},"how",[47,383,87],{"class":53},[47,385,386],{"class":93},"\"inner\"",[47,388,389],{"class":57},").drop(",[47,391,392],{"class":100},"columns",[47,394,87],{"class":53},[47,396,397],{"class":57},"[",[47,399,376],{"class":93},[47,401,402],{"class":57},"])\n",[14,404,405,408],{},[272,406,407],{},"Duplicate Keys Causing Row Multiplication","\nDeduplicate the lookup table before merging to prevent Cartesian expansion:",[38,410,412],{"className":40,"code":411,"language":42,"meta":43,"style":43},"df_lookup = df_lookup.drop_duplicates(subset=[\"product_sku\"], keep=\"last\")\n",[18,413,414],{"__ignoreMap":43},[47,415,416,418,420,423,426,428,430,432,435,438,440,443],{"class":49,"line":50},[47,417,115],{"class":57},[47,419,87],{"class":53},[47,421,422],{"class":57}," df_lookup.drop_duplicates(",[47,424,425],{"class":100},"subset",[47,427,87],{"class":53},[47,429,397],{"class":57},[47,431,177],{"class":93},[47,433,434],{"class":57},"], ",[47,436,437],{"class":100},"keep",[47,439,87],{"class":53},[47,441,442],{"class":93},"\"last\"",[47,444,109],{"class":57},[14,446,447,450],{},[272,448,449],{},"Large Dataset Memory Overflow","\nSwitch to Polars for lazy evaluation and ~50% lower RAM footprint:",[38,452,454],{"className":40,"code":453,"language":42,"meta":43,"style":43},"import polars as pl\n# Requires: pip install polars xlsx2csv\ndf1 = pl.read_excel(\"sales_Q3.xlsx\")\ndf2 = pl.read_excel(\"product_catalog.xlsx\")\nmerged = df1.join(df2, on=\"product_sku\", how=\"left\")\nmerged.write_excel(\"merged_sales_report.xlsx\")\n",[18,455,456,468,473,487,500,525],{"__ignoreMap":43},[47,457,458,460,463,465],{"class":49,"line":50},[47,459,54],{"class":53},[47,461,462],{"class":57}," polars ",[47,464,61],{"class":53},[47,466,467],{"class":57}," pl\n",[47,469,470],{"class":49,"line":67},[47,471,472],{"class":77},"# Requires: pip install polars xlsx2csv\n",[47,474,475,478,480,483,485],{"class":49,"line":74},[47,476,477],{"class":57},"df1 ",[47,479,87],{"class":53},[47,481,482],{"class":57}," pl.read_excel(",[47,484,94],{"class":93},[47,486,109],{"class":57},[47,488,489,492,494,496,498],{"class":49,"line":81},[47,490,491],{"class":57},"df2 ",[47,493,87],{"class":53},[47,495,482],{"class":57},[47,497,122],{"class":93},[47,499,109],{"class":57},[47,501,502,504,506,509,511,513,515,517,519,521,523],{"class":49,"line":112},[47,503,149],{"class":57},[47,505,87],{"class":53},[47,507,508],{"class":57}," df1.join(df2, ",[47,510,296],{"class":100},[47,512,87],{"class":53},[47,514,177],{"class":93},[47,516,97],{"class":57},[47,518,381],{"class":100},[47,520,87],{"class":53},[47,522,191],{"class":93},[47,524,109],{"class":57},[47,526,527,530,532],{"class":49,"line":135},[47,528,529],{"class":57},"merged.write_excel(",[47,531,239],{"class":93},[47,533,109],{"class":57},[14,535,536,539,540,279,543,546],{},[272,537,538],{},"Air-Gapped\u002FRestricted Environments","\nBypass third-party libraries using Python’s built-in ",[18,541,542],{},"sqlite3",[18,544,545],{},"csv"," modules:",[38,548,550],{"className":40,"code":549,"language":42,"meta":43,"style":43},"import csv, sqlite3\n\nconn = sqlite3.connect(\":memory:\")\nconn.execute(\"CREATE TABLE t1 (sku TEXT, qty INT)\")\nconn.execute(\"CREATE TABLE t2 (sku TEXT, price REAL)\")\n\nwith open(\"sales.csv\") as f:\n conn.executemany(\"INSERT INTO t1 VALUES (?, ?)\", csv.reader(f))\nwith open(\"catalog.csv\") as f:\n conn.executemany(\"INSERT INTO t2 VALUES (?, ?)\", csv.reader(f))\n\ncursor = conn.execute(\"SELECT t1.*, t2.price FROM t1 LEFT JOIN t2 ON t1.sku = t2.sku\")\nwith open(\"merged_output.csv\", \"w\", newline=\"\") as out:\n csv.writer(out).writerows(cursor.fetchall())\nconn.close()\n",[18,551,552,559,563,578,588,597,601,622,633,650,659,663,678,711,716],{"__ignoreMap":43},[47,553,554,556],{"class":49,"line":50},[47,555,54],{"class":53},[47,557,558],{"class":57}," csv, sqlite3\n",[47,560,561],{"class":49,"line":67},[47,562,71],{"emptyLinePlaceholder":70},[47,564,565,568,570,573,576],{"class":49,"line":74},[47,566,567],{"class":57},"conn ",[47,569,87],{"class":53},[47,571,572],{"class":57}," sqlite3.connect(",[47,574,575],{"class":93},"\":memory:\"",[47,577,109],{"class":57},[47,579,580,583,586],{"class":49,"line":81},[47,581,582],{"class":57},"conn.execute(",[47,584,585],{"class":93},"\"CREATE TABLE t1 (sku TEXT, qty INT)\"",[47,587,109],{"class":57},[47,589,590,592,595],{"class":49,"line":112},[47,591,582],{"class":57},[47,593,594],{"class":93},"\"CREATE TABLE t2 (sku TEXT, price REAL)\"",[47,596,109],{"class":57},[47,598,599],{"class":49,"line":135},[47,600,71],{"emptyLinePlaceholder":70},[47,602,603,606,609,611,614,617,619],{"class":49,"line":140},[47,604,605],{"class":53},"with",[47,607,608],{"class":249}," open",[47,610,204],{"class":57},[47,612,613],{"class":93},"\"sales.csv\"",[47,615,616],{"class":57},") ",[47,618,61],{"class":53},[47,620,621],{"class":57}," f:\n",[47,623,624,627,630],{"class":49,"line":146},[47,625,626],{"class":57}," conn.executemany(",[47,628,629],{"class":93},"\"INSERT INTO t1 VALUES (?, ?)\"",[47,631,632],{"class":57},", csv.reader(f))\n",[47,634,635,637,639,641,644,646,648],{"class":49,"line":157},[47,636,605],{"class":53},[47,638,608],{"class":249},[47,640,204],{"class":57},[47,642,643],{"class":93},"\"catalog.csv\"",[47,645,616],{"class":57},[47,647,61],{"class":53},[47,649,621],{"class":57},[47,651,652,654,657],{"class":49,"line":163},[47,653,626],{"class":57},[47,655,656],{"class":93},"\"INSERT INTO t2 VALUES (?, ?)\"",[47,658,632],{"class":57},[47,660,661],{"class":49,"line":169},[47,662,71],{"emptyLinePlaceholder":70},[47,664,665,668,670,673,676],{"class":49,"line":183},[47,666,667],{"class":57},"cursor ",[47,669,87],{"class":53},[47,671,672],{"class":57}," conn.execute(",[47,674,675],{"class":93},"\"SELECT t1.*, t2.price FROM t1 LEFT JOIN t2 ON t1.sku = t2.sku\"",[47,677,109],{"class":57},[47,679,680,682,684,686,689,691,694,696,699,701,704,706,708],{"class":49,"line":196},[47,681,605],{"class":53},[47,683,608],{"class":249},[47,685,204],{"class":57},[47,687,688],{"class":93},"\"merged_output.csv\"",[47,690,97],{"class":57},[47,692,693],{"class":93},"\"w\"",[47,695,97],{"class":57},[47,697,698],{"class":100},"newline",[47,700,87],{"class":53},[47,702,703],{"class":93},"\"\"",[47,705,616],{"class":57},[47,707,61],{"class":53},[47,709,710],{"class":57}," out:\n",[47,712,713],{"class":49,"line":217},[47,714,715],{"class":57}," csv.writer(out).writerows(cursor.fetchall())\n",[47,717,718],{"class":49,"line":222},[47,719,720],{"class":57},"conn.close()\n",[33,722,724],{"id":723},"validation-pipeline-integration","Validation & Pipeline Integration",[14,726,727],{},"Embed deterministic checks to catch upstream data drift before reports deploy. Wrap execution in error handling that logs row counts and missing keys:",[38,729,731],{"className":40,"code":730,"language":42,"meta":43,"style":43},"# Validate join integrity (adjust threshold based on `how` parameter)\nassert len(merged) >= len(df_primary), \"Unexpected row loss during merge\"\nmissing_keys = merged[\"product_sku\"].isna().sum()\nif missing_keys > 0:\n print(f\"Warning: {missing_keys} unmatched keys detected\")\n",[18,732,733,738,760,775,792],{"__ignoreMap":43},[47,734,735],{"class":49,"line":50},[47,736,737],{"class":77},"# Validate join integrity (adjust threshold based on `how` parameter)\n",[47,739,740,743,746,749,752,754,757],{"class":49,"line":67},[47,741,742],{"class":53},"assert",[47,744,745],{"class":249}," len",[47,747,748],{"class":57},"(merged) ",[47,750,751],{"class":53},">=",[47,753,745],{"class":249},[47,755,756],{"class":57},"(df_primary), ",[47,758,759],{"class":93},"\"Unexpected row loss during merge\"\n",[47,761,762,765,767,770,772],{"class":49,"line":74},[47,763,764],{"class":57},"missing_keys ",[47,766,87],{"class":53},[47,768,769],{"class":57}," merged[",[47,771,177],{"class":93},[47,773,774],{"class":57},"].isna().sum()\n",[47,776,777,780,783,786,789],{"class":49,"line":81},[47,778,779],{"class":53},"if",[47,781,782],{"class":57}," missing_keys ",[47,784,785],{"class":53},">",[47,787,788],{"class":249}," 0",[47,790,791],{"class":57},":\n",[47,793,794,797,799,802,805,808,811,814,817],{"class":49,"line":112},[47,795,796],{"class":249}," print",[47,798,204],{"class":57},[47,800,801],{"class":53},"f",[47,803,804],{"class":93},"\"Warning: ",[47,806,807],{"class":249},"{",[47,809,810],{"class":57},"missing_keys",[47,812,813],{"class":249},"}",[47,815,816],{"class":93}," unmatched keys detected\"",[47,818,109],{"class":57},[14,820,821,822,826],{},"Parameterize file paths, enforce strict schema contracts, and log merge statistics. This workflow slots directly into larger ",[27,823,825],{"href":824},"\u002Fadvanced-data-transformation-and-cleaning\u002F","Advanced Data Transformation and Cleaning"," pipelines where audit trails and idempotent execution are mandatory.",[828,829,830],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":43,"searchDepth":67,"depth":67,"links":832},[833,834,835,836],{"id":35,"depth":74,"text":36},{"id":263,"depth":74,"text":264},{"id":331,"depth":74,"text":332},{"id":723,"depth":74,"text":724},"To merge two Excel files on a shared column in Python, load both workbooks into pandas DataFrames with pd.read_excel(), then align them using pd.merge(). This pattern handles dtype coercion automatically, preserves row alignment for left joins, and integrates cleanly into automated reporting pipelines. For broader strategies on Merging and Joining Excel DataFrames, follow the production-ready implementation below.","md",{},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-two-excel-files-on-common-column-python",{"title":5,"description":837},"advanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-two-excel-files-on-common-column-python\u002Findex","X8_Eei3DUIDqY-NjQMfkPzJ1Aiu3RO6w4dkt3wrK-Yc",[845,848],{"title":30,"path":846,"stem":847,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes","advanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Findex",{"title":849,"path":850,"stem":851,"children":-1},"Automating Reporting Workflows: A Production-Ready Guide for Python Developers","\u002Fautomating-reporting-workflows","automating-reporting-workflows\u002Findex",1777830514975]