[{"data":1,"prerenderedAt":2109},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freorder-columns-to-match-a-template-layout-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freorder-columns-to-match-a-template-layout-with-pandas":2075,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freorder-columns-to-match-a-template-layout-with-pandas":2084,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freorder-columns-to-match-a-template-layout-with-pandas":2089,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freorder-columns-to-match-a-template-layout-with-pandas":2090},{"id":4,"title":5,"body":6,"dateModified":2040,"datePublished":2040,"description":2041,"extension":2042,"faq":2043,"keyword":2052,"kind":2053,"meta":2054,"navigation":136,"path":2064,"seo":2065,"slug":2068,"stem":2069,"topics":2070,"type":2073,"__hash__":2074},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freorder-columns-to-match-a-template-layout-with-pandas\u002Findex.md","Reorder Columns to Match a Template Layout with pandas",{"type":7,"value":8,"toc":2027},"minimark",[9,24,29,57,87,91,94,265,272,276,279,456,478,482,492,717,730,899,902,1015,1021,1025,1028,1086,1093,1097,1104,1374,1385,1515,1519,1522,1892,1902,1906,1944,1948,1955,1959,1972,1978,1984,1990,1994,2023],[10,11,12,13,17,18,23],"p",{},"An import template for a finance system expects exactly twelve columns in exactly this order. The monthly files that feed it come from four branches whose sheets have the same information — mostly — in four different orders, with one branch calling the column \"Cust No\" instead of \"Customer ID\" and another adding a \"Notes\" column nobody asked for. Pasting them into the template by hand is how columns get shifted by one and a month of invoices gets posted against the wrong accounts. pandas can enforce the layout: read the expected columns from the template, rename the variants, reorder with ",[14,15,16],"code",{},"reindex",", and decide explicitly what to do about anything missing or extra. This page belongs to ",[19,20,22],"a",{"href":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002F","Cleaning Excel Data with pandas",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,37,38],{"__ignoreMap":35},[39,40,43,47,51,54],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sMTad","pip",[39,48,50],{"class":49},"srMev"," install",[39,52,53],{"class":49}," pandas",[39,55,56],{"class":49}," openpyxl\n",[10,58,59,62,63,66,67,70,71,74,75,78,79,82,83,86],{},[14,60,61],{},"import_template.xlsx"," is the target: a title in row 1 and a formatted header in row 3 with eight columns. ",[14,64,65],{},"branch_north.xlsx"," has the same data in a different order, a lower-case ",[14,68,69],{},"invoice date",", ",[14,72,73],{},"Cust No"," instead of ",[14,76,77],{},"Customer ID",", no ",[14,80,81],{},"Currency"," column and an extra ",[14,84,85],{},"Notes"," column.",[25,88,90],{"id":89},"step-1-read-the-required-layout-from-the-template","Step 1 — Read the required layout from the template",[10,92,93],{},"Do not copy the column list into the script — read it from the template, so the code follows the template when it changes:",[30,95,99],{"className":96,"code":97,"language":98,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom openpyxl import load_workbook\n\ntpl_ws = load_workbook(\"import_template.xlsx\")[\"Import\"]\nHEADER_ROW = 3\ntemplate_cols = [c.value for c in tpl_ws[HEADER_ROW] if c.value is not None]\nprint(template_cols)\n\nbranch = pd.read_excel(\"branch_north.xlsx\")\nprint(list(branch.columns))\n","python",[14,100,101,117,131,138,162,175,220,229,234,251],{"__ignoreMap":35},[39,102,103,107,111,114],{"class":41,"line":42},[39,104,106],{"class":105},"s-kum","import",[39,108,110],{"class":109},"skGVy"," pandas ",[39,112,113],{"class":105},"as",[39,115,116],{"class":109}," pd\n",[39,118,120,123,126,128],{"class":41,"line":119},2,[39,121,122],{"class":105},"from",[39,124,125],{"class":109}," openpyxl ",[39,127,106],{"class":105},[39,129,130],{"class":109}," load_workbook\n",[39,132,134],{"class":41,"line":133},3,[39,135,137],{"emptyLinePlaceholder":136},true,"\n",[39,139,141,144,147,150,153,156,159],{"class":41,"line":140},4,[39,142,143],{"class":109},"tpl_ws ",[39,145,146],{"class":105},"=",[39,148,149],{"class":109}," load_workbook(",[39,151,152],{"class":49},"\"import_template.xlsx\"",[39,154,155],{"class":109},")[",[39,157,158],{"class":49},"\"Import\"",[39,160,161],{"class":109},"]\n",[39,163,165,169,172],{"class":41,"line":164},5,[39,166,168],{"class":167},"sP0c6","HEADER_ROW",[39,170,171],{"class":105}," =",[39,173,174],{"class":167}," 3\n",[39,176,178,181,183,186,189,192,195,198,200,203,206,209,212,215,218],{"class":41,"line":177},6,[39,179,180],{"class":109},"template_cols ",[39,182,146],{"class":105},[39,184,185],{"class":109}," [c.value ",[39,187,188],{"class":105},"for",[39,190,191],{"class":109}," c ",[39,193,194],{"class":105},"in",[39,196,197],{"class":109}," tpl_ws[",[39,199,168],{"class":167},[39,201,202],{"class":109},"] ",[39,204,205],{"class":105},"if",[39,207,208],{"class":109}," c.value ",[39,210,211],{"class":105},"is",[39,213,214],{"class":105}," not",[39,216,217],{"class":167}," None",[39,219,161],{"class":109},[39,221,223,226],{"class":41,"line":222},7,[39,224,225],{"class":167},"print",[39,227,228],{"class":109},"(template_cols)\n",[39,230,232],{"class":41,"line":231},8,[39,233,137],{"emptyLinePlaceholder":136},[39,235,237,240,242,245,248],{"class":41,"line":236},9,[39,238,239],{"class":109},"branch ",[39,241,146],{"class":105},[39,243,244],{"class":109}," pd.read_excel(",[39,246,247],{"class":49},"\"branch_north.xlsx\"",[39,249,250],{"class":109},")\n",[39,252,254,256,259,262],{"class":41,"line":253},10,[39,255,225],{"class":167},[39,257,258],{"class":109},"(",[39,260,261],{"class":167},"list",[39,263,264],{"class":109},"(branch.columns))\n",[10,266,267,268,271],{},"The template wants ",[14,269,270],{},"Invoice No, Invoice Date, Customer ID, …, Currency","; the branch file has its own order and names. A single source of truth for the layout matters more than it seems: when finance adds a column to the template next quarter, the script picks it up — and the missing-column check below tells you which branch files need updating.",[25,273,275],{"id":274},"step-2-map-header-variants-to-the-template-names","Step 2 — Map header variants to the template names",[10,277,278],{},"Normalise case and spacing first, then map known aliases:",[30,280,282],{"className":96,"code":281,"language":98,"meta":35,"style":35},"ALIASES = {\"cust no\": \"Customer ID\", \"customer no\": \"Customer ID\", \"inv no\": \"Invoice No\",\n           \"date\": \"Invoice Date\", \"ccy\": \"Currency\"}\ncanon = {c.lower(): c for c in template_cols}\n\ndef to_template_name(col):\n    key = \" \".join(str(col).split()).lower()\n    return canon.get(key) or ALIASES.get(key) or col\n\nbranch = branch.rename(columns=to_template_name)\nprint(list(branch.columns))\n",[14,283,284,325,348,367,371,383,402,424,428,446],{"__ignoreMap":35},[39,285,286,289,291,294,297,300,303,305,308,310,312,314,317,319,322],{"class":41,"line":42},[39,287,288],{"class":167},"ALIASES",[39,290,171],{"class":105},[39,292,293],{"class":109}," {",[39,295,296],{"class":49},"\"cust no\"",[39,298,299],{"class":109},": ",[39,301,302],{"class":49},"\"Customer ID\"",[39,304,70],{"class":109},[39,306,307],{"class":49},"\"customer no\"",[39,309,299],{"class":109},[39,311,302],{"class":49},[39,313,70],{"class":109},[39,315,316],{"class":49},"\"inv no\"",[39,318,299],{"class":109},[39,320,321],{"class":49},"\"Invoice No\"",[39,323,324],{"class":109},",\n",[39,326,327,330,332,335,337,340,342,345],{"class":41,"line":119},[39,328,329],{"class":49},"           \"date\"",[39,331,299],{"class":109},[39,333,334],{"class":49},"\"Invoice Date\"",[39,336,70],{"class":109},[39,338,339],{"class":49},"\"ccy\"",[39,341,299],{"class":109},[39,343,344],{"class":49},"\"Currency\"",[39,346,347],{"class":109},"}\n",[39,349,350,353,355,358,360,362,364],{"class":41,"line":133},[39,351,352],{"class":109},"canon ",[39,354,146],{"class":105},[39,356,357],{"class":109}," {c.lower(): c ",[39,359,188],{"class":105},[39,361,191],{"class":109},[39,363,194],{"class":105},[39,365,366],{"class":109}," template_cols}\n",[39,368,369],{"class":41,"line":140},[39,370,137],{"emptyLinePlaceholder":136},[39,372,373,376,380],{"class":41,"line":164},[39,374,375],{"class":105},"def",[39,377,379],{"class":378},"s_Opv"," to_template_name",[39,381,382],{"class":109},"(col):\n",[39,384,385,388,390,393,396,399],{"class":41,"line":177},[39,386,387],{"class":109},"    key ",[39,389,146],{"class":105},[39,391,392],{"class":49}," \" \"",[39,394,395],{"class":109},".join(",[39,397,398],{"class":167},"str",[39,400,401],{"class":109},"(col).split()).lower()\n",[39,403,404,407,410,413,416,419,421],{"class":41,"line":222},[39,405,406],{"class":105},"    return",[39,408,409],{"class":109}," canon.get(key) ",[39,411,412],{"class":105},"or",[39,414,415],{"class":167}," ALIASES",[39,417,418],{"class":109},".get(key) ",[39,420,412],{"class":105},[39,422,423],{"class":109}," col\n",[39,425,426],{"class":41,"line":231},[39,427,137],{"emptyLinePlaceholder":136},[39,429,430,432,434,437,441,443],{"class":41,"line":236},[39,431,239],{"class":109},[39,433,146],{"class":105},[39,435,436],{"class":109}," branch.rename(",[39,438,440],{"class":439},"sa561","columns",[39,442,146],{"class":105},[39,444,445],{"class":109},"to_template_name)\n",[39,447,448,450,452,454],{"class":41,"line":253},[39,449,225],{"class":167},[39,451,258],{"class":109},[39,453,261],{"class":167},[39,455,264],{"class":109},[10,457,458,460,461,464,465,467,468,470,471,473,474,23],{},[14,459,69],{}," matches ",[14,462,463],{},"Invoice Date"," by case-insensitive comparison; ",[14,466,73],{}," becomes ",[14,469,77],{}," through the alias table; ",[14,472,85],{}," is left as it is, because it has no counterpart. Keeping aliases in one dictionary turns each new branch spelling into a one-line change. The same normalisation idea is covered for all columns in ",[19,475,477],{"href":476},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-excel-column-names-with-pandas\u002F","Standardise Excel Column Names with pandas",[25,479,481],{"id":480},"step-3-reorder-with-reindex-and-decide-about-missing-columns","Step 3 — Reorder with reindex, and decide about missing columns",[10,483,484,487,488,491],{},[14,485,486],{},"reindex(columns=...)"," returns the columns in the given order. Columns that do not exist are created and filled with ",[14,489,490],{},"NaN"," — convenient, and dangerous if a required column is silently empty. Check first:",[30,493,495],{"className":96,"code":494,"language":98,"meta":35,"style":35},"missing = [c for c in template_cols if c not in branch.columns]\nextra = [c for c in branch.columns if c not in template_cols]\nprint(\"missing:\", missing, \"| extra:\", extra)\n\nDEFAULTS = {\"Currency\": \"EUR\"}\nunfixable = [c for c in missing if c not in DEFAULTS]\nif unfixable:\n    raise ValueError(f\"branch_north.xlsx lacks required columns: {unfixable}\")\naligned = branch.reindex(columns=template_cols)\nfor col, value in DEFAULTS.items():\n    aligned[col] = aligned[col].fillna(value)\nprint(aligned)\n",[14,496,497,529,558,576,580,598,629,636,667,684,698,709],{"__ignoreMap":35},[39,498,499,502,504,507,509,511,513,516,518,520,523,526],{"class":41,"line":42},[39,500,501],{"class":109},"missing ",[39,503,146],{"class":105},[39,505,506],{"class":109}," [c ",[39,508,188],{"class":105},[39,510,191],{"class":109},[39,512,194],{"class":105},[39,514,515],{"class":109}," template_cols ",[39,517,205],{"class":105},[39,519,191],{"class":109},[39,521,522],{"class":105},"not",[39,524,525],{"class":105}," in",[39,527,528],{"class":109}," branch.columns]\n",[39,530,531,534,536,538,540,542,544,547,549,551,553,555],{"class":41,"line":119},[39,532,533],{"class":109},"extra ",[39,535,146],{"class":105},[39,537,506],{"class":109},[39,539,188],{"class":105},[39,541,191],{"class":109},[39,543,194],{"class":105},[39,545,546],{"class":109}," branch.columns ",[39,548,205],{"class":105},[39,550,191],{"class":109},[39,552,522],{"class":105},[39,554,525],{"class":105},[39,556,557],{"class":109}," template_cols]\n",[39,559,560,562,564,567,570,573],{"class":41,"line":133},[39,561,225],{"class":167},[39,563,258],{"class":109},[39,565,566],{"class":49},"\"missing:\"",[39,568,569],{"class":109},", missing, ",[39,571,572],{"class":49},"\"| extra:\"",[39,574,575],{"class":109},", extra)\n",[39,577,578],{"class":41,"line":140},[39,579,137],{"emptyLinePlaceholder":136},[39,581,582,585,587,589,591,593,596],{"class":41,"line":164},[39,583,584],{"class":167},"DEFAULTS",[39,586,171],{"class":105},[39,588,293],{"class":109},[39,590,344],{"class":49},[39,592,299],{"class":109},[39,594,595],{"class":49},"\"EUR\"",[39,597,347],{"class":109},[39,599,600,603,605,607,609,611,613,616,618,620,622,624,627],{"class":41,"line":177},[39,601,602],{"class":109},"unfixable ",[39,604,146],{"class":105},[39,606,506],{"class":109},[39,608,188],{"class":105},[39,610,191],{"class":109},[39,612,194],{"class":105},[39,614,615],{"class":109}," missing ",[39,617,205],{"class":105},[39,619,191],{"class":109},[39,621,522],{"class":105},[39,623,525],{"class":105},[39,625,626],{"class":167}," DEFAULTS",[39,628,161],{"class":109},[39,630,631,633],{"class":41,"line":222},[39,632,205],{"class":105},[39,634,635],{"class":109}," unfixable:\n",[39,637,638,641,644,646,649,652,656,659,662,665],{"class":41,"line":231},[39,639,640],{"class":105},"    raise",[39,642,643],{"class":167}," ValueError",[39,645,258],{"class":109},[39,647,648],{"class":105},"f",[39,650,651],{"class":49},"\"branch_north.xlsx lacks required columns: ",[39,653,655],{"class":654},"sSjpA","{",[39,657,658],{"class":109},"unfixable",[39,660,661],{"class":654},"}",[39,663,664],{"class":49},"\"",[39,666,250],{"class":109},[39,668,669,672,674,677,679,681],{"class":41,"line":236},[39,670,671],{"class":109},"aligned ",[39,673,146],{"class":105},[39,675,676],{"class":109}," branch.reindex(",[39,678,440],{"class":439},[39,680,146],{"class":105},[39,682,683],{"class":109},"template_cols)\n",[39,685,686,688,691,693,695],{"class":41,"line":253},[39,687,188],{"class":105},[39,689,690],{"class":109}," col, value ",[39,692,194],{"class":105},[39,694,626],{"class":167},[39,696,697],{"class":109},".items():\n",[39,699,701,704,706],{"class":41,"line":700},11,[39,702,703],{"class":109},"    aligned[col] ",[39,705,146],{"class":105},[39,707,708],{"class":109}," aligned[col].fillna(value)\n",[39,710,712,714],{"class":41,"line":711},12,[39,713,225],{"class":167},[39,715,716],{"class":109},"(aligned)\n",[10,718,719,721,722,724,725,729],{},[14,720,81],{}," is missing, but the business rule says branch files are always in euros, so it gets a documented default; any other missing column stops the run with a message naming it. ",[14,723,85],{}," is dropped by the reindex because it is not in the template. Whether extras should be dropped, kept at the end, or treated as an error depends on the receiving system; for an import that rejects unknown columns, dropping is right — but log it, so a column that ",[726,727,728],"em",{},"should"," have mapped (a new spelling of \"Customer ID\", say) does not vanish unnoticed.",[731,732,740,741,740,745,740,749,740,757,740,765,740,772,740,775,740,780,740,783,740,787,740,793,740,797,740,800,740,804,740,807,740,811,740,814,740,817,740,822,740,826,740,830,740,833,740,837,740,840,740,842,740,844,740,846,740,848,740,850,740,852,740,854,740,856,740,858,740,860,740,864,740,868,740,872,740,875,740,880,740,885,740,893],"svg",{"viewBox":733,"role":734,"ariaLabelledBy":735,"xmlns":738,"style":739},"129 0 497 280","img",[736,737],"rc-align-t","rc-align-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:497px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[742,743,744],"title",{"id":736},"A branch file reordered to the template layout",[746,747,748],"desc",{"id":737},"On the left the branch columns in their own order — Customer Name, Gross, Invoice No, Cust No, Net, VAT, invoice date, Notes. On the right the template order — Invoice No, Invoice Date, Customer ID, Customer Name, Net, VAT, Gross, Currency — with Currency filled by default and Notes dropped.",[750,751],"rect",{"x":752,"y":753,"width":754,"height":755,"fill":756},"129","0","497","280","#ffffff",[750,758],{"x":759,"y":760,"width":761,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},"150.1","40.0","107.0","28.0","var(--line,#cdd5e6)","stroke-width:1px",[766,767,771],"text",{"x":768,"y":769,"style":770},"203.6","59.0","font-size:10px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Customer Name",[750,773],{"x":759,"y":774,"width":761,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},"68.0",[766,776,779],{"x":768,"y":777,"style":778},"87.0","font-size:10px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Gross",[750,781],{"x":759,"y":782,"width":761,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},"96.0",[766,784,786],{"x":768,"y":785,"style":778},"115.0","Invoice No",[750,788],{"x":759,"y":789,"width":761,"height":762,"rx":753,"fill":790,"stroke":791,"style":792},"124.0","#fdefd8","var(--gold,#b4740a)","stroke-width:1.5px",[766,794,73],{"x":768,"y":795,"style":796},"143.0","font-size:10px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[750,798],{"x":759,"y":799,"width":761,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},"152.0",[766,801,803],{"x":768,"y":802,"style":778},"171.0","Net",[750,805],{"x":759,"y":806,"width":761,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},"180.0",[766,808,810],{"x":768,"y":809,"style":778},"199.0","VAT",[750,812],{"x":759,"y":813,"width":761,"height":762,"rx":753,"fill":790,"stroke":791,"style":792},"208.0",[766,815,69],{"x":768,"y":816,"style":796},"227.0",[750,818],{"x":759,"y":819,"width":761,"height":762,"rx":753,"fill":820,"stroke":821,"style":792},"236.0","#fce9e9","var(--danger,#dc2626)",[766,823,85],{"x":768,"y":824,"style":825},"255.0","font-size:10px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[750,827],{"x":828,"y":760,"width":829,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},"467.5","142.4",[766,831,786],{"x":832,"y":769,"style":770},"538.7",[750,834],{"x":828,"y":774,"width":829,"height":762,"rx":753,"fill":835,"stroke":836,"style":792},"#d9f4f1","var(--teal,#0f9488)",[766,838,463],{"x":832,"y":777,"style":839},"font-size:10px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[750,841],{"x":828,"y":782,"width":829,"height":762,"rx":753,"fill":835,"stroke":836,"style":792},[766,843,77],{"x":832,"y":785,"style":839},[750,845],{"x":828,"y":789,"width":829,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},[766,847,771],{"x":832,"y":795,"style":778},[750,849],{"x":828,"y":799,"width":829,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},[766,851,803],{"x":832,"y":802,"style":778},[750,853],{"x":828,"y":806,"width":829,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},[766,855,810],{"x":832,"y":809,"style":778},[750,857],{"x":828,"y":813,"width":829,"height":762,"rx":753,"fill":756,"stroke":763,"style":764},[766,859,779],{"x":832,"y":816,"style":778},[750,861],{"x":828,"y":819,"width":829,"height":762,"rx":753,"fill":862,"stroke":863,"style":792},"#f0f4ff","var(--brand,#5b5cf0)",[766,865,867],{"x":832,"y":824,"style":866},"font-size:10px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Currency (default EUR)",[766,869,65],{"x":768,"y":870,"style":871},"29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle",[766,873,874],{"x":832,"y":870,"style":871},"template order",[41,876],{"x1":877,"y1":799,"x2":878,"y2":799,"stroke":863,"style":879},"267.1","453.5","stroke-width:2px",[881,882],"polygon",{"points":883,"fill":884},"457.5,152.0 448.5,157.0 448.5,147.0","#5b5cf0",[750,886],{"x":887,"y":888,"width":889,"height":890,"rx":891,"fill":756,"stroke":763,"style":892},"294.2","119.0","136.3","22.0","11","stroke-width:1.2px",[766,894,898],{"x":895,"y":896,"style":897},"362.3","134.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","rename + reindex",[10,900,901],{},"The two kinds of mismatch each need a policy, chosen per column rather than once for the whole file:",[731,903,740,909,740,912,740,915,740,920,740,925,740,929,740,935,740,938,740,942,740,947,740,953,740,955,740,960,740,962,740,964,740,967,740,971,740,973,740,977,740,979,740,982,740,984,740,988,740,990,740,994,740,996,740,998,740,1001,740,1004,740,1006,740,1010,740,1012],{"viewBox":904,"role":734,"ariaLabelledBy":905,"xmlns":738,"style":908},"4 0 752 208",[906,907],"rc-policy-t","rc-policy-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[742,910,911],{"id":906},"Policies for missing and extra columns",[746,913,914],{"id":907},"A required column that is missing stops the run with an error. A missing column with a business default, such as Currency, is filled with that default. An extra column is dropped and logged for strict imports, or appended after the template columns when the receiver accepts extras.",[750,916],{"x":917,"y":753,"width":918,"height":919,"fill":756},"4","752","208",[750,921],{"x":922,"y":923,"width":924,"height":760,"rx":753,"fill":756,"stroke":763,"style":764},"20.0","16.0","175.4",[750,926],{"x":927,"y":923,"width":928,"height":760,"rx":753,"fill":862,"stroke":863,"style":892},"195.4","272.3",[766,930,934],{"x":931,"y":932,"style":933},"331.6","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","action",[750,936],{"x":937,"y":923,"width":928,"height":760,"rx":753,"fill":862,"stroke":863,"style":892},"467.7",[766,939,941],{"x":940,"y":932,"style":933},"603.9","example",[750,943],{"x":922,"y":944,"width":924,"height":945,"rx":753,"fill":946,"stroke":763,"style":764},"56.0","34.0","#e7ebef",[766,948,952],{"x":949,"y":950,"style":951},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Missing and required",[750,954],{"x":927,"y":944,"width":928,"height":945,"rx":753,"fill":756,"stroke":763,"style":764},[766,956,959],{"x":931,"y":957,"style":958},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","raise an error",[750,961],{"x":937,"y":944,"width":928,"height":945,"rx":753,"fill":756,"stroke":763,"style":764},[766,963,77],{"x":940,"y":957,"style":958},[750,965],{"x":922,"y":966,"width":924,"height":945,"rx":753,"fill":946,"stroke":763,"style":764},"90.0",[766,968,970],{"x":949,"y":969,"style":951},"112.0","Missing with a default",[750,972],{"x":927,"y":966,"width":928,"height":945,"rx":753,"fill":756,"stroke":763,"style":764},[766,974,976],{"x":931,"y":975,"style":958},"111.0","fill the default",[750,978],{"x":937,"y":966,"width":928,"height":945,"rx":753,"fill":756,"stroke":763,"style":764},[766,980,981],{"x":940,"y":975,"style":958},"Currency = EUR",[750,983],{"x":922,"y":789,"width":924,"height":945,"rx":753,"fill":946,"stroke":763,"style":764},[766,985,987],{"x":949,"y":986,"style":951},"146.0","Extra, strict receiver",[750,989],{"x":927,"y":789,"width":928,"height":945,"rx":753,"fill":756,"stroke":763,"style":764},[766,991,993],{"x":931,"y":992,"style":958},"145.0","drop and log",[750,995],{"x":937,"y":789,"width":928,"height":945,"rx":753,"fill":756,"stroke":763,"style":764},[766,997,85],{"x":940,"y":992,"style":958},[750,999],{"x":922,"y":1000,"width":924,"height":945,"rx":753,"fill":946,"stroke":763,"style":764},"158.0",[766,1002,1003],{"x":949,"y":806,"style":951},"Extra, tolerant receiver",[750,1005],{"x":927,"y":1000,"width":928,"height":945,"rx":753,"fill":756,"stroke":763,"style":764},[766,1007,1009],{"x":931,"y":1008,"style":958},"179.0","append after template columns",[750,1011],{"x":937,"y":1000,"width":928,"height":945,"rx":753,"fill":756,"stroke":763,"style":764},[766,1013,1014],{"x":940,"y":1008,"style":958},"Notes at the end",[10,1016,1017,1018,1020],{},"Writing the policy down as data — a ",[14,1019,584],{}," dictionary, a list of columns allowed to be missing — keeps it reviewable by the people who own the import format, who rarely read Python but can read a table.",[25,1022,1024],{"id":1023},"step-4-verify-the-layout-before-writing","Step 4 — Verify the layout before writing",[10,1026,1027],{},"A final assertion costs one line and catches regressions in the mapping code:",[30,1029,1031],{"className":96,"code":1030,"language":98,"meta":35,"style":35},"assert list(aligned.columns) == template_cols, \"column order does not match the template\"\nproblems = aligned[template_cols].isna().sum()\nprint(problems[problems > 0].to_dict() or \"no empty required values\")\n",[14,1032,1033,1053,1063],{"__ignoreMap":35},[39,1034,1035,1038,1041,1044,1047,1050],{"class":41,"line":42},[39,1036,1037],{"class":105},"assert",[39,1039,1040],{"class":167}," list",[39,1042,1043],{"class":109},"(aligned.columns) ",[39,1045,1046],{"class":105},"==",[39,1048,1049],{"class":109}," template_cols, ",[39,1051,1052],{"class":49},"\"column order does not match the template\"\n",[39,1054,1055,1058,1060],{"class":41,"line":119},[39,1056,1057],{"class":109},"problems ",[39,1059,146],{"class":105},[39,1061,1062],{"class":109}," aligned[template_cols].isna().sum()\n",[39,1064,1065,1067,1070,1073,1076,1079,1081,1084],{"class":41,"line":133},[39,1066,225],{"class":167},[39,1068,1069],{"class":109},"(problems[problems ",[39,1071,1072],{"class":105},">",[39,1074,1075],{"class":167}," 0",[39,1077,1078],{"class":109},"].to_dict() ",[39,1080,412],{"class":105},[39,1082,1083],{"class":49}," \"no empty required values\"",[39,1085,250],{"class":109},[10,1087,1088,1089,23],{},"The order check guarantees nothing was shifted; the per-column count of missing values shows whether a column that exists is actually filled. Deeper checks — types, value ranges, allowed codes — belong in validation; see ",[19,1090,1092],{"href":1091},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fvalidate-excel-columns-before-import-with-pandas\u002F","Validate Excel Columns Before Import with pandas",[25,1094,1096],{"id":1095},"step-5-write-the-data-into-the-template-itself","Step 5 — Write the data into the template itself",[10,1098,1099,1100,1103],{},"Writing the aligned frame to a new file with ",[14,1101,1102],{},"to_excel"," would lose the template's title and formatted header. Load the template with openpyxl and write the values under its header row instead:",[30,1105,1107],{"className":96,"code":1106,"language":98,"meta":35,"style":35},"import datetime as dt\n\nbook = load_workbook(\"import_template.xlsx\")\nsheet = book[\"Import\"]\nfor r, row in enumerate(aligned.itertuples(index=False), start=HEADER_ROW + 1):\n    for c, value in enumerate(row, start=1):\n        if isinstance(value, pd.Timestamp):\n            value = value.to_pydatetime()\n        cell = sheet.cell(r, c, None if pd.isna(value) else value)\n        if isinstance(value, dt.datetime):\n            cell.number_format = \"dd\u002Fmm\u002Fyyyy\"\n        elif isinstance(value, float):\n            cell.number_format = \"#,##0.00\"\nbook.save(\"import_north_2026-09.xlsx\")\ncheck = pd.read_excel(\"import_north_2026-09.xlsx\", header=HEADER_ROW - 1)\nprint(list(check.columns) == template_cols, len(check), \"rows\")\n",[14,1108,1109,1121,1125,1138,1152,1194,1218,1229,1239,1264,1273,1283,1298,1308,1319,1347],{"__ignoreMap":35},[39,1110,1111,1113,1116,1118],{"class":41,"line":42},[39,1112,106],{"class":105},[39,1114,1115],{"class":109}," datetime ",[39,1117,113],{"class":105},[39,1119,1120],{"class":109}," dt\n",[39,1122,1123],{"class":41,"line":119},[39,1124,137],{"emptyLinePlaceholder":136},[39,1126,1127,1130,1132,1134,1136],{"class":41,"line":133},[39,1128,1129],{"class":109},"book ",[39,1131,146],{"class":105},[39,1133,149],{"class":109},[39,1135,152],{"class":49},[39,1137,250],{"class":109},[39,1139,1140,1143,1145,1148,1150],{"class":41,"line":140},[39,1141,1142],{"class":109},"sheet ",[39,1144,146],{"class":105},[39,1146,1147],{"class":109}," book[",[39,1149,158],{"class":49},[39,1151,161],{"class":109},[39,1153,1154,1156,1159,1161,1164,1167,1170,1172,1175,1178,1181,1183,1185,1188,1191],{"class":41,"line":164},[39,1155,188],{"class":105},[39,1157,1158],{"class":109}," r, row ",[39,1160,194],{"class":105},[39,1162,1163],{"class":167}," enumerate",[39,1165,1166],{"class":109},"(aligned.itertuples(",[39,1168,1169],{"class":439},"index",[39,1171,146],{"class":105},[39,1173,1174],{"class":167},"False",[39,1176,1177],{"class":109},"), ",[39,1179,1180],{"class":439},"start",[39,1182,146],{"class":105},[39,1184,168],{"class":167},[39,1186,1187],{"class":105}," +",[39,1189,1190],{"class":167}," 1",[39,1192,1193],{"class":109},"):\n",[39,1195,1196,1199,1202,1204,1206,1209,1211,1213,1216],{"class":41,"line":177},[39,1197,1198],{"class":105},"    for",[39,1200,1201],{"class":109}," c, value ",[39,1203,194],{"class":105},[39,1205,1163],{"class":167},[39,1207,1208],{"class":109},"(row, ",[39,1210,1180],{"class":439},[39,1212,146],{"class":105},[39,1214,1215],{"class":167},"1",[39,1217,1193],{"class":109},[39,1219,1220,1223,1226],{"class":41,"line":222},[39,1221,1222],{"class":105},"        if",[39,1224,1225],{"class":167}," isinstance",[39,1227,1228],{"class":109},"(value, pd.Timestamp):\n",[39,1230,1231,1234,1236],{"class":41,"line":231},[39,1232,1233],{"class":109},"            value ",[39,1235,146],{"class":105},[39,1237,1238],{"class":109}," value.to_pydatetime()\n",[39,1240,1241,1244,1246,1249,1252,1255,1258,1261],{"class":41,"line":236},[39,1242,1243],{"class":109},"        cell ",[39,1245,146],{"class":105},[39,1247,1248],{"class":109}," sheet.cell(r, c, ",[39,1250,1251],{"class":167},"None",[39,1253,1254],{"class":105}," if",[39,1256,1257],{"class":109}," pd.isna(value) ",[39,1259,1260],{"class":105},"else",[39,1262,1263],{"class":109}," value)\n",[39,1265,1266,1268,1270],{"class":41,"line":253},[39,1267,1222],{"class":105},[39,1269,1225],{"class":167},[39,1271,1272],{"class":109},"(value, dt.datetime):\n",[39,1274,1275,1278,1280],{"class":41,"line":700},[39,1276,1277],{"class":109},"            cell.number_format ",[39,1279,146],{"class":105},[39,1281,1282],{"class":49}," \"dd\u002Fmm\u002Fyyyy\"\n",[39,1284,1285,1288,1290,1293,1296],{"class":41,"line":711},[39,1286,1287],{"class":105},"        elif",[39,1289,1225],{"class":167},[39,1291,1292],{"class":109},"(value, ",[39,1294,1295],{"class":167},"float",[39,1297,1193],{"class":109},[39,1299,1301,1303,1305],{"class":41,"line":1300},13,[39,1302,1277],{"class":109},[39,1304,146],{"class":105},[39,1306,1307],{"class":49}," \"#,##0.00\"\n",[39,1309,1311,1314,1317],{"class":41,"line":1310},14,[39,1312,1313],{"class":109},"book.save(",[39,1315,1316],{"class":49},"\"import_north_2026-09.xlsx\"",[39,1318,250],{"class":109},[39,1320,1322,1325,1327,1329,1331,1333,1336,1338,1340,1343,1345],{"class":41,"line":1321},15,[39,1323,1324],{"class":109},"check ",[39,1326,146],{"class":105},[39,1328,244],{"class":109},[39,1330,1316],{"class":49},[39,1332,70],{"class":109},[39,1334,1335],{"class":439},"header",[39,1337,146],{"class":105},[39,1339,168],{"class":167},[39,1341,1342],{"class":105}," -",[39,1344,1190],{"class":167},[39,1346,250],{"class":109},[39,1348,1350,1352,1354,1356,1359,1361,1363,1366,1369,1372],{"class":41,"line":1349},16,[39,1351,225],{"class":167},[39,1353,258],{"class":109},[39,1355,261],{"class":167},[39,1357,1358],{"class":109},"(check.columns) ",[39,1360,1046],{"class":105},[39,1362,1049],{"class":109},[39,1364,1365],{"class":167},"len",[39,1367,1368],{"class":109},"(check), ",[39,1370,1371],{"class":49},"\"rows\"",[39,1373,250],{"class":109},[10,1375,1376,1377,1380,1381,23],{},"The title, the indigo header and the column widths stay exactly as the template's owner made them; the data starts on row 4. Timestamps are converted to Python datetimes and given a date format, and amounts a number format, because openpyxl writes raw values. Reading the result back with ",[14,1378,1379],{},"header=2"," (the zero-based header row) confirms the layout end to end. Filling templates in general is covered in ",[19,1382,1384],{"href":1383},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fpopulate-excel-template-without-losing-formatting\u002F","Populate an Excel Template Without Losing Formatting",[731,1386,740,1391,740,1394,740,1397,740,1400,740,1405,740,1416,740,1422,740,1425,740,1429,740,1434,740,1437,740,1440,740,1447,740,1452,740,1456,740,1459,740,1463,740,1466,740,1469,740,1476,740,1481,740,1484,740,1487,740,1491,740,1494,740,1497,740,1504,740,1509,740,1512],{"viewBox":1387,"role":734,"ariaLabelledBy":1388,"xmlns":738,"style":908},"4 0 752 142",[1389,1390],"rc-pipeline-t","rc-pipeline-d",[742,1392,1393],{"id":1389},"From any branch file to a valid import file",[746,1395,1396],{"id":1390},"The template header is read, the branch file's headers are mapped through aliases, reindex puts the columns in template order with defaults and checks for missing columns, and the values are written below the template's own header.",[750,1398],{"x":917,"y":753,"width":918,"height":1399,"fill":756},"142",[750,1401],{"x":922,"y":923,"width":1402,"height":1403,"rx":1404,"fill":946,"stroke":763,"style":879},"153.0","110.5","12",[1406,1407,1409],"g",{"transform":1408},"translate(84.5,29.0) scale(1.000)",[1410,1411],"path",{"d":1412,"fill":1413,"stroke":1414,"style":1415},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[766,1417,1421],{"x":1418,"y":1419,"style":1420},"96.5","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Read template",[766,1423,1335],{"x":1418,"y":1424,"style":1420},"93.0",[766,1426,1428],{"x":1418,"y":1427,"style":958},"112.5","single source of truth",[41,1430],{"x1":1431,"y1":1432,"x2":1433,"y2":1432,"stroke":863,"style":879},"178.0","71.2","200.0",[881,1435],{"points":1436,"fill":884},"204.0,71.2 195.0,76.2 195.0,66.2",[750,1438],{"x":1439,"y":923,"width":1402,"height":1403,"rx":1404,"fill":862,"stroke":863,"style":879},"209.0",[1406,1441,1443],{"transform":1442},"translate(273.5,29.0) scale(1.000)",[1410,1444],{"d":1445,"fill":1413,"stroke":1446,"style":1415},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","var(--brand-strong,#4338ca)",[766,1448,1451],{"x":1449,"y":1419,"style":1450},"285.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Map aliases",[766,1453,1455],{"x":1449,"y":1454,"style":958},"95.0","Cust No → Customer",[766,1457,1458],{"x":1449,"y":1427,"style":958},"ID",[41,1460],{"x1":1461,"y1":1432,"x2":1462,"y2":1432,"stroke":863,"style":879},"367.0","389.0",[881,1464],{"points":1465,"fill":884},"393.0,71.2 384.0,76.2 384.0,66.2",[750,1467],{"x":1468,"y":923,"width":1402,"height":1403,"rx":1404,"fill":790,"stroke":791,"style":879},"398.0",[1406,1470,1472],{"transform":1471},"translate(462.5,29.0) scale(1.000)",[1410,1473],{"d":1474,"fill":1413,"stroke":1475,"style":1415},"M3 5h18l-7 8v6l-4 2v-8z","var(--gold-ink,#7a4e06)",[766,1477,1480],{"x":1478,"y":1419,"style":1479},"474.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Reindex + check",[766,1482,1483],{"x":1478,"y":1454,"style":958},"defaults, fail on",[766,1485,1486],{"x":1478,"y":1427,"style":958},"missing",[41,1488],{"x1":1489,"y1":1432,"x2":1490,"y2":1432,"stroke":863,"style":879},"556.0","578.0",[881,1492],{"points":1493,"fill":884},"582.0,71.2 573.0,76.2 573.0,66.2",[750,1495],{"x":1496,"y":923,"width":1402,"height":1403,"rx":1404,"fill":835,"stroke":836,"style":879},"587.0",[1406,1498,1500],{"transform":1499},"translate(651.5,29.0) scale(1.000)",[1410,1501],{"d":1502,"fill":1413,"stroke":1503,"style":1415},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","var(--teal-ink,#0b6157)",[766,1505,1508],{"x":1506,"y":1419,"style":1507},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Write into",[766,1510,1511],{"x":1506,"y":1424,"style":1507},"template",[766,1513,1514],{"x":1506,"y":1427,"style":958},"formatting kept",[25,1516,1518],{"id":1517},"step-6-process-every-branch-the-same-way","Step 6 — Process every branch the same way",[10,1520,1521],{},"Wrap the steps in a function and run it over all incoming files, collecting problems instead of stopping at the first:",[30,1523,1525],{"className":96,"code":1524,"language":98,"meta":35,"style":35},"def align_to_template(path):\n    df = pd.read_excel(path).rename(columns=to_template_name)\n    missing = [c for c in template_cols if c not in df.columns and c not in DEFAULTS]\n    if missing:\n        return None, f\"{path}: missing {missing}\"\n    out = df.reindex(columns=template_cols)\n    for col, value in DEFAULTS.items():\n        out[col] = out[col].fillna(value)\n    dropped = [c for c in df.columns if c not in template_cols]\n    return out, f\"{path}: ok\" + (f\", dropped {dropped}\" if dropped else \"\")\n\npd.DataFrame({\"Invoice No\": [\"S-9\"], \"Net\": [10.0]}).to_excel(\"branch_south.xlsx\", index=False)\nresults = [align_to_template(p) for p in (\"branch_north.xlsx\", \"branch_south.xlsx\")]\nfor _, message in results:\n    print(message)\ncombined = pd.concat([df for df, _ in results if df is not None], ignore_index=True)\nprint(combined.shape)\n",[14,1526,1527,1537,1553,1594,1602,1633,1649,1661,1671,1698,1749,1753,1793,1821,1833,1841,1884],{"__ignoreMap":35},[39,1528,1529,1531,1534],{"class":41,"line":42},[39,1530,375],{"class":105},[39,1532,1533],{"class":378}," align_to_template",[39,1535,1536],{"class":109},"(path):\n",[39,1538,1539,1542,1544,1547,1549,1551],{"class":41,"line":119},[39,1540,1541],{"class":109},"    df ",[39,1543,146],{"class":105},[39,1545,1546],{"class":109}," pd.read_excel(path).rename(",[39,1548,440],{"class":439},[39,1550,146],{"class":105},[39,1552,445],{"class":109},[39,1554,1555,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1578,1581,1584,1586,1588,1590,1592],{"class":41,"line":133},[39,1556,1557],{"class":109},"    missing ",[39,1559,146],{"class":105},[39,1561,506],{"class":109},[39,1563,188],{"class":105},[39,1565,191],{"class":109},[39,1567,194],{"class":105},[39,1569,515],{"class":109},[39,1571,205],{"class":105},[39,1573,191],{"class":109},[39,1575,522],{"class":105},[39,1577,525],{"class":105},[39,1579,1580],{"class":109}," df.columns ",[39,1582,1583],{"class":105},"and",[39,1585,191],{"class":109},[39,1587,522],{"class":105},[39,1589,525],{"class":105},[39,1591,626],{"class":167},[39,1593,161],{"class":109},[39,1595,1596,1599],{"class":41,"line":140},[39,1597,1598],{"class":105},"    if",[39,1600,1601],{"class":109}," missing:\n",[39,1603,1604,1607,1609,1611,1613,1615,1617,1619,1621,1624,1626,1628,1630],{"class":41,"line":164},[39,1605,1606],{"class":105},"        return",[39,1608,217],{"class":167},[39,1610,70],{"class":109},[39,1612,648],{"class":105},[39,1614,664],{"class":49},[39,1616,655],{"class":654},[39,1618,1410],{"class":109},[39,1620,661],{"class":654},[39,1622,1623],{"class":49},": missing ",[39,1625,655],{"class":654},[39,1627,1486],{"class":109},[39,1629,661],{"class":654},[39,1631,1632],{"class":49},"\"\n",[39,1634,1635,1638,1640,1643,1645,1647],{"class":41,"line":177},[39,1636,1637],{"class":109},"    out ",[39,1639,146],{"class":105},[39,1641,1642],{"class":109}," df.reindex(",[39,1644,440],{"class":439},[39,1646,146],{"class":105},[39,1648,683],{"class":109},[39,1650,1651,1653,1655,1657,1659],{"class":41,"line":222},[39,1652,1198],{"class":105},[39,1654,690],{"class":109},[39,1656,194],{"class":105},[39,1658,626],{"class":167},[39,1660,697],{"class":109},[39,1662,1663,1666,1668],{"class":41,"line":231},[39,1664,1665],{"class":109},"        out[col] ",[39,1667,146],{"class":105},[39,1669,1670],{"class":109}," out[col].fillna(value)\n",[39,1672,1673,1676,1678,1680,1682,1684,1686,1688,1690,1692,1694,1696],{"class":41,"line":236},[39,1674,1675],{"class":109},"    dropped ",[39,1677,146],{"class":105},[39,1679,506],{"class":109},[39,1681,188],{"class":105},[39,1683,191],{"class":109},[39,1685,194],{"class":105},[39,1687,1580],{"class":109},[39,1689,205],{"class":105},[39,1691,191],{"class":109},[39,1693,522],{"class":105},[39,1695,525],{"class":105},[39,1697,557],{"class":109},[39,1699,1700,1702,1705,1707,1709,1711,1713,1715,1718,1720,1723,1725,1728,1730,1733,1735,1737,1739,1742,1744,1747],{"class":41,"line":253},[39,1701,406],{"class":105},[39,1703,1704],{"class":109}," out, ",[39,1706,648],{"class":105},[39,1708,664],{"class":49},[39,1710,655],{"class":654},[39,1712,1410],{"class":109},[39,1714,661],{"class":654},[39,1716,1717],{"class":49},": ok\"",[39,1719,1187],{"class":105},[39,1721,1722],{"class":109}," (",[39,1724,648],{"class":105},[39,1726,1727],{"class":49},"\", dropped ",[39,1729,655],{"class":654},[39,1731,1732],{"class":109},"dropped",[39,1734,661],{"class":654},[39,1736,664],{"class":49},[39,1738,1254],{"class":105},[39,1740,1741],{"class":109}," dropped ",[39,1743,1260],{"class":105},[39,1745,1746],{"class":49}," \"\"",[39,1748,250],{"class":109},[39,1750,1751],{"class":41,"line":700},[39,1752,137],{"emptyLinePlaceholder":136},[39,1754,1755,1758,1760,1763,1766,1769,1772,1774,1777,1780,1783,1785,1787,1789,1791],{"class":41,"line":711},[39,1756,1757],{"class":109},"pd.DataFrame({",[39,1759,321],{"class":49},[39,1761,1762],{"class":109},": [",[39,1764,1765],{"class":49},"\"S-9\"",[39,1767,1768],{"class":109},"], ",[39,1770,1771],{"class":49},"\"Net\"",[39,1773,1762],{"class":109},[39,1775,1776],{"class":167},"10.0",[39,1778,1779],{"class":109},"]}).to_excel(",[39,1781,1782],{"class":49},"\"branch_south.xlsx\"",[39,1784,70],{"class":109},[39,1786,1169],{"class":439},[39,1788,146],{"class":105},[39,1790,1174],{"class":167},[39,1792,250],{"class":109},[39,1794,1795,1798,1800,1803,1805,1808,1810,1812,1814,1816,1818],{"class":41,"line":1300},[39,1796,1797],{"class":109},"results ",[39,1799,146],{"class":105},[39,1801,1802],{"class":109}," [align_to_template(p) ",[39,1804,188],{"class":105},[39,1806,1807],{"class":109}," p ",[39,1809,194],{"class":105},[39,1811,1722],{"class":109},[39,1813,247],{"class":49},[39,1815,70],{"class":109},[39,1817,1782],{"class":49},[39,1819,1820],{"class":109},")]\n",[39,1822,1823,1825,1828,1830],{"class":41,"line":1310},[39,1824,188],{"class":105},[39,1826,1827],{"class":109}," _, message ",[39,1829,194],{"class":105},[39,1831,1832],{"class":109}," results:\n",[39,1834,1835,1838],{"class":41,"line":1321},[39,1836,1837],{"class":167},"    print",[39,1839,1840],{"class":109},"(message)\n",[39,1842,1843,1846,1848,1851,1853,1856,1858,1861,1863,1866,1868,1870,1872,1874,1877,1879,1882],{"class":41,"line":1349},[39,1844,1845],{"class":109},"combined ",[39,1847,146],{"class":105},[39,1849,1850],{"class":109}," pd.concat([df ",[39,1852,188],{"class":105},[39,1854,1855],{"class":109}," df, _ ",[39,1857,194],{"class":105},[39,1859,1860],{"class":109}," results ",[39,1862,205],{"class":105},[39,1864,1865],{"class":109}," df ",[39,1867,211],{"class":105},[39,1869,214],{"class":105},[39,1871,217],{"class":167},[39,1873,1768],{"class":109},[39,1875,1876],{"class":439},"ignore_index",[39,1878,146],{"class":105},[39,1880,1881],{"class":167},"True",[39,1883,250],{"class":109},[39,1885,1887,1889],{"class":41,"line":1886},17,[39,1888,225],{"class":167},[39,1890,1891],{"class":109},"(combined.shape)\n",[10,1893,1894,1895,1897,1898,23],{},"North aligns, with ",[14,1896,85],{}," reported as dropped; South is rejected with the list of columns it lacks; the combined import contains only valid files. Sending the messages back to the branches is what eventually makes their files consistent — see ",[19,1899,1901],{"href":1900},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fsend-an-alert-when-an-excel-report-job-fails\u002F","Send an Alert When an Excel Report Job Fails",[25,1903,1905],{"id":1904},"common-pitfalls","Common pitfalls",[1907,1908,1909,1920,1926,1932,1938],"ul",{},[1910,1911,1912,1916,1917,1919],"li",{},[1913,1914,1915],"strong",{},"Relying on reindex to catch missing columns."," It fills them with ",[14,1918,490],{}," silently; check explicitly.",[1910,1921,1922,1925],{},[1913,1923,1924],{},"Hard-coding the column list."," Read it from the template so changes propagate.",[1910,1927,1928,1931],{},[1913,1929,1930],{},"Dropping extras without logging."," A misspelled required column looks like an extra; report what was dropped.",[1910,1933,1934,1937],{},[1913,1935,1936],{},"Overwriting the template's formatting with to_excel."," Write values into the loaded template instead.",[1910,1939,1940,1943],{},[1913,1941,1942],{},"Position-based pasting."," Matching by name is what makes the result independent of each branch's order.",[25,1945,1947],{"id":1946},"conclusion","Conclusion",[10,1949,1950,1951,1954],{},"Read the required column order from the template, map header variants to the template's names with a normalised lookup and an alias table, and reorder with ",[14,1952,1953],{},"reindex(columns=template_cols)"," — after checking for missing columns, filling only those with documented defaults, and logging any extras that are dropped. Assert the final order, then write the values below the template's own header with openpyxl so its formatting survives, and run the same function over every incoming file, collecting problems rather than stopping.",[25,1956,1958],{"id":1957},"frequently-asked-questions","Frequently asked questions",[10,1960,1961,1964,1965,1968,1969,1971],{},[1913,1962,1963],{},"How do I reorder DataFrame columns to match a list?","\nUse df.reindex(columns=template_cols) or df",[39,1966,1967],{},"template_cols",". reindex adds any missing columns filled with NaN, while df",[39,1970,1967],{}," raises a KeyError, so check for missing columns first if you use reindex.",[10,1973,1974,1977],{},[1913,1975,1976],{},"How do I handle columns that are named differently in each file?","\nNormalise header text (trim, single spaces, lower case) and map it to the template names through a dictionary of known aliases with df.rename(columns=function) before reordering.",[10,1979,1980,1983],{},[1913,1981,1982],{},"How do I write data into an Excel template without losing its header formatting?","\nLoad the template with openpyxl, write the values cell by cell starting below the header row, apply number formats, and save under a new name. to_excel would replace the sheet's layout.",[10,1985,1986,1989],{},[1913,1987,1988],{},"What should happen to extra columns that are not in the template?","\nFor strict import formats, drop them with reindex, but log their names so a misspelled required column is noticed. If the receiver accepts extras, append them after the template columns instead.",[25,1991,1993],{"id":1992},"related","Related",[1907,1995,1996,2002,2009,2016],{},[1910,1997,1998,1999,2001],{},"Up one level: ",[19,2000,22],{"href":21}," — tidying data before it moves on.",[1910,2003,2004,2008],{},[19,2005,2007],{"href":2006},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-excel-files-with-different-column-names\u002F","Merge Excel Files with Different Column Names"," — combining files whose headers disagree.",[1910,2010,2011,2015],{},[19,2012,2014],{"href":2013},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fconcatenate-excel-sheets-with-different-columns\u002F","Concatenate Excel Sheets with Different Columns"," — stacking when layouts differ.",[1910,2017,2018,2022],{},[19,2019,2021],{"href":2020},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl\u002F","Fill an Excel Template with Python and openpyxl"," — template filling in depth.",[2024,2025,2026],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":35,"searchDepth":119,"depth":119,"links":2028},[2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039],{"id":27,"depth":119,"text":28},{"id":89,"depth":119,"text":90},{"id":274,"depth":119,"text":275},{"id":480,"depth":119,"text":481},{"id":1023,"depth":119,"text":1024},{"id":1095,"depth":119,"text":1096},{"id":1517,"depth":119,"text":1518},{"id":1904,"depth":119,"text":1905},{"id":1946,"depth":119,"text":1947},{"id":1957,"depth":119,"text":1958},{"id":1992,"depth":119,"text":1993},"2026-10-02","Make every incoming file match the column layout a template, upload form or downstream system expects: read the required order from the template itself, map renamed headers, reindex so columns land in the right place, decide what happens to missing and extra columns, check the result, and write the data under the template's own header row without disturbing its formatting.","md",[2044,2046,2048,2050],{"q":1963,"a":2045},"Use df.reindex(columns=template_cols) or df[template_cols]. reindex adds any missing columns filled with NaN, while df[template_cols] raises a KeyError, so check for missing columns first if you use reindex.",{"q":1976,"a":2047},"Normalise header text (trim, single spaces, lower case) and map it to the template names through a dictionary of known aliases with df.rename(columns=function) before reordering.",{"q":1982,"a":2049},"Load the template with openpyxl, write the values cell by cell starting below the header row, apply number formats, and save under a new name. to_excel would replace the sheet's layout.",{"q":1988,"a":2051},"For strict import formats, drop them with reindex, but log their names so a misspelled required column is noticed. If the receiver accepts extras, append them after the template columns instead.","reorder columns to match a template layout with pandas",null,{"breadcrumb":2055},[2056,2059,2062],{"name":2057,"item":2058},"Home","\u002F",{"name":2060,"item":2061},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2063,"item":21},"Cleaning Excel Data With Pandas: A Production Workflow","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freorder-columns-to-match-a-template-layout-with-pandas",{"title":2066,"description":2067},"Reorder Excel Columns to Match a Template with pandas","Align a DataFrame to a template layout: read the template header, rename aliases, reindex(columns=...), fail or fill on missing columns, keep or drop extras, validate the order, and write into the template with openpyxl below its formatted header.","reorder-columns-to-match-a-template-layout-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freorder-columns-to-match-a-template-layout-with-pandas\u002Findex",[2071,2072],"data-cleaning","writing-excel","how-to","ZlmkH5AU0a9Y8HmSnmDEp8UtThX6qLBvzQ2orDwtMC0",[2076,2080],{"title":2077,"path":2078,"stem":2079,"children":-1},"Remove Non-Printable Characters from Excel Data with Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fremove-non-printable-characters-from-excel-data","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fremove-non-printable-characters-from-excel-data\u002Findex",{"title":2081,"path":2082,"stem":2083,"children":-1},"Replace Values in an Excel Column with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freplace-values-in-an-excel-column-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freplace-values-in-an-excel-column-with-pandas\u002Findex",[2085,2087],{"path":2086,"title":2063},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas",{"path":2088,"title":2060},"\u002Fadvanced-data-transformation-and-cleaning",[],[2091,2094,2097,2100,2103,2106],{"path":2092,"title":2093,"kind":2073},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Ffind-duplicate-rows-in-excel-with-python","Find Duplicate Rows in Excel with Python",{"path":2095,"title":2096,"kind":2073},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-duplicate-values-in-excel-with-openpyxl","Highlight Duplicate Values in Excel with openpyxl",{"path":2098,"title":2099,"kind":2073},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fclean-mixed-currency-amounts-into-amount-and-currency-columns","Clean Mixed-Currency Amounts into Amount and Currency Columns",{"path":2101,"title":2102,"kind":2073},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fcoalesce-several-excel-columns-into-one-with-pandas","Coalesce Several Excel Columns into One with pandas",{"path":2104,"title":2105,"kind":2073},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-currency-strings-to-numbers-with-pandas","Convert Currency Strings Like \"$1,234.56\" and \"(500)\" to Numbers with pandas",{"path":2107,"title":2108,"kind":2073},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-excel-text-columns-to-numbers-with-pandas","Convert Excel Text Columns to Numbers with pandas",1790972085649]