[{"data":1,"prerenderedAt":1989},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-delimited-cell-values-into-rows-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-delimited-cell-values-into-rows-with-pandas":1955,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-delimited-cell-values-into-rows-with-pandas":1964,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-delimited-cell-values-into-rows-with-pandas":1969,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-delimited-cell-values-into-rows-with-pandas":1970},{"id":4,"title":5,"body":6,"dateModified":1920,"datePublished":1920,"description":1921,"extension":1922,"faq":1923,"keyword":1932,"kind":1933,"meta":1934,"navigation":121,"path":1944,"seo":1945,"slug":1948,"stem":1949,"topics":1950,"type":1953,"__hash__":1954},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-delimited-cell-values-into-rows-with-pandas\u002Findex.md","Split Delimited Cell Values into Rows with pandas",{"type":7,"value":8,"toc":1906},"minimark",[9,39,44,72,89,93,209,227,465,469,472,572,587,591,594,739,764,768,775,864,871,1162,1168,1290,1294,1297,1436,1439,1443,1454,1585,1598,1719,1723,1739,1743,1808,1812,1828,1832,1841,1854,1860,1869,1873,1902],[10,11,12,13,17,18,21,22,17,25,28,29,32,33,38],"p",{},"Spreadsheets kept by hand pack several values into one cell all the time: a product row with ",[14,15,16],"code",{},"Tags"," = ",[14,19,20],{},"outdoor; garden; sale",", an order with ",[14,23,24],{},"Invoices",[14,26,27],{},"INV-1001, INV-1002",", a contact with three email addresses separated by commas. That is convenient to type and awkward to analyse — you cannot filter for \"garden\", count how often each tag occurs, or join the invoice numbers to an invoice table until each value has a row of its own. pandas does this in two steps: split the text into a list, then ",[14,30,31],{},"explode"," the list into rows. This page covers that, the clean-up that real cells need, splitting two related columns in step, and the reverse operation. It belongs to ",[34,35,37],"a",{"href":36},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002F","Cleaning Excel Data with pandas",".",[40,41,43],"h2",{"id":42},"prerequisites","Prerequisites",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,52,53],{"__ignoreMap":50},[54,55,58,62,66,69],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"sMTad","pip",[54,63,65],{"class":64},"srMev"," install",[54,67,68],{"class":64}," pandas",[54,70,71],{"class":64}," openpyxl\n",[10,73,74,77,78,80,81,84,85,88],{},[14,75,76],{},"catalogue.xlsx"," has five products. ",[14,79,16],{}," is a semicolon-separated list with the usual inconsistencies: missing spaces, extra spaces, an empty item between two semicolons, an empty cell, and the same tag typed twice in different case. ",[14,82,83],{},"Aliases"," and ",[14,86,87],{},"Prices"," are comma-separated lists that belong together — the first price goes with the first alias — except for the ladder, where one price is missing.",[40,90,92],{"id":91},"step-1-split-then-explode","Step 1 — Split, then explode",[45,94,98],{"className":95,"code":96,"language":97,"meta":50,"style":50},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ncat = pd.read_excel(\"catalogue.xlsx\", keep_default_na=False)\ntags = cat.assign(Tag=cat[\"Tags\"].str.split(\";\")).explode(\"Tag\")\nprint(tags[[\"SKU\", \"Tag\"]].to_string())\n","python",[14,99,100,116,123,154,190],{"__ignoreMap":50},[54,101,102,106,110,113],{"class":56,"line":57},[54,103,105],{"class":104},"s-kum","import",[54,107,109],{"class":108},"skGVy"," pandas ",[54,111,112],{"class":104},"as",[54,114,115],{"class":108}," pd\n",[54,117,119],{"class":56,"line":118},2,[54,120,122],{"emptyLinePlaceholder":121},true,"\n",[54,124,126,129,132,135,138,141,145,147,151],{"class":56,"line":125},3,[54,127,128],{"class":108},"cat ",[54,130,131],{"class":104},"=",[54,133,134],{"class":108}," pd.read_excel(",[54,136,137],{"class":64},"\"catalogue.xlsx\"",[54,139,140],{"class":108},", ",[54,142,144],{"class":143},"sa561","keep_default_na",[54,146,131],{"class":104},[54,148,150],{"class":149},"sP0c6","False",[54,152,153],{"class":108},")\n",[54,155,157,160,162,165,168,170,173,176,179,182,185,188],{"class":56,"line":156},4,[54,158,159],{"class":108},"tags ",[54,161,131],{"class":104},[54,163,164],{"class":108}," cat.assign(",[54,166,167],{"class":143},"Tag",[54,169,131],{"class":104},[54,171,172],{"class":108},"cat[",[54,174,175],{"class":64},"\"Tags\"",[54,177,178],{"class":108},"].str.split(",[54,180,181],{"class":64},"\";\"",[54,183,184],{"class":108},")).explode(",[54,186,187],{"class":64},"\"Tag\"",[54,189,153],{"class":108},[54,191,193,196,199,202,204,206],{"class":56,"line":192},5,[54,194,195],{"class":149},"print",[54,197,198],{"class":108},"(tags[[",[54,200,201],{"class":64},"\"SKU\"",[54,203,140],{"class":108},[54,205,187],{"class":64},[54,207,208],{"class":108},"]].to_string())\n",[10,210,211,214,215,218,219,222,223,226],{},[14,212,213],{},"str.split(\";\")"," turns each cell into a Python list, and ",[14,216,217],{},"explode(\"Tag\")"," repeats the row once per list element, copying every other column. The index is repeated too — all of P-100's rows keep index 0 — which is exactly what makes it possible to trace each new row back to its source row later. The raw result still has problems: ",[14,220,221],{},"\" outdoor\""," with a leading space, ",[14,224,225],{},"\"power tools \""," with a trailing one, an empty string from the double semicolon, and an empty string for the gloves.",[228,229,237,238,237,242,237,246,237,254,237,263,237,267,237,274,237,278,237,282,237,286,237,291,237,296,237,300,237,302,237,304,237,307,237,311,237,313,237,317,237,321,237,325,237,328,237,332,237,334,237,337,237,339,237,342,237,345,237,348,237,351,237,355,237,358,237,360,237,363,237,365,237,367,237,369,237,371,237,373,237,375,237,377,237,379,237,383,237,386,237,388,237,390,237,392,237,394,237,396,237,399,237,402,237,406,237,408,237,410,237,412,237,415,237,418,237,422,237,424,237,426,237,428,237,430,237,436,237,440,237,446,237,451,237,459],"svg",{"viewBox":230,"role":231,"ariaLabelledBy":232,"xmlns":235,"style":236},"50 0 660 220","img",[233,234],"ex-basic-t","ex-basic-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:660px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[239,240,241],"title",{"id":233},"One product row becomes one row per tag",[243,244,245],"desc",{"id":234},"On the left the product P-100 with the cell \"garden; outdoor;sale\". On the right three rows for P-100 with the tags garden, outdoor and sale, after splitting on semicolons, exploding and trimming.",[247,248],"rect",{"x":249,"y":250,"width":251,"height":252,"fill":253},"50","0","660","220","#ffffff",[247,255],{"x":256,"y":257,"width":258,"height":259,"rx":250,"fill":260,"stroke":261,"style":262},"66.1","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[247,264],{"x":265,"y":257,"width":266,"height":259,"rx":250,"fill":260,"stroke":261,"style":262},"98.1","57.4",[268,269,273],"text",{"x":270,"y":271,"style":272},"126.9","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[247,275],{"x":276,"y":257,"width":277,"height":259,"rx":250,"fill":260,"stroke":261,"style":262},"155.6","142.6",[268,279,281],{"x":280,"y":271,"style":272},"226.9","B",[247,283],{"x":256,"y":284,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"64.0","28.0",[268,287,290],{"x":288,"y":289,"style":272},"82.1","83.0","1",[247,292],{"x":265,"y":284,"width":266,"height":285,"rx":250,"fill":293,"stroke":294,"style":295},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[268,297,299],{"x":270,"y":289,"style":298},"font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","SKU",[247,301],{"x":276,"y":284,"width":277,"height":285,"rx":250,"fill":293,"stroke":294,"style":295},[268,303,16],{"x":280,"y":289,"style":298},[247,305],{"x":256,"y":306,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"92.0",[268,308,310],{"x":288,"y":309,"style":272},"111.0","2",[247,312],{"x":265,"y":306,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,314,316],{"x":270,"y":309,"style":315},"font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","P-100",[247,318],{"x":276,"y":306,"width":277,"height":285,"rx":250,"fill":319,"stroke":320,"style":295},"#fdefd8","var(--gold,#b4740a)",[268,322,324],{"x":280,"y":309,"style":323},"font-size:11px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","garden; outdoor;sale",[247,326],{"x":256,"y":327,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"120.0",[268,329,331],{"x":288,"y":330,"style":272},"139.0","3",[247,333],{"x":265,"y":327,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,335,336],{"x":270,"y":330,"style":315},"P-101",[247,338],{"x":276,"y":327,"width":277,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,340,341],{"x":280,"y":330,"style":315},"garden",[247,343],{"x":344,"y":257,"width":258,"height":259,"rx":250,"fill":260,"stroke":261,"style":262},"535.8",[247,346],{"x":347,"y":257,"width":266,"height":259,"rx":250,"fill":260,"stroke":261,"style":262},"567.8",[268,349,273],{"x":350,"y":271,"style":272},"596.5",[247,352],{"x":353,"y":257,"width":354,"height":259,"rx":250,"fill":260,"stroke":261,"style":262},"625.2","68.6",[268,356,281],{"x":357,"y":271,"style":272},"659.5",[247,359],{"x":344,"y":284,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},[268,361,290],{"x":362,"y":289,"style":272},"551.8",[247,364],{"x":347,"y":284,"width":266,"height":285,"rx":250,"fill":293,"stroke":294,"style":295},[268,366,299],{"x":350,"y":289,"style":298},[247,368],{"x":353,"y":284,"width":354,"height":285,"rx":250,"fill":293,"stroke":294,"style":295},[268,370,167],{"x":357,"y":289,"style":298},[247,372],{"x":344,"y":306,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},[268,374,310],{"x":362,"y":309,"style":272},[247,376],{"x":347,"y":306,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,378,316],{"x":350,"y":309,"style":315},[247,380],{"x":353,"y":306,"width":354,"height":285,"rx":250,"fill":381,"stroke":382,"style":295},"#d9f4f1","var(--teal,#0f9488)",[268,384,341],{"x":357,"y":309,"style":385},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[247,387],{"x":344,"y":327,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},[268,389,331],{"x":362,"y":330,"style":272},[247,391],{"x":347,"y":327,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,393,316],{"x":350,"y":330,"style":315},[247,395],{"x":353,"y":327,"width":354,"height":285,"rx":250,"fill":381,"stroke":382,"style":295},[268,397,398],{"x":357,"y":330,"style":385},"outdoor",[247,400],{"x":344,"y":401,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"148.0",[268,403,405],{"x":362,"y":404,"style":272},"167.0","4",[247,407],{"x":347,"y":401,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,409,316],{"x":350,"y":404,"style":315},[247,411],{"x":353,"y":401,"width":354,"height":285,"rx":250,"fill":381,"stroke":382,"style":295},[268,413,414],{"x":357,"y":404,"style":385},"sale",[247,416],{"x":344,"y":417,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"176.0",[268,419,421],{"x":362,"y":420,"style":272},"195.0","5",[247,423],{"x":347,"y":417,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,425,336],{"x":350,"y":420,"style":315},[247,427],{"x":353,"y":417,"width":354,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,429,341],{"x":357,"y":420,"style":315},[268,431,435],{"x":432,"y":433,"style":434},"182.1","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[268,437,439],{"x":438,"y":433,"style":434},"614.8","after",[56,441],{"x1":442,"y1":443,"x2":444,"y2":443,"stroke":294,"style":445},"308.1","94.0","521.8","stroke-width:2px",[447,448],"polygon",{"points":449,"fill":450},"525.8,94.0 516.8,99.0 516.8,89.0","#5b5cf0",[247,452],{"x":453,"y":454,"width":455,"height":456,"rx":457,"fill":253,"stroke":261,"style":458},"337.9","61.0","158.1","22.0","11","stroke-width:1.2px",[268,460,464],{"x":461,"y":462,"style":463},"417.0","76.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","str.split + explode",[40,466,468],{"id":467},"step-2-clean-the-pieces","Step 2 — Clean the pieces",[10,470,471],{},"Trim, normalise case, drop empty items and remove duplicates within each product:",[45,473,475],{"className":95,"code":474,"language":97,"meta":50,"style":50},"tags = (cat.assign(Tag=cat[\"Tags\"].str.split(\";\"))\n           .explode(\"Tag\")\n           .assign(Tag=lambda d: d[\"Tag\"].str.strip().str.lower())\n           .query(\"Tag != ''\")\n           .drop_duplicates([\"SKU\", \"Tag\"]))\nprint(tags[[\"SKU\", \"Product\", \"Tag\"]].to_string())\n",[14,476,477,501,510,528,538,552],{"__ignoreMap":50},[54,478,479,481,483,486,488,490,492,494,496,498],{"class":56,"line":57},[54,480,159],{"class":108},[54,482,131],{"class":104},[54,484,485],{"class":108}," (cat.assign(",[54,487,167],{"class":143},[54,489,131],{"class":104},[54,491,172],{"class":108},[54,493,175],{"class":64},[54,495,178],{"class":108},[54,497,181],{"class":64},[54,499,500],{"class":108},"))\n",[54,502,503,506,508],{"class":56,"line":118},[54,504,505],{"class":108},"           .explode(",[54,507,187],{"class":64},[54,509,153],{"class":108},[54,511,512,515,517,520,523,525],{"class":56,"line":125},[54,513,514],{"class":108},"           .assign(",[54,516,167],{"class":143},[54,518,519],{"class":104},"=lambda",[54,521,522],{"class":108}," d: d[",[54,524,187],{"class":64},[54,526,527],{"class":108},"].str.strip().str.lower())\n",[54,529,530,533,536],{"class":56,"line":156},[54,531,532],{"class":108},"           .query(",[54,534,535],{"class":64},"\"Tag != ''\"",[54,537,153],{"class":108},[54,539,540,543,545,547,549],{"class":56,"line":192},[54,541,542],{"class":108},"           .drop_duplicates([",[54,544,201],{"class":64},[54,546,140],{"class":108},[54,548,187],{"class":64},[54,550,551],{"class":108},"]))\n",[54,553,555,557,559,561,563,566,568,570],{"class":56,"line":554},6,[54,556,195],{"class":149},[54,558,198],{"class":108},[54,560,201],{"class":64},[54,562,140],{"class":108},[54,564,565],{"class":64},"\"Product\"",[54,567,140],{"class":108},[54,569,187],{"class":64},[54,571,208],{"class":108},[10,573,574,575,578,579,582,583,586],{},"Stripping after the explode handles every spacing variant at once. Filtering out empty strings removes both the item between the two semicolons and the product with no tags at all — decide whether that product should vanish or keep a row with a placeholder such as ",[14,576,577],{},"\"(none)\"","; for a tag count, vanishing is right. ",[14,580,581],{},"drop_duplicates"," on SKU and tag removes the ladder's second \"outdoor\", which only differed by case and spaces. For splitting on several possible separators, use a regular expression: ",[14,584,585],{},"str.split(r\"\\s*[;,|]\\s*\", regex=True)"," splits on semicolons, commas or pipes and eats the surrounding spaces in one go.",[40,588,590],{"id":589},"step-3-count-filter-and-pivot-the-values","Step 3 — Count, filter and pivot the values",[10,592,593],{},"With one value per row, the questions that were impossible become one-liners:",[45,595,597],{"className":95,"code":596,"language":97,"meta":50,"style":50},"print(tags[\"Tag\"].value_counts())\nprint(\"garden products:\", tags.loc[tags[\"Tag\"] == \"garden\", \"Product\"].tolist())\ntry:\n    pd.crosstab(tags[\"Product\"], tags[\"Tag\"])\nexcept ValueError as exc:\n    print(\"ValueError:\", exc)\ntags = tags.reset_index(drop=True)\nmatrix = pd.crosstab(tags[\"Product\"], tags[\"Tag\"])\nprint(matrix)\n",[14,598,599,611,642,650,665,679,692,712,731],{"__ignoreMap":50},[54,600,601,603,606,608],{"class":56,"line":57},[54,602,195],{"class":149},[54,604,605],{"class":108},"(tags[",[54,607,187],{"class":64},[54,609,610],{"class":108},"].value_counts())\n",[54,612,613,615,618,621,624,626,629,632,635,637,639],{"class":56,"line":118},[54,614,195],{"class":149},[54,616,617],{"class":108},"(",[54,619,620],{"class":64},"\"garden products:\"",[54,622,623],{"class":108},", tags.loc[tags[",[54,625,187],{"class":64},[54,627,628],{"class":108},"] ",[54,630,631],{"class":104},"==",[54,633,634],{"class":64}," \"garden\"",[54,636,140],{"class":108},[54,638,565],{"class":64},[54,640,641],{"class":108},"].tolist())\n",[54,643,644,647],{"class":56,"line":125},[54,645,646],{"class":104},"try",[54,648,649],{"class":108},":\n",[54,651,652,655,657,660,662],{"class":56,"line":156},[54,653,654],{"class":108},"    pd.crosstab(tags[",[54,656,565],{"class":64},[54,658,659],{"class":108},"], tags[",[54,661,187],{"class":64},[54,663,664],{"class":108},"])\n",[54,666,667,670,673,676],{"class":56,"line":192},[54,668,669],{"class":104},"except",[54,671,672],{"class":149}," ValueError",[54,674,675],{"class":104}," as",[54,677,678],{"class":108}," exc:\n",[54,680,681,684,686,689],{"class":56,"line":554},[54,682,683],{"class":149},"    print",[54,685,617],{"class":108},[54,687,688],{"class":64},"\"ValueError:\"",[54,690,691],{"class":108},", exc)\n",[54,693,695,697,699,702,705,707,710],{"class":56,"line":694},7,[54,696,159],{"class":108},[54,698,131],{"class":104},[54,700,701],{"class":108}," tags.reset_index(",[54,703,704],{"class":143},"drop",[54,706,131],{"class":104},[54,708,709],{"class":149},"True",[54,711,153],{"class":108},[54,713,715,718,720,723,725,727,729],{"class":56,"line":714},8,[54,716,717],{"class":108},"matrix ",[54,719,131],{"class":104},[54,721,722],{"class":108}," pd.crosstab(tags[",[54,724,565],{"class":64},[54,726,659],{"class":108},[54,728,187],{"class":64},[54,730,664],{"class":108},[54,732,734,736],{"class":56,"line":733},9,[54,735,195],{"class":149},[54,737,738],{"class":108},"(matrix)\n",[10,740,741,744,745,748,749,752,753,755,756,759,760,38],{},[14,742,743],{},"value_counts"," ranks the tags and filtering finds every product with a tag. The first ",[14,746,747],{},"crosstab"," attempt fails with ",[14,750,751],{},"ValueError: cannot reindex on an axis with duplicate labels",": after ",[14,754,31],{},", several rows share each original index label, and functions that align Series by index cannot tell them apart. ",[14,757,758],{},"reset_index(drop=True)"," gives every row its own label, and the second attempt turns the long list into a product × tag matrix of 0s and 1s, ready to write to Excel with conditional formatting. The same shape feeds a co-occurrence analysis — which tags appear together — as in ",[34,761,763],{"href":762},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-product-co-occurrence-matrix-from-order-lines\u002F","Build a Product Co-occurrence Matrix from Order Lines",[40,765,767],{"id":766},"step-4-explode-two-parallel-columns-together","Step 4 — Explode two parallel columns together",[10,769,770,84,772,774],{},[14,771,83],{},[14,773,87],{}," are paired lists: the second alias goes with the second price. Exploding them separately would pair every alias with every price. pandas can explode several columns in one call, keeping the pairs — if every row's lists have the same length:",[45,776,778],{"className":95,"code":777,"language":97,"meta":50,"style":50},"pairs = cat.assign(Alias=cat[\"Aliases\"].str.split(\",\"), Price=cat[\"Prices\"].str.split(\",\"))\ntry:\n    pairs.explode([\"Alias\", \"Price\"])\nexcept ValueError as exc:\n    print(\"ValueError:\", exc)\n",[14,779,780,823,829,844,854],{"__ignoreMap":50},[54,781,782,785,787,789,792,794,796,799,801,804,807,810,812,814,817,819,821],{"class":56,"line":57},[54,783,784],{"class":108},"pairs ",[54,786,131],{"class":104},[54,788,164],{"class":108},[54,790,791],{"class":143},"Alias",[54,793,131],{"class":104},[54,795,172],{"class":108},[54,797,798],{"class":64},"\"Aliases\"",[54,800,178],{"class":108},[54,802,803],{"class":64},"\",\"",[54,805,806],{"class":108},"), ",[54,808,809],{"class":143},"Price",[54,811,131],{"class":104},[54,813,172],{"class":108},[54,815,816],{"class":64},"\"Prices\"",[54,818,178],{"class":108},[54,820,803],{"class":64},[54,822,500],{"class":108},[54,824,825,827],{"class":56,"line":118},[54,826,646],{"class":104},[54,828,649],{"class":108},[54,830,831,834,837,839,842],{"class":56,"line":125},[54,832,833],{"class":108},"    pairs.explode([",[54,835,836],{"class":64},"\"Alias\"",[54,838,140],{"class":108},[54,840,841],{"class":64},"\"Price\"",[54,843,664],{"class":108},[54,845,846,848,850,852],{"class":56,"line":156},[54,847,669],{"class":104},[54,849,672],{"class":149},[54,851,675],{"class":104},[54,853,678],{"class":108},[54,855,856,858,860,862],{"class":56,"line":192},[54,857,683],{"class":149},[54,859,617],{"class":108},[54,861,688],{"class":64},[54,863,691],{"class":108},[10,865,866,867,870],{},"The ladder has two aliases and one price, so the call fails with ",[14,868,869],{},"ValueError: columns must have matching element counts",". That error is a feature: it found a data problem. Report the offending rows and decide how to handle them before exploding:",[45,872,874],{"className":95,"code":873,"language":97,"meta":50,"style":50},"lengths = pairs[\"Alias\"].str.len() != pairs[\"Price\"].str.len()\nprint(\"mismatched:\", pairs.loc[lengths, [\"SKU\", \"Aliases\", \"Prices\"]].to_dict(\"records\"))\n\ndef pad_to_match(row):\n    n = max(len(row[\"Alias\"]), len(row[\"Price\"]))\n    return pd.Series({\"Alias\": row[\"Alias\"] + [None] * (n - len(row[\"Alias\"])),\n                      \"Price\": row[\"Price\"] + [None] * (n - len(row[\"Price\"]))})\n\npairs[[\"Alias\", \"Price\"]] = pairs.apply(pad_to_match, axis=1)\nlong = pairs.explode([\"Alias\", \"Price\"])[[\"SKU\", \"Alias\", \"Price\"]]\nlong[\"Price\"] = pd.to_numeric(long[\"Price\"])\nprint(long.to_string())\n",[14,875,876,901,931,935,947,978,1025,1059,1063,1091,1125,1150],{"__ignoreMap":50},[54,877,878,881,883,886,888,891,894,896,898],{"class":56,"line":57},[54,879,880],{"class":108},"lengths ",[54,882,131],{"class":104},[54,884,885],{"class":108}," pairs[",[54,887,836],{"class":64},[54,889,890],{"class":108},"].str.len() ",[54,892,893],{"class":104},"!=",[54,895,885],{"class":108},[54,897,841],{"class":64},[54,899,900],{"class":108},"].str.len()\n",[54,902,903,905,907,910,913,915,917,919,921,923,926,929],{"class":56,"line":118},[54,904,195],{"class":149},[54,906,617],{"class":108},[54,908,909],{"class":64},"\"mismatched:\"",[54,911,912],{"class":108},", pairs.loc[lengths, [",[54,914,201],{"class":64},[54,916,140],{"class":108},[54,918,798],{"class":64},[54,920,140],{"class":108},[54,922,816],{"class":64},[54,924,925],{"class":108},"]].to_dict(",[54,927,928],{"class":64},"\"records\"",[54,930,500],{"class":108},[54,932,933],{"class":56,"line":125},[54,934,122],{"emptyLinePlaceholder":121},[54,936,937,940,944],{"class":56,"line":156},[54,938,939],{"class":104},"def",[54,941,943],{"class":942},"s_Opv"," pad_to_match",[54,945,946],{"class":108},"(row):\n",[54,948,949,952,954,957,959,962,965,967,970,972,974,976],{"class":56,"line":192},[54,950,951],{"class":108},"    n ",[54,953,131],{"class":104},[54,955,956],{"class":149}," max",[54,958,617],{"class":108},[54,960,961],{"class":149},"len",[54,963,964],{"class":108},"(row[",[54,966,836],{"class":64},[54,968,969],{"class":108},"]), ",[54,971,961],{"class":149},[54,973,964],{"class":108},[54,975,841],{"class":64},[54,977,551],{"class":108},[54,979,980,983,986,988,991,993,995,998,1001,1004,1006,1009,1012,1015,1018,1020,1022],{"class":56,"line":554},[54,981,982],{"class":104},"    return",[54,984,985],{"class":108}," pd.Series({",[54,987,836],{"class":64},[54,989,990],{"class":108},": row[",[54,992,836],{"class":64},[54,994,628],{"class":108},[54,996,997],{"class":104},"+",[54,999,1000],{"class":108}," [",[54,1002,1003],{"class":149},"None",[54,1005,628],{"class":108},[54,1007,1008],{"class":104},"*",[54,1010,1011],{"class":108}," (n ",[54,1013,1014],{"class":104},"-",[54,1016,1017],{"class":149}," len",[54,1019,964],{"class":108},[54,1021,836],{"class":64},[54,1023,1024],{"class":108},"])),\n",[54,1026,1027,1030,1032,1034,1036,1038,1040,1042,1044,1046,1048,1050,1052,1054,1056],{"class":56,"line":694},[54,1028,1029],{"class":64},"                      \"Price\"",[54,1031,990],{"class":108},[54,1033,841],{"class":64},[54,1035,628],{"class":108},[54,1037,997],{"class":104},[54,1039,1000],{"class":108},[54,1041,1003],{"class":149},[54,1043,628],{"class":108},[54,1045,1008],{"class":104},[54,1047,1011],{"class":108},[54,1049,1014],{"class":104},[54,1051,1017],{"class":149},[54,1053,964],{"class":108},[54,1055,841],{"class":64},[54,1057,1058],{"class":108},"]))})\n",[54,1060,1061],{"class":56,"line":714},[54,1062,122],{"emptyLinePlaceholder":121},[54,1064,1065,1068,1070,1072,1074,1077,1079,1082,1085,1087,1089],{"class":56,"line":733},[54,1066,1067],{"class":108},"pairs[[",[54,1069,836],{"class":64},[54,1071,140],{"class":108},[54,1073,841],{"class":64},[54,1075,1076],{"class":108},"]] ",[54,1078,131],{"class":104},[54,1080,1081],{"class":108}," pairs.apply(pad_to_match, ",[54,1083,1084],{"class":143},"axis",[54,1086,131],{"class":104},[54,1088,290],{"class":149},[54,1090,153],{"class":108},[54,1092,1094,1097,1100,1103,1105,1107,1109,1112,1114,1116,1118,1120,1122],{"class":56,"line":1093},10,[54,1095,1096],{"class":143},"long",[54,1098,1099],{"class":104}," =",[54,1101,1102],{"class":108}," pairs.explode([",[54,1104,836],{"class":64},[54,1106,140],{"class":108},[54,1108,841],{"class":64},[54,1110,1111],{"class":108},"])[[",[54,1113,201],{"class":64},[54,1115,140],{"class":108},[54,1117,836],{"class":64},[54,1119,140],{"class":108},[54,1121,841],{"class":64},[54,1123,1124],{"class":108},"]]\n",[54,1126,1128,1130,1133,1135,1137,1139,1142,1144,1146,1148],{"class":56,"line":1127},11,[54,1129,1096],{"class":143},[54,1131,1132],{"class":108},"[",[54,1134,841],{"class":64},[54,1136,628],{"class":108},[54,1138,131],{"class":104},[54,1140,1141],{"class":108}," pd.to_numeric(",[54,1143,1096],{"class":143},[54,1145,1132],{"class":108},[54,1147,841],{"class":64},[54,1149,664],{"class":108},[54,1151,1153,1155,1157,1159],{"class":56,"line":1152},12,[54,1154,195],{"class":149},[54,1156,617],{"class":108},[54,1158,1096],{"class":143},[54,1160,1161],{"class":108},".to_string())\n",[10,1163,1164,1165,1167],{},"Padding the shorter list with ",[14,1166,1003],{}," keeps every alias and marks the missing price as empty, which is honest; repeating the last price would invent data. After the explode, prices are converted from text to numbers. The result has one row per alias with its own price: the drill's three names keep their three prices in order, and the step ladder appears with an empty price that a reviewer can fill in, rather than silently inheriting the ladder's 79.00. Writing this long table back to Excel gives the catalogue owner a list they can sort and correct line by line — far easier than hunting for a missing comma inside a cell.",[228,1169,237,1175,237,1178,237,1181,237,1185,237,1194,237,1205,237,1211,237,1216,237,1221,237,1228,237,1232,237,1237,237,1241,237,1246,237,1249,237,1256,237,1261,237,1265,237,1269,237,1274,237,1277,237,1282,237,1285],{"viewBox":1170,"role":231,"ariaLabelledBy":1171,"xmlns":235,"style":1174},"4 0 752 207",[1172,1173],"ex-parallel-t","ex-parallel-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[239,1176,1177],{"id":1172},"Exploding paired columns separately versus together",[243,1179,1180],{"id":1173},"Exploding Aliases and then Prices separately pairs every alias with every price, giving nine rows for the drill instead of three. Exploding both columns in one call keeps the pairs, and raises an error when a row's lists differ in length.",[247,1182],{"x":405,"y":250,"width":1183,"height":1184,"fill":253},"752","207",[247,1186],{"x":1187,"y":1188,"width":1189,"height":1190,"rx":1191,"fill":1192,"stroke":1193,"style":445},"20.0","16.0","351.0","175.0","14","#fce9e9","var(--danger,#dc2626)",[1195,1196,1198],"g",{"transform":1197},"translate(182.5,29.0) scale(1.083)",[1199,1200],"path",{"d":1201,"fill":1202,"stroke":1203,"style":1204},"M6 6l12 12 M18 6L6 18","none","var(--accent-ink,#be185d)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[268,1206,1210],{"x":1207,"y":1208,"style":1209},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","explode(\"Alias\").explode(\"Price\")",[56,1212],{"x1":1213,"y1":1214,"x2":1215,"y2":1214,"stroke":1193,"style":262},"36.0","91.0","355.0",[268,1217,1220],{"x":1218,"y":309,"style":1219},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","every alias × every price",[1195,1222,1224],{"transform":1223},"translate(38.5,99.5) scale(0.625)",[1199,1225],{"d":1201,"fill":1202,"stroke":1226,"style":1227},"var(--accent,#f43f8f)","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[268,1229,1231],{"x":1218,"y":1230,"style":1219},"137.0","drill: 3 × 3 = 9 rows",[1195,1233,1235],{"transform":1234},"translate(38.5,125.5) scale(0.625)",[1199,1236],{"d":1201,"fill":1202,"stroke":1226,"style":1227},[268,1238,1240],{"x":1218,"y":1239,"style":1219},"163.0","wrong prices silently",[1195,1242,1244],{"transform":1243},"translate(38.5,151.5) scale(0.625)",[1199,1245],{"d":1201,"fill":1202,"stroke":1226,"style":1227},[247,1247],{"x":1248,"y":1188,"width":1189,"height":1190,"rx":1191,"fill":381,"stroke":382,"style":445},"389.0",[1195,1250,1252],{"transform":1251},"translate(551.5,29.0) scale(1.083)",[1199,1253],{"d":1254,"fill":1202,"stroke":1255,"style":1204},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[268,1257,1260],{"x":1258,"y":1208,"style":1259},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","explode([\"Alias\", \"Price\"])",[56,1262],{"x1":1263,"y1":1214,"x2":1264,"y2":1214,"stroke":382,"style":262},"405.0","724.0",[268,1266,1268],{"x":1267,"y":309,"style":1219},"429.0","pairs kept in order",[1195,1270,1272],{"transform":1271},"translate(407.5,99.5) scale(0.625)",[1199,1273],{"d":1254,"fill":1202,"stroke":382,"style":1227},[268,1275,1276],{"x":1267,"y":1230,"style":1219},"drill: 3 rows",[1195,1278,1280],{"transform":1279},"translate(407.5,125.5) scale(0.625)",[1199,1281],{"d":1254,"fill":1202,"stroke":382,"style":1227},[268,1283,1284],{"x":1267,"y":1239,"style":1219},"length mismatch raises ValueError",[1195,1286,1288],{"transform":1287},"translate(407.5,151.5) scale(0.625)",[1199,1289],{"d":1254,"fill":1202,"stroke":382,"style":1227},[40,1291,1293],{"id":1292},"step-5-keep-a-link-to-the-source-row","Step 5 — Keep a link to the source row",[10,1295,1296],{},"When the exploded table goes back to someone who will fix the source sheet, include the original row number:",[45,1298,1300],{"className":95,"code":1299,"language":97,"meta":50,"style":50},"tags_with_row = (cat.reset_index(names=\"SourceRow\")\n                    .assign(SourceRow=lambda d: d[\"SourceRow\"] + 2,       # Excel row: header is row 1\n                            Tag=lambda d: d[\"Tags\"].str.split(\";\"))\n                    .explode(\"Tag\"))\ntags_with_row[\"Tag\"] = tags_with_row[\"Tag\"].str.strip()\nprint(tags_with_row.loc[tags_with_row[\"Tag\"] == \"\", [\"SourceRow\", \"SKU\", \"Tags\"]].to_string(index=False))\n",[14,1301,1302,1322,1350,1367,1376,1395],{"__ignoreMap":50},[54,1303,1304,1307,1309,1312,1315,1317,1320],{"class":56,"line":57},[54,1305,1306],{"class":108},"tags_with_row ",[54,1308,131],{"class":104},[54,1310,1311],{"class":108}," (cat.reset_index(",[54,1313,1314],{"class":143},"names",[54,1316,131],{"class":104},[54,1318,1319],{"class":64},"\"SourceRow\"",[54,1321,153],{"class":108},[54,1323,1324,1327,1330,1332,1334,1336,1338,1340,1343,1346],{"class":56,"line":118},[54,1325,1326],{"class":108},"                    .assign(",[54,1328,1329],{"class":143},"SourceRow",[54,1331,519],{"class":104},[54,1333,522],{"class":108},[54,1335,1319],{"class":64},[54,1337,628],{"class":108},[54,1339,997],{"class":104},[54,1341,1342],{"class":149}," 2",[54,1344,1345],{"class":108},",       ",[54,1347,1349],{"class":1348},"s-wDw","# Excel row: header is row 1\n",[54,1351,1352,1355,1357,1359,1361,1363,1365],{"class":56,"line":125},[54,1353,1354],{"class":143},"                            Tag",[54,1356,519],{"class":104},[54,1358,522],{"class":108},[54,1360,175],{"class":64},[54,1362,178],{"class":108},[54,1364,181],{"class":64},[54,1366,500],{"class":108},[54,1368,1369,1372,1374],{"class":56,"line":156},[54,1370,1371],{"class":108},"                    .explode(",[54,1373,187],{"class":64},[54,1375,500],{"class":108},[54,1377,1378,1381,1383,1385,1387,1390,1392],{"class":56,"line":192},[54,1379,1380],{"class":108},"tags_with_row[",[54,1382,187],{"class":64},[54,1384,628],{"class":108},[54,1386,131],{"class":104},[54,1388,1389],{"class":108}," tags_with_row[",[54,1391,187],{"class":64},[54,1393,1394],{"class":108},"].str.strip()\n",[54,1396,1397,1399,1402,1404,1406,1408,1411,1414,1416,1418,1420,1422,1424,1427,1430,1432,1434],{"class":56,"line":554},[54,1398,195],{"class":149},[54,1400,1401],{"class":108},"(tags_with_row.loc[tags_with_row[",[54,1403,187],{"class":64},[54,1405,628],{"class":108},[54,1407,631],{"class":104},[54,1409,1410],{"class":64}," \"\"",[54,1412,1413],{"class":108},", [",[54,1415,1319],{"class":64},[54,1417,140],{"class":108},[54,1419,201],{"class":64},[54,1421,140],{"class":108},[54,1423,175],{"class":64},[54,1425,1426],{"class":108},"]].to_string(",[54,1428,1429],{"class":143},"index",[54,1431,131],{"class":104},[54,1433,150],{"class":149},[54,1435,500],{"class":108},[10,1437,1438],{},"The report lists the Excel rows that contain empty items — the double semicolon on row 4 and the empty cell on row 5 — so the owner of the sheet can fix them at the source. Adding 2 converts the zero-based DataFrame position to a worksheet row number, because row 1 is the header.",[40,1440,1442],{"id":1441},"step-6-join-values-back-into-one-cell","Step 6 — Join values back into one cell",[10,1444,1445,1446,1449,1450,1453],{},"The reverse — one cell per product, values joined with a separator — is a group-by with ",[14,1447,1448],{},"str.join",", the pandas version of Excel's ",[14,1451,1452],{},"TEXTJOIN",":",[45,1455,1457],{"className":95,"code":1456,"language":97,"meta":50,"style":50},"clean_tags = tags.groupby(\"SKU\")[\"Tag\"].agg(lambda s: \"; \".join(sorted(s))).rename(\"Tags (clean)\")\nout = cat[[\"SKU\", \"Product\"]].merge(clean_tags, on=\"SKU\", how=\"left\").fillna({\"Tags (clean)\": \"\"})\nout.to_excel(\"catalogue_clean.xlsx\", index=False)\nprint(out.to_string(index=False))\n",[14,1458,1459,1502,1552,1570],{"__ignoreMap":50},[54,1460,1461,1464,1466,1469,1471,1474,1476,1479,1482,1485,1488,1491,1494,1497,1500],{"class":56,"line":57},[54,1462,1463],{"class":108},"clean_tags ",[54,1465,131],{"class":104},[54,1467,1468],{"class":108}," tags.groupby(",[54,1470,201],{"class":64},[54,1472,1473],{"class":108},")[",[54,1475,187],{"class":64},[54,1477,1478],{"class":108},"].agg(",[54,1480,1481],{"class":104},"lambda",[54,1483,1484],{"class":108}," s: ",[54,1486,1487],{"class":64},"\"; \"",[54,1489,1490],{"class":108},".join(",[54,1492,1493],{"class":149},"sorted",[54,1495,1496],{"class":108},"(s))).rename(",[54,1498,1499],{"class":64},"\"Tags (clean)\"",[54,1501,153],{"class":108},[54,1503,1504,1507,1509,1512,1514,1516,1518,1521,1524,1526,1528,1530,1533,1535,1538,1541,1543,1546,1549],{"class":56,"line":118},[54,1505,1506],{"class":108},"out ",[54,1508,131],{"class":104},[54,1510,1511],{"class":108}," cat[[",[54,1513,201],{"class":64},[54,1515,140],{"class":108},[54,1517,565],{"class":64},[54,1519,1520],{"class":108},"]].merge(clean_tags, ",[54,1522,1523],{"class":143},"on",[54,1525,131],{"class":104},[54,1527,201],{"class":64},[54,1529,140],{"class":108},[54,1531,1532],{"class":143},"how",[54,1534,131],{"class":104},[54,1536,1537],{"class":64},"\"left\"",[54,1539,1540],{"class":108},").fillna({",[54,1542,1499],{"class":64},[54,1544,1545],{"class":108},": ",[54,1547,1548],{"class":64},"\"\"",[54,1550,1551],{"class":108},"})\n",[54,1553,1554,1557,1560,1562,1564,1566,1568],{"class":56,"line":125},[54,1555,1556],{"class":108},"out.to_excel(",[54,1558,1559],{"class":64},"\"catalogue_clean.xlsx\"",[54,1561,140],{"class":108},[54,1563,1429],{"class":143},[54,1565,131],{"class":104},[54,1567,150],{"class":149},[54,1569,153],{"class":108},[54,1571,1572,1574,1577,1579,1581,1583],{"class":56,"line":156},[54,1573,195],{"class":149},[54,1575,1576],{"class":108},"(out.to_string(",[54,1578,1429],{"class":143},[54,1580,131],{"class":104},[54,1582,150],{"class":149},[54,1584,500],{"class":108},[10,1586,1587,1588,1592,1593,1597],{},"Round-tripping through the long format is also the cleanest way to ",[1589,1590,1591],"em",{},"normalise"," a delimited column: split, clean, de-duplicate and sort the values, then join them back with one consistent separator. See ",[34,1594,1596],{"href":1595},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ftextjoin-equivalent-in-pandas\u002F","TEXTJOIN Equivalent in pandas"," for more on joining.",[228,1599,237,1604,237,1607,237,1610,237,1613,237,1618,237,1626,237,1632,237,1637,237,1641,237,1646,237,1649,237,1652,237,1659,237,1665,237,1669,237,1672,237,1675,237,1678,237,1684,237,1688,237,1692,237,1695,237,1699,237,1702,237,1705,237,1711,237,1716],{"viewBox":1600,"role":231,"ariaLabelledBy":1601,"xmlns":235,"style":1174},"4 0 752 142",[1602,1603],"ex-roundtrip-t","ex-roundtrip-d",[239,1605,1606],{"id":1602},"Normalising a delimited column by round-tripping through rows",[243,1608,1609],{"id":1603},"The messy Tags cell is split and exploded into rows, each value is trimmed, lower-cased and de-duplicated, then the values are joined back into one cell per product with a consistent separator.",[247,1611],{"x":405,"y":250,"width":1183,"height":1612,"fill":253},"142",[247,1614],{"x":1187,"y":1188,"width":1615,"height":1616,"rx":1617,"fill":319,"stroke":320,"style":445},"153.0","110.5","12",[1195,1619,1621],{"transform":1620},"translate(84.5,29.0) scale(1.000)",[1199,1622],{"d":1623,"fill":1202,"stroke":1624,"style":1625},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--gold-ink,#7a4e06)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[268,1627,1631],{"x":1628,"y":1629,"style":1630},"96.5","75.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Messy cell",[268,1633,1636],{"x":1628,"y":1634,"style":1635},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","\"outdoor; Outdoor",[268,1638,1640],{"x":1628,"y":1639,"style":1635},"112.5",";safety\"",[56,1642],{"x1":1643,"y1":1644,"x2":1645,"y2":1644,"stroke":294,"style":445},"178.0","71.2","200.0",[447,1647],{"points":1648,"fill":450},"204.0,71.2 195.0,76.2 195.0,66.2",[247,1650],{"x":1651,"y":1188,"width":1615,"height":1616,"rx":1617,"fill":293,"stroke":294,"style":445},"209.0",[1195,1653,1655],{"transform":1654},"translate(273.5,37.8) scale(1.000)",[1199,1656],{"d":1657,"fill":1202,"stroke":1658,"style":1625},"M4 12h6 M10 12l6-6h4 M10 12l6 6h4 M17 3l3 3-3 3 M17 15l3 3-3 3","var(--brand-strong,#4338ca)",[268,1660,1664],{"x":1661,"y":1662,"style":1663},"285.5","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Split + explode",[268,1666,1668],{"x":1661,"y":1667,"style":1635},"103.8","one row per value",[56,1670],{"x1":1671,"y1":1644,"x2":1248,"y2":1644,"stroke":294,"style":445},"367.0",[447,1673],{"points":1674,"fill":450},"393.0,71.2 384.0,76.2 384.0,66.2",[247,1676],{"x":1677,"y":1188,"width":1615,"height":1616,"rx":1617,"fill":293,"stroke":294,"style":445},"398.0",[1195,1679,1681],{"transform":1680},"translate(462.5,29.0) scale(1.000)",[1199,1682],{"d":1683,"fill":1202,"stroke":1658,"style":1625},"M3 5h18l-7 8v6l-4 2v-8z",[268,1685,1687],{"x":1686,"y":1629,"style":1663},"474.5","Strip, lower,",[268,1689,1691],{"x":1686,"y":1690,"style":1663},"93.0","dedupe",[268,1693,1694],{"x":1686,"y":1639,"style":1635},"clean values",[56,1696],{"x1":1697,"y1":1644,"x2":1698,"y2":1644,"stroke":294,"style":445},"556.0","578.0",[447,1700],{"points":1701,"fill":450},"582.0,71.2 573.0,76.2 573.0,66.2",[247,1703],{"x":1704,"y":1188,"width":1615,"height":1616,"rx":1617,"fill":381,"stroke":382,"style":445},"587.0",[1195,1706,1708],{"transform":1707},"translate(651.5,37.8) scale(1.000)",[1199,1709],{"d":1710,"fill":1202,"stroke":1255,"style":1625},"M6 4v5a5 5 0 0 0 5 5h7 M6 20v-5 M15 11l3 3-3 3",[268,1712,1715],{"x":1713,"y":1662,"style":1714},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Join back",[268,1717,1718],{"x":1713,"y":1667,"style":1635},"\"outdoor; safety\"",[40,1720,1722],{"id":1721},"performance-notes","Performance notes",[10,1724,1725,84,1728,1730,1731,1734,1735,1738],{},[14,1726,1727],{},"str.split",[14,1729,31],{}," are vectorised and handle hundreds of thousands of rows in well under a second; the cost grows with the number of values produced, not the number of source rows. The row-wise ",[14,1732,1733],{},"apply"," used to pad mismatched lists in Step 4 is the slow part, so run it only on the rows that need it — select them with the length comparison first and assign the padded lists back to those rows. For very large files where most cells hold a single value, splitting only the cells that contain a separator (",[14,1736,1737],{},"str.contains(\";\")",") avoids creating millions of one-element lists.",[40,1740,1742],{"id":1741},"common-pitfalls","Common pitfalls",[1744,1745,1746,1760,1783,1789,1798],"ul",{},[1747,1748,1749,1753,1754,84,1756,1759],"li",{},[1750,1751,1752],"strong",{},"Forgetting to strip."," ",[14,1755,221],{},[14,1757,1758],{},"\"outdoor\""," count as different values; strip after exploding.",[1747,1761,1762,1765,1766,1768,1769,1772,1773,1768,1776,1778,1779,1782],{},[1750,1763,1764],{},"Empty strings versus NaN."," Splitting ",[14,1767,1548],{}," gives ",[14,1770,1771],{},"[\"\"]"," and an empty-string row; splitting ",[14,1774,1775],{},"NaN",[14,1777,1775],{},". Read with ",[14,1780,1781],{},"keep_default_na=False"," or fill first, then drop empty items.",[1747,1784,1785,1788],{},[1750,1786,1787],{},"Exploding paired columns separately."," It creates a cross product; explode them together.",[1747,1790,1791,1794,1795,1797],{},[1750,1792,1793],{},"Duplicate index after explode."," Call ",[14,1796,758],{}," before index-based operations that expect unique labels.",[1747,1799,1800,1803,1804,1807],{},[1750,1801,1802],{},"Separators inside values."," A product called ",[14,1805,1806],{},"Nuts, bolts & washers"," in a comma-separated list cannot be split reliably; agree a separator that never appears in the values.",[40,1809,1811],{"id":1810},"conclusion","Conclusion",[10,1813,1814,1815,1817,1818,1820,1821,84,1824,1827],{},"Split delimited cells with ",[14,1816,1727],{},", give each value its own row with ",[14,1819,31],{},", then strip, normalise case, drop empty items and de-duplicate. Explode paired columns together so their values stay matched, treat the \"matching element counts\" error as a data-quality report, keep the source row number for corrections, and join the cleaned values back with ",[14,1822,1823],{},"groupby",[14,1825,1826],{},"\"; \".join"," when one cell per record is needed again.",[40,1829,1831],{"id":1830},"frequently-asked-questions","Frequently asked questions",[10,1833,1834,1837,1838,1840],{},[1750,1835,1836],{},"How do I split a cell with several values into multiple rows in pandas?","\nSplit the text into lists with df",[54,1839,175],{},".str.split(\";\"), then call df.explode(\"Tags\"). Each list element gets its own row and the other columns are repeated.",[10,1842,1843,1846,1847,1853],{},[1750,1844,1845],{},"How do I remove spaces and empty values after exploding?","\nApply str.strip() to the exploded column, then filter out empty strings with df",[54,1848,1849,1850,1852],{},"df",[54,1851,187],{}," != \"\"",". Lower-case the values and drop duplicates if the same value can appear twice in one cell.",[10,1855,1856,1859],{},[1750,1857,1858],{},"Why does explode say \"columns must have matching element counts\"?","\nYou exploded several columns at once and at least one row has lists of different lengths. Find those rows by comparing str.len() of the columns, fix or pad them, then explode again.",[10,1861,1862,1865,1866,1868],{},[1750,1863,1864],{},"How do I combine exploded rows back into one cell?","\nGroup by the record key and join the values: df.groupby(\"SKU\")",[54,1867,187],{},".agg(\"; \".join). Sort the values first if you want a consistent order.",[40,1870,1872],{"id":1871},"related","Related",[1744,1874,1875,1881,1888,1895],{},[1747,1876,1877,1878,1880],{},"Up one level: ",[34,1879,37],{"href":36}," — tidying messy spreadsheet data.",[1747,1882,1883,1887],{},[34,1884,1886],{"href":1885},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-a-column-into-multiple-columns-with-pandas\u002F","Split a Column into Multiple Columns with pandas"," — splitting into columns instead of rows.",[1747,1889,1890,1894],{},[34,1891,1893],{"href":1892},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fparse-records-that-span-multiple-rows-in-excel\u002F","Parse Records That Span Multiple Rows in Excel"," — the opposite layout problem.",[1747,1896,1897,1901],{},[34,1898,1900],{"href":1899},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstrip-whitespace-and-normalise-text-columns-with-pandas\u002F","Strip Whitespace and Normalise Text Columns with pandas"," — cleaning the pieces.",[1903,1904,1905],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":50,"searchDepth":118,"depth":118,"links":1907},[1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919],{"id":42,"depth":118,"text":43},{"id":91,"depth":118,"text":92},{"id":467,"depth":118,"text":468},{"id":589,"depth":118,"text":590},{"id":766,"depth":118,"text":767},{"id":1292,"depth":118,"text":1293},{"id":1441,"depth":118,"text":1442},{"id":1721,"depth":118,"text":1722},{"id":1741,"depth":118,"text":1742},{"id":1810,"depth":118,"text":1811},{"id":1830,"depth":118,"text":1831},{"id":1871,"depth":118,"text":1872},"2026-10-02","Turn cells like \"Red; Blue; Green\" or \"INV-1, INV-2\" into one row per value with str.split and explode: clean up spaces and empty items, split two parallel columns together (and handle the \"matching element counts\" error when they disagree), keep a link to the original row, count and pivot the results, and join them back into one cell when needed.","md",[1924,1926,1928,1930],{"q":1836,"a":1925},"Split the text into lists with df[\"Tags\"].str.split(\";\"), then call df.explode(\"Tags\"). Each list element gets its own row and the other columns are repeated.",{"q":1845,"a":1927},"Apply str.strip() to the exploded column, then filter out empty strings with df[df[\"Tag\"] != \"\"]. Lower-case the values and drop duplicates if the same value can appear twice in one cell.",{"q":1858,"a":1929},"You exploded several columns at once and at least one row has lists of different lengths. Find those rows by comparing str.len() of the columns, fix or pad them, then explode again.",{"q":1864,"a":1931},"Group by the record key and join the values: df.groupby(\"SKU\")[\"Tag\"].agg(\"; \".join). Sort the values first if you want a consistent order.","split delimited cell values into rows with pandas",null,{"breadcrumb":1935},[1936,1939,1942],{"name":1937,"item":1938},"Home","\u002F",{"name":1940,"item":1941},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":1943,"item":36},"Cleaning Excel Data With Pandas: A Production Workflow","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-delimited-cell-values-into-rows-with-pandas",{"title":1946,"description":1947},"Split Delimited Excel Cell Values into Rows with pandas","One row per value from cells like \"A; B; C\": str.split + explode, trimming and dropping empty items, exploding parallel columns and the matching element counts ValueError, keeping the source row, counting values, and rejoining with groupby.","split-delimited-cell-values-into-rows-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-delimited-cell-values-into-rows-with-pandas\u002Findex",[1951,1952],"data-cleaning","pandas","how-to","wo6vDCx49ZhD9KM8OkiyWU_HDdbAnh1ClW9xG8SmO4Q",[1956,1960],{"title":1957,"path":1958,"stem":1959,"children":-1},"Split an Address Column into Street, City and Postcode","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-an-address-column-into-street-city-and-postcode","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-an-address-column-into-street-city-and-postcode\u002Findex",{"title":1961,"path":1962,"stem":1963,"children":-1},"Split Full Names into First and Last Names with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-full-names-into-first-and-last-names-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-full-names-into-first-and-last-names-with-pandas\u002Findex",[1965,1967],{"path":1966,"title":1943},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas",{"path":1968,"title":1940},"\u002Fadvanced-data-transformation-and-cleaning",[],[1971,1974,1977,1980,1983,1986],{"path":1972,"title":1973,"kind":1953},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Ffind-duplicate-rows-in-excel-with-python","Find Duplicate Rows in Excel with Python",{"path":1975,"title":1976,"kind":1953},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-duplicate-values-in-excel-with-openpyxl","Highlight Duplicate Values in Excel with openpyxl",{"path":1978,"title":1979,"kind":1953},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fclean-mixed-currency-amounts-into-amount-and-currency-columns","Clean Mixed-Currency Amounts into Amount and Currency Columns",{"path":1981,"title":1982,"kind":1953},"\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":1984,"title":1985,"kind":1953},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-currency-strings-to-numbers-with-pandas","Convert Currency Strings Like \"$1,234.56\" and \"(500)\" to Numbers with pandas",{"path":1987,"title":1988,"kind":1953},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-excel-text-columns-to-numbers-with-pandas","Convert Excel Text Columns to Numbers with pandas",1790972085923]