[{"data":1,"prerenderedAt":2163},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fforward-fill-blank-cells-in-grouped-excel-reports":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fforward-fill-blank-cells-in-grouped-excel-reports":2129,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fforward-fill-blank-cells-in-grouped-excel-reports":2138,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fforward-fill-blank-cells-in-grouped-excel-reports":2143,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fforward-fill-blank-cells-in-grouped-excel-reports":2144},{"id":4,"title":5,"body":6,"dateModified":2093,"datePublished":2093,"description":2094,"extension":2095,"faq":2096,"keyword":2105,"kind":2106,"meta":2107,"navigation":498,"path":2117,"seo":2118,"slug":2121,"stem":2122,"topics":2123,"type":2127,"__hash__":2128},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fforward-fill-blank-cells-in-grouped-excel-reports\u002Findex.md","Forward-Fill Blank Cells in Grouped Excel Reports with pandas",{"type":7,"value":8,"toc":2079},"minimark",[9,19,420,425,453,456,743,746,750,753,867,875,879,882,931,934,1071,1075,1078,1243,1249,1253,1259,1348,1356,1360,1363,1440,1450,1454,1457,1638,1644,1790,1794,1797,1959,1962,1966,2007,2011,2014,2018,2024,2030,2036,2042,2046,2075],[10,11,12,13,18],"p",{},"Reports exported from finance and ERP systems are laid out for reading, not for analysis. The region appears once, on the first row of its block, and the rows below are blank — sometimes because the cells are merged, sometimes because the report writer suppressed repeated values. Read that sheet with pandas and most rows have no region, so any grouping or filter by region is wrong. The fix is a forward fill, and it is easy to get subtly wrong. This guide, part of ",[14,15,17],"a",{"href":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002F","Handling Missing Data in Excel Reports",", fills labels down safely, including multi-level hierarchies, merged cells and total rows.",[20,21,29,30,29,34,29,38,29,46,29,55,29,59,29,66,29,70,29,74,29,78,29,82,29,86,29,90,29,94,29,99,29,101,29,105,29,107,29,110,29,112,29,115,29,117,29,120,29,123,29,127,29,129,29,133,29,135,29,138,29,140,29,143,29,145,29,148,29,151,29,155,29,157,29,159,29,161,29,164,29,166,29,169,29,172,29,175,29,177,29,179,29,182,29,184,29,186,29,188,29,191,29,194,29,198,29,200,29,203,29,205,29,208,29,210,29,212,29,214,29,216,29,219,29,223,29,225,29,227,29,229,29,231,29,233,29,236,29,239,29,242,29,245,29,248,29,251,29,254,29,257,29,260,29,263,29,265,29,268,29,270,29,272,29,274,29,276,29,278,29,280,29,282,29,284,29,286,29,288,29,290,29,292,29,294,29,296,29,298,29,300,29,302,29,304,29,306,29,308,29,310,29,312,29,314,29,316,29,318,29,320,29,322,29,324,29,326,29,328,29,330,29,332,29,334,29,336,29,338,29,340,29,342,29,344,29,346,29,348,29,350,29,352,29,354,29,356,29,358,29,360,29,362,29,364,29,366,29,368,29,370,29,372,29,374,29,376,29,378,29,380,29,382,29,384,29,390,29,394,29,401,29,406,29,414],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"4 0 752 248","img",[25,26],"ff-overview-t","ff-overview-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"A grouped report becomes one complete row per record",[35,36,37],"desc",{"id":26},"In the report, North appears only on the first of three rows, Leeds only on the first of two, and South on the first of two. After filling the label columns down, every row carries its region and city, while the product and sales values are unchanged.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"4","0","752","248","#ffffff",[39,47],{"x":48,"y":49,"width":50,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"20.0","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[39,56],{"x":57,"y":49,"width":58,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"52.0","57.6",[60,61,65],"text",{"x":62,"y":63,"style":64},"80.8","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[39,67],{"x":68,"y":49,"width":69,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"109.6","52.7",[60,71,73],{"x":72,"y":63,"style":64},"136.0","B",[39,75],{"x":76,"y":49,"width":77,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"162.3","62.8",[60,79,81],{"x":80,"y":63,"style":64},"193.7","C",[39,83],{"x":84,"y":49,"width":85,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"225.2","49.7",[60,87,89],{"x":88,"y":63,"style":64},"250.0","D",[39,91],{"x":48,"y":92,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},"64.0","28.0",[60,95,98],{"x":96,"y":97,"style":64},"36.0","83.0","1",[39,100],{"x":57,"y":92,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,102,104],{"x":62,"y":97,"style":103},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Region",[39,106],{"x":68,"y":92,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,108,109],{"x":72,"y":97,"style":103},"City",[39,111],{"x":76,"y":92,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,113,114],{"x":80,"y":97,"style":103},"Product",[39,116],{"x":84,"y":92,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,118,119],{"x":88,"y":97,"style":103},"Sales",[39,121],{"x":48,"y":122,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},"92.0",[60,124,126],{"x":96,"y":125,"style":64},"111.0","2",[39,128],{"x":57,"y":122,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,130,132],{"x":62,"y":125,"style":131},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","North",[39,134],{"x":68,"y":122,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,136,137],{"x":72,"y":125,"style":131},"Leeds",[39,139],{"x":76,"y":122,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,141,142],{"x":80,"y":125,"style":131},"Tea",[39,144],{"x":84,"y":122,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,146,147],{"x":88,"y":125,"style":131},"10",[39,149],{"x":48,"y":150,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},"120.0",[60,152,154],{"x":96,"y":153,"style":64},"139.0","3",[39,156],{"x":57,"y":150,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[39,158],{"x":68,"y":150,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[39,160],{"x":76,"y":150,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,162,163],{"x":80,"y":153,"style":131},"Coffee",[39,165],{"x":84,"y":150,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,167,168],{"x":88,"y":153,"style":131},"12",[39,170],{"x":48,"y":171,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},"148.0",[60,173,41],{"x":96,"y":174,"style":64},"167.0",[39,176],{"x":57,"y":171,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[39,178],{"x":68,"y":171,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,180,181],{"x":72,"y":174,"style":131},"York",[39,183],{"x":76,"y":171,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,185,142],{"x":80,"y":174,"style":131},[39,187],{"x":84,"y":171,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,189,190],{"x":88,"y":174,"style":131},"7",[39,192],{"x":48,"y":193,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},"176.0",[60,195,197],{"x":96,"y":196,"style":64},"195.0","5",[39,199],{"x":57,"y":193,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,201,202],{"x":62,"y":196,"style":131},"South",[39,204],{"x":68,"y":193,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,206,207],{"x":72,"y":196,"style":131},"Bath",[39,209],{"x":76,"y":193,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,211,142],{"x":80,"y":196,"style":131},[39,213],{"x":84,"y":193,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,215,197],{"x":88,"y":196,"style":131},[39,217],{"x":48,"y":218,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},"204.0",[60,220,222],{"x":96,"y":221,"style":64},"223.0","6",[39,224],{"x":57,"y":218,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[39,226],{"x":68,"y":218,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[39,228],{"x":76,"y":218,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,230,163],{"x":80,"y":221,"style":131},[39,232],{"x":84,"y":218,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,234,235],{"x":88,"y":221,"style":131},"9",[39,237],{"x":238,"y":49,"width":50,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"485.2",[39,240],{"x":241,"y":49,"width":58,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"517.2",[60,243,65],{"x":244,"y":63,"style":64},"546.0",[39,246],{"x":247,"y":49,"width":69,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"574.8",[60,249,73],{"x":250,"y":63,"style":64},"601.1",[39,252],{"x":253,"y":49,"width":77,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"627.5",[60,255,81],{"x":256,"y":63,"style":64},"658.9",[39,258],{"x":259,"y":49,"width":85,"height":51,"rx":42,"fill":52,"stroke":53,"style":54},"690.3",[60,261,89],{"x":262,"y":63,"style":64},"715.2",[39,264],{"x":238,"y":92,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},[60,266,98],{"x":267,"y":97,"style":64},"501.2",[39,269],{"x":241,"y":92,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,271,104],{"x":244,"y":97,"style":103},[39,273],{"x":247,"y":92,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,275,109],{"x":250,"y":97,"style":103},[39,277],{"x":253,"y":92,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,279,114],{"x":256,"y":97,"style":103},[39,281],{"x":259,"y":92,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,283,119],{"x":262,"y":97,"style":103},[39,285],{"x":238,"y":122,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},[60,287,126],{"x":267,"y":125,"style":64},[39,289],{"x":241,"y":122,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,291,132],{"x":244,"y":125,"style":131},[39,293],{"x":247,"y":122,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,295,137],{"x":250,"y":125,"style":131},[39,297],{"x":253,"y":122,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,299,142],{"x":256,"y":125,"style":131},[39,301],{"x":259,"y":122,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,303,147],{"x":262,"y":125,"style":131},[39,305],{"x":238,"y":150,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},[60,307,154],{"x":267,"y":153,"style":64},[39,309],{"x":241,"y":150,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,311,132],{"x":244,"y":153,"style":131},[39,313],{"x":247,"y":150,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,315,137],{"x":250,"y":153,"style":131},[39,317],{"x":253,"y":150,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,319,163],{"x":256,"y":153,"style":131},[39,321],{"x":259,"y":150,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,323,168],{"x":262,"y":153,"style":131},[39,325],{"x":238,"y":171,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},[60,327,41],{"x":267,"y":174,"style":64},[39,329],{"x":241,"y":171,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,331,132],{"x":244,"y":174,"style":131},[39,333],{"x":247,"y":171,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,335,181],{"x":250,"y":174,"style":131},[39,337],{"x":253,"y":171,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,339,142],{"x":256,"y":174,"style":131},[39,341],{"x":259,"y":171,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,343,190],{"x":262,"y":174,"style":131},[39,345],{"x":238,"y":193,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},[60,347,197],{"x":267,"y":196,"style":64},[39,349],{"x":241,"y":193,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,351,202],{"x":244,"y":196,"style":131},[39,353],{"x":247,"y":193,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,355,207],{"x":250,"y":196,"style":131},[39,357],{"x":253,"y":193,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,359,142],{"x":256,"y":196,"style":131},[39,361],{"x":259,"y":193,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,363,197],{"x":262,"y":196,"style":131},[39,365],{"x":238,"y":218,"width":50,"height":93,"rx":42,"fill":52,"stroke":53,"style":54},[60,367,222],{"x":267,"y":221,"style":64},[39,369],{"x":241,"y":218,"width":58,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,371,202],{"x":244,"y":221,"style":131},[39,373],{"x":247,"y":218,"width":69,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,375,207],{"x":250,"y":221,"style":131},[39,377],{"x":253,"y":218,"width":77,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,379,163],{"x":256,"y":221,"style":131},[39,381],{"x":259,"y":218,"width":85,"height":93,"rx":42,"fill":45,"stroke":53,"style":54},[60,383,235],{"x":262,"y":221,"style":131},[60,385,389],{"x":386,"y":387,"style":388},"147.4","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[60,391,393],{"x":392,"y":387,"style":388},"612.6","after",[395,396],"line",{"x1":397,"y1":72,"x2":398,"y2":72,"stroke":399,"style":400},"284.8","471.2","var(--brand,#5b5cf0)","stroke-width:2px",[402,403],"polygon",{"points":404,"fill":405},"475.2,136.0 466.2,141.0 466.2,131.0","#5b5cf0",[39,407],{"x":408,"y":409,"width":410,"height":411,"rx":412,"fill":45,"stroke":53,"style":413},"311.8","103.0","136.3","22.0","11","stroke-width:1.2px",[60,415,419],{"x":416,"y":417,"style":418},"380.0","118.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","fill labels down",[421,422,424],"h2",{"id":423},"prerequisites","Prerequisites",[426,427,432],"pre",{"className":428,"code":429,"language":430,"meta":431,"style":431},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[433,434,435],"code",{"__ignoreMap":431},[436,437,439,443,447,450],"span",{"class":395,"line":438},1,[436,440,442],{"class":441},"sMTad","pip",[436,444,446],{"class":445},"srMev"," install",[436,448,449],{"class":445}," pandas",[436,451,452],{"class":445}," openpyxl\n",[10,454,455],{},"A sample report with a merged region cell, a city missing at the start of a group, and a total row:",[426,457,461],{"className":458,"code":459,"language":460,"meta":431,"style":431},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom openpyxl import Workbook\n\nwb = Workbook()\nws = wb.active\nfor row in [[\"Region\", \"City\", \"Product\", \"Sales\"],\n            [\"North\", \"Leeds\", \"Tea\", 10], [None, None, \"Coffee\", 12], [None, \"York\", \"Tea\", 7],\n            [\"South\", None, \"Tea\", 5], [None, \"Bath\", \"Coffee\", 9],\n            [\"Total\", None, None, 43]]:\n    ws.append(row)\nws.merge_cells(\"A2:A4\")\nwb.save(\"grouped.xlsx\")\n\ndf = pd.read_excel(\"grouped.xlsx\")\nprint(df)\n","python",[433,462,463,479,493,500,512,523,560,622,661,685,691,703,714,719,734],{"__ignoreMap":431},[436,464,465,469,473,476],{"class":395,"line":438},[436,466,468],{"class":467},"s-kum","import",[436,470,472],{"class":471},"skGVy"," pandas ",[436,474,475],{"class":467},"as",[436,477,478],{"class":471}," pd\n",[436,480,482,485,488,490],{"class":395,"line":481},2,[436,483,484],{"class":467},"from",[436,486,487],{"class":471}," openpyxl ",[436,489,468],{"class":467},[436,491,492],{"class":471}," Workbook\n",[436,494,496],{"class":395,"line":495},3,[436,497,499],{"emptyLinePlaceholder":498},true,"\n",[436,501,503,506,509],{"class":395,"line":502},4,[436,504,505],{"class":471},"wb ",[436,507,508],{"class":467},"=",[436,510,511],{"class":471}," Workbook()\n",[436,513,515,518,520],{"class":395,"line":514},5,[436,516,517],{"class":471},"ws ",[436,519,508],{"class":467},[436,521,522],{"class":471}," wb.active\n",[436,524,526,529,532,535,538,541,544,547,549,552,554,557],{"class":395,"line":525},6,[436,527,528],{"class":467},"for",[436,530,531],{"class":471}," row ",[436,533,534],{"class":467},"in",[436,536,537],{"class":471}," [[",[436,539,540],{"class":445},"\"Region\"",[436,542,543],{"class":471},", ",[436,545,546],{"class":445},"\"City\"",[436,548,543],{"class":471},[436,550,551],{"class":445},"\"Product\"",[436,553,543],{"class":471},[436,555,556],{"class":445},"\"Sales\"",[436,558,559],{"class":471},"],\n",[436,561,563,566,569,571,574,576,579,581,584,587,590,592,594,596,599,601,603,605,607,609,612,614,616,618,620],{"class":395,"line":562},7,[436,564,565],{"class":471},"            [",[436,567,568],{"class":445},"\"North\"",[436,570,543],{"class":471},[436,572,573],{"class":445},"\"Leeds\"",[436,575,543],{"class":471},[436,577,578],{"class":445},"\"Tea\"",[436,580,543],{"class":471},[436,582,147],{"class":583},"sP0c6",[436,585,586],{"class":471},"], [",[436,588,589],{"class":583},"None",[436,591,543],{"class":471},[436,593,589],{"class":583},[436,595,543],{"class":471},[436,597,598],{"class":445},"\"Coffee\"",[436,600,543],{"class":471},[436,602,168],{"class":583},[436,604,586],{"class":471},[436,606,589],{"class":583},[436,608,543],{"class":471},[436,610,611],{"class":445},"\"York\"",[436,613,543],{"class":471},[436,615,578],{"class":445},[436,617,543],{"class":471},[436,619,190],{"class":583},[436,621,559],{"class":471},[436,623,625,627,630,632,634,636,638,640,642,644,646,648,651,653,655,657,659],{"class":395,"line":624},8,[436,626,565],{"class":471},[436,628,629],{"class":445},"\"South\"",[436,631,543],{"class":471},[436,633,589],{"class":583},[436,635,543],{"class":471},[436,637,578],{"class":445},[436,639,543],{"class":471},[436,641,197],{"class":583},[436,643,586],{"class":471},[436,645,589],{"class":583},[436,647,543],{"class":471},[436,649,650],{"class":445},"\"Bath\"",[436,652,543],{"class":471},[436,654,598],{"class":445},[436,656,543],{"class":471},[436,658,235],{"class":583},[436,660,559],{"class":471},[436,662,664,666,669,671,673,675,677,679,682],{"class":395,"line":663},9,[436,665,565],{"class":471},[436,667,668],{"class":445},"\"Total\"",[436,670,543],{"class":471},[436,672,589],{"class":583},[436,674,543],{"class":471},[436,676,589],{"class":583},[436,678,543],{"class":471},[436,680,681],{"class":583},"43",[436,683,684],{"class":471},"]]:\n",[436,686,688],{"class":395,"line":687},10,[436,689,690],{"class":471},"    ws.append(row)\n",[436,692,694,697,700],{"class":395,"line":693},11,[436,695,696],{"class":471},"ws.merge_cells(",[436,698,699],{"class":445},"\"A2:A4\"",[436,701,702],{"class":471},")\n",[436,704,706,709,712],{"class":395,"line":705},12,[436,707,708],{"class":471},"wb.save(",[436,710,711],{"class":445},"\"grouped.xlsx\"",[436,713,702],{"class":471},[436,715,717],{"class":395,"line":716},13,[436,718,499],{"emptyLinePlaceholder":498},[436,720,722,725,727,730,732],{"class":395,"line":721},14,[436,723,724],{"class":471},"df ",[436,726,508],{"class":467},[436,728,729],{"class":471}," pd.read_excel(",[436,731,711],{"class":445},[436,733,702],{"class":471},[436,735,737,740],{"class":395,"line":736},15,[436,738,739],{"class":583},"print",[436,741,742],{"class":471},"(df)\n",[10,744,745],{},"The merged North cell is read as a value in the first row and blanks below — exactly like a suppressed repeat — so one technique handles both. The South group has no city on its first row: a real gap in the source that the fill must not paper over.",[421,747,749],{"id":748},"step-1-remove-total-rows-first","Step 1 — Remove total rows first",[10,751,752],{},"Total and subtotal rows sit inside the same columns as the labels. Fill before removing them and \"Total\" becomes a region that is carried down, or the last region is carried into the total row. Drop them first:",[426,754,756],{"className":458,"code":755,"language":460,"meta":431,"style":431},"raw = df.copy()\nis_total = df[\"Region\"].astype(\"string\").str.contains(r\"^\\s*(?:sub)?total\\b\", case=False, na=False)\ndf = df[~is_total].reset_index(drop=True)\n",[433,757,758,768,843],{"__ignoreMap":431},[436,759,760,763,765],{"class":395,"line":438},[436,761,762],{"class":471},"raw ",[436,764,508],{"class":467},[436,766,767],{"class":471}," df.copy()\n",[436,769,770,773,775,778,780,783,786,789,792,795,798,801,804,807,810,813,816,819,821,823,827,829,832,834,837,839,841],{"class":395,"line":481},[436,771,772],{"class":471},"is_total ",[436,774,508],{"class":467},[436,776,777],{"class":471}," df[",[436,779,540],{"class":445},[436,781,782],{"class":471},"].astype(",[436,784,785],{"class":445},"\"string\"",[436,787,788],{"class":471},").str.contains(",[436,790,791],{"class":467},"r",[436,793,794],{"class":445},"\"",[436,796,797],{"class":583},"^\\s",[436,799,800],{"class":467},"*",[436,802,803],{"class":583},"(?:",[436,805,806],{"class":445},"sub",[436,808,809],{"class":583},")",[436,811,812],{"class":467},"?",[436,814,815],{"class":445},"total",[436,817,818],{"class":583},"\\b",[436,820,794],{"class":445},[436,822,543],{"class":471},[436,824,826],{"class":825},"sa561","case",[436,828,508],{"class":467},[436,830,831],{"class":583},"False",[436,833,543],{"class":471},[436,835,836],{"class":825},"na",[436,838,508],{"class":467},[436,840,831],{"class":583},[436,842,702],{"class":471},[436,844,845,847,849,851,854,857,860,862,865],{"class":395,"line":495},[436,846,724],{"class":471},[436,848,508],{"class":467},[436,850,777],{"class":471},[436,852,853],{"class":467},"~",[436,855,856],{"class":471},"is_total].reset_index(",[436,858,859],{"class":825},"drop",[436,861,508],{"class":467},[436,863,864],{"class":583},"True",[436,866,702],{"class":471},[10,868,869,870,874],{},"Totals in exports take many shapes; ",[14,871,873],{"href":872},"\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"," covers detecting them by label, by position and by checking the sums.",[421,876,878],{"id":877},"step-2-the-naive-fill-and-why-it-is-wrong","Step 2 — The naive fill, and why it is wrong",[10,880,881],{},"The obvious approach fills every label column in one call:",[426,883,885],{"className":458,"code":884,"language":460,"meta":431,"style":431},"naive = df.copy()\nnaive[[\"Region\", \"City\"]] = naive[[\"Region\", \"City\"]].ffill()\nprint(naive)\n",[433,886,887,896,924],{"__ignoreMap":431},[436,888,889,892,894],{"class":395,"line":438},[436,890,891],{"class":471},"naive ",[436,893,508],{"class":467},[436,895,767],{"class":471},[436,897,898,901,903,905,907,910,912,915,917,919,921],{"class":395,"line":481},[436,899,900],{"class":471},"naive[[",[436,902,540],{"class":445},[436,904,543],{"class":471},[436,906,546],{"class":445},[436,908,909],{"class":471},"]] ",[436,911,508],{"class":467},[436,913,914],{"class":471}," naive[[",[436,916,540],{"class":445},[436,918,543],{"class":471},[436,920,546],{"class":445},[436,922,923],{"class":471},"]].ffill()\n",[436,925,926,928],{"class":395,"line":495},[436,927,739],{"class":583},[436,929,930],{"class":471},"(naive)\n",[10,932,933],{},"Look at the first South row: it now says York. The city column was blank because the source omitted it, and a plain forward fill does not know that a new region started, so it carries the last North city across the boundary. Nothing fails, the frame has no gaps, and a report by city now counts a South sale under York.",[20,935,29,940,29,943,29,946,29,949,29,957,29,968,29,974,29,978,29,983,29,991,29,995,29,1002,29,1006,29,1011,29,1015,29,1020,29,1024,29,1030,29,1035,29,1039,29,1042,29,1047,29,1050,29,1055,29,1058,29,1063,29,1066],{"viewBox":936,"role":23,"ariaLabelledBy":937,"xmlns":27,"style":28},"4 0 752 233",[938,939],"ff-naive-t","ff-naive-d",[31,941,942],{"id":938},"A plain fill crosses group boundaries, a grouped fill does not",[35,944,945],{"id":939},"With a plain forward fill on both columns, the first South row inherits York from the North group. Filling Region first and then City within each region leaves that cell empty, so the genuine gap stays visible.",[39,947],{"x":41,"y":42,"width":43,"height":948,"fill":45},"233",[39,950],{"x":48,"y":951,"width":952,"height":953,"rx":954,"fill":955,"stroke":956,"style":400},"16.0","351.0","201.0","14","#fdefd8","var(--gold,#b4740a)",[958,959,961],"g",{"transform":960},"translate(182.5,29.0) scale(1.083)",[962,963],"path",{"d":964,"fill":965,"stroke":966,"style":967},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","none","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[60,969,973],{"x":970,"y":971,"style":972},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","ffill() on both columns",[395,975],{"x1":96,"y1":976,"x2":977,"y2":976,"stroke":956,"style":54},"91.0","355.0",[60,979,982],{"x":980,"y":125,"style":981},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","fills North and Leeds correctly",[958,984,986],{"transform":985},"translate(38.5,99.5) scale(0.625)",[962,987],{"d":988,"fill":965,"stroke":989,"style":990},"M5 12.5l4.5 4.5L19 7","var(--teal,#0f9488)","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[60,992,994],{"x":980,"y":993,"style":981},"137.0","South row gets York",[958,996,998],{"transform":997},"translate(38.5,125.5) scale(0.625)",[962,999],{"d":1000,"fill":965,"stroke":1001,"style":990},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[60,1003,1005],{"x":980,"y":1004,"style":981},"163.0","the source gap disappears",[958,1007,1009],{"transform":1008},"translate(38.5,151.5) scale(0.625)",[962,1010],{"d":1000,"fill":965,"stroke":1001,"style":990},[60,1012,1014],{"x":980,"y":1013,"style":981},"189.0","no error or warning",[958,1016,1018],{"transform":1017},"translate(38.5,177.5) scale(0.625)",[962,1019],{"d":1000,"fill":965,"stroke":1001,"style":990},[39,1021],{"x":1022,"y":951,"width":952,"height":953,"rx":954,"fill":1023,"stroke":989,"style":400},"389.0","#d9f4f1",[958,1025,1027],{"transform":1026},"translate(551.5,29.0) scale(1.083)",[962,1028],{"d":988,"fill":965,"stroke":1029,"style":967},"var(--teal-ink,#0b6157)",[60,1031,1034],{"x":1032,"y":971,"style":1033},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","groupby(Region).ffill()",[395,1036],{"x1":1037,"y1":976,"x2":1038,"y2":976,"stroke":989,"style":54},"405.0","724.0",[60,1040,982],{"x":1041,"y":125,"style":981},"429.0",[958,1043,1045],{"transform":1044},"translate(407.5,99.5) scale(0.625)",[962,1046],{"d":988,"fill":965,"stroke":989,"style":990},[60,1048,1049],{"x":1041,"y":993,"style":981},"South row keeps a blank city",[958,1051,1053],{"transform":1052},"translate(407.5,125.5) scale(0.625)",[962,1054],{"d":988,"fill":965,"stroke":989,"style":990},[60,1056,1057],{"x":1041,"y":1004,"style":981},"gap can be reported",[958,1059,1061],{"transform":1060},"translate(407.5,151.5) scale(0.625)",[962,1062],{"d":988,"fill":965,"stroke":989,"style":990},[60,1064,1065],{"x":1041,"y":1013,"style":981},"labels never cross a region",[958,1067,1069],{"transform":1068},"translate(407.5,177.5) scale(0.625)",[962,1070],{"d":988,"fill":965,"stroke":989,"style":990},[421,1072,1074],{"id":1073},"step-3-fill-hierarchically","Step 3 — Fill hierarchically",[10,1076,1077],{},"Fill the top level of the hierarchy with a plain forward fill, then fill each lower level within the groups of the level above it:",[426,1079,1081],{"className":458,"code":1080,"language":460,"meta":431,"style":431},"def fill_hierarchy(frame, levels):\n    out = frame.copy()\n    out[levels[0]] = out[levels[0]].ffill()\n    for depth in range(1, len(levels)):\n        parents = out[levels[:depth]]\n        block = parents.ne(parents.shift()).any(axis=1).cumsum()   # new block when a parent changes\n        out[levels[depth]] = out.groupby(block)[levels[depth]].ffill()\n    return out\n\nfilled = fill_hierarchy(df, [\"Region\", \"City\"])\nprint(filled)\nprint(filled[filled[\"City\"].isna()])\n",[433,1082,1083,1095,1105,1123,1149,1159,1183,1193,1201,1205,1224,1231],{"__ignoreMap":431},[436,1084,1085,1088,1092],{"class":395,"line":438},[436,1086,1087],{"class":467},"def",[436,1089,1091],{"class":1090},"s_Opv"," fill_hierarchy",[436,1093,1094],{"class":471},"(frame, levels):\n",[436,1096,1097,1100,1102],{"class":395,"line":481},[436,1098,1099],{"class":471},"    out ",[436,1101,508],{"class":467},[436,1103,1104],{"class":471}," frame.copy()\n",[436,1106,1107,1110,1112,1114,1116,1119,1121],{"class":395,"line":495},[436,1108,1109],{"class":471},"    out[levels[",[436,1111,42],{"class":583},[436,1113,909],{"class":471},[436,1115,508],{"class":467},[436,1117,1118],{"class":471}," out[levels[",[436,1120,42],{"class":583},[436,1122,923],{"class":471},[436,1124,1125,1128,1131,1133,1136,1139,1141,1143,1146],{"class":395,"line":502},[436,1126,1127],{"class":467},"    for",[436,1129,1130],{"class":471}," depth ",[436,1132,534],{"class":467},[436,1134,1135],{"class":583}," range",[436,1137,1138],{"class":471},"(",[436,1140,98],{"class":583},[436,1142,543],{"class":471},[436,1144,1145],{"class":583},"len",[436,1147,1148],{"class":471},"(levels)):\n",[436,1150,1151,1154,1156],{"class":395,"line":514},[436,1152,1153],{"class":471},"        parents ",[436,1155,508],{"class":467},[436,1157,1158],{"class":471}," out[levels[:depth]]\n",[436,1160,1161,1164,1166,1169,1172,1174,1176,1179],{"class":395,"line":525},[436,1162,1163],{"class":471},"        block ",[436,1165,508],{"class":467},[436,1167,1168],{"class":471}," parents.ne(parents.shift()).any(",[436,1170,1171],{"class":825},"axis",[436,1173,508],{"class":467},[436,1175,98],{"class":583},[436,1177,1178],{"class":471},").cumsum()   ",[436,1180,1182],{"class":1181},"s-wDw","# new block when a parent changes\n",[436,1184,1185,1188,1190],{"class":395,"line":562},[436,1186,1187],{"class":471},"        out[levels[depth]] ",[436,1189,508],{"class":467},[436,1191,1192],{"class":471}," out.groupby(block)[levels[depth]].ffill()\n",[436,1194,1195,1198],{"class":395,"line":624},[436,1196,1197],{"class":467},"    return",[436,1199,1200],{"class":471}," out\n",[436,1202,1203],{"class":395,"line":663},[436,1204,499],{"emptyLinePlaceholder":498},[436,1206,1207,1210,1212,1215,1217,1219,1221],{"class":395,"line":687},[436,1208,1209],{"class":471},"filled ",[436,1211,508],{"class":467},[436,1213,1214],{"class":471}," fill_hierarchy(df, [",[436,1216,540],{"class":445},[436,1218,543],{"class":471},[436,1220,546],{"class":445},[436,1222,1223],{"class":471},"])\n",[436,1225,1226,1228],{"class":395,"line":693},[436,1227,739],{"class":583},[436,1229,1230],{"class":471},"(filled)\n",[436,1232,1233,1235,1238,1240],{"class":395,"line":705},[436,1234,739],{"class":583},[436,1236,1237],{"class":471},"(filled[filled[",[436,1239,546],{"class":445},[436,1241,1242],{"class":471},"].isna()])\n",[10,1244,1245,1248],{},[433,1246,1247],{},"groupby(...).ffill()"," restarts at the first row of every group, so a label can never leak into a neighbouring group. The groups are consecutive blocks — a new block starts whenever any parent label changes — rather than the parent values themselves. That matters when a region appears twice in one report, say North at the top and again after a page break: grouping by the value would let the second North block inherit a city from the first, while grouping by block keeps them apart. The same function handles three or four levels — region, city, store — by listing them in order. The remaining gap is now an honest one: the South row without a city is reported instead of silently filled, and the owner of the export can tell you whether it should be Bath.",[421,1250,1252],{"id":1251},"step-4-leave-the-data-columns-alone","Step 4 — Leave the data columns alone",[10,1254,1255,1256,1258],{},"Only label columns follow the \"shown once, applies to the rows below\" rule. A blank in ",[433,1257,119],{}," means no figure was recorded, and filling it would copy another row's number. Name the label columns explicitly rather than filling the whole frame:",[426,1260,1262],{"className":458,"code":1261,"language":460,"meta":431,"style":431},"LABELS = [\"Region\", \"City\"]\nassert not set(LABELS) & set(df.select_dtypes(\"number\").columns), \"a label column is numeric\"\nclean = fill_hierarchy(df, LABELS)\nprint(clean[\"Sales\"].isna().sum())   # 0 — and any NaN here would be real\n",[433,1263,1264,1284,1319,1333],{"__ignoreMap":431},[436,1265,1266,1269,1272,1275,1277,1279,1281],{"class":395,"line":438},[436,1267,1268],{"class":583},"LABELS",[436,1270,1271],{"class":467}," =",[436,1273,1274],{"class":471}," [",[436,1276,540],{"class":445},[436,1278,543],{"class":471},[436,1280,546],{"class":445},[436,1282,1283],{"class":471},"]\n",[436,1285,1286,1289,1292,1295,1297,1299,1302,1305,1307,1310,1313,1316],{"class":395,"line":481},[436,1287,1288],{"class":467},"assert",[436,1290,1291],{"class":467}," not",[436,1293,1294],{"class":583}," set",[436,1296,1138],{"class":471},[436,1298,1268],{"class":583},[436,1300,1301],{"class":471},") ",[436,1303,1304],{"class":467},"&",[436,1306,1294],{"class":583},[436,1308,1309],{"class":471},"(df.select_dtypes(",[436,1311,1312],{"class":445},"\"number\"",[436,1314,1315],{"class":471},").columns), ",[436,1317,1318],{"class":445},"\"a label column is numeric\"\n",[436,1320,1321,1324,1326,1329,1331],{"class":395,"line":495},[436,1322,1323],{"class":471},"clean ",[436,1325,508],{"class":467},[436,1327,1328],{"class":471}," fill_hierarchy(df, ",[436,1330,1268],{"class":583},[436,1332,702],{"class":471},[436,1334,1335,1337,1340,1342,1345],{"class":395,"line":502},[436,1336,739],{"class":583},[436,1338,1339],{"class":471},"(clean[",[436,1341,556],{"class":445},[436,1343,1344],{"class":471},"].isna().sum())   ",[436,1346,1347],{"class":1181},"# 0 — and any NaN here would be real\n",[10,1349,1350,1351,1355],{},"Gaps in numeric columns deserve their own decision — zero, interpolation or leave as missing — covered in ",[14,1352,1354],{"href":1353},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Ffill-missing-values-in-excel-with-pandas-fillna\u002F","Fill Missing Values in Excel with pandas fillna",".",[421,1357,1359],{"id":1358},"step-5-check-the-result-against-the-reports-own-totals","Step 5 — Check the result against the report's own totals",[10,1361,1362],{},"The total row you dropped in Step 1 is a free test. After filling, the sum of the data rows should equal it, and the sums per region should match any subtotals the report printed:",[426,1364,1366],{"className":458,"code":1365,"language":460,"meta":431,"style":431},"reported_total = raw.loc[is_total, \"Sales\"].sum()\nprint(clean[\"Sales\"].sum(), reported_total)          # 43 43\nassert clean[\"Sales\"].sum() == reported_total, \"rows lost or duplicated while cleaning\"\nprint(clean.groupby(\"Region\")[\"Sales\"].sum().to_dict())\n# {'North': 29, 'South': 14}\n",[433,1367,1368,1383,1397,1418,1435],{"__ignoreMap":431},[436,1369,1370,1373,1375,1378,1380],{"class":395,"line":438},[436,1371,1372],{"class":471},"reported_total ",[436,1374,508],{"class":467},[436,1376,1377],{"class":471}," raw.loc[is_total, ",[436,1379,556],{"class":445},[436,1381,1382],{"class":471},"].sum()\n",[436,1384,1385,1387,1389,1391,1394],{"class":395,"line":481},[436,1386,739],{"class":583},[436,1388,1339],{"class":471},[436,1390,556],{"class":445},[436,1392,1393],{"class":471},"].sum(), reported_total)          ",[436,1395,1396],{"class":1181},"# 43 43\n",[436,1398,1399,1401,1404,1406,1409,1412,1415],{"class":395,"line":495},[436,1400,1288],{"class":467},[436,1402,1403],{"class":471}," clean[",[436,1405,556],{"class":445},[436,1407,1408],{"class":471},"].sum() ",[436,1410,1411],{"class":467},"==",[436,1413,1414],{"class":471}," reported_total, ",[436,1416,1417],{"class":445},"\"rows lost or duplicated while cleaning\"\n",[436,1419,1420,1422,1425,1427,1430,1432],{"class":395,"line":502},[436,1421,739],{"class":583},[436,1423,1424],{"class":471},"(clean.groupby(",[436,1426,540],{"class":445},[436,1428,1429],{"class":471},")[",[436,1431,556],{"class":445},[436,1433,1434],{"class":471},"].sum().to_dict())\n",[436,1436,1437],{"class":395,"line":514},[436,1438,1439],{"class":1181},"# {'North': 29, 'South': 14}\n",[10,1441,1442,1443,1446,1447,1355],{},"A mismatch means a data row was mistaken for a total, a total survived as data, or the fill assigned rows to the wrong group. Checking the per-group sums against the subtotals catches the last case, which the grand total alone cannot. Keeping ",[433,1444,1445],{},"raw"," — the frame exactly as read — makes the check possible after the cleaning steps have changed ",[433,1448,1449],{},"df",[421,1451,1453],{"id":1452},"step-6-blocks-separated-by-blank-rows","Step 6 — Blocks separated by blank rows",[10,1455,1456],{},"Some reports start each group after an entirely blank row and put the group name alone on a header line. Use the blank rows to number the blocks, take the label from each block's first line, then drop the separator and header lines:",[426,1458,1460],{"className":458,"code":1459,"language":460,"meta":431,"style":431},"blocks = pd.DataFrame({\n    \"A\": [\"North\", \"Leeds\", \"York\", None, \"South\", \"Bath\"],\n    \"B\": [None, 10, 7, None, None, 9],\n})\nblock_id = blocks.isna().all(axis=1).cumsum()\nblocks[\"Region\"] = blocks.groupby(block_id)[\"A\"].transform(\"first\")\nrecords = blocks[blocks[\"B\"].notna()].rename(columns={\"A\": \"City\", \"B\": \"Sales\"})\nprint(records.values.tolist())\n# [['Leeds', 10.0, 'North'], ['York', 7.0, 'North'], ['Bath', 9.0, 'South']]\n",[433,1461,1462,1472,1504,1535,1540,1559,1585,1626,1633],{"__ignoreMap":431},[436,1463,1464,1467,1469],{"class":395,"line":438},[436,1465,1466],{"class":471},"blocks ",[436,1468,508],{"class":467},[436,1470,1471],{"class":471}," pd.DataFrame({\n",[436,1473,1474,1477,1480,1482,1484,1486,1488,1490,1492,1494,1496,1498,1500,1502],{"class":395,"line":481},[436,1475,1476],{"class":445},"    \"A\"",[436,1478,1479],{"class":471},": [",[436,1481,568],{"class":445},[436,1483,543],{"class":471},[436,1485,573],{"class":445},[436,1487,543],{"class":471},[436,1489,611],{"class":445},[436,1491,543],{"class":471},[436,1493,589],{"class":583},[436,1495,543],{"class":471},[436,1497,629],{"class":445},[436,1499,543],{"class":471},[436,1501,650],{"class":445},[436,1503,559],{"class":471},[436,1505,1506,1509,1511,1513,1515,1517,1519,1521,1523,1525,1527,1529,1531,1533],{"class":395,"line":495},[436,1507,1508],{"class":445},"    \"B\"",[436,1510,1479],{"class":471},[436,1512,589],{"class":583},[436,1514,543],{"class":471},[436,1516,147],{"class":583},[436,1518,543],{"class":471},[436,1520,190],{"class":583},[436,1522,543],{"class":471},[436,1524,589],{"class":583},[436,1526,543],{"class":471},[436,1528,589],{"class":583},[436,1530,543],{"class":471},[436,1532,235],{"class":583},[436,1534,559],{"class":471},[436,1536,1537],{"class":395,"line":502},[436,1538,1539],{"class":471},"})\n",[436,1541,1542,1545,1547,1550,1552,1554,1556],{"class":395,"line":514},[436,1543,1544],{"class":471},"block_id ",[436,1546,508],{"class":467},[436,1548,1549],{"class":471}," blocks.isna().all(",[436,1551,1171],{"class":825},[436,1553,508],{"class":467},[436,1555,98],{"class":583},[436,1557,1558],{"class":471},").cumsum()\n",[436,1560,1561,1564,1566,1569,1571,1574,1577,1580,1583],{"class":395,"line":525},[436,1562,1563],{"class":471},"blocks[",[436,1565,540],{"class":445},[436,1567,1568],{"class":471},"] ",[436,1570,508],{"class":467},[436,1572,1573],{"class":471}," blocks.groupby(block_id)[",[436,1575,1576],{"class":445},"\"A\"",[436,1578,1579],{"class":471},"].transform(",[436,1581,1582],{"class":445},"\"first\"",[436,1584,702],{"class":471},[436,1586,1587,1590,1592,1595,1598,1601,1604,1606,1609,1611,1614,1616,1618,1620,1622,1624],{"class":395,"line":562},[436,1588,1589],{"class":471},"records ",[436,1591,508],{"class":467},[436,1593,1594],{"class":471}," blocks[blocks[",[436,1596,1597],{"class":445},"\"B\"",[436,1599,1600],{"class":471},"].notna()].rename(",[436,1602,1603],{"class":825},"columns",[436,1605,508],{"class":467},[436,1607,1608],{"class":471},"{",[436,1610,1576],{"class":445},[436,1612,1613],{"class":471},": ",[436,1615,546],{"class":445},[436,1617,543],{"class":471},[436,1619,1597],{"class":445},[436,1621,1613],{"class":471},[436,1623,556],{"class":445},[436,1625,1539],{"class":471},[436,1627,1628,1630],{"class":395,"line":624},[436,1629,739],{"class":583},[436,1631,1632],{"class":471},"(records.values.tolist())\n",[436,1634,1635],{"class":395,"line":663},[436,1636,1637],{"class":1181},"# [['Leeds', 10.0, 'North'], ['York', 7.0, 'North'], ['Bath', 9.0, 'South']]\n",[10,1639,1640,1643],{},[433,1641,1642],{},"cumsum"," over the \"row is blank\" flag gives every block its own number, so the header of one block can never be applied to another. The header rows — where the value column is empty — are dropped at the end.",[20,1645,29,1650,29,1653,29,1656,29,1659,29,1664,29,1672,29,1678,29,1683,29,1687,29,1692,29,1695,29,1698,29,1704,29,1710,29,1714,29,1718,29,1721,29,1724,29,1730,29,1733,29,1736,29,1740,29,1743,29,1746,29,1752,29,1757,29,1761,29,1764,29,1768,29,1771,29,1775,29,1782,29,1787],{"viewBox":1646,"role":23,"ariaLabelledBy":1647,"xmlns":27,"style":28},"4 0 752 142",[1648,1649],"ff-steps-t","ff-steps-d",[31,1651,1652],{"id":1648},"Filling a grouped report safely",[35,1654,1655],{"id":1649},"Read the report, drop total and subtotal rows, fill the top label level down, fill each lower level within its parent group, leave numeric columns untouched, and report any label that is still blank.",[39,1657],{"x":41,"y":42,"width":43,"height":1658,"fill":45},"142",[39,1660],{"x":48,"y":951,"width":1661,"height":1662,"rx":168,"fill":1663,"stroke":399,"style":400},"115.2","110.5","#f0f4ff",[958,1665,1667],{"transform":1666},"translate(65.6,29.0) scale(1.000)",[962,1668],{"d":1669,"fill":965,"stroke":1670,"style":1671},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[60,1673,1677],{"x":1674,"y":1675,"style":1676},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Read report",[60,1679,1682],{"x":1674,"y":1680,"style":1681},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","merged cells →",[60,1684,1686],{"x":1674,"y":1685,"style":1681},"112.5","blanks",[395,1688],{"x1":1689,"y1":1690,"x2":1691,"y2":1690,"stroke":399,"style":400},"140.2","71.2","162.2",[402,1693],{"points":1694,"fill":405},"166.2,71.2 157.2,76.2 157.2,66.2",[39,1696],{"x":1697,"y":951,"width":1661,"height":1662,"rx":168,"fill":955,"stroke":956,"style":400},"171.2",[958,1699,1701],{"transform":1700},"translate(216.8,37.8) scale(1.000)",[962,1702],{"d":1703,"fill":965,"stroke":966,"style":1671},"M3 5h18l-7 8v6l-4 2v-8z",[60,1705,1709],{"x":1706,"y":1707,"style":1708},"228.8","84.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Drop totals",[60,1711,1713],{"x":1706,"y":1712,"style":1681},"103.8","before filling",[395,1715],{"x1":1716,"y1":1690,"x2":1717,"y2":1690,"stroke":399,"style":400},"291.4","313.4",[402,1719],{"points":1720,"fill":405},"317.4,71.2 308.4,76.2 308.4,66.2",[39,1722],{"x":1723,"y":951,"width":1661,"height":1662,"rx":168,"fill":1663,"stroke":399,"style":400},"322.4",[958,1725,1727],{"transform":1726},"translate(368.0,37.8) scale(1.000)",[962,1728],{"d":1729,"fill":965,"stroke":1670,"style":1671},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5",[60,1731,1732],{"x":416,"y":1707,"style":1676},"Fill top level",[60,1734,1735],{"x":416,"y":1712,"style":1681},"ffill()",[395,1737],{"x1":1738,"y1":1690,"x2":1739,"y2":1690,"stroke":399,"style":400},"442.6","464.6",[402,1741],{"points":1742,"fill":405},"468.6,71.2 459.6,76.2 459.6,66.2",[39,1744],{"x":1745,"y":951,"width":1661,"height":1662,"rx":168,"fill":1023,"stroke":989,"style":400},"473.6",[958,1747,1749],{"transform":1748},"translate(519.2,29.0) scale(1.000)",[962,1750],{"d":1751,"fill":965,"stroke":1029,"style":1671},"M4 12h6 M10 12l6-6h4 M10 12l6 6h4 M17 3l3 3-3 3 M17 15l3 3-3 3",[60,1753,1756],{"x":1754,"y":1675,"style":1755},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Fill within",[60,1758,1760],{"x":1754,"y":1759,"style":1755},"93.0","parents",[60,1762,1763],{"x":1754,"y":1685,"style":1681},"groupby().ffill()",[395,1765],{"x1":1766,"y1":1690,"x2":1767,"y2":1690,"stroke":399,"style":400},"593.8","615.8",[402,1769],{"points":1770,"fill":405},"619.8,71.2 610.8,76.2 610.8,66.2",[39,1772],{"x":1773,"y":951,"width":1661,"height":1662,"rx":168,"fill":1774,"stroke":1001,"style":400},"624.8","#fee8f2",[958,1776,1778],{"transform":1777},"translate(670.4,37.8) scale(1.000)",[962,1779],{"d":1780,"fill":965,"stroke":1781,"style":1671},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","var(--accent-ink,#be185d)",[60,1783,1786],{"x":1784,"y":1707,"style":1785},"682.4","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Report gaps",[60,1788,1789],{"x":1784,"y":1712,"style":1681},"labels still blank",[421,1791,1793],{"id":1792},"step-7-put-the-blanks-back-for-output","Step 7 — Put the blanks back for output",[10,1795,1796],{},"When the cleaned data goes back into a report, readers expect the grouped look again. Blank each label wherever it repeats the row above within the same parent:",[426,1798,1800],{"className":458,"code":1799,"language":460,"meta":431,"style":431},"def blank_repeats(frame, levels):\n    out = frame.copy()\n    key = pd.Series(\"\", index=out.index)\n    for level in levels:\n        key = key + \"|\" + out[level].astype(str)\n        repeat = key.eq(key.shift())\n        out[level] = out[level].mask(repeat)\n    return out\n\nreport = blank_repeats(filled, [\"Region\", \"City\"])\nreport.to_excel(\"grouped_clean.xlsx\", index=False)\nprint(report.fillna(\"\").values.tolist())\n",[433,1801,1802,1811,1819,1842,1854,1881,1891,1901,1907,1911,1929,1947],{"__ignoreMap":431},[436,1803,1804,1806,1809],{"class":395,"line":438},[436,1805,1087],{"class":467},[436,1807,1808],{"class":1090}," blank_repeats",[436,1810,1094],{"class":471},[436,1812,1813,1815,1817],{"class":395,"line":481},[436,1814,1099],{"class":471},[436,1816,508],{"class":467},[436,1818,1104],{"class":471},[436,1820,1821,1824,1826,1829,1832,1834,1837,1839],{"class":395,"line":495},[436,1822,1823],{"class":471},"    key ",[436,1825,508],{"class":467},[436,1827,1828],{"class":471}," pd.Series(",[436,1830,1831],{"class":445},"\"\"",[436,1833,543],{"class":471},[436,1835,1836],{"class":825},"index",[436,1838,508],{"class":467},[436,1840,1841],{"class":471},"out.index)\n",[436,1843,1844,1846,1849,1851],{"class":395,"line":502},[436,1845,1127],{"class":467},[436,1847,1848],{"class":471}," level ",[436,1850,534],{"class":467},[436,1852,1853],{"class":471}," levels:\n",[436,1855,1856,1859,1861,1864,1867,1870,1873,1876,1879],{"class":395,"line":514},[436,1857,1858],{"class":471},"        key ",[436,1860,508],{"class":467},[436,1862,1863],{"class":471}," key ",[436,1865,1866],{"class":467},"+",[436,1868,1869],{"class":445}," \"|\"",[436,1871,1872],{"class":467}," +",[436,1874,1875],{"class":471}," out[level].astype(",[436,1877,1878],{"class":583},"str",[436,1880,702],{"class":471},[436,1882,1883,1886,1888],{"class":395,"line":525},[436,1884,1885],{"class":471},"        repeat ",[436,1887,508],{"class":467},[436,1889,1890],{"class":471}," key.eq(key.shift())\n",[436,1892,1893,1896,1898],{"class":395,"line":562},[436,1894,1895],{"class":471},"        out[level] ",[436,1897,508],{"class":467},[436,1899,1900],{"class":471}," out[level].mask(repeat)\n",[436,1902,1903,1905],{"class":395,"line":624},[436,1904,1197],{"class":467},[436,1906,1200],{"class":471},[436,1908,1909],{"class":395,"line":663},[436,1910,499],{"emptyLinePlaceholder":498},[436,1912,1913,1916,1918,1921,1923,1925,1927],{"class":395,"line":687},[436,1914,1915],{"class":471},"report ",[436,1917,508],{"class":467},[436,1919,1920],{"class":471}," blank_repeats(filled, [",[436,1922,540],{"class":445},[436,1924,543],{"class":471},[436,1926,546],{"class":445},[436,1928,1223],{"class":471},[436,1930,1931,1934,1937,1939,1941,1943,1945],{"class":395,"line":693},[436,1932,1933],{"class":471},"report.to_excel(",[436,1935,1936],{"class":445},"\"grouped_clean.xlsx\"",[436,1938,543],{"class":471},[436,1940,1836],{"class":825},[436,1942,508],{"class":467},[436,1944,831],{"class":583},[436,1946,702],{"class":471},[436,1948,1949,1951,1954,1956],{"class":395,"line":705},[436,1950,739],{"class":583},[436,1952,1953],{"class":471},"(report.fillna(",[436,1955,1831],{"class":445},[436,1957,1958],{"class":471},").values.tolist())\n",[10,1960,1961],{},"Comparing the combined key rather than the single column means a city is shown again whenever its region changes, even if two regions share a city name. Keep the filled frame for any further calculation; the blanked version is for presentation only.",[421,1963,1965],{"id":1964},"common-pitfalls","Common pitfalls",[1967,1968,1969,1977,1987,1993],"ul",{},[1970,1971,1972,1976],"li",{},[1973,1974,1975],"strong",{},"Filling before sorting changes."," A forward fill relies on the original row order. Fill first, then sort; sorting a grouped report before filling scatters the blanks away from their labels.",[1970,1978,1979,1982,1983,1986],{},[1973,1980,1981],{},"Whitespace that looks blank."," A cell containing a space is not ",[433,1984,1985],{},"NaN",", so the fill stops there. Convert whitespace-only cells to missing values first.",[1970,1988,1989,1992],{},[1973,1990,1991],{},"Filling across sheets."," When you combine several sheets, fill each one before concatenating so the last label of one sheet does not flow into the next.",[1970,1994,1995,1998,1999,2002,2003,2006],{},[1973,1996,1997],{},"Merged cells spanning columns."," A title merged across A1:D1 is not a group label; skip title rows with ",[433,2000,2001],{},"header="," or ",[433,2004,2005],{},"skiprows="," before filling.",[421,2008,2010],{"id":2009},"conclusion","Conclusion",[10,2012,2013],{},"Grouped Excel reports hide their labels in blank and merged cells. Drop total rows, forward-fill the top label level, fill each lower level within its parent group so labels never cross boundaries, and leave numeric columns alone. Report any label that is still blank instead of guessing, and restore the grouped look only when writing the finished report.",[421,2015,2017],{"id":2016},"frequently-asked-questions","Frequently asked questions",[10,2019,2020,2023],{},[1973,2021,2022],{},"How do I fill merged cells when reading Excel with pandas?","\npandas reads only the top-left cell of a merged range and leaves the rest empty, so the same forward fill that handles blank cells also fills merged ones. Fill only the label columns that were merged.",[10,2025,2026,2029],{},[1973,2027,2028],{},"Why did forward fill put a city under the wrong region?","\nA plain ffill on the lower-level column carries the last value down regardless of groups. Fill the higher level first, then fill the lower level within each higher-level group with groupby(...).ffill().",[10,2031,2032,2035],{},[1973,2033,2034],{},"Should I forward-fill numeric columns too?","\nAlmost never. A blank amount in a grouped report is usually a genuine missing value or a zero, not a repeat of the row above. Fill label columns only and treat numeric gaps separately.",[10,2037,2038,2041],{},[1973,2039,2040],{},"How do I put the blanks back when writing the report?","\nBlank a label wherever it equals the value in the row above within the same parent group, using mask with shift. That restores the grouped look without changing the data.",[421,2043,2045],{"id":2044},"related","Related",[1967,2047,2048,2054,2061,2068],{},[1970,2049,2050,2051,2053],{},"Up one level: ",[14,2052,17],{"href":16}," — everything about gaps in Excel data.",[1970,2055,2056,2060],{},[14,2057,2059],{"href":2058},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Funmerge-cells-and-fill-values-with-openpyxl\u002F","Unmerge Cells and Fill Values with openpyxl"," — fixing merged cells in the workbook itself.",[1970,2062,2063,2067],{},[14,2064,2066],{"href":2065},"\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"," — the same problem in the header rows.",[1970,2069,2070,2074],{},[14,2071,2073],{"href":2072},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Finterpolate-missing-numeric-values-in-excel-data\u002F","Interpolate Missing Numeric Values in Excel Data"," — filling numbers, which follows different rules.",[2076,2077,2078],"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_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":431,"searchDepth":481,"depth":481,"links":2080},[2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092],{"id":423,"depth":481,"text":424},{"id":748,"depth":481,"text":749},{"id":877,"depth":481,"text":878},{"id":1073,"depth":481,"text":1074},{"id":1251,"depth":481,"text":1252},{"id":1358,"depth":481,"text":1359},{"id":1452,"depth":481,"text":1453},{"id":1792,"depth":481,"text":1793},{"id":1964,"depth":481,"text":1965},{"id":2009,"depth":481,"text":2010},{"id":2016,"depth":481,"text":2017},{"id":2044,"depth":481,"text":2045},"2026-09-23","Grouped Excel reports show each region or category once and leave the rows below it blank, often in merged cells. Fill those labels down with pandas without leaking a label into the next group, filling data columns by mistake, or carrying labels into total rows.","md",[2097,2099,2101,2103],{"q":2022,"a":2098},"pandas reads only the top-left cell of a merged range and leaves the rest empty, so the same forward fill that handles blank cells also fills merged ones. Fill only the label columns that were merged.",{"q":2028,"a":2100},"A plain ffill on the lower-level column carries the last value down regardless of groups. Fill the higher level first, then fill the lower level within each higher-level group with groupby(...).ffill().",{"q":2034,"a":2102},"Almost never. A blank amount in a grouped report is usually a genuine missing value or a zero, not a repeat of the row above. Fill label columns only and treat numeric gaps separately.",{"q":2040,"a":2104},"Blank a label wherever it equals the value in the row above within the same parent group, using mask with shift. That restores the grouped look without changing the data.","forward-fill blank cells in grouped excel reports with pandas",null,{"breadcrumb":2108},[2109,2112,2115],{"name":2110,"item":2111},"Home","\u002F",{"name":2113,"item":2114},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2116,"item":16},"Handling Missing Data in Excel Reports with Pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fforward-fill-blank-cells-in-grouped-excel-reports",{"title":2119,"description":2120},"Forward-Fill Blank Cells in Grouped Excel Reports (pandas ffill)","Fill down blank group labels in Excel reports with pandas — ffill on label columns only, hierarchical fills with groupby so labels never cross groups, merged cells, total rows, blank-row blocks and re-blanking for output.","forward-fill-blank-cells-in-grouped-excel-reports","advanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fforward-fill-blank-cells-in-grouped-excel-reports\u002Findex",[2124,2125,2126],"missing-data","messy-sheets","pandas","how-to","v4TTor5eevRoS8HqI7iuBZMiojXaNKLx_NvGCTizdGc",[2130,2134],{"title":2131,"path":2132,"stem":2133,"children":-1},"Flag Rows with Too Many Missing Values","\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fflag-rows-with-too-many-missing-values","advanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fflag-rows-with-too-many-missing-values\u002Findex",{"title":2135,"path":2136,"stem":2137,"children":-1},"How Missing Values Change Sums, Averages and Counts","\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fhow-missing-values-change-sums-averages-and-counts","advanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fhow-missing-values-change-sums-averages-and-counts\u002Findex",[2139,2141],{"path":2140,"title":2116},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports",{"path":2142,"title":2113},"\u002Fadvanced-data-transformation-and-cleaning",[],[2145,2148,2151,2154,2157,2160],{"path":2146,"title":2147,"kind":2127},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-blank-cells-with-openpyxl","Highlight Blank Cells in Excel with openpyxl",{"path":2149,"title":2150,"kind":2127},"\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":2152,"title":2153,"kind":2127},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fiferror-and-isna-equivalents-in-pandas","IFERROR, IFNA, ISNA and ISNUMBER Equivalents in pandas",{"path":2155,"title":2156,"kind":2127},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fdrop-rows-with-missing-required-fields-in-pandas","Drop Rows with Missing Required Fields in pandas",{"path":2158,"title":2159,"kind":2127},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Ffill-gaps-in-per-item-time-series-with-a-staleness-limit","Fill Gaps in Per-Item Time Series with a Staleness Limit",{"path":2161,"title":2162,"kind":2127},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Ffill-missing-values-in-excel-with-pandas-fillna","Fill Missing Values in Excel with Pandas fillna",1790972095979]