[{"data":1,"prerenderedAt":2674},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet":2641,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet":2648,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet":2654,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet":2655},{"id":4,"title":5,"body":6,"dateModified":2607,"datePublished":2607,"description":2608,"extension":2609,"faq":2610,"keyword":2619,"kind":2620,"meta":2621,"navigation":514,"path":2630,"seo":2631,"slug":2634,"stem":2635,"topics":2636,"type":2639,"__hash__":2640},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet\u002Findex.md","Read Multiple Tables from One Excel Sheet",{"type":7,"value":8,"toc":2593},"minimark",[9,28,446,451,479,486,805,809,822,907,918,922,929,1235,1259,1350,1354,1361,1515,1518,1522,1525,1838,1844,1928,1932,1952,2140,2151,2155,2161,2231,2254,2258,2366,2380,2384,2403,2506,2509,2513,2519,2523,2529,2535,2541,2547,2551,2589],[10,11,12,13,17,18,21,22,27],"p",{},"A summary sheet that a person built by hand rarely holds one table. It holds a title, a regional\nbreakdown, a gap, a product table underneath, and perhaps a quota list off to the right — all on the\nsame worksheet. ",[14,15,16],"code",{},"pd.read_excel()"," has no idea where one ends and the next begins; it treats the whole\nused range as a single grid and hands back a DataFrame full of ",[14,19,20],{},"NaN"," and misplaced headers. This\nguide, part of ",[23,24,26],"a",{"href":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002F","Extracting Data from Messy Excel Sheets",",\nshows three reliable ways to get each table out on its own.",[29,30,38,39,38,43,38,47,38,54,38,61,38,70,38,74,38,80,38,84,38,88,38,92,38,96,38,100,38,104,38,107,38,111,38,114,38,118,38,122,38,126,38,129,38,133,38,135,38,139,38,141,38,143,38,145,38,147,38,149,38,151,38,154,38,158,38,160,38,162,38,164,38,166,38,168,38,170,38,172,38,175,38,179,38,183,38,187,38,189,38,192,38,194,38,197,38,199,38,201,38,205,38,209,38,211,38,214,38,217,38,221,38,223,38,227,38,229,38,232,38,234,38,237,38,239,38,241,38,243,38,246,38,248,38,251,38,254,38,258,38,260,38,263,38,265,38,268,38,270,38,273,38,275,38,277,38,279,38,282,38,284,38,287,38,290,38,294,38,296,38,299,38,301,38,304,38,306,38,309,38,311,38,313,38,315,38,317,38,320,38,324,38,326,38,328,38,330,38,332,38,334,38,336,38,338,38,341,38,345,38,347,38,349,38,351,38,353,38,355,38,357,38,359,38,362,38,366,38,370,38,374,38,376,38,379,38,381,38,383,38,385,38,387,38,389,38,392,38,396,38,398,38,401,38,403,38,406,38,408,38,410,38,412,38,414,38,416,38,419,38,422,38,424,38,427,38,429,38,431,38,433,38,435,38,437,38,439,38,441],"svg",{"viewBox":31,"role":32,"ariaLabelledBy":33,"xmlns":36,"style":37},"0 0 506 464","img",[34,35],"mts-sheet-t","mts-sheet-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:506px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[40,41,42],"title",{"id":34},"One worksheet holding three separate tables",[44,45,46],"desc",{"id":35},"A sheet with a title in row 1, a region table in rows 3 to 6, a product table in rows 9 to 11 and a rep quota table in columns F and G, separated by blank rows and a blank column.",[48,49],"rect",{"x":50,"y":50,"width":51,"height":52,"fill":53},"0","506","464","#ffffff",[55,56,60],"text",{"x":57,"y":58,"style":59},"253","32","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","what read_excel sees as one grid",[48,62],{"x":63,"y":64,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"20","50","36","30","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[48,71],{"x":72,"y":64,"width":73,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"56","100",[55,75,79],{"x":76,"y":77,"style":78},"106","70","font-size:11.5px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[48,81],{"x":82,"y":64,"width":83,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"156","78",[55,85,87],{"x":86,"y":77,"style":78},"195","B",[48,89],{"x":90,"y":64,"width":91,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"234","60",[55,93,95],{"x":94,"y":77,"style":78},"264","C",[48,97],{"x":98,"y":64,"width":99,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"294","34",[55,101,103],{"x":102,"y":77,"style":78},"311","D",[48,105],{"x":106,"y":64,"width":99,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"328",[55,108,110],{"x":109,"y":77,"style":78},"345","E",[48,112],{"x":113,"y":64,"width":91,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"362",[55,115,117],{"x":116,"y":77,"style":78},"392","F",[48,119],{"x":120,"y":64,"width":121,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"422","64",[55,123,125],{"x":124,"y":77,"style":78},"454","G",[48,127],{"x":63,"y":128,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"80",[55,130,132],{"x":131,"y":73,"style":78},"38","1",[48,134],{"x":72,"y":128,"width":73,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},[55,136,138],{"x":76,"y":73,"style":137},"font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Sales — March",[48,140],{"x":82,"y":128,"width":83,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,142],{"x":90,"y":128,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,144],{"x":98,"y":128,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,146],{"x":106,"y":128,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,148],{"x":113,"y":128,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,150],{"x":120,"y":128,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,152],{"x":63,"y":153,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"110",[55,155,157],{"x":131,"y":156,"style":78},"130","2",[48,159],{"x":72,"y":153,"width":73,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,161],{"x":82,"y":153,"width":83,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,163],{"x":90,"y":153,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,165],{"x":98,"y":153,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,167],{"x":106,"y":153,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,169],{"x":113,"y":153,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,171],{"x":120,"y":153,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,173],{"x":63,"y":174,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"140",[55,176,178],{"x":131,"y":177,"style":78},"160","3",[48,180],{"x":72,"y":174,"width":73,"height":66,"rx":50,"fill":181,"stroke":182,"style":69},"#f0f4ff","var(--brand,#5b5cf0)",[55,184,186],{"x":76,"y":177,"style":185},"font-size:11.5px;font-weight:600;fill:var(--brand-strong,#4338ca);text-anchor:middle","Region",[48,188],{"x":82,"y":174,"width":83,"height":66,"rx":50,"fill":181,"stroke":182,"style":69},[55,190,191],{"x":86,"y":177,"style":185},"Revenue",[48,193],{"x":90,"y":174,"width":91,"height":66,"rx":50,"fill":181,"stroke":182,"style":69},[55,195,196],{"x":94,"y":177,"style":185},"Deals",[48,198],{"x":98,"y":174,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,200],{"x":106,"y":174,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,202],{"x":113,"y":174,"width":91,"height":66,"rx":50,"fill":203,"stroke":204,"style":69},"#fdefd8","var(--gold,#b4740a)",[55,206,208],{"x":116,"y":177,"style":207},"font-size:11.5px;font-weight:600;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Rep",[48,210],{"x":120,"y":174,"width":121,"height":66,"rx":50,"fill":203,"stroke":204,"style":69},[55,212,213],{"x":124,"y":177,"style":207},"Quota",[48,215],{"x":63,"y":216,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"170",[55,218,220],{"x":131,"y":219,"style":78},"190","4",[48,222],{"x":72,"y":216,"width":73,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,224,226],{"x":76,"y":219,"style":225},"font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","North",[48,228],{"x":82,"y":216,"width":83,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,230,231],{"x":86,"y":219,"style":225},"120500",[48,233],{"x":90,"y":216,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,235,236],{"x":94,"y":219,"style":225},"14",[48,238],{"x":98,"y":216,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,240],{"x":106,"y":216,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,242],{"x":113,"y":216,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,244,245],{"x":116,"y":219,"style":225},"Ana",[48,247],{"x":120,"y":216,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,249,250],{"x":124,"y":219,"style":225},"50000",[48,252],{"x":63,"y":253,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"200",[55,255,257],{"x":131,"y":256,"style":78},"220","5",[48,259],{"x":72,"y":253,"width":73,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,261,262],{"x":76,"y":256,"style":225},"South",[48,264],{"x":82,"y":253,"width":83,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,266,267],{"x":86,"y":256,"style":225},"98200",[48,269],{"x":90,"y":253,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,271,272],{"x":94,"y":256,"style":225},"11",[48,274],{"x":98,"y":253,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,276],{"x":106,"y":253,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,278],{"x":113,"y":253,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,280,281],{"x":116,"y":256,"style":225},"Ben",[48,283],{"x":120,"y":253,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,285,286],{"x":124,"y":256,"style":225},"45000",[48,288],{"x":63,"y":289,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"230",[55,291,293],{"x":131,"y":292,"style":78},"250","6",[48,295],{"x":72,"y":289,"width":73,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,297,298],{"x":76,"y":292,"style":225},"West",[48,300],{"x":82,"y":289,"width":83,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,302,303],{"x":86,"y":292,"style":225},"143900",[48,305],{"x":90,"y":289,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,307,308],{"x":94,"y":292,"style":225},"17",[48,310],{"x":98,"y":289,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,312],{"x":106,"y":289,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,314],{"x":113,"y":289,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,316],{"x":120,"y":289,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,318],{"x":63,"y":319,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"260",[55,321,323],{"x":131,"y":322,"style":78},"280","7",[48,325],{"x":72,"y":319,"width":73,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,327],{"x":82,"y":319,"width":83,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,329],{"x":90,"y":319,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,331],{"x":98,"y":319,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,333],{"x":106,"y":319,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,335],{"x":113,"y":319,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,337],{"x":120,"y":319,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,339],{"x":63,"y":340,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"290",[55,342,344],{"x":131,"y":343,"style":78},"310","8",[48,346],{"x":72,"y":340,"width":73,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,348],{"x":82,"y":340,"width":83,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,350],{"x":90,"y":340,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,352],{"x":98,"y":340,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,354],{"x":106,"y":340,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,356],{"x":113,"y":340,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,358],{"x":120,"y":340,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,360],{"x":63,"y":361,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"320",[55,363,365],{"x":131,"y":364,"style":78},"340","9",[48,367],{"x":72,"y":361,"width":73,"height":66,"rx":50,"fill":368,"stroke":369,"style":69},"#d9f4f1","var(--teal,#0f9488)",[55,371,373],{"x":76,"y":364,"style":372},"font-size:11.5px;font-weight:600;fill:var(--teal-ink,#0b6157);text-anchor:middle","Product",[48,375],{"x":82,"y":361,"width":83,"height":66,"rx":50,"fill":368,"stroke":369,"style":69},[55,377,378],{"x":86,"y":364,"style":372},"Units",[48,380],{"x":90,"y":361,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,382],{"x":98,"y":361,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,384],{"x":106,"y":361,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,386],{"x":113,"y":361,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,388],{"x":120,"y":361,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,390],{"x":63,"y":391,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"350",[55,393,395],{"x":131,"y":394,"style":78},"370","10",[48,397],{"x":72,"y":391,"width":73,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,399,400],{"x":76,"y":394,"style":225},"Widget",[48,402],{"x":82,"y":391,"width":83,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,404,405],{"x":86,"y":394,"style":225},"420",[48,407],{"x":90,"y":391,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,409],{"x":98,"y":391,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,411],{"x":106,"y":391,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,413],{"x":113,"y":391,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,415],{"x":120,"y":391,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,417],{"x":63,"y":418,"width":65,"height":66,"rx":50,"fill":67,"stroke":68,"style":69},"380",[55,420,272],{"x":131,"y":421,"style":78},"400",[48,423],{"x":72,"y":418,"width":73,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,425,426],{"x":76,"y":421,"style":225},"Gadget",[48,428],{"x":82,"y":418,"width":83,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,430,343],{"x":86,"y":421,"style":225},[48,432],{"x":90,"y":418,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,434],{"x":98,"y":418,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,436],{"x":106,"y":418,"width":99,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,438],{"x":113,"y":418,"width":91,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[48,440],{"x":120,"y":418,"width":121,"height":66,"rx":50,"fill":53,"stroke":68,"style":69},[55,442,445],{"x":57,"y":443,"style":444},"442","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","blank rows and columns are the only boundaries the file records",[447,448,450],"h2",{"id":449},"prerequisites","Prerequisites",[452,453,458],"pre",{"className":454,"code":455,"language":456,"meta":457,"style":457},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,459,460],{"__ignoreMap":457},[461,462,465,469,473,476],"span",{"class":463,"line":464},"line",1,[461,466,468],{"class":467},"sMTad","pip",[461,470,472],{"class":471},"srMev"," install",[461,474,475],{"class":471}," pandas",[461,477,478],{"class":471}," openpyxl\n",[10,480,481,482,485],{},"The examples read a workbook called ",[14,483,484],{},"summary.xlsx"," shaped like the diagram above. This snippet builds\nit, so every block on the page can be run as written:",[452,487,491],{"className":488,"code":489,"language":490,"meta":457,"style":457},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\n\nwb = Workbook()\nws = wb.active\nws.title = \"Summary\"\nws.append([\"Regional sales — March\"])\nws.append([])\nws.append([\"Region\", \"Revenue\", \"Deals\"])\nfor row in ([\"North\", 120500, 14], [\"South\", 98200, 11], [\"West\", 143900, 17]):\n    ws.append(row)\nws.append([])\nws.append([])\nws.append([\"Product\", \"Units\"])\nws.append([\"Widget\", 420])\nws.append([\"Gadget\", 310])\nfor ref, value in {\"F3\": \"Rep\", \"G3\": \"Quota\", \"F4\": \"Ana\", \"G4\": 50000,\n                   \"F5\": \"Ben\", \"G5\": 45000}.items():\n    ws[ref] = value\nwb.save(\"summary.xlsx\")\n","python",[14,492,493,509,516,528,539,550,562,568,589,646,652,657,662,677,691,705,759,782,793],{"__ignoreMap":457},[461,494,495,499,503,506],{"class":463,"line":464},[461,496,498],{"class":497},"s-kum","from",[461,500,502],{"class":501},"skGVy"," openpyxl ",[461,504,505],{"class":497},"import",[461,507,508],{"class":501}," Workbook\n",[461,510,512],{"class":463,"line":511},2,[461,513,515],{"emptyLinePlaceholder":514},true,"\n",[461,517,519,522,525],{"class":463,"line":518},3,[461,520,521],{"class":501},"wb ",[461,523,524],{"class":497},"=",[461,526,527],{"class":501}," Workbook()\n",[461,529,531,534,536],{"class":463,"line":530},4,[461,532,533],{"class":501},"ws ",[461,535,524],{"class":497},[461,537,538],{"class":501}," wb.active\n",[461,540,542,545,547],{"class":463,"line":541},5,[461,543,544],{"class":501},"ws.title ",[461,546,524],{"class":497},[461,548,549],{"class":471}," \"Summary\"\n",[461,551,553,556,559],{"class":463,"line":552},6,[461,554,555],{"class":501},"ws.append([",[461,557,558],{"class":471},"\"Regional sales — March\"",[461,560,561],{"class":501},"])\n",[461,563,565],{"class":463,"line":564},7,[461,566,567],{"class":501},"ws.append([])\n",[461,569,571,573,576,579,582,584,587],{"class":463,"line":570},8,[461,572,555],{"class":501},[461,574,575],{"class":471},"\"Region\"",[461,577,578],{"class":501},", ",[461,580,581],{"class":471},"\"Revenue\"",[461,583,578],{"class":501},[461,585,586],{"class":471},"\"Deals\"",[461,588,561],{"class":501},[461,590,592,595,598,601,604,607,609,612,614,616,619,622,624,626,628,630,632,635,637,639,641,643],{"class":463,"line":591},9,[461,593,594],{"class":497},"for",[461,596,597],{"class":501}," row ",[461,599,600],{"class":497},"in",[461,602,603],{"class":501}," ([",[461,605,606],{"class":471},"\"North\"",[461,608,578],{"class":501},[461,610,231],{"class":611},"sP0c6",[461,613,578],{"class":501},[461,615,236],{"class":611},[461,617,618],{"class":501},"], [",[461,620,621],{"class":471},"\"South\"",[461,623,578],{"class":501},[461,625,267],{"class":611},[461,627,578],{"class":501},[461,629,272],{"class":611},[461,631,618],{"class":501},[461,633,634],{"class":471},"\"West\"",[461,636,578],{"class":501},[461,638,303],{"class":611},[461,640,578],{"class":501},[461,642,308],{"class":611},[461,644,645],{"class":501},"]):\n",[461,647,649],{"class":463,"line":648},10,[461,650,651],{"class":501},"    ws.append(row)\n",[461,653,655],{"class":463,"line":654},11,[461,656,567],{"class":501},[461,658,660],{"class":463,"line":659},12,[461,661,567],{"class":501},[461,663,665,667,670,672,675],{"class":463,"line":664},13,[461,666,555],{"class":501},[461,668,669],{"class":471},"\"Product\"",[461,671,578],{"class":501},[461,673,674],{"class":471},"\"Units\"",[461,676,561],{"class":501},[461,678,680,682,685,687,689],{"class":463,"line":679},14,[461,681,555],{"class":501},[461,683,684],{"class":471},"\"Widget\"",[461,686,578],{"class":501},[461,688,405],{"class":611},[461,690,561],{"class":501},[461,692,694,696,699,701,703],{"class":463,"line":693},15,[461,695,555],{"class":501},[461,697,698],{"class":471},"\"Gadget\"",[461,700,578],{"class":501},[461,702,343],{"class":611},[461,704,561],{"class":501},[461,706,708,710,713,715,718,721,724,727,729,732,734,737,739,742,744,747,749,752,754,756],{"class":463,"line":707},16,[461,709,594],{"class":497},[461,711,712],{"class":501}," ref, value ",[461,714,600],{"class":497},[461,716,717],{"class":501}," {",[461,719,720],{"class":471},"\"F3\"",[461,722,723],{"class":501},": ",[461,725,726],{"class":471},"\"Rep\"",[461,728,578],{"class":501},[461,730,731],{"class":471},"\"G3\"",[461,733,723],{"class":501},[461,735,736],{"class":471},"\"Quota\"",[461,738,578],{"class":501},[461,740,741],{"class":471},"\"F4\"",[461,743,723],{"class":501},[461,745,746],{"class":471},"\"Ana\"",[461,748,578],{"class":501},[461,750,751],{"class":471},"\"G4\"",[461,753,723],{"class":501},[461,755,250],{"class":611},[461,757,758],{"class":501},",\n",[461,760,762,765,767,770,772,775,777,779],{"class":463,"line":761},17,[461,763,764],{"class":471},"                   \"F5\"",[461,766,723],{"class":501},[461,768,769],{"class":471},"\"Ben\"",[461,771,578],{"class":501},[461,773,774],{"class":471},"\"G5\"",[461,776,723],{"class":501},[461,778,286],{"class":611},[461,780,781],{"class":501},"}.items():\n",[461,783,785,788,790],{"class":463,"line":784},18,[461,786,787],{"class":501},"    ws[ref] ",[461,789,524],{"class":497},[461,791,792],{"class":501}," value\n",[461,794,796,799,802],{"class":463,"line":795},19,[461,797,798],{"class":501},"wb.save(",[461,800,801],{"class":471},"\"summary.xlsx\"",[461,803,804],{"class":501},")\n",[447,806,808],{"id":807},"step-1-read-the-sheet-without-a-header","Step 1 — Read the sheet without a header",[10,810,811,812,815,816,818,819,821],{},"The first move is always the same: stop pandas guessing. ",[14,813,814],{},"header=None"," returns every cell exactly\nwhere it sits, with integer row and column labels that map straight back to the worksheet (row label\n",[14,817,50],{}," is Excel row 1, column label ",[14,820,50],{}," is column A).",[452,823,825],{"className":488,"code":824,"language":490,"meta":457,"style":457},"import pandas as pd\n\nraw = pd.read_excel(\"summary.xlsx\", sheet_name=\"Summary\", header=None)\nprint(raw.shape)          # (11, 7)\nprint(raw.iloc[:6, :3])\n",[14,826,827,840,844,879,891],{"__ignoreMap":457},[461,828,829,831,834,837],{"class":463,"line":464},[461,830,505],{"class":497},[461,832,833],{"class":501}," pandas ",[461,835,836],{"class":497},"as",[461,838,839],{"class":501}," pd\n",[461,841,842],{"class":463,"line":511},[461,843,515],{"emptyLinePlaceholder":514},[461,845,846,849,851,854,856,858,862,864,867,869,872,874,877],{"class":463,"line":518},[461,847,848],{"class":501},"raw ",[461,850,524],{"class":497},[461,852,853],{"class":501}," pd.read_excel(",[461,855,801],{"class":471},[461,857,578],{"class":501},[461,859,861],{"class":860},"sa561","sheet_name",[461,863,524],{"class":497},[461,865,866],{"class":471},"\"Summary\"",[461,868,578],{"class":501},[461,870,871],{"class":860},"header",[461,873,524],{"class":497},[461,875,876],{"class":611},"None",[461,878,804],{"class":501},[461,880,881,884,887],{"class":463,"line":530},[461,882,883],{"class":611},"print",[461,885,886],{"class":501},"(raw.shape)          ",[461,888,890],{"class":889},"s-wDw","# (11, 7)\n",[461,892,893,895,898,900,903,905],{"class":463,"line":541},[461,894,883],{"class":611},[461,896,897],{"class":501},"(raw.iloc[:",[461,899,293],{"class":611},[461,901,902],{"class":501},", :",[461,904,178],{"class":611},[461,906,561],{"class":501},[10,908,909,910,913,914,917],{},"Everything that follows is plain DataFrame slicing on ",[14,911,912],{},"raw",". Nothing is re-read from disk, so you can\ntry the split several ways without paying for the parse again. If the worksheet is large, add\n",[14,915,916],{},"dtype=object"," so pandas does not coerce a column that mixes a header string with numbers below it —\nthough for small summary sheets the default is fine because each block is re-typed at the end.",[447,919,921],{"id":920},"step-2-split-stacked-tables-on-blank-rows","Step 2 — Split stacked tables on blank rows",[10,923,924,925,928],{},"When tables are stacked vertically, a row where every cell is empty marks the gap. A cumulative sum\nover that boolean gives each block its own number, and ",[14,926,927],{},"groupby"," hands the blocks back one at a time.",[452,930,932],{"className":488,"code":931,"language":490,"meta":457,"style":457},"def split_blocks(raw: pd.DataFrame, min_rows: int = 2) -> list[pd.DataFrame]:\n    \"\"\"Split a header=None frame into the tables separated by blank rows.\"\"\"\n    blank = raw.isna().all(axis=1)\n    block_id = blank.cumsum()\n    tables = []\n    for _, block in raw[~blank].groupby(block_id[~blank]):\n        block = block.dropna(axis=1, how=\"all\")\n        if len(block) \u003C min_rows:\n            continue                      # a title line, not a table\n        header, body = block.iloc[0], block.iloc[1:]\n        table = (\n            body.set_axis(header.astype(str).str.strip(), axis=1)\n                .rename_axis(None, axis=1)\n                .reset_index(drop=True)\n        )\n        tables.append(table)\n    return tables\n\nleft = raw.iloc[:, :3]\nfor table in split_blocks(left):\n    print(table, end=\"\\n\\n\")\n",[14,933,934,958,963,982,992,1002,1026,1054,1071,1079,1099,1109,1128,1145,1160,1165,1170,1178,1182,1197,1210],{"__ignoreMap":457},[461,935,936,939,943,946,949,952,955],{"class":463,"line":464},[461,937,938],{"class":497},"def",[461,940,942],{"class":941},"s_Opv"," split_blocks",[461,944,945],{"class":501},"(raw: pd.DataFrame, min_rows: ",[461,947,948],{"class":611},"int",[461,950,951],{"class":497}," =",[461,953,954],{"class":611}," 2",[461,956,957],{"class":501},") -> list[pd.DataFrame]:\n",[461,959,960],{"class":463,"line":511},[461,961,962],{"class":471},"    \"\"\"Split a header=None frame into the tables separated by blank rows.\"\"\"\n",[461,964,965,968,970,973,976,978,980],{"class":463,"line":518},[461,966,967],{"class":501},"    blank ",[461,969,524],{"class":497},[461,971,972],{"class":501}," raw.isna().all(",[461,974,975],{"class":860},"axis",[461,977,524],{"class":497},[461,979,132],{"class":611},[461,981,804],{"class":501},[461,983,984,987,989],{"class":463,"line":530},[461,985,986],{"class":501},"    block_id ",[461,988,524],{"class":497},[461,990,991],{"class":501}," blank.cumsum()\n",[461,993,994,997,999],{"class":463,"line":541},[461,995,996],{"class":501},"    tables ",[461,998,524],{"class":497},[461,1000,1001],{"class":501}," []\n",[461,1003,1004,1007,1010,1012,1015,1018,1021,1023],{"class":463,"line":552},[461,1005,1006],{"class":497},"    for",[461,1008,1009],{"class":501}," _, block ",[461,1011,600],{"class":497},[461,1013,1014],{"class":501}," raw[",[461,1016,1017],{"class":497},"~",[461,1019,1020],{"class":501},"blank].groupby(block_id[",[461,1022,1017],{"class":497},[461,1024,1025],{"class":501},"blank]):\n",[461,1027,1028,1031,1033,1036,1038,1040,1042,1044,1047,1049,1052],{"class":463,"line":564},[461,1029,1030],{"class":501},"        block ",[461,1032,524],{"class":497},[461,1034,1035],{"class":501}," block.dropna(",[461,1037,975],{"class":860},[461,1039,524],{"class":497},[461,1041,132],{"class":611},[461,1043,578],{"class":501},[461,1045,1046],{"class":860},"how",[461,1048,524],{"class":497},[461,1050,1051],{"class":471},"\"all\"",[461,1053,804],{"class":501},[461,1055,1056,1059,1062,1065,1068],{"class":463,"line":570},[461,1057,1058],{"class":497},"        if",[461,1060,1061],{"class":611}," len",[461,1063,1064],{"class":501},"(block) ",[461,1066,1067],{"class":497},"\u003C",[461,1069,1070],{"class":501}," min_rows:\n",[461,1072,1073,1076],{"class":463,"line":591},[461,1074,1075],{"class":497},"            continue",[461,1077,1078],{"class":889},"                      # a title line, not a table\n",[461,1080,1081,1084,1086,1089,1091,1094,1096],{"class":463,"line":648},[461,1082,1083],{"class":501},"        header, body ",[461,1085,524],{"class":497},[461,1087,1088],{"class":501}," block.iloc[",[461,1090,50],{"class":611},[461,1092,1093],{"class":501},"], block.iloc[",[461,1095,132],{"class":611},[461,1097,1098],{"class":501},":]\n",[461,1100,1101,1104,1106],{"class":463,"line":654},[461,1102,1103],{"class":501},"        table ",[461,1105,524],{"class":497},[461,1107,1108],{"class":501}," (\n",[461,1110,1111,1114,1117,1120,1122,1124,1126],{"class":463,"line":659},[461,1112,1113],{"class":501},"            body.set_axis(header.astype(",[461,1115,1116],{"class":611},"str",[461,1118,1119],{"class":501},").str.strip(), ",[461,1121,975],{"class":860},[461,1123,524],{"class":497},[461,1125,132],{"class":611},[461,1127,804],{"class":501},[461,1129,1130,1133,1135,1137,1139,1141,1143],{"class":463,"line":664},[461,1131,1132],{"class":501},"                .rename_axis(",[461,1134,876],{"class":611},[461,1136,578],{"class":501},[461,1138,975],{"class":860},[461,1140,524],{"class":497},[461,1142,132],{"class":611},[461,1144,804],{"class":501},[461,1146,1147,1150,1153,1155,1158],{"class":463,"line":679},[461,1148,1149],{"class":501},"                .reset_index(",[461,1151,1152],{"class":860},"drop",[461,1154,524],{"class":497},[461,1156,1157],{"class":611},"True",[461,1159,804],{"class":501},[461,1161,1162],{"class":463,"line":693},[461,1163,1164],{"class":501},"        )\n",[461,1166,1167],{"class":463,"line":707},[461,1168,1169],{"class":501},"        tables.append(table)\n",[461,1171,1172,1175],{"class":463,"line":761},[461,1173,1174],{"class":497},"    return",[461,1176,1177],{"class":501}," tables\n",[461,1179,1180],{"class":463,"line":784},[461,1181,515],{"emptyLinePlaceholder":514},[461,1183,1184,1187,1189,1192,1194],{"class":463,"line":795},[461,1185,1186],{"class":501},"left ",[461,1188,524],{"class":497},[461,1190,1191],{"class":501}," raw.iloc[:, :",[461,1193,178],{"class":611},[461,1195,1196],{"class":501},"]\n",[461,1198,1200,1202,1205,1207],{"class":463,"line":1199},20,[461,1201,594],{"class":497},[461,1203,1204],{"class":501}," table ",[461,1206,600],{"class":497},[461,1208,1209],{"class":501}," split_blocks(left):\n",[461,1211,1213,1216,1219,1222,1224,1227,1231,1233],{"class":463,"line":1212},21,[461,1214,1215],{"class":611},"    print",[461,1217,1218],{"class":501},"(table, ",[461,1220,1221],{"class":860},"end",[461,1223,524],{"class":497},[461,1225,1226],{"class":471},"\"",[461,1228,1230],{"class":1229},"sSjpA","\\n\\n",[461,1232,1226],{"class":471},[461,1234,804],{"class":501},[10,1236,1237,1238,1241,1242,1246,1247,1250,1251,1254,1255,1258],{},"Three details carry the weight here. ",[14,1239,1240],{},"dropna(axis=1, how=\"all\")"," trims the empty columns that belong\nto a ",[1243,1244,1245],"em",{},"different"," table, so the product block comes back two columns wide rather than three.\n",[14,1248,1249],{},"min_rows=2"," throws away single-line blocks such as the title in row 1, which would otherwise become a\ntable with a header and no body. And ",[14,1252,1253],{},"rename_axis(None, axis=1)"," removes the leftover row number that\n",[14,1256,1257],{},"set_axis"," would otherwise print above the column names.",[29,1260,38,1266,38,1269,38,1272,38,1276,38,1282,38,1287,38,1291,38,1296,38,1301,38,1304,38,1309,38,1312,38,1316,38,1319,38,1321,38,1325,38,1328,38,1332,38,1335,38,1338,38,1343,38,1346],{"viewBox":1261,"role":32,"ariaLabelledBy":1262,"xmlns":36,"style":1265},"0 0 760 136",[1263,1264],"mts-split-t","mts-split-d","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[40,1267,1268],{"id":1263},"Turning blank rows into table boundaries",[44,1270,1271],{"id":1264},"Mark rows where every cell is empty, take a cumulative sum to number the blocks, group by that number, then promote each block first row to the header.",[48,1273],{"x":50,"y":50,"width":1274,"height":1275,"fill":53},"760","136",[48,1277],{"x":63,"y":63,"width":1278,"height":1279,"rx":1280,"fill":67,"stroke":68,"style":1281},"150","66","12","stroke-width:2px",[55,1283,1286],{"x":1284,"y":64,"style":1285},"95","font-size:14px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","blank rows",[55,1288,1290],{"x":1284,"y":1289,"style":137},"72","isna().all(axis=1)",[463,1292],{"x1":1293,"y1":1294,"x2":1295,"y2":1294,"stroke":182,"style":1281},"175","53","204",[1297,1298],"polygon",{"points":1299,"fill":1300},"207,53 198,48 198,58","#5b5cf0",[48,1302],{"x":1303,"y":63,"width":1278,"height":1279,"rx":1280,"fill":181,"stroke":182,"style":1281},"210",[55,1305,1308],{"x":1306,"y":64,"style":1307},"285","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","block ids",[55,1310,1311],{"x":1306,"y":1289,"style":225},"blank.cumsum()",[463,1313],{"x1":1314,"y1":1294,"x2":1315,"y2":1294,"stroke":182,"style":1281},"365","394",[1297,1317],{"points":1318,"fill":1300},"397,53 388,48 388,58",[48,1320],{"x":421,"y":63,"width":1278,"height":1279,"rx":1280,"fill":181,"stroke":182,"style":1281},[55,1322,1324],{"x":1323,"y":64,"style":1307},"475","group",[55,1326,1327],{"x":1323,"y":1289,"style":225},"groupby(block_id)",[463,1329],{"x1":1330,"y1":1294,"x2":1331,"y2":1294,"stroke":182,"style":1281},"555","584",[1297,1333],{"points":1334,"fill":1300},"587,53 578,48 578,58",[48,1336],{"x":1337,"y":63,"width":1278,"height":1279,"rx":1280,"fill":368,"stroke":369,"style":1281},"590",[55,1339,1342],{"x":1340,"y":64,"style":1341},"665","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","promote header",[55,1344,1345],{"x":1340,"y":1289,"style":225},"first row → columns",[55,1347,1349],{"x":418,"y":1348,"style":444},"120","each blank row bumps the counter, so every table gets its own id",[447,1351,1353],{"id":1352},"step-3-handle-tables-that-sit-side-by-side","Step 3 — Handle tables that sit side by side",[10,1355,1356,1357,1360],{},"The rep quota table in columns F:G shares rows with the region table, so a blank-row split alone\nmerges them. Split on blank ",[1243,1358,1359],{},"columns"," first, then split each vertical strip on blank rows. The logic\nis the same cumulative-sum trick turned ninety degrees.",[452,1362,1364],{"className":488,"code":1363,"language":490,"meta":457,"style":457},"def split_columns(raw: pd.DataFrame) -> list[pd.DataFrame]:\n    blank_col = raw.isna().all(axis=0)\n    col_id = blank_col.cumsum()\n    return [raw.loc[:, ~blank_col & (col_id == g)] for g in col_id[~blank_col].unique()]\n\ntables = []\nfor strip in split_columns(raw):\n    tables.extend(split_blocks(strip))\n\nfor table in tables:\n    print(table.columns.tolist(), len(table))\n# ['Region', 'Revenue', 'Deals'] 3\n# ['Product', 'Units'] 2\n# ['Rep', 'Quota'] 2\n",[14,1365,1366,1376,1393,1403,1442,1446,1455,1467,1472,1476,1487,1500,1505,1510],{"__ignoreMap":457},[461,1367,1368,1370,1373],{"class":463,"line":464},[461,1369,938],{"class":497},[461,1371,1372],{"class":941}," split_columns",[461,1374,1375],{"class":501},"(raw: pd.DataFrame) -> list[pd.DataFrame]:\n",[461,1377,1378,1381,1383,1385,1387,1389,1391],{"class":463,"line":511},[461,1379,1380],{"class":501},"    blank_col ",[461,1382,524],{"class":497},[461,1384,972],{"class":501},[461,1386,975],{"class":860},[461,1388,524],{"class":497},[461,1390,50],{"class":611},[461,1392,804],{"class":501},[461,1394,1395,1398,1400],{"class":463,"line":518},[461,1396,1397],{"class":501},"    col_id ",[461,1399,524],{"class":497},[461,1401,1402],{"class":501}," blank_col.cumsum()\n",[461,1404,1405,1407,1410,1412,1415,1418,1421,1424,1427,1429,1432,1434,1437,1439],{"class":463,"line":530},[461,1406,1174],{"class":497},[461,1408,1409],{"class":501}," [raw.loc[:, ",[461,1411,1017],{"class":497},[461,1413,1414],{"class":501},"blank_col ",[461,1416,1417],{"class":497},"&",[461,1419,1420],{"class":501}," (col_id ",[461,1422,1423],{"class":497},"==",[461,1425,1426],{"class":501}," g)] ",[461,1428,594],{"class":497},[461,1430,1431],{"class":501}," g ",[461,1433,600],{"class":497},[461,1435,1436],{"class":501}," col_id[",[461,1438,1017],{"class":497},[461,1440,1441],{"class":501},"blank_col].unique()]\n",[461,1443,1444],{"class":463,"line":541},[461,1445,515],{"emptyLinePlaceholder":514},[461,1447,1448,1451,1453],{"class":463,"line":552},[461,1449,1450],{"class":501},"tables ",[461,1452,524],{"class":497},[461,1454,1001],{"class":501},[461,1456,1457,1459,1462,1464],{"class":463,"line":564},[461,1458,594],{"class":497},[461,1460,1461],{"class":501}," strip ",[461,1463,600],{"class":497},[461,1465,1466],{"class":501}," split_columns(raw):\n",[461,1468,1469],{"class":463,"line":570},[461,1470,1471],{"class":501},"    tables.extend(split_blocks(strip))\n",[461,1473,1474],{"class":463,"line":591},[461,1475,515],{"emptyLinePlaceholder":514},[461,1477,1478,1480,1482,1484],{"class":463,"line":648},[461,1479,594],{"class":497},[461,1481,1204],{"class":501},[461,1483,600],{"class":497},[461,1485,1486],{"class":501}," tables:\n",[461,1488,1489,1491,1494,1497],{"class":463,"line":654},[461,1490,1215],{"class":611},[461,1492,1493],{"class":501},"(table.columns.tolist(), ",[461,1495,1496],{"class":611},"len",[461,1498,1499],{"class":501},"(table))\n",[461,1501,1502],{"class":463,"line":659},[461,1503,1504],{"class":889},"# ['Region', 'Revenue', 'Deals'] 3\n",[461,1506,1507],{"class":463,"line":664},[461,1508,1509],{"class":889},"# ['Product', 'Units'] 2\n",[461,1511,1512],{"class":463,"line":679},[461,1513,1514],{"class":889},"# ['Rep', 'Quota'] 2\n",[10,1516,1517],{},"This breaks down in exactly one situation: a gap column that is not fully empty, because a note or a\nstray total was typed into it further down the sheet. When that happens the strips never separate, and\nthe anchor approach in the next step is the more robust choice.",[447,1519,1521],{"id":1520},"step-4-find-a-table-by-its-header-label-instead","Step 4 — Find a table by its header label instead",[10,1523,1524],{},"Blank-line splitting depends on the layout staying tidy. An anchor search depends only on a header\nlabel existing somewhere on the sheet, which is usually the one thing the person maintaining the file\nnever changes. Find the cell, then extend right and down until the first empty cell.",[452,1526,1528],{"className":488,"code":1527,"language":490,"meta":457,"style":457},"import numpy as np\n\ndef table_at_anchor(raw: pd.DataFrame, label: str) -> pd.DataFrame:\n    hits = np.argwhere(raw.eq(label).to_numpy())\n    if not len(hits):\n        raise LookupError(f\"no cell contains {label!r}\")\n    r, c = hits[0]\n    region = raw.iloc[r:, c:]\n    first_col = region.iloc[:, 0].isna().to_numpy()\n    height = first_col.argmax() if first_col.any() else len(region)\n    header_row = region.iloc[0].isna().to_numpy()\n    width = header_row.argmax() if header_row.any() else region.shape[1]\n    block = region.iloc[:height, :width]\n    return block.iloc[1:].set_axis(block.iloc[0], axis=1).rename_axis(None, axis=1).reset_index(drop=True)\n\nsales = table_at_anchor(raw, \"Region\")\nquota = table_at_anchor(raw, \"Rep\")\nproducts = table_at_anchor(raw, \"Product\")\nprint(sales, quota, products, sep=\"\\n\\n\")\n",[14,1529,1530,1542,1546,1561,1571,1584,1617,1631,1641,1656,1680,1694,1718,1728,1774,1778,1792,1805,1818],{"__ignoreMap":457},[461,1531,1532,1534,1537,1539],{"class":463,"line":464},[461,1533,505],{"class":497},[461,1535,1536],{"class":501}," numpy ",[461,1538,836],{"class":497},[461,1540,1541],{"class":501}," np\n",[461,1543,1544],{"class":463,"line":511},[461,1545,515],{"emptyLinePlaceholder":514},[461,1547,1548,1550,1553,1556,1558],{"class":463,"line":518},[461,1549,938],{"class":497},[461,1551,1552],{"class":941}," table_at_anchor",[461,1554,1555],{"class":501},"(raw: pd.DataFrame, label: ",[461,1557,1116],{"class":611},[461,1559,1560],{"class":501},") -> pd.DataFrame:\n",[461,1562,1563,1566,1568],{"class":463,"line":530},[461,1564,1565],{"class":501},"    hits ",[461,1567,524],{"class":497},[461,1569,1570],{"class":501}," np.argwhere(raw.eq(label).to_numpy())\n",[461,1572,1573,1576,1579,1581],{"class":463,"line":541},[461,1574,1575],{"class":497},"    if",[461,1577,1578],{"class":497}," not",[461,1580,1061],{"class":611},[461,1582,1583],{"class":501},"(hits):\n",[461,1585,1586,1589,1592,1595,1598,1601,1604,1607,1610,1613,1615],{"class":463,"line":552},[461,1587,1588],{"class":497},"        raise",[461,1590,1591],{"class":611}," LookupError",[461,1593,1594],{"class":501},"(",[461,1596,1597],{"class":497},"f",[461,1599,1600],{"class":471},"\"no cell contains ",[461,1602,1603],{"class":1229},"{",[461,1605,1606],{"class":501},"label",[461,1608,1609],{"class":497},"!r",[461,1611,1612],{"class":1229},"}",[461,1614,1226],{"class":471},[461,1616,804],{"class":501},[461,1618,1619,1622,1624,1627,1629],{"class":463,"line":564},[461,1620,1621],{"class":501},"    r, c ",[461,1623,524],{"class":497},[461,1625,1626],{"class":501}," hits[",[461,1628,50],{"class":611},[461,1630,1196],{"class":501},[461,1632,1633,1636,1638],{"class":463,"line":570},[461,1634,1635],{"class":501},"    region ",[461,1637,524],{"class":497},[461,1639,1640],{"class":501}," raw.iloc[r:, c:]\n",[461,1642,1643,1646,1648,1651,1653],{"class":463,"line":591},[461,1644,1645],{"class":501},"    first_col ",[461,1647,524],{"class":497},[461,1649,1650],{"class":501}," region.iloc[:, ",[461,1652,50],{"class":611},[461,1654,1655],{"class":501},"].isna().to_numpy()\n",[461,1657,1658,1661,1663,1666,1669,1672,1675,1677],{"class":463,"line":648},[461,1659,1660],{"class":501},"    height ",[461,1662,524],{"class":497},[461,1664,1665],{"class":501}," first_col.argmax() ",[461,1667,1668],{"class":497},"if",[461,1670,1671],{"class":501}," first_col.any() ",[461,1673,1674],{"class":497},"else",[461,1676,1061],{"class":611},[461,1678,1679],{"class":501},"(region)\n",[461,1681,1682,1685,1687,1690,1692],{"class":463,"line":654},[461,1683,1684],{"class":501},"    header_row ",[461,1686,524],{"class":497},[461,1688,1689],{"class":501}," region.iloc[",[461,1691,50],{"class":611},[461,1693,1655],{"class":501},[461,1695,1696,1699,1701,1704,1706,1709,1711,1714,1716],{"class":463,"line":659},[461,1697,1698],{"class":501},"    width ",[461,1700,524],{"class":497},[461,1702,1703],{"class":501}," header_row.argmax() ",[461,1705,1668],{"class":497},[461,1707,1708],{"class":501}," header_row.any() ",[461,1710,1674],{"class":497},[461,1712,1713],{"class":501}," region.shape[",[461,1715,132],{"class":611},[461,1717,1196],{"class":501},[461,1719,1720,1723,1725],{"class":463,"line":664},[461,1721,1722],{"class":501},"    block ",[461,1724,524],{"class":497},[461,1726,1727],{"class":501}," region.iloc[:height, :width]\n",[461,1729,1730,1732,1734,1736,1739,1741,1744,1746,1748,1750,1753,1755,1757,1759,1761,1763,1766,1768,1770,1772],{"class":463,"line":679},[461,1731,1174],{"class":497},[461,1733,1088],{"class":501},[461,1735,132],{"class":611},[461,1737,1738],{"class":501},":].set_axis(block.iloc[",[461,1740,50],{"class":611},[461,1742,1743],{"class":501},"], ",[461,1745,975],{"class":860},[461,1747,524],{"class":497},[461,1749,132],{"class":611},[461,1751,1752],{"class":501},").rename_axis(",[461,1754,876],{"class":611},[461,1756,578],{"class":501},[461,1758,975],{"class":860},[461,1760,524],{"class":497},[461,1762,132],{"class":611},[461,1764,1765],{"class":501},").reset_index(",[461,1767,1152],{"class":860},[461,1769,524],{"class":497},[461,1771,1157],{"class":611},[461,1773,804],{"class":501},[461,1775,1776],{"class":463,"line":693},[461,1777,515],{"emptyLinePlaceholder":514},[461,1779,1780,1783,1785,1788,1790],{"class":463,"line":707},[461,1781,1782],{"class":501},"sales ",[461,1784,524],{"class":497},[461,1786,1787],{"class":501}," table_at_anchor(raw, ",[461,1789,575],{"class":471},[461,1791,804],{"class":501},[461,1793,1794,1797,1799,1801,1803],{"class":463,"line":761},[461,1795,1796],{"class":501},"quota ",[461,1798,524],{"class":497},[461,1800,1787],{"class":501},[461,1802,726],{"class":471},[461,1804,804],{"class":501},[461,1806,1807,1810,1812,1814,1816],{"class":463,"line":784},[461,1808,1809],{"class":501},"products ",[461,1811,524],{"class":497},[461,1813,1787],{"class":501},[461,1815,669],{"class":471},[461,1817,804],{"class":501},[461,1819,1820,1822,1825,1828,1830,1832,1834,1836],{"class":463,"line":795},[461,1821,883],{"class":611},[461,1823,1824],{"class":501},"(sales, quota, products, ",[461,1826,1827],{"class":860},"sep",[461,1829,524],{"class":497},[461,1831,1226],{"class":471},[461,1833,1230],{"class":1229},[461,1835,1226],{"class":471},[461,1837,804],{"class":501},[10,1839,1840,1841,1843],{},"The height comes from the first column only, which is deliberate: a key column like ",[14,1842,186],{}," is never\nblank inside a table, while a numeric column might legitimately have gaps. If your anchor column can be\nempty mid-table, pass a different column index to measure the height from.",[29,1845,38,1850,38,1853,38,1856,38,1859,38,1863,38,1869,38,1872,38,1875,38,1879,38,1883,38,1886,38,1890,38,1894,38,1897,38,1900,38,1903,38,1906,38,1911,38,1915,38,1918,38,1921,38,1924],{"viewBox":1846,"role":32,"ariaLabelledBy":1847,"xmlns":36,"style":1265},"0 0 760 191",[1848,1849],"mts-methods-t","mts-methods-d",[40,1851,1852],{"id":1848},"Three ways to locate a table on a shared sheet",[44,1854,1855],{"id":1849},"Blank-line splitting needs a tidy layout, anchor search needs a stable header label, and named Excel tables need the author to have created a table object.",[48,1857],{"x":50,"y":50,"width":1274,"height":1858,"fill":53},"191",[48,1860],{"x":63,"y":63,"width":1861,"height":1862,"rx":236,"fill":181,"stroke":182,"style":1281},"226.7","127",[55,1864,1868],{"x":1865,"y":1866,"style":1867},"133.3","46","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","split on blanks",[463,1870],{"x1":65,"y1":72,"x2":1871,"y2":72,"stroke":182,"style":69},"230.7",[55,1873,1874],{"x":1865,"y":83,"style":225},"no labels needed",[55,1876,1878],{"x":1865,"y":1877,"style":225},"101","finds every table",[55,1880,1882],{"x":1865,"y":1881,"style":225},"124","breaks on stray notes",[48,1884],{"x":1885,"y":63,"width":1861,"height":1862,"rx":236,"fill":368,"stroke":369,"style":1281},"266.7",[55,1887,1889],{"x":418,"y":1866,"style":1888},"font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","anchor label",[463,1891],{"x1":1892,"y1":72,"x2":1893,"y2":72,"stroke":369,"style":69},"282.7","477.3",[55,1895,1896],{"x":418,"y":83,"style":225},"survives moved blocks",[55,1898,1899],{"x":418,"y":1877,"style":225},"one table per call",[55,1901,1902],{"x":418,"y":1881,"style":225},"needs a stable header",[48,1904],{"x":1905,"y":63,"width":1861,"height":1862,"rx":236,"fill":203,"stroke":204,"style":1281},"513.3",[55,1907,1910],{"x":1908,"y":1866,"style":1909},"626.7","font-size:13px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","named Excel table",[463,1912],{"x1":1913,"y1":72,"x2":1914,"y2":72,"stroke":204,"style":69},"529.3","724",[55,1916,1917],{"x":1908,"y":83,"style":225},"exact range stored",[55,1919,1920],{"x":1908,"y":1877,"style":225},"no guessing at all",[55,1922,1923],{"x":1908,"y":1881,"style":225},"only if author made one",[55,1925,1927],{"x":418,"y":1926,"style":444},"177","prefer the named table when it exists, the anchor when it does not",[447,1929,1931],{"id":1930},"step-5-read-named-excel-tables-directly","Step 5 — Read named Excel tables directly",[10,1933,1934,1935,1939,1940,1943,1944,1947,1948,1951],{},"If whoever built the workbook used ",[1936,1937,1938],"strong",{},"Insert → Table",", the ranges are already recorded in the file and\nthere is nothing to infer. openpyxl exposes them on each worksheet through ",[14,1941,1942],{},"ws.tables",", which maps the\ntable name to its ",[14,1945,1946],{},"A1:C4","-style reference. Note that this requires a normal (not ",[14,1949,1950],{},"read_only",") load,\nbecause read-only worksheets do not parse table parts.",[452,1953,1955],{"className":488,"code":1954,"language":490,"meta":457,"style":457},"from openpyxl import load_workbook\n\ndef read_named_tables(path: str) -> dict[str, pd.DataFrame]:\n    wb = load_workbook(path, data_only=True)\n    out = {}\n    for ws in wb.worksheets:\n        for name, ref in ws.tables.items():\n            rows = [[cell.value for cell in row] for row in ws[ref]]\n            out[name] = pd.DataFrame(rows[1:], columns=rows[0])\n    return out\n\ntables = read_named_tables(\"summary.xlsx\")\nprint(list(tables))       # [] for this file — no Insert → Table was used\n",[14,1956,1957,1968,1972,1992,2011,2021,2033,2046,2075,2101,2108,2112,2125],{"__ignoreMap":457},[461,1958,1959,1961,1963,1965],{"class":463,"line":464},[461,1960,498],{"class":497},[461,1962,502],{"class":501},[461,1964,505],{"class":497},[461,1966,1967],{"class":501}," load_workbook\n",[461,1969,1970],{"class":463,"line":511},[461,1971,515],{"emptyLinePlaceholder":514},[461,1973,1974,1976,1979,1982,1984,1987,1989],{"class":463,"line":518},[461,1975,938],{"class":497},[461,1977,1978],{"class":941}," read_named_tables",[461,1980,1981],{"class":501},"(path: ",[461,1983,1116],{"class":611},[461,1985,1986],{"class":501},") -> dict[",[461,1988,1116],{"class":611},[461,1990,1991],{"class":501},", pd.DataFrame]:\n",[461,1993,1994,1997,1999,2002,2005,2007,2009],{"class":463,"line":530},[461,1995,1996],{"class":501},"    wb ",[461,1998,524],{"class":497},[461,2000,2001],{"class":501}," load_workbook(path, ",[461,2003,2004],{"class":860},"data_only",[461,2006,524],{"class":497},[461,2008,1157],{"class":611},[461,2010,804],{"class":501},[461,2012,2013,2016,2018],{"class":463,"line":541},[461,2014,2015],{"class":501},"    out ",[461,2017,524],{"class":497},[461,2019,2020],{"class":501}," {}\n",[461,2022,2023,2025,2028,2030],{"class":463,"line":552},[461,2024,1006],{"class":497},[461,2026,2027],{"class":501}," ws ",[461,2029,600],{"class":497},[461,2031,2032],{"class":501}," wb.worksheets:\n",[461,2034,2035,2038,2041,2043],{"class":463,"line":564},[461,2036,2037],{"class":497},"        for",[461,2039,2040],{"class":501}," name, ref ",[461,2042,600],{"class":497},[461,2044,2045],{"class":501}," ws.tables.items():\n",[461,2047,2048,2051,2053,2056,2058,2061,2063,2066,2068,2070,2072],{"class":463,"line":570},[461,2049,2050],{"class":501},"            rows ",[461,2052,524],{"class":497},[461,2054,2055],{"class":501}," [[cell.value ",[461,2057,594],{"class":497},[461,2059,2060],{"class":501}," cell ",[461,2062,600],{"class":497},[461,2064,2065],{"class":501}," row] ",[461,2067,594],{"class":497},[461,2069,597],{"class":501},[461,2071,600],{"class":497},[461,2073,2074],{"class":501}," ws[ref]]\n",[461,2076,2077,2080,2082,2085,2087,2090,2092,2094,2097,2099],{"class":463,"line":591},[461,2078,2079],{"class":501},"            out[name] ",[461,2081,524],{"class":497},[461,2083,2084],{"class":501}," pd.DataFrame(rows[",[461,2086,132],{"class":611},[461,2088,2089],{"class":501},":], ",[461,2091,1359],{"class":860},[461,2093,524],{"class":497},[461,2095,2096],{"class":501},"rows[",[461,2098,50],{"class":611},[461,2100,561],{"class":501},[461,2102,2103,2105],{"class":463,"line":648},[461,2104,1174],{"class":497},[461,2106,2107],{"class":501}," out\n",[461,2109,2110],{"class":463,"line":654},[461,2111,515],{"emptyLinePlaceholder":514},[461,2113,2114,2116,2118,2121,2123],{"class":463,"line":659},[461,2115,1450],{"class":501},[461,2117,524],{"class":497},[461,2119,2120],{"class":501}," read_named_tables(",[461,2122,801],{"class":471},[461,2124,804],{"class":501},[461,2126,2127,2129,2131,2134,2137],{"class":463,"line":664},[461,2128,883],{"class":611},[461,2130,1594],{"class":501},[461,2132,2133],{"class":611},"list",[461,2135,2136],{"class":501},"(tables))       ",[461,2138,2139],{"class":889},"# [] for this file — no Insert → Table was used\n",[10,2141,2142,2145,2146,2150],{},[14,2143,2144],{},"data_only=True"," returns the cached results of any formulas inside the table rather than the formula\nstrings. The full treatment of this route, including totals rows and header options, is in\n",[23,2147,2149],{"href":2148},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fread-an-excel-table-by-name-into-pandas\u002F","Read an Excel Table by Name into pandas",".",[447,2152,2154],{"id":2153},"step-6-restore-the-types","Step 6 — Restore the types",[10,2156,2157,2158,2160],{},"Because the sheet was read with ",[14,2159,814],{},", each column initially mixed a header string with the\nvalues under it, and pandas typed it accordingly. Once the header has been promoted the column holds\nonly data, so a single conversion puts the numbers back.",[452,2162,2164],{"className":488,"code":2163,"language":490,"meta":457,"style":457},"sales = sales.astype({\"Revenue\": \"int64\", \"Deals\": \"int64\"})\nquota = quota.assign(Quota=pd.to_numeric(quota[\"Quota\"], errors=\"coerce\"))\nprint(sales.dtypes)\n",[14,2165,2166,2193,2224],{"__ignoreMap":457},[461,2167,2168,2170,2172,2175,2177,2179,2182,2184,2186,2188,2190],{"class":463,"line":464},[461,2169,1782],{"class":501},[461,2171,524],{"class":497},[461,2173,2174],{"class":501}," sales.astype({",[461,2176,581],{"class":471},[461,2178,723],{"class":501},[461,2180,2181],{"class":471},"\"int64\"",[461,2183,578],{"class":501},[461,2185,586],{"class":471},[461,2187,723],{"class":501},[461,2189,2181],{"class":471},[461,2191,2192],{"class":501},"})\n",[461,2194,2195,2197,2199,2202,2204,2206,2209,2211,2213,2216,2218,2221],{"class":463,"line":511},[461,2196,1796],{"class":501},[461,2198,524],{"class":497},[461,2200,2201],{"class":501}," quota.assign(",[461,2203,213],{"class":860},[461,2205,524],{"class":497},[461,2207,2208],{"class":501},"pd.to_numeric(quota[",[461,2210,736],{"class":471},[461,2212,1743],{"class":501},[461,2214,2215],{"class":860},"errors",[461,2217,524],{"class":497},[461,2219,2220],{"class":471},"\"coerce\"",[461,2222,2223],{"class":501},"))\n",[461,2225,2226,2228],{"class":463,"line":518},[461,2227,883],{"class":611},[461,2229,2230],{"class":501},"(sales.dtypes)\n",[10,2232,2233,2234,2237,2238,2241,2242,2245,2246,2248,2249,2253],{},"Use ",[14,2235,2236],{},"astype"," when you are sure the column is clean and want a loud failure if it is not, and\n",[14,2239,2240],{},"pd.to_numeric(..., errors=\"coerce\")"," when a stray ",[14,2243,2244],{},"\"n\u002Fa\""," should become ",[14,2247,20],{}," instead. The second is\nthe safer default for a hand-maintained sheet; ",[23,2250,2252],{"href":2251},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-excel-text-columns-to-numbers-with-pandas\u002F","Convert Excel Text Columns to Numbers with pandas","\nwalks through the cases where it matters.",[447,2255,2257],{"id":2256},"common-pitfalls","Common pitfalls",[2259,2260,2261,2277],"table",{},[2262,2263,2264],"thead",{},[2265,2266,2267,2271,2274],"tr",{},[2268,2269,2270],"th",{},"Symptom",[2268,2272,2273],{},"Cause",[2268,2275,2276],{},"Fix",[2278,2279,2280,2296,2313,2329,2340,2351],"tbody",{},[2265,2281,2282,2286,2289],{},[2283,2284,2285],"td",{},"Two tables come back merged",[2283,2287,2288],{},"The gap row holds a space or an invisible formatted value",[2283,2290,2291,2292,2295],{},"Run ",[14,2293,2294],{},"raw.replace(r\"^\\s*$\", np.nan, regex=True)"," before splitting",[2265,2297,2298,2301,2307],{},[2283,2299,2300],{},"Title becomes a one-row table",[2283,2302,2303,2306],{},[14,2304,2305],{},"min_rows"," too low",[2283,2308,2309,2310,2312],{},"Keep ",[14,2311,1249],{}," or higher",[2265,2314,2315,2321,2326],{},[2283,2316,2317,2318],{},"Header row reads ",[14,2319,2320],{},"Unnamed: 0",[2283,2322,2323,2325],{},[14,2324,814],{}," was not passed",[2283,2327,2328],{},"Always read the whole sheet raw first",[2265,2330,2331,2334,2337],{},[2283,2332,2333],{},"Anchor finds the wrong cell",[2283,2335,2336],{},"The label also appears in a note above",[2283,2338,2339],{},"Search a narrower slice, or match the second occurrence",[2265,2341,2342,2345,2348],{},[2283,2343,2344],{},"Numbers read as text after the split",[2283,2346,2347],{},"Column typed while it held the header",[2283,2349,2350],{},"Re-type each table as in Step 6",[2265,2352,2353,2356,2359],{},[2283,2354,2355],{},"Totals row included in the table",[2283,2357,2358],{},"The block has no blank row before the total",[2283,2360,2361,2362],{},"Filter it out, as in ",[23,2363,2365],{"href":2364},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fremove-subtotal-and-total-rows-from-an-excel-export\u002F","Remove Subtotal and Total Rows from an Excel Export",[10,2367,2368,2369,2372,2373,2375,2376,2379],{},"A cell that ",[1243,2370,2371],{},"looks"," empty but is not is the most common reason a split fails. Excel keeps a cell alive\nonce it has been formatted, and a cell containing a single space is indistinguishable on screen from an\nempty one. Normalising whitespace to ",[14,2374,20],{}," before you compute ",[14,2377,2378],{},"blank"," removes the whole class of problem.",[447,2381,2383],{"id":2382},"performance-and-scale-notes","Performance and scale notes",[10,2385,2386,2387,2390,2391,2394,2395,2398,2399,2150],{},"Everything here happens in memory after a single read, so the cost is the parse, not the splitting. A\nsummary sheet of a few hundred cells takes milliseconds either way. For a large worksheet, pass\n",[14,2388,2389],{},"usecols=\"A:G\""," and ",[14,2392,2393],{},"nrows"," to ",[14,2396,2397],{},"read_excel"," so pandas never parses the thousands of unrelated rows\nbelow the area you care about, or switch the engine to calamine as described in\n",[23,2400,2402],{"href":2401},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fspeed-up-pandas-excel-reads-with-the-calamine-engine\u002F","Speed Up pandas Excel Reads with the calamine Engine",[29,2404,38,2410,38,2413,38,2416,38,2420,38,2424,38,2427,38,2433,38,2438,38,2444,38,2449,38,2452,38,2456,38,2459,38,2463,38,2467,38,2470,38,2474,38,2477,38,2480,38,2483,38,2486,38,2490,38,2493,38,2497,38,2501],{"viewBox":2405,"role":32,"ariaLabelledBy":2406,"xmlns":36,"style":2409},"0 0 720 256",[2407,2408],"mts-cost-t","mts-cost-d","width:100%;max-width:720px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[40,2411,2412],{"id":2407},"Where the time goes when extracting tables from a sheet",[44,2414,2415],{"id":2408},"Parsing the workbook dominates. Splitting on blank rows and columns and searching for anchors are small in-memory operations by comparison.",[48,2417],{"x":50,"y":50,"width":2418,"height":2419,"fill":53},"720","256",[55,2421,2423],{"x":63,"y":66,"style":2422},"font-size:12px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:start","relative cost on a 50,000-cell sheet",[48,2425],{"x":63,"y":1866,"width":216,"height":2426,"rx":293,"fill":53,"stroke":68,"style":69},"28",[55,2428,2432],{"x":2429,"y":2430,"style":2431},"105","65","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:middle","read_excel parse",[48,2434],{"x":1295,"y":2435,"width":2436,"height":2437,"rx":293,"fill":67,"stroke":68,"style":69},"47","330","26",[48,2439],{"x":2440,"y":2441,"width":2442,"height":2443,"rx":257,"fill":203,"stroke":204,"style":69},"205","48","301.6","24",[55,2445,2448],{"x":2446,"y":2430,"style":2447},"546","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","dominant",[48,2450],{"x":63,"y":2451,"width":216,"height":2426,"rx":293,"fill":53,"stroke":68,"style":69},"90",[55,2453,2455],{"x":2429,"y":2454,"style":2431},"109","blank-line split",[48,2457],{"x":1295,"y":2458,"width":2436,"height":2437,"rx":293,"fill":67,"stroke":68,"style":69},"91",[48,2460],{"x":2440,"y":2461,"width":2462,"height":2443,"rx":257,"fill":181,"stroke":182,"style":69},"92","24.4",[55,2464,2466],{"x":2446,"y":2454,"style":2465},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","in memory",[48,2468],{"x":63,"y":2469,"width":216,"height":2426,"rx":293,"fill":53,"stroke":68,"style":69},"134",[55,2471,2473],{"x":2429,"y":2472,"style":2431},"153","anchor search",[48,2475],{"x":1295,"y":2476,"width":2436,"height":2437,"rx":293,"fill":67,"stroke":68,"style":69},"135",[48,2478],{"x":2440,"y":1275,"width":2479,"height":2443,"rx":257,"fill":368,"stroke":369,"style":69},"14.5",[55,2481,2466],{"x":2446,"y":2472,"style":2482},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start",[48,2484],{"x":63,"y":2485,"width":216,"height":2426,"rx":293,"fill":53,"stroke":68,"style":69},"178",[55,2487,2489],{"x":2429,"y":2488,"style":2431},"197","re-typing columns",[48,2491],{"x":1295,"y":2492,"width":2436,"height":2437,"rx":293,"fill":67,"stroke":68,"style":69},"179",[48,2494],{"x":2440,"y":2495,"width":2496,"height":2443,"rx":257,"fill":67,"stroke":68,"style":69},"180","11.2",[55,2498,2500],{"x":2446,"y":2488,"style":2499},"font-size:12px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:start","negligible",[55,2502,2505],{"x":2503,"y":2504,"style":444},"360","238","trim what you parse; the splitting itself is cheap",[10,2507,2508],{},"If the same workbook is processed on every run, read the sheet once, split it, and write each table to\nits own Parquet or CSV file. Downstream steps then load a clean, typed table directly rather than\nrepeating the inference.",[447,2510,2512],{"id":2511},"conclusion","Conclusion",[10,2514,2515,2516,2518],{},"Read the sheet with ",[14,2517,814],{},", then decide how to find the edges. Blank rows and columns work when\nthe layout is tidy; an anchor label works when the blocks move around but their headers stay put; and a\nnamed Excel table, when one exists, removes the guesswork entirely. Whichever route you take, re-type\neach table after its header is promoted, and normalise whitespace first so that invisible content\nnever glues two tables together.",[447,2520,2522],{"id":2521},"frequently-asked-questions","Frequently asked questions",[10,2524,2525,2528],{},[1936,2526,2527],{},"Can read_excel read a specific range like B3:D6 directly?","\nNot by cell reference, but usecols=\"B:D\" with skiprows=2 and nrows=4 selects the same rectangle. That works for a fixed layout; the splitting approaches on this page are for layouts that shift.",[10,2530,2531,2534],{},[1936,2532,2533],{},"How do I know how many tables a sheet contains?","\nRun the blank-row and blank-column split and count the blocks it returns. If the count changes between files, log it — a new or missing block usually means the workbook layout has changed.",[10,2536,2537,2540],{},[1936,2538,2539],{},"What if the tables have no blank row between them?","\nThen the only boundary is the header itself. Use the anchor search, giving the second table's header label explicitly, and cut the first table at the row where that label appears.",[10,2542,2543,2546],{},[1936,2544,2545],{},"Does this work with .xls and .ods files?","\nYes. Everything after the read is pandas slicing, so only the engine changes: xlrd for .xls and odf for .ods. The named-table route needs openpyxl and therefore .xlsx or .xlsm.",[447,2548,2550],{"id":2549},"related","Related",[2552,2553,2554,2561,2568,2575,2582],"ul",{},[2555,2556,2557,2558,2560],"li",{},"Up one level: ",[23,2559,26],{"href":25}," — the full set of techniques for human-shaped worksheets.",[2555,2562,2563,2567],{},[23,2564,2566],{"href":2565},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Ffind-the-header-row-in-an-excel-sheet-with-a-shifting-layout\u002F","Find the Header Row in an Excel Sheet with a Shifting Layout"," — the single-table version of the anchor search.",[2555,2569,2570,2574],{},[23,2571,2573],{"href":2572},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-excel-with-multi-row-headers-in-pandas\u002F","Read Excel with Multi-Row Headers in pandas"," — when each block's header spans two rows.",[2555,2576,2577,2581],{},[23,2578,2580],{"href":2579},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fskip-rows-and-set-header-when-reading-excel-with-pandas\u002F","Skip Rows and Set the Header When Reading Excel with pandas"," — the fixed-layout alternative.",[2555,2583,2584,2588],{},[23,2585,2587],{"href":2586},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fremove-blank-rows-from-excel-with-pandas\u002F","Remove Blank Rows From Excel With Pandas"," — defining blank when whitespace is involved.",[2590,2591,2592],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":457,"searchDepth":511,"depth":511,"links":2594},[2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606],{"id":449,"depth":511,"text":450},{"id":807,"depth":511,"text":808},{"id":920,"depth":511,"text":921},{"id":1352,"depth":511,"text":1353},{"id":1520,"depth":511,"text":1521},{"id":1930,"depth":511,"text":1931},{"id":2153,"depth":511,"text":2154},{"id":2256,"depth":511,"text":2257},{"id":2382,"depth":511,"text":2383},{"id":2511,"depth":511,"text":2512},{"id":2521,"depth":511,"text":2522},{"id":2549,"depth":511,"text":2550},"2026-09-17","Pull several stacked or side-by-side tables out of a single worksheet with pandas: split on blank rows and columns, find blocks by their header label, or read named Excel tables.","md",[2611,2613,2615,2617],{"q":2527,"a":2612},"Not by cell reference, but usecols=\"B:D\" with skiprows=2 and nrows=4 selects the same rectangle. That works for a fixed layout; the splitting approaches on this page are for layouts that shift.",{"q":2533,"a":2614},"Run the blank-row and blank-column split and count the blocks it returns. If the count changes between files, log it — a new or missing block usually means the workbook layout has changed.",{"q":2539,"a":2616},"Then the only boundary is the header itself. Use the anchor search, giving the second table's header label explicitly, and cut the first table at the row where that label appears.",{"q":2545,"a":2618},"Yes. Everything after the read is pandas slicing, so only the engine changes: xlrd for .xls and odf for .ods. The named-table route needs openpyxl and therefore .xlsx or .xlsm.","read multiple tables from one excel sheet",null,{"breadcrumb":2622},[2623,2626,2629],{"name":2624,"item":2625},"Home","\u002F",{"name":2627,"item":2628},"Advanced Data Transformation and Cleaning","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":26,"item":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet",{"title":2632,"description":2633},"Read Multiple Tables from One Excel Sheet with pandas","Extract several tables from one Excel worksheet in Python: split on blank rows and columns, locate blocks by anchor label, and read named Excel tables with openpyxl.","read-multiple-tables-from-one-excel-sheet","advanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet\u002Findex",[2637,2638],"messy-sheets","reading-excel","how-to","QouUJS38SNETH59JYyfoCmzL4Esh7_gNtkhIg43Cd-c",[2642,2645],{"title":2573,"path":2643,"stem":2644,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-excel-with-multi-row-headers-in-pandas","advanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-excel-with-multi-row-headers-in-pandas\u002Findex",{"title":2365,"path":2646,"stem":2647,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fremove-subtotal-and-total-rows-from-an-excel-export","advanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fremove-subtotal-and-total-rows-from-an-excel-export\u002Findex",[2649,2651],{"path":2650,"title":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets",{"path":2652,"title":2653},"\u002Fadvanced-data-transformation-and-cleaning","Advanced Data Transformation and Cleaning for Excel Automation",[],[2656,2659,2662,2665,2668,2671],{"path":2657,"title":2658,"kind":2639},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Ffreeze-header-row-in-excel-with-openpyxl","Freeze the Header Row in Excel with openpyxl",{"path":2660,"title":2661,"kind":2639},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fhandle-merged-cells-when-reading-excel-with-pandas","Handle Merged Cells When Reading Excel with pandas",{"path":2663,"title":2664,"kind":2639},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Funmerge-cells-and-fill-values-with-openpyxl","Unmerge Cells and Fill the Values with openpyxl",{"path":2666,"title":2667,"kind":2639},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fcombine-sheets-with-different-layouts-into-one-table","Combine Sheets with Different Layouts into One Table",{"path":2669,"title":2670,"kind":2639},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fextract-hyperlink-urls-from-excel-cells","Extract Hyperlink URLs from Excel Cells with openpyxl",{"path":2672,"title":2673,"kind":2639},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fextract-report-header-fields-from-an-excel-sheet","Extract Report Header Fields from an Excel Sheet with Python",1790972091078]