[{"data":1,"prerenderedAt":2220},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-trailing-minus-and-cr-dr-amounts-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-trailing-minus-and-cr-dr-amounts-with-pandas":2187,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-trailing-minus-and-cr-dr-amounts-with-pandas":2196,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-trailing-minus-and-cr-dr-amounts-with-pandas":2201,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-trailing-minus-and-cr-dr-amounts-with-pandas":2202},{"id":4,"title":5,"body":6,"dateModified":2153,"datePublished":2153,"description":2154,"extension":2155,"faq":2156,"keyword":2165,"kind":2166,"meta":2167,"navigation":135,"path":2176,"seo":2177,"slug":2180,"stem":2181,"topics":2182,"type":2185,"__hash__":2186},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-trailing-minus-and-cr-dr-amounts-with-pandas\u002Findex.md","Convert Trailing Minus and CR\u002FDR Amounts with pandas",{"type":7,"value":8,"toc":2139},"minimark",[9,44,49,77,107,266,290,498,502,505,562,571,718,722,725,1115,1140,1144,1150,1313,1323,1465,1469,1472,1583,1598,1602,1629,1828,1841,1845,1848,1978,1986,1990,2009,2013,2053,2057,2074,2078,2084,2090,2096,2102,2106,2135],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,37,38,43],"p",{},"Open an export from SAP, a mainframe ledger or a bank portal and the amount column often looks numeric but is text: ",[14,15,16],"code",{},"1,250.00-"," with the minus at the end, ",[14,19,20],{},"(310.50)"," in accounting brackets, ",[14,23,24],{},"845.20 CR",", or two columns — an amount and a ",[14,27,28],{},"D","\u002F",[14,31,32],{},"C"," indicator — that together give the sign. Excel shows these as left-aligned text, sums them to zero, and its own Text to Columns fixes only some. ",[14,35,36],{},"pd.to_numeric"," rejects them outright. The fix is one parsing function that recognises each convention, applied to the whole column at once, followed by a check that every value was understood. This page is part of ",[39,40,42],"a",{"href":41},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002F","Cleaning Excel Data with pandas",".",[45,46,48],"h2",{"id":47},"prerequisites","Prerequisites",[50,51,56],"pre",{"className":52,"code":53,"language":54,"meta":55,"style":55},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,57,58],{"__ignoreMap":55},[59,60,63,67,71,74],"span",{"class":61,"line":62},"line",1,[59,64,66],{"class":65},"sMTad","pip",[59,68,70],{"class":69},"srMev"," install",[59,72,73],{"class":69}," pandas",[59,75,76],{"class":69}," openpyxl\n",[10,78,79,82,83,86,87,90,91,94,95,98,99,102,103,106],{},[14,80,81],{},"gl_export.xlsx"," is a general ledger extract where the ",[14,84,85],{},"Amount"," column mixes every convention: plain numbers with thousands separators, a trailing minus, brackets, ",[14,88,89],{},"CR"," and ",[14,92,93],{},"DR"," suffixes, padding spaces, an ordinary leading minus, and one ",[14,96,97],{},"n\u002Fa",". ",[14,100,101],{},"bank_export.xlsx"," comes from a German bank: amounts written ",[14,104,105],{},"1.234,56-",", and a second layout with an unsigned amount and a separate debit\u002Fcredit column.",[50,108,112],{"className":109,"code":110,"language":111,"meta":55,"style":55},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ngl = pd.read_excel(\"gl_export.xlsx\", dtype={\"DocNo\": str, \"Amount\": str}, keep_default_na=False)\nprint(gl.to_string(index=False))\ntry:\n    pd.to_numeric(gl[\"Amount\"])\nexcept ValueError as err:\n    print(\"to_numeric:\", err)\n","python",[14,113,114,130,137,197,216,225,236,251],{"__ignoreMap":55},[59,115,116,120,124,127],{"class":61,"line":62},[59,117,119],{"class":118},"s-kum","import",[59,121,123],{"class":122},"skGVy"," pandas ",[59,125,126],{"class":118},"as",[59,128,129],{"class":122}," pd\n",[59,131,133],{"class":61,"line":132},2,[59,134,136],{"emptyLinePlaceholder":135},true,"\n",[59,138,140,143,146,149,152,155,159,161,164,167,170,174,176,179,181,183,186,189,191,194],{"class":61,"line":139},3,[59,141,142],{"class":122},"gl ",[59,144,145],{"class":118},"=",[59,147,148],{"class":122}," pd.read_excel(",[59,150,151],{"class":69},"\"gl_export.xlsx\"",[59,153,154],{"class":122},", ",[59,156,158],{"class":157},"sa561","dtype",[59,160,145],{"class":118},[59,162,163],{"class":122},"{",[59,165,166],{"class":69},"\"DocNo\"",[59,168,169],{"class":122},": ",[59,171,173],{"class":172},"sP0c6","str",[59,175,154],{"class":122},[59,177,178],{"class":69},"\"Amount\"",[59,180,169],{"class":122},[59,182,173],{"class":172},[59,184,185],{"class":122},"}, ",[59,187,188],{"class":157},"keep_default_na",[59,190,145],{"class":118},[59,192,193],{"class":172},"False",[59,195,196],{"class":122},")\n",[59,198,200,203,206,209,211,213],{"class":61,"line":199},4,[59,201,202],{"class":172},"print",[59,204,205],{"class":122},"(gl.to_string(",[59,207,208],{"class":157},"index",[59,210,145],{"class":118},[59,212,193],{"class":172},[59,214,215],{"class":122},"))\n",[59,217,219,222],{"class":61,"line":218},5,[59,220,221],{"class":118},"try",[59,223,224],{"class":122},":\n",[59,226,228,231,233],{"class":61,"line":227},6,[59,229,230],{"class":122},"    pd.to_numeric(gl[",[59,232,178],{"class":69},[59,234,235],{"class":122},"])\n",[59,237,239,242,245,248],{"class":61,"line":238},7,[59,240,241],{"class":118},"except",[59,243,244],{"class":172}," ValueError",[59,246,247],{"class":118}," as",[59,249,250],{"class":122}," err:\n",[59,252,254,257,260,263],{"class":61,"line":253},8,[59,255,256],{"class":172},"    print",[59,258,259],{"class":122},"(",[59,261,262],{"class":69},"\"to_numeric:\"",[59,264,265],{"class":122},", err)\n",[10,267,268,269,272,273,276,277,280,281,154,283,90,286,289],{},"Reading with ",[14,270,271],{},"dtype=str"," keeps the column exactly as exported; letting pandas guess would turn the few plain numbers into floats and leave the rest as text, a mixed column that is harder to reason about. ",[14,274,275],{},"keep_default_na=False"," matters too: by default ",[14,278,279],{},"read_excel"," turns strings such as ",[14,282,97],{},[14,284,285],{},"NA",[14,287,288],{},"null"," into missing values before your code sees them, so a placeholder that someone typed into the export would vanish instead of being reported. With it switched off, only truly empty cells arrive as empty strings, and every other value reaches the parser.",[291,292,300,301,300,305,300,309,300,317,300,326,300,333,300,340,300,345,300,348,300,352,300,354,300,356,300,359,300,363,300,365,300,367,300,370,300,374,300,376,300,378,300,381,300,385,300,387,300,390,300,393,300,397,300,399,300,402,300,405,300,408,300,414,300,418,300,420,300,422,300,424,300,427,300,429,300,431,300,433,300,436,300,438,300,440,300,442,300,445,300,447,300,449,300,451,300,454,300,456,300,458,300,460,300,463,300,469,300,473,300,479,300,484,300,492],"svg",{"viewBox":293,"role":294,"ariaLabelledBy":295,"xmlns":298,"style":299},"155 0 449 224","img",[296,297],"tm-before-after-t","tm-before-after-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:449px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[302,303,304],"title",{"id":296},"Six sign conventions, one numeric column",[306,307,308],"desc",{"id":297},"On the left the exported amount strings with trailing minus, brackets, CR and DR suffixes and padding. On the right the same values as signed numbers.",[310,311],"rect",{"x":312,"y":313,"width":314,"height":315,"fill":316},"155","0","449","224","#ffffff",[310,318],{"x":319,"y":320,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},"171.8","40.0","32.0","28.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[327,328,332],"text",{"x":329,"y":330,"style":331},"187.8","59.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","1",[310,334],{"x":335,"y":320,"width":336,"height":322,"rx":313,"fill":337,"stroke":338,"style":339},"203.8","102.4","#fdefd8","var(--gold,#b4740a)","stroke-width:1.5px",[327,341,344],{"x":342,"y":330,"style":343},"255.0","font-size:11px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Amount (text)",[310,346],{"x":319,"y":347,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},"68.0",[327,349,351],{"x":329,"y":350,"style":331},"87.0","2",[310,353],{"x":335,"y":347,"width":336,"height":322,"rx":313,"fill":337,"stroke":338,"style":339},[327,355,16],{"x":342,"y":350,"style":343},[310,357],{"x":319,"y":358,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},"96.0",[327,360,362],{"x":329,"y":361,"style":331},"115.0","3",[310,364],{"x":335,"y":358,"width":336,"height":322,"rx":313,"fill":337,"stroke":338,"style":339},[327,366,20],{"x":342,"y":361,"style":343},[310,368],{"x":319,"y":369,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},"124.0",[327,371,373],{"x":329,"y":372,"style":331},"143.0","4",[310,375],{"x":335,"y":369,"width":336,"height":322,"rx":313,"fill":337,"stroke":338,"style":339},[327,377,24],{"x":342,"y":372,"style":343},[310,379],{"x":319,"y":380,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},"152.0",[327,382,384],{"x":329,"y":383,"style":331},"171.0","5",[310,386],{"x":335,"y":380,"width":336,"height":322,"rx":313,"fill":337,"stroke":338,"style":339},[327,388,389],{"x":342,"y":383,"style":343},"12.00 DR",[310,391],{"x":319,"y":392,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},"180.0",[327,394,396],{"x":329,"y":395,"style":331},"199.0","6",[310,398],{"x":335,"y":392,"width":336,"height":322,"rx":313,"fill":337,"stroke":338,"style":339},[327,400,401],{"x":342,"y":395,"style":343},"  2,000  ",[310,403],{"x":404,"y":320,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},"480.2",[327,406,332],{"x":407,"y":330,"style":331},"496.2",[310,409],{"x":410,"y":320,"width":411,"height":322,"rx":313,"fill":412,"stroke":413,"style":339},"512.2","76.1","#d9f4f1","var(--teal,#0f9488)",[327,415,85],{"x":416,"y":330,"style":417},"550.2","font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[310,419],{"x":404,"y":347,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},[327,421,351],{"x":407,"y":350,"style":331},[310,423],{"x":410,"y":347,"width":411,"height":322,"rx":313,"fill":412,"stroke":413,"style":339},[327,425,426],{"x":416,"y":350,"style":417},"-1250.00",[310,428],{"x":404,"y":358,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},[327,430,362],{"x":407,"y":361,"style":331},[310,432],{"x":410,"y":358,"width":411,"height":322,"rx":313,"fill":412,"stroke":413,"style":339},[327,434,435],{"x":416,"y":361,"style":417},"-310.50",[310,437],{"x":404,"y":369,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},[327,439,373],{"x":407,"y":372,"style":331},[310,441],{"x":410,"y":369,"width":411,"height":322,"rx":313,"fill":412,"stroke":413,"style":339},[327,443,444],{"x":416,"y":372,"style":417},"-845.20",[310,446],{"x":404,"y":380,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},[327,448,384],{"x":407,"y":383,"style":331},[310,450],{"x":410,"y":380,"width":411,"height":322,"rx":313,"fill":412,"stroke":413,"style":339},[327,452,453],{"x":416,"y":383,"style":417},"12.00",[310,455],{"x":404,"y":392,"width":321,"height":322,"rx":313,"fill":323,"stroke":324,"style":325},[327,457,396],{"x":407,"y":395,"style":331},[310,459],{"x":410,"y":392,"width":411,"height":322,"rx":313,"fill":412,"stroke":413,"style":339},[327,461,462],{"x":416,"y":395,"style":417},"2000.00",[327,464,468],{"x":465,"y":466,"style":467},"239.0","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[327,470,472],{"x":471,"y":466,"style":467},"534.2","after",[61,474],{"x1":475,"y1":369,"x2":476,"y2":369,"stroke":477,"style":478},"316.1","466.2","var(--brand,#5b5cf0)","stroke-width:2px",[480,481],"polygon",{"points":482,"fill":483},"470.2,124.0 461.2,129.0 461.2,119.0","#5b5cf0",[310,485],{"x":486,"y":487,"width":488,"height":489,"rx":490,"fill":316,"stroke":324,"style":491},"339.5","91.0","107.2","22.0","11","stroke-width:1.2px",[327,493,497],{"x":494,"y":495,"style":496},"393.1","106.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","parse_amount",[45,499,501],{"id":500},"step-1-decide-what-each-convention-means","Step 1 — Decide what each convention means",[10,503,504],{},"Before writing code, pin down the sign rules, because they are not universal:",[506,507,508,519,527,553],"ul",{},[509,510,511,515,516,518],"li",{},[512,513,514],"strong",{},"Trailing minus"," (",[14,517,16],{},") — negative. Standard in SAP and many mainframe reports.",[509,520,521,515,524,526],{},[512,522,523],{},"Brackets",[14,525,20],{},") — negative, the accounting convention.",[509,528,529,535,536,538,539,541,542,544,545,547,548,552],{},[512,530,531,90,533],{},[14,532,89],{},[14,534,93],{}," — depends on whose books you are looking at. In a customer's account in your ledger, a credit (",[14,537,89],{},") reduces what they owe, so it is negative; a debit (",[14,540,93],{},") increases it. In a bank statement, ",[14,543,89],{}," is money in. Decide which sign ",[14,546,89],{}," gets ",[549,550,551],"em",{},"for this report"," and write it down.",[509,554,555,515,558,561],{},[512,556,557],{},"Leading minus",[14,559,560],{},"-75.25",") — negative, as usual.",[10,563,564,565,567,568,570],{},"The code below treats ",[14,566,89],{}," as negative and ",[14,569,93],{}," as positive, the ledger view. Swap them in one place if your source uses the bank view.",[291,572,300,578,300,581,300,584,300,588,300,593,300,598,300,604,300,607,300,611,300,614,300,618,300,622,300,626,300,628,300,632,300,634,300,637,300,639,300,641,300,644,300,647,300,649,300,652,300,654,300,656,300,658,300,660,300,662,300,665,300,667,300,670,300,672,300,674,300,676,300,678,300,681,300,684,300,686,300,689,300,691,300,693,300,695,300,698,300,701,300,705,300,707,300,710,300,712,300,714,300,716],{"viewBox":573,"role":294,"ariaLabelledBy":574,"xmlns":298,"style":577},"4 0 752 242",[575,576],"tm-rules-t","tm-rules-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[302,579,580],{"id":575},"Sign conventions and what they mean",[306,582,583],{"id":576},"A table of export conventions with an example of each and its sign in a ledger view and in a bank-statement view. Trailing minus, brackets and leading minus are negative in both. CR is negative in the ledger view but positive on a bank statement; DR is the reverse.",[310,585],{"x":373,"y":313,"width":586,"height":587,"fill":316},"752","242",[310,589],{"x":590,"y":591,"width":592,"height":320,"rx":313,"fill":316,"stroke":324,"style":325},"20.0","16.0","121.5",[310,594],{"x":595,"y":591,"width":596,"height":320,"rx":313,"fill":597,"stroke":477,"style":491},"141.5","199.5","#f0f4ff",[327,599,603],{"x":600,"y":601,"style":602},"241.3","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Example",[310,605],{"x":606,"y":591,"width":596,"height":320,"rx":313,"fill":597,"stroke":477,"style":491},"341.0",[327,608,610],{"x":609,"y":601,"style":602},"440.8","Ledger view",[310,612],{"x":613,"y":591,"width":596,"height":320,"rx":313,"fill":597,"stroke":477,"style":491},"540.5",[327,615,617],{"x":616,"y":601,"style":602},"640.3","Bank view",[310,619],{"x":590,"y":620,"width":592,"height":621,"rx":313,"fill":323,"stroke":324,"style":325},"56.0","34.0",[327,623,514],{"x":321,"y":624,"style":625},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[310,627],{"x":595,"y":620,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,629,16],{"x":600,"y":630,"style":631},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[310,633],{"x":606,"y":620,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,635,636],{"x":609,"y":630,"style":631},"negative",[310,638],{"x":613,"y":620,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,640,636],{"x":616,"y":630,"style":631},[310,642],{"x":590,"y":643,"width":592,"height":621,"rx":313,"fill":323,"stroke":324,"style":325},"90.0",[327,645,523],{"x":321,"y":646,"style":625},"112.0",[310,648],{"x":595,"y":643,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,650,20],{"x":600,"y":651,"style":631},"111.0",[310,653],{"x":606,"y":643,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,655,636],{"x":609,"y":651,"style":631},[310,657],{"x":613,"y":643,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,659,636],{"x":616,"y":651,"style":631},[310,661],{"x":590,"y":369,"width":592,"height":621,"rx":313,"fill":323,"stroke":324,"style":325},[327,663,557],{"x":321,"y":664,"style":625},"146.0",[310,666],{"x":595,"y":369,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,668,560],{"x":600,"y":669,"style":631},"145.0",[310,671],{"x":606,"y":369,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,673,636],{"x":609,"y":669,"style":631},[310,675],{"x":613,"y":369,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,677,636],{"x":616,"y":669,"style":631},[310,679],{"x":590,"y":680,"width":592,"height":621,"rx":313,"fill":323,"stroke":324,"style":325},"158.0",[327,682,683],{"x":321,"y":392,"style":625},"CR suffix",[310,685],{"x":595,"y":680,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,687,24],{"x":600,"y":688,"style":631},"179.0",[310,690],{"x":606,"y":680,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,692,636],{"x":609,"y":688,"style":631},[310,694],{"x":613,"y":680,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,696,697],{"x":616,"y":688,"style":631},"positive",[310,699],{"x":590,"y":700,"width":592,"height":621,"rx":313,"fill":323,"stroke":324,"style":325},"192.0",[327,702,704],{"x":321,"y":703,"style":625},"214.0","DR suffix",[310,706],{"x":595,"y":700,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,708,389],{"x":600,"y":709,"style":631},"213.0",[310,711],{"x":606,"y":700,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,713,697],{"x":609,"y":709,"style":631},[310,715],{"x":613,"y":700,"width":596,"height":621,"rx":313,"fill":316,"stroke":324,"style":325},[327,717,636],{"x":616,"y":709,"style":631},[45,719,721],{"id":720},"step-2-parse-every-convention-in-one-pass","Step 2 — Parse every convention in one pass",[10,723,724],{},"Clean the text, detect each marker with vectorised string methods, strip the markers, convert the remaining digits, and apply the sign:",[50,726,728],{"className":109,"code":727,"language":111,"meta":55,"style":55},"import numpy as np\n\nCR_IS_NEGATIVE = True\n\ndef parse_amount(s, decimal=\".\", thousands=\",\"):\n    t = s.astype(\"string\").str.strip().str.upper()\n    neg = (t.str.endswith(\"-\") | t.str.startswith(\"-\")\n           | (t.str.startswith(\"(\") & t.str.endswith(\")\")))\n    cr = t.str.endswith(\"CR\")\n    dr = t.str.endswith(\"DR\")\n    neg = neg | (cr if CR_IS_NEGATIVE else dr)\n    digits = (t.str.replace(r\"\\s*(CR|DR)$\", \"\", regex=True)\n                .str.strip(\"-() \")\n                .str.replace(thousands, \"\", regex=False)\n                .str.replace(decimal, \".\", regex=False))\n    value = pd.to_numeric(digits, errors=\"coerce\")\n    return value.where(~neg.fillna(False), -value)\n\ngl[\"AmountNum\"] = parse_amount(gl[\"Amount\"])\nprint(gl[[\"DocNo\", \"Amount\", \"AmountNum\"]].to_string(index=False))\n",[14,729,730,742,746,757,761,789,805,831,856,871,886,913,966,977,995,1013,1034,1060,1065,1086],{"__ignoreMap":55},[59,731,732,734,737,739],{"class":61,"line":62},[59,733,119],{"class":118},[59,735,736],{"class":122}," numpy ",[59,738,126],{"class":118},[59,740,741],{"class":122}," np\n",[59,743,744],{"class":61,"line":132},[59,745,136],{"emptyLinePlaceholder":135},[59,747,748,751,754],{"class":61,"line":139},[59,749,750],{"class":172},"CR_IS_NEGATIVE",[59,752,753],{"class":118}," =",[59,755,756],{"class":172}," True\n",[59,758,759],{"class":61,"line":199},[59,760,136],{"emptyLinePlaceholder":135},[59,762,763,766,770,773,775,778,781,783,786],{"class":61,"line":218},[59,764,765],{"class":118},"def",[59,767,769],{"class":768},"s_Opv"," parse_amount",[59,771,772],{"class":122},"(s, decimal",[59,774,145],{"class":118},[59,776,777],{"class":69},"\".\"",[59,779,780],{"class":122},", thousands",[59,782,145],{"class":118},[59,784,785],{"class":69},"\",\"",[59,787,788],{"class":122},"):\n",[59,790,791,794,796,799,802],{"class":61,"line":227},[59,792,793],{"class":122},"    t ",[59,795,145],{"class":118},[59,797,798],{"class":122}," s.astype(",[59,800,801],{"class":69},"\"string\"",[59,803,804],{"class":122},").str.strip().str.upper()\n",[59,806,807,810,812,815,818,821,824,827,829],{"class":61,"line":238},[59,808,809],{"class":122},"    neg ",[59,811,145],{"class":118},[59,813,814],{"class":122}," (t.str.endswith(",[59,816,817],{"class":69},"\"-\"",[59,819,820],{"class":122},") ",[59,822,823],{"class":118},"|",[59,825,826],{"class":122}," t.str.startswith(",[59,828,817],{"class":69},[59,830,196],{"class":122},[59,832,833,836,839,842,844,847,850,853],{"class":61,"line":253},[59,834,835],{"class":118},"           |",[59,837,838],{"class":122}," (t.str.startswith(",[59,840,841],{"class":69},"\"(\"",[59,843,820],{"class":122},[59,845,846],{"class":118},"&",[59,848,849],{"class":122}," t.str.endswith(",[59,851,852],{"class":69},"\")\"",[59,854,855],{"class":122},")))\n",[59,857,859,862,864,866,869],{"class":61,"line":858},9,[59,860,861],{"class":122},"    cr ",[59,863,145],{"class":118},[59,865,849],{"class":122},[59,867,868],{"class":69},"\"CR\"",[59,870,196],{"class":122},[59,872,874,877,879,881,884],{"class":61,"line":873},10,[59,875,876],{"class":122},"    dr ",[59,878,145],{"class":118},[59,880,849],{"class":122},[59,882,883],{"class":69},"\"DR\"",[59,885,196],{"class":122},[59,887,889,891,893,896,898,901,904,907,910],{"class":61,"line":888},11,[59,890,809],{"class":122},[59,892,145],{"class":118},[59,894,895],{"class":122}," neg ",[59,897,823],{"class":118},[59,899,900],{"class":122}," (cr ",[59,902,903],{"class":118},"if",[59,905,906],{"class":172}," CR_IS_NEGATIVE",[59,908,909],{"class":118}," else",[59,911,912],{"class":122}," dr)\n",[59,914,916,919,921,924,927,930,933,936,938,940,942,944,947,949,951,954,956,959,961,964],{"class":61,"line":915},12,[59,917,918],{"class":122},"    digits ",[59,920,145],{"class":118},[59,922,923],{"class":122}," (t.str.replace(",[59,925,926],{"class":118},"r",[59,928,929],{"class":69},"\"",[59,931,932],{"class":172},"\\s",[59,934,935],{"class":118},"*",[59,937,259],{"class":172},[59,939,89],{"class":69},[59,941,823],{"class":118},[59,943,93],{"class":69},[59,945,946],{"class":172},")$",[59,948,929],{"class":69},[59,950,154],{"class":122},[59,952,953],{"class":69},"\"\"",[59,955,154],{"class":122},[59,957,958],{"class":157},"regex",[59,960,145],{"class":118},[59,962,963],{"class":172},"True",[59,965,196],{"class":122},[59,967,969,972,975],{"class":61,"line":968},13,[59,970,971],{"class":122},"                .str.strip(",[59,973,974],{"class":69},"\"-() \"",[59,976,196],{"class":122},[59,978,980,983,985,987,989,991,993],{"class":61,"line":979},14,[59,981,982],{"class":122},"                .str.replace(thousands, ",[59,984,953],{"class":69},[59,986,154],{"class":122},[59,988,958],{"class":157},[59,990,145],{"class":118},[59,992,193],{"class":172},[59,994,196],{"class":122},[59,996,998,1001,1003,1005,1007,1009,1011],{"class":61,"line":997},15,[59,999,1000],{"class":122},"                .str.replace(decimal, ",[59,1002,777],{"class":69},[59,1004,154],{"class":122},[59,1006,958],{"class":157},[59,1008,145],{"class":118},[59,1010,193],{"class":172},[59,1012,215],{"class":122},[59,1014,1016,1019,1021,1024,1027,1029,1032],{"class":61,"line":1015},16,[59,1017,1018],{"class":122},"    value ",[59,1020,145],{"class":118},[59,1022,1023],{"class":122}," pd.to_numeric(digits, ",[59,1025,1026],{"class":157},"errors",[59,1028,145],{"class":118},[59,1030,1031],{"class":69},"\"coerce\"",[59,1033,196],{"class":122},[59,1035,1037,1040,1043,1046,1049,1051,1054,1057],{"class":61,"line":1036},17,[59,1038,1039],{"class":118},"    return",[59,1041,1042],{"class":122}," value.where(",[59,1044,1045],{"class":118},"~",[59,1047,1048],{"class":122},"neg.fillna(",[59,1050,193],{"class":172},[59,1052,1053],{"class":122},"), ",[59,1055,1056],{"class":118},"-",[59,1058,1059],{"class":122},"value)\n",[59,1061,1063],{"class":61,"line":1062},18,[59,1064,136],{"emptyLinePlaceholder":135},[59,1066,1068,1071,1074,1077,1079,1082,1084],{"class":61,"line":1067},19,[59,1069,1070],{"class":122},"gl[",[59,1072,1073],{"class":69},"\"AmountNum\"",[59,1075,1076],{"class":122},"] ",[59,1078,145],{"class":118},[59,1080,1081],{"class":122}," parse_amount(gl[",[59,1083,178],{"class":69},[59,1085,235],{"class":122},[59,1087,1089,1091,1094,1096,1098,1100,1102,1104,1107,1109,1111,1113],{"class":61,"line":1088},20,[59,1090,202],{"class":172},[59,1092,1093],{"class":122},"(gl[[",[59,1095,166],{"class":69},[59,1097,154],{"class":122},[59,1099,178],{"class":69},[59,1101,154],{"class":122},[59,1103,1073],{"class":69},[59,1105,1106],{"class":122},"]].to_string(",[59,1108,208],{"class":157},[59,1110,145],{"class":118},[59,1112,193],{"class":172},[59,1114,215],{"class":122},[10,1116,1117,1118,1121,1122,1124,1125,1127,1128,1131,1132,1135,1136,1139],{},"Each step works on the whole column: ",[14,1119,1120],{},"str.endswith(\"-\")"," finds trailing minus signs, the bracket test needs both an opening and a closing bracket, and the regular expression removes a ",[14,1123,89],{}," or ",[14,1126,93],{}," suffix together with the space before it. ",[14,1129,1130],{},"str.strip(\"-() \")"," then removes the sign characters from both ends, and the thousands separator goes before the decimal separator is normalised to a point — the order matters for the European format in Step 4. ",[14,1133,1134],{},"errors=\"coerce\""," turns anything that is still not a number into ",[14,1137,1138],{},"NaN"," instead of raising, which the next step deals with.",[45,1141,1143],{"id":1142},"step-3-check-that-nothing-was-lost","Step 3 — Check that nothing was lost",[10,1145,1146,1147,1149],{},"Coercing to ",[14,1148,1138],{}," is only safe if you then look at what was coerced. List every value that was not blank but did not parse, and reconcile the total with the source system:",[50,1151,1153],{"className":109,"code":1152,"language":111,"meta":55,"style":55},"failed = gl[gl[\"AmountNum\"].isna() & gl[\"Amount\"].str.strip().ne(\"\")]\nprint(\"could not parse:\", failed[[\"DocNo\", \"Amount\"]].values.tolist())\nprint(f\"net total: {gl['AmountNum'].sum():,.2f}\")\nprint(f\"debits {gl.loc[gl['AmountNum'] > 0, 'AmountNum'].sum():,.2f}  \"\n      f\"credits {gl.loc[gl['AmountNum'] \u003C 0, 'AmountNum'].sum():,.2f}\")\n",[14,1154,1155,1185,1206,1239,1278],{"__ignoreMap":55},[59,1156,1157,1160,1162,1165,1167,1170,1172,1175,1177,1180,1182],{"class":61,"line":62},[59,1158,1159],{"class":122},"failed ",[59,1161,145],{"class":118},[59,1163,1164],{"class":122}," gl[gl[",[59,1166,1073],{"class":69},[59,1168,1169],{"class":122},"].isna() ",[59,1171,846],{"class":118},[59,1173,1174],{"class":122}," gl[",[59,1176,178],{"class":69},[59,1178,1179],{"class":122},"].str.strip().ne(",[59,1181,953],{"class":69},[59,1183,1184],{"class":122},")]\n",[59,1186,1187,1189,1191,1194,1197,1199,1201,1203],{"class":61,"line":132},[59,1188,202],{"class":172},[59,1190,259],{"class":122},[59,1192,1193],{"class":69},"\"could not parse:\"",[59,1195,1196],{"class":122},", failed[[",[59,1198,166],{"class":69},[59,1200,154],{"class":122},[59,1202,178],{"class":69},[59,1204,1205],{"class":122},"]].values.tolist())\n",[59,1207,1208,1210,1212,1215,1218,1221,1223,1226,1229,1232,1235,1237],{"class":61,"line":139},[59,1209,202],{"class":172},[59,1211,259],{"class":122},[59,1213,1214],{"class":118},"f",[59,1216,1217],{"class":69},"\"net total: ",[59,1219,163],{"class":1220},"sSjpA",[59,1222,1070],{"class":122},[59,1224,1225],{"class":69},"'AmountNum'",[59,1227,1228],{"class":122},"].sum()",[59,1230,1231],{"class":118},":,.2f",[59,1233,1234],{"class":1220},"}",[59,1236,929],{"class":69},[59,1238,196],{"class":122},[59,1240,1241,1243,1245,1247,1250,1252,1255,1257,1259,1262,1265,1267,1269,1271,1273,1275],{"class":61,"line":199},[59,1242,202],{"class":172},[59,1244,259],{"class":122},[59,1246,1214],{"class":118},[59,1248,1249],{"class":69},"\"debits ",[59,1251,163],{"class":1220},[59,1253,1254],{"class":122},"gl.loc[gl[",[59,1256,1225],{"class":69},[59,1258,1076],{"class":122},[59,1260,1261],{"class":118},">",[59,1263,1264],{"class":172}," 0",[59,1266,154],{"class":122},[59,1268,1225],{"class":69},[59,1270,1228],{"class":122},[59,1272,1231],{"class":118},[59,1274,1234],{"class":1220},[59,1276,1277],{"class":69},"  \"\n",[59,1279,1280,1283,1286,1288,1290,1292,1294,1297,1299,1301,1303,1305,1307,1309,1311],{"class":61,"line":218},[59,1281,1282],{"class":118},"      f",[59,1284,1285],{"class":69},"\"credits ",[59,1287,163],{"class":1220},[59,1289,1254],{"class":122},[59,1291,1225],{"class":69},[59,1293,1076],{"class":122},[59,1295,1296],{"class":118},"\u003C",[59,1298,1264],{"class":172},[59,1300,154],{"class":122},[59,1302,1225],{"class":69},[59,1304,1228],{"class":122},[59,1306,1231],{"class":118},[59,1308,1234],{"class":1220},[59,1310,929],{"class":69},[59,1312,196],{"class":122},[10,1314,1315,1316,1318,1319,43],{},"The ",[14,1317,97],{}," in document 4720 is reported rather than silently treated as zero. Whether to stop the job, exclude the row or ask someone depends on the report, but it must be a decision — a report that quietly drops a posting will not reconcile. Totals of debits and credits are what an accountant will compare with the ledger's own trial balance; see ",[39,1320,1322],{"href":1321},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Freconcile-excel-totals-with-control-totals\u002F","Reconcile Excel Totals with Control Totals",[291,1324,300,1329,300,1332,300,1335,300,1338,300,1344,300,1355,300,1360,300,1364,300,1368,300,1371,300,1374,300,1380,300,1385,300,1392,300,1399,300,1404,300,1408,300,1410,300,1413,300,1417,300,1420,300,1423,300,1429,300,1433,300,1436,300,1438,300,1441,300,1445,300,1448,300,1451,300,1458,300,1462],{"viewBox":1325,"role":294,"ariaLabelledBy":1326,"xmlns":298,"style":577},"4 0 752 272",[1327,1328],"tm-decide-t","tm-decide-d",[302,1330,1331],{"id":1327},"What happens to each exported value",[306,1333,1334],{"id":1328},"A decision diagram. A value with a trailing minus, brackets, a leading minus or a CR suffix becomes negative; a plain or DR value stays positive; a value that does not parse is reported for review.",[310,1336],{"x":373,"y":313,"width":586,"height":1337,"fill":316},"272",[310,1339],{"x":1340,"y":591,"width":1341,"height":1342,"rx":1343,"fill":597,"stroke":477,"style":478},"240.0","280.0","75.5","12",[1345,1346,1348],"g",{"transform":1347},"translate(368.0,29.0) scale(1.000)",[1349,1350],"path",{"d":1351,"fill":1352,"stroke":1353,"style":1354},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[327,1356,1359],{"x":1357,"y":1342,"style":1358},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","How is the value written?",[61,1361],{"x1":1357,"y1":1362,"x2":1357,"y2":1363,"stroke":477,"style":478},"91.5","111.5",[61,1365],{"x1":1366,"y1":1363,"x2":1367,"y2":1363,"stroke":477,"style":478},"134.7","625.3",[61,1369],{"x1":1366,"y1":1363,"x2":1366,"y2":1370,"stroke":477,"style":478},"157.5",[480,1372],{"points":1373,"fill":483},"134.7,161.5 129.7,152.5 139.7,152.5",[310,1375],{"x":1376,"y":592,"width":1377,"height":1378,"rx":1379,"fill":316,"stroke":324,"style":491},"81.2","106.8","21.0","10",[327,1381,1384],{"x":1366,"y":1382,"style":1383},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","x-, (x), -x, x CR",[310,1386],{"x":590,"y":1387,"width":1388,"height":1389,"rx":1343,"fill":1390,"stroke":1391,"style":478},"163.5","229.3","93.0","#fee8f2","var(--accent,#f43f8f)",[1345,1393,1395],{"transform":1394},"translate(122.7,176.5) scale(1.000)",[1349,1396],{"d":1397,"fill":1352,"stroke":1398,"style":1354},"M5 12.5l4.5 4.5L19 7","var(--accent-ink,#be185d)",[327,1400,1403],{"x":1366,"y":1401,"style":1402},"223.0","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Negative",[327,1405,1407],{"x":1366,"y":1406,"style":631},"242.5","sign flipped",[61,1409],{"x1":1357,"y1":1363,"x2":1357,"y2":1370,"stroke":477,"style":478},[480,1411],{"points":1412,"fill":483},"380.0,161.5 375.0,152.5 385.0,152.5",[310,1414],{"x":1415,"y":592,"width":1416,"height":1378,"rx":1379,"fill":316,"stroke":324,"style":491},"350.8","58.4",[327,1418,1419],{"x":1357,"y":1382,"style":1383},"x, x DR",[310,1421],{"x":1422,"y":1387,"width":1388,"height":1389,"rx":1343,"fill":412,"stroke":413,"style":478},"265.3",[1345,1424,1426],{"transform":1425},"translate(368.0,176.5) scale(1.000)",[1349,1427],{"d":1397,"fill":1352,"stroke":1428,"style":1354},"var(--teal-ink,#0b6157)",[327,1430,1432],{"x":1357,"y":1401,"style":1431},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Positive",[327,1434,1435],{"x":1357,"y":1406,"style":631},"unchanged",[61,1437],{"x1":1367,"y1":1363,"x2":1367,"y2":1370,"stroke":477,"style":478},[480,1439],{"points":1440,"fill":483},"625.3,161.5 620.3,152.5 630.3,152.5",[310,1442],{"x":1443,"y":592,"width":1444,"height":1378,"rx":1379,"fill":316,"stroke":324,"style":491},"591.8","67.2",[327,1446,1447],{"x":1367,"y":1382,"style":1383},"n\u002Fa, text",[310,1449],{"x":1450,"y":1387,"width":1388,"height":1389,"rx":1343,"fill":337,"stroke":338,"style":478},"510.7",[1345,1452,1454],{"transform":1453},"translate(613.3,176.5) scale(1.000)",[1349,1455],{"d":1456,"fill":1352,"stroke":1457,"style":1354},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[327,1459,1461],{"x":1367,"y":1401,"style":1460},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Not a number",[327,1463,1464],{"x":1367,"y":1406,"style":631},"reported for review",[45,1466,1468],{"id":1467},"step-4-european-formats-123456","Step 4 — European formats: 1.234,56-",[10,1470,1471],{},"German, Dutch, French and many other exports use a dot for thousands and a comma for decimals — and SAP in those locales puts the minus at the end too. The same function handles it with the separators swapped:",[50,1473,1475],{"className":109,"code":1474,"language":111,"meta":55,"style":55},"bank = pd.read_excel(\"bank_export.xlsx\", sheet_name=\"Statement\", dtype={\"Betrag\": str})\nbank[\"BetragNum\"] = parse_amount(bank[\"Betrag\"], decimal=\",\", thousands=\".\")\nprint(bank[[\"Posting\", \"Betrag\", \"BetragNum\"]].to_string(index=False))\n",[14,1476,1477,1517,1555],{"__ignoreMap":55},[59,1478,1479,1482,1484,1486,1489,1491,1494,1496,1499,1501,1503,1505,1507,1510,1512,1514],{"class":61,"line":62},[59,1480,1481],{"class":122},"bank ",[59,1483,145],{"class":118},[59,1485,148],{"class":122},[59,1487,1488],{"class":69},"\"bank_export.xlsx\"",[59,1490,154],{"class":122},[59,1492,1493],{"class":157},"sheet_name",[59,1495,145],{"class":118},[59,1497,1498],{"class":69},"\"Statement\"",[59,1500,154],{"class":122},[59,1502,158],{"class":157},[59,1504,145],{"class":118},[59,1506,163],{"class":122},[59,1508,1509],{"class":69},"\"Betrag\"",[59,1511,169],{"class":122},[59,1513,173],{"class":172},[59,1515,1516],{"class":122},"})\n",[59,1518,1519,1522,1525,1527,1529,1532,1534,1537,1540,1542,1544,1546,1549,1551,1553],{"class":61,"line":132},[59,1520,1521],{"class":122},"bank[",[59,1523,1524],{"class":69},"\"BetragNum\"",[59,1526,1076],{"class":122},[59,1528,145],{"class":118},[59,1530,1531],{"class":122}," parse_amount(bank[",[59,1533,1509],{"class":69},[59,1535,1536],{"class":122},"], ",[59,1538,1539],{"class":157},"decimal",[59,1541,145],{"class":118},[59,1543,785],{"class":69},[59,1545,154],{"class":122},[59,1547,1548],{"class":157},"thousands",[59,1550,145],{"class":118},[59,1552,777],{"class":69},[59,1554,196],{"class":122},[59,1556,1557,1559,1562,1565,1567,1569,1571,1573,1575,1577,1579,1581],{"class":61,"line":139},[59,1558,202],{"class":172},[59,1560,1561],{"class":122},"(bank[[",[59,1563,1564],{"class":69},"\"Posting\"",[59,1566,154],{"class":122},[59,1568,1509],{"class":69},[59,1570,154],{"class":122},[59,1572,1524],{"class":69},[59,1574,1106],{"class":122},[59,1576,208],{"class":157},[59,1578,145],{"class":118},[59,1580,193],{"class":172},[59,1582,215],{"class":122},[10,1584,1585,1586,1589,1590,1593,1594,1597],{},"Removing the thousands dots first and only then turning the decimal comma into a point is what keeps ",[14,1587,1588],{},"1.234,56"," from becoming ",[14,1591,1592],{},"1.23456",". If one file mixes both formats — rare, but it happens when exports from two subsidiaries are pasted together — split it by source and parse each part with its own separators; no rule can tell what ",[14,1595,1596],{},"1.234"," means on its own.",[45,1599,1601],{"id":1600},"step-5-a-separate-debitcredit-indicator-column","Step 5 — A separate debit\u002Fcredit indicator column",[10,1603,1604,1605,29,1607,154,1609,29,1612,1615,1616,90,1619,1053,1622,29,1625,1628],{},"Some exports keep the amount unsigned and put the direction in another column: ",[14,1606,28],{},[14,1608,32],{},[14,1610,1611],{},"S",[14,1613,1614],{},"H"," (German ",[549,1617,1618],{},"Soll",[549,1620,1621],{},"Haben",[14,1623,1624],{},"Dr",[14,1626,1627],{},"Cr",". Map the indicator to a sign and multiply:",[50,1630,1632],{"className":109,"code":1631,"language":111,"meta":55,"style":55},"SIGN = {\"D\": 1, \"C\": -1, \"S\": 1, \"H\": -1}\nbank[\"Signed\"] = bank[\"Amount\"] * bank[\"DC\"].str.upper().map(SIGN)\nbank[\"BetragSigned\"] = bank[\"BetragNum\"].abs() * bank[\"Soll\u002FHaben\"].map(SIGN)\nprint(bank[[\"Amount\", \"DC\", \"Signed\", \"Soll\u002FHaben\", \"BetragSigned\"]].to_string(index=False))\n\nunknown = bank.loc[bank[\"DC\"].str.upper().map(SIGN).isna(), \"DC\"].unique()\nprint(\"unknown indicators:\", list(unknown))\n",[14,1633,1634,1685,1717,1749,1783,1787,1811],{"__ignoreMap":55},[59,1635,1636,1639,1641,1644,1647,1649,1651,1653,1656,1658,1660,1662,1664,1667,1669,1671,1673,1676,1678,1680,1682],{"class":61,"line":62},[59,1637,1638],{"class":172},"SIGN",[59,1640,753],{"class":118},[59,1642,1643],{"class":122}," {",[59,1645,1646],{"class":69},"\"D\"",[59,1648,169],{"class":122},[59,1650,332],{"class":172},[59,1652,154],{"class":122},[59,1654,1655],{"class":69},"\"C\"",[59,1657,169],{"class":122},[59,1659,1056],{"class":118},[59,1661,332],{"class":172},[59,1663,154],{"class":122},[59,1665,1666],{"class":69},"\"S\"",[59,1668,169],{"class":122},[59,1670,332],{"class":172},[59,1672,154],{"class":122},[59,1674,1675],{"class":69},"\"H\"",[59,1677,169],{"class":122},[59,1679,1056],{"class":118},[59,1681,332],{"class":172},[59,1683,1684],{"class":122},"}\n",[59,1686,1687,1689,1692,1694,1696,1699,1701,1703,1705,1707,1710,1713,1715],{"class":61,"line":132},[59,1688,1521],{"class":122},[59,1690,1691],{"class":69},"\"Signed\"",[59,1693,1076],{"class":122},[59,1695,145],{"class":118},[59,1697,1698],{"class":122}," bank[",[59,1700,178],{"class":69},[59,1702,1076],{"class":122},[59,1704,935],{"class":118},[59,1706,1698],{"class":122},[59,1708,1709],{"class":69},"\"DC\"",[59,1711,1712],{"class":122},"].str.upper().map(",[59,1714,1638],{"class":172},[59,1716,196],{"class":122},[59,1718,1719,1721,1724,1726,1728,1730,1732,1735,1737,1739,1742,1745,1747],{"class":61,"line":139},[59,1720,1521],{"class":122},[59,1722,1723],{"class":69},"\"BetragSigned\"",[59,1725,1076],{"class":122},[59,1727,145],{"class":118},[59,1729,1698],{"class":122},[59,1731,1524],{"class":69},[59,1733,1734],{"class":122},"].abs() ",[59,1736,935],{"class":118},[59,1738,1698],{"class":122},[59,1740,1741],{"class":69},"\"Soll\u002FHaben\"",[59,1743,1744],{"class":122},"].map(",[59,1746,1638],{"class":172},[59,1748,196],{"class":122},[59,1750,1751,1753,1755,1757,1759,1761,1763,1765,1767,1769,1771,1773,1775,1777,1779,1781],{"class":61,"line":199},[59,1752,202],{"class":172},[59,1754,1561],{"class":122},[59,1756,178],{"class":69},[59,1758,154],{"class":122},[59,1760,1709],{"class":69},[59,1762,154],{"class":122},[59,1764,1691],{"class":69},[59,1766,154],{"class":122},[59,1768,1741],{"class":69},[59,1770,154],{"class":122},[59,1772,1723],{"class":69},[59,1774,1106],{"class":122},[59,1776,208],{"class":157},[59,1778,145],{"class":118},[59,1780,193],{"class":172},[59,1782,215],{"class":122},[59,1784,1785],{"class":61,"line":218},[59,1786,136],{"emptyLinePlaceholder":135},[59,1788,1789,1792,1794,1797,1799,1801,1803,1806,1808],{"class":61,"line":227},[59,1790,1791],{"class":122},"unknown ",[59,1793,145],{"class":118},[59,1795,1796],{"class":122}," bank.loc[bank[",[59,1798,1709],{"class":69},[59,1800,1712],{"class":122},[59,1802,1638],{"class":172},[59,1804,1805],{"class":122},").isna(), ",[59,1807,1709],{"class":69},[59,1809,1810],{"class":122},"].unique()\n",[59,1812,1813,1815,1817,1820,1822,1825],{"class":61,"line":238},[59,1814,202],{"class":172},[59,1816,259],{"class":122},[59,1818,1819],{"class":69},"\"unknown indicators:\"",[59,1821,154],{"class":122},[59,1823,1824],{"class":172},"list",[59,1826,1827],{"class":122},"(unknown))\n",[10,1829,1830,1833,1834,1836,1837,1840],{},[14,1831,1832],{},"map"," returns ",[14,1835,1138],{}," for an indicator that is not in the dictionary, so an unexpected code — a ",[14,1838,1839],{},"X"," for \"reversed\", say — shows up as a missing amount instead of being treated as positive. Checking for unknown codes explicitly, as the last line does, turns that into a clear message.",[45,1842,1844],{"id":1843},"step-6-write-the-result-back-to-excel","Step 6 — Write the result back to Excel",[10,1846,1847],{},"Keep the original text column next to the parsed number while the rules are new, so anyone can check a value against the export, and give the number an accounting format:",[50,1849,1851],{"className":109,"code":1850,"language":111,"meta":55,"style":55},"with pd.ExcelWriter(\"gl_clean.xlsx\", engine=\"xlsxwriter\") as xw:\n    gl.to_excel(xw, index=False, sheet_name=\"GL\")\n    fmt = xw.book.add_format({\"num_format\": \"#,##0.00;[Red](#,##0.00)\"})\n    xw.sheets[\"GL\"].set_column(\"C:C\", 14)\n    xw.sheets[\"GL\"].set_column(\"D:D\", 14, fmt)\nprint(pd.read_excel(\"gl_clean.xlsx\")[\"AmountNum\"].dtype)\n",[14,1852,1853,1881,1903,1923,1943,1961],{"__ignoreMap":55},[59,1854,1855,1858,1861,1864,1866,1869,1871,1874,1876,1878],{"class":61,"line":62},[59,1856,1857],{"class":118},"with",[59,1859,1860],{"class":122}," pd.ExcelWriter(",[59,1862,1863],{"class":69},"\"gl_clean.xlsx\"",[59,1865,154],{"class":122},[59,1867,1868],{"class":157},"engine",[59,1870,145],{"class":118},[59,1872,1873],{"class":69},"\"xlsxwriter\"",[59,1875,820],{"class":122},[59,1877,126],{"class":118},[59,1879,1880],{"class":122}," xw:\n",[59,1882,1883,1886,1888,1890,1892,1894,1896,1898,1901],{"class":61,"line":132},[59,1884,1885],{"class":122},"    gl.to_excel(xw, ",[59,1887,208],{"class":157},[59,1889,145],{"class":118},[59,1891,193],{"class":172},[59,1893,154],{"class":122},[59,1895,1493],{"class":157},[59,1897,145],{"class":118},[59,1899,1900],{"class":69},"\"GL\"",[59,1902,196],{"class":122},[59,1904,1905,1908,1910,1913,1916,1918,1921],{"class":61,"line":139},[59,1906,1907],{"class":122},"    fmt ",[59,1909,145],{"class":118},[59,1911,1912],{"class":122}," xw.book.add_format({",[59,1914,1915],{"class":69},"\"num_format\"",[59,1917,169],{"class":122},[59,1919,1920],{"class":69},"\"#,##0.00;[Red](#,##0.00)\"",[59,1922,1516],{"class":122},[59,1924,1925,1928,1930,1933,1936,1938,1941],{"class":61,"line":199},[59,1926,1927],{"class":122},"    xw.sheets[",[59,1929,1900],{"class":69},[59,1931,1932],{"class":122},"].set_column(",[59,1934,1935],{"class":69},"\"C:C\"",[59,1937,154],{"class":122},[59,1939,1940],{"class":172},"14",[59,1942,196],{"class":122},[59,1944,1945,1947,1949,1951,1954,1956,1958],{"class":61,"line":218},[59,1946,1927],{"class":122},[59,1948,1900],{"class":69},[59,1950,1932],{"class":122},[59,1952,1953],{"class":69},"\"D:D\"",[59,1955,154],{"class":122},[59,1957,1940],{"class":172},[59,1959,1960],{"class":122},", fmt)\n",[59,1962,1963,1965,1968,1970,1973,1975],{"class":61,"line":227},[59,1964,202],{"class":172},[59,1966,1967],{"class":122},"(pd.read_excel(",[59,1969,1863],{"class":69},[59,1971,1972],{"class":122},")[",[59,1974,1073],{"class":69},[59,1976,1977],{"class":122},"].dtype)\n",[10,1979,1980,1981,1985],{},"The number format shows negatives in red brackets, the way the export printed some of them — but now they are real numbers that sum, sort and pivot. ",[39,1982,1984],{"href":1983},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fshow-negative-numbers-in-red-with-parentheses\u002F","Show Negative Numbers in Red with Parentheses"," explains the format code.",[45,1987,1989],{"id":1988},"performance-notes","Performance notes",[10,1991,1992,1993,1995,1996,1999,2000,29,2002,2005,2006,2008],{},"Every operation in ",[14,1994,497],{}," is a vectorised string method, so a column of a million amounts parses in a second or two. Avoid the tempting alternative of ",[14,1997,1998],{},"df[\"Amount\"].apply(some_function)"," with a Python ",[14,2001,903],{},[14,2003,2004],{},"elif"," chain per value: it is easier to write and many times slower, and it tends to grow special cases that nobody tests. If the same export is parsed every day, keep ",[14,2007,497],{}," in a small shared module with a unit test per convention.",[45,2010,2012],{"id":2011},"common-pitfalls","Common pitfalls",[506,2014,2015,2021,2031,2037,2043],{},[509,2016,2017,2020],{},[512,2018,2019],{},"Stripping the minus with the thousands separator."," Detect the sign before removing any characters.",[509,2022,2023,2026,2027,2030],{},[512,2024,2025],{},"Brackets without a matching pair."," ",[14,2028,2029],{},"(310.50"," from a truncated field is not negative by convention; require both brackets.",[509,2032,2033,2036],{},[512,2034,2035],{},"CR meaning different things."," Ledger and bank views disagree about the sign of a credit; make it a setting.",[509,2038,2039,2042],{},[512,2040,2041],{},"European separators in the wrong order."," Remove thousands dots before converting the decimal comma.",[509,2044,2045,2026,2048,2050,2051,43],{},[512,2046,2047],{},"Coercing without checking.",[14,2049,1134],{}," hides problems unless you list the values it turned into ",[14,2052,1138],{},[45,2054,2056],{"id":2055},"conclusion","Conclusion",[10,2058,2059,2060,29,2062,2064,2065,29,2067,1124,2069,29,2071,2073],{},"Read the amount column as text, decide what each convention means for your report, and parse it in one vectorised pass: detect trailing and leading minus signs, brackets and ",[14,2061,89],{},[14,2063,93],{}," suffixes, strip them, remove thousands separators before normalising the decimal separator, and apply the sign. Map indicator columns such as ",[14,2066,28],{},[14,2068,32],{},[14,2070,1611],{},[14,2072,1614],{}," to ±1, list every value that did not parse, and reconcile the totals before the numbers go into a report.",[45,2075,2077],{"id":2076},"frequently-asked-questions","Frequently asked questions",[10,2079,2080,2083],{},[512,2081,2082],{},"How do I convert numbers with a trailing minus sign in pandas?","\nDetect values ending in \"-\" with str.endswith(\"-\"), strip the minus and the thousands separators, convert with pd.to_numeric, and negate the values that had the trailing sign. The parse_amount function on this page does that together with brackets and CR\u002FDR suffixes.",[10,2085,2086,2089],{},[512,2087,2088],{},"How do I handle CR and DR in amounts?","\nRemove the suffix with a regular expression and use it to set the sign. Whether CR is negative depends on the point of view: in your ledger a customer credit is usually negative, while on a bank statement CR is money in.",[10,2091,2092,2095],{},[512,2093,2094],{},"How do I convert 1.234,56 to a number in pandas?","\nRemove the dot thousands separators first, then replace the decimal comma with a point, then convert with pd.to_numeric. Doing it in the other order turns 1.234,56 into 1.23456.",[10,2097,2098,2101],{},[512,2099,2100],{},"What if the sign is in a separate column?","\nMap the indicator values to +1 and -1 with a dictionary — for example D and S to 1, C and H to -1 — and multiply the amount by the result. Check for indicator values missing from the dictionary.",[45,2103,2105],{"id":2104},"related","Related",[506,2107,2108,2114,2121,2128],{},[509,2109,2110,2111,2113],{},"Up one level: ",[39,2112,42],{"href":41}," — the everyday clean-up toolkit.",[509,2115,2116,2120],{},[39,2117,2119],{"href":2118},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-currency-strings-to-numbers-with-pandas\u002F","Convert Currency Strings to Numbers with pandas"," — currency symbols and codes.",[509,2122,2123,2127],{},[39,2124,2126],{"href":2125},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-excel-text-columns-to-numbers-with-pandas\u002F","Convert Excel Text Columns to Numbers with pandas"," — the general text-to-number problem.",[509,2129,2130,2134],{},[39,2131,2133],{"href":2132},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-csv-exports-with-semicolons-and-decimal-commas\u002F","Read CSV Exports with Semicolons and Decimal Commas"," — the same formats arriving as CSV.",[2136,2137,2138],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":55,"searchDepth":132,"depth":132,"links":2140},[2141,2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152],{"id":47,"depth":132,"text":48},{"id":500,"depth":132,"text":501},{"id":720,"depth":132,"text":721},{"id":1142,"depth":132,"text":1143},{"id":1467,"depth":132,"text":1468},{"id":1600,"depth":132,"text":1601},{"id":1843,"depth":132,"text":1844},{"id":1988,"depth":132,"text":1989},{"id":2011,"depth":132,"text":2012},{"id":2055,"depth":132,"text":2056},{"id":2076,"depth":132,"text":2077},{"id":2104,"depth":132,"text":2105},"2026-09-29","Turn amounts exported by SAP, Oracle and bank systems into signed numbers: trailing minus signs like 1,234.56-, accounting brackets, CR and DR suffixes, a separate debit\u002Fcredit indicator column, European 1.234,56 formats, and a check that nothing was left as text.","md",[2157,2159,2161,2163],{"q":2082,"a":2158},"Detect values ending in \"-\" with str.endswith(\"-\"), strip the minus and the thousands separators, convert with pd.to_numeric, and negate the values that had the trailing sign. The parse_amount function on this page does that together with brackets and CR\u002FDR suffixes.",{"q":2088,"a":2160},"Remove the suffix with a regular expression and use it to set the sign. Whether CR is negative depends on the point of view: in your ledger a customer credit is usually negative, while on a bank statement CR is money in.",{"q":2094,"a":2162},"Remove the dot thousands separators first, then replace the decimal comma with a point, then convert with pd.to_numeric. Doing it in the other order turns 1.234,56 into 1.23456.",{"q":2100,"a":2164},"Map the indicator values to +1 and -1 with a dictionary — for example D and S to 1, C and H to -1 — and multiply the amount by the result. Check for indicator values missing from the dictionary.","convert trailing minus and cr dr amounts with pandas",null,{"breadcrumb":2168},[2169,2171,2174],{"name":2170,"item":29},"Home",{"name":2172,"item":2173},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2175,"item":41},"Cleaning Excel Data With Pandas: A Production Workflow","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-trailing-minus-and-cr-dr-amounts-with-pandas",{"title":2178,"description":2179},"Convert Trailing Minus and CR\u002FDR Amounts to Numbers in pandas","Parse ERP and bank export amounts in pandas: trailing minus \"500.00-\", brackets \"(500.00)\", \"500.00 CR\", debit\u002Fcredit indicator columns and 1.234,56 European formats, vectorised with str.extract, with a check for anything left unparsed.","convert-trailing-minus-and-cr-dr-amounts-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-trailing-minus-and-cr-dr-amounts-with-pandas\u002Findex",[2183,2184],"data-cleaning","pandas","how-to","lrEMMdeCQ988KknYSJROFjAcStcARLHX1HVPVEB2Nok",[2188,2192],{"title":2189,"path":2190,"stem":2191,"children":-1},"Convert Percentage Strings Like \"12.5%\" to Numbers with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-percentage-strings-to-numbers-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-percentage-strings-to-numbers-with-pandas\u002Findex",{"title":2193,"path":2194,"stem":2195,"children":-1},"Detect Outliers in Excel Data with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fdetect-outliers-in-excel-data-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fdetect-outliers-in-excel-data-with-pandas\u002Findex",[2197,2199],{"path":2198,"title":2175},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas",{"path":2200,"title":2172},"\u002Fadvanced-data-transformation-and-cleaning",[],[2203,2206,2209,2212,2215,2218],{"path":2204,"title":2205,"kind":2185},"\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":2207,"title":2208,"kind":2185},"\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":2210,"title":2211,"kind":2185},"\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":2213,"title":2214,"kind":2185},"\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":2216,"title":2217,"kind":2185},"\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":2219,"title":2126,"kind":2185},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-excel-text-columns-to-numbers-with-pandas",1790972080622]