[{"data":1,"prerenderedAt":1723},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-nullable-dtypes-when-reading-excel-with-pandas":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-nullable-dtypes-when-reading-excel-with-pandas":1689,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-nullable-dtypes-when-reading-excel-with-pandas":1697,"children:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-nullable-dtypes-when-reading-excel-with-pandas":1702,"related:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-nullable-dtypes-when-reading-excel-with-pandas":1703},{"id":4,"title":5,"body":6,"dateModified":1654,"datePublished":1654,"description":1655,"extension":1656,"faq":1657,"keyword":1666,"kind":1667,"meta":1668,"navigation":141,"path":1677,"seo":1678,"slug":1681,"stem":1682,"topics":1683,"type":1687,"__hash__":1688},"docs\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-nullable-dtypes-when-reading-excel-with-pandas\u002Findex.md","Use Nullable Data Types When Reading Excel with pandas",{"type":7,"value":8,"toc":1640},"minimark",[9,58,63,94,109,113,179,187,228,456,460,502,508,538,541,575,605,751,755,762,825,863,867,873,909,934,1029,1033,1047,1141,1183,1296,1327,1331,1418,1429,1433,1509,1513,1524,1528,1561,1565,1571,1581,1587,1603,1607,1636],[10,11,12,13,17,18,21,22,21,25,28,29,32,33,36,37,39,40,43,44,47,48,51,52,57],"p",{},"A quantity column in Excel holds whole numbers. A \"Shipped\" column holds TRUE and FALSE. Read the sheet with ",[14,15,16],"code",{},"pd.read_excel"," and both can arrive as floats — ",[14,19,20],{},"5.0",", ",[14,23,24],{},"1.0",[14,26,27],{},"0.0"," — because one blank cell somewhere in the column forced pandas to use ",[14,30,31],{},"NaN",", and the classic NumPy integer and boolean types have no way to store a missing value. The numbers still add up, but IDs print as ",[14,34,35],{},"1.200340e+05",", flags become arithmetic, and a report written back from the DataFrame shows ",[14,38,20],{}," where the source said ",[14,41,42],{},"5",". pandas has had a fix for years: nullable data types, which keep the column's real type and mark gaps as ",[14,45,46],{},"\u003CNA>",". This page shows how to ask ",[14,49,50],{},"read_excel"," for them, per file or per column, and what changes downstream. It belongs to ",[53,54,56],"a",{"href":55},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002F","Reading Excel Files with Pandas",".",[59,60,62],"h2",{"id":61},"prerequisites","Prerequisites",[64,65,70],"pre",{"className":66,"code":67,"language":68,"meta":69,"style":69},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install \"pandas>=2.0\" openpyxl pyarrow\n","bash","",[14,71,72],{"__ignoreMap":69},[73,74,77,81,85,88,91],"span",{"class":75,"line":76},"line",1,[73,78,80],{"class":79},"sMTad","pip",[73,82,84],{"class":83},"srMev"," install",[73,86,87],{"class":83}," \"pandas>=2.0\"",[73,89,90],{"class":83}," openpyxl",[73,92,93],{"class":83}," pyarrow\n",[10,95,96,97,100,101,104,105,108],{},"The ",[14,98,99],{},"dtype_backend"," argument arrived in pandas 2.0; everything below was run on pandas 3.0. ",[14,102,103],{},"pyarrow"," is only needed for the Arrow-backed variant. The sample file ",[14,106,107],{},"orders.xlsx"," has four orders. Order 1002 has no quantity, discount or customer, and order 1003 has no shipped flag and no region — the kind of gaps any hand-maintained sheet has.",[59,110,112],{"id":111},"step-1-see-what-the-default-read-does","Step 1 — See what the default read does",[64,114,118],{"className":115,"code":116,"language":117,"meta":69,"style":69},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.read_excel(\"orders.xlsx\")\nprint(df.dtypes)\nprint(df)\n","python",[14,119,120,136,143,161,171],{"__ignoreMap":69},[73,121,122,126,130,133],{"class":75,"line":76},[73,123,125],{"class":124},"s-kum","import",[73,127,129],{"class":128},"skGVy"," pandas ",[73,131,132],{"class":124},"as",[73,134,135],{"class":128}," pd\n",[73,137,139],{"class":75,"line":138},2,[73,140,142],{"emptyLinePlaceholder":141},true,"\n",[73,144,146,149,152,155,158],{"class":75,"line":145},3,[73,147,148],{"class":128},"df ",[73,150,151],{"class":124},"=",[73,153,154],{"class":128}," pd.read_excel(",[73,156,157],{"class":83},"\"orders.xlsx\"",[73,159,160],{"class":128},")\n",[73,162,164,168],{"class":75,"line":163},4,[73,165,167],{"class":166},"sP0c6","print",[73,169,170],{"class":128},"(df.dtypes)\n",[73,172,174,176],{"class":75,"line":173},5,[73,175,167],{"class":166},[73,177,178],{"class":128},"(df)\n",[64,180,185],{"className":181,"code":183,"language":184,"meta":69},[182],"language-text","OrderID          int64\nQty            float64\nDiscount       float64\nShipped        float64\nRegion             str\nCustomer ID    float64\n","text",[14,186,183],{"__ignoreMap":69},[10,188,189,190,193,194,197,198,201,202,205,206,209,210,212,213,216,217,21,219,197,221,223,224,227],{},"Only ",[14,191,192],{},"OrderID",", the one numeric column without a gap, stays an integer. ",[14,195,196],{},"Qty"," and ",[14,199,200],{},"Customer ID"," became ",[14,203,204],{},"float64"," because NumPy's ",[14,207,208],{},"int64"," cannot hold ",[14,211,31],{},". ",[14,214,215],{},"Shipped"," is the surprising one: a column of TRUE, FALSE and one blank comes back as ",[14,218,24],{},[14,220,27],{},[14,222,31],{},", because pandas falls back to the nearest NumPy type that can hold a missing value, and for booleans that is a float. A filter such as ",[14,225,226],{},"df[df[\"Shipped\"]]"," now raises an error instead of selecting shipped orders.",[229,230,238,239,238,243,238,247,238,255,238,264,238,268,238,274,238,278,238,282,238,286,238,291,238,293,238,296,238,298,238,300,238,303,238,307,238,309,238,312,238,314,238,317,238,320,238,324,238,329,238,331,238,334,238,337,238,341,238,343,238,346,238,348,238,351,238,354,238,356,238,358,238,360,238,362,238,368,238,373,238,376,238,379,238,382,238,385,238,387,238,389,238,392,238,395,238,397,238,399,238,402,238,406,238,408,238,410,238,413,238,417,238,419,238,421,238,427,238,431,238,438,238,443,238,451],"svg",{"viewBox":231,"role":232,"ariaLabelledBy":233,"xmlns":236,"style":237},"112 0 536 220","img",[234,235],"nd-default-t","nd-default-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:536px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[240,241,242],"title",{"id":234},"One blank cell changes the type of the whole column",[244,245,246],"desc",{"id":235},"On the left the Excel sheet with whole quantities, TRUE and FALSE flags and two blank cells. On the right the default DataFrame, where Qty shows 5.0 and NaN and Shipped shows 1.0, 0.0 and NaN.",[248,249],"rect",{"x":250,"y":251,"width":252,"height":253,"fill":254},"112","0","536","220","#ffffff",[248,256],{"x":257,"y":258,"width":259,"height":260,"rx":251,"fill":261,"stroke":262,"style":263},"128.3","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[248,265],{"x":266,"y":258,"width":267,"height":260,"rx":251,"fill":261,"stroke":262,"style":263},"160.3","44.2",[184,269,273],{"x":270,"y":271,"style":272},"182.4","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[248,275],{"x":276,"y":258,"width":277,"height":260,"rx":251,"fill":261,"stroke":262,"style":263},"204.5","70.6",[184,279,281],{"x":280,"y":271,"style":272},"239.8","B",[248,283],{"x":257,"y":284,"width":259,"height":285,"rx":251,"fill":261,"stroke":262,"style":263},"64.0","28.0",[184,287,290],{"x":288,"y":289,"style":272},"144.3","83.0","1",[248,292],{"x":266,"y":284,"width":267,"height":285,"rx":251,"fill":254,"stroke":262,"style":263},[184,294,196],{"x":270,"y":289,"style":295},"font-size:11px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[248,297],{"x":276,"y":284,"width":277,"height":285,"rx":251,"fill":254,"stroke":262,"style":263},[184,299,215],{"x":280,"y":289,"style":295},[248,301],{"x":257,"y":302,"width":259,"height":285,"rx":251,"fill":261,"stroke":262,"style":263},"92.0",[184,304,306],{"x":288,"y":305,"style":272},"111.0","2",[248,308],{"x":266,"y":302,"width":267,"height":285,"rx":251,"fill":254,"stroke":262,"style":263},[184,310,42],{"x":270,"y":305,"style":311},"font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[248,313],{"x":276,"y":302,"width":277,"height":285,"rx":251,"fill":254,"stroke":262,"style":263},[184,315,316],{"x":280,"y":305,"style":311},"TRUE",[248,318],{"x":257,"y":319,"width":259,"height":285,"rx":251,"fill":261,"stroke":262,"style":263},"120.0",[184,321,323],{"x":288,"y":322,"style":272},"139.0","3",[248,325],{"x":266,"y":319,"width":267,"height":285,"rx":251,"fill":326,"stroke":327,"style":328},"#fdefd8","var(--gold,#b4740a)","stroke-width:1.5px",[248,330],{"x":276,"y":319,"width":277,"height":285,"rx":251,"fill":254,"stroke":262,"style":263},[184,332,333],{"x":280,"y":322,"style":311},"FALSE",[248,335],{"x":257,"y":336,"width":259,"height":285,"rx":251,"fill":261,"stroke":262,"style":263},"148.0",[184,338,340],{"x":288,"y":339,"style":272},"167.0","4",[248,342],{"x":266,"y":336,"width":267,"height":285,"rx":251,"fill":254,"stroke":262,"style":263},[184,344,345],{"x":270,"y":339,"style":311},"7",[248,347],{"x":276,"y":336,"width":277,"height":285,"rx":251,"fill":326,"stroke":327,"style":328},[248,349],{"x":257,"y":350,"width":259,"height":285,"rx":251,"fill":261,"stroke":262,"style":263},"176.0",[184,352,42],{"x":288,"y":353,"style":272},"195.0",[248,355],{"x":266,"y":350,"width":267,"height":285,"rx":251,"fill":254,"stroke":262,"style":263},[184,357,306],{"x":270,"y":353,"style":311},[248,359],{"x":276,"y":350,"width":277,"height":285,"rx":251,"fill":254,"stroke":262,"style":263},[184,361,316],{"x":280,"y":353,"style":311},[248,363],{"x":364,"y":258,"width":365,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},"512.7","48.3","#fce9e9","var(--danger,#dc2626)",[184,369,196],{"x":370,"y":371,"style":372},"536.9","59.0","font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[248,374],{"x":375,"y":258,"width":277,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},"561.1",[184,377,215],{"x":378,"y":371,"style":372},"596.4",[248,380],{"x":364,"y":381,"width":365,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},"68.0",[184,383,20],{"x":370,"y":384,"style":372},"87.0",[248,386],{"x":375,"y":381,"width":277,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},[184,388,24],{"x":378,"y":384,"style":372},[248,390],{"x":364,"y":391,"width":365,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},"96.0",[184,393,31],{"x":370,"y":394,"style":372},"115.0",[248,396],{"x":375,"y":391,"width":277,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},[184,398,27],{"x":378,"y":394,"style":372},[248,400],{"x":364,"y":401,"width":365,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},"124.0",[184,403,405],{"x":370,"y":404,"style":372},"143.0","7.0",[248,407],{"x":375,"y":401,"width":277,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},[184,409,31],{"x":378,"y":404,"style":372},[248,411],{"x":364,"y":412,"width":365,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},"152.0",[184,414,416],{"x":370,"y":415,"style":372},"171.0","2.0",[248,418],{"x":375,"y":412,"width":277,"height":285,"rx":251,"fill":366,"stroke":367,"style":328},[184,420,24],{"x":378,"y":415,"style":372},[184,422,426],{"x":423,"y":424,"style":425},"201.7","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","Excel sheet",[184,428,430],{"x":429,"y":424,"style":425},"572.2","default DataFrame",[75,432],{"x1":433,"y1":434,"x2":435,"y2":434,"stroke":436,"style":437},"285.1","110.0","498.7","var(--brand,#5b5cf0)","stroke-width:2px",[439,440],"polygon",{"points":441,"fill":442},"502.7,110.0 493.7,115.0 493.7,105.0","#5b5cf0",[248,444],{"x":445,"y":446,"width":447,"height":448,"rx":449,"fill":254,"stroke":262,"style":450},"314.9","77.0","158.1","22.0","11","stroke-width:1.2px",[184,452,455],{"x":453,"y":302,"style":454},"393.9","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","pd.read_excel(path)",[59,457,459],{"id":458},"step-2-ask-for-nullable-types-for-the-whole-file","Step 2 — Ask for nullable types for the whole file",[64,461,463],{"className":115,"code":462,"language":117,"meta":69,"style":69},"nullable = pd.read_excel(\"orders.xlsx\", dtype_backend=\"numpy_nullable\")\nprint(nullable.dtypes)\nprint(nullable)\n",[14,464,465,488,495],{"__ignoreMap":69},[73,466,467,470,472,474,476,478,481,483,486],{"class":75,"line":76},[73,468,469],{"class":128},"nullable ",[73,471,151],{"class":124},[73,473,154],{"class":128},[73,475,157],{"class":83},[73,477,21],{"class":128},[73,479,99],{"class":480},"sa561",[73,482,151],{"class":124},[73,484,485],{"class":83},"\"numpy_nullable\"",[73,487,160],{"class":128},[73,489,490,492],{"class":75,"line":138},[73,491,167],{"class":166},[73,493,494],{"class":128},"(nullable.dtypes)\n",[73,496,497,499],{"class":75,"line":145},[73,498,167],{"class":166},[73,500,501],{"class":128},"(nullable)\n",[64,503,506],{"className":504,"code":505,"language":184,"meta":69},[182],"OrderID          Int64\nQty              Int64\nDiscount       Float64\nShipped        boolean\nRegion          string\nCustomer ID      Int64\n",[14,507,505],{"__ignoreMap":69},[10,509,510,511,513,514,21,517,21,520,197,523,526,527,530,531,533,534,537],{},"Every column now has the type its values actually have, and each gap shows as ",[14,512,46],{},". Note the capital letters: ",[14,515,516],{},"Int64",[14,518,519],{},"Float64",[14,521,522],{},"boolean",[14,524,525],{},"string"," are pandas' extension types, which store a separate mask of missing positions next to the values, so a missing entry no longer needs a float to represent it. Customer IDs print as ",[14,528,529],{},"120034"," again, quantities as ",[14,532,42],{},", and ",[14,535,536],{},"nullable[nullable[\"Shipped\"].fillna(False)]"," selects the shipped orders.",[10,539,540],{},"The other option is Arrow-backed columns:",[64,542,544],{"className":115,"code":543,"language":117,"meta":69,"style":69},"arrow = pd.read_excel(\"orders.xlsx\", dtype_backend=\"pyarrow\")\nprint(arrow.dtypes)\n",[14,545,546,568],{"__ignoreMap":69},[73,547,548,551,553,555,557,559,561,563,566],{"class":75,"line":76},[73,549,550],{"class":128},"arrow ",[73,552,151],{"class":124},[73,554,154],{"class":128},[73,556,157],{"class":83},[73,558,21],{"class":128},[73,560,99],{"class":480},[73,562,151],{"class":124},[73,564,565],{"class":83},"\"pyarrow\"",[73,567,160],{"class":128},[73,569,570,572],{"class":75,"line":138},[73,571,167],{"class":166},[73,573,574],{"class":128},"(arrow.dtypes)\n",[10,576,577,578,21,581,21,584,197,587,590,591,593,594,597,598,600,601,57],{},"This gives ",[14,579,580],{},"int64[pyarrow]",[14,582,583],{},"double[pyarrow]",[14,585,586],{},"bool[pyarrow]",[14,588,589],{},"string[pyarrow]",". The values and the ",[14,592,46],{}," markers are the same; the storage is Apache Arrow memory, which uses less RAM for text and hands columns to Polars, DuckDB or Parquet without copying. For a report pipeline that stays inside pandas, ",[14,595,596],{},"numpy_nullable"," is the conservative choice; pick ",[14,599,103],{}," when the next step is Parquet or another Arrow tool, as in ",[53,602,604],{"href":603},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-excel-files-to-parquet-with-python\u002F","Convert Excel Files to Parquet with Python",[229,606,238,612,238,615,238,618,238,622,238,627,238,632,238,638,238,641,238,644,238,647,238,650,238,654,238,659,238,661,238,664,238,666,238,668,238,670,238,672,238,675,238,679,238,681,238,683,238,685,238,687,238,689,238,691,238,693,238,697,238,699,238,702,238,704,238,706,238,708,238,710,238,713,238,717,238,719,238,723,238,725,238,727,238,729,238,731,238,734,238,738,238,740,238,743,238,745,238,747,238,749],{"viewBox":607,"role":232,"ariaLabelledBy":608,"xmlns":236,"style":611},"4 0 752 242",[609,610],"nd-backends-t","nd-backends-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[240,613,614],{"id":609},"What each dtype_backend gives a column with blanks",[244,616,617],{"id":610},"A grid comparing the default backend, numpy_nullable and pyarrow for integer, boolean, decimal and text columns that contain blank cells.",[248,619],{"x":340,"y":251,"width":620,"height":621,"fill":254},"752","242",[248,623],{"x":624,"y":625,"width":626,"height":258,"rx":251,"fill":254,"stroke":262,"style":263},"20.0","16.0","209.3",[248,628],{"x":629,"y":625,"width":630,"height":258,"rx":251,"fill":631,"stroke":436,"style":450},"229.3","170.2","#f0f4ff",[184,633,637],{"x":634,"y":635,"style":636},"314.5","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","default",[248,639],{"x":640,"y":625,"width":630,"height":258,"rx":251,"fill":631,"stroke":436,"style":450},"399.6",[184,642,596],{"x":643,"y":635,"style":636},"484.7",[248,645],{"x":646,"y":625,"width":630,"height":258,"rx":251,"fill":631,"stroke":436,"style":450},"569.8",[184,648,103],{"x":649,"y":635,"style":636},"654.9",[248,651],{"x":624,"y":652,"width":626,"height":653,"rx":251,"fill":261,"stroke":262,"style":263},"56.0","34.0",[184,655,658],{"x":259,"y":656,"style":657},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Whole numbers with a blank",[248,660],{"x":629,"y":652,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,662,204],{"x":634,"y":446,"style":663},"font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[248,665],{"x":640,"y":652,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,667,516],{"x":643,"y":446,"style":663},[248,669],{"x":646,"y":652,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,671,580],{"x":649,"y":446,"style":663},[248,673],{"x":624,"y":674,"width":626,"height":653,"rx":251,"fill":261,"stroke":262,"style":263},"90.0",[184,676,678],{"x":259,"y":677,"style":657},"112.0","TRUE\u002FFALSE with a blank",[248,680],{"x":629,"y":674,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,682,204],{"x":634,"y":305,"style":663},[248,684],{"x":640,"y":674,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,686,522],{"x":643,"y":305,"style":663},[248,688],{"x":646,"y":674,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,690,586],{"x":649,"y":305,"style":663},[248,692],{"x":624,"y":401,"width":626,"height":653,"rx":251,"fill":261,"stroke":262,"style":263},[184,694,696],{"x":259,"y":695,"style":657},"146.0","Decimals with a blank",[248,698],{"x":629,"y":401,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,700,204],{"x":634,"y":701,"style":663},"145.0",[248,703],{"x":640,"y":401,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,705,519],{"x":643,"y":701,"style":663},[248,707],{"x":646,"y":401,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,709,583],{"x":649,"y":701,"style":663},[248,711],{"x":624,"y":712,"width":626,"height":653,"rx":251,"fill":261,"stroke":262,"style":263},"158.0",[184,714,716],{"x":259,"y":715,"style":657},"180.0","Text with a blank",[248,718],{"x":629,"y":712,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,720,722],{"x":634,"y":721,"style":663},"179.0","str",[248,724],{"x":640,"y":712,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,726,525],{"x":643,"y":721,"style":663},[248,728],{"x":646,"y":712,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,730,589],{"x":649,"y":721,"style":663},[248,732],{"x":624,"y":733,"width":626,"height":653,"rx":251,"fill":261,"stroke":262,"style":263},"192.0",[184,735,737],{"x":259,"y":736,"style":657},"214.0","Missing marker",[248,739],{"x":629,"y":733,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,741,31],{"x":634,"y":742,"style":663},"213.0",[248,744],{"x":640,"y":733,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,746,46],{"x":643,"y":742,"style":663},[248,748],{"x":646,"y":733,"width":630,"height":653,"rx":251,"fill":254,"stroke":262,"style":263},[184,750,46],{"x":649,"y":742,"style":663},[59,752,754],{"id":753},"step-3-set-types-for-specific-columns-only","Step 3 — Set types for specific columns only",[10,756,757,758,761],{},"You do not have to switch the whole file. The ",[14,759,760],{},"dtype"," argument accepts the extension type names per column, which is handy when only a couple of columns matter or when the rest of the code expects classic NumPy types:",[64,763,765],{"className":115,"code":764,"language":117,"meta":69,"style":69},"mixed = pd.read_excel(\"orders.xlsx\", dtype={\"Qty\": \"Int64\", \"Shipped\": \"boolean\", \"Customer ID\": \"Int64\"})\nprint(mixed.dtypes)\n",[14,766,767,818],{"__ignoreMap":69},[73,768,769,772,774,776,778,780,782,784,787,790,793,796,798,801,803,806,808,811,813,815],{"class":75,"line":76},[73,770,771],{"class":128},"mixed ",[73,773,151],{"class":124},[73,775,154],{"class":128},[73,777,157],{"class":83},[73,779,21],{"class":128},[73,781,760],{"class":480},[73,783,151],{"class":124},[73,785,786],{"class":128},"{",[73,788,789],{"class":83},"\"Qty\"",[73,791,792],{"class":128},": ",[73,794,795],{"class":83},"\"Int64\"",[73,797,21],{"class":128},[73,799,800],{"class":83},"\"Shipped\"",[73,802,792],{"class":128},[73,804,805],{"class":83},"\"boolean\"",[73,807,21],{"class":128},[73,809,810],{"class":83},"\"Customer ID\"",[73,812,792],{"class":128},[73,814,795],{"class":83},[73,816,817],{"class":128},"})\n",[73,819,820,822],{"class":75,"line":138},[73,821,167],{"class":166},[73,823,824],{"class":128},"(mixed.dtypes)\n",[10,826,827,21,829,197,831,833,834,837,838,197,840,843,844,846,847,850,851,854,855,858,859,57],{},[14,828,196],{},[14,830,215],{},[14,832,200],{}," come back nullable; ",[14,835,836],{},"Discount"," stays ",[14,839,204],{},[14,841,842],{},"Region"," keeps the default text type. If a column you mark ",[14,845,516],{}," contains a real decimal such as ",[14,848,849],{},"0.05",", the read fails with ",[14,852,853],{},"TypeError: cannot safely cast non-equivalent object to int64"," instead of silently rounding — a free check that an integer column really holds integers. The message names the sheet but not the cell, so find the offending values with ",[14,856,857],{},"df[col] % 1 != 0"," on a default read. For identifiers that must keep leading zeros, read them as text instead, as described in ",[53,860,862],{"href":861},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fkeep-leading-zeros-when-reading-excel-with-pandas\u002F","Keep Leading Zeros When Reading Excel with pandas",[59,864,866],{"id":865},"step-4-why-convert_dtypes-after-the-read-is-too-late","Step 4 — Why convert_dtypes after the read is too late",[10,868,869,872],{},[14,870,871],{},"DataFrame.convert_dtypes()"," converts an existing frame to nullable types, and it is tempting to read normally and convert afterwards. For integers that works. For booleans it does not:",[64,874,876],{"className":115,"code":875,"language":117,"meta":69,"style":69},"late = pd.read_excel(\"orders.xlsx\").convert_dtypes()\nprint(late[\"Shipped\"].dtype, late[\"Shipped\"].tolist())\n",[14,877,878,892],{"__ignoreMap":69},[73,879,880,883,885,887,889],{"class":75,"line":76},[73,881,882],{"class":128},"late ",[73,884,151],{"class":124},[73,886,154],{"class":128},[73,888,157],{"class":83},[73,890,891],{"class":128},").convert_dtypes()\n",[73,893,894,896,899,901,904,906],{"class":75,"line":138},[73,895,167],{"class":166},[73,897,898],{"class":128},"(late[",[73,900,800],{"class":83},[73,902,903],{"class":128},"].dtype, late[",[73,905,800],{"class":83},[73,907,908],{"class":128},"].tolist())\n",[10,910,911,912,914,915,918,919,922,923,197,925,927,928,930,931,933],{},"The output is ",[14,913,516],{}," with ",[14,916,917],{},"[1, 0, \u003CNA>, 1]",". By the time ",[14,920,921],{},"convert_dtypes"," runs, the default read has already turned TRUE and FALSE into ",[14,924,24],{},[14,926,27],{},", and nothing in the frame remembers they were booleans. Choose the type at read time — through ",[14,929,99],{}," or ",[14,932,760],{}," — so that the reader sees the original cell values.",[229,935,238,940,238,943,238,946,238,949,238,954,238,965,238,971,238,975,238,980,238,983,238,986,238,993,238,998,238,1001,238,1005,238,1008,238,1011,238,1017,238,1021,238,1024],{"viewBox":936,"role":232,"ariaLabelledBy":937,"xmlns":236,"style":611},"4 0 752 151",[938,939],"nd-late-t","nd-late-d",[240,941,942],{"id":938},"Reading first and converting later loses the boolean type",[244,944,945],{"id":939},"Two paths from the same TRUE\u002FFALSE column. Reading with the default backend produces floats, and convert_dtypes then produces Int64. Reading with dtype_backend numpy_nullable produces boolean directly.",[248,947],{"x":340,"y":251,"width":620,"height":948,"fill":254},"151",[248,950],{"x":624,"y":625,"width":951,"height":952,"rx":953,"fill":261,"stroke":262,"style":437},"216.0","93.0","12",[955,956,958],"g",{"transform":957},"translate(116.0,29.0) scale(1.000)",[959,960],"path",{"d":961,"fill":962,"stroke":963,"style":964},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[184,966,970],{"x":967,"y":968,"style":969},"128.0","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","TRUE \u002F FALSE \u002F blank",[184,972,974],{"x":967,"y":973,"style":663},"95.0","cells in the sheet",[75,976],{"x1":977,"y1":978,"x2":979,"y2":978,"stroke":436,"style":437},"241.0","62.5","263.0",[439,981],{"points":982,"fill":442},"267.0,62.5 258.0,67.5 258.0,57.5",[248,984],{"x":985,"y":625,"width":951,"height":952,"rx":953,"fill":366,"stroke":367,"style":437},"272.0",[955,987,989],{"transform":988},"translate(368.0,29.0) scale(1.000)",[959,990],{"d":991,"fill":962,"stroke":992,"style":964},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--accent-ink,#be185d)",[184,994,997],{"x":995,"y":968,"style":996},"380.0","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","1.0 \u002F 0.0 \u002F NaN",[184,999,1000],{"x":995,"y":973,"style":663},"default read_excel",[75,1002],{"x1":1003,"y1":978,"x2":1004,"y2":978,"stroke":436,"style":437},"493.0","515.0",[439,1006],{"points":1007,"fill":442},"519.0,62.5 510.0,67.5 510.0,57.5",[248,1009],{"x":1010,"y":625,"width":951,"height":952,"rx":953,"fill":366,"stroke":367,"style":437},"524.0",[955,1012,1014],{"transform":1013},"translate(620.0,29.0) scale(1.000)",[959,1015],{"d":1016,"fill":962,"stroke":992,"style":964},"M6 6l12 12 M18 6L6 18",[184,1018,1020],{"x":1019,"y":968,"style":996},"632.0","1 \u002F 0 \u002F \u003CNA>",[184,1022,1023],{"x":1019,"y":973,"style":663},"convert_dtypes(): Int64",[184,1025,1028],{"x":995,"y":1026,"style":1027},"131.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","With dtype_backend=\"numpy_nullable\" the reader sees the cells and returns boolean True \u002F False \u002F \u003CNA>.",[59,1030,1032],{"id":1031},"step-5-work-with-na-values-downstream","Step 5 — Work with NA values downstream",[10,1034,1035,1036,1038,1039,1042,1043,1046],{},"Nullable columns behave like their NumPy cousins with one difference: a comparison involving a missing value returns ",[14,1037,46],{}," instead of ",[14,1040,1041],{},"False",". Aggregations are unaffected, but filters and ",[14,1044,1045],{},"if"," tests need a decision about what a missing value means:",[64,1048,1050],{"className":115,"code":1049,"language":117,"meta":69,"style":69},"df = nullable\nprint(df[\"Qty\"].sum(), df[\"Qty\"].mean())\nmask = df[\"Qty\"] > 4\nprint(mask.tolist())\nprint(\"big:\", df[mask][\"OrderID\"].tolist(), \"not big:\", df[~mask][\"OrderID\"].tolist())\n",[14,1051,1052,1061,1078,1099,1106],{"__ignoreMap":69},[73,1053,1054,1056,1058],{"class":75,"line":76},[73,1055,148],{"class":128},[73,1057,151],{"class":124},[73,1059,1060],{"class":128}," nullable\n",[73,1062,1063,1065,1068,1070,1073,1075],{"class":75,"line":138},[73,1064,167],{"class":166},[73,1066,1067],{"class":128},"(df[",[73,1069,789],{"class":83},[73,1071,1072],{"class":128},"].sum(), df[",[73,1074,789],{"class":83},[73,1076,1077],{"class":128},"].mean())\n",[73,1079,1080,1083,1085,1088,1090,1093,1096],{"class":75,"line":145},[73,1081,1082],{"class":128},"mask ",[73,1084,151],{"class":124},[73,1086,1087],{"class":128}," df[",[73,1089,789],{"class":83},[73,1091,1092],{"class":128},"] ",[73,1094,1095],{"class":124},">",[73,1097,1098],{"class":166}," 4\n",[73,1100,1101,1103],{"class":75,"line":163},[73,1102,167],{"class":166},[73,1104,1105],{"class":128},"(mask.tolist())\n",[73,1107,1108,1110,1113,1116,1119,1122,1125,1128,1131,1134,1137,1139],{"class":75,"line":173},[73,1109,167],{"class":166},[73,1111,1112],{"class":128},"(",[73,1114,1115],{"class":83},"\"big:\"",[73,1117,1118],{"class":128},", df[mask][",[73,1120,1121],{"class":83},"\"OrderID\"",[73,1123,1124],{"class":128},"].tolist(), ",[73,1126,1127],{"class":83},"\"not big:\"",[73,1129,1130],{"class":128},", df[",[73,1132,1133],{"class":124},"~",[73,1135,1136],{"class":128},"mask][",[73,1138,1121],{"class":83},[73,1140,908],{"class":128},[10,1142,1143,197,1146,1149,1150,197,1153,1156,1157,1160,1161,1163,1164,1167,1168,1171,1172,1176,1177,1179,1180,1182],{},[14,1144,1145],{},"sum",[14,1147,1148],{},"mean"," skip missing values exactly as before (",[14,1151,1152],{},"14",[14,1154,1155],{},"4.67","). The mask is ",[14,1158,1159],{},"[True, \u003CNA>, True, False]",", and when you index with it pandas treats ",[14,1162,46],{}," as \"do not select\". That looks harmless until you take the opposite filter: ",[14,1165,1166],{},"~mask"," is ",[14,1169,1170],{},"[False, \u003CNA>, False, True]",", so order 1002 appears in ",[1173,1174,1175],"em",{},"neither"," list. With the default float columns the same comparison gives ",[14,1178,1041],{}," for ",[14,1181,31],{},", and the inverted mask would have picked 1002 up. Any report that splits rows into \"above\" and \"below\" a threshold therefore needs an explicit choice:",[64,1184,1186],{"className":115,"code":1185,"language":117,"meta":69,"style":69},"split = mask.fillna(False)\nprint(\"big:\", df[split][\"OrderID\"].tolist(), \"rest:\", df[~split][\"OrderID\"].tolist())\ntry:\n    if df.loc[1, \"Qty\"] > 4:\n        pass\nexcept TypeError as exc:\n    print(\"TypeError:\", exc)\n",[14,1187,1188,1202,1231,1239,1262,1267,1282],{"__ignoreMap":69},[73,1189,1190,1193,1195,1198,1200],{"class":75,"line":76},[73,1191,1192],{"class":128},"split ",[73,1194,151],{"class":124},[73,1196,1197],{"class":128}," mask.fillna(",[73,1199,1041],{"class":166},[73,1201,160],{"class":128},[73,1203,1204,1206,1208,1210,1213,1215,1217,1220,1222,1224,1227,1229],{"class":75,"line":138},[73,1205,167],{"class":166},[73,1207,1112],{"class":128},[73,1209,1115],{"class":83},[73,1211,1212],{"class":128},", df[split][",[73,1214,1121],{"class":83},[73,1216,1124],{"class":128},[73,1218,1219],{"class":83},"\"rest:\"",[73,1221,1130],{"class":128},[73,1223,1133],{"class":124},[73,1225,1226],{"class":128},"split][",[73,1228,1121],{"class":83},[73,1230,908],{"class":128},[73,1232,1233,1236],{"class":75,"line":145},[73,1234,1235],{"class":124},"try",[73,1237,1238],{"class":128},":\n",[73,1240,1241,1244,1247,1249,1251,1253,1255,1257,1260],{"class":75,"line":163},[73,1242,1243],{"class":124},"    if",[73,1245,1246],{"class":128}," df.loc[",[73,1248,290],{"class":166},[73,1250,21],{"class":128},[73,1252,789],{"class":83},[73,1254,1092],{"class":128},[73,1256,1095],{"class":124},[73,1258,1259],{"class":166}," 4",[73,1261,1238],{"class":128},[73,1263,1264],{"class":75,"line":173},[73,1265,1266],{"class":124},"        pass\n",[73,1268,1270,1273,1276,1279],{"class":75,"line":1269},6,[73,1271,1272],{"class":124},"except",[73,1274,1275],{"class":166}," TypeError",[73,1277,1278],{"class":124}," as",[73,1280,1281],{"class":128}," exc:\n",[73,1283,1285,1288,1290,1293],{"class":75,"line":1284},7,[73,1286,1287],{"class":166},"    print",[73,1289,1112],{"class":128},[73,1291,1292],{"class":83},"\"TypeError:\"",[73,1294,1295],{"class":128},", exc)\n",[10,1297,1298,1299,1302,1303,1305,1306,1309,1310,1313,1314,1316,1317,1320,1321,1167,1324,1326],{},"With ",[14,1300,1301],{},"fillna(False)"," the missing quantity lands in \"rest\". Plain Python ",[14,1304,1045],{}," statements and ",[14,1307,1308],{},"numpy.where"," refuse to guess and raise ",[14,1311,1312],{},"TypeError: boolean value of NA is ambiguous",", which is the behaviour you want — it points at the exact place a missing value needs handling. String methods on a ",[14,1315,525],{}," column return nullable results too, ",[14,1318,1319],{},"isna()"," works on every column regardless of backend, and integer arithmetic stays integer: ",[14,1322,1323],{},"df[\"Qty\"] * 2",[14,1325,516],{},", not float.",[59,1328,1330],{"id":1329},"step-6-write-the-frame-back-to-excel","Step 6 — Write the frame back to Excel",[64,1332,1334],{"className":115,"code":1333,"language":117,"meta":69,"style":69},"nullable.to_excel(\"orders_out.xlsx\", index=False)\n\nfrom openpyxl import load_workbook\nfor row in load_workbook(\"orders_out.xlsx\").active.iter_rows(min_row=2, values_only=True):\n    print(row)\n",[14,1335,1336,1355,1359,1372,1411],{"__ignoreMap":69},[73,1337,1338,1341,1344,1346,1349,1351,1353],{"class":75,"line":76},[73,1339,1340],{"class":128},"nullable.to_excel(",[73,1342,1343],{"class":83},"\"orders_out.xlsx\"",[73,1345,21],{"class":128},[73,1347,1348],{"class":480},"index",[73,1350,151],{"class":124},[73,1352,1041],{"class":166},[73,1354,160],{"class":128},[73,1356,1357],{"class":75,"line":138},[73,1358,142],{"emptyLinePlaceholder":141},[73,1360,1361,1364,1367,1369],{"class":75,"line":145},[73,1362,1363],{"class":124},"from",[73,1365,1366],{"class":128}," openpyxl ",[73,1368,125],{"class":124},[73,1370,1371],{"class":128}," load_workbook\n",[73,1373,1374,1377,1380,1383,1386,1388,1391,1394,1396,1398,1400,1403,1405,1408],{"class":75,"line":163},[73,1375,1376],{"class":124},"for",[73,1378,1379],{"class":128}," row ",[73,1381,1382],{"class":124},"in",[73,1384,1385],{"class":128}," load_workbook(",[73,1387,1343],{"class":83},[73,1389,1390],{"class":128},").active.iter_rows(",[73,1392,1393],{"class":480},"min_row",[73,1395,151],{"class":124},[73,1397,306],{"class":166},[73,1399,21],{"class":128},[73,1401,1402],{"class":480},"values_only",[73,1404,151],{"class":124},[73,1406,1407],{"class":166},"True",[73,1409,1410],{"class":128},"):\n",[73,1412,1413,1415],{"class":75,"line":173},[73,1414,1287],{"class":166},[73,1416,1417],{"class":128},"(row)\n",[10,1419,1420,1421,1424,1425,57],{},"Missing values are written as empty cells, integers as integers and booleans as real TRUE\u002FFALSE cells, so the output looks like the input. The round trip is lossless for these types, which matters when another script reads the output: it sees integers and booleans, not floats it has to convert back. If identifiers already lost digits ",[1173,1422,1423],{},"in the file"," (Excel only stores 15 significant digits), no dtype can restore them; see ",[53,1426,1428],{"href":1427},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-long-id-numbers-losing-digits-in-excel\u002F","Fix Long ID Numbers Losing Digits in Excel",[59,1430,1432],{"id":1431},"common-pitfalls","Common pitfalls",[1434,1435,1436,1450,1465,1480,1492],"ul",{},[1437,1438,1439,1443,1444,1446,1447,1449],"li",{},[1440,1441,1442],"strong",{},"Boolean columns read as floats."," A single blank turns a TRUE\u002FFALSE column into ",[14,1445,204],{},"; ask for ",[14,1448,522],{}," at read time.",[1437,1451,1452,1455,1456,1459,1460,1462,1463,57],{},[1440,1453,1454],{},"Converting too late."," ",[14,1457,1458],{},"convert_dtypes()"," after a default read turns those floats into ",[14,1461,516],{},", not ",[14,1464,522],{},[1437,1466,1467,1455,1470,197,1473,1476,1477,1479],{},[1440,1468,1469],{},"Filtering with an NA mask.",[14,1471,1472],{},"df[mask]",[14,1474,1475],{},"df[~mask]"," both drop rows where the comparison is ",[14,1478,46],{},"; fill the mask first so every row lands on one side.",[1437,1481,1482,1485,1486,930,1489,57],{},[1440,1483,1484],{},"Libraries that expect NumPy types."," Some older plotting and statistics code chokes on extension arrays. Convert at the boundary with ",[14,1487,1488],{},"df[\"Qty\"].astype(\"float64\")",[14,1490,1491],{},"to_numpy(dtype=float, na_value=np.nan)",[1437,1493,1494,1497,1498,930,1501,1504,1505,57],{},[1440,1495,1496],{},"Placeholder text."," Cells holding ",[14,1499,1500],{},"\"n\u002Fa\"",[14,1502,1503],{},"\"-\""," make a numeric column text under any backend; clean them first, as in ",[53,1506,1508],{"href":1507},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Freplace-placeholder-text-with-nan-in-pandas\u002F","Replace Placeholder Text with NaN in pandas",[59,1510,1512],{"id":1511},"performance-notes","Performance notes",[10,1514,1515,1516,1519,1520,57],{},"The nullable NumPy types cost one extra byte per value for the mask, which is negligible next to the cost of parsing the XML in the first place. Arrow-backed frames are usually smaller than default ones for text-heavy sheets, because Arrow stores strings contiguously rather than as one Python object each. Reading speed is dominated by the engine, not the backend: combining ",[14,1517,1518],{},"engine=\"calamine\""," with either backend is the fastest option for large files, as measured in ",[53,1521,1523],{"href":1522},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fspeed-up-pandas-excel-reads-with-the-calamine-engine\u002F","Speed Up pandas Excel Reads with the Calamine Engine",[59,1525,1527],{"id":1526},"conclusion","Conclusion",[10,1529,1530,1531,1534,1535,1538,1539,1541,1542,1544,1545,21,1547,197,1549,1551,1552,1554,1555,1557,1558,1560],{},"Blank cells force the classic NumPy types into floats, which is why integer columns show ",[14,1532,1533],{},".0",", IDs switch to scientific notation and TRUE\u002FFALSE flags become numbers. Pass ",[14,1536,1537],{},"dtype_backend=\"numpy_nullable\""," (or ",[14,1540,565],{},") to ",[14,1543,50],{}," to keep ",[14,1546,516],{},[14,1548,522],{},[14,1550,525],{}," columns with ",[14,1553,46],{}," for the gaps, or set the extension types per column with ",[14,1556,760],{},". Do it at read time rather than with ",[14,1559,921],{}," afterwards, decide how NA should behave in filters, and the frame writes back to Excel with empty cells where the source had them.",[59,1562,1564],{"id":1563},"frequently-asked-questions","Frequently asked questions",[10,1566,1567,1570],{},[1440,1568,1569],{},"Why does pandas read my integer Excel column as float?","\nBecause the column contains at least one blank cell. NumPy's int64 has no missing-value marker, so pandas switches to float64 and uses NaN. Read with dtype_backend=\"numpy_nullable\" or dtype={\"col\": \"Int64\"} to keep integers.",[10,1572,1573,1576,1577],{},[1440,1574,1575],{},"What is the difference between numpy_nullable and pyarrow in read_excel?","\nBoth keep integer, boolean and text columns with ",[1578,1579,1580],"na",{}," for missing values. numpy_nullable uses pandas' masked NumPy arrays (Int64, boolean, string); pyarrow stores columns in Apache Arrow memory, which is more compact for text and shares data with Parquet, Polars and DuckDB without copying.",[10,1582,1583,1586],{},[1440,1584,1585],{},"Why did my TRUE\u002FFALSE column turn into 1.0 and 0.0?","\nThe column has blanks, so the default reader stored it as float64. Ask for the boolean type when reading — dtype={\"Shipped\": \"boolean\"} or dtype_backend=\"numpy_nullable\" — because converting afterwards gives Int64, not boolean.",[10,1588,1589,1592,1593,1595,1596,1599,1600,57],{},[1440,1590,1591],{},"How do I filter on a nullable column that contains NA?","\nComparisons with a missing value return NA. Indexing treats NA as not selected — in both the mask and its inverse, so those rows vanish from both halves of a split. Decide what NA means and fill the mask first, for example split = (df",[73,1594,789],{}," > 4).fillna(False), then use df",[73,1597,1598],{},"split"," and df",[73,1601,1602],{},"~split",[59,1604,1606],{"id":1605},"related","Related",[1434,1608,1609,1615,1622,1629],{},[1437,1610,1611,1612,1614],{},"Up one level: ",[53,1613,56],{"href":55}," — every read_excel option that shapes the DataFrame.",[1437,1616,1617,1621],{},[53,1618,1620],{"href":1619},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcheck-excel-data-types-with-pandas\u002F","Check Excel Data Types with pandas"," — confirm each column has the type you expect.",[1437,1623,1624,1628],{},[53,1625,1627],{"href":1626},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-boolean-columns-from-excel-with-pandas\u002F","Read Boolean Columns from Excel with pandas"," — when flags arrive as text such as \"Yes\" and \"No\".",[1437,1630,1631,1635],{},[53,1632,1634],{"href":1633},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-converters-when-reading-excel-with-pandas\u002F","Use Converters When Reading Excel with pandas"," — per-column functions for values that need parsing.",[1637,1638,1639],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":69,"searchDepth":138,"depth":138,"links":1641},[1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653],{"id":61,"depth":138,"text":62},{"id":111,"depth":138,"text":112},{"id":458,"depth":138,"text":459},{"id":753,"depth":138,"text":754},{"id":865,"depth":138,"text":866},{"id":1031,"depth":138,"text":1032},{"id":1329,"depth":138,"text":1330},{"id":1431,"depth":138,"text":1432},{"id":1511,"depth":138,"text":1512},{"id":1526,"depth":138,"text":1527},{"id":1563,"depth":138,"text":1564},{"id":1605,"depth":138,"text":1606},"2026-10-02","Stop blank cells turning whole integer and TRUE\u002FFALSE columns into floats: read Excel with dtype_backend=\"numpy_nullable\" or \"pyarrow\", set Int64 and boolean per column, see why convert_dtypes after the read is too late for booleans, and write the NA values back as empty cells.","md",[1658,1660,1662,1664],{"q":1569,"a":1659},"Because the column contains at least one blank cell. NumPy's int64 has no missing-value marker, so pandas switches to float64 and uses NaN. Read with dtype_backend=\"numpy_nullable\" or dtype={\"col\": \"Int64\"} to keep integers.",{"q":1575,"a":1661},"Both keep integer, boolean and text columns with \u003CNA> for missing values. numpy_nullable uses pandas' masked NumPy arrays (Int64, boolean, string); pyarrow stores columns in Apache Arrow memory, which is more compact for text and shares data with Parquet, Polars and DuckDB without copying.",{"q":1585,"a":1663},"The column has blanks, so the default reader stored it as float64. Ask for the boolean type when reading — dtype={\"Shipped\": \"boolean\"} or dtype_backend=\"numpy_nullable\" — because converting afterwards gives Int64, not boolean.",{"q":1591,"a":1665},"Comparisons with a missing value return NA. Indexing treats NA as not selected — in both the mask and its inverse, so those rows vanish from both halves of a split. Decide what NA means and fill the mask first, for example split = (df[\"Qty\"] > 4).fillna(False), then use df[split] and df[~split].","use nullable dtypes when reading excel with pandas",null,{"breadcrumb":1669},[1670,1673,1676],{"name":1671,"item":1672},"Home","\u002F",{"name":1674,"item":1675},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":56,"item":55},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-nullable-dtypes-when-reading-excel-with-pandas",{"title":1679,"description":1680},"Read Excel with Nullable dtypes in pandas (Int64, boolean)","Keep integer and boolean columns intact when an Excel sheet has blanks: dtype_backend=\"numpy_nullable\" or \"pyarrow\" in read_excel, per-column Int64 and boolean, the convert_dtypes trap, and writing NA back to empty cells.","use-nullable-dtypes-when-reading-excel-with-pandas","getting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-nullable-dtypes-when-reading-excel-with-pandas\u002Findex",[1684,1685,1686],"reading-excel","pandas","missing-data","how-to","z7AsVb_OyBzFqLOAmeAE7fNhUwADhs8lHUnsMvphtAc",[1690,1693],{"title":1634,"path":1691,"stem":1692,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-converters-when-reading-excel-with-pandas","getting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fuse-converters-when-reading-excel-with-pandas\u002Findex",{"title":1694,"path":1695,"stem":1696,"children":-1},"Troubleshooting Common Python Excel Errors","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Findex",[1698,1700],{"path":1699,"title":1674},"\u002Fgetting-started-with-python-excel-automation",{"path":1701,"title":56},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas",[],[1704,1707,1710,1713,1716,1719],{"path":1705,"title":1706,"kind":1687},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-an-excel-file-from-a-url-or-bytes-in-python","Read an Excel File from a URL or Bytes in Python",{"path":1708,"title":1709,"kind":1687},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Ffind-the-last-row-with-data-in-openpyxl","Find the Last Row with Data in openpyxl",{"path":1711,"title":1712,"kind":1687},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fiterate-over-rows-and-columns-with-openpyxl","Iterate over Rows and Columns with openpyxl",{"path":1714,"title":1715,"kind":1687},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fread-cell-value-from-excel-with-openpyxl","Read a Cell Value From Excel With openpyxl",{"path":1717,"title":1718,"kind":1687},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-excel-column-names-with-pandas","Standardise Excel Column Names with pandas",{"path":1720,"title":1721,"kind":1722},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets","Extracting Data from Messy Excel Sheets","guide",1790972065379]