[{"data":1,"prerenderedAt":1942},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-markdown-table":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-markdown-table":1907,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-markdown-table":1915,"children:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-markdown-table":1920,"related:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-markdown-table":1921},{"id":4,"title":5,"body":6,"dateModified":1872,"datePublished":1872,"description":1873,"extension":1874,"faq":1875,"keyword":1884,"kind":1885,"meta":1886,"navigation":322,"path":1895,"seo":1896,"slug":1899,"stem":1900,"topics":1901,"type":1905,"__hash__":1906},"docs\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-markdown-table\u002Findex.md","Convert an Excel Sheet to a Markdown Table with Python",{"type":7,"value":8,"toc":1857},"minimark",[9,24,241,246,276,290,294,375,393,397,407,621,627,741,745,759,842,861,865,872,1119,1129,1133,1140,1376,1391,1522,1526,1529,1658,1665,1669,1685,1689,1748,1752,1757,1761,1785,1789,1795,1801,1811,1817,1821,1853],[10,11,12,13,17,18,23],"p",{},"Markdown tables are how numbers travel outside spreadsheets: a weekly summary pasted into a pull request, a results table in a README, a status update in a wiki or a chat channel. pandas can produce one in a single call — ",[14,15,16],"code",{},"df.to_markdown()"," — and for tidy data it looks right the first time. Real sheets are not always tidy, though, and a single pipe character in a customer name, a line break in a notes cell or a blank cell produces a table that renders wrongly or not at all. This page, part of ",[19,20,22],"a",{"href":21},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002F","Handling Excel File Formats and Conversions",", converts a sheet to Markdown and fixes each of those cases.",[25,26,34,35,34,39,34,43,34,51,34,60,34,64,34,71,34,75,34,79,34,83,34,87,34,91,34,96,34,98,34,102,34,104,34,107,34,109,34,112,34,115,34,119,34,121,34,125,34,127,34,130,34,132,34,135,34,138,34,142,34,144,34,147,34,149,34,151,34,154,34,157,34,161,34,164,34,166,34,169,34,171,34,174,34,176,34,178,34,180,34,183,34,185,34,187,34,189,34,192,34,195,34,199,34,201,34,204,34,210,34,214,34,222,34,227,34,235],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"52 0 655 192","img",[30,31],"md-convert-t","md-convert-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:655px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"A worksheet and the Markdown table made from it",[40,41,42],"desc",{"id":31},"Left: a Sales sheet with region, revenue and units columns. Right: the same data as Markdown text, with a header row, an alignment row of dashes and colons, and one line per data row separated by pipe characters.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"52","0","655","192","#ffffff",[44,52],{"x":53,"y":54,"width":55,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"68.8","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[44,61],{"x":62,"y":54,"width":63,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"100.8","63.6",[65,66,70],"text",{"x":67,"y":68,"style":69},"132.6","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[44,72],{"x":73,"y":54,"width":74,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"164.4","74.9",[65,76,78],{"x":77,"y":68,"style":69},"201.9","B",[44,80],{"x":81,"y":54,"width":82,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"239.3","54.8",[65,84,86],{"x":85,"y":68,"style":69},"266.7","C",[44,88],{"x":53,"y":89,"width":55,"height":90,"rx":47,"fill":57,"stroke":58,"style":59},"64.0","28.0",[65,92,95],{"x":93,"y":94,"style":69},"84.8","83.0","1",[44,97],{"x":62,"y":89,"width":63,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,99,101],{"x":67,"y":94,"style":100},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","region",[44,103],{"x":73,"y":89,"width":74,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,105,106],{"x":77,"y":94,"style":100},"revenue",[44,108],{"x":81,"y":89,"width":82,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,110,111],{"x":85,"y":94,"style":100},"units",[44,113],{"x":53,"y":114,"width":55,"height":90,"rx":47,"fill":57,"stroke":58,"style":59},"92.0",[65,116,118],{"x":93,"y":117,"style":69},"111.0","2",[44,120],{"x":62,"y":114,"width":63,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,122,124],{"x":67,"y":117,"style":123},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","North",[44,126],{"x":73,"y":114,"width":74,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,128,129],{"x":77,"y":117,"style":123},"1200.5",[44,131],{"x":81,"y":114,"width":82,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,133,134],{"x":85,"y":117,"style":123},"12",[44,136],{"x":53,"y":137,"width":55,"height":90,"rx":47,"fill":57,"stroke":58,"style":59},"120.0",[65,139,141],{"x":93,"y":140,"style":69},"139.0","3",[44,143],{"x":62,"y":137,"width":63,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,145,146],{"x":67,"y":140,"style":123},"West",[44,148],{"x":73,"y":137,"width":74,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[44,150],{"x":81,"y":137,"width":82,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,152,153],{"x":85,"y":140,"style":123},"9",[44,155],{"x":156,"y":54,"width":55,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"477.2",[44,158],{"x":159,"y":54,"width":160,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"509.2","182.0",[65,162,70],{"x":163,"y":68,"style":69},"600.2",[44,165],{"x":156,"y":89,"width":55,"height":90,"rx":47,"fill":57,"stroke":58,"style":59},[65,167,95],{"x":168,"y":94,"style":69},"493.2",[44,170],{"x":159,"y":89,"width":160,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,172,173],{"x":163,"y":94,"style":123},"| region | revenue | units |",[44,175],{"x":156,"y":114,"width":55,"height":90,"rx":47,"fill":57,"stroke":58,"style":59},[65,177,118],{"x":168,"y":117,"style":69},[44,179],{"x":159,"y":114,"width":160,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,181,182],{"x":163,"y":117,"style":123},"|:-------|--------:|------:|",[44,184],{"x":156,"y":137,"width":55,"height":90,"rx":47,"fill":57,"stroke":58,"style":59},[65,186,141],{"x":168,"y":140,"style":69},[44,188],{"x":159,"y":137,"width":160,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,190,191],{"x":163,"y":140,"style":123},"| North  | 1,200.50|    12 |",[44,193],{"x":156,"y":194,"width":55,"height":90,"rx":47,"fill":57,"stroke":58,"style":59},"148.0",[65,196,198],{"x":168,"y":197,"style":69},"167.0","4",[44,200],{"x":159,"y":194,"width":160,"height":90,"rx":47,"fill":50,"stroke":58,"style":59},[65,202,203],{"x":163,"y":197,"style":123},"| West   |         |     9 |",[65,205,209],{"x":206,"y":207,"style":208},"181.5","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","summary.xlsx — Sales",[65,211,213],{"x":212,"y":207,"style":208},"584.2","Markdown text",[215,216],"line",{"x1":217,"y1":218,"x2":219,"y2":218,"stroke":220,"style":221},"304.1","94.0","463.2","var(--brand,#5b5cf0)","stroke-width:2px",[223,224],"polygon",{"points":225,"fill":226},"467.2,94.0 458.2,99.0 458.2,89.0","#5b5cf0",[44,228],{"x":229,"y":230,"width":231,"height":232,"rx":233,"fill":50,"stroke":58,"style":234},"328.4","61.0","114.5","22.0","11","stroke-width:1.2px",[65,236,240],{"x":237,"y":238,"style":239},"385.7","76.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","to_markdown()",[242,243,245],"h2",{"id":244},"prerequisites","Prerequisites",[247,248,253],"pre",{"className":249,"code":250,"language":251,"meta":252,"style":252},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl tabulate\n","bash","",[14,254,255],{"__ignoreMap":252},[256,257,259,263,267,270,273],"span",{"class":215,"line":258},1,[256,260,262],{"class":261},"sMTad","pip",[256,264,266],{"class":265},"srMev"," install",[256,268,269],{"class":265}," pandas",[256,271,272],{"class":265}," openpyxl",[256,274,275],{"class":265}," tabulate\n",[10,277,278,281,282,285,286,289],{},[14,279,280],{},"to_markdown"," needs the ",[14,283,284],{},"tabulate"," package; without it pandas raises \"Missing optional dependency 'tabulate'\". The examples use ",[14,287,288],{},"summary.xlsx",", which has a Sales sheet with a pipe in one region name, a two-line note and a missing revenue value, and a small Stock sheet.",[242,291,293],{"id":292},"step-1-convert-with-to_markdown","Step 1 — Convert with to_markdown",[247,295,299],{"className":296,"code":297,"language":298,"meta":252,"style":252},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.read_excel(\"summary.xlsx\", sheet_name=\"Sales\")\nprint(sales.to_markdown(index=False))\n","python",[14,300,301,317,324,354],{"__ignoreMap":252},[256,302,303,307,311,314],{"class":215,"line":258},[256,304,306],{"class":305},"s-kum","import",[256,308,310],{"class":309},"skGVy"," pandas ",[256,312,313],{"class":305},"as",[256,315,316],{"class":309}," pd\n",[256,318,320],{"class":215,"line":319},2,[256,321,323],{"emptyLinePlaceholder":322},true,"\n",[256,325,327,330,333,336,339,342,346,348,351],{"class":215,"line":326},3,[256,328,329],{"class":309},"sales ",[256,331,332],{"class":305},"=",[256,334,335],{"class":309}," pd.read_excel(",[256,337,338],{"class":265},"\"summary.xlsx\"",[256,340,341],{"class":309},", ",[256,343,345],{"class":344},"sa561","sheet_name",[256,347,332],{"class":305},[256,349,350],{"class":265},"\"Sales\"",[256,352,353],{"class":309},")\n",[256,355,357,361,364,367,369,372],{"class":215,"line":356},4,[256,358,360],{"class":359},"sP0c6","print",[256,362,363],{"class":309},"(sales.to_markdown(",[256,365,366],{"class":344},"index",[256,368,332],{"class":305},[256,370,371],{"class":359},"False",[256,373,374],{"class":309},"))\n",[10,376,377,380,381,384,385,388,389,392],{},[14,378,379],{},"index=False"," drops pandas' row numbers, which almost never belong in a published table. The output is a GitHub-style \"pipe\" table with an alignment row: text columns are left-aligned (",[14,382,383],{},":---",") and numbers right-aligned (",[14,386,387],{},"---:","). But look at the South row and the West row. The region \"South | East\" has split into two columns, the note's line break has produced a second, mostly empty line that Markdown will read as a separate row, and the missing revenue prints as ",[14,390,391],{},"nan",".",[242,394,396],{"id":395},"step-2-clean-the-cells-before-converting","Step 2 — Clean the cells before converting",[10,398,399,400,402,403,406],{},"All three problems are fixed in the data, before ",[14,401,280],{}," sees it. Escape pipes, turn newlines into ",[14,404,405],{},"\u003Cbr>"," (which GitHub, GitLab and most renderers accept inside table cells), and replace missing values with empty strings:",[247,408,410],{"className":296,"code":409,"language":298,"meta":252,"style":252},"def markdown_safe(frame):\n    out = frame.copy()\n    text_cols = out.select_dtypes(include=[\"object\", \"string\"]).columns\n    for col in text_cols:\n        out[col] = (out[col].astype(\"string\")\n                    .str.replace(\"|\", r\"\\|\", regex=False)\n                    .str.replace(\"\\r\\n\", \"\u003Cbr>\", regex=False)\n                    .str.replace(\"\\n\", \"\u003Cbr>\", regex=False))\n    return out.astype(object).where(out.notna(), \"\")\n\nprint(markdown_safe(sales).to_markdown(index=False))\n",[14,411,412,424,434,463,477,492,526,554,580,600,605],{"__ignoreMap":252},[256,413,414,417,421],{"class":215,"line":258},[256,415,416],{"class":305},"def",[256,418,420],{"class":419},"s_Opv"," markdown_safe",[256,422,423],{"class":309},"(frame):\n",[256,425,426,429,431],{"class":215,"line":319},[256,427,428],{"class":309},"    out ",[256,430,332],{"class":305},[256,432,433],{"class":309}," frame.copy()\n",[256,435,436,439,441,444,447,449,452,455,457,460],{"class":215,"line":326},[256,437,438],{"class":309},"    text_cols ",[256,440,332],{"class":305},[256,442,443],{"class":309}," out.select_dtypes(",[256,445,446],{"class":344},"include",[256,448,332],{"class":305},[256,450,451],{"class":309},"[",[256,453,454],{"class":265},"\"object\"",[256,456,341],{"class":309},[256,458,459],{"class":265},"\"string\"",[256,461,462],{"class":309},"]).columns\n",[256,464,465,468,471,474],{"class":215,"line":356},[256,466,467],{"class":305},"    for",[256,469,470],{"class":309}," col ",[256,472,473],{"class":305},"in",[256,475,476],{"class":309}," text_cols:\n",[256,478,480,483,485,488,490],{"class":215,"line":479},5,[256,481,482],{"class":309},"        out[col] ",[256,484,332],{"class":305},[256,486,487],{"class":309}," (out[col].astype(",[256,489,459],{"class":265},[256,491,353],{"class":309},[256,493,495,498,501,503,506,509,513,515,517,520,522,524],{"class":215,"line":494},6,[256,496,497],{"class":309},"                    .str.replace(",[256,499,500],{"class":265},"\"|\"",[256,502,341],{"class":309},[256,504,505],{"class":305},"r",[256,507,508],{"class":265},"\"",[256,510,512],{"class":511},"s_b0D","\\|",[256,514,508],{"class":265},[256,516,341],{"class":309},[256,518,519],{"class":344},"regex",[256,521,332],{"class":305},[256,523,371],{"class":359},[256,525,353],{"class":309},[256,527,529,531,533,537,539,541,544,546,548,550,552],{"class":215,"line":528},7,[256,530,497],{"class":309},[256,532,508],{"class":265},[256,534,536],{"class":535},"sSjpA","\\r\\n",[256,538,508],{"class":265},[256,540,341],{"class":309},[256,542,543],{"class":265},"\"\u003Cbr>\"",[256,545,341],{"class":309},[256,547,519],{"class":344},[256,549,332],{"class":305},[256,551,371],{"class":359},[256,553,353],{"class":309},[256,555,557,559,561,564,566,568,570,572,574,576,578],{"class":215,"line":556},8,[256,558,497],{"class":309},[256,560,508],{"class":265},[256,562,563],{"class":535},"\\n",[256,565,508],{"class":265},[256,567,341],{"class":309},[256,569,543],{"class":265},[256,571,341],{"class":309},[256,573,519],{"class":344},[256,575,332],{"class":305},[256,577,371],{"class":359},[256,579,374],{"class":309},[256,581,583,586,589,592,595,598],{"class":215,"line":582},9,[256,584,585],{"class":305},"    return",[256,587,588],{"class":309}," out.astype(",[256,590,591],{"class":359},"object",[256,593,594],{"class":309},").where(out.notna(), ",[256,596,597],{"class":265},"\"\"",[256,599,353],{"class":309},[256,601,603],{"class":215,"line":602},10,[256,604,323],{"emptyLinePlaceholder":322},[256,606,608,610,613,615,617,619],{"class":215,"line":607},11,[256,609,360],{"class":359},[256,611,612],{"class":309},"(markdown_safe(sales).to_markdown(",[256,614,366],{"class":344},[256,616,332],{"class":305},[256,618,371],{"class":359},[256,620,374],{"class":309},[10,622,623,624,626],{},"The table now has the right number of columns on every line and one line per row. Converting to ",[14,625,591],{}," before filling blanks keeps numeric columns from being coerced into strings early, so number formatting in the next step still applies to the numbers that are present.",[25,628,34,634,34,637,34,640,34,644,34,649,34,654,34,660,34,663,34,667,34,671,34,676,34,678,34,683,34,685,34,688,34,691,34,695,34,697,34,700,34,702,34,705,34,708,34,712,34,714,34,718,34,720,34,723,34,726,34,730,34,732,34,736,34,738],{"viewBox":629,"role":28,"ariaLabelledBy":630,"xmlns":32,"style":633},"4 0 752 208",[631,632],"md-fixes-t","md-fixes-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,635,636],{"id":631},"Cell contents that break Markdown tables",[40,638,639],{"id":632},"A table of problem cell contents. A pipe character splits the cell into two columns; replace it with backslash-pipe. A newline ends the row early; replace it with \u003Cbr>. A missing value prints as nan; replace it with an empty string. Unformatted floats show raw precision; pass floatfmt.",[44,641],{"x":198,"y":47,"width":642,"height":643,"fill":50},"752","208",[44,645],{"x":646,"y":647,"width":648,"height":54,"rx":47,"fill":50,"stroke":58,"style":59},"20.0","16.0","153.5",[44,650],{"x":651,"y":647,"width":652,"height":54,"rx":47,"fill":653,"stroke":220,"style":234},"173.5","283.2","#f0f4ff",[65,655,659],{"x":656,"y":657,"style":658},"315.1","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","What to_markdown does",[44,661],{"x":662,"y":647,"width":652,"height":54,"rx":47,"fill":653,"stroke":220,"style":234},"456.8",[65,664,666],{"x":665,"y":657,"style":658},"598.4","Fix before converting",[44,668],{"x":646,"y":669,"width":648,"height":670,"rx":47,"fill":57,"stroke":58,"style":59},"56.0","34.0",[65,672,675],{"x":55,"y":673,"style":674},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Pipe  \"South | East\"",[44,677],{"x":651,"y":669,"width":652,"height":670,"rx":47,"fill":50,"stroke":58,"style":59},[65,679,682],{"x":656,"y":680,"style":681},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","splits into two columns",[44,684],{"x":662,"y":669,"width":652,"height":670,"rx":47,"fill":50,"stroke":58,"style":59},[65,686,687],{"x":665,"y":680,"style":681},"replace | with \\|",[44,689],{"x":646,"y":690,"width":648,"height":670,"rx":47,"fill":57,"stroke":58,"style":59},"90.0",[65,692,694],{"x":55,"y":693,"style":674},"112.0","Newline in a note",[44,696],{"x":651,"y":690,"width":652,"height":670,"rx":47,"fill":50,"stroke":58,"style":59},[65,698,699],{"x":656,"y":117,"style":681},"ends the row early",[44,701],{"x":662,"y":690,"width":652,"height":670,"rx":47,"fill":50,"stroke":58,"style":59},[65,703,704],{"x":665,"y":117,"style":681},"replace with \u003Cbr>",[44,706],{"x":646,"y":707,"width":648,"height":670,"rx":47,"fill":57,"stroke":58,"style":59},"124.0",[65,709,711],{"x":55,"y":710,"style":674},"146.0","Missing value",[44,713],{"x":651,"y":707,"width":652,"height":670,"rx":47,"fill":50,"stroke":58,"style":59},[65,715,717],{"x":656,"y":716,"style":681},"145.0","prints \"nan\"",[44,719],{"x":662,"y":707,"width":652,"height":670,"rx":47,"fill":50,"stroke":58,"style":59},[65,721,722],{"x":665,"y":716,"style":681},"fill with \"\"",[44,724],{"x":646,"y":725,"width":648,"height":670,"rx":47,"fill":57,"stroke":58,"style":59},"158.0",[65,727,729],{"x":55,"y":728,"style":674},"180.0","Float 1200.5",[44,731],{"x":651,"y":725,"width":652,"height":670,"rx":47,"fill":50,"stroke":58,"style":59},[65,733,735],{"x":656,"y":734,"style":681},"179.0","raw precision",[44,737],{"x":662,"y":725,"width":652,"height":670,"rx":47,"fill":50,"stroke":58,"style":59},[65,739,740],{"x":665,"y":734,"style":681},"floatfmt=\",.2f\"",[242,742,744],{"id":743},"step-3-format-numbers-and-align-columns","Step 3 — Format numbers and align columns",[10,746,747,750,751,754,755,758],{},[14,748,749],{},"floatfmt"," formats float columns with a Python format specification — ",[14,752,753],{},"\",.2f\""," for thousands separators and two decimals. Pass a tuple for one format per column, and ",[14,756,757],{},"colalign"," to override the automatic alignment:",[247,760,762],{"className":296,"code":761,"language":298,"meta":252,"style":252},"clean = markdown_safe(sales)\nprint(clean.to_markdown(index=False, floatfmt=(\"\", \",.2f\", \"\", \",.0f\"),\n                        colalign=(\"left\", \"right\", \"left\", \"right\")))\n",[14,763,764,774,814],{"__ignoreMap":252},[256,765,766,769,771],{"class":215,"line":258},[256,767,768],{"class":309},"clean ",[256,770,332],{"class":305},[256,772,773],{"class":309}," markdown_safe(sales)\n",[256,775,776,778,781,783,785,787,789,791,793,796,798,800,802,804,806,808,811],{"class":215,"line":319},[256,777,360],{"class":359},[256,779,780],{"class":309},"(clean.to_markdown(",[256,782,366],{"class":344},[256,784,332],{"class":305},[256,786,371],{"class":359},[256,788,341],{"class":309},[256,790,749],{"class":344},[256,792,332],{"class":305},[256,794,795],{"class":309},"(",[256,797,597],{"class":265},[256,799,341],{"class":309},[256,801,753],{"class":265},[256,803,341],{"class":309},[256,805,597],{"class":265},[256,807,341],{"class":309},[256,809,810],{"class":265},"\",.0f\"",[256,812,813],{"class":309},"),\n",[256,815,816,819,821,823,826,828,831,833,835,837,839],{"class":215,"line":326},[256,817,818],{"class":344},"                        colalign",[256,820,332],{"class":305},[256,822,795],{"class":309},[256,824,825],{"class":265},"\"left\"",[256,827,341],{"class":309},[256,829,830],{"class":265},"\"right\"",[256,832,341],{"class":309},[256,834,825],{"class":265},[256,836,341],{"class":309},[256,838,830],{"class":265},[256,840,841],{"class":309},")))\n",[10,843,844,845,847,848,851,852,855,856,860],{},"Formats apply to floats only; integer columns such as ",[14,846,111],{}," print as they are. If the table should show exactly what the spreadsheet shows — percentages as ",[14,849,850],{},"12.5%",", dates as ",[14,853,854],{},"01 Mar 2026"," — format those columns as strings first. ",[19,857,859],{"href":858},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fread-cell-values-as-displayed-in-excel\u002F","Read Cell Values as Displayed in Excel with Python"," shows how to reproduce Excel's displayed text from the cell formats.",[242,862,864],{"id":863},"step-4-convert-every-sheet-into-one-document","Step 4 — Convert every sheet into one document",[10,866,867,868,871],{},"A workbook with several sheets becomes one Markdown document with a heading per sheet. ",[14,869,870],{},"sheet_name=None"," reads all sheets into a dictionary:",[247,873,875],{"className":296,"code":874,"language":298,"meta":252,"style":252},"from pathlib import Path\n\ndef workbook_to_markdown(path, title=None):\n    sheets = pd.read_excel(path, sheet_name=None)\n    parts = [f\"# {title or Path(path).stem}\", \"\"]\n    for name, frame in sheets.items():\n        parts += [f\"## {name}\", \"\", markdown_safe(frame).to_markdown(index=False), \"\"]\n    return \"\\n\".join(parts)\n\nmd = workbook_to_markdown(\"summary.xlsx\", title=\"Weekly summary\")\nPath(\"summary.md\").write_text(md, encoding=\"utf-8\")\nprint(md.splitlines()[:4])\nprint(len(md.splitlines()), \"lines written\")\n",[14,876,877,890,894,912,930,970,982,1026,1040,1044,1067,1088,1101],{"__ignoreMap":252},[256,878,879,882,885,887],{"class":215,"line":258},[256,880,881],{"class":305},"from",[256,883,884],{"class":309}," pathlib ",[256,886,306],{"class":305},[256,888,889],{"class":309}," Path\n",[256,891,892],{"class":215,"line":319},[256,893,323],{"emptyLinePlaceholder":322},[256,895,896,898,901,904,906,909],{"class":215,"line":326},[256,897,416],{"class":305},[256,899,900],{"class":419}," workbook_to_markdown",[256,902,903],{"class":309},"(path, title",[256,905,332],{"class":305},[256,907,908],{"class":359},"None",[256,910,911],{"class":309},"):\n",[256,913,914,917,919,922,924,926,928],{"class":215,"line":356},[256,915,916],{"class":309},"    sheets ",[256,918,332],{"class":305},[256,920,921],{"class":309}," pd.read_excel(path, ",[256,923,345],{"class":344},[256,925,332],{"class":305},[256,927,908],{"class":359},[256,929,353],{"class":309},[256,931,932,935,937,940,943,946,949,952,955,958,961,963,965,967],{"class":215,"line":479},[256,933,934],{"class":309},"    parts ",[256,936,332],{"class":305},[256,938,939],{"class":309}," [",[256,941,942],{"class":305},"f",[256,944,945],{"class":265},"\"# ",[256,947,948],{"class":535},"{",[256,950,951],{"class":309},"title ",[256,953,954],{"class":305},"or",[256,956,957],{"class":309}," Path(path).stem",[256,959,960],{"class":535},"}",[256,962,508],{"class":265},[256,964,341],{"class":309},[256,966,597],{"class":265},[256,968,969],{"class":309},"]\n",[256,971,972,974,977,979],{"class":215,"line":494},[256,973,467],{"class":305},[256,975,976],{"class":309}," name, frame ",[256,978,473],{"class":305},[256,980,981],{"class":309}," sheets.items():\n",[256,983,984,987,990,992,994,997,999,1002,1004,1006,1008,1010,1013,1015,1017,1019,1022,1024],{"class":215,"line":528},[256,985,986],{"class":309},"        parts ",[256,988,989],{"class":305},"+=",[256,991,939],{"class":309},[256,993,942],{"class":305},[256,995,996],{"class":265},"\"## ",[256,998,948],{"class":535},[256,1000,1001],{"class":309},"name",[256,1003,960],{"class":535},[256,1005,508],{"class":265},[256,1007,341],{"class":309},[256,1009,597],{"class":265},[256,1011,1012],{"class":309},", markdown_safe(frame).to_markdown(",[256,1014,366],{"class":344},[256,1016,332],{"class":305},[256,1018,371],{"class":359},[256,1020,1021],{"class":309},"), ",[256,1023,597],{"class":265},[256,1025,969],{"class":309},[256,1027,1028,1030,1033,1035,1037],{"class":215,"line":556},[256,1029,585],{"class":305},[256,1031,1032],{"class":265}," \"",[256,1034,563],{"class":535},[256,1036,508],{"class":265},[256,1038,1039],{"class":309},".join(parts)\n",[256,1041,1042],{"class":215,"line":582},[256,1043,323],{"emptyLinePlaceholder":322},[256,1045,1046,1049,1051,1054,1056,1058,1060,1062,1065],{"class":215,"line":602},[256,1047,1048],{"class":309},"md ",[256,1050,332],{"class":305},[256,1052,1053],{"class":309}," workbook_to_markdown(",[256,1055,338],{"class":265},[256,1057,341],{"class":309},[256,1059,36],{"class":344},[256,1061,332],{"class":305},[256,1063,1064],{"class":265},"\"Weekly summary\"",[256,1066,353],{"class":309},[256,1068,1069,1072,1075,1078,1081,1083,1086],{"class":215,"line":607},[256,1070,1071],{"class":309},"Path(",[256,1073,1074],{"class":265},"\"summary.md\"",[256,1076,1077],{"class":309},").write_text(md, ",[256,1079,1080],{"class":344},"encoding",[256,1082,332],{"class":305},[256,1084,1085],{"class":265},"\"utf-8\"",[256,1087,353],{"class":309},[256,1089,1091,1093,1096,1098],{"class":215,"line":1090},12,[256,1092,360],{"class":359},[256,1094,1095],{"class":309},"(md.splitlines()[:",[256,1097,198],{"class":359},[256,1099,1100],{"class":309},"])\n",[256,1102,1104,1106,1108,1111,1114,1117],{"class":215,"line":1103},13,[256,1105,360],{"class":359},[256,1107,795],{"class":309},[256,1109,1110],{"class":359},"len",[256,1112,1113],{"class":309},"(md.splitlines()), ",[256,1115,1116],{"class":265},"\"lines written\"",[256,1118,353],{"class":309},[10,1120,1121,1122,1125,1126,1128],{},"Booleans in the Stock sheet print as ",[14,1123,1124],{},"True"," and ",[14,1127,371],{},"; map them to \"yes\"\u002F\"no\" first if the audience expects words. For very wide sheets, pick the columns that matter — a Markdown table with twenty columns wraps badly in every renderer.",[242,1130,1132],{"id":1131},"step-5-convert-without-tabulate","Step 5 — Convert without tabulate",[10,1134,1135,1136,1139],{},"On a machine where adding a dependency is not an option, a small function produces the same pipe-table format from any DataFrame. It reuses ",[14,1137,1138],{},"markdown_safe"," and right-aligns numeric columns:",[247,1141,1143],{"className":296,"code":1142,"language":298,"meta":252,"style":252},"def to_pipe_table(frame):\n    safe = markdown_safe(frame)\n    header = \"| \" + \" | \".join(map(str, safe.columns)) + \" |\"\n    def is_number(col):\n        return pd.api.types.is_numeric_dtype(frame[col]) and not pd.api.types.is_bool_dtype(frame[col])\n    align = \"|\" + \"|\".join(\"---:\" if is_number(c) else \":---\" for c in frame.columns) + \"|\"\n    body = [\"| \" + \" | \".join(str(v) for v in row) + \" |\" for row in safe.itertuples(index=False)]\n    return \"\\n\".join([header, align, *body])\n\nprint(to_pipe_table(pd.read_excel(\"summary.xlsx\", sheet_name=\"Stock\")))\n",[14,1144,1145,1154,1164,1200,1211,1228,1275,1333,1352,1356],{"__ignoreMap":252},[256,1146,1147,1149,1152],{"class":215,"line":258},[256,1148,416],{"class":305},[256,1150,1151],{"class":419}," to_pipe_table",[256,1153,423],{"class":309},[256,1155,1156,1159,1161],{"class":215,"line":319},[256,1157,1158],{"class":309},"    safe ",[256,1160,332],{"class":305},[256,1162,1163],{"class":309}," markdown_safe(frame)\n",[256,1165,1166,1169,1171,1174,1177,1180,1183,1186,1188,1191,1194,1197],{"class":215,"line":326},[256,1167,1168],{"class":309},"    header ",[256,1170,332],{"class":305},[256,1172,1173],{"class":265}," \"| \"",[256,1175,1176],{"class":305}," +",[256,1178,1179],{"class":265}," \" | \"",[256,1181,1182],{"class":309},".join(",[256,1184,1185],{"class":359},"map",[256,1187,795],{"class":309},[256,1189,1190],{"class":359},"str",[256,1192,1193],{"class":309},", safe.columns)) ",[256,1195,1196],{"class":305},"+",[256,1198,1199],{"class":265}," \" |\"\n",[256,1201,1202,1205,1208],{"class":215,"line":356},[256,1203,1204],{"class":305},"    def",[256,1206,1207],{"class":419}," is_number",[256,1209,1210],{"class":309},"(col):\n",[256,1212,1213,1216,1219,1222,1225],{"class":215,"line":479},[256,1214,1215],{"class":305},"        return",[256,1217,1218],{"class":309}," pd.api.types.is_numeric_dtype(frame[col]) ",[256,1220,1221],{"class":305},"and",[256,1223,1224],{"class":305}," not",[256,1226,1227],{"class":309}," pd.api.types.is_bool_dtype(frame[col])\n",[256,1229,1230,1233,1235,1238,1240,1242,1244,1247,1250,1253,1256,1259,1262,1265,1267,1270,1272],{"class":215,"line":494},[256,1231,1232],{"class":309},"    align ",[256,1234,332],{"class":305},[256,1236,1237],{"class":265}," \"|\"",[256,1239,1176],{"class":305},[256,1241,1237],{"class":265},[256,1243,1182],{"class":309},[256,1245,1246],{"class":265},"\"---:\"",[256,1248,1249],{"class":305}," if",[256,1251,1252],{"class":309}," is_number(c) ",[256,1254,1255],{"class":305},"else",[256,1257,1258],{"class":265}," \":---\"",[256,1260,1261],{"class":305}," for",[256,1263,1264],{"class":309}," c ",[256,1266,473],{"class":305},[256,1268,1269],{"class":309}," frame.columns) ",[256,1271,1196],{"class":305},[256,1273,1274],{"class":265}," \"|\"\n",[256,1276,1277,1280,1282,1284,1287,1289,1291,1293,1295,1298,1301,1304,1306,1309,1311,1314,1316,1319,1321,1324,1326,1328,1330],{"class":215,"line":528},[256,1278,1279],{"class":309},"    body ",[256,1281,332],{"class":305},[256,1283,939],{"class":309},[256,1285,1286],{"class":265},"\"| \"",[256,1288,1176],{"class":305},[256,1290,1179],{"class":265},[256,1292,1182],{"class":309},[256,1294,1190],{"class":359},[256,1296,1297],{"class":309},"(v) ",[256,1299,1300],{"class":305},"for",[256,1302,1303],{"class":309}," v ",[256,1305,473],{"class":305},[256,1307,1308],{"class":309}," row) ",[256,1310,1196],{"class":305},[256,1312,1313],{"class":265}," \" |\"",[256,1315,1261],{"class":305},[256,1317,1318],{"class":309}," row ",[256,1320,473],{"class":305},[256,1322,1323],{"class":309}," safe.itertuples(",[256,1325,366],{"class":344},[256,1327,332],{"class":305},[256,1329,371],{"class":359},[256,1331,1332],{"class":309},")]\n",[256,1334,1335,1337,1339,1341,1343,1346,1349],{"class":215,"line":556},[256,1336,585],{"class":305},[256,1338,1032],{"class":265},[256,1340,563],{"class":535},[256,1342,508],{"class":265},[256,1344,1345],{"class":309},".join([header, align, ",[256,1347,1348],{"class":305},"*",[256,1350,1351],{"class":309},"body])\n",[256,1353,1354],{"class":215,"line":582},[256,1355,323],{"emptyLinePlaceholder":322},[256,1357,1358,1360,1363,1365,1367,1369,1371,1374],{"class":215,"line":602},[256,1359,360],{"class":359},[256,1361,1362],{"class":309},"(to_pipe_table(pd.read_excel(",[256,1364,338],{"class":265},[256,1366,341],{"class":309},[256,1368,345],{"class":344},[256,1370,332],{"class":305},[256,1372,1373],{"class":265},"\"Stock\"",[256,1375,841],{"class":309},[10,1377,1378,1379,1382,1383,1386,1387,1390],{},"The ",[14,1380,1381],{},"is_number"," check excludes booleans on purpose: pandas counts a ",[14,1384,1385],{},"bool"," column as numeric, which would right-align the ",[14,1388,1389],{},"reorder"," flags. The columns are not padded to equal widths, which makes the raw text less tidy but renders identically — Markdown ignores the padding. Numbers print with Python's default formatting, so format them as strings first if the defaults are not what you want.",[25,1392,34,1397,34,1400,34,1403,34,1406,34,1410,34,1421,34,1427,34,1430,34,1435,34,1438,34,1443,34,1450,34,1456,34,1460,34,1464,34,1468,34,1471,34,1474,34,1481,34,1486,34,1489,34,1491,34,1495,34,1498,34,1503,34,1510,34,1515,34,1519],{"viewBox":1393,"role":28,"ariaLabelledBy":1394,"xmlns":32,"style":633},"4 0 752 142",[1395,1396],"md-pipeline-t","md-pipeline-d",[36,1398,1399],{"id":1395},"From workbook to publishable Markdown",[40,1401,1402],{"id":1396},"Four steps. Read every sheet with sheet_name=None. Make each DataFrame Markdown-safe by escaping pipes, converting newlines to \u003Cbr> and blanking missing values. Convert with to_markdown and number formats, or with the dependency-free function. Write one document with a heading per sheet.",[44,1404],{"x":198,"y":47,"width":642,"height":1405,"fill":50},"142",[44,1407],{"x":646,"y":647,"width":1408,"height":1409,"rx":134,"fill":57,"stroke":58,"style":221},"153.0","110.5",[1411,1412,1414],"g",{"transform":1413},"translate(84.5,37.8) scale(1.000)",[1415,1416],"path",{"d":1417,"fill":1418,"stroke":1419,"style":1420},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[65,1422,1426],{"x":1423,"y":1424,"style":1425},"96.5","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Read all sheets",[65,1428,870],{"x":1423,"y":1429,"style":681},"103.8",[215,1431],{"x1":1432,"y1":1433,"x2":1434,"y2":1433,"stroke":220,"style":221},"178.0","71.2","200.0",[223,1436],{"points":1437,"fill":226},"204.0,71.2 195.0,76.2 195.0,66.2",[44,1439],{"x":1440,"y":647,"width":1408,"height":1409,"rx":134,"fill":1441,"stroke":1442,"style":221},"209.0","#fdefd8","var(--gold,#b4740a)",[1411,1444,1446],{"transform":1445},"translate(273.5,29.0) scale(1.000)",[1415,1447],{"d":1448,"fill":1418,"stroke":1449,"style":1420},"M3 5h18l-7 8v6l-4 2v-8z","var(--gold-ink,#7a4e06)",[65,1451,1455],{"x":1452,"y":1453,"style":1454},"285.5","75.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Make cells safe",[65,1457,1459],{"x":1452,"y":1458,"style":681},"95.0","pipes, newlines,",[65,1461,1463],{"x":1452,"y":1462,"style":681},"112.5","blanks",[215,1465],{"x1":1466,"y1":1433,"x2":1467,"y2":1433,"stroke":220,"style":221},"367.0","389.0",[223,1469],{"points":1470,"fill":226},"393.0,71.2 384.0,76.2 384.0,66.2",[44,1472],{"x":1473,"y":647,"width":1408,"height":1409,"rx":134,"fill":653,"stroke":220,"style":221},"398.0",[1411,1475,1477],{"transform":1476},"translate(462.5,29.0) scale(1.000)",[1415,1478],{"d":1479,"fill":1418,"stroke":1480,"style":1420},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--brand-strong,#4338ca)",[65,1482,1485],{"x":1483,"y":1453,"style":1484},"474.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Convert",[65,1487,1488],{"x":1483,"y":1458,"style":681},"to_markdown with",[65,1490,749],{"x":1483,"y":1462,"style":681},[215,1492],{"x1":1493,"y1":1433,"x2":1494,"y2":1433,"stroke":220,"style":221},"556.0","578.0",[223,1496],{"points":1497,"fill":226},"582.0,71.2 573.0,76.2 573.0,66.2",[44,1499],{"x":1500,"y":647,"width":1408,"height":1409,"rx":134,"fill":1501,"stroke":1502,"style":221},"587.0","#d9f4f1","var(--teal,#0f9488)",[1411,1504,1506],{"transform":1505},"translate(651.5,29.0) scale(1.000)",[1415,1507],{"d":1508,"fill":1418,"stroke":1509,"style":1420},"M6 3h9l4 4v14H6z M15 3v4h4","var(--teal-ink,#0b6157)",[65,1511,1514],{"x":1512,"y":1453,"style":1513},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Write one",[65,1516,1518],{"x":1512,"y":1517,"style":1513},"93.0","document",[65,1520,1521],{"x":1512,"y":1462,"style":681},"## heading per sheet",[242,1523,1525],{"id":1524},"keep-a-readme-table-in-sync-with-its-workbook","Keep a README table in sync with its workbook",[10,1527,1528],{},"A common use is a table in a repository's README or docs that mirrors a workbook someone maintains — a price list, a list of supported regions, a results summary. Regenerate the Markdown in a scheduled job or a CI step, and write the file only when the content actually changed, so the repository history records real changes rather than a new commit every night:",[247,1530,1532],{"className":296,"code":1531,"language":298,"meta":252,"style":252},"def update_if_changed(md_text, target):\n    target = Path(target)\n    old = target.read_text(encoding=\"utf-8\") if target.exists() else None\n    if old == md_text:\n        return False\n    target.write_text(md_text, encoding=\"utf-8\")\n    return True\n\nprint(update_if_changed(workbook_to_markdown(\"summary.xlsx\", title=\"Weekly summary\"), \"summary.md\"))\n# False — identical to the file written in Step 4\n",[14,1533,1534,1544,1554,1584,1598,1605,1618,1625,1629,1652],{"__ignoreMap":252},[256,1535,1536,1538,1541],{"class":215,"line":258},[256,1537,416],{"class":305},[256,1539,1540],{"class":419}," update_if_changed",[256,1542,1543],{"class":309},"(md_text, target):\n",[256,1545,1546,1549,1551],{"class":215,"line":319},[256,1547,1548],{"class":309},"    target ",[256,1550,332],{"class":305},[256,1552,1553],{"class":309}," Path(target)\n",[256,1555,1556,1559,1561,1564,1566,1568,1570,1573,1576,1579,1581],{"class":215,"line":326},[256,1557,1558],{"class":309},"    old ",[256,1560,332],{"class":305},[256,1562,1563],{"class":309}," target.read_text(",[256,1565,1080],{"class":344},[256,1567,332],{"class":305},[256,1569,1085],{"class":265},[256,1571,1572],{"class":309},") ",[256,1574,1575],{"class":305},"if",[256,1577,1578],{"class":309}," target.exists() ",[256,1580,1255],{"class":305},[256,1582,1583],{"class":359}," None\n",[256,1585,1586,1589,1592,1595],{"class":215,"line":356},[256,1587,1588],{"class":305},"    if",[256,1590,1591],{"class":309}," old ",[256,1593,1594],{"class":305},"==",[256,1596,1597],{"class":309}," md_text:\n",[256,1599,1600,1602],{"class":215,"line":479},[256,1601,1215],{"class":305},[256,1603,1604],{"class":359}," False\n",[256,1606,1607,1610,1612,1614,1616],{"class":215,"line":494},[256,1608,1609],{"class":309},"    target.write_text(md_text, ",[256,1611,1080],{"class":344},[256,1613,332],{"class":305},[256,1615,1085],{"class":265},[256,1617,353],{"class":309},[256,1619,1620,1622],{"class":215,"line":528},[256,1621,585],{"class":305},[256,1623,1624],{"class":359}," True\n",[256,1626,1627],{"class":215,"line":556},[256,1628,323],{"emptyLinePlaceholder":322},[256,1630,1631,1633,1636,1638,1640,1642,1644,1646,1648,1650],{"class":215,"line":582},[256,1632,360],{"class":359},[256,1634,1635],{"class":309},"(update_if_changed(workbook_to_markdown(",[256,1637,338],{"class":265},[256,1639,341],{"class":309},[256,1641,36],{"class":344},[256,1643,332],{"class":305},[256,1645,1064],{"class":265},[256,1647,1021],{"class":309},[256,1649,1074],{"class":265},[256,1651,374],{"class":309},[256,1653,1654],{"class":215,"line":602},[256,1655,1657],{"class":1656},"s-wDw","# False — identical to the file written in Step 4\n",[10,1659,1660,1661,1664],{},"Because the conversion is deterministic — same workbook in, same text out — comparing strings is enough. A CI job can go one step further and fail when the committed Markdown no longer matches the workbook, which catches the case where someone edits the spreadsheet and forgets to regenerate the docs. Put a comment such as ",[14,1662,1663],{},"\u003C!-- generated from summary.xlsx, do not edit -->"," at the top of the file so nobody fixes a typo by hand in the Markdown and has it overwritten the next night.",[242,1666,1668],{"id":1667},"where-the-markdown-goes-next","Where the Markdown goes next",[10,1670,1671,1672,1676,1677,1681,1682,392],{},"A generated Markdown file is easiest to use when it lands where people read. A job can commit it to a repository's docs folder, post it to a chat channel as a message, or attach it to an issue. For chat tools, keep tables small — ten or so rows — and link to the full workbook; ",[19,1673,1675],{"href":1674},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fpost-an-excel-report-to-a-slack-channel-with-python\u002F","Post an Excel Report to a Slack Channel with Python"," shows a job that posts a summary and the file together. For a web page instead of Markdown, ",[19,1678,1680],{"href":1679},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-an-html-table\u002F","Convert an Excel Sheet to an HTML Table with Python"," covers ",[14,1683,1684],{},"to_html",[242,1686,1688],{"id":1687},"common-pitfalls","Common pitfalls",[1690,1691,1692,1702,1712,1720,1728,1742],"ul",{},[1693,1694,1695,1701],"li",{},[1696,1697,1698,1699,392],"strong",{},"Forgetting ",[14,1700,379],{}," A column of 0, 1, 2 appears on the left.",[1693,1703,1704,1707,1708,1711],{},[1696,1705,1706],{},"Unescaped pipes."," One ",[14,1709,1710],{},"|"," in a cell shifts every column after it.",[1693,1713,1714,1717,1718,392],{},[1696,1715,1716],{},"Raw newlines."," They end the row; use ",[14,1719,405],{},[1693,1721,1722,1727],{},[1696,1723,1724,1726],{},[14,1725,391],{}," in the output."," Fill missing values with empty strings.",[1693,1729,1730,1733,1734,1737,1738,1741],{},[1696,1731,1732],{},"Dates with a time attached."," A date column read from Excel prints in full ISO form with microseconds, ",[14,1735,1736],{},"2026-03-01T00:00:00.000000",", which nobody wants in a table. Format it first with ",[14,1739,1740],{},"df[col].dt.strftime(\"%Y-%m-%d\")"," — for Markdown, text is exactly what you want.",[1693,1743,1744,1747],{},[1696,1745,1746],{},"Huge tables."," Markdown renders thousands of rows slowly and nobody reads them; summarise first, and link to the full workbook for anyone who needs every row.",[242,1749,1751],{"id":1750},"performance-notes","Performance notes",[10,1753,1754,1756],{},[14,1755,280],{}," handles tens of thousands of rows in a second or two, far more than any Markdown viewer displays comfortably. The cleaning step is vectorised string work and adds little. If a table is too large to read, the fix is a summary — a pivot or a top-N list — rather than a faster converter.",[242,1758,1760],{"id":1759},"conclusion","Conclusion",[10,1762,1763,1766,1767,1769,1770,1772,1773,1775,1776,1778,1779,1781,1782,1784],{},[14,1764,1765],{},"df.to_markdown(index=False)"," converts a sheet to a Markdown table in one call, once the data is made safe for it: escape ",[14,1768,1710],{}," as ",[14,1771,512],{},", replace newlines with ",[14,1774,405],{},", and fill missing values with empty strings. Format numbers with ",[14,1777,749],{},", set alignment with ",[14,1780,757],{},", convert every sheet into one document with a heading per sheet, and fall back to a twelve-line function where ",[14,1783,284],{}," is not available.",[242,1786,1788],{"id":1787},"frequently-asked-questions","Frequently asked questions",[10,1790,1791,1794],{},[1696,1792,1793],{},"Why do I get \"Missing optional dependency 'tabulate'\"?","\nDataFrame.to_markdown uses the tabulate package to lay out the table, and pandas does not install it. Run pip install tabulate, or use the small dependency-free function on this page.",[10,1796,1797,1800],{},[1696,1798,1799],{},"Why does a pipe character in a cell break the table?","\nMarkdown uses | to separate columns, and to_markdown does not escape it. A cell containing \"South | East\" becomes two columns. Replace | with | in text cells before converting.",[10,1802,1803,1806,1807,1810],{},[1696,1804,1805],{},"How do I keep line breaks inside a cell?","\nA raw newline ends the Markdown row, so the table breaks. Replace newlines with ",[1808,1809],"br",{},", which GitHub, GitLab and most Markdown renderers accept inside table cells.",[10,1812,1813,1816],{},[1696,1814,1815],{},"Can I control the number format?","\nYes. Pass floatfmt to to_markdown — a single format such as \",.2f\" or a tuple with one format per column — or format the numbers as strings yourself before converting.",[242,1818,1820],{"id":1819},"related","Related",[1690,1822,1823,1829,1836,1846],{},[1693,1824,1825,1826,1828],{},"Up one level: ",[19,1827,22],{"href":21}," — every format Excel data moves through.",[1693,1830,1831,1835],{},[19,1832,1834],{"href":1833},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-json-with-python\u002F","Convert an Excel Sheet to JSON with Python"," — the machine-readable counterpart.",[1693,1837,1838,1842,1843,1845],{},[19,1839,1841],{"href":1840},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fread-all-sheets-from-an-excel-file-into-dataframes\u002F","Read All Sheets from an Excel File into DataFrames"," — the ",[14,1844,870],{}," pattern.",[1693,1847,1848,1852],{},[19,1849,1851],{"href":1850},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Femail-an-excel-report-with-an-html-summary-body\u002F","Email an Excel Report with an HTML Summary Body"," — another way to put a table in front of readers.",[1854,1855,1856],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":252,"searchDepth":319,"depth":319,"links":1858},[1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871],{"id":244,"depth":319,"text":245},{"id":292,"depth":319,"text":293},{"id":395,"depth":319,"text":396},{"id":743,"depth":319,"text":744},{"id":863,"depth":319,"text":864},{"id":1131,"depth":319,"text":1132},{"id":1524,"depth":319,"text":1525},{"id":1667,"depth":319,"text":1668},{"id":1687,"depth":319,"text":1688},{"id":1750,"depth":319,"text":1751},{"id":1759,"depth":319,"text":1760},{"id":1787,"depth":319,"text":1788},{"id":1819,"depth":319,"text":1820},"2026-09-25","Turn a worksheet into a Markdown table for a README, a wiki page, a pull request or a chat message. Use DataFrame.to_markdown, then fix what it gets wrong — unescaped pipes, line breaks inside cells, \"nan\" for blanks and raw float formatting — and convert every sheet of a workbook into one document.","md",[1876,1878,1880,1882],{"q":1793,"a":1877},"DataFrame.to_markdown uses the tabulate package to lay out the table, and pandas does not install it. Run pip install tabulate, or use the small dependency-free function on this page.",{"q":1799,"a":1879},"Markdown uses | to separate columns, and to_markdown does not escape it. A cell containing \"South | East\" becomes two columns. Replace | with \\| in text cells before converting.",{"q":1805,"a":1881},"A raw newline ends the Markdown row, so the table breaks. Replace newlines with \u003Cbr>, which GitHub, GitLab and most Markdown renderers accept inside table cells.",{"q":1815,"a":1883},"Yes. Pass floatfmt to to_markdown — a single format such as \",.2f\" or a tuple with one format per column — or format the numbers as strings yourself before converting.","convert an excel sheet to a markdown table",null,{"breadcrumb":1887},[1888,1891,1894],{"name":1889,"item":1890},"Home","\u002F",{"name":1892,"item":1893},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":22,"item":21},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-markdown-table",{"title":1897,"description":1898},"Excel to Markdown Table in Python (pandas to_markdown)","Convert Excel sheets to Markdown tables with pandas to_markdown and tabulate: escape pipes, handle newlines and blanks, format numbers, set column alignment and write all sheets to one Markdown file.","convert-an-excel-sheet-to-a-markdown-table","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-markdown-table\u002Findex",[1902,1903,1904],"file-formats","pandas","publishing","how-to","T7HJm-KlFQlYRR3haEgz5IMBPfis7CwX5M7fIo9mQ0g",[1908,1912],{"title":1909,"path":1910,"stem":1911,"children":-1},"Convert an Excel Sheet to a LaTeX Table with Python","\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-latex-table","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-a-latex-table\u002Findex",{"title":1680,"path":1913,"stem":1914,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-an-html-table","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-an-excel-sheet-to-an-html-table\u002Findex",[1916,1918],{"path":1917,"title":22},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions",{"path":1919,"title":1892},"\u002Fgetting-started-with-python-excel-automation",[],[1922,1925,1928,1932,1935,1939],{"path":1923,"title":1924,"kind":1905},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-excel-files-to-parquet-with-python","Convert Excel Files to Parquet with Python",{"path":1926,"title":1927,"kind":1905},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fconvert-excel-to-csv-with-python","Convert Excel to CSV with Python",{"path":1929,"title":1930,"kind":1931},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fexcel-vs-csv-vs-parquet-for-python-data-pipelines","Excel vs CSV vs Parquet for Python Data Pipelines","compare",{"path":1933,"title":1934,"kind":1931},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpick-an-excel-engine-for-xlsx-xlsm-xls-xlsb-and-ods","Pick an Excel Engine for .xlsx, .xlsm, .xls, .xlsb and .ods",{"path":1936,"title":1937,"kind":1938},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Ffix-garbled-characters-in-excel-data","Fix Garbled Characters Like \"CafÃ©\" and \"â€™\" in Excel Data","fix",{"path":1940,"title":1941,"kind":1905},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fextract-vba-macro-code-from-an-excel-file","Extract VBA Macro Code from an Excel File",1790972059050]