2025, Oct 30 01:00

Exporting Numeric Data from Polars to Excel: Control Formatting, Avoid Trailing Zeros and Separators

Export Polars DataFrames to Excel without surprises: fix trailing zeros, prevent thousands separators, use float_precision and dtype_formats for stable numbers.

Exporting numeric data from Polars to Excel often looks trivial until formatting gets in the way. A common scenario is that Float64 values rounded to two decimals in Python suddenly gain an extra trailing zero in Excel, and Int64 or Float64 columns that had no thousands separators start displaying them in the worksheet. The underlying values are fine; the mismatch is in how Excel renders them.

Repro case

The following pipeline reads an Excel file with mixed dtypes, computes a grouped sum, rounds all Float64 columns to two decimals, flags mismatches, and writes the result back to .xlsx. The logic is sound, yet the output sheet may show an unexpected trailing zero and insert thousands separators.

import polars as pl
import polars.selectors as sx
sheet = pl.read_excel('../Documents/abc.xlsx', engine='openpyxl')
sheet = sheet.with_columns(
    sum_per_group=pl.col('Amount').sum().over('ID').sort_by('ID')
)
sheet = sheet.with_columns(
    sx.by_dtype(pl.Float64).round(2)
)
sheet = sheet.with_columns([
    pl.when(pl.col('sum_per_group') != pl.col('Total Amount'))
    .then(pl.lit('No'))
    .otherwise(pl.lit('Yes'))
    .alias('Flag')
])
sheet.write_excel('abc_v2.xlsx')

What actually goes wrong

There are two separate issues. First, Excel cell content and its representation are different things. Polars writes numbers; Excel decides how to show them based on number formats. If you do not explicitly set the cell format, Excel picks something that may not align with your expectations. That explains both the extra trailing zero and the unexpected thousands separator.

Second, Excel’s defaults are not the same as what you see in your Python console. Excel does not automatically apply a thousands separator to a number as you type it, and the displayed number of decimal digits often depends on the column width and the cell format that Excel settles on. When a worksheet ends up showing a thousands separator or an extra zero, you are looking at formatting, not changed values.

One more subtlety to keep an eye on is the difference between numbers and text. If a step in your pipeline makes Excel receive text instead of a number, Excel can no longer control that value with numeric formats. If you notice the thousands separator appearing right after the rounding step, treat that as a hint to verify that the output remains numeric and not text.

The practical fixes that work today

For the trailing zero problem, control the export precision directly when writing the workbook. A straightforward way is to set float_precision on export. This keeps Float64 columns at two decimals without adding unexpected digits.

# Keep the same pipeline as above, then export with precision
sheet.write_excel('abc_v2.xlsx', float_precision=2)

For the thousands separator, the most reliable approach at the moment is to avoid Polars’ Excel writer when you need precise on-sheet formatting. Use pandas.to_excel or export to CSV with Polars instead. According to the current guidance, the default xlsxwriter engine used by polars.write_excel is not fully mature yet, so using pandas.to_excel or polars.write_csv is the pragmatic choice if you need a clean numeric layout without separators.

# Alternative path when you need predictable rendering in Excel
# 1) Write CSV directly from Polars
sheet.write_csv('abc_v2.csv')

If you stay with Polars’ Excel export, steer formatting via the arguments intended for it. In particular, focus on float_precision and dtype_formats in polars.DataFrame.write_excel. The mapping lets you define the Excel number format per dtype, which governs both decimal places and separators. A specific example suggested for controlling numeric appearance is to map Int64 and Float64 to an explicit Excel number format string.

# Example of controlling Excel formats per dtype
# Choose a format string that matches how you want numbers displayed
# and avoids thousands separators.
sheet.write_excel(
    'abc_v2.xlsx',
    float_precision=2,
    dtype_formats={
        pl.Int64: '0',
        pl.Float64: '0'
    }
)

This keeps numeric content numeric and lets Excel render it using the format you specify, rather than the sheet’s defaults. If you require two visible decimals, adjust the Float64 format accordingly to represent exactly what you want displayed, and keep using float_precision to avoid undesired digits.

Why this matters

Data pipelines often get judged at the last mile: the spreadsheet a stakeholder opens. If numeric representation drifts from the Python view, you risk misinterpretation, validation churn, and extra rework. Separating raw values from display choices and making formatting explicit keeps exports predictable and repeatable across environments.

Takeaways

Treat Excel rendering as a formatting concern and make it explicit at export time. Use float_precision in Polars to control trailing digits. When the worksheet must avoid thousands separators or adhere to a strict display standard, rely on number formats through dtype_formats or, if you need a smoother path right now, prefer pandas.to_excel or switch to CSV via polars.write_csv. Ensure the pipeline preserves numeric dtypes all the way to the file, especially around any steps that could accidentally turn numbers into text. Making these decisions deliberately will save you from last-minute surprises in the file your users actually open.

The article is based on a question from StackOverflow by JeffCh and an answer by rotabor.