[{"data":1,"prerenderedAt":2612},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcreate-dependent-dropdown-lists-in-excel-with-python":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcreate-dependent-dropdown-lists-in-excel-with-python":2579,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcreate-dependent-dropdown-lists-in-excel-with-python":2588,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcreate-dependent-dropdown-lists-in-excel-with-python":2593,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcreate-dependent-dropdown-lists-in-excel-with-python":2594},{"id":4,"title":5,"body":6,"dateModified":2545,"datePublished":2545,"description":2546,"extension":2547,"faq":2548,"keyword":2557,"kind":2558,"meta":2559,"navigation":115,"path":2568,"seo":2569,"slug":2572,"stem":2573,"topics":2574,"type":2577,"__hash__":2578},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcreate-dependent-dropdown-lists-in-excel-with-python\u002Findex.md","Create Dependent Dropdown Lists in Excel with Python",{"type":7,"value":8,"toc":2533},"minimark",[9,51,56,84,87,277,464,468,475,902,915,919,922,1103,1110,1114,1131,1407,1434,1700,1704,1711,1849,1903,2020,2031,2225,2229,2232,2387,2402,2406,2451,2455,2468,2472,2478,2484,2490,2496,2500,2529],[10,11,12,13,17,18,21,22,25,26,17,29,21,32,35,36,39,40,44,45,50],"p",{},"Expense claims, order forms and asset registers often ask for two related choices: a category, and then an item within that category — ",[14,15,16],"em",{},"Travel"," then ",[14,19,20],{},"Rail"," or ",[14,23,24],{},"Taxi",", ",[14,27,28],{},"Hardware",[14,30,31],{},"Laptop",[14,33,34],{},"Monitor",". A plain dropdown for the second column offers every item from every category, and people pick combinations that make no sense. A ",[14,37,38],{},"dependent"," dropdown shows only the items that belong to the category chosen in the same row. Excel does this with data validation and the ",[41,42,43],"code",{},"INDIRECT"," function pointing at a named range per category, and all of it can be generated from Python out of a simple mapping table — so when the list of categories changes, the template is rebuilt rather than edited by hand. This page is part of ",[46,47,49],"a",{"href":48},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002F","Validating Excel Data with Python",".",[52,53,55],"h2",{"id":54},"prerequisites","Prerequisites",[57,58,63],"pre",{"className":59,"code":60,"language":61,"meta":62,"style":62},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[41,64,65],{"__ignoreMap":62},[66,67,70,74,78,81],"span",{"class":68,"line":69},"line",1,[66,71,73],{"class":72},"sMTad","pip",[66,75,77],{"class":76},"srMev"," install",[66,79,80],{"class":76}," openpyxl",[66,82,83],{"class":76}," pandas\n",[10,85,86],{},"The mapping of categories to items is a small table — in practice a sheet someone maintains, here a DataFrame. Two of the categories contain spaces, which named ranges do not allow; handling that is part of the job. LibreOffice is used at the end to recalculate a copy of the template and prove each category's list resolves.",[57,88,92],{"className":89,"code":90,"language":91,"meta":62,"style":62},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nmapping = pd.DataFrame({\n    \"Category\": [\"Travel\", \"Travel\", \"Travel\", \"Hardware\", \"Hardware\", \"Office supplies\", \"Office supplies\", \"Office supplies\", \"Client meals\"],\n    \"Item\": [\"Rail\", \"Taxi\", \"Flight\", \"Laptop\", \"Monitor\", \"Paper\", \"Toner\", \"Stationery\", \"Lunch\"],\n})\nprint(mapping.groupby(\"Category\", sort=False)[\"Item\"].apply(list).to_dict())\n","python",[41,93,94,110,117,129,179,232,238],{"__ignoreMap":62},[66,95,96,100,104,107],{"class":68,"line":69},[66,97,99],{"class":98},"s-kum","import",[66,101,103],{"class":102},"skGVy"," pandas ",[66,105,106],{"class":98},"as",[66,108,109],{"class":102}," pd\n",[66,111,113],{"class":68,"line":112},2,[66,114,116],{"emptyLinePlaceholder":115},true,"\n",[66,118,120,123,126],{"class":68,"line":119},3,[66,121,122],{"class":102},"mapping ",[66,124,125],{"class":98},"=",[66,127,128],{"class":102}," pd.DataFrame({\n",[66,130,132,135,138,141,143,145,147,149,151,154,156,158,160,163,165,167,169,171,173,176],{"class":68,"line":131},4,[66,133,134],{"class":76},"    \"Category\"",[66,136,137],{"class":102},": [",[66,139,140],{"class":76},"\"Travel\"",[66,142,25],{"class":102},[66,144,140],{"class":76},[66,146,25],{"class":102},[66,148,140],{"class":76},[66,150,25],{"class":102},[66,152,153],{"class":76},"\"Hardware\"",[66,155,25],{"class":102},[66,157,153],{"class":76},[66,159,25],{"class":102},[66,161,162],{"class":76},"\"Office supplies\"",[66,164,25],{"class":102},[66,166,162],{"class":76},[66,168,25],{"class":102},[66,170,162],{"class":76},[66,172,25],{"class":102},[66,174,175],{"class":76},"\"Client meals\"",[66,177,178],{"class":102},"],\n",[66,180,182,185,187,190,192,195,197,200,202,205,207,210,212,215,217,220,222,225,227,230],{"class":68,"line":181},5,[66,183,184],{"class":76},"    \"Item\"",[66,186,137],{"class":102},[66,188,189],{"class":76},"\"Rail\"",[66,191,25],{"class":102},[66,193,194],{"class":76},"\"Taxi\"",[66,196,25],{"class":102},[66,198,199],{"class":76},"\"Flight\"",[66,201,25],{"class":102},[66,203,204],{"class":76},"\"Laptop\"",[66,206,25],{"class":102},[66,208,209],{"class":76},"\"Monitor\"",[66,211,25],{"class":102},[66,213,214],{"class":76},"\"Paper\"",[66,216,25],{"class":102},[66,218,219],{"class":76},"\"Toner\"",[66,221,25],{"class":102},[66,223,224],{"class":76},"\"Stationery\"",[66,226,25],{"class":102},[66,228,229],{"class":76},"\"Lunch\"",[66,231,178],{"class":102},[66,233,235],{"class":68,"line":234},6,[66,236,237],{"class":102},"})\n",[66,239,241,245,248,251,253,257,259,262,265,268,271,274],{"class":68,"line":240},7,[66,242,244],{"class":243},"sP0c6","print",[66,246,247],{"class":102},"(mapping.groupby(",[66,249,250],{"class":76},"\"Category\"",[66,252,25],{"class":102},[66,254,256],{"class":255},"sa561","sort",[66,258,125],{"class":98},[66,260,261],{"class":243},"False",[66,263,264],{"class":102},")[",[66,266,267],{"class":76},"\"Item\"",[66,269,270],{"class":102},"].apply(",[66,272,273],{"class":243},"list",[66,275,276],{"class":102},").to_dict())\n",[278,279,287,288,287,292,287,296,287,304,287,314,287,325,287,332,287,336,287,341,287,345,287,351,287,356,287,360,287,367,287,372,287,375,287,379,287,382,287,385,287,391,287,396,287,400,287,404,287,408,287,411,287,416,287,423,287,428,287,431,287,435,287,438,287,443,287,450,287,455,287,458,287,461],"svg",{"viewBox":280,"role":281,"ariaLabelledBy":282,"xmlns":285,"style":286},"4 0 752 160","img",[283,284],"dd-flow-t","dd-flow-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  ",[289,290,291],"title",{"id":283},"How the second dropdown follows the first",[293,294,295],"desc",{"id":284},"The user picks a category in column A; the validation list in column B is INDIRECT of that category's safe name, which is a named range pointing at the category's items on the hidden lookup sheet, so only those items are offered.",[297,298],"rect",{"x":299,"y":300,"width":301,"height":302,"fill":303},"4","0","752","160","#ffffff",[297,305],{"x":306,"y":307,"width":308,"height":309,"rx":310,"fill":311,"stroke":312,"style":313},"20.0","16.0","115.2","128.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[315,316,318],"g",{"transform":317},"translate(65.6,29.0) scale(1.000)",[319,320],"path",{"d":321,"fill":322,"stroke":323,"style":324},"M3 5h18l-7 8v6l-4 2v-8z","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[326,327,331],"text",{"x":328,"y":329,"style":330},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Pick",[326,333,335],{"x":328,"y":334,"style":330},"93.0","category",[326,337,340],{"x":328,"y":338,"style":339},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","A2 = Office",[326,342,344],{"x":328,"y":343,"style":339},"130.0","supplies",[68,346],{"x1":347,"y1":348,"x2":349,"y2":348,"stroke":350,"style":313},"140.2","80.0","162.2","var(--brand,#5b5cf0)",[352,353],"polygon",{"points":354,"fill":355},"166.2,80.0 157.2,85.0 157.2,75.0","#5b5cf0",[297,357],{"x":358,"y":307,"width":308,"height":309,"rx":310,"fill":359,"stroke":350,"style":313},"171.2","#f0f4ff",[315,361,363],{"transform":362},"translate(216.8,46.5) scale(1.000)",[319,364],{"d":365,"fill":322,"stroke":366,"style":324},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--brand-strong,#4338ca)",[326,368,371],{"x":369,"y":334,"style":370},"228.8","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Safe name",[326,373,374],{"x":369,"y":338,"style":339},"Office_supplies",[68,376],{"x1":377,"y1":348,"x2":378,"y2":348,"stroke":350,"style":313},"291.4","313.4",[352,380],{"points":381,"fill":355},"317.4,80.0 308.4,85.0 308.4,75.0",[297,383],{"x":384,"y":307,"width":308,"height":309,"rx":310,"fill":359,"stroke":350,"style":313},"322.4",[315,386,388],{"transform":387},"translate(368.0,37.8) scale(1.000)",[319,389],{"d":390,"fill":322,"stroke":366,"style":324},"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1 M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1",[326,392,395],{"x":393,"y":394,"style":370},"380.0","84.2","INDIRECT(…)",[326,397,399],{"x":393,"y":398,"style":339},"103.8","resolves the",[326,401,403],{"x":393,"y":402,"style":339},"121.2","name",[68,405],{"x1":406,"y1":348,"x2":407,"y2":348,"stroke":350,"style":313},"442.6","464.6",[352,409],{"points":410,"fill":355},"468.6,80.0 459.6,85.0 459.6,75.0",[297,412],{"x":413,"y":307,"width":308,"height":309,"rx":310,"fill":414,"stroke":415,"style":313},"473.6","#d9f4f1","var(--teal,#0f9488)",[315,417,419],{"transform":418},"translate(519.2,46.5) scale(1.000)",[319,420],{"d":421,"fill":322,"stroke":422,"style":324},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--teal-ink,#0b6157)",[326,424,427],{"x":425,"y":334,"style":426},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Named range",[326,429,430],{"x":425,"y":338,"style":339},"Lists!$C$2:$C$4",[68,432],{"x1":433,"y1":348,"x2":434,"y2":348,"stroke":350,"style":313},"593.8","615.8",[352,436],{"points":437,"fill":355},"619.8,80.0 610.8,85.0 610.8,75.0",[297,439],{"x":440,"y":307,"width":308,"height":309,"rx":310,"fill":441,"stroke":442,"style":313},"624.8","#fdefd8","var(--gold,#b4740a)",[315,444,446],{"transform":445},"translate(670.4,29.0) scale(1.000)",[319,447],{"d":448,"fill":322,"stroke":449,"style":324},"M5 12.5l4.5 4.5L19 7","var(--gold-ink,#7a4e06)",[326,451,454],{"x":452,"y":329,"style":453},"682.4","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Dropdown in",[326,456,457],{"x":452,"y":334,"style":453},"B2",[326,459,460],{"x":452,"y":338,"style":339},"Paper, Toner,",[326,462,463],{"x":452,"y":343,"style":339},"Stationery",[52,465,467],{"id":466},"step-1-a-lookup-sheet-with-one-column-per-category","Step 1 — A lookup sheet with one column per category",[10,469,470,471,474],{},"Write each category's items into its own column on a sheet called ",[41,472,473],{},"Lists",", with the category name in the header row. Also write the list of categories itself, for the first dropdown:",[57,476,478],{"className":89,"code":477,"language":91,"meta":62,"style":62},"import re\nfrom openpyxl import Workbook\nfrom openpyxl.utils import get_column_letter\n\ndef safe_name(category):\n    name = re.sub(r\"[^A-Za-z0-9_]\", \"_\", category.strip())\n    return name if re.match(r\"[A-Za-z_]\", name) else f\"_{name}\"\n\ngroups = mapping.groupby(\"Category\", sort=False)[\"Item\"].apply(list)\nwb = Workbook()\nform = wb.active\nform.title = \"Claims\"\nlists = wb.create_sheet(\"Lists\")\n\nlists.cell(1, 1, \"Categories\")\nfor r, category in enumerate(groups.index, start=2):\n    lists.cell(r, 1, category)\nfor c, (category, items) in enumerate(groups.items(), start=2):\n    lists.cell(1, c, category)\n    for r, item in enumerate(items, start=2):\n        lists.cell(r, c, item)\nprint([[lists.cell(r, c).value for c in range(1, 6)] for r in range(1, 5)])\nprint({cat: safe_name(cat) for cat in groups.index})\n",[41,479,480,487,500,512,516,528,567,617,622,654,665,676,687,703,708,728,757,768,791,802,826,832,884],{"__ignoreMap":62},[66,481,482,484],{"class":68,"line":69},[66,483,99],{"class":98},[66,485,486],{"class":102}," re\n",[66,488,489,492,495,497],{"class":68,"line":112},[66,490,491],{"class":98},"from",[66,493,494],{"class":102}," openpyxl ",[66,496,99],{"class":98},[66,498,499],{"class":102}," Workbook\n",[66,501,502,504,507,509],{"class":68,"line":119},[66,503,491],{"class":98},[66,505,506],{"class":102}," openpyxl.utils ",[66,508,99],{"class":98},[66,510,511],{"class":102}," get_column_letter\n",[66,513,514],{"class":68,"line":131},[66,515,116],{"emptyLinePlaceholder":115},[66,517,518,521,525],{"class":68,"line":181},[66,519,520],{"class":98},"def",[66,522,524],{"class":523},"s_Opv"," safe_name",[66,526,527],{"class":102},"(category):\n",[66,529,530,533,535,538,541,544,547,550,554,557,559,561,564],{"class":68,"line":234},[66,531,532],{"class":102},"    name ",[66,534,125],{"class":98},[66,536,537],{"class":102}," re.sub(",[66,539,540],{"class":98},"r",[66,542,543],{"class":76},"\"",[66,545,546],{"class":243},"[",[66,548,549],{"class":98},"^",[66,551,553],{"class":552},"sSjpA","A-Za-z0-9_",[66,555,556],{"class":243},"]",[66,558,543],{"class":76},[66,560,25],{"class":102},[66,562,563],{"class":76},"\"_\"",[66,565,566],{"class":102},", category.strip())\n",[66,568,569,572,575,578,581,583,585,587,590,592,594,597,600,603,606,609,611,614],{"class":68,"line":240},[66,570,571],{"class":98},"    return",[66,573,574],{"class":102}," name ",[66,576,577],{"class":98},"if",[66,579,580],{"class":102}," re.match(",[66,582,540],{"class":98},[66,584,543],{"class":76},[66,586,546],{"class":243},[66,588,589],{"class":552},"A-Za-z_",[66,591,556],{"class":243},[66,593,543],{"class":76},[66,595,596],{"class":102},", name) ",[66,598,599],{"class":98},"else",[66,601,602],{"class":98}," f",[66,604,605],{"class":76},"\"_",[66,607,608],{"class":552},"{",[66,610,403],{"class":102},[66,612,613],{"class":552},"}",[66,615,616],{"class":76},"\"\n",[66,618,620],{"class":68,"line":619},8,[66,621,116],{"emptyLinePlaceholder":115},[66,623,625,628,630,633,635,637,639,641,643,645,647,649,651],{"class":68,"line":624},9,[66,626,627],{"class":102},"groups ",[66,629,125],{"class":98},[66,631,632],{"class":102}," mapping.groupby(",[66,634,250],{"class":76},[66,636,25],{"class":102},[66,638,256],{"class":255},[66,640,125],{"class":98},[66,642,261],{"class":243},[66,644,264],{"class":102},[66,646,267],{"class":76},[66,648,270],{"class":102},[66,650,273],{"class":243},[66,652,653],{"class":102},")\n",[66,655,657,660,662],{"class":68,"line":656},10,[66,658,659],{"class":102},"wb ",[66,661,125],{"class":98},[66,663,664],{"class":102}," Workbook()\n",[66,666,668,671,673],{"class":68,"line":667},11,[66,669,670],{"class":102},"form ",[66,672,125],{"class":98},[66,674,675],{"class":102}," wb.active\n",[66,677,679,682,684],{"class":68,"line":678},12,[66,680,681],{"class":102},"form.title ",[66,683,125],{"class":98},[66,685,686],{"class":76}," \"Claims\"\n",[66,688,690,693,695,698,701],{"class":68,"line":689},13,[66,691,692],{"class":102},"lists ",[66,694,125],{"class":98},[66,696,697],{"class":102}," wb.create_sheet(",[66,699,700],{"class":76},"\"Lists\"",[66,702,653],{"class":102},[66,704,706],{"class":68,"line":705},14,[66,707,116],{"emptyLinePlaceholder":115},[66,709,711,714,717,719,721,723,726],{"class":68,"line":710},15,[66,712,713],{"class":102},"lists.cell(",[66,715,716],{"class":243},"1",[66,718,25],{"class":102},[66,720,716],{"class":243},[66,722,25],{"class":102},[66,724,725],{"class":76},"\"Categories\"",[66,727,653],{"class":102},[66,729,731,734,737,740,743,746,749,751,754],{"class":68,"line":730},16,[66,732,733],{"class":98},"for",[66,735,736],{"class":102}," r, category ",[66,738,739],{"class":98},"in",[66,741,742],{"class":243}," enumerate",[66,744,745],{"class":102},"(groups.index, ",[66,747,748],{"class":255},"start",[66,750,125],{"class":98},[66,752,753],{"class":243},"2",[66,755,756],{"class":102},"):\n",[66,758,760,763,765],{"class":68,"line":759},17,[66,761,762],{"class":102},"    lists.cell(r, ",[66,764,716],{"class":243},[66,766,767],{"class":102},", category)\n",[66,769,771,773,776,778,780,783,785,787,789],{"class":68,"line":770},18,[66,772,733],{"class":98},[66,774,775],{"class":102}," c, (category, items) ",[66,777,739],{"class":98},[66,779,742],{"class":243},[66,781,782],{"class":102},"(groups.items(), ",[66,784,748],{"class":255},[66,786,125],{"class":98},[66,788,753],{"class":243},[66,790,756],{"class":102},[66,792,794,797,799],{"class":68,"line":793},19,[66,795,796],{"class":102},"    lists.cell(",[66,798,716],{"class":243},[66,800,801],{"class":102},", c, category)\n",[66,803,805,808,811,813,815,818,820,822,824],{"class":68,"line":804},20,[66,806,807],{"class":98},"    for",[66,809,810],{"class":102}," r, item ",[66,812,739],{"class":98},[66,814,742],{"class":243},[66,816,817],{"class":102},"(items, ",[66,819,748],{"class":255},[66,821,125],{"class":98},[66,823,753],{"class":243},[66,825,756],{"class":102},[66,827,829],{"class":68,"line":828},21,[66,830,831],{"class":102},"        lists.cell(r, c, item)\n",[66,833,835,837,840,842,845,847,850,853,855,857,860,863,865,868,870,872,874,876,878,881],{"class":68,"line":834},22,[66,836,244],{"class":243},[66,838,839],{"class":102},"([[lists.cell(r, c).value ",[66,841,733],{"class":98},[66,843,844],{"class":102}," c ",[66,846,739],{"class":98},[66,848,849],{"class":243}," range",[66,851,852],{"class":102},"(",[66,854,716],{"class":243},[66,856,25],{"class":102},[66,858,859],{"class":243},"6",[66,861,862],{"class":102},")] ",[66,864,733],{"class":98},[66,866,867],{"class":102}," r ",[66,869,739],{"class":98},[66,871,849],{"class":243},[66,873,852],{"class":102},[66,875,716],{"class":243},[66,877,25],{"class":102},[66,879,880],{"class":243},"5",[66,882,883],{"class":102},")])\n",[66,885,887,889,892,894,897,899],{"class":68,"line":886},23,[66,888,244],{"class":243},[66,890,891],{"class":102},"({cat: safe_name(cat) ",[66,893,733],{"class":98},[66,895,896],{"class":102}," cat ",[66,898,739],{"class":98},[66,900,901],{"class":102}," groups.index})\n",[10,903,904,907,908,911,912,914],{},[41,905,906],{},"safe_name"," turns each category into a valid Excel name: letters, digits and underscores only, not starting with a digit — so ",[41,909,910],{},"Office supplies"," becomes ",[41,913,374],{},". The mapping between the text people see and the name Excel looks up is the one fragile point of the technique, which is why it lives in one function and the same rule is applied inside the validation formula in Step 3.",[52,916,918],{"id":917},"step-2-one-named-range-per-category","Step 2 — One named range per category",[10,920,921],{},"Define a workbook-level name for the list of categories and one for each category's items, each covering exactly the filled cells of its column:",[57,923,925],{"className":89,"code":924,"language":91,"meta":62,"style":62},"from openpyxl.workbook.defined_name import DefinedName\n\nwb.defined_names[\"Categories\"] = DefinedName(\"Categories\", attr_text=f\"Lists!$A$2:$A${len(groups) + 1}\")\nfor c, (category, items) in enumerate(groups.items(), start=2):\n    col = get_column_letter(c)\n    ref = f\"Lists!${col}$2:${col}${len(items) + 1}\"\n    wb.defined_names[safe_name(category)] = DefinedName(safe_name(category), attr_text=ref)\nprint({n: d.attr_text for n, d in wb.defined_names.items()})\n",[41,926,927,939,943,993,1013,1023,1069,1086],{"__ignoreMap":62},[66,928,929,931,934,936],{"class":68,"line":69},[66,930,491],{"class":98},[66,932,933],{"class":102}," openpyxl.workbook.defined_name ",[66,935,99],{"class":98},[66,937,938],{"class":102}," DefinedName\n",[66,940,941],{"class":68,"line":112},[66,942,116],{"emptyLinePlaceholder":115},[66,944,945,948,950,953,955,958,960,962,965,967,970,973,975,978,981,984,987,989,991],{"class":68,"line":119},[66,946,947],{"class":102},"wb.defined_names[",[66,949,725],{"class":76},[66,951,952],{"class":102},"] ",[66,954,125],{"class":98},[66,956,957],{"class":102}," DefinedName(",[66,959,725],{"class":76},[66,961,25],{"class":102},[66,963,964],{"class":255},"attr_text",[66,966,125],{"class":98},[66,968,969],{"class":98},"f",[66,971,972],{"class":76},"\"Lists!$A$2:$A$",[66,974,608],{"class":552},[66,976,977],{"class":243},"len",[66,979,980],{"class":102},"(groups) ",[66,982,983],{"class":98},"+",[66,985,986],{"class":243}," 1",[66,988,613],{"class":552},[66,990,543],{"class":76},[66,992,653],{"class":102},[66,994,995,997,999,1001,1003,1005,1007,1009,1011],{"class":68,"line":131},[66,996,733],{"class":98},[66,998,775],{"class":102},[66,1000,739],{"class":98},[66,1002,742],{"class":243},[66,1004,782],{"class":102},[66,1006,748],{"class":255},[66,1008,125],{"class":98},[66,1010,753],{"class":243},[66,1012,756],{"class":102},[66,1014,1015,1018,1020],{"class":68,"line":181},[66,1016,1017],{"class":102},"    col ",[66,1019,125],{"class":98},[66,1021,1022],{"class":102}," get_column_letter(c)\n",[66,1024,1025,1028,1030,1032,1035,1037,1040,1042,1045,1047,1049,1051,1054,1056,1058,1061,1063,1065,1067],{"class":68,"line":234},[66,1026,1027],{"class":102},"    ref ",[66,1029,125],{"class":98},[66,1031,602],{"class":98},[66,1033,1034],{"class":76},"\"Lists!$",[66,1036,608],{"class":552},[66,1038,1039],{"class":102},"col",[66,1041,613],{"class":552},[66,1043,1044],{"class":76},"$2:$",[66,1046,608],{"class":552},[66,1048,1039],{"class":102},[66,1050,613],{"class":552},[66,1052,1053],{"class":76},"$",[66,1055,608],{"class":552},[66,1057,977],{"class":243},[66,1059,1060],{"class":102},"(items) ",[66,1062,983],{"class":98},[66,1064,986],{"class":243},[66,1066,613],{"class":552},[66,1068,616],{"class":76},[66,1070,1071,1074,1076,1079,1081,1083],{"class":68,"line":240},[66,1072,1073],{"class":102},"    wb.defined_names[safe_name(category)] ",[66,1075,125],{"class":98},[66,1077,1078],{"class":102}," DefinedName(safe_name(category), ",[66,1080,964],{"class":255},[66,1082,125],{"class":98},[66,1084,1085],{"class":102},"ref)\n",[66,1087,1088,1090,1093,1095,1098,1100],{"class":68,"line":619},[66,1089,244],{"class":243},[66,1091,1092],{"class":102},"({n: d.attr_text ",[66,1094,733],{"class":98},[66,1096,1097],{"class":102}," n, d ",[66,1099,739],{"class":98},[66,1101,1102],{"class":102}," wb.defined_names.items()})\n",[10,1104,1105,1106,50],{},"Each range stops at the last item of its category, so the dropdown shows no blank entries at the bottom of the shorter lists. Named ranges are covered in more depth in ",[46,1107,1109],{"href":1108},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fcreate-named-range-in-excel-with-openpyxl\u002F","Create a Named Range in Excel with openpyxl",[52,1111,1113],{"id":1112},"step-3-the-two-validation-rules","Step 3 — The two validation rules",[10,1115,1116,1117,1120,1121,1123,1124,1127,1128,1130],{},"The category column validates against ",[41,1118,1119],{},"=Categories",". The item column validates against ",[41,1122,43],{}," of the category in the same row — converted to its safe name with ",[41,1125,1126],{},"SUBSTITUTE",", mirroring ",[41,1129,906],{}," for the one character the example needs, the space:",[57,1132,1134],{"className":89,"code":1133,"language":91,"meta":62,"style":62},"from openpyxl.worksheet.datavalidation import DataValidation\n\nform.append([\"Category\", \"Item\", \"Amount\", \"Date\"])\nform.column_dimensions[\"A\"].width = 18\nform.column_dimensions[\"B\"].width = 16\n\ncat_rule = DataValidation(type=\"list\", formula1=\"=Categories\", allow_blank=True,\n                          showErrorMessage=True, errorTitle=\"Unknown category\",\n                          error=\"Choose a category from the list.\")\nitem_rule = DataValidation(type=\"list\", formula1='=INDIRECT(SUBSTITUTE($A2,\" \",\"_\"))', allow_blank=True,\n                           showErrorMessage=True, errorTitle=\"Item not in category\",\n                           error=\"Pick the category first, then an item from its list.\")\nform.add_data_validation(cat_rule)\nform.add_data_validation(item_rule)\ncat_rule.add(\"A2:A500\")\nitem_rule.add(\"B2:B500\")\nlists.sheet_state = \"hidden\"\nwb.save(\"claims_template.xlsx\")\nprint(cat_rule.sqref, item_rule.formula1)\n",[41,1135,1136,1148,1152,1176,1192,1206,1210,1251,1272,1284,1318,1338,1350,1355,1360,1370,1380,1390,1400],{"__ignoreMap":62},[66,1137,1138,1140,1143,1145],{"class":68,"line":69},[66,1139,491],{"class":98},[66,1141,1142],{"class":102}," openpyxl.worksheet.datavalidation ",[66,1144,99],{"class":98},[66,1146,1147],{"class":102}," DataValidation\n",[66,1149,1150],{"class":68,"line":112},[66,1151,116],{"emptyLinePlaceholder":115},[66,1153,1154,1157,1159,1161,1163,1165,1168,1170,1173],{"class":68,"line":119},[66,1155,1156],{"class":102},"form.append([",[66,1158,250],{"class":76},[66,1160,25],{"class":102},[66,1162,267],{"class":76},[66,1164,25],{"class":102},[66,1166,1167],{"class":76},"\"Amount\"",[66,1169,25],{"class":102},[66,1171,1172],{"class":76},"\"Date\"",[66,1174,1175],{"class":102},"])\n",[66,1177,1178,1181,1184,1187,1189],{"class":68,"line":131},[66,1179,1180],{"class":102},"form.column_dimensions[",[66,1182,1183],{"class":76},"\"A\"",[66,1185,1186],{"class":102},"].width ",[66,1188,125],{"class":98},[66,1190,1191],{"class":243}," 18\n",[66,1193,1194,1196,1199,1201,1203],{"class":68,"line":181},[66,1195,1180],{"class":102},[66,1197,1198],{"class":76},"\"B\"",[66,1200,1186],{"class":102},[66,1202,125],{"class":98},[66,1204,1205],{"class":243}," 16\n",[66,1207,1208],{"class":68,"line":234},[66,1209,116],{"emptyLinePlaceholder":115},[66,1211,1212,1215,1217,1220,1223,1225,1228,1230,1233,1235,1238,1240,1243,1245,1248],{"class":68,"line":240},[66,1213,1214],{"class":102},"cat_rule ",[66,1216,125],{"class":98},[66,1218,1219],{"class":102}," DataValidation(",[66,1221,1222],{"class":255},"type",[66,1224,125],{"class":98},[66,1226,1227],{"class":76},"\"list\"",[66,1229,25],{"class":102},[66,1231,1232],{"class":255},"formula1",[66,1234,125],{"class":98},[66,1236,1237],{"class":76},"\"=Categories\"",[66,1239,25],{"class":102},[66,1241,1242],{"class":255},"allow_blank",[66,1244,125],{"class":98},[66,1246,1247],{"class":243},"True",[66,1249,1250],{"class":102},",\n",[66,1252,1253,1256,1258,1260,1262,1265,1267,1270],{"class":68,"line":619},[66,1254,1255],{"class":255},"                          showErrorMessage",[66,1257,125],{"class":98},[66,1259,1247],{"class":243},[66,1261,25],{"class":102},[66,1263,1264],{"class":255},"errorTitle",[66,1266,125],{"class":98},[66,1268,1269],{"class":76},"\"Unknown category\"",[66,1271,1250],{"class":102},[66,1273,1274,1277,1279,1282],{"class":68,"line":624},[66,1275,1276],{"class":255},"                          error",[66,1278,125],{"class":98},[66,1280,1281],{"class":76},"\"Choose a category from the list.\"",[66,1283,653],{"class":102},[66,1285,1286,1289,1291,1293,1295,1297,1299,1301,1303,1305,1308,1310,1312,1314,1316],{"class":68,"line":656},[66,1287,1288],{"class":102},"item_rule ",[66,1290,125],{"class":98},[66,1292,1219],{"class":102},[66,1294,1222],{"class":255},[66,1296,125],{"class":98},[66,1298,1227],{"class":76},[66,1300,25],{"class":102},[66,1302,1232],{"class":255},[66,1304,125],{"class":98},[66,1306,1307],{"class":76},"'=INDIRECT(SUBSTITUTE($A2,\" \",\"_\"))'",[66,1309,25],{"class":102},[66,1311,1242],{"class":255},[66,1313,125],{"class":98},[66,1315,1247],{"class":243},[66,1317,1250],{"class":102},[66,1319,1320,1323,1325,1327,1329,1331,1333,1336],{"class":68,"line":667},[66,1321,1322],{"class":255},"                           showErrorMessage",[66,1324,125],{"class":98},[66,1326,1247],{"class":243},[66,1328,25],{"class":102},[66,1330,1264],{"class":255},[66,1332,125],{"class":98},[66,1334,1335],{"class":76},"\"Item not in category\"",[66,1337,1250],{"class":102},[66,1339,1340,1343,1345,1348],{"class":68,"line":678},[66,1341,1342],{"class":255},"                           error",[66,1344,125],{"class":98},[66,1346,1347],{"class":76},"\"Pick the category first, then an item from its list.\"",[66,1349,653],{"class":102},[66,1351,1352],{"class":68,"line":689},[66,1353,1354],{"class":102},"form.add_data_validation(cat_rule)\n",[66,1356,1357],{"class":68,"line":705},[66,1358,1359],{"class":102},"form.add_data_validation(item_rule)\n",[66,1361,1362,1365,1368],{"class":68,"line":710},[66,1363,1364],{"class":102},"cat_rule.add(",[66,1366,1367],{"class":76},"\"A2:A500\"",[66,1369,653],{"class":102},[66,1371,1372,1375,1378],{"class":68,"line":730},[66,1373,1374],{"class":102},"item_rule.add(",[66,1376,1377],{"class":76},"\"B2:B500\"",[66,1379,653],{"class":102},[66,1381,1382,1385,1387],{"class":68,"line":759},[66,1383,1384],{"class":102},"lists.sheet_state ",[66,1386,125],{"class":98},[66,1388,1389],{"class":76}," \"hidden\"\n",[66,1391,1392,1395,1398],{"class":68,"line":770},[66,1393,1394],{"class":102},"wb.save(",[66,1396,1397],{"class":76},"\"claims_template.xlsx\"",[66,1399,653],{"class":102},[66,1401,1402,1404],{"class":68,"line":793},[66,1403,244],{"class":243},[66,1405,1406],{"class":102},"(cat_rule.sqref, item_rule.formula1)\n",[10,1408,1409,1412,1413,1416,1417,1420,1421,1423,1424,1427,1428,1430,1431,1433],{},[41,1410,1411],{},"$A2"," is relative in its row: the rule is written once for ",[41,1414,1415],{},"B2:B500",", and Excel adjusts it so row 7's dropdown reads ",[41,1418,1419],{},"A7",". The error messages turn a rejected entry into an instruction. Hiding the ",[41,1422,473],{}," sheet keeps the lookup tables out of the way while leaving them readable by the validation; ",[41,1425,1426],{},"veryHidden"," would also stop users unhiding it from the menu. If category names can contain other characters — hyphens, ampersands — either restrict the names or add a hidden helper column that holds each category's safe name and point ",[41,1429,43],{}," at that instead of nesting more ",[41,1432,1126],{}," calls.",[278,1435,287,1441,287,1444,287,1447,287,1452,287,1459,287,1464,287,1471,287,1477,287,1483,287,1486,287,1492,287,1496,287,1500,287,1504,287,1508,287,1511,287,1515,287,1519,287,1523,287,1527,287,1531,287,1533,287,1537,287,1539,287,1541,287,1543,287,1545,287,1547,287,1549,287,1551,287,1554,287,1557,287,1560,287,1562,287,1565,287,1567,287,1569,287,1571,287,1573,287,1575,287,1579,287,1581,287,1584,287,1587,287,1591,287,1593,287,1595,287,1597,287,1599,287,1601,287,1603,287,1605,287,1608,287,1610,287,1613,287,1616,287,1618,287,1620,287,1622,287,1625,287,1627,287,1629,287,1631,287,1633,287,1636,287,1639,287,1641,287,1643,287,1645,287,1647,287,1649,287,1651,287,1656,287,1662,287,1666,287,1672,287,1676,287,1680,287,1683,287,1688,287,1693,287,1696],{"viewBox":1436,"role":281,"ariaLabelledBy":1437,"xmlns":285,"style":1440},"106 0 547 381",[1438,1439],"dd-lists-t","dd-lists-d","width:100%;max-width:547px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[289,1442,1443],{"id":1438},"The hidden Lists sheet",[293,1445,1446],{"id":1439},"A lookup sheet with the list of categories in column A and one column per category holding its items, each column covered by a named range.",[297,1448],{"x":1449,"y":300,"width":1450,"height":1451,"fill":303},"106","547","381",[297,1453],{"x":1454,"y":307,"width":1455,"height":1456,"rx":1457,"fill":311,"stroke":312,"style":1458},"122.9","514.2","30.0","8","stroke-width:1.5px",[297,1460],{"x":1454,"y":1461,"width":1455,"height":1462,"rx":300,"fill":311,"stroke":312,"style":1463},"38.0","8.0","stroke-width:0px",[315,1465,1467],{"transform":1466},"translate(132.9,23.0) scale(0.667)",[319,1468],{"d":1469,"fill":322,"stroke":422,"style":1470},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[326,1472,1476],{"x":1473,"y":1474,"style":1475},"154.9","36.0","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","claims_template.xlsx",[297,1478],{"x":1454,"y":1479,"width":1480,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"46.0","32.0","24.0","stroke-width:1px",[297,1484],{"x":1473,"y":1479,"width":1485,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"117.2",[326,1487,1491],{"x":1488,"y":1489,"style":1490},"213.5","63.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[297,1493],{"x":1494,"y":1479,"width":1495,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"272.1","63.3",[326,1497,1499],{"x":1498,"y":1489,"style":1490},"303.8","B",[297,1501],{"x":1502,"y":1479,"width":1503,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"335.5","84.5",[326,1505,1507],{"x":1506,"y":1489,"style":1490},"377.7","C",[297,1509],{"x":1510,"y":1479,"width":1485,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"419.9",[326,1512,1514],{"x":1513,"y":1489,"style":1490},"478.6","D",[297,1516],{"x":1517,"y":1479,"width":1518,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"537.2","99.9",[326,1520,1522],{"x":1521,"y":1489,"style":1490},"587.1","E",[297,1524],{"x":1454,"y":1525,"width":1480,"height":1526,"rx":300,"fill":311,"stroke":312,"style":1482},"70.0","28.0",[326,1528,716],{"x":1529,"y":1530,"style":1490},"138.9","89.0",[297,1532],{"x":1473,"y":1525,"width":1485,"height":1526,"rx":300,"fill":359,"stroke":350,"style":1458},[326,1534,1536],{"x":1488,"y":1530,"style":1535},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Categories",[297,1538],{"x":1494,"y":1525,"width":1495,"height":1526,"rx":300,"fill":359,"stroke":350,"style":1458},[326,1540,16],{"x":1498,"y":1530,"style":1535},[297,1542],{"x":1502,"y":1525,"width":1503,"height":1526,"rx":300,"fill":359,"stroke":350,"style":1458},[326,1544,28],{"x":1506,"y":1530,"style":1535},[297,1546],{"x":1510,"y":1525,"width":1485,"height":1526,"rx":300,"fill":359,"stroke":350,"style":1458},[326,1548,910],{"x":1513,"y":1530,"style":1535},[297,1550],{"x":1517,"y":1525,"width":1518,"height":1526,"rx":300,"fill":359,"stroke":350,"style":1458},[326,1552,1553],{"x":1521,"y":1530,"style":1535},"Client meals",[297,1555],{"x":1454,"y":1556,"width":1480,"height":1526,"rx":300,"fill":311,"stroke":312,"style":1482},"98.0",[326,1558,753],{"x":1529,"y":1559,"style":1490},"117.0",[297,1561],{"x":1473,"y":1556,"width":1485,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1563,16],{"x":1488,"y":1559,"style":1564},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[297,1566],{"x":1494,"y":1556,"width":1495,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1568,20],{"x":1498,"y":1559,"style":1564},[297,1570],{"x":1502,"y":1556,"width":1503,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1572,31],{"x":1506,"y":1559,"style":1564},[297,1574],{"x":1510,"y":1556,"width":1485,"height":1526,"rx":300,"fill":414,"stroke":415,"style":1458},[326,1576,1578],{"x":1513,"y":1559,"style":1577},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Paper",[297,1580],{"x":1517,"y":1556,"width":1518,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1582,1583],{"x":1521,"y":1559,"style":1564},"Lunch",[297,1585],{"x":1454,"y":1586,"width":1480,"height":1526,"rx":300,"fill":311,"stroke":312,"style":1482},"126.0",[326,1588,1590],{"x":1529,"y":1589,"style":1490},"145.0","3",[297,1592],{"x":1473,"y":1586,"width":1485,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1594,28],{"x":1488,"y":1589,"style":1564},[297,1596],{"x":1494,"y":1586,"width":1495,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1598,24],{"x":1498,"y":1589,"style":1564},[297,1600],{"x":1502,"y":1586,"width":1503,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1602,34],{"x":1506,"y":1589,"style":1564},[297,1604],{"x":1510,"y":1586,"width":1485,"height":1526,"rx":300,"fill":414,"stroke":415,"style":1458},[326,1606,1607],{"x":1513,"y":1589,"style":1577},"Toner",[297,1609],{"x":1517,"y":1586,"width":1518,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[297,1611],{"x":1454,"y":1612,"width":1480,"height":1526,"rx":300,"fill":311,"stroke":312,"style":1482},"154.0",[326,1614,299],{"x":1529,"y":1615,"style":1490},"173.0",[297,1617],{"x":1473,"y":1612,"width":1485,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1619,910],{"x":1488,"y":1615,"style":1564},[297,1621],{"x":1494,"y":1612,"width":1495,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1623,1624],{"x":1498,"y":1615,"style":1564},"Flight",[297,1626],{"x":1502,"y":1612,"width":1503,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[297,1628],{"x":1510,"y":1612,"width":1485,"height":1526,"rx":300,"fill":414,"stroke":415,"style":1458},[326,1630,463],{"x":1513,"y":1615,"style":1577},[297,1632],{"x":1517,"y":1612,"width":1518,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[297,1634],{"x":1454,"y":1635,"width":1480,"height":1526,"rx":300,"fill":311,"stroke":312,"style":1482},"182.0",[326,1637,880],{"x":1529,"y":1638,"style":1490},"201.0",[297,1640],{"x":1473,"y":1635,"width":1485,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,1642,1553],{"x":1488,"y":1638,"style":1564},[297,1644],{"x":1494,"y":1635,"width":1495,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[297,1646],{"x":1502,"y":1635,"width":1503,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[297,1648],{"x":1510,"y":1635,"width":1485,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[297,1650],{"x":1517,"y":1635,"width":1518,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[297,1652],{"x":1653,"y":1654,"width":1655,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"130.9","210.0","62.4",[326,1657,1661],{"x":1658,"y":1659,"style":1660},"162.1","226.5","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Claims",[297,1663],{"x":1664,"y":1654,"width":1665,"height":1481,"rx":300,"fill":303,"stroke":312,"style":1482},"195.3","52.0",[68,1667],{"x1":1668,"y1":1669,"x2":1670,"y2":1669,"stroke":415,"style":1671},"197.3","233.0","245.3","stroke-width:3px",[326,1673,473],{"x":1674,"y":1659,"style":1675},"221.3","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[68,1677],{"x1":393,"y1":1678,"x2":393,"y2":1679,"stroke":350,"style":313},"240.0","264.0",[352,1681],{"points":1682,"fill":355},"380.0,268.0 375.0,259.0 385.0,259.0",[297,1684],{"x":1685,"y":1686,"width":1687,"height":334,"rx":310,"fill":414,"stroke":415,"style":313},"140.0","272.0","480.0",[315,1689,1691],{"transform":1690},"translate(368.0,285.0) scale(1.000)",[319,1692],{"d":390,"fill":322,"stroke":422,"style":324},[326,1694,374],{"x":393,"y":1695,"style":426},"331.5",[326,1697,1699],{"x":393,"y":1698,"style":339},"351.0","= Lists!$D$2:$D$4",[52,1701,1703],{"id":1702},"step-4-prove-every-category-resolves","Step 4 — Prove every category resolves",[10,1705,1706,1707,1710],{},"A validation list that points at a name that does not exist simply shows an empty dropdown — no error, just a confused user. Check the formula for every category by letting LibreOffice calculate it: write each category into a scratch copy with a ",[41,1708,1709],{},"ROWS(INDIRECT(...))"," formula beside it and compare the counts with the mapping:",[57,1712,1714],{"className":89,"code":1713,"language":91,"meta":62,"style":62},"from openpyxl import load_workbook\n\ncheck = load_workbook(\"claims_template.xlsx\")\nws = check[\"Claims\"]\nfor r, category in enumerate(groups.index, start=2):\n    ws.cell(r, 1, category)\n    ws.cell(r, 6, f'=ROWS(INDIRECT(SUBSTITUTE(A{r},\" \",\"_\")))')\n    ws.cell(r, 7, f'=INDEX(INDIRECT(SUBSTITUTE(A{r},\" \",\"_\")),1)')\ncheck.save(\"claims_check.xlsx\")\n",[41,1715,1716,1727,1731,1745,1761,1781,1790,1814,1839],{"__ignoreMap":62},[66,1717,1718,1720,1722,1724],{"class":68,"line":69},[66,1719,491],{"class":98},[66,1721,494],{"class":102},[66,1723,99],{"class":98},[66,1725,1726],{"class":102}," load_workbook\n",[66,1728,1729],{"class":68,"line":112},[66,1730,116],{"emptyLinePlaceholder":115},[66,1732,1733,1736,1738,1741,1743],{"class":68,"line":119},[66,1734,1735],{"class":102},"check ",[66,1737,125],{"class":98},[66,1739,1740],{"class":102}," load_workbook(",[66,1742,1397],{"class":76},[66,1744,653],{"class":102},[66,1746,1747,1750,1752,1755,1758],{"class":68,"line":131},[66,1748,1749],{"class":102},"ws ",[66,1751,125],{"class":98},[66,1753,1754],{"class":102}," check[",[66,1756,1757],{"class":76},"\"Claims\"",[66,1759,1760],{"class":102},"]\n",[66,1762,1763,1765,1767,1769,1771,1773,1775,1777,1779],{"class":68,"line":181},[66,1764,733],{"class":98},[66,1766,736],{"class":102},[66,1768,739],{"class":98},[66,1770,742],{"class":243},[66,1772,745],{"class":102},[66,1774,748],{"class":255},[66,1776,125],{"class":98},[66,1778,753],{"class":243},[66,1780,756],{"class":102},[66,1782,1783,1786,1788],{"class":68,"line":234},[66,1784,1785],{"class":102},"    ws.cell(r, ",[66,1787,716],{"class":243},[66,1789,767],{"class":102},[66,1791,1792,1794,1796,1798,1800,1803,1805,1807,1809,1812],{"class":68,"line":240},[66,1793,1785],{"class":102},[66,1795,859],{"class":243},[66,1797,25],{"class":102},[66,1799,969],{"class":98},[66,1801,1802],{"class":76},"'=ROWS(INDIRECT(SUBSTITUTE(A",[66,1804,608],{"class":552},[66,1806,540],{"class":102},[66,1808,613],{"class":552},[66,1810,1811],{"class":76},",\" \",\"_\")))'",[66,1813,653],{"class":102},[66,1815,1816,1818,1821,1823,1825,1828,1830,1832,1834,1837],{"class":68,"line":619},[66,1817,1785],{"class":102},[66,1819,1820],{"class":243},"7",[66,1822,25],{"class":102},[66,1824,969],{"class":98},[66,1826,1827],{"class":76},"'=INDEX(INDIRECT(SUBSTITUTE(A",[66,1829,608],{"class":552},[66,1831,540],{"class":102},[66,1833,613],{"class":552},[66,1835,1836],{"class":76},",\" \",\"_\")),1)'",[66,1838,653],{"class":102},[66,1840,1841,1844,1847],{"class":68,"line":624},[66,1842,1843],{"class":102},"check.save(",[66,1845,1846],{"class":76},"\"claims_check.xlsx\"",[66,1848,653],{"class":102},[57,1850,1852],{"className":59,"code":1851,"language":61,"meta":62,"style":62},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to xlsx --outdir calc claims_check.xlsx > \u002Fdev\u002Fnull 2>&1\nls calc\n",[41,1853,1854,1895],{"__ignoreMap":62},[66,1855,1856,1859,1862,1865,1868,1871,1874,1877,1880,1883,1886,1889,1892],{"class":68,"line":69},[66,1857,1858],{"class":72},"soffice",[66,1860,1861],{"class":243}," -env:UserInstallation=file:\u002F\u002F",[66,1863,1864],{"class":102},"$PWD",[66,1866,1867],{"class":243},"\u002Flo_profile",[66,1869,1870],{"class":243}," --headless",[66,1872,1873],{"class":243}," --convert-to",[66,1875,1876],{"class":76}," xlsx",[66,1878,1879],{"class":243}," --outdir",[66,1881,1882],{"class":76}," calc",[66,1884,1885],{"class":76}," claims_check.xlsx",[66,1887,1888],{"class":98}," >",[66,1890,1891],{"class":76}," \u002Fdev\u002Fnull",[66,1893,1894],{"class":98}," 2>&1\n",[66,1896,1897,1900],{"class":68,"line":112},[66,1898,1899],{"class":72},"ls",[66,1901,1902],{"class":76}," calc\n",[57,1904,1906],{"className":89,"code":1905,"language":91,"meta":62,"style":62},"calc = load_workbook(\"calc\u002Fclaims_check.xlsx\", data_only=True)[\"Claims\"]\nfor r, (category, items) in enumerate(groups.items(), start=2):\n    print(f\"{category:16} items={calc.cell(r, 6).value} expected={len(items)} first={calc.cell(r, 7).value}\")\n",[41,1907,1908,1935,1956],{"__ignoreMap":62},[66,1909,1910,1913,1915,1917,1920,1922,1925,1927,1929,1931,1933],{"class":68,"line":69},[66,1911,1912],{"class":102},"calc ",[66,1914,125],{"class":98},[66,1916,1740],{"class":102},[66,1918,1919],{"class":76},"\"calc\u002Fclaims_check.xlsx\"",[66,1921,25],{"class":102},[66,1923,1924],{"class":255},"data_only",[66,1926,125],{"class":98},[66,1928,1247],{"class":243},[66,1930,264],{"class":102},[66,1932,1757],{"class":76},[66,1934,1760],{"class":102},[66,1936,1937,1939,1942,1944,1946,1948,1950,1952,1954],{"class":68,"line":112},[66,1938,733],{"class":98},[66,1940,1941],{"class":102}," r, (category, items) ",[66,1943,739],{"class":98},[66,1945,742],{"class":243},[66,1947,782],{"class":102},[66,1949,748],{"class":255},[66,1951,125],{"class":98},[66,1953,753],{"class":243},[66,1955,756],{"class":102},[66,1957,1958,1961,1963,1965,1967,1969,1971,1974,1976,1979,1981,1984,1986,1989,1991,1994,1996,1998,2001,2003,2006,2008,2010,2012,2014,2016,2018],{"class":68,"line":119},[66,1959,1960],{"class":243},"    print",[66,1962,852],{"class":102},[66,1964,969],{"class":98},[66,1966,543],{"class":76},[66,1968,608],{"class":552},[66,1970,335],{"class":102},[66,1972,1973],{"class":98},":16",[66,1975,613],{"class":552},[66,1977,1978],{"class":76}," items=",[66,1980,608],{"class":552},[66,1982,1983],{"class":102},"calc.cell(r, ",[66,1985,859],{"class":243},[66,1987,1988],{"class":102},").value",[66,1990,613],{"class":552},[66,1992,1993],{"class":76}," expected=",[66,1995,608],{"class":552},[66,1997,977],{"class":243},[66,1999,2000],{"class":102},"(items)",[66,2002,613],{"class":552},[66,2004,2005],{"class":76}," first=",[66,2007,608],{"class":552},[66,2009,1983],{"class":102},[66,2011,1820],{"class":243},[66,2013,1988],{"class":102},[66,2015,613],{"class":552},[66,2017,543],{"class":76},[66,2019,653],{"class":102},[10,2021,2022,2023,2025,2026,2030],{},"Every category’s ",[41,2024,43],{}," resolves to a range with the right number of items and the right first item — including the two names with spaces, and the single-item Client meals list. Run this check whenever the mapping changes; it catches a category whose safe name was altered or a name range written one row short. ",[46,2027,2029],{"href":2028},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Frecalculate-excel-formulas-without-excel-in-python\u002F","Recalculate Excel Formulas Without Excel"," explains the recalculation step.",[278,2032,287,2038,287,2041,287,2044,287,2049,287,2053,287,2055,287,2060,287,2063,287,2065,287,2067,287,2070,287,2074,287,2077,287,2081,287,2084,287,2088,287,2091,287,2093,287,2096,287,2098,287,2101,287,2103,287,2106,287,2108,287,2111,287,2113,287,2116,287,2118,287,2120,287,2122,287,2124,287,2126,287,2128,287,2130,287,2133,287,2135,287,2138,287,2140,287,2142,287,2144,287,2146,287,2148,287,2150,287,2152,287,2155,287,2157,287,2160,287,2162,287,2164,287,2166,287,2168,287,2170,287,2172,287,2174,287,2178,287,2183,287,2187,287,2191,287,2194,287,2199,287,2202,287,2208,287,2213,287,2217,287,2221],{"viewBox":2033,"role":281,"ariaLabelledBy":2034,"xmlns":285,"style":2037},"61 0 637 222",[2035,2036],"dd-form-t","dd-form-d","width:100%;max-width:637px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[289,2039,2040],{"id":2035},"The Claims sheet in use",[293,2042,2043],{"id":2036},"The form sheet with a category and an item in each row. Row 2 has Travel and Rail, row 3 Office supplies and Toner; the dropdown for row 3's item offers only Paper, Toner and Stationery.",[297,2045],{"x":2046,"y":300,"width":2047,"height":2048,"fill":303},"61","637","222",[297,2050],{"x":2051,"y":307,"width":2052,"height":1456,"rx":1457,"fill":311,"stroke":312,"style":1458},"77.9","384.3",[297,2054],{"x":2051,"y":1461,"width":2052,"height":1462,"rx":300,"fill":311,"stroke":312,"style":1463},[315,2056,2058],{"transform":2057},"translate(87.9,23.0) scale(0.667)",[319,2059],{"d":1469,"fill":322,"stroke":422,"style":1470},[326,2061,1476],{"x":2062,"y":1474,"style":1475},"109.9",[297,2064],{"x":2051,"y":1479,"width":1480,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},[297,2066],{"x":2062,"y":1479,"width":1485,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},[326,2068,1491],{"x":2069,"y":1489,"style":1490},"168.5",[297,2071],{"x":2072,"y":1479,"width":2073,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"227.1","60.6",[326,2075,1499],{"x":2076,"y":1489,"style":1490},"257.4",[297,2078],{"x":2079,"y":1479,"width":2080,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"287.7","73.0",[326,2082,1507],{"x":2083,"y":1489,"style":1490},"324.2",[297,2085],{"x":2086,"y":1479,"width":2087,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"360.7","101.5",[326,2089,1514],{"x":2090,"y":1489,"style":1490},"411.4",[297,2092],{"x":2051,"y":1525,"width":1480,"height":1526,"rx":300,"fill":311,"stroke":312,"style":1482},[326,2094,716],{"x":2095,"y":1530,"style":1490},"93.9",[297,2097],{"x":2062,"y":1525,"width":1485,"height":1526,"rx":300,"fill":359,"stroke":350,"style":1458},[326,2099,2100],{"x":2069,"y":1530,"style":1535},"Category",[297,2102],{"x":2072,"y":1525,"width":2073,"height":1526,"rx":300,"fill":359,"stroke":350,"style":1458},[326,2104,2105],{"x":2076,"y":1530,"style":1535},"Item",[297,2107],{"x":2079,"y":1525,"width":2080,"height":1526,"rx":300,"fill":359,"stroke":350,"style":1458},[326,2109,2110],{"x":2083,"y":1530,"style":1535},"Amount",[297,2112],{"x":2086,"y":1525,"width":2087,"height":1526,"rx":300,"fill":359,"stroke":350,"style":1458},[326,2114,2115],{"x":2090,"y":1530,"style":1535},"Date",[297,2117],{"x":2051,"y":1556,"width":1480,"height":1526,"rx":300,"fill":311,"stroke":312,"style":1482},[326,2119,753],{"x":2095,"y":1559,"style":1490},[297,2121],{"x":2062,"y":1556,"width":1485,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,2123,16],{"x":2069,"y":1559,"style":1564},[297,2125],{"x":2072,"y":1556,"width":2073,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,2127,20],{"x":2076,"y":1559,"style":1564},[297,2129],{"x":2079,"y":1556,"width":2080,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,2131,2132],{"x":2083,"y":1559,"style":1564},"84.50",[297,2134],{"x":2086,"y":1556,"width":2087,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,2136,2137],{"x":2090,"y":1559,"style":1564},"2026-09-21",[297,2139],{"x":2051,"y":1586,"width":1480,"height":1526,"rx":300,"fill":311,"stroke":312,"style":1482},[326,2141,1590],{"x":2095,"y":1589,"style":1490},[297,2143],{"x":2062,"y":1586,"width":1485,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,2145,910],{"x":2069,"y":1589,"style":1564},[297,2147],{"x":2072,"y":1586,"width":2073,"height":1526,"rx":300,"fill":414,"stroke":415,"style":1458},[326,2149,1607],{"x":2076,"y":1589,"style":1577},[297,2151],{"x":2079,"y":1586,"width":2080,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,2153,2154],{"x":2083,"y":1589,"style":1564},"39.99",[297,2156],{"x":2086,"y":1586,"width":2087,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,2158,2159],{"x":2090,"y":1589,"style":1564},"2026-09-24",[297,2161],{"x":2051,"y":1612,"width":1480,"height":1526,"rx":300,"fill":311,"stroke":312,"style":1482},[326,2163,299],{"x":2095,"y":1615,"style":1490},[297,2165],{"x":2062,"y":1612,"width":1485,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[326,2167,28],{"x":2069,"y":1615,"style":1564},[297,2169],{"x":2072,"y":1612,"width":2073,"height":1526,"rx":300,"fill":441,"stroke":442,"style":1458},[297,2171],{"x":2079,"y":1612,"width":2080,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[297,2173],{"x":2086,"y":1612,"width":2087,"height":1526,"rx":300,"fill":303,"stroke":312,"style":1482},[297,2175],{"x":2176,"y":1635,"width":2177,"height":1481,"rx":300,"fill":303,"stroke":312,"style":1482},"85.9","64.3",[68,2179],{"x1":2180,"y1":2181,"x2":2182,"y2":2181,"stroke":415,"style":1671},"87.9","205.0","148.1",[326,2184,1661],{"x":2185,"y":2186,"style":1675},"118.0","198.5",[297,2188],{"x":2189,"y":1635,"width":2190,"height":1481,"rx":300,"fill":311,"stroke":312,"style":1482},"152.1","50.4",[326,2192,473],{"x":2193,"y":2186,"style":1660},"177.4",[68,2195],{"x1":2196,"y1":2197,"x2":2198,"y2":2197,"stroke":350,"style":313},"470.1","111.0","502.1",[352,2200],{"points":2201,"fill":355},"506.1,111.0 497.1,116.0 497.1,106.0",[297,2203],{"x":2204,"y":2205,"width":2206,"height":2207,"rx":310,"fill":441,"stroke":442,"style":313},"512.1","55.8","170.0","110.5",[315,2209,2211],{"transform":2210},"translate(585.1,68.8) scale(1.000)",[319,2212],{"d":321,"fill":322,"stroke":449,"style":324},[326,2214,2216],{"x":2215,"y":308,"style":453},"597.1","Dropdown for B4",[326,2218,2220],{"x":2215,"y":2219,"style":339},"134.8","Laptop · Monitor",[326,2222,2224],{"x":2215,"y":2223,"style":339},"152.2","only Hardware items",[52,2226,2228],{"id":2227},"step-5-check-rows-that-were-already-filled-in","Step 5 — Check rows that were already filled in",[10,2230,2231],{},"Data validation only acts when someone types or picks a value. Rows pasted in, entered before the rules existed, or left behind when a category changed can still hold invalid pairs. When the completed template comes back, check it in pandas against the same mapping:",[57,2233,2235],{"className":89,"code":2234,"language":91,"meta":62,"style":62},"filled = pd.DataFrame({\"Category\": [\"Travel\", \"Hardware\", \"Travel\", \"Client meals\"],\n                       \"Item\": [\"Taxi\", \"Toner\", \"Rail\", \"Dinner\"]})\nvalid_pairs = set(map(tuple, mapping[[\"Category\", \"Item\"]].to_numpy()))\nfilled[\"Valid\"] = [pair in valid_pairs for pair in map(tuple, filled[[\"Category\", \"Item\"]].to_numpy())]\nprint(filled.to_string(index=False))\n",[41,2236,2237,2267,2292,2324,2370],{"__ignoreMap":62},[66,2238,2239,2242,2244,2247,2249,2251,2253,2255,2257,2259,2261,2263,2265],{"class":68,"line":69},[66,2240,2241],{"class":102},"filled ",[66,2243,125],{"class":98},[66,2245,2246],{"class":102}," pd.DataFrame({",[66,2248,250],{"class":76},[66,2250,137],{"class":102},[66,2252,140],{"class":76},[66,2254,25],{"class":102},[66,2256,153],{"class":76},[66,2258,25],{"class":102},[66,2260,140],{"class":76},[66,2262,25],{"class":102},[66,2264,175],{"class":76},[66,2266,178],{"class":102},[66,2268,2269,2272,2274,2276,2278,2280,2282,2284,2286,2289],{"class":68,"line":112},[66,2270,2271],{"class":76},"                       \"Item\"",[66,2273,137],{"class":102},[66,2275,194],{"class":76},[66,2277,25],{"class":102},[66,2279,219],{"class":76},[66,2281,25],{"class":102},[66,2283,189],{"class":76},[66,2285,25],{"class":102},[66,2287,2288],{"class":76},"\"Dinner\"",[66,2290,2291],{"class":102},"]})\n",[66,2293,2294,2297,2299,2302,2304,2307,2309,2312,2315,2317,2319,2321],{"class":68,"line":119},[66,2295,2296],{"class":102},"valid_pairs ",[66,2298,125],{"class":98},[66,2300,2301],{"class":243}," set",[66,2303,852],{"class":102},[66,2305,2306],{"class":243},"map",[66,2308,852],{"class":102},[66,2310,2311],{"class":243},"tuple",[66,2313,2314],{"class":102},", mapping[[",[66,2316,250],{"class":76},[66,2318,25],{"class":102},[66,2320,267],{"class":76},[66,2322,2323],{"class":102},"]].to_numpy()))\n",[66,2325,2326,2329,2332,2334,2336,2339,2341,2344,2346,2349,2351,2354,2356,2358,2361,2363,2365,2367],{"class":68,"line":131},[66,2327,2328],{"class":102},"filled[",[66,2330,2331],{"class":76},"\"Valid\"",[66,2333,952],{"class":102},[66,2335,125],{"class":98},[66,2337,2338],{"class":102}," [pair ",[66,2340,739],{"class":98},[66,2342,2343],{"class":102}," valid_pairs ",[66,2345,733],{"class":98},[66,2347,2348],{"class":102}," pair ",[66,2350,739],{"class":98},[66,2352,2353],{"class":243}," map",[66,2355,852],{"class":102},[66,2357,2311],{"class":243},[66,2359,2360],{"class":102},", filled[[",[66,2362,250],{"class":76},[66,2364,25],{"class":102},[66,2366,267],{"class":76},[66,2368,2369],{"class":102},"]].to_numpy())]\n",[66,2371,2372,2374,2377,2380,2382,2384],{"class":68,"line":181},[66,2373,244],{"class":243},[66,2375,2376],{"class":102},"(filled.to_string(",[66,2378,2379],{"class":255},"index",[66,2381,125],{"class":98},[66,2383,261],{"class":243},[66,2385,2386],{"class":102},"))\n",[10,2388,2389,2392,2393,2396,2397,2401],{},[41,2390,2391],{},"Hardware\u002FToner"," and ",[41,2394,2395],{},"Client meals\u002FDinner"," are rejected: toner belongs to office supplies and dinner is not on the list at all. Keeping the mapping as the single source for both the template and this check means they cannot drift apart. ",[46,2398,2400],{"href":2399},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fvalidate-excel-columns-before-import-with-pandas\u002F","Validate Excel Columns Before Import with pandas"," covers checks like this in general.",[52,2403,2405],{"id":2404},"common-pitfalls","Common pitfalls",[2407,2408,2409,2417,2423,2435,2441],"ul",{},[2410,2411,2412,2416],"li",{},[2413,2414,2415],"strong",{},"Spaces and symbols in category names."," Named ranges cannot contain them; derive safe names in one function and mirror it in the formula.",[2410,2418,2419,2422],{},[2413,2420,2421],{},"Ranges longer than the list."," Blank cells in the named range appear as empty choices; size each range exactly.",[2410,2424,2425,2428,2429,2432,2433,50],{},[2413,2426,2427],{},"Absolute row references."," ",[41,2430,2431],{},"$A$2"," would make every row depend on the first row's category; use ",[41,2434,1411],{},[2410,2436,2437,2440],{},[2413,2438,2439],{},"Changing the category after the item."," Excel does not clear the dependent cell; check pairs when the file comes back.",[2410,2442,2443,2446,2447,50],{},[2413,2444,2445],{},"Validation lost on re-save."," Some validation extensions do not survive openpyxl round trips; generate the template in one pass — see ",[46,2448,2450],{"href":2449},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-data-validation-extension-is-not-supported-warning\u002F","Fix \"Data Validation extension is not supported\" Warning",[52,2452,2454],{"id":2453},"conclusion","Conclusion",[10,2456,2457,2458,2460,2461,2463,2464,2467],{},"Generate the template from the category-item mapping: a hidden lookup sheet with one column per category, a ",[41,2459,1536],{}," name and a safe-named range per category, a list validation ",[41,2462,1119],{}," for the first column and ",[41,2465,2466],{},"=INDIRECT(SUBSTITUTE($A2,\" \",\"_\"))"," for the second. Confirm every name resolves by recalculating a scratch copy, and when completed files come back, check each category-item pair against the same mapping in pandas.",[52,2469,2471],{"id":2470},"frequently-asked-questions","Frequently asked questions",[10,2473,2474,2477],{},[2413,2475,2476],{},"How do I create a dependent dropdown list in Excel with Python?","\nPut each category's items in their own column on a lookup sheet, define a named range for each category, and add a list DataValidation to the second column with formula1 set to =INDIRECT($A2), so it shows the range named after the category chosen in column A.",[10,2479,2480,2483],{},[2413,2481,2482],{},"Why does my dependent dropdown show nothing?","\nINDIRECT could not find a name matching the selected category, usually because the category contains spaces or symbols that are not allowed in names. Convert category names to valid names consistently, for example with SUBSTITUTE in the formula.",[10,2485,2486,2489],{},[2413,2487,2488],{},"Can openpyxl create named ranges for dropdowns?","\nYes. Add a DefinedName to wb.defined_names with attr_text pointing at the item cells, such as Lists!$B$2:$B$4, and reference the name in the validation formula.",[10,2491,2492,2495],{},[2413,2493,2494],{},"Does data validation stop invalid values that are pasted in?","\nNo. Pasting replaces the validation or bypasses it, and existing values are not rechecked. Validate completed files in Python against the same mapping.",[52,2497,2499],{"id":2498},"related","Related",[2407,2501,2502,2508,2515,2522],{},[2410,2503,2504,2505,2507],{},"Up one level: ",[46,2506,49],{"href":48}," — checks that data is fit for use.",[2410,2509,2510,2514],{},[46,2511,2513],{"href":2512},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fadd-dropdown-data-validation-to-excel-with-openpyxl\u002F","Add Dropdown Data Validation to Excel with openpyxl"," — single dropdowns and other rule types.",[2410,2516,2517,2521],{},[46,2518,2520],{"href":2519},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-dropdown-lists-from-an-existing-workbook\u002F","Read Dropdown Lists and Validation Rules from an Existing Workbook"," — the reverse direction.",[2410,2523,2524,2528],{},[46,2525,2527],{"href":2526},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fhide-sheets-rows-and-columns-with-openpyxl\u002F","Hide Sheets, Rows and Columns with openpyxl"," — hidden and very hidden lookup sheets.",[2530,2531,2532],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":62,"searchDepth":112,"depth":112,"links":2534},[2535,2536,2537,2538,2539,2540,2541,2542,2543,2544],{"id":54,"depth":112,"text":55},{"id":466,"depth":112,"text":467},{"id":917,"depth":112,"text":918},{"id":1112,"depth":112,"text":1113},{"id":1702,"depth":112,"text":1703},{"id":2227,"depth":112,"text":2228},{"id":2404,"depth":112,"text":2405},{"id":2453,"depth":112,"text":2454},{"id":2470,"depth":112,"text":2471},{"id":2498,"depth":112,"text":2499},"2026-09-29","Build cascading dropdowns — pick a category, then only its subcategories are offered — from a mapping table in Python: a hidden lookup sheet, one named range per category with safe names, a data validation list for the first column and an INDIRECT-based list for the second, error messages, and a Python check that existing rows still match.","md",[2549,2551,2553,2555],{"q":2476,"a":2550},"Put each category's items in their own column on a lookup sheet, define a named range for each category, and add a list DataValidation to the second column with formula1 set to =INDIRECT($A2), so it shows the range named after the category chosen in column A.",{"q":2482,"a":2552},"INDIRECT could not find a name matching the selected category, usually because the category contains spaces or symbols that are not allowed in names. Convert category names to valid names consistently, for example with SUBSTITUTE in the formula.",{"q":2488,"a":2554},"Yes. Add a DefinedName to wb.defined_names with attr_text pointing at the item cells, such as Lists!$B$2:$B$4, and reference the name in the validation formula.",{"q":2494,"a":2556},"No. Pasting replaces the validation or bypasses it, and existing values are not rechecked. Validate completed files in Python against the same mapping.","create dependent dropdown lists in excel with python",null,{"breadcrumb":2560},[2561,2564,2567],{"name":2562,"item":2563},"Home","\u002F",{"name":2565,"item":2566},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":49,"item":48},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcreate-dependent-dropdown-lists-in-excel-with-python",{"title":2570,"description":2571},"Dependent (Cascading) Dropdown Lists in Excel with openpyxl","Create dependent dropdown lists in Excel with Python: a lookup sheet generated from a mapping, a named range per category, DataValidation with =Categories and =INDIRECT($A2), names for categories with spaces, a hidden lookup sheet, recalculation checks and validating existing entries with pandas.","create-dependent-dropdown-lists-in-excel-with-python","advanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcreate-dependent-dropdown-lists-in-excel-with-python\u002Findex",[2575,2576],"data-validation","openpyxl","how-to","xvx3Tu3WMJtL1XN10MMreIZrklRx2RawA0zPSOC2jZA",[2580,2584],{"title":2581,"path":2582,"stem":2583,"children":-1},"Compare Two Excel Files for Differences with Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcompare-two-excel-files-for-differences-with-python","advanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcompare-two-excel-files-for-differences-with-python\u002Findex",{"title":2585,"path":2586,"stem":2587,"children":-1},"Detect Possible Duplicate Invoices","\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fdetect-possible-duplicate-invoices","advanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fdetect-possible-duplicate-invoices\u002Findex",[2589,2591],{"path":2590,"title":2565},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2592,"title":49},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python",[],[2595,2598,2601,2604,2606,2609],{"path":2596,"title":2597,"kind":2577},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fdetect-outliers-in-excel-data-with-pandas","Detect Outliers in Excel Data with pandas",{"path":2599,"title":2600,"kind":2577},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-containing-text-with-openpyxl","Highlight Cells Containing Specific Text with openpyxl",{"path":2602,"title":2603,"kind":2577},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Ffind-near-duplicate-names-within-one-excel-column","Find Near-Duplicate Names Within One Excel Column",{"path":2605,"title":2520,"kind":2577},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-dropdown-lists-from-an-existing-workbook",{"path":2607,"title":2608,"kind":2577},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Ffind-and-report-missing-values-in-an-excel-file","Find and Report Missing Values in an Excel File",{"path":2610,"title":2611,"kind":2577},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fflag-rows-with-too-many-missing-values","Flag Rows with Too Many Missing Values",1790972098585]