[{"data":1,"prerenderedAt":2332},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-apple-numbers-files-in-python":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-apple-numbers-files-in-python":2296,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-apple-numbers-files-in-python":2305,"children:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-apple-numbers-files-in-python":2310,"related:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-apple-numbers-files-in-python":2311},{"id":4,"title":5,"body":6,"dateModified":2263,"datePublished":2263,"description":2264,"extension":2265,"faq":2266,"keyword":2275,"kind":2276,"meta":2277,"navigation":133,"path":2285,"seo":2286,"slug":2289,"stem":2290,"topics":2291,"type":2294,"__hash__":2295},"docs\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-apple-numbers-files-in-python\u002Findex.md","Read Apple Numbers Files in Python",{"type":7,"value":8,"toc":2248},"minimark",[9,42,47,75,101,105,205,217,221,365,379,524,528,534,770,796,874,893,994,1289,1293,1296,1379,1415,1419,1426,1568,1575,1579,1588,1633,1676,1702,1832,1836,1841,2068,2086,2090,2135,2139,2147,2151,2183,2187,2193,2199,2205,2211,2215,2244],[10,11,12,13,17,18,21,22,25,26,29,30,32,33,35,36,41],"p",{},"Mac users send ",[14,15,16],"code",{},".numbers"," files the way Windows users send ",[14,19,20],{},".xlsx",", and a reporting script that only understands Excel stops at the first one. ",[14,23,24],{},"pd.read_excel"," does not read them — a Numbers document is not an Office Open XML package but a ZIP of Apple's own binary format — and asking the sender to export to Excel every time does not scale. The open-source ",[14,27,28],{},"numbers-parser"," package reads the format directly, on any operating system, without Numbers installed. This page shows how to get from a ",[14,31,16],{}," file to a clean DataFrame, what the document structure looks like (it differs from Excel's in one important way), how values and formats come through, and when converting to ",[14,34,20],{}," with LibreOffice is the simpler route. It belongs to ",[37,38,40],"a",{"href":39},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002F","Handling Excel File Formats and Conversions",".",[43,44,46],"h2",{"id":45},"prerequisites","Prerequisites",[48,49,54],"pre",{"className":50,"code":51,"language":52,"meta":53,"style":53},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install numbers-parser pandas\n","bash","",[14,55,56],{"__ignoreMap":53},[57,58,61,65,69,72],"span",{"class":59,"line":60},"line",1,[57,62,64],{"class":63},"sMTad","pip",[57,66,68],{"class":67},"srMev"," install",[57,70,71],{"class":67}," numbers-parser",[57,73,74],{"class":67}," pandas\n",[10,76,77,79,80,83,84,87,88,91,92,95,96,100],{},[14,78,28],{}," is a pure-Python reader and writer for documents saved by Apple Numbers; the examples were run with version 4.19 on Linux. The sample ",[14,81,82],{},"orders.numbers"," has a ",[14,85,86],{},"Sales"," sheet with an ",[14,89,90],{},"Orders"," table, and a ",[14,93,94],{},"Stock"," sheet with ",[97,98,99],"em",{},"two"," tables, one per warehouse.",[43,102,104],{"id":103},"step-1-why-pandas-cannot-read-it","Step 1 — Why pandas cannot read it",[48,106,110],{"className":107,"code":108,"language":109,"meta":53,"style":53},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ntry:\n    pd.read_excel(\"orders.numbers\")\nexcept Exception as exc:\n    print(type(exc).__name__, str(exc)[:80])\n","python",[14,111,112,128,135,144,156,172],{"__ignoreMap":53},[57,113,114,118,122,125],{"class":59,"line":60},[57,115,117],{"class":116},"s-kum","import",[57,119,121],{"class":120},"skGVy"," pandas ",[57,123,124],{"class":116},"as",[57,126,127],{"class":120}," pd\n",[57,129,131],{"class":59,"line":130},2,[57,132,134],{"emptyLinePlaceholder":133},true,"\n",[57,136,138,141],{"class":59,"line":137},3,[57,139,140],{"class":116},"try",[57,142,143],{"class":120},":\n",[57,145,147,150,153],{"class":59,"line":146},4,[57,148,149],{"class":120},"    pd.read_excel(",[57,151,152],{"class":67},"\"orders.numbers\"",[57,154,155],{"class":120},")\n",[57,157,159,162,166,169],{"class":59,"line":158},5,[57,160,161],{"class":116},"except",[57,163,165],{"class":164},"sP0c6"," Exception",[57,167,168],{"class":116}," as",[57,170,171],{"class":120}," exc:\n",[57,173,175,178,181,184,187,190,193,196,199,202],{"class":59,"line":174},6,[57,176,177],{"class":164},"    print",[57,179,180],{"class":120},"(",[57,182,183],{"class":164},"type",[57,185,186],{"class":120},"(exc).",[57,188,189],{"class":164},"__name__",[57,191,192],{"class":120},", ",[57,194,195],{"class":164},"str",[57,197,198],{"class":120},"(exc)[:",[57,200,201],{"class":164},"80",[57,203,204],{"class":120},"])\n",[10,206,207,208,211,212,216],{},"pandas inspects the file, sees a ZIP that contains none of the parts an Excel workbook has, falls back to the extension and fails with ",[14,209,210],{},"OptionError: No such keys(s): 'io.excel.zip.reader'"," — the same confusing message it gives for a plain ZIP archive, explained in ",[37,213,215],{"href":214},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python\u002F","Read Excel Files Inside a ZIP Archive with Python",". There is no engine to pass; a different library is needed.",[43,218,220],{"id":219},"step-2-sheets-contain-tables-not-cells","Step 2 — Sheets contain tables, not cells",[48,222,224],{"className":107,"code":223,"language":109,"meta":53,"style":53},"from numbers_parser import Document\n\ndoc = Document(\"orders.numbers\")\nfor sheet in doc.sheets:\n    for table in sheet.tables:\n        print(f\"{sheet.name:6} \u002F {table.name:12} {table.num_rows} rows x {table.num_cols} cols, \"\n              f\"header rows: {table.num_header_rows}\")\n",[14,225,226,239,243,258,272,285,345],{"__ignoreMap":53},[57,227,228,231,234,236],{"class":59,"line":60},[57,229,230],{"class":116},"from",[57,232,233],{"class":120}," numbers_parser ",[57,235,117],{"class":116},[57,237,238],{"class":120}," Document\n",[57,240,241],{"class":59,"line":130},[57,242,134],{"emptyLinePlaceholder":133},[57,244,245,248,251,254,256],{"class":59,"line":137},[57,246,247],{"class":120},"doc ",[57,249,250],{"class":116},"=",[57,252,253],{"class":120}," Document(",[57,255,152],{"class":67},[57,257,155],{"class":120},[57,259,260,263,266,269],{"class":59,"line":146},[57,261,262],{"class":116},"for",[57,264,265],{"class":120}," sheet ",[57,267,268],{"class":116},"in",[57,270,271],{"class":120}," doc.sheets:\n",[57,273,274,277,280,282],{"class":59,"line":158},[57,275,276],{"class":116},"    for",[57,278,279],{"class":120}," table ",[57,281,268],{"class":116},[57,283,284],{"class":120}," sheet.tables:\n",[57,286,287,290,292,295,298,302,305,308,311,314,316,319,322,324,327,330,332,335,337,340,342],{"class":59,"line":174},[57,288,289],{"class":164},"        print",[57,291,180],{"class":120},[57,293,294],{"class":116},"f",[57,296,297],{"class":67},"\"",[57,299,301],{"class":300},"sSjpA","{",[57,303,304],{"class":120},"sheet.name",[57,306,307],{"class":116},":6",[57,309,310],{"class":300},"}",[57,312,313],{"class":67}," \u002F ",[57,315,301],{"class":300},[57,317,318],{"class":120},"table.name",[57,320,321],{"class":116},":12",[57,323,310],{"class":300},[57,325,326],{"class":300}," {",[57,328,329],{"class":120},"table.num_rows",[57,331,310],{"class":300},[57,333,334],{"class":67}," rows x ",[57,336,301],{"class":300},[57,338,339],{"class":120},"table.num_cols",[57,341,310],{"class":300},[57,343,344],{"class":67}," cols, \"\n",[57,346,348,351,354,356,359,361,363],{"class":59,"line":347},7,[57,349,350],{"class":116},"              f",[57,352,353],{"class":67},"\"header rows: ",[57,355,301],{"class":300},[57,357,358],{"class":120},"table.num_header_rows",[57,360,310],{"class":300},[57,362,297],{"class":67},[57,364,155],{"class":120},[10,366,367,368,371,372,374,375,378],{},"This is the main structural difference from Excel. An Excel sheet ",[97,369,370],{},"is"," a grid; a Numbers sheet is a canvas holding any number of separate tables, each with its own name, size and header rows. The ",[14,373,94],{}," sheet holds two tables that would be two blocks on one Excel sheet. Address them by name — ",[14,376,377],{},"doc.sheets[\"Stock\"].tables[\"Warehouse B\"]"," — rather than by position, so a reordered canvas does not break the script.",[380,381,389,390,389,394,389,398,389,406,389,416,389,427,389,433,389,438,389,443,389,448,389,452,389,459,389,464,389,468,389,472,389,475,389,480,389,487,389,492,389,496,389,500,389,503,389,508,389,515,389,520],"svg",{"viewBox":382,"role":383,"ariaLabelledBy":384,"xmlns":387,"style":388},"84 0 592 512","img",[385,386],"nb-structure-t","nb-structure-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[391,392,393],"title",{"id":385},"A Numbers document is sheets that hold tables",[395,396,397],"desc",{"id":386},"The document orders.numbers contains two sheets. The Sales sheet holds one table named Orders. The Stock sheet holds two tables, Warehouse A and Warehouse B, each a separate grid with its own header row.",[399,400],"rect",{"x":401,"y":402,"width":403,"height":404,"fill":405},"84","0","592","512","#ffffff",[399,407],{"x":408,"y":409,"width":410,"height":411,"rx":412,"fill":413,"stroke":414,"style":415},"100.0","16.0","560.0","93.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[417,418,420],"g",{"transform":419},"translate(368.0,29.0) scale(1.000)",[421,422],"path",{"d":423,"fill":424,"stroke":425,"style":426},"M6 3h9l4 4v14H6z M15 3v4h4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[428,429,82],"text",{"x":430,"y":431,"style":432},"380.0","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[428,434,437],{"x":430,"y":435,"style":436},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Document",[59,439],{"x1":430,"y1":440,"x2":430,"y2":441,"stroke":442,"style":415},"114.0","136.0","var(--brand,#5b5cf0)",[444,445],"polygon",{"points":446,"fill":447},"380.0,140.0 375.0,131.0 385.0,131.0","#5b5cf0",[399,449],{"x":408,"y":450,"width":410,"height":411,"rx":412,"fill":451,"stroke":442,"style":415},"145.0","#f0f4ff",[417,453,455],{"transform":454},"translate(368.0,158.0) scale(1.000)",[421,456],{"d":457,"fill":424,"stroke":458,"style":426},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--brand-strong,#4338ca)",[428,460,463],{"x":430,"y":461,"style":462},"204.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Sheets: Sales, Stock",[428,465,467],{"x":430,"y":466,"style":436},"224.0","doc.sheets[\"Stock\"]",[59,469],{"x1":430,"y1":470,"x2":430,"y2":471,"stroke":442,"style":415},"243.0","265.0",[444,473],{"points":474,"fill":447},"380.0,269.0 375.0,260.0 385.0,260.0",[399,476],{"x":408,"y":477,"width":410,"height":411,"rx":412,"fill":478,"stroke":479,"style":415},"274.0","#d9f4f1","var(--teal,#0f9488)",[417,481,483],{"transform":482},"translate(368.0,287.0) scale(1.000)",[421,484],{"d":485,"fill":424,"stroke":486,"style":426},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--teal-ink,#0b6157)",[428,488,491],{"x":430,"y":489,"style":490},"333.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Tables: Orders · Warehouse A · Warehouse B",[428,493,495],{"x":430,"y":494,"style":436},"353.0","sheet.tables[\"Warehouse B\"]",[59,497],{"x1":430,"y1":498,"x2":430,"y2":499,"stroke":442,"style":415},"372.0","394.0",[444,501],{"points":502,"fill":447},"380.0,398.0 375.0,389.0 385.0,389.0",[399,504],{"x":408,"y":505,"width":410,"height":411,"rx":412,"fill":506,"stroke":507,"style":415},"403.0","#fdefd8","var(--gold,#b4740a)",[417,509,511],{"transform":510},"translate(368.0,416.0) scale(1.000)",[421,512],{"d":513,"fill":424,"stroke":514,"style":426},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--gold-ink,#7a4e06)",[428,516,519],{"x":430,"y":517,"style":518},"462.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Cells: NumberCell, TextCell, DateCell, BoolCell, EmptyCell",[428,521,523],{"x":430,"y":522,"style":436},"482.0","table.cell(row, col)",[43,525,527],{"id":526},"step-3-turn-a-table-into-a-dataframe","Step 3 — Turn a table into a DataFrame",[10,529,530,533],{},[14,531,532],{},"table.rows(values_only=True)"," returns the table as a list of lists of Python values. A table's grid is often larger than its data — new tables start at 12 rows by 8 columns, and people leave empty rows at the bottom — so trim the padding:",[48,535,537],{"className":107,"code":536,"language":109,"meta":53,"style":53},"def table_to_frame(table):\n    rows = table.rows(values_only=True)\n    header_rows = max(table.num_header_rows, 1)\n    df = pd.DataFrame(rows[header_rows:], columns=rows[header_rows - 1])\n    df = df.dropna(how=\"all\").dropna(axis=1, how=\"all\")\n    df.columns = [c if c is not None else f\"col_{i}\" for i, c in enumerate(df.columns)]\n    return df.reset_index(drop=True)\n\norders = table_to_frame(doc.sheets[\"Sales\"].tables[\"Orders\"])\nprint(orders)\nprint(orders.dtypes)\n",[14,538,539,551,572,590,616,653,709,726,731,753,762],{"__ignoreMap":53},[57,540,541,544,548],{"class":59,"line":60},[57,542,543],{"class":116},"def",[57,545,547],{"class":546},"s_Opv"," table_to_frame",[57,549,550],{"class":120},"(table):\n",[57,552,553,556,558,561,565,567,570],{"class":59,"line":130},[57,554,555],{"class":120},"    rows ",[57,557,250],{"class":116},[57,559,560],{"class":120}," table.rows(",[57,562,564],{"class":563},"sa561","values_only",[57,566,250],{"class":116},[57,568,569],{"class":164},"True",[57,571,155],{"class":120},[57,573,574,577,579,582,585,588],{"class":59,"line":137},[57,575,576],{"class":120},"    header_rows ",[57,578,250],{"class":116},[57,580,581],{"class":164}," max",[57,583,584],{"class":120},"(table.num_header_rows, ",[57,586,587],{"class":164},"1",[57,589,155],{"class":120},[57,591,592,595,597,600,603,605,608,611,614],{"class":59,"line":146},[57,593,594],{"class":120},"    df ",[57,596,250],{"class":116},[57,598,599],{"class":120}," pd.DataFrame(rows[header_rows:], ",[57,601,602],{"class":563},"columns",[57,604,250],{"class":116},[57,606,607],{"class":120},"rows[header_rows ",[57,609,610],{"class":116},"-",[57,612,613],{"class":164}," 1",[57,615,204],{"class":120},[57,617,618,620,622,625,628,630,633,636,639,641,643,645,647,649,651],{"class":59,"line":158},[57,619,594],{"class":120},[57,621,250],{"class":116},[57,623,624],{"class":120}," df.dropna(",[57,626,627],{"class":563},"how",[57,629,250],{"class":116},[57,631,632],{"class":67},"\"all\"",[57,634,635],{"class":120},").dropna(",[57,637,638],{"class":563},"axis",[57,640,250],{"class":116},[57,642,587],{"class":164},[57,644,192],{"class":120},[57,646,627],{"class":563},[57,648,250],{"class":116},[57,650,632],{"class":67},[57,652,155],{"class":120},[57,654,655,658,660,663,666,669,671,674,677,680,683,686,688,691,693,695,698,701,703,706],{"class":59,"line":174},[57,656,657],{"class":120},"    df.columns ",[57,659,250],{"class":116},[57,661,662],{"class":120}," [c ",[57,664,665],{"class":116},"if",[57,667,668],{"class":120}," c ",[57,670,370],{"class":116},[57,672,673],{"class":116}," not",[57,675,676],{"class":164}," None",[57,678,679],{"class":116}," else",[57,681,682],{"class":116}," f",[57,684,685],{"class":67},"\"col_",[57,687,301],{"class":300},[57,689,690],{"class":120},"i",[57,692,310],{"class":300},[57,694,297],{"class":67},[57,696,697],{"class":116}," for",[57,699,700],{"class":120}," i, c ",[57,702,268],{"class":116},[57,704,705],{"class":164}," enumerate",[57,707,708],{"class":120},"(df.columns)]\n",[57,710,711,714,717,720,722,724],{"class":59,"line":347},[57,712,713],{"class":116},"    return",[57,715,716],{"class":120}," df.reset_index(",[57,718,719],{"class":563},"drop",[57,721,250],{"class":116},[57,723,569],{"class":164},[57,725,155],{"class":120},[57,727,729],{"class":59,"line":728},8,[57,730,134],{"emptyLinePlaceholder":133},[57,732,734,737,739,742,745,748,751],{"class":59,"line":733},9,[57,735,736],{"class":120},"orders ",[57,738,250],{"class":116},[57,740,741],{"class":120}," table_to_frame(doc.sheets[",[57,743,744],{"class":67},"\"Sales\"",[57,746,747],{"class":120},"].tables[",[57,749,750],{"class":67},"\"Orders\"",[57,752,204],{"class":120},[57,754,756,759],{"class":59,"line":755},10,[57,757,758],{"class":164},"print",[57,760,761],{"class":120},"(orders)\n",[57,763,765,767],{"class":59,"line":764},11,[57,766,758],{"class":164},[57,768,769],{"class":120},"(orders.dtypes)\n",[10,771,772,773,776,777,780,781,784,785,788,789,792,793,795],{},"The raw rows include five unused columns and seven unused rows filled with ",[14,774,775],{},"None","; dropping all-empty rows and columns leaves the four orders. The last header row becomes the column names. Notice the types: Numbers stores every number as a decimal, so ",[14,778,779],{},"Qty"," arrives as ",[14,782,783],{},"3.0"," rather than ",[14,786,787],{},"3",", dates arrive as ",[14,790,791],{},"datetime"," objects, and the TRUE\u002FFALSE checkboxes as booleans — with ",[14,794,775],{}," for the empty Rake row. Whole numbers need one more step than you would expect:",[48,797,799],{"className":107,"code":798,"language":109,"meta":53,"style":53},"print([float(q).hex() for q in orders[\"Qty\"]])\ntry:\n    orders[\"Qty\"].astype(\"Int64\")\nexcept TypeError as exc:\n    print(\"TypeError:\", exc)\n",[14,800,801,830,836,851,862],{"__ignoreMap":53},[57,802,803,805,808,811,814,816,819,821,824,827],{"class":59,"line":60},[57,804,758],{"class":164},[57,806,807],{"class":120},"([",[57,809,810],{"class":164},"float",[57,812,813],{"class":120},"(q).hex() ",[57,815,262],{"class":116},[57,817,818],{"class":120}," q ",[57,820,268],{"class":116},[57,822,823],{"class":120}," orders[",[57,825,826],{"class":67},"\"Qty\"",[57,828,829],{"class":120},"]])\n",[57,831,832,834],{"class":59,"line":130},[57,833,140],{"class":116},[57,835,143],{"class":120},[57,837,838,841,843,846,849],{"class":59,"line":137},[57,839,840],{"class":120},"    orders[",[57,842,826],{"class":67},[57,844,845],{"class":120},"].astype(",[57,847,848],{"class":67},"\"Int64\"",[57,850,155],{"class":120},[57,852,853,855,858,860],{"class":59,"line":146},[57,854,161],{"class":116},[57,856,857],{"class":164}," TypeError",[57,859,168],{"class":116},[57,861,171],{"class":120},[57,863,864,866,868,871],{"class":59,"line":158},[57,865,177],{"class":164},[57,867,180],{"class":120},[57,869,870],{"class":67},"\"TypeError:\"",[57,872,873],{"class":120},", exc)\n",[10,875,876,877,880,881,884,885,888,889,892],{},"The quantity 12 comes back as ",[14,878,879],{},"12.000000000000002"," — the hex form ",[14,882,883],{},"0x1.8000000000001p+3"," shows the extra bit. Numbers keeps values in a 128-bit decimal format, and the conversion to a Python float is not always exact, so pandas refuses the cast with ",[14,886,887],{},"cannot safely cast non-equivalent object to int64",". The column prints as ",[14,890,891],{},"12.0",", which hides the problem until the cast or an equality test fails. Round before casting:",[48,894,896],{"className":107,"code":895,"language":109,"meta":53,"style":53},"orders = orders.astype({\"Shipped\": \"boolean\"})\norders[\"Qty\"] = orders[\"Qty\"].round().astype(\"Int64\")\norders[\"Total\"] = orders[\"Qty\"] * orders[\"Unit price\"]\nprint(orders[[\"Product\", \"Qty\", \"Shipped\", \"Total\"]])\n",[14,897,898,919,942,970],{"__ignoreMap":53},[57,899,900,902,904,907,910,913,916],{"class":59,"line":60},[57,901,736],{"class":120},[57,903,250],{"class":116},[57,905,906],{"class":120}," orders.astype({",[57,908,909],{"class":67},"\"Shipped\"",[57,911,912],{"class":120},": ",[57,914,915],{"class":67},"\"boolean\"",[57,917,918],{"class":120},"})\n",[57,920,921,924,926,929,931,933,935,938,940],{"class":59,"line":130},[57,922,923],{"class":120},"orders[",[57,925,826],{"class":67},[57,927,928],{"class":120},"] ",[57,930,250],{"class":116},[57,932,823],{"class":120},[57,934,826],{"class":67},[57,936,937],{"class":120},"].round().astype(",[57,939,848],{"class":67},[57,941,155],{"class":120},[57,943,944,946,949,951,953,955,957,959,962,964,967],{"class":59,"line":137},[57,945,923],{"class":120},[57,947,948],{"class":67},"\"Total\"",[57,950,928],{"class":120},[57,952,250],{"class":116},[57,954,823],{"class":120},[57,956,826],{"class":67},[57,958,928],{"class":120},[57,960,961],{"class":116},"*",[57,963,823],{"class":120},[57,965,966],{"class":67},"\"Unit price\"",[57,968,969],{"class":120},"]\n",[57,971,972,974,977,980,982,984,986,988,990,992],{"class":59,"line":146},[57,973,758],{"class":164},[57,975,976],{"class":120},"(orders[[",[57,978,979],{"class":67},"\"Product\"",[57,981,192],{"class":120},[57,983,826],{"class":67},[57,985,192],{"class":120},[57,987,909],{"class":67},[57,989,192],{"class":120},[57,991,948],{"class":67},[57,993,829],{"class":120},[380,995,389,1001,389,1004,389,1007,389,1012,389,1019,389,1023,389,1029,389,1033,389,1037,389,1041,389,1045,389,1048,389,1052,389,1055,389,1059,389,1063,389,1067,389,1070,389,1074,389,1076,389,1078,389,1080,389,1083,389,1085,389,1087,389,1090,389,1094,389,1096,389,1100,389,1102,389,1104,389,1106,389,1109,389,1111,389,1113,389,1116,389,1119,389,1121,389,1124,389,1126,389,1129,389,1131,389,1134,389,1136,389,1138,389,1141,389,1145,389,1147,389,1150,389,1152,389,1155,389,1157,389,1160,389,1162,389,1164,389,1167,389,1171,389,1173,389,1175,389,1177,389,1179,389,1181,389,1184,389,1188,389,1190,389,1192,389,1194,389,1196,389,1198,389,1201,389,1205,389,1208,389,1211,389,1214,389,1217,389,1220,389,1223,389,1225,389,1227,389,1229,389,1231,389,1234,389,1237,389,1239,389,1241,389,1243,389,1245,389,1248,389,1251,389,1253,389,1255,389,1257,389,1259,389,1265,389,1269,389,1273,389,1276,389,1283],{"viewBox":996,"role":383,"ariaLabelledBy":997,"xmlns":387,"style":1000},"16 0 727 248",[998,999],"nb-frame-t","nb-frame-d","width:100%;max-width:727px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[391,1002,1003],{"id":998},"The padded table grid becomes a tidy DataFrame",[395,1005,1006],{"id":999},"On the left the Orders table as a 12 by 8 grid with data in the top five rows and first five columns and empty cells elsewhere. On the right the DataFrame after dropping all-empty rows and columns, with four orders and five columns.",[399,1008],{"x":1009,"y":402,"width":1010,"height":1011,"fill":405},"16","727","248",[399,1013],{"x":1014,"y":1015,"width":1016,"height":1017,"rx":402,"fill":413,"stroke":414,"style":1018},"32.6","40.0","32.0","24.0","stroke-width:1px",[399,1020],{"x":1021,"y":1015,"width":1022,"height":1017,"rx":402,"fill":413,"stroke":414,"style":1018},"64.6","64.7",[428,1024,1028],{"x":1025,"y":1026,"style":1027},"96.9","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[399,1030],{"x":1031,"y":1015,"width":1032,"height":1017,"rx":402,"fill":413,"stroke":414,"style":1018},"129.2","44.0",[428,1034,1036],{"x":1035,"y":1026,"style":1027},"151.2","B",[399,1038],{"x":1039,"y":1015,"width":1040,"height":1017,"rx":402,"fill":413,"stroke":414,"style":1018},"173.2","73.6",[428,1042,1044],{"x":1043,"y":1026,"style":1027},"210.0","C",[399,1046],{"x":1047,"y":1015,"width":1032,"height":1017,"rx":402,"fill":413,"stroke":414,"style":1018},"246.8",[428,1049,1051],{"x":1050,"y":1026,"style":1027},"268.8","D",[399,1053],{"x":1054,"y":1015,"width":1032,"height":1017,"rx":402,"fill":413,"stroke":414,"style":1018},"290.8",[428,1056,1058],{"x":1057,"y":1026,"style":1027},"312.8","E",[399,1060],{"x":1014,"y":1061,"width":1016,"height":1062,"rx":402,"fill":413,"stroke":414,"style":1018},"64.0","28.0",[428,1064,587],{"x":1065,"y":1066,"style":1027},"48.6","83.0",[399,1068],{"x":1021,"y":1061,"width":1022,"height":1062,"rx":402,"fill":451,"stroke":442,"style":1069},"stroke-width:1.5px",[428,1071,1073],{"x":1025,"y":1066,"style":1072},"font-size:10px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Product",[399,1075],{"x":1031,"y":1061,"width":1032,"height":1062,"rx":402,"fill":451,"stroke":442,"style":1069},[428,1077,779],{"x":1035,"y":1066,"style":1072},[399,1079],{"x":1039,"y":1061,"width":1040,"height":1062,"rx":402,"fill":451,"stroke":442,"style":1069},[428,1081,1082],{"x":1043,"y":1066,"style":1072},"Unit price",[399,1084],{"x":1047,"y":1061,"width":1032,"height":1062,"rx":402,"fill":451,"stroke":442,"style":1069},[399,1086],{"x":1054,"y":1061,"width":1032,"height":1062,"rx":402,"fill":451,"stroke":442,"style":1069},[399,1088],{"x":1014,"y":1089,"width":1016,"height":1062,"rx":402,"fill":413,"stroke":414,"style":1018},"92.0",[428,1091,1093],{"x":1065,"y":1092,"style":1027},"111.0","2",[399,1095],{"x":1021,"y":1089,"width":1022,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1097,1099],{"x":1025,"y":1092,"style":1098},"font-size:10px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Drill",[399,1101],{"x":1031,"y":1089,"width":1032,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1103,783],{"x":1035,"y":1092,"style":1098},[399,1105],{"x":1039,"y":1089,"width":1040,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1107,1108],{"x":1043,"y":1092,"style":1098},"89.0",[399,1110],{"x":1047,"y":1089,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1112],{"x":1054,"y":1089,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1114],{"x":1014,"y":1115,"width":1016,"height":1062,"rx":402,"fill":413,"stroke":414,"style":1018},"120.0",[428,1117,787],{"x":1065,"y":1118,"style":1027},"139.0",[399,1120],{"x":1021,"y":1115,"width":1022,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1122,1123],{"x":1025,"y":1118,"style":1098},"Saw",[399,1125],{"x":1031,"y":1115,"width":1032,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1127,1128],{"x":1035,"y":1118,"style":1098},"5.0",[399,1130],{"x":1039,"y":1115,"width":1040,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1132,1133],{"x":1043,"y":1118,"style":1098},"45.5",[399,1135],{"x":1047,"y":1115,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1137],{"x":1054,"y":1115,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1139],{"x":1014,"y":1140,"width":1016,"height":1062,"rx":402,"fill":413,"stroke":414,"style":1018},"148.0",[428,1142,1144],{"x":1065,"y":1143,"style":1027},"167.0","4",[399,1146],{"x":1021,"y":1140,"width":1022,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1148,1149],{"x":1025,"y":1143,"style":1098},"Rake",[399,1151],{"x":1031,"y":1140,"width":1032,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1153,1154],{"x":1035,"y":1143,"style":1098},"4.0",[399,1156],{"x":1039,"y":1140,"width":1040,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1158,1159],{"x":1043,"y":1143,"style":1098},"22.0",[399,1161],{"x":1047,"y":1140,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1163],{"x":1054,"y":1140,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1165],{"x":1014,"y":1166,"width":1016,"height":1062,"rx":402,"fill":413,"stroke":414,"style":1018},"176.0",[428,1168,1170],{"x":1065,"y":1169,"style":1027},"195.0","5",[399,1172],{"x":1021,"y":1166,"width":1022,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1174],{"x":1031,"y":1166,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1176],{"x":1039,"y":1166,"width":1040,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1178],{"x":1047,"y":1166,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1180],{"x":1054,"y":1166,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1182],{"x":1014,"y":1183,"width":1016,"height":1062,"rx":402,"fill":413,"stroke":414,"style":1018},"204.0",[428,1185,1187],{"x":1065,"y":1186,"style":1027},"223.0","6",[399,1189],{"x":1021,"y":1183,"width":1022,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1191],{"x":1031,"y":1183,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1193],{"x":1039,"y":1183,"width":1040,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1195],{"x":1047,"y":1183,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1197],{"x":1054,"y":1183,"width":1032,"height":1062,"rx":402,"fill":506,"stroke":507,"style":1069},[399,1199],{"x":1200,"y":1015,"width":1022,"height":1062,"rx":402,"fill":451,"stroke":442,"style":1069},"545.2",[428,1202,1073],{"x":1203,"y":1204,"style":1072},"577.5","59.0",[399,1206],{"x":1207,"y":1015,"width":1032,"height":1062,"rx":402,"fill":451,"stroke":442,"style":1069},"609.9",[428,1209,779],{"x":1210,"y":1204,"style":1072},"631.9",[399,1212],{"x":1213,"y":1015,"width":1040,"height":1062,"rx":402,"fill":451,"stroke":442,"style":1069},"653.9",[428,1215,1082],{"x":1216,"y":1204,"style":1072},"690.7",[399,1218],{"x":1200,"y":1219,"width":1022,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},"68.0",[428,1221,1099],{"x":1203,"y":1222,"style":1098},"87.0",[399,1224],{"x":1207,"y":1219,"width":1032,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1226,787],{"x":1210,"y":1222,"style":1098},[399,1228],{"x":1213,"y":1219,"width":1040,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1230,1108],{"x":1216,"y":1222,"style":1098},[399,1232],{"x":1200,"y":1233,"width":1022,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},"96.0",[428,1235,1123],{"x":1203,"y":1236,"style":1098},"115.0",[399,1238],{"x":1207,"y":1233,"width":1032,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1240,1170],{"x":1210,"y":1236,"style":1098},[399,1242],{"x":1213,"y":1233,"width":1040,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1244,1133],{"x":1216,"y":1236,"style":1098},[399,1246],{"x":1200,"y":1247,"width":1022,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},"124.0",[428,1249,1149],{"x":1203,"y":1250,"style":1098},"143.0",[399,1252],{"x":1207,"y":1247,"width":1032,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1254,1144],{"x":1210,"y":1250,"style":1098},[399,1256],{"x":1213,"y":1247,"width":1040,"height":1062,"rx":402,"fill":405,"stroke":414,"style":1018},[428,1258,1159],{"x":1216,"y":1250,"style":1098},[428,1260,1264],{"x":1261,"y":1262,"style":1263},"183.7","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","Numbers table grid",[428,1266,1268],{"x":1267,"y":1262,"style":1263},"636.3","DataFrame",[59,1270],{"x1":1271,"y1":1233,"x2":1272,"y2":1233,"stroke":442,"style":415},"344.8","531.2",[444,1274],{"points":1275,"fill":447},"535.2,96.0 526.2,101.0 526.2,91.0",[399,1277],{"x":1278,"y":1279,"width":1280,"height":1159,"rx":1281,"fill":405,"stroke":414,"style":1282},"371.8","63.0","136.3","11","stroke-width:1.2px",[428,1284,1288],{"x":1285,"y":1286,"style":1287},"440.0","78.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","table_to_frame()",[43,1290,1292],{"id":1291},"step-4-values-versus-formatted-text","Step 4 — Values versus formatted text",[10,1294,1295],{},"Each cell object carries both the stored value and the text Numbers would display:",[48,1297,1299],{"className":107,"code":1298,"language":109,"meta":53,"style":53},"price = doc.sheets[\"Sales\"].tables[\"Orders\"].cell(1, 2)\nprint(type(price).__name__, price.value, repr(price.formatted_value))\nprint(type(doc.sheets[\"Sales\"].tables[\"Orders\"].cell(\"D2\")).__name__)\n",[14,1300,1301,1328,1350],{"__ignoreMap":53},[57,1302,1303,1306,1308,1311,1313,1315,1317,1320,1322,1324,1326],{"class":59,"line":60},[57,1304,1305],{"class":120},"price ",[57,1307,250],{"class":116},[57,1309,1310],{"class":120}," doc.sheets[",[57,1312,744],{"class":67},[57,1314,747],{"class":120},[57,1316,750],{"class":67},[57,1318,1319],{"class":120},"].cell(",[57,1321,587],{"class":164},[57,1323,192],{"class":120},[57,1325,1093],{"class":164},[57,1327,155],{"class":120},[57,1329,1330,1332,1334,1336,1339,1341,1344,1347],{"class":59,"line":130},[57,1331,758],{"class":164},[57,1333,180],{"class":120},[57,1335,183],{"class":164},[57,1337,1338],{"class":120},"(price).",[57,1340,189],{"class":164},[57,1342,1343],{"class":120},", price.value, ",[57,1345,1346],{"class":164},"repr",[57,1348,1349],{"class":120},"(price.formatted_value))\n",[57,1351,1352,1354,1356,1358,1361,1363,1365,1367,1369,1372,1375,1377],{"class":59,"line":137},[57,1353,758],{"class":164},[57,1355,180],{"class":120},[57,1357,183],{"class":164},[57,1359,1360],{"class":120},"(doc.sheets[",[57,1362,744],{"class":67},[57,1364,747],{"class":120},[57,1366,750],{"class":67},[57,1368,1319],{"class":120},[57,1370,1371],{"class":67},"\"D2\"",[57,1373,1374],{"class":120},")).",[57,1376,189],{"class":164},[57,1378,155],{"class":120},[10,1380,1381,1382,1385,1386,1388,1389,1392,1393,1396,1397,1400,1401,1403,1404,1407,1408,1411,1412,1414],{},"The price cell is a ",[14,1383,1384],{},"NumberCell"," with value ",[14,1387,1108],{}," and formatted value ",[14,1390,1391],{},"'€89.00'",", because the column has a euro currency format. Use ",[14,1394,1395],{},"value"," for calculations and ",[14,1398,1399],{},"formatted_value"," when a report must reproduce exactly what the sender saw. Cells can be addressed by row and column numbers (zero-based) or by Excel-style references such as ",[14,1402,1371],{},". Cells with formulas report ",[14,1405,1406],{},"is_formula=True"," and expose the formula text through ",[14,1409,1410],{},"formula","; their ",[14,1413,1395],{}," is the result Numbers last calculated.",[43,1416,1418],{"id":1417},"step-5-read-every-table-at-once","Step 5 — Read every table at once",[10,1420,1421,1422,1425],{},"To load a whole document, build a dictionary keyed by sheet and table name — the Numbers equivalent of ",[14,1423,1424],{},"pd.read_excel(path, sheet_name=None)",":",[48,1427,1429],{"className":107,"code":1428,"language":109,"meta":53,"style":53},"frames = {f\"{s.name}\u002F{t.name}\": table_to_frame(t) for s in doc.sheets for t in s.tables}\nfor key, df in frames.items():\n    print(key, df.shape)\nstock = pd.concat([frames[\"Stock\u002FWarehouse A\"].assign(Warehouse=\"A\"),\n                   frames[\"Stock\u002FWarehouse B\"].assign(Warehouse=\"B\")], ignore_index=True)\nprint(stock)\n",[14,1430,1431,1486,1498,1505,1532,1561],{"__ignoreMap":53},[57,1432,1433,1436,1438,1440,1442,1444,1446,1449,1451,1454,1456,1459,1461,1463,1466,1468,1471,1473,1476,1478,1481,1483],{"class":59,"line":60},[57,1434,1435],{"class":120},"frames ",[57,1437,250],{"class":116},[57,1439,326],{"class":120},[57,1441,294],{"class":116},[57,1443,297],{"class":67},[57,1445,301],{"class":300},[57,1447,1448],{"class":120},"s.name",[57,1450,310],{"class":300},[57,1452,1453],{"class":67},"\u002F",[57,1455,301],{"class":300},[57,1457,1458],{"class":120},"t.name",[57,1460,310],{"class":300},[57,1462,297],{"class":67},[57,1464,1465],{"class":120},": table_to_frame(t) ",[57,1467,262],{"class":116},[57,1469,1470],{"class":120}," s ",[57,1472,268],{"class":116},[57,1474,1475],{"class":120}," doc.sheets ",[57,1477,262],{"class":116},[57,1479,1480],{"class":120}," t ",[57,1482,268],{"class":116},[57,1484,1485],{"class":120}," s.tables}\n",[57,1487,1488,1490,1493,1495],{"class":59,"line":130},[57,1489,262],{"class":116},[57,1491,1492],{"class":120}," key, df ",[57,1494,268],{"class":116},[57,1496,1497],{"class":120}," frames.items():\n",[57,1499,1500,1502],{"class":59,"line":137},[57,1501,177],{"class":164},[57,1503,1504],{"class":120},"(key, df.shape)\n",[57,1506,1507,1510,1512,1515,1518,1521,1524,1526,1529],{"class":59,"line":146},[57,1508,1509],{"class":120},"stock ",[57,1511,250],{"class":116},[57,1513,1514],{"class":120}," pd.concat([frames[",[57,1516,1517],{"class":67},"\"Stock\u002FWarehouse A\"",[57,1519,1520],{"class":120},"].assign(",[57,1522,1523],{"class":563},"Warehouse",[57,1525,250],{"class":116},[57,1527,1528],{"class":67},"\"A\"",[57,1530,1531],{"class":120},"),\n",[57,1533,1534,1537,1540,1542,1544,1546,1549,1552,1555,1557,1559],{"class":59,"line":158},[57,1535,1536],{"class":120},"                   frames[",[57,1538,1539],{"class":67},"\"Stock\u002FWarehouse B\"",[57,1541,1520],{"class":120},[57,1543,1523],{"class":563},[57,1545,250],{"class":116},[57,1547,1548],{"class":67},"\"B\"",[57,1550,1551],{"class":120},")], ",[57,1553,1554],{"class":563},"ignore_index",[57,1556,250],{"class":116},[57,1558,569],{"class":164},[57,1560,155],{"class":120},[57,1562,1563,1565],{"class":59,"line":174},[57,1564,758],{"class":164},[57,1566,1567],{"class":120},"(stock)\n",[10,1569,1570,1571,41],{},"The two warehouse tables share a layout, so they stack into one stock list with a column recording which table each row came from — the same idea as combining several Excel sheets, described in ",[37,1572,1574],{"href":1573},"\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",[43,1576,1578],{"id":1577},"step-6-convert-the-whole-document-to-xlsx","Step 6 — Convert the whole document to .xlsx",[10,1580,1581,1582,1584,1585,1425],{},"When the rest of the pipeline expects Excel files, LibreOffice can convert ",[14,1583,16],{}," documents through its Apple import filter, and the result reads with ordinary ",[14,1586,1587],{},"read_excel",[48,1589,1591],{"className":50,"code":1590,"language":52,"meta":53,"style":53},"soffice --headless --convert-to xlsx --outdir converted orders.numbers > \u002Fdev\u002Fnull 2>&1\nls converted\n",[14,1592,1593,1625],{"__ignoreMap":53},[57,1594,1595,1598,1601,1604,1607,1610,1613,1616,1619,1622],{"class":59,"line":60},[57,1596,1597],{"class":63},"soffice",[57,1599,1600],{"class":164}," --headless",[57,1602,1603],{"class":164}," --convert-to",[57,1605,1606],{"class":67}," xlsx",[57,1608,1609],{"class":164}," --outdir",[57,1611,1612],{"class":67}," converted",[57,1614,1615],{"class":67}," orders.numbers",[57,1617,1618],{"class":116}," >",[57,1620,1621],{"class":67}," \u002Fdev\u002Fnull",[57,1623,1624],{"class":116}," 2>&1\n",[57,1626,1627,1630],{"class":59,"line":130},[57,1628,1629],{"class":63},"ls",[57,1631,1632],{"class":67}," converted\n",[48,1634,1636],{"className":107,"code":1635,"language":109,"meta":53,"style":53},"for name, df in pd.read_excel(\"converted\u002Forders.xlsx\", sheet_name=None).items():\n    print(repr(name), df.shape)\n",[14,1637,1638,1665],{"__ignoreMap":53},[57,1639,1640,1642,1645,1647,1650,1653,1655,1658,1660,1662],{"class":59,"line":60},[57,1641,262],{"class":116},[57,1643,1644],{"class":120}," name, df ",[57,1646,268],{"class":116},[57,1648,1649],{"class":120}," pd.read_excel(",[57,1651,1652],{"class":67},"\"converted\u002Forders.xlsx\"",[57,1654,192],{"class":120},[57,1656,1657],{"class":563},"sheet_name",[57,1659,250],{"class":116},[57,1661,775],{"class":164},[57,1663,1664],{"class":120},").items():\n",[57,1666,1667,1669,1671,1673],{"class":59,"line":130},[57,1668,177],{"class":164},[57,1670,180],{"class":120},[57,1672,1346],{"class":164},[57,1674,1675],{"class":120},"(name), df.shape)\n",[10,1677,1678,1679,1682,1683,192,1686,1689,1690,1693,1694,1698,1699,1701],{},"Each Numbers table becomes its own Excel sheet, named ",[14,1680,1681],{},"Sheet_Table"," — here ",[14,1684,1685],{},"'Sales_Orders'",[14,1687,1688],{},"'Stock_Warehouse A'"," and ",[14,1691,1692],{},"'Stock_Warehouse B'"," — because Excel has no concept of several free-floating tables per sheet. Conversion is handy for batch jobs that feed existing Excel-based code, at the cost of a LibreOffice dependency and a few seconds per file; see ",[37,1695,1697],{"href":1696},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-to-pdf-on-linux-with-libreoffice\u002F","Convert Excel to PDF on Linux with LibreOffice"," for running ",[14,1700,1597],{}," reliably on a server.",[380,1703,389,1709,389,1712,389,1715,389,1719,389,1723,389,1728,389,1731,389,1735,389,1739,389,1742,389,1745,389,1752,389,1756,389,1762,389,1768,389,1771,389,1775,389,1777,389,1780,389,1784,389,1787,389,1790,389,1796,389,1799,389,1803,389,1806,389,1808,389,1811,389,1815,389,1818,389,1821,389,1826,389,1829],{"viewBox":1704,"role":383,"ariaLabelledBy":1705,"xmlns":387,"style":1708},"4 0 752 290",[1706,1707],"nb-route-t","nb-route-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[391,1710,1711],{"id":1706},"Read directly or convert first?",[395,1713,1714],{"id":1707},"If the script only needs the data, read the .numbers file directly with numbers-parser. If existing code expects .xlsx files, convert with LibreOffice and use read_excel. If the reply must go back as a Numbers file, write it with numbers-parser.",[399,1716],{"x":1144,"y":402,"width":1717,"height":1718,"fill":405},"752","290",[399,1720],{"x":1721,"y":409,"width":1722,"height":431,"rx":412,"fill":451,"stroke":442,"style":415},"227.4","305.2",[417,1724,1725],{"transform":419},[421,1726],{"d":1727,"fill":424,"stroke":458,"style":426},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5",[428,1729,1730],{"x":430,"y":431,"style":462},"What does the rest of the job need?",[59,1732],{"x1":430,"y1":1733,"x2":430,"y2":1734,"stroke":442,"style":415},"91.5","111.5",[59,1736],{"x1":1737,"y1":1734,"x2":1738,"y2":1734,"stroke":442,"style":415},"134.7","625.3",[59,1740],{"x1":1737,"y1":1734,"x2":1737,"y2":1741,"stroke":442,"style":415},"157.5",[444,1743],{"points":1744,"fill":447},"134.7,161.5 129.7,152.5 139.7,152.5",[399,1746],{"x":1747,"y":1748,"width":1749,"height":1750,"rx":1751,"fill":405,"stroke":414,"style":1282},"89.2","121.5","90.9","21.0","10",[428,1753,1755],{"x":1737,"y":441,"style":1754},"font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","just the data",[399,1757],{"x":1758,"y":1759,"width":1760,"height":1761,"rx":412,"fill":478,"stroke":479,"style":415},"20.0","163.5","229.3","110.5",[417,1763,1765],{"transform":1764},"translate(122.7,185.2) scale(1.000)",[421,1766],{"d":1767,"fill":424,"stroke":486,"style":426},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01",[428,1769,28],{"x":1737,"y":1770,"style":490},"231.8",[428,1772,1774],{"x":1737,"y":1773,"style":436},"251.2","no extra software, any OS",[59,1776],{"x1":430,"y1":1734,"x2":430,"y2":1741,"stroke":442,"style":415},[444,1778],{"points":1779,"fill":447},"380.0,161.5 375.0,152.5 385.0,152.5",[399,1781],{"x":1782,"y":1748,"width":1783,"height":1750,"rx":1751,"fill":405,"stroke":414,"style":1282},"344.9","70.2",[428,1785,1786],{"x":430,"y":441,"style":1754},"xlsx files",[399,1788],{"x":1789,"y":1759,"width":1760,"height":1761,"rx":412,"fill":451,"stroke":442,"style":415},"265.3",[417,1791,1793],{"transform":1792},"translate(368.0,176.5) scale(1.000)",[421,1794],{"d":1795,"fill":424,"stroke":458,"style":426},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7",[428,1797,1798],{"x":430,"y":1186,"style":462},"LibreOffice convert",[428,1800,1802],{"x":430,"y":1801,"style":436},"242.5","one sheet per table, then",[428,1804,1587],{"x":430,"y":1805,"style":436},"260.0",[59,1807],{"x1":1738,"y1":1734,"x2":1738,"y2":1741,"stroke":442,"style":415},[444,1809],{"points":1810,"fill":447},"625.3,161.5 620.3,152.5 630.3,152.5",[399,1812],{"x":1813,"y":1748,"width":1814,"height":1750,"rx":1751,"fill":405,"stroke":414,"style":1282},"567.7","115.2",[428,1816,1817],{"x":1738,"y":441,"style":1754},"a Numbers reply",[399,1819],{"x":1820,"y":1759,"width":1760,"height":1761,"rx":412,"fill":506,"stroke":507,"style":415},"510.7",[417,1822,1824],{"transform":1823},"translate(613.3,185.2) scale(1.000)",[421,1825],{"d":423,"fill":424,"stroke":514,"style":426},[428,1827,1828],{"x":1738,"y":1770,"style":518},"Document().save()",[428,1830,1831],{"x":1738,"y":1773,"style":436},"write cells and formats",[43,1833,1835],{"id":1834},"step-7-write-a-numbers-file-back","Step 7 — Write a Numbers file back",[10,1837,1838,1840],{},[14,1839,28],{}," also writes documents, which helps when results must go back to a Mac user in the format they sent:",[48,1842,1844],{"className":107,"code":1843,"language":109,"meta":53,"style":53},"from numbers_parser import Document as NewDoc\n\nout = NewDoc()\ntable = out.sheets[0].tables[0]\nsummary = orders.groupby(\"Shipped\", dropna=False)[\"Total\"].sum().reset_index()\ntable.write(0, 0, \"Shipped\")\ntable.write(0, 1, \"Total\")\nfor r, (flag, total) in enumerate(summary.itertuples(index=False), start=1):\n    table.write(r, 0, \"unknown\" if pd.isna(flag) else str(bool(flag)))\n    table.write(r, 1, float(total))\nout.save(\"summary.numbers\")\nprint(table_to_frame(NewDoc(\"summary.numbers\").sheets[0].tables[0]))\n",[14,1845,1846,1862,1866,1876,1894,1924,1941,1957,1991,2023,2036,2046],{"__ignoreMap":53},[57,1847,1848,1850,1852,1854,1857,1859],{"class":59,"line":60},[57,1849,230],{"class":116},[57,1851,233],{"class":120},[57,1853,117],{"class":116},[57,1855,1856],{"class":120}," Document ",[57,1858,124],{"class":116},[57,1860,1861],{"class":120}," NewDoc\n",[57,1863,1864],{"class":59,"line":130},[57,1865,134],{"emptyLinePlaceholder":133},[57,1867,1868,1871,1873],{"class":59,"line":137},[57,1869,1870],{"class":120},"out ",[57,1872,250],{"class":116},[57,1874,1875],{"class":120}," NewDoc()\n",[57,1877,1878,1881,1883,1886,1888,1890,1892],{"class":59,"line":146},[57,1879,1880],{"class":120},"table ",[57,1882,250],{"class":116},[57,1884,1885],{"class":120}," out.sheets[",[57,1887,402],{"class":164},[57,1889,747],{"class":120},[57,1891,402],{"class":164},[57,1893,969],{"class":120},[57,1895,1896,1899,1901,1904,1906,1908,1911,1913,1916,1919,1921],{"class":59,"line":158},[57,1897,1898],{"class":120},"summary ",[57,1900,250],{"class":116},[57,1902,1903],{"class":120}," orders.groupby(",[57,1905,909],{"class":67},[57,1907,192],{"class":120},[57,1909,1910],{"class":563},"dropna",[57,1912,250],{"class":116},[57,1914,1915],{"class":164},"False",[57,1917,1918],{"class":120},")[",[57,1920,948],{"class":67},[57,1922,1923],{"class":120},"].sum().reset_index()\n",[57,1925,1926,1929,1931,1933,1935,1937,1939],{"class":59,"line":174},[57,1927,1928],{"class":120},"table.write(",[57,1930,402],{"class":164},[57,1932,192],{"class":120},[57,1934,402],{"class":164},[57,1936,192],{"class":120},[57,1938,909],{"class":67},[57,1940,155],{"class":120},[57,1942,1943,1945,1947,1949,1951,1953,1955],{"class":59,"line":347},[57,1944,1928],{"class":120},[57,1946,402],{"class":164},[57,1948,192],{"class":120},[57,1950,587],{"class":164},[57,1952,192],{"class":120},[57,1954,948],{"class":67},[57,1956,155],{"class":120},[57,1958,1959,1961,1964,1966,1968,1971,1974,1976,1978,1981,1984,1986,1988],{"class":59,"line":728},[57,1960,262],{"class":116},[57,1962,1963],{"class":120}," r, (flag, total) ",[57,1965,268],{"class":116},[57,1967,705],{"class":164},[57,1969,1970],{"class":120},"(summary.itertuples(",[57,1972,1973],{"class":563},"index",[57,1975,250],{"class":116},[57,1977,1915],{"class":164},[57,1979,1980],{"class":120},"), ",[57,1982,1983],{"class":563},"start",[57,1985,250],{"class":116},[57,1987,587],{"class":164},[57,1989,1990],{"class":120},"):\n",[57,1992,1993,1996,1998,2000,2003,2006,2009,2012,2015,2017,2020],{"class":59,"line":733},[57,1994,1995],{"class":120},"    table.write(r, ",[57,1997,402],{"class":164},[57,1999,192],{"class":120},[57,2001,2002],{"class":67},"\"unknown\"",[57,2004,2005],{"class":116}," if",[57,2007,2008],{"class":120}," pd.isna(flag) ",[57,2010,2011],{"class":116},"else",[57,2013,2014],{"class":164}," str",[57,2016,180],{"class":120},[57,2018,2019],{"class":164},"bool",[57,2021,2022],{"class":120},"(flag)))\n",[57,2024,2025,2027,2029,2031,2033],{"class":59,"line":755},[57,2026,1995],{"class":120},[57,2028,587],{"class":164},[57,2030,192],{"class":120},[57,2032,810],{"class":164},[57,2034,2035],{"class":120},"(total))\n",[57,2037,2038,2041,2044],{"class":59,"line":764},[57,2039,2040],{"class":120},"out.save(",[57,2042,2043],{"class":67},"\"summary.numbers\"",[57,2045,155],{"class":120},[57,2047,2049,2051,2054,2056,2059,2061,2063,2065],{"class":59,"line":2048},12,[57,2050,758],{"class":164},[57,2052,2053],{"class":120},"(table_to_frame(NewDoc(",[57,2055,2043],{"class":67},[57,2057,2058],{"class":120},").sheets[",[57,2060,402],{"class":164},[57,2062,747],{"class":120},[57,2064,402],{"class":164},[57,2066,2067],{"class":120},"]))\n",[10,2069,2070,2071,2074,2075,2078,2079,2082,2083,41],{},"Values are written cell by cell with ",[14,2072,2073],{},"write(row, col, value)","; numbers, text, booleans and datetimes are supported, and ",[14,2076,2077],{},"set_cell_formatting"," applies number formats such as currency or percentage. pandas' ",[14,2080,2081],{},"NA"," must be turned into something Numbers understands first — here the unknown shipping status becomes the text ",[14,2084,2085],{},"unknown",[43,2087,2089],{"id":2088},"common-pitfalls","Common pitfalls",[2091,2092,2093,2101,2111,2117,2127],"ul",{},[2094,2095,2096,2100],"li",{},[2097,2098,2099],"strong",{},"Expecting one grid per sheet."," A sheet can hold several tables; always pick the table by name.",[2094,2102,2103,2106,2107,2110],{},[2097,2104,2105],{},"Padding."," ",[14,2108,2109],{},"rows()"," returns the full table grid; drop all-empty rows and columns.",[2094,2112,2113,2116],{},[2097,2114,2115],{},"Floats for whole numbers."," Values can carry tiny conversion errors (12 as 12.000000000000002); round before casting counts and IDs, and compare money with a tolerance or after rounding.",[2094,2118,2119,2122,2123,2126],{},[2097,2120,2121],{},"Header rows."," Tables can have zero or several header rows; use ",[14,2124,2125],{},"num_header_rows"," instead of assuming one.",[2094,2128,2129,2106,2132,2134],{},[2097,2130,2131],{},"Very old files.",[14,2133,28],{}," targets documents from recent versions of Numbers; files from very old versions may need re-saving in Numbers first.",[43,2136,2138],{"id":2137},"performance-notes","Performance notes",[10,2140,2141,2143,2144,41],{},[14,2142,28],{}," decodes the whole document when it opens it, including styles and every table, so opening time grows with the document rather than with the one table you need. Documents of a few thousand rows open in well under a second; for a folder of large files, open each document once and extract every table you need from it, rather than reopening it per table. The LibreOffice route costs a process start per file — a few seconds — but converts many files in one call when you pass several paths to ",[14,2145,2146],{},"soffice --convert-to",[43,2148,2150],{"id":2149},"conclusion","Conclusion",[10,2152,2153,2155,2156,2158,2159,2161,2162,2165,2166,2169,2170,2173,2174,2176,2177,2179,2180,2182],{},[14,2154,24],{}," cannot open ",[14,2157,16],{}," files, but ",[14,2160,28],{}," reads them on any system: walk ",[14,2163,2164],{},"doc.sheets"," and each sheet's ",[14,2167,2168],{},"tables",", turn a table into a DataFrame with ",[14,2171,2172],{},"rows(values_only=True)",", trim the empty padding and cast whole numbers. Use ",[14,2175,1399],{}," when you need what the sender saw, LibreOffice when downstream code expects ",[14,2178,20],{},", and ",[14,2181,1828],{}," to send a Numbers file back.",[43,2184,2186],{"id":2185},"frequently-asked-questions","Frequently asked questions",[10,2188,2189,2192],{},[2097,2190,2191],{},"Can pandas read Apple Numbers files?","\nNo. read_excel only reads Excel and OpenDocument formats. Read .numbers files with the numbers-parser package and build a DataFrame from table.rows(values_only=True), or convert the file to .xlsx with LibreOffice first.",[10,2194,2195,2198],{},[2097,2196,2197],{},"Why does my Numbers table have so many empty rows and columns?","\nnumbers-parser returns the table's full grid, including unused rows and columns. Drop them with df.dropna(how=\"all\") and df.dropna(axis=1, how=\"all\").",[10,2200,2201,2204],{},[2097,2202,2203],{},"Why are whole numbers read as floats from a .numbers file?","\nNumbers stores all numeric values as decimals, so 3 comes back as 3.0, and some values pick up a tiny conversion error such as 12.000000000000002. Round the column first and then cast it to Int64.",[10,2206,2207,2210],{},[2097,2208,2209],{},"Do I need a Mac or Numbers installed to read .numbers files?","\nNo. numbers-parser is a Python package that reads the file format directly on Windows, Linux or macOS.",[43,2212,2214],{"id":2213},"related","Related",[2091,2216,2217,2223,2230,2237],{},[2094,2218,2219,2220,2222],{},"Up one level: ",[37,2221,40],{"href":39}," — every format a report script meets.",[2094,2224,2225,2229],{},[37,2226,2228],{"href":2227},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-and-write-ods-files-with-python\u002F","Read and Write ODS Files with Python"," — the OpenDocument format LibreOffice uses.",[2094,2231,2232,2236],{},[37,2233,2235],{"href":2234},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpick-an-excel-engine-for-xlsx-xlsm-xls-xlsb-and-ods\u002F","Pick an Excel Engine for xlsx, xlsm, xls, xlsb and ods"," — which reader handles which extension.",[2094,2238,2239,2243],{},[37,2240,2242],{"href":2241},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-multiple-tables-from-one-excel-sheet\u002F","Read Multiple Tables from One Excel Sheet"," — the Excel version of several tables per sheet.",[2245,2246,2247],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":53,"searchDepth":130,"depth":130,"links":2249},[2250,2251,2252,2253,2254,2255,2256,2257,2258,2259,2260,2261,2262],{"id":45,"depth":130,"text":46},{"id":103,"depth":130,"text":104},{"id":219,"depth":130,"text":220},{"id":526,"depth":130,"text":527},{"id":1291,"depth":130,"text":1292},{"id":1417,"depth":130,"text":1418},{"id":1577,"depth":130,"text":1578},{"id":1834,"depth":130,"text":1835},{"id":2088,"depth":130,"text":2089},{"id":2137,"depth":130,"text":2138},{"id":2149,"depth":130,"text":2150},{"id":2185,"depth":130,"text":2186},{"id":2213,"depth":130,"text":2214},"2026-10-02","pandas cannot open .numbers files, but the numbers-parser package can: list the sheets and tables inside a Numbers document, turn a table into a DataFrame without the padding of empty rows and columns, read typed values and formatted text, handle several tables per sheet, convert a whole document to .xlsx with LibreOffice, and write a Numbers file back.","md",[2267,2269,2271,2273],{"q":2191,"a":2268},"No. read_excel only reads Excel and OpenDocument formats. Read .numbers files with the numbers-parser package and build a DataFrame from table.rows(values_only=True), or convert the file to .xlsx with LibreOffice first.",{"q":2197,"a":2270},"numbers-parser returns the table's full grid, including unused rows and columns. Drop them with df.dropna(how=\"all\") and df.dropna(axis=1, how=\"all\").",{"q":2203,"a":2272},"Numbers stores all numeric values as decimals, so 3 comes back as 3.0, and some values pick up a tiny conversion error such as 12.000000000000002. Round the column first and then cast it to Int64.",{"q":2209,"a":2274},"No. numbers-parser is a Python package that reads the file format directly on Windows, Linux or macOS.","read apple numbers files in python",null,{"breadcrumb":2278},[2279,2281,2284],{"name":2280,"item":1453},"Home",{"name":2282,"item":2283},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":40,"item":39},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-apple-numbers-files-in-python",{"title":2287,"description":2288},"Read Apple Numbers (.numbers) Files in Python with pandas","Read .numbers files in Python with numbers-parser: sheets and tables, a clean pandas DataFrame from table.rows(values_only=True), typed cells and formatted_value, multiple tables per sheet, LibreOffice conversion to xlsx, and writing Numbers files.","read-apple-numbers-files-in-python","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-apple-numbers-files-in-python\u002Findex",[2292,2293],"file-formats","reading-excel","how-to","VtgPPOvbBYP5cwwP8BEprD67xWZXP1q6w6iLrbw5kH8",[2297,2301],{"title":2298,"path":2299,"stem":2300,"children":-1},"Read and Write .ods Files with Python","\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-and-write-ods-files-with-python","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-and-write-ods-files-with-python\u002Findex",{"title":2302,"path":2303,"stem":2304,"children":-1},"Read European CSV Exports with Semicolons and Decimal Commas","\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-csv-exports-with-semicolons-and-decimal-commas","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-csv-exports-with-semicolons-and-decimal-commas\u002Findex",[2306,2308],{"path":2307,"title":40},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions",{"path":2309,"title":2282},"\u002Fgetting-started-with-python-excel-automation",[],[2312,2315,2318,2322,2325,2329],{"path":2313,"title":2314,"kind":2294},"\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":2316,"title":2317,"kind":2294},"\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":2319,"title":2320,"kind":2321},"\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":2323,"title":2324,"kind":2321},"\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":2326,"title":2327,"kind":2328},"\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":2330,"title":2331,"kind":2294},"\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",1790972064216]