[{"data":1,"prerenderedAt":2405},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmatch-rows-by-nearest-date-with-merge-asof":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmatch-rows-by-nearest-date-with-merge-asof":2371,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmatch-rows-by-nearest-date-with-merge-asof":2380,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmatch-rows-by-nearest-date-with-merge-asof":2385,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmatch-rows-by-nearest-date-with-merge-asof":2386},{"id":4,"title":5,"body":6,"dateModified":2335,"datePublished":2335,"description":2336,"extension":2337,"faq":2338,"keyword":2347,"kind":2348,"meta":2349,"navigation":281,"path":2359,"seo":2360,"slug":2363,"stem":2364,"topics":2365,"type":2369,"__hash__":2370},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmatch-rows-by-nearest-date-with-merge-asof\u002Findex.md","Match Rows by Nearest Date with pandas merge_asof",{"type":7,"value":8,"toc":2321},"minimark",[9,28,218,223,250,253,593,597,603,771,792,796,807,956,959,1236,1240,1247,1366,1374,1378,1383,1491,1505,1643,1647,1654,1722,1725,1729,1734,1901,1915,1919,1922,2191,2202,2206,2241,2245,2256,2260,2266,2272,2278,2284,2288,2317],[10,11,12,13,17,18,21,22,27],"p",{},"Transactions happen every day, but exchange rates are published on business days, price lists change on arbitrary dates, and an employee's cost centre changes when they move team. Converting a transaction or costing an order needs the value that was in force at that moment: the latest rate on or before the transaction date. An exact ",[14,15,16],"code",{},"merge"," on the date fails for every weekend transaction, and a loop that searches the rate table for each row is slow. ",[14,19,20],{},"pd.merge_asof"," does exactly this join. This guide, part of ",[23,24,26],"a",{"href":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002F","Merging and Joining Excel DataFrames",", uses it to convert transactions with daily FX rates from a second workbook.",[29,30,38,39,38,43,38,47,38,55,38,63,38,73,38,84,38,91,38,96,38,100,38,104,38,108,38,113,38,118,38,122,38,124,38,126,38,131,38,138,38,143,38,146,38,148,38,150,38,153,38,158,38,162,38,165,38,167,38,169,38,172,38,177,38,181,38,184,38,186,38,188,38,193,38,200,38,206,38,210,38,214,38,216],"svg",{"viewBox":31,"role":32,"ariaLabelledBy":33,"xmlns":36,"style":37},"4 0 752 313","img",[34,35],"ma-timeline-t","ma-timeline-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[40,41,42],"title",{"id":34},"Each transaction takes the latest rate on or before its date",[44,45,46],"desc",{"id":35},"USD rates are published on 2, 3 and 5 March. A transaction on 4 March takes the 3 March rate of 0.93, one on 6 March takes the 5 March rate of 0.91, and one on 9 March also takes the 5 March rate unless a tolerance of three days rules it out.",[48,49],"rect",{"x":50,"y":51,"width":52,"height":53,"fill":54},"4","0","752","313","#ffffff",[56,57],"line",{"x1":58,"y1":59,"x2":60,"y2":59,"stroke":61,"style":62},"64.0","156.5","696.0","var(--muted,#5b6780)","stroke-width:2.5px",[48,64],{"x":65,"y":66,"width":67,"height":68,"rx":69,"fill":70,"stroke":71,"style":72},"20.0","16.0","112.0","108.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[74,75,77],"g",{"transform":76},"translate(64.0,37.5) scale(1.000)",[78,79],"path",{"d":80,"fill":81,"stroke":82,"style":83},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[85,86,90],"text",{"x":87,"y":88,"style":89},"76.0","83.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","2 Mar",[85,92,95],{"x":87,"y":93,"style":94},"101.5","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","rate 0.92",[56,97],{"x1":87,"y1":98,"x2":87,"y2":59,"stroke":61,"style":99},"124.5","stroke-width:1.5px",[101,102],"circle",{"cx":87,"cy":59,"r":103,"fill":61,"stroke":54,"style":72},"7.0",[48,105],{"x":106,"y":107,"width":67,"height":68,"rx":69,"fill":70,"stroke":71,"style":72},"141.6","188.5",[74,109,111],{"transform":110},"translate(185.6,210.0) scale(1.000)",[78,112],{"d":80,"fill":81,"stroke":82,"style":83},[85,114,117],{"x":115,"y":116,"style":89},"197.6","255.5","3 Mar",[85,119,121],{"x":115,"y":120,"style":94},"274.0","rate 0.93",[56,123],{"x1":115,"y1":59,"x2":115,"y2":107,"stroke":61,"style":99},[101,125],{"cx":115,"cy":59,"r":103,"fill":61,"stroke":54,"style":72},[48,127],{"x":128,"y":66,"width":67,"height":68,"rx":69,"fill":129,"stroke":130,"style":72},"263.2","#f0f4ff","var(--brand,#5b5cf0)",[74,132,134],{"transform":133},"translate(307.2,37.5) scale(1.000)",[78,135],{"d":136,"fill":81,"stroke":137,"style":83},"M13 2L4 14h7l-1 8 9-12h-7z","var(--brand-strong,#4338ca)",[85,139,142],{"x":140,"y":88,"style":141},"319.2","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","4 Mar",[85,144,145],{"x":140,"y":93,"style":94},"tx 1 → 0.93",[56,147],{"x1":140,"y1":98,"x2":140,"y2":59,"stroke":61,"style":99},[101,149],{"cx":140,"cy":59,"r":103,"fill":130,"stroke":54,"style":72},[48,151],{"x":152,"y":107,"width":67,"height":68,"rx":69,"fill":70,"stroke":71,"style":72},"384.8",[74,154,156],{"transform":155},"translate(428.8,210.0) scale(1.000)",[78,157],{"d":80,"fill":81,"stroke":82,"style":83},[85,159,161],{"x":160,"y":116,"style":89},"440.8","5 Mar",[85,163,164],{"x":160,"y":120,"style":94},"rate 0.91",[56,166],{"x1":160,"y1":59,"x2":160,"y2":107,"stroke":61,"style":99},[101,168],{"cx":160,"cy":59,"r":103,"fill":61,"stroke":54,"style":72},[48,170],{"x":171,"y":66,"width":67,"height":68,"rx":69,"fill":129,"stroke":130,"style":72},"506.4",[74,173,175],{"transform":174},"translate(550.4,37.5) scale(1.000)",[78,176],{"d":136,"fill":81,"stroke":137,"style":83},[85,178,180],{"x":179,"y":88,"style":141},"562.4","6 Mar",[85,182,183],{"x":179,"y":93,"style":94},"tx 3 → 0.91",[56,185],{"x1":179,"y1":98,"x2":179,"y2":59,"stroke":61,"style":99},[101,187],{"cx":179,"cy":59,"r":103,"fill":130,"stroke":54,"style":72},[48,189],{"x":190,"y":107,"width":67,"height":68,"rx":69,"fill":191,"stroke":192,"style":72},"628.0","#fdefd8","var(--gold,#b4740a)",[74,194,196],{"transform":195},"translate(672.0,201.5) scale(1.000)",[78,197],{"d":198,"fill":81,"stroke":199,"style":83},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[85,201,205],{"x":202,"y":203,"style":204},"684.0","247.0","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","9 Mar",[85,207,209],{"x":202,"y":208,"style":94},"265.5","tx 4 → 0.91,",[85,211,213],{"x":202,"y":212,"style":94},"282.5","stale?",[56,215],{"x1":202,"y1":59,"x2":202,"y2":107,"stroke":61,"style":99},[101,217],{"cx":202,"cy":59,"r":103,"fill":192,"stroke":54,"style":72},[219,220,222],"h2",{"id":221},"prerequisites","Prerequisites",[224,225,230],"pre",{"className":226,"code":227,"language":228,"meta":229,"style":229},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,231,232],{"__ignoreMap":229},[233,234,236,240,244,247],"span",{"class":56,"line":235},1,[233,237,239],{"class":238},"sMTad","pip",[233,241,243],{"class":242},"srMev"," install",[233,245,246],{"class":242}," pandas",[233,248,249],{"class":242}," openpyxl\n",[10,251,252],{},"Transactions in two currencies and a rate table, each in its own workbook:",[224,254,258],{"className":255,"code":256,"language":257,"meta":229,"style":229},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\npd.DataFrame({\n    \"tx\": [1, 2, 3, 4, 5],\n    \"date\": pd.to_datetime([\"2026-03-04\", \"2026-03-02\", \"2026-03-06\", \"2026-03-09\", \"2026-02-27\"]),\n    \"currency\": [\"USD\", \"GBP\", \"USD\", \"USD\", \"GBP\"],\n    \"amount\": [100, 50, 80, 20, 10],\n}).to_excel(\"transactions.xlsx\", index=False)\npd.DataFrame({\n    \"date\": pd.to_datetime([\"2026-03-02\", \"2026-03-03\", \"2026-03-05\", \"2026-03-02\", \"2026-03-05\"]),\n    \"currency\": [\"USD\", \"USD\", \"USD\", \"GBP\", \"GBP\"],\n    \"rate\": [0.92, 0.93, 0.91, 1.17, 1.18],\n}).to_excel(\"fx_rates.xlsx\", index=False)\n\ntx = pd.read_excel(\"transactions.xlsx\")\nfx = pd.read_excel(\"fx_rates.xlsx\")\n","python",[14,259,260,276,283,289,325,360,390,423,447,452,481,508,541,559,564,579],{"__ignoreMap":229},[233,261,262,266,270,273],{"class":56,"line":235},[233,263,265],{"class":264},"s-kum","import",[233,267,269],{"class":268},"skGVy"," pandas ",[233,271,272],{"class":264},"as",[233,274,275],{"class":268}," pd\n",[233,277,279],{"class":56,"line":278},2,[233,280,282],{"emptyLinePlaceholder":281},true,"\n",[233,284,286],{"class":56,"line":285},3,[233,287,288],{"class":268},"pd.DataFrame({\n",[233,290,292,295,298,302,305,308,310,313,315,317,319,322],{"class":56,"line":291},4,[233,293,294],{"class":242},"    \"tx\"",[233,296,297],{"class":268},": [",[233,299,301],{"class":300},"sP0c6","1",[233,303,304],{"class":268},", ",[233,306,307],{"class":300},"2",[233,309,304],{"class":268},[233,311,312],{"class":300},"3",[233,314,304],{"class":268},[233,316,50],{"class":300},[233,318,304],{"class":268},[233,320,321],{"class":300},"5",[233,323,324],{"class":268},"],\n",[233,326,328,331,334,337,339,342,344,347,349,352,354,357],{"class":56,"line":327},5,[233,329,330],{"class":242},"    \"date\"",[233,332,333],{"class":268},": pd.to_datetime([",[233,335,336],{"class":242},"\"2026-03-04\"",[233,338,304],{"class":268},[233,340,341],{"class":242},"\"2026-03-02\"",[233,343,304],{"class":268},[233,345,346],{"class":242},"\"2026-03-06\"",[233,348,304],{"class":268},[233,350,351],{"class":242},"\"2026-03-09\"",[233,353,304],{"class":268},[233,355,356],{"class":242},"\"2026-02-27\"",[233,358,359],{"class":268},"]),\n",[233,361,363,366,368,371,373,376,378,380,382,384,386,388],{"class":56,"line":362},6,[233,364,365],{"class":242},"    \"currency\"",[233,367,297],{"class":268},[233,369,370],{"class":242},"\"USD\"",[233,372,304],{"class":268},[233,374,375],{"class":242},"\"GBP\"",[233,377,304],{"class":268},[233,379,370],{"class":242},[233,381,304],{"class":268},[233,383,370],{"class":242},[233,385,304],{"class":268},[233,387,375],{"class":242},[233,389,324],{"class":268},[233,391,393,396,398,401,403,406,408,411,413,416,418,421],{"class":56,"line":392},7,[233,394,395],{"class":242},"    \"amount\"",[233,397,297],{"class":268},[233,399,400],{"class":300},"100",[233,402,304],{"class":268},[233,404,405],{"class":300},"50",[233,407,304],{"class":268},[233,409,410],{"class":300},"80",[233,412,304],{"class":268},[233,414,415],{"class":300},"20",[233,417,304],{"class":268},[233,419,420],{"class":300},"10",[233,422,324],{"class":268},[233,424,426,429,432,434,438,441,444],{"class":56,"line":425},8,[233,427,428],{"class":268},"}).to_excel(",[233,430,431],{"class":242},"\"transactions.xlsx\"",[233,433,304],{"class":268},[233,435,437],{"class":436},"sa561","index",[233,439,440],{"class":264},"=",[233,442,443],{"class":300},"False",[233,445,446],{"class":268},")\n",[233,448,450],{"class":56,"line":449},9,[233,451,288],{"class":268},[233,453,455,457,459,461,463,466,468,471,473,475,477,479],{"class":56,"line":454},10,[233,456,330],{"class":242},[233,458,333],{"class":268},[233,460,341],{"class":242},[233,462,304],{"class":268},[233,464,465],{"class":242},"\"2026-03-03\"",[233,467,304],{"class":268},[233,469,470],{"class":242},"\"2026-03-05\"",[233,472,304],{"class":268},[233,474,341],{"class":242},[233,476,304],{"class":268},[233,478,470],{"class":242},[233,480,359],{"class":268},[233,482,484,486,488,490,492,494,496,498,500,502,504,506],{"class":56,"line":483},11,[233,485,365],{"class":242},[233,487,297],{"class":268},[233,489,370],{"class":242},[233,491,304],{"class":268},[233,493,370],{"class":242},[233,495,304],{"class":268},[233,497,370],{"class":242},[233,499,304],{"class":268},[233,501,375],{"class":242},[233,503,304],{"class":268},[233,505,375],{"class":242},[233,507,324],{"class":268},[233,509,511,514,516,519,521,524,526,529,531,534,536,539],{"class":56,"line":510},12,[233,512,513],{"class":242},"    \"rate\"",[233,515,297],{"class":268},[233,517,518],{"class":300},"0.92",[233,520,304],{"class":268},[233,522,523],{"class":300},"0.93",[233,525,304],{"class":268},[233,527,528],{"class":300},"0.91",[233,530,304],{"class":268},[233,532,533],{"class":300},"1.17",[233,535,304],{"class":268},[233,537,538],{"class":300},"1.18",[233,540,324],{"class":268},[233,542,544,546,549,551,553,555,557],{"class":56,"line":543},13,[233,545,428],{"class":268},[233,547,548],{"class":242},"\"fx_rates.xlsx\"",[233,550,304],{"class":268},[233,552,437],{"class":436},[233,554,440],{"class":264},[233,556,443],{"class":300},[233,558,446],{"class":268},[233,560,562],{"class":56,"line":561},14,[233,563,282],{"emptyLinePlaceholder":281},[233,565,567,570,572,575,577],{"class":56,"line":566},15,[233,568,569],{"class":268},"tx ",[233,571,440],{"class":264},[233,573,574],{"class":268}," pd.read_excel(",[233,576,431],{"class":242},[233,578,446],{"class":268},[233,580,582,585,587,589,591],{"class":56,"line":581},16,[233,583,584],{"class":268},"fx ",[233,586,440],{"class":264},[233,588,574],{"class":268},[233,590,548],{"class":242},[233,592,446],{"class":268},[219,594,596],{"id":595},"step-1-sort-then-merge","Step 1 — Sort, then merge",[10,598,599,602],{},[14,600,601],{},"merge_asof"," needs both tables sorted by the key. The transactions come in the order they were entered, so the first attempt fails:",[224,604,606],{"className":255,"code":605,"language":257,"meta":229,"style":229},"try:\n    pd.merge_asof(tx, fx, on=\"date\", by=\"currency\")\nexcept ValueError as exc:\n    print(exc)                                   # left keys must be sorted\n\nmatched = pd.merge_asof(tx.sort_values(\"date\"), fx.sort_values(\"date\"),\n                        on=\"date\", by=\"currency\", direction=\"backward\")\nmatched = matched.sort_values(\"tx\").reset_index(drop=True)\nprint(matched[[\"tx\", \"date\", \"currency\", \"rate\"]])\n",[14,607,608,616,641,655,667,671,691,720,745],{"__ignoreMap":229},[233,609,610,613],{"class":56,"line":235},[233,611,612],{"class":264},"try",[233,614,615],{"class":268},":\n",[233,617,618,621,624,626,629,631,634,636,639],{"class":56,"line":278},[233,619,620],{"class":268},"    pd.merge_asof(tx, fx, ",[233,622,623],{"class":436},"on",[233,625,440],{"class":264},[233,627,628],{"class":242},"\"date\"",[233,630,304],{"class":268},[233,632,633],{"class":436},"by",[233,635,440],{"class":264},[233,637,638],{"class":242},"\"currency\"",[233,640,446],{"class":268},[233,642,643,646,649,652],{"class":56,"line":285},[233,644,645],{"class":264},"except",[233,647,648],{"class":300}," ValueError",[233,650,651],{"class":264}," as",[233,653,654],{"class":268}," exc:\n",[233,656,657,660,663],{"class":56,"line":291},[233,658,659],{"class":300},"    print",[233,661,662],{"class":268},"(exc)                                   ",[233,664,666],{"class":665},"s-wDw","# left keys must be sorted\n",[233,668,669],{"class":56,"line":327},[233,670,282],{"emptyLinePlaceholder":281},[233,672,673,676,678,681,683,686,688],{"class":56,"line":362},[233,674,675],{"class":268},"matched ",[233,677,440],{"class":264},[233,679,680],{"class":268}," pd.merge_asof(tx.sort_values(",[233,682,628],{"class":242},[233,684,685],{"class":268},"), fx.sort_values(",[233,687,628],{"class":242},[233,689,690],{"class":268},"),\n",[233,692,693,696,698,700,702,704,706,708,710,713,715,718],{"class":56,"line":392},[233,694,695],{"class":436},"                        on",[233,697,440],{"class":264},[233,699,628],{"class":242},[233,701,304],{"class":268},[233,703,633],{"class":436},[233,705,440],{"class":264},[233,707,638],{"class":242},[233,709,304],{"class":268},[233,711,712],{"class":436},"direction",[233,714,440],{"class":264},[233,716,717],{"class":242},"\"backward\"",[233,719,446],{"class":268},[233,721,722,724,726,729,732,735,738,740,743],{"class":56,"line":425},[233,723,675],{"class":268},[233,725,440],{"class":264},[233,727,728],{"class":268}," matched.sort_values(",[233,730,731],{"class":242},"\"tx\"",[233,733,734],{"class":268},").reset_index(",[233,736,737],{"class":436},"drop",[233,739,440],{"class":264},[233,741,742],{"class":300},"True",[233,744,446],{"class":268},[233,746,747,750,753,755,757,759,761,763,765,768],{"class":56,"line":449},[233,748,749],{"class":300},"print",[233,751,752],{"class":268},"(matched[[",[233,754,731],{"class":242},[233,756,304],{"class":268},[233,758,628],{"class":242},[233,760,304],{"class":268},[233,762,638],{"class":242},[233,764,304],{"class":268},[233,766,767],{"class":242},"\"rate\"",[233,769,770],{"class":268},"]])\n",[10,772,773,776,777,780,781,784,785,787,788,791],{},[14,774,775],{},"by=\"currency\""," restricts each match to rates of the same currency, so a USD transaction never picks up a GBP rate even when that is the nearest row by date. ",[14,778,779],{},"direction=\"backward\""," — the default — takes the latest rate on or before the transaction date: transaction 1 on 4 March gets the 3 March USD rate, 0.93. Sorting back by ",[14,782,783],{},"tx"," restores the original order, which ",[14,786,601],{}," does not preserve. If the transactions have no ID column, keep the original index with ",[14,789,790],{},"reset_index()"," before sorting and sort back on it afterwards.",[219,793,795],{"id":794},"step-2-keep-the-date-of-the-matched-rate","Step 2 — Keep the date of the matched rate",[10,797,798,799,802,803,806],{},"The merged frame shows the rate but not which day it came from. For auditing — \"why was this converted at 0.91?\" — keep the rate's own date by giving it a different name and using ",[14,800,801],{},"left_on"," and ",[14,804,805],{},"right_on",":",[224,808,810],{"className":255,"code":809,"language":257,"meta":229,"style":229},"rates = fx.rename(columns={\"date\": \"rate_date\"}).sort_values(\"rate_date\")\nmatched = pd.merge_asof(tx.sort_values(\"date\"), rates, left_on=\"date\", right_on=\"rate_date\",\n                        by=\"currency\").sort_values(\"tx\").reset_index(drop=True)\nmatched[\"rate_age_days\"] = (matched[\"date\"] - matched[\"rate_date\"]).dt.days\nprint(matched[[\"tx\", \"date\", \"rate_date\", \"rate\", \"rate_age_days\"]])\n",[14,811,812,845,875,899,930],{"__ignoreMap":229},[233,813,814,817,819,822,825,827,830,832,835,838,841,843],{"class":56,"line":235},[233,815,816],{"class":268},"rates ",[233,818,440],{"class":264},[233,820,821],{"class":268}," fx.rename(",[233,823,824],{"class":436},"columns",[233,826,440],{"class":264},[233,828,829],{"class":268},"{",[233,831,628],{"class":242},[233,833,834],{"class":268},": ",[233,836,837],{"class":242},"\"rate_date\"",[233,839,840],{"class":268},"}).sort_values(",[233,842,837],{"class":242},[233,844,446],{"class":268},[233,846,847,849,851,853,855,858,860,862,864,866,868,870,872],{"class":56,"line":278},[233,848,675],{"class":268},[233,850,440],{"class":264},[233,852,680],{"class":268},[233,854,628],{"class":242},[233,856,857],{"class":268},"), rates, ",[233,859,801],{"class":436},[233,861,440],{"class":264},[233,863,628],{"class":242},[233,865,304],{"class":268},[233,867,805],{"class":436},[233,869,440],{"class":264},[233,871,837],{"class":242},[233,873,874],{"class":268},",\n",[233,876,877,880,882,884,887,889,891,893,895,897],{"class":56,"line":285},[233,878,879],{"class":436},"                        by",[233,881,440],{"class":264},[233,883,638],{"class":242},[233,885,886],{"class":268},").sort_values(",[233,888,731],{"class":242},[233,890,734],{"class":268},[233,892,737],{"class":436},[233,894,440],{"class":264},[233,896,742],{"class":300},[233,898,446],{"class":268},[233,900,901,904,907,910,912,915,917,919,922,925,927],{"class":56,"line":291},[233,902,903],{"class":268},"matched[",[233,905,906],{"class":242},"\"rate_age_days\"",[233,908,909],{"class":268},"] ",[233,911,440],{"class":264},[233,913,914],{"class":268}," (matched[",[233,916,628],{"class":242},[233,918,909],{"class":268},[233,920,921],{"class":264},"-",[233,923,924],{"class":268}," matched[",[233,926,837],{"class":242},[233,928,929],{"class":268},"]).dt.days\n",[233,931,932,934,936,938,940,942,944,946,948,950,952,954],{"class":56,"line":327},[233,933,749],{"class":300},[233,935,752],{"class":268},[233,937,731],{"class":242},[233,939,304],{"class":268},[233,941,628],{"class":242},[233,943,304],{"class":268},[233,945,837],{"class":242},[233,947,304],{"class":268},[233,949,767],{"class":242},[233,951,304],{"class":268},[233,953,906],{"class":242},[233,955,770],{"class":268},[10,957,958],{},"Transaction 4, on 9 March, was converted with a rate four days old, and transaction 5 has no rate at all: it is dated 27 February, before the first GBP rate in the table. Both deserve attention before the numbers are used.",[29,960,38,966,38,969,38,972,38,977,38,983,38,987,38,993,38,997,38,1001,38,1005,38,1009,38,1013,38,1017,38,1021,38,1025,38,1029,38,1033,38,1037,38,1041,38,1043,38,1046,38,1048,38,1051,38,1053,38,1056,38,1058,38,1061,38,1063,38,1066,38,1068,38,1071,38,1074,38,1077,38,1079,38,1082,38,1084,38,1087,38,1089,38,1092,38,1094,38,1097,38,1099,38,1101,38,1103,38,1105,38,1108,38,1111,38,1113,38,1115,38,1117,38,1120,38,1122,38,1125,38,1127,38,1129,38,1131,38,1133,38,1135,38,1137,38,1140,38,1143,38,1145,38,1147,38,1149,38,1152,38,1154,38,1156,38,1158,38,1161,38,1163,38,1165,38,1167,38,1169,38,1172,38,1175,38,1177,38,1180,38,1182,38,1185,38,1187,38,1189,38,1191,38,1193,38,1195,38,1197,38,1199,38,1201,38,1204,38,1208,38,1212,38,1215,38,1217,38,1220,38,1222,38,1224,38,1226,38,1228,38,1230],{"viewBox":961,"role":32,"ariaLabelledBy":962,"xmlns":36,"style":965},"163 0 434 250",[963,964],"ma-result-t","ma-result-d","width:100%;max-width:434px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[40,967,968],{"id":963},"The matched table with the rate's own date",[44,970,971],{"id":964},"Each transaction row shows its date, the date of the rate that was used and the rate. Transaction 1 on 4 March used the 3 March rate. Transaction 4 on 9 March used a rate from 5 March, four days old. Transaction 5 on 27 February found no rate because the table starts later.",[48,973],{"x":974,"y":51,"width":975,"height":976,"fill":54},"163","434","250",[48,978],{"x":979,"y":66,"width":980,"height":981,"rx":51,"fill":70,"stroke":71,"style":982},"179.4","32.0","24.0","stroke-width:1px",[48,984],{"x":985,"y":66,"width":986,"height":981,"rx":51,"fill":70,"stroke":71,"style":982},"211.4","44.0",[85,988,992],{"x":989,"y":990,"style":991},"233.4","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[48,994],{"x":995,"y":66,"width":996,"height":981,"rx":51,"fill":70,"stroke":71,"style":982},"255.4","63.5",[85,998,1000],{"x":999,"y":990,"style":991},"287.2","B",[48,1002],{"x":1003,"y":66,"width":1004,"height":981,"rx":51,"fill":70,"stroke":71,"style":982},"319.0","79.9",[85,1006,1008],{"x":1007,"y":990,"style":991},"358.9","C",[48,1010],{"x":1011,"y":66,"width":1012,"height":981,"rx":51,"fill":70,"stroke":71,"style":982},"398.8","81.9",[85,1014,1016],{"x":1015,"y":990,"style":991},"439.8","D",[48,1018],{"x":1019,"y":66,"width":1020,"height":981,"rx":51,"fill":70,"stroke":71,"style":982},"480.8","53.0",[85,1022,1024],{"x":1023,"y":990,"style":991},"507.3","E",[48,1026],{"x":1027,"y":66,"width":1028,"height":981,"rx":51,"fill":70,"stroke":71,"style":982},"533.8","46.8",[85,1030,1032],{"x":1031,"y":990,"style":991},"557.2","F",[48,1034],{"x":979,"y":1035,"width":980,"height":1036,"rx":51,"fill":70,"stroke":71,"style":982},"40.0","28.0",[85,1038,301],{"x":1039,"y":1040,"style":991},"195.4","59.0",[48,1042],{"x":985,"y":1035,"width":986,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1044,783],{"x":989,"y":1040,"style":1045},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[48,1047],{"x":995,"y":1035,"width":996,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1049,1050],{"x":999,"y":1040,"style":1045},"date",[48,1052],{"x":1003,"y":1035,"width":1004,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1054,1055],{"x":1007,"y":1040,"style":1045},"currency",[48,1057],{"x":1011,"y":1035,"width":1012,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1059,1060],{"x":1015,"y":1040,"style":1045},"rate_date",[48,1062],{"x":1019,"y":1035,"width":1020,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1064,1065],{"x":1023,"y":1040,"style":1045},"rate",[48,1067],{"x":1027,"y":1035,"width":1028,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1069,1070],{"x":1031,"y":1040,"style":1045},"age",[48,1072],{"x":979,"y":1073,"width":980,"height":1036,"rx":51,"fill":70,"stroke":71,"style":982},"68.0",[85,1075,307],{"x":1039,"y":1076,"style":991},"87.0",[48,1078],{"x":985,"y":1073,"width":986,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1080,301],{"x":989,"y":1076,"style":1081},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[48,1083],{"x":995,"y":1073,"width":996,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1085,1086],{"x":999,"y":1076,"style":1081},"03-04",[48,1088],{"x":1003,"y":1073,"width":1004,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1090,1091],{"x":1007,"y":1076,"style":1081},"USD",[48,1093],{"x":1011,"y":1073,"width":1012,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1095,1096],{"x":1015,"y":1076,"style":1081},"03-03",[48,1098],{"x":1019,"y":1073,"width":1020,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1100,523],{"x":1023,"y":1076,"style":1081},[48,1102],{"x":1027,"y":1073,"width":1028,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1104,301],{"x":1031,"y":1076,"style":1081},[48,1106],{"x":979,"y":1107,"width":980,"height":1036,"rx":51,"fill":70,"stroke":71,"style":982},"96.0",[85,1109,312],{"x":1039,"y":1110,"style":991},"115.0",[48,1112],{"x":985,"y":1107,"width":986,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1114,307],{"x":989,"y":1110,"style":1081},[48,1116],{"x":995,"y":1107,"width":996,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1118,1119],{"x":999,"y":1110,"style":1081},"03-02",[48,1121],{"x":1003,"y":1107,"width":1004,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1123,1124],{"x":1007,"y":1110,"style":1081},"GBP",[48,1126],{"x":1011,"y":1107,"width":1012,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1128,1119],{"x":1015,"y":1110,"style":1081},[48,1130],{"x":1019,"y":1107,"width":1020,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1132,533],{"x":1023,"y":1110,"style":1081},[48,1134],{"x":1027,"y":1107,"width":1028,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1136,51],{"x":1031,"y":1110,"style":1081},[48,1138],{"x":979,"y":1139,"width":980,"height":1036,"rx":51,"fill":70,"stroke":71,"style":982},"124.0",[85,1141,50],{"x":1039,"y":1142,"style":991},"143.0",[48,1144],{"x":985,"y":1139,"width":986,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1146,312],{"x":989,"y":1142,"style":1081},[48,1148],{"x":995,"y":1139,"width":996,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1150,1151],{"x":999,"y":1142,"style":1081},"03-06",[48,1153],{"x":1003,"y":1139,"width":1004,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1155,1091],{"x":1007,"y":1142,"style":1081},[48,1157],{"x":1011,"y":1139,"width":1012,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1159,1160],{"x":1015,"y":1142,"style":1081},"03-05",[48,1162],{"x":1019,"y":1139,"width":1020,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1164,528],{"x":1023,"y":1142,"style":1081},[48,1166],{"x":1027,"y":1139,"width":1028,"height":1036,"rx":51,"fill":54,"stroke":71,"style":982},[85,1168,301],{"x":1031,"y":1142,"style":1081},[48,1170],{"x":979,"y":1171,"width":980,"height":1036,"rx":51,"fill":70,"stroke":71,"style":982},"152.0",[85,1173,321],{"x":1039,"y":1174,"style":991},"171.0",[48,1176],{"x":985,"y":1171,"width":986,"height":1036,"rx":51,"fill":191,"stroke":192,"style":99},[85,1178,50],{"x":989,"y":1174,"style":1179},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[48,1181],{"x":995,"y":1171,"width":996,"height":1036,"rx":51,"fill":191,"stroke":192,"style":99},[85,1183,1184],{"x":999,"y":1174,"style":1179},"03-09",[48,1186],{"x":1003,"y":1171,"width":1004,"height":1036,"rx":51,"fill":191,"stroke":192,"style":99},[85,1188,1091],{"x":1007,"y":1174,"style":1179},[48,1190],{"x":1011,"y":1171,"width":1012,"height":1036,"rx":51,"fill":191,"stroke":192,"style":99},[85,1192,1160],{"x":1015,"y":1174,"style":1179},[48,1194],{"x":1019,"y":1171,"width":1020,"height":1036,"rx":51,"fill":191,"stroke":192,"style":99},[85,1196,528],{"x":1023,"y":1174,"style":1179},[48,1198],{"x":1027,"y":1171,"width":1028,"height":1036,"rx":51,"fill":191,"stroke":192,"style":99},[85,1200,50],{"x":1031,"y":1174,"style":1179},[48,1202],{"x":979,"y":1203,"width":980,"height":1036,"rx":51,"fill":70,"stroke":71,"style":982},"180.0",[85,1205,1207],{"x":1039,"y":1206,"style":991},"199.0","6",[48,1209],{"x":985,"y":1203,"width":986,"height":1036,"rx":51,"fill":1210,"stroke":1211,"style":99},"#fee8f2","var(--accent,#f43f8f)",[85,1213,321],{"x":989,"y":1206,"style":1214},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[48,1216],{"x":995,"y":1203,"width":996,"height":1036,"rx":51,"fill":1210,"stroke":1211,"style":99},[85,1218,1219],{"x":999,"y":1206,"style":1214},"02-27",[48,1221],{"x":1003,"y":1203,"width":1004,"height":1036,"rx":51,"fill":1210,"stroke":1211,"style":99},[85,1223,1124],{"x":1007,"y":1206,"style":1214},[48,1225],{"x":1011,"y":1203,"width":1012,"height":1036,"rx":51,"fill":1210,"stroke":1211,"style":99},[48,1227],{"x":1019,"y":1203,"width":1020,"height":1036,"rx":51,"fill":1210,"stroke":1211,"style":99},[48,1229],{"x":1027,"y":1203,"width":1028,"height":1036,"rx":51,"fill":1210,"stroke":1211,"style":99},[85,1231,1235],{"x":1232,"y":1233,"style":1234},"380.0","230.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","gold is a stale match, pink found no rate",[219,1237,1239],{"id":1238},"step-3-refuse-stale-matches-with-tolerance","Step 3 — Refuse stale matches with tolerance",[10,1241,1242,1243,1246],{},"Rates older than a few days usually mean the rate file was not updated — a missed download, a holiday feed. ",[14,1244,1245],{},"tolerance"," caps how far back a match may reach; rows beyond it get a missing value instead of a quietly stale rate:",[224,1248,1250],{"className":255,"code":1249,"language":257,"meta":229,"style":229},"strict = pd.merge_asof(tx.sort_values(\"date\"), fx.sort_values(\"date\"), on=\"date\", by=\"currency\",\n                       tolerance=pd.Timedelta(days=3)).sort_values(\"tx\")\nprint(strict[[\"tx\", \"rate\"]].values.tolist())\n# [[1.0, 0.93], [2.0, 1.17], [3.0, 0.91], [4.0, nan], [5.0, nan]]\nmissing = strict.loc[strict[\"rate\"].isna(), [\"tx\", \"date\", \"currency\"]]\nprint(missing.values.tolist())\n",[14,1251,1252,1286,1310,1326,1331,1359],{"__ignoreMap":229},[233,1253,1254,1257,1259,1261,1263,1265,1267,1270,1272,1274,1276,1278,1280,1282,1284],{"class":56,"line":235},[233,1255,1256],{"class":268},"strict ",[233,1258,440],{"class":264},[233,1260,680],{"class":268},[233,1262,628],{"class":242},[233,1264,685],{"class":268},[233,1266,628],{"class":242},[233,1268,1269],{"class":268},"), ",[233,1271,623],{"class":436},[233,1273,440],{"class":264},[233,1275,628],{"class":242},[233,1277,304],{"class":268},[233,1279,633],{"class":436},[233,1281,440],{"class":264},[233,1283,638],{"class":242},[233,1285,874],{"class":268},[233,1287,1288,1291,1293,1296,1299,1301,1303,1306,1308],{"class":56,"line":278},[233,1289,1290],{"class":436},"                       tolerance",[233,1292,440],{"class":264},[233,1294,1295],{"class":268},"pd.Timedelta(",[233,1297,1298],{"class":436},"days",[233,1300,440],{"class":264},[233,1302,312],{"class":300},[233,1304,1305],{"class":268},")).sort_values(",[233,1307,731],{"class":242},[233,1309,446],{"class":268},[233,1311,1312,1314,1317,1319,1321,1323],{"class":56,"line":285},[233,1313,749],{"class":300},[233,1315,1316],{"class":268},"(strict[[",[233,1318,731],{"class":242},[233,1320,304],{"class":268},[233,1322,767],{"class":242},[233,1324,1325],{"class":268},"]].values.tolist())\n",[233,1327,1328],{"class":56,"line":291},[233,1329,1330],{"class":665},"# [[1.0, 0.93], [2.0, 1.17], [3.0, 0.91], [4.0, nan], [5.0, nan]]\n",[233,1332,1333,1336,1338,1341,1343,1346,1348,1350,1352,1354,1356],{"class":56,"line":327},[233,1334,1335],{"class":268},"missing ",[233,1337,440],{"class":264},[233,1339,1340],{"class":268}," strict.loc[strict[",[233,1342,767],{"class":242},[233,1344,1345],{"class":268},"].isna(), [",[233,1347,731],{"class":242},[233,1349,304],{"class":268},[233,1351,628],{"class":242},[233,1353,304],{"class":268},[233,1355,638],{"class":242},[233,1357,1358],{"class":268},"]]\n",[233,1360,1361,1363],{"class":56,"line":362},[233,1362,749],{"class":300},[233,1364,1365],{"class":268},"(missing.values.tolist())\n",[10,1367,1368,1369,1373],{},"Choose the tolerance from how the source publishes: three or four days covers weekends and single holidays for daily FX rates. Then fail the run or write the unmatched rows to an exceptions sheet, just as for an ordinary merge — see ",[23,1370,1372],{"href":1371},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fvalidate-a-merge-to-avoid-duplicate-rows\u002F","Validate a pandas Merge to Avoid Duplicate Rows",".",[219,1375,1377],{"id":1376},"step-4-backward-forward-or-nearest","Step 4 — Backward, forward or nearest",[10,1379,1380,1382],{},[14,1381,712],{}," changes which neighbour counts:",[224,1384,1386],{"className":255,"code":1385,"language":257,"meta":229,"style":229},"for direction in [\"backward\", \"forward\", \"nearest\"]:\n    m = pd.merge_asof(tx.sort_values(\"date\"), fx.sort_values(\"date\"), on=\"date\", by=\"currency\",\n                      direction=direction).sort_values(\"tx\")\n    print(direction, m[\"rate\"].tolist())\n# backward [0.93, 1.17, 0.91, 0.91, nan]\n# forward [0.91, 1.17, nan, nan, 1.17]\n# nearest [0.93, 1.17, 0.91, 0.91, 1.17]\n",[14,1387,1388,1417,1450,1464,1476,1481,1486],{"__ignoreMap":229},[233,1389,1390,1393,1396,1399,1402,1404,1406,1409,1411,1414],{"class":56,"line":235},[233,1391,1392],{"class":264},"for",[233,1394,1395],{"class":268}," direction ",[233,1397,1398],{"class":264},"in",[233,1400,1401],{"class":268}," [",[233,1403,717],{"class":242},[233,1405,304],{"class":268},[233,1407,1408],{"class":242},"\"forward\"",[233,1410,304],{"class":268},[233,1412,1413],{"class":242},"\"nearest\"",[233,1415,1416],{"class":268},"]:\n",[233,1418,1419,1422,1424,1426,1428,1430,1432,1434,1436,1438,1440,1442,1444,1446,1448],{"class":56,"line":278},[233,1420,1421],{"class":268},"    m ",[233,1423,440],{"class":264},[233,1425,680],{"class":268},[233,1427,628],{"class":242},[233,1429,685],{"class":268},[233,1431,628],{"class":242},[233,1433,1269],{"class":268},[233,1435,623],{"class":436},[233,1437,440],{"class":264},[233,1439,628],{"class":242},[233,1441,304],{"class":268},[233,1443,633],{"class":436},[233,1445,440],{"class":264},[233,1447,638],{"class":242},[233,1449,874],{"class":268},[233,1451,1452,1455,1457,1460,1462],{"class":56,"line":285},[233,1453,1454],{"class":436},"                      direction",[233,1456,440],{"class":264},[233,1458,1459],{"class":268},"direction).sort_values(",[233,1461,731],{"class":242},[233,1463,446],{"class":268},[233,1465,1466,1468,1471,1473],{"class":56,"line":291},[233,1467,659],{"class":300},[233,1469,1470],{"class":268},"(direction, m[",[233,1472,767],{"class":242},[233,1474,1475],{"class":268},"].tolist())\n",[233,1477,1478],{"class":56,"line":327},[233,1479,1480],{"class":665},"# backward [0.93, 1.17, 0.91, 0.91, nan]\n",[233,1482,1483],{"class":56,"line":362},[233,1484,1485],{"class":665},"# forward [0.91, 1.17, nan, nan, 1.17]\n",[233,1487,1488],{"class":56,"line":392},[233,1489,1490],{"class":665},"# nearest [0.93, 1.17, 0.91, 0.91, 1.17]\n",[10,1492,1493,1496,1497,1500,1501,1504],{},[14,1494,1495],{},"backward"," is right for anything \"in force at the time\": rates, prices, statuses. ",[14,1498,1499],{},"forward"," finds the next event — the first delivery after an order, the next price review. ",[14,1502,1503],{},"nearest"," suits measurements, such as matching a sensor reading to the closest weather observation; it is rarely right for financial data, because it can use a rate published after the transaction.",[29,1506,38,1511,38,1514,38,1517,38,1520,38,1525,38,1531,38,1535,38,1539,38,1543,38,1546,38,1551,38,1558,38,1563,38,1567,38,1570,38,1575,38,1578,38,1581,38,1585,38,1588,38,1593,38,1596,38,1599,38,1602,38,1605,38,1609,38,1612,38,1615,38,1618,38,1621,38,1623,38,1626,38,1630,38,1633,38,1636,38,1640],{"viewBox":1507,"role":32,"ariaLabelledBy":1508,"xmlns":36,"style":37},"4 0 752 238",[1509,1510],"ma-direction-t","ma-direction-d",[40,1512,1513],{"id":1509},"Choosing the direction for merge_asof",[44,1515,1516],{"id":1510},"Use backward for values in force at the time, such as rates, prices and statuses. Use forward to find the next event after a date. Use nearest for measurements where either side is acceptable. Add allow_exact_matches False when the same-day value must be excluded.",[48,1518],{"x":50,"y":51,"width":52,"height":1519,"fill":54},"238",[48,1521],{"x":1522,"y":66,"width":1523,"height":1524,"rx":69,"fill":129,"stroke":130,"style":72},"185.3","389.4","75.5",[74,1526,1528],{"transform":1527},"translate(368.0,29.0) scale(1.000)",[78,1529],{"d":1530,"fill":81,"stroke":137,"style":83},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4",[85,1532,1534],{"x":1232,"y":1524,"style":1533},"font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Which neighbouring row should each row take?",[56,1536],{"x1":1232,"y1":1537,"x2":1232,"y2":1538,"stroke":130,"style":72},"91.5","111.5",[56,1540],{"x1":1541,"y1":1538,"x2":1542,"y2":1538,"stroke":130,"style":72},"104.0","656.0",[56,1544],{"x1":1541,"y1":1538,"x2":1541,"y2":1545,"stroke":130,"style":72},"157.5",[1547,1548],"polygon",{"points":1549,"fill":1550},"104.0,161.5 99.0,152.5 109.0,152.5","#5b5cf0",[48,1552],{"x":1553,"y":1554,"width":1555,"height":1556,"rx":420,"fill":54,"stroke":71,"style":1557},"39.9","121.5","128.1","21.0","stroke-width:1.2px",[85,1559,1562],{"x":1541,"y":1560,"style":1561},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","in force at the time",[48,1564],{"x":65,"y":1565,"width":1566,"height":1040,"rx":69,"fill":129,"stroke":130,"style":72},"163.5","168.0",[85,1568,1495],{"x":1541,"y":1569,"style":1533},"189.0",[85,1571,1574],{"x":1541,"y":1572,"style":1573},"208.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","rates, prices, status",[56,1576],{"x1":1577,"y1":1538,"x2":1577,"y2":1545,"stroke":130,"style":72},"288.0",[1547,1579],{"points":1580,"fill":1550},"288.0,161.5 283.0,152.5 293.0,152.5",[48,1582],{"x":1583,"y":1554,"width":1584,"height":1556,"rx":420,"fill":54,"stroke":71,"style":1557},"247.7","80.6",[85,1586,1587],{"x":1577,"y":1560,"style":1561},"next event",[48,1589],{"x":1590,"y":1565,"width":1566,"height":1040,"rx":69,"fill":1591,"stroke":1592,"style":72},"204.0","#d9f4f1","var(--teal,#0f9488)",[85,1594,1499],{"x":1577,"y":1569,"style":1595},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[85,1597,1598],{"x":1577,"y":1572,"style":1573},"deliveries, reviews",[56,1600],{"x1":1601,"y1":1538,"x2":1601,"y2":1545,"stroke":130,"style":72},"472.0",[1547,1603],{"points":1604,"fill":1550},"472.0,161.5 467.0,152.5 477.0,152.5",[48,1606],{"x":1607,"y":1554,"width":1608,"height":1556,"rx":420,"fill":54,"stroke":71,"style":1557},"399.2","145.6",[85,1610,1611],{"x":1601,"y":1560,"style":1561},"closest measurement",[48,1613],{"x":1614,"y":1565,"width":1566,"height":1040,"rx":69,"fill":191,"stroke":192,"style":72},"388.0",[85,1616,1503],{"x":1601,"y":1569,"style":1617},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[85,1619,1620],{"x":1601,"y":1572,"style":1573},"sensors, readings",[56,1622],{"x1":1542,"y1":1538,"x2":1542,"y2":1545,"stroke":130,"style":72},[1547,1624],{"points":1625,"fill":1550},"656.0,161.5 651.0,152.5 661.0,152.5",[48,1627],{"x":1628,"y":1554,"width":1629,"height":1556,"rx":420,"fill":54,"stroke":71,"style":1557},"605.3","101.4",[85,1631,1632],{"x":1542,"y":1560,"style":1561},"strictly before",[48,1634],{"x":1635,"y":1565,"width":1566,"height":1040,"rx":69,"fill":1210,"stroke":1211,"style":72},"572.0",[85,1637,1639],{"x":1542,"y":1569,"style":1638},"font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","exact matches off",[85,1641,1642],{"x":1542,"y":1572,"style":1573},"previous day's close",[219,1644,1646],{"id":1645},"step-5-exclude-same-day-matches-when-needed","Step 5 — Exclude same-day matches when needed",[10,1648,1649,1650,1653],{},"Some rules require the previous day's value: invoices converted at the prior business day's closing rate, for example. ",[14,1651,1652],{},"allow_exact_matches=False"," skips a right row with exactly the same date:",[224,1655,1657],{"className":255,"code":1656,"language":257,"meta":229,"style":229},"prior = pd.merge_asof(tx.sort_values(\"date\"), fx.sort_values(\"date\"), on=\"date\", by=\"currency\",\n                      allow_exact_matches=False).sort_values(\"tx\")\nprint(prior[\"rate\"].tolist())     # [0.93, nan, 0.91, 0.91, nan]\n",[14,1658,1659,1692,1707],{"__ignoreMap":229},[233,1660,1661,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688,1690],{"class":56,"line":235},[233,1662,1663],{"class":268},"prior ",[233,1665,440],{"class":264},[233,1667,680],{"class":268},[233,1669,628],{"class":242},[233,1671,685],{"class":268},[233,1673,628],{"class":242},[233,1675,1269],{"class":268},[233,1677,623],{"class":436},[233,1679,440],{"class":264},[233,1681,628],{"class":242},[233,1683,304],{"class":268},[233,1685,633],{"class":436},[233,1687,440],{"class":264},[233,1689,638],{"class":242},[233,1691,874],{"class":268},[233,1693,1694,1697,1699,1701,1703,1705],{"class":56,"line":278},[233,1695,1696],{"class":436},"                      allow_exact_matches",[233,1698,440],{"class":264},[233,1700,443],{"class":300},[233,1702,886],{"class":268},[233,1704,731],{"class":242},[233,1706,446],{"class":268},[233,1708,1709,1711,1714,1716,1719],{"class":56,"line":285},[233,1710,749],{"class":300},[233,1712,1713],{"class":268},"(prior[",[233,1715,767],{"class":242},[233,1717,1718],{"class":268},"].tolist())     ",[233,1720,1721],{"class":665},"# [0.93, nan, 0.91, 0.91, nan]\n",[10,1723,1724],{},"Transaction 2 on 2 March no longer takes the 2 March GBP rate, and there is no earlier one, so it is unmatched. The rule belongs in the accounting policy, not in the code; confirm it before choosing.",[219,1726,1728],{"id":1727},"step-6-fix-the-dtype-errors","Step 6 — Fix the dtype errors",[10,1730,1731,1733],{},[14,1732,601],{}," is strict about the key types. Dates read from different sources can have different resolutions — microseconds from one reader, seconds from another — and the merge refuses them:",[224,1735,1737],{"className":255,"code":1736,"language":257,"meta":229,"style":229},"fx_seconds = fx.assign(date=fx[\"date\"].astype(\"datetime64[s]\"))\ntry:\n    pd.merge_asof(tx.sort_values(\"date\"), fx_seconds.sort_values(\"date\"), on=\"date\", by=\"currency\")\nexcept pd.errors.MergeError as exc:\n    print(str(exc)[:40])          # incompatible merge keys [1] dtype('\u003CM8[u\naligned = fx_seconds.assign(date=fx_seconds[\"date\"].astype(tx[\"date\"].dtype))\nprint(len(pd.merge_asof(tx.sort_values(\"date\"), aligned.sort_values(\"date\"), on=\"date\", by=\"currency\")))\n",[14,1738,1739,1767,1773,1803,1814,1836,1863],{"__ignoreMap":229},[233,1740,1741,1744,1746,1749,1751,1753,1756,1758,1761,1764],{"class":56,"line":235},[233,1742,1743],{"class":268},"fx_seconds ",[233,1745,440],{"class":264},[233,1747,1748],{"class":268}," fx.assign(",[233,1750,1050],{"class":436},[233,1752,440],{"class":264},[233,1754,1755],{"class":268},"fx[",[233,1757,628],{"class":242},[233,1759,1760],{"class":268},"].astype(",[233,1762,1763],{"class":242},"\"datetime64[s]\"",[233,1765,1766],{"class":268},"))\n",[233,1768,1769,1771],{"class":56,"line":278},[233,1770,612],{"class":264},[233,1772,615],{"class":268},[233,1774,1775,1778,1780,1783,1785,1787,1789,1791,1793,1795,1797,1799,1801],{"class":56,"line":285},[233,1776,1777],{"class":268},"    pd.merge_asof(tx.sort_values(",[233,1779,628],{"class":242},[233,1781,1782],{"class":268},"), fx_seconds.sort_values(",[233,1784,628],{"class":242},[233,1786,1269],{"class":268},[233,1788,623],{"class":436},[233,1790,440],{"class":264},[233,1792,628],{"class":242},[233,1794,304],{"class":268},[233,1796,633],{"class":436},[233,1798,440],{"class":264},[233,1800,638],{"class":242},[233,1802,446],{"class":268},[233,1804,1805,1807,1810,1812],{"class":56,"line":291},[233,1806,645],{"class":264},[233,1808,1809],{"class":268}," pd.errors.MergeError ",[233,1811,272],{"class":264},[233,1813,654],{"class":268},[233,1815,1816,1818,1821,1824,1827,1830,1833],{"class":56,"line":327},[233,1817,659],{"class":300},[233,1819,1820],{"class":268},"(",[233,1822,1823],{"class":300},"str",[233,1825,1826],{"class":268},"(exc)[:",[233,1828,1829],{"class":300},"40",[233,1831,1832],{"class":268},"])          ",[233,1834,1835],{"class":665},"# incompatible merge keys [1] dtype('\u003CM8[u\n",[233,1837,1838,1841,1843,1846,1848,1850,1853,1855,1858,1860],{"class":56,"line":362},[233,1839,1840],{"class":268},"aligned ",[233,1842,440],{"class":264},[233,1844,1845],{"class":268}," fx_seconds.assign(",[233,1847,1050],{"class":436},[233,1849,440],{"class":264},[233,1851,1852],{"class":268},"fx_seconds[",[233,1854,628],{"class":242},[233,1856,1857],{"class":268},"].astype(tx[",[233,1859,628],{"class":242},[233,1861,1862],{"class":268},"].dtype))\n",[233,1864,1865,1867,1869,1872,1875,1877,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898],{"class":56,"line":392},[233,1866,749],{"class":300},[233,1868,1820],{"class":268},[233,1870,1871],{"class":300},"len",[233,1873,1874],{"class":268},"(pd.merge_asof(tx.sort_values(",[233,1876,628],{"class":242},[233,1878,1879],{"class":268},"), aligned.sort_values(",[233,1881,628],{"class":242},[233,1883,1269],{"class":268},[233,1885,623],{"class":436},[233,1887,440],{"class":264},[233,1889,628],{"class":242},[233,1891,304],{"class":268},[233,1893,633],{"class":436},[233,1895,440],{"class":264},[233,1897,638],{"class":242},[233,1899,1900],{"class":268},")))\n",[10,1902,1903,1904,1906,1907,1910,1911,1373],{},"Cast one side to the other's dtype. The same applies to ",[14,1905,633],{}," columns: a currency column that is text in one file and categorical in the other must be made to match. Dates stored as text in one workbook need ",[14,1908,1909],{},"pd.to_datetime"," first; see ",[23,1912,1914],{"href":1913},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fparse-excel-dates-into-python-datetimes-with-pandas\u002F","Parse Excel Dates into Python datetimes with pandas",[219,1916,1918],{"id":1917},"step-7-convert-and-write-an-auditable-result","Step 7 — Convert and write an auditable result",[10,1920,1921],{},"With the rate and its date attached, the conversion is one multiplication. Write the rate, its date and its age next to the converted amount, and put the exceptions on their own sheet, so anyone checking a figure can see exactly how it was produced:",[224,1923,1925],{"className":255,"code":1924,"language":257,"meta":229,"style":229},"final = pd.merge_asof(tx.sort_values(\"date\"), rates, left_on=\"date\", right_on=\"rate_date\",\n                      by=\"currency\", tolerance=pd.Timedelta(days=3)).sort_values(\"tx\")\nfinal[\"amount_eur\"] = (final[\"amount\"] * final[\"rate\"]).round(2)\nfinal[\"rate_age_days\"] = (final[\"date\"] - final[\"rate_date\"]).dt.days\nok = final[\"rate\"].notna()\nwith pd.ExcelWriter(\"converted.xlsx\", engine=\"openpyxl\", datetime_format=\"yyyy-mm-dd\") as writer:\n    final[ok].to_excel(writer, sheet_name=\"Converted\", index=False)\n    final[~ok][[\"tx\", \"date\", \"currency\", \"amount\"]].to_excel(writer, sheet_name=\"No rate\", index=False)\nprint(final.loc[ok, \"amount_eur\"].tolist(), int((~ok).sum()))   # [93.0, 58.5, 72.8] 2\n",[14,1926,1927,1956,1985,2020,2044,2058,2097,2120,2165],{"__ignoreMap":229},[233,1928,1929,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954],{"class":56,"line":235},[233,1930,1931],{"class":268},"final ",[233,1933,440],{"class":264},[233,1935,680],{"class":268},[233,1937,628],{"class":242},[233,1939,857],{"class":268},[233,1941,801],{"class":436},[233,1943,440],{"class":264},[233,1945,628],{"class":242},[233,1947,304],{"class":268},[233,1949,805],{"class":436},[233,1951,440],{"class":264},[233,1953,837],{"class":242},[233,1955,874],{"class":268},[233,1957,1958,1961,1963,1965,1967,1969,1971,1973,1975,1977,1979,1981,1983],{"class":56,"line":278},[233,1959,1960],{"class":436},"                      by",[233,1962,440],{"class":264},[233,1964,638],{"class":242},[233,1966,304],{"class":268},[233,1968,1245],{"class":436},[233,1970,440],{"class":264},[233,1972,1295],{"class":268},[233,1974,1298],{"class":436},[233,1976,440],{"class":264},[233,1978,312],{"class":300},[233,1980,1305],{"class":268},[233,1982,731],{"class":242},[233,1984,446],{"class":268},[233,1986,1987,1990,1993,1995,1997,2000,2003,2005,2008,2011,2013,2016,2018],{"class":56,"line":285},[233,1988,1989],{"class":268},"final[",[233,1991,1992],{"class":242},"\"amount_eur\"",[233,1994,909],{"class":268},[233,1996,440],{"class":264},[233,1998,1999],{"class":268}," (final[",[233,2001,2002],{"class":242},"\"amount\"",[233,2004,909],{"class":268},[233,2006,2007],{"class":264},"*",[233,2009,2010],{"class":268}," final[",[233,2012,767],{"class":242},[233,2014,2015],{"class":268},"]).round(",[233,2017,307],{"class":300},[233,2019,446],{"class":268},[233,2021,2022,2024,2026,2028,2030,2032,2034,2036,2038,2040,2042],{"class":56,"line":291},[233,2023,1989],{"class":268},[233,2025,906],{"class":242},[233,2027,909],{"class":268},[233,2029,440],{"class":264},[233,2031,1999],{"class":268},[233,2033,628],{"class":242},[233,2035,909],{"class":268},[233,2037,921],{"class":264},[233,2039,2010],{"class":268},[233,2041,837],{"class":242},[233,2043,929],{"class":268},[233,2045,2046,2049,2051,2053,2055],{"class":56,"line":327},[233,2047,2048],{"class":268},"ok ",[233,2050,440],{"class":264},[233,2052,2010],{"class":268},[233,2054,767],{"class":242},[233,2056,2057],{"class":268},"].notna()\n",[233,2059,2060,2063,2066,2069,2071,2074,2076,2079,2081,2084,2086,2089,2092,2094],{"class":56,"line":362},[233,2061,2062],{"class":264},"with",[233,2064,2065],{"class":268}," pd.ExcelWriter(",[233,2067,2068],{"class":242},"\"converted.xlsx\"",[233,2070,304],{"class":268},[233,2072,2073],{"class":436},"engine",[233,2075,440],{"class":264},[233,2077,2078],{"class":242},"\"openpyxl\"",[233,2080,304],{"class":268},[233,2082,2083],{"class":436},"datetime_format",[233,2085,440],{"class":264},[233,2087,2088],{"class":242},"\"yyyy-mm-dd\"",[233,2090,2091],{"class":268},") ",[233,2093,272],{"class":264},[233,2095,2096],{"class":268}," writer:\n",[233,2098,2099,2102,2105,2107,2110,2112,2114,2116,2118],{"class":56,"line":392},[233,2100,2101],{"class":268},"    final[ok].to_excel(writer, ",[233,2103,2104],{"class":436},"sheet_name",[233,2106,440],{"class":264},[233,2108,2109],{"class":242},"\"Converted\"",[233,2111,304],{"class":268},[233,2113,437],{"class":436},[233,2115,440],{"class":264},[233,2117,443],{"class":300},[233,2119,446],{"class":268},[233,2121,2122,2125,2128,2131,2133,2135,2137,2139,2141,2143,2145,2148,2150,2152,2155,2157,2159,2161,2163],{"class":56,"line":425},[233,2123,2124],{"class":268},"    final[",[233,2126,2127],{"class":264},"~",[233,2129,2130],{"class":268},"ok][[",[233,2132,731],{"class":242},[233,2134,304],{"class":268},[233,2136,628],{"class":242},[233,2138,304],{"class":268},[233,2140,638],{"class":242},[233,2142,304],{"class":268},[233,2144,2002],{"class":242},[233,2146,2147],{"class":268},"]].to_excel(writer, ",[233,2149,2104],{"class":436},[233,2151,440],{"class":264},[233,2153,2154],{"class":242},"\"No rate\"",[233,2156,304],{"class":268},[233,2158,437],{"class":436},[233,2160,440],{"class":264},[233,2162,443],{"class":300},[233,2164,446],{"class":268},[233,2166,2167,2169,2172,2174,2177,2180,2183,2185,2188],{"class":56,"line":449},[233,2168,749],{"class":300},[233,2170,2171],{"class":268},"(final.loc[ok, ",[233,2173,1992],{"class":242},[233,2175,2176],{"class":268},"].tolist(), ",[233,2178,2179],{"class":300},"int",[233,2181,2182],{"class":268},"((",[233,2184,2127],{"class":264},[233,2186,2187],{"class":268},"ok).sum()))   ",[233,2189,2190],{"class":665},"# [93.0, 58.5, 72.8] 2\n",[10,2192,2193,2194,2197,2198,1373],{},"Keeping the rate date in the output is what makes the result defensible: a reviewer can compare it with the published rate for that day without rerunning anything. The \"No rate\" sheet tells whoever maintains the rate file exactly which currencies and dates are missing. If rounding must match an existing spreadsheet to the cent, use an Excel-compatible rounding function rather than ",[14,2195,2196],{},"round","; see ",[23,2199,2201],{"href":2200},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas\u002F","Match Excel Rounding in pandas",[219,2203,2205],{"id":2204},"common-pitfalls","Common pitfalls",[2207,2208,2209,2217,2226,2232],"ul",{},[2210,2211,2212,2216],"li",{},[2213,2214,2215],"strong",{},"Missing values in the key."," Rows with no date cannot be placed in order; drop or fix them before merging, or the merge raises an error.",[2210,2218,2219,2222,2223,2225],{},[2213,2220,2221],{},"Duplicate dates on the right."," If the rate table has two rates for the same currency and day, ",[14,2224,601],{}," takes the last one in sorted order. Deduplicate explicitly first.",[2210,2227,2228,2231],{},[2213,2229,2230],{},"Forgetting by."," Without it, every transaction takes the latest rate of any currency.",[2210,2233,2234,2237,2238,2240],{},[2213,2235,2236],{},"Timestamps with times of day."," A transaction at 09:00 does not match a rate stamped 17:00 on the same day with ",[14,2239,1495],{},". Normalise both sides to dates when the rule is \"that day's rate\", or keep the times when intraday rates matter.",[219,2242,2244],{"id":2243},"conclusion","Conclusion",[10,2246,2247,2249,2250,2252,2253,2255],{},[14,2248,601],{}," joins each row to the latest earlier row of another table — the right tool for rates, prices and statuses that were in force at a date. Sort both tables, match within groups with ",[14,2251,633],{},", keep the matched date for auditing, cap staleness with ",[14,2254,1245],{},", pick the direction that fits the rule and align key dtypes. Report the unmatched and stale rows rather than letting them through.",[219,2257,2259],{"id":2258},"frequently-asked-questions","Frequently asked questions",[10,2261,2262,2265],{},[2213,2263,2264],{},"What does merge_asof do?","\nFor each row on the left, it finds the row on the right with the closest key that is on or before it by default, like a VLOOKUP with approximate match. It is the tool for joining transactions to the rate, price or status that was valid at the time.",[10,2267,2268,2271],{},[2213,2269,2270],{},"Why do I get \"left keys must be sorted\"?","\nmerge_asof requires both tables to be sorted by the key column. Sort both with sort_values before merging, and restore the original order afterwards using an ID or the original index.",[10,2273,2274,2277],{},[2213,2275,2276],{},"How do I match only within the same currency or product?","\nPass by=\"currency\" (or a list of columns). Each left row is then matched only against right rows with the same values in those columns.",[10,2279,2280,2283],{},[2213,2281,2282],{},"How do I stop a very old rate from being used?","\nPass tolerance, for example tolerance=pd.Timedelta(days=3). Rows whose nearest match is further away get missing values instead of a stale match, so you can report them.",[219,2285,2287],{"id":2286},"related","Related",[2207,2289,2290,2296,2303,2310],{},[2210,2291,2292,2293,2295],{},"Up one level: ",[23,2294,26],{"href":25}," — combining data from several workbooks.",[2210,2297,2298,2302],{},[23,2299,2301],{"href":2300},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fxlookup-equivalent-in-pandas\u002F","XLOOKUP Equivalent in pandas"," — next smaller and next larger matches on numbers.",[2210,2304,2305,2309],{},[23,2306,2308],{"href":2307},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fvlookup-equivalent-in-pandas-for-excel-files\u002F","The VLOOKUP Equivalent in pandas for Excel Files"," — exact lookups and approximate bands.",[2210,2311,2312,2316],{},[23,2313,2315],{"href":2314},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas\u002F","EOMONTH and EDATE Equivalents in pandas"," — date arithmetic for the keys.",[2318,2319,2320],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":229,"searchDepth":278,"depth":278,"links":2322},[2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334],{"id":221,"depth":278,"text":222},{"id":595,"depth":278,"text":596},{"id":794,"depth":278,"text":795},{"id":1238,"depth":278,"text":1239},{"id":1376,"depth":278,"text":1377},{"id":1645,"depth":278,"text":1646},{"id":1727,"depth":278,"text":1728},{"id":1917,"depth":278,"text":1918},{"id":2204,"depth":278,"text":2205},{"id":2243,"depth":278,"text":2244},{"id":2258,"depth":278,"text":2259},{"id":2286,"depth":278,"text":2287},"2026-09-23","Attach the exchange rate, price or status that applied at each transaction's date — the latest one on or before it — with pandas merge_asof. Match within groups such as currency, limit how stale a match may be, choose backward, forward or nearest, and fix the sorting and dtype errors it raises.","md",[2339,2341,2343,2345],{"q":2264,"a":2340},"For each row on the left, it finds the row on the right with the closest key that is on or before it by default, like a VLOOKUP with approximate match. It is the tool for joining transactions to the rate, price or status that was valid at the time.",{"q":2270,"a":2342},"merge_asof requires both tables to be sorted by the key column. Sort both with sort_values before merging, and restore the original order afterwards using an ID or the original index.",{"q":2276,"a":2344},"Pass by=\"currency\" (or a list of columns). Each left row is then matched only against right rows with the same values in those columns.",{"q":2282,"a":2346},"Pass tolerance, for example tolerance=pd.Timedelta(days=3). Rows whose nearest match is further away get missing values instead of a stale match, so you can report them.","match rows by nearest date with pandas merge_asof",null,{"breadcrumb":2350},[2351,2354,2357],{"name":2352,"item":2353},"Home","\u002F",{"name":2355,"item":2356},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2358,"item":25},"Merging and Joining Excel DataFrames with Pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmatch-rows-by-nearest-date-with-merge-asof",{"title":2361,"description":2362},"Match Rows by Nearest Date with pandas merge_asof (Excel data)","Join Excel tables on the most recent earlier date with merge_asof — FX rates to transactions, by= for per-currency matching, tolerance for stale rates, direction and allow_exact_matches, sorting and dtype errors, and keeping the rate date.","match-rows-by-nearest-date-with-merge-asof","advanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmatch-rows-by-nearest-date-with-merge-asof\u002Findex",[2366,2367,2368],"joins-and-lookups","dates-and-times","pandas","how-to","n9Kogwq4q2wdDK_0gGsl-VN3XVBDLTrNHHFOTqmYyQA",[2372,2376],{"title":2373,"path":2374,"stem":2375,"children":-1},"Look Up All Matches for a Key Instead of the First","\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Flook-up-all-matches-for-a-key-instead-of-the-first","advanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Flook-up-all-matches-for-a-key-instead-of-the-first\u002Findex",{"title":2377,"path":2378,"stem":2379,"children":-1},"Merge Excel Data on Multiple Key Columns with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-excel-data-on-multiple-key-columns","advanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-excel-data-on-multiple-key-columns\u002Findex",[2381,2383],{"path":2382,"title":2355},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2384,"title":2358},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes",[],[2387,2390,2393,2396,2399,2402],{"path":2388,"title":2389,"kind":2369},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcompare-two-excel-files-for-differences-with-python","Compare Two Excel Files for Differences with Python",{"path":2391,"title":2392,"kind":2369},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fconvert-multi-currency-transactions-with-an-fx-rate-table","Convert Multi-Currency Transactions with an FX Rate Table",{"path":2394,"title":2395,"kind":2369},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-differences-between-two-columns-with-openpyxl","Highlight Differences Between Two Columns with openpyxl",{"path":2397,"title":2398,"kind":2369},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fmap-free-text-job-titles-to-standard-categories","Map Free-Text Job Titles to Standard Categories",{"path":2400,"title":2401,"kind":2369},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-category-values-with-a-mapping-table","Standardise Category Values with a Mapping Table in pandas",{"path":2403,"title":2404,"kind":2369},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fapproximate-match-vlookup-in-pandas","Approximate-Match VLOOKUP for Tiered Rates in pandas",1790972096328]