How-to
Formatting And Charting Excel Reports With PythonDeep dive

Show Zeros as Dashes or Hide Them in Excel

Make zero values in a generated Excel report less noisy without changing the data: show them as a dash with the zero section of a number format, hide them in chosen cells or across a whole sheet, keep dashes aligned with the numbers around them, and understand how displayed dashes, hidden zeros, empty strings and true blanks behave in totals, averages and charts.

A financial report full of 0.00 is hard to read: the figures that matter are lost among rows of zeros. Printed accounts solve this with a dash — "–" — in place of a zero, and some reports leave such cells empty altogether. Both are display choices, and Excel can make them without touching the underlying values, so totals and formulas keep working. The common shortcut in generated reports — writing an empty string or None instead of 0 — looks the same on screen but behaves differently: averages change, counts change, and formulas that do arithmetic on the cell can fail. This page shows zeros as dashes with a number format, hides them in chosen cells or a whole sheet, keeps the dashes aligned with the numbers around them, and checks with LibreOffice how each approach affects the calculations. It belongs to Applying Number and Date Formats in Excel.

Prerequisites

Bash
pip install openpyxl pandas

LibreOffice (soffice) is used to recalculate the workbook and export what each cell displays, so the formats can be checked without opening Excel.

Step 1 — A dash from the zero section

A number format has up to four sections separated by semicolons: positive; negative; zero; text. Put a dash in the third and zeros display as a dash while staying zero:

Python
from openpyxl import Workbook

DASH = '#,##0.00;-#,##0.00;"–"'
DASH_ALIGNED = '#,##0.00_);(#,##0.00);"–"_)'
HIDDEN = '#,##0.00;-#,##0.00;'

wb = Workbook()
ws = wb.active
ws.title = "Formats"
ws.append(["Value", "General", "Dash", "Dash, aligned", "Hidden"])
for v in [1250.5, 0, -40, 0, 87.25]:
    ws.append([v, v, v, v, v])
for row in ws.iter_rows(min_row=2):
    row[2].number_format, row[3].number_format, row[4].number_format = DASH, DASH_ALIGNED, HIDDEN
print(DASH, "|", DASH_ALIGNED, "|", HIDDEN)

In DASH, the third section is a quoted en dash, so a zero shows "–". DASH_ALIGNED follows the accounting convention of negatives in parentheses: _) inserts a space the width of a closing parenthesis after positive numbers and the dash, so that digits and dashes line up with negatives such as (40.00) above and below them. HIDDEN leaves the third section empty — the semicolon is there, but nothing after it — so a zero displays as nothing at all. A dash written with "-" instead of the en dash works too; the en dash is simply the typographic convention in financial statements. The section syntax is explained further in Write Custom Number Format Codes in Excel with Python.

The same values under three formats The values 1,250.50, 0, minus 40, 0 and 87.25. With the dash format zeros show as an en dash. With the aligned format negatives show in parentheses and positives and dashes leave room for the parenthesis. With the hidden format zeros show nothing. A B C D 1 Value Dash Dash, aligned Hidden 2 1250.5 1,250.50 1,250.50 1,250.50 3 0 – – 4 -40 -40.00 (40.00) -40.00 5 0 – – 6 87.25 87.25 87.25 87.25

Step 2 — Hidden zero, empty string or true blank?

There are three ways to make a cell look empty, and they are not equivalent. Build a column of each, add formulas over them, and let LibreOffice calculate:

Python
ws2 = wb.create_sheet("Behaviour")
ws2.append(["Zero, hidden by format", 'Formula returning ""', "Blank (None)"])
for v in [100, 0, 50]:
    ws2.append([v, v if v else '=""', v if v else None])
for c in ws2["A"][1:]:
    c.number_format = HIDDEN
labels = ["SUM", "AVERAGE", "COUNT", "COUNTA", "Cell + 1"]
formulas = ["=SUM({c}2:{c}4)", "=AVERAGE({c}2:{c}4)", "=COUNT({c}2:{c}4)", "=COUNTA({c}2:{c}4)", "={c}3+1"]
for r, (label, f) in enumerate(zip(labels, formulas), start=6):
    ws2[f"D{r}"] = label
    for col in "ABC":
        ws2[f"{col}{r}"] = f.format(c=col)
wb.save("zeros.xlsx")

import csv, subprocess
AS_SHOWN = "csv:Text - txt - csv (StarCalc):44,34,76,1,,0,false,true,true,false,false,-1"
subprocess.run(["soffice", "--headless", "--convert-to", AS_SHOWN, "--outdir", "calc", "zeros.xlsx"], check=True, capture_output=True)
for name in ["Formats", "Behaviour"]:
    print(f"--- {name}")
    with open(f"calc/zeros-{name}.csv", encoding="utf-8") as f:
        for row in csv.reader(f):
            print(" | ".join(f"{c:>12}" for c in row[:5]))

The -1 at the end of the filter options exports every sheet to its own CSV file, with each cell as displayed. On the Formats sheet the dashes and hidden zeros appear exactly as designed. On the Behaviour sheet the three columns look identical — 100, nothing, 50 — but calculate differently. The hidden zero is still a number: SUM is 150, AVERAGE is 50 because it divides by three, COUNT is 3, and adding 1 to it gives 1. The formula returning "" — the familiar =IF(A2=0,"",A2) pattern — produces text: SUM ignores it, but AVERAGE becomes 75 because it only averages two numbers, COUNT drops to 2, COUNTA still counts it, and cell + 1 fails with #VALUE!. The true blank behaves like the empty text for SUM, AVERAGE and COUNT, but is not counted by COUNTA, and cell + 1 treats it as zero. Note that writing the Python value "" with openpyxl is not the same as the formula: openpyxl stores it as a string cell with no content, which LibreOffice reads as a blank — one more reason not to rely on empty strings to mean anything.

Three cells that look empty, calculated A zero hidden by its number format is still counted by AVERAGE and COUNT and works in arithmetic. Empty text from a formula is skipped by AVERAGE and COUNT, counted by COUNTA, and breaks arithmetic with VALUE errors. A true blank is skipped by AVERAGE, COUNT and COUNTA and counts as zero in arithmetic. AVERAGE(100, x, 50) COUNT x + 1 0, hidden by format 50 3 1 formula returning "" 75 2 #VALUE! blank (None) 75 2 1

Step 3 — Choose what the cell should be, then how it looks

The table makes the rule clear. If the value is genuinely zero — no sales of a product this month, a cost line that was nil — write 0 and use a number format to show a dash or nothing; averages and counts then include it, as they should. If the value is missing — no data, not applicable — write a true blank, so averages and counts leave it out. Never use empty text — a "" value or a formula returning "" — to stand for zero in a numeric column. From pandas this means keeping zeros as zeros and missing values as NaN, which to_excel writes as true blanks:

Python
import pandas as pd
from openpyxl import load_workbook

df = pd.DataFrame({"Product": ["Bolts", "Nuts", "Washers", "Rivets"],
                   "Jul": [1200.0, 0.0, 310.0, None], "Aug": [980.0, 0.0, 0.0, 150.0], "Sep": [1105.5, 45.0, 0.0, 0.0]})
df.to_excel("sales_q3.xlsx", index=False)
wb = load_workbook("sales_q3.xlsx")
ws = wb.active
for row in ws.iter_rows(min_row=2, min_col=2):
    for cell in row:
        cell.number_format = DASH_ALIGNED
wb.save("sales_q3.xlsx")
subprocess.run(["soffice", "--headless", "--convert-to", AS_SHOWN, "--outdir", "calc", "sales_q3.xlsx"], check=True, capture_output=True)
from pathlib import Path
print(Path("calc/sales_q3-Sheet1.csv").read_text(encoding="utf-8"))

The zeros now read as aligned dashes — Nuts in July and August, Washers in August and September — while Rivets' July, missing rather than zero, stays empty. A reader can tell the difference between "nothing sold" and "no data", which a sheet of blanks or a sheet of zeros would hide. How pandas writes NaN and infinity in general is covered in Control How NaN and Infinity Appear in Excel Output.

What to write, and how to show it If the value is genuinely zero, write 0 and use a number format to show a dash or nothing. If the value is missing or not applicable, write a true blank so averages and counts skip it. Never use empty text to stand for zero in a numeric column. What does the cell mean? a real zero write 0, format as dash counted in averages no data write a blank skipped by averages tempted by "" avoid empty text breaks arithmetic

Step 4 — Hide every zero on a sheet

To hide zeros across an entire sheet — including formula results — Excel has a sheet-level option, Show a zero in cells that have zero value. openpyxl exposes it on the sheet view:

Python
wb = load_workbook("sales_q3.xlsx")
wb.active.sheet_view.showZeros = False
wb.save("sales_q3_nozeros.xlsx")

import zipfile
with zipfile.ZipFile("sales_q3_nozeros.xlsx") as z:
    xml = z.read("xl/worksheets/sheet1.xml").decode()
print("showZeros written:", 'showZeros="0"' in xml)

The setting is saved in the sheet's XML as showZeros="0" and Excel honours it when the file is opened; LibreOffice does not read it from .xlsx files, which is why the CSV export cannot be used to check it. Because it is a view setting, it applies to every cell on the sheet and a reader can turn it back on in Excel's options — useful for a dashboard sheet, but a number format is the better tool when only some cells should change. Neither approach alters values, so charts still plot zeros as zero; if a chart should leave a gap instead, the cell needs to be blank or #N/A, as in Build a Rolling 12-Month Chart on an Excel Dashboard.

Common pitfalls

  • Writing "" instead of 0. It is text: averages, counts and arithmetic change.
  • Forgetting the third section. Two sections apply the negative format to zeros too.
  • Misaligned dashes. Use _) so dashes line up with parenthesised negatives.
  • Hiding missing data as zeros. Missing values belong as blanks, not hidden zeros.
  • Expecting charts to skip hidden zeros. They plot the value, not the display.

Conclusion

To show zeros as dashes, give the number format a third section such as "–", with _) padding if negatives use parentheses; to hide them, leave the third section empty, or turn off showZeros for a whole sheet. Keep real zeros as 0 and missing values as true blanks — never empty strings — so that averages, counts and arithmetic stay correct, and check the displayed result with a LibreOffice CSV export.

Frequently asked questions

How do I display zeros as dashes in Excel with Python? Set a number format with a zero section, for example #,##0.00;-#,##0.00;"–", using openpyxl's cell.number_format.

How do I hide zero values without deleting them? Use a number format whose third section is empty, such as #,##0.00;-#,##0.00;, or set ws.sheet_view.showZeros = False to hide all zeros on a sheet.

Is a hidden zero the same as an empty cell? No. A hidden zero is still counted by AVERAGE and COUNT, while blank cells and empty strings are skipped; empty strings also cause VALUE errors in arithmetic.

Why do my dashes not line up with negative numbers in parentheses? Add ) after the positive and zero sections so they reserve the width of a closing parenthesis, as in #,##0.00);(#,##0.00);"–"_).

More on number & date formats

More on number & date formats →