ホーム / お役立ち記事 / pandas の read_excel で #N/A が空になる理由|エラー値の数え方
Knowledge
pandas の read_excel で Excel を読むと、#N/A などのエラー値のセルは空(NaN)になり、keep_default_na=False を付けても戻りません。read_excel が読み込みの途中で、エラー値のセルを空の値に置き換えるためです。弊社の事例では、品名が #N/A の行を「0行」と数え、実際は約2万4千行ありました。原因の切り分け、見分け方、openpyxl で数えて XML と照らす直し方、落とし穴、確かめ方を、弊社の実測と公開の文書でまとめます。
pandas の read_excel で Excel を読むと、#N/A や #REF! などのエラー値のセルは、空(NaN)として読まれます。keep_default_na=False を付けても、エラー値のセルは空のままです。
原因は、read_excel が .xlsx を openpyxl で読むときの pandas の処理の中で、エラー値のセルが空の値に置き換わることです。弊社は pandas 3.0.5 のソースと実測で確かめました。
弊社の事例では、品名が #N/A の行を pandas で数えて「0行」と出しました。読み方を替えて数え直すと、実際は約2万4千行ありました。
直し方は、エラー値を数える列を read_excel で読まず、openpyxl で開いてセルの型がエラー値のセルを数えることです。数が合っているかは、シートの XML から数えた数と照らします。
この記事は、Excel の台帳やマスタを Python で集計・照合する仕組みを任されている業務の担当者・情報システムの担当者に向けて書きます。
原因の切り分け、見分け方、直し方、落とし穴、確かめ方を、弊社の実測と、pandas の文書とソース、ECMA-376(Office のファイルの形式の国際規格)でまとめます。
.xlsx のシートの XML では、エラー値のセルは型の印 t="e" を持ち、値の要素(v)にエラー値の文字が入ります。ECMA-376 の Part 1 は、型 e を「エラーを含むセル」と定めています(18.18.11 節)。
<c r="B6" t="e"><f>VLOOKUP(A6,マスタ!A:B,2,FALSE)</f><v>#N/A</v></c>
数式のセルは、数式と、最後に計算した結果のエラー値の両方を持ちます。弊社が XML を手で書いたファイルでは、値だけのエラー値のセルは、数式の無い t="e" のセルでした。
pandas の文書は、read_excel の engine を指定しないとき、.xlsx などは openpyxl で読むと書いています。pandas 3.0.5 のソースは、そのとき openpyxl を data_only=True で開きます。
同じソースは、セルの型がエラー値なら np.nan を返します(pandas/io/excel/_openpyxl.py の _convert_cell)。np.nan は、pandas が空として扱う値です。
この置き換えは、セルの値を取り出す段で起きます。na_values や keep_default_na が文字を空にするのは、その後の段です。そのため、この2つの引数を変えても、エラー値のセルは戻りません。
pandas の文書は、keep_default_na が False で na_values を指定しないとき「no strings will be parsed as NaN」と書いています。
エラー値のセルについての記述は、read_excel の頁にありません。この2点は、2026年11月1日に見た 3.0 系の文書(3.0.6)と最新の文書(3.1.0)で同じでした。
エラー値を np.nan にするソースの部分も、pandas 3.0.5 と 3.1.0 で同じ文字でした。
文字として入った “#N/A” は、型 s の普通の文字のセルです。こちらは、pandas が既定で空と読む文字の一覧に “#N/A” が入っているために、空になります。
pandas の文書の na_values の説明は、既定で空と読む文字として、“#N/A”・“N/A”・“NA”・“NULL” など19個を挙げています(空の文字を含む)。
| セル | keep_default_na=False のとき |
|---|---|
| エラー値の #N/A(型 e) | 空のまま |
| 文字の “#N/A”(型 s) | 文字のまま残る |
見た目は同じ #N/A でも、空になる理由が違います。そのため、効く直し方も違います。
弊社は 2026年11月1日に、pandas 3.0.5・openpyxl 3.1.5(Python 3.14.6)と合成のファイルで試しました。品名の列には、次のセルを入れました。
| 品名の列 | セルの数 |
|---|---|
| VLOOKUP の結果の #N/A | 12 |
| 値で貼り付けた #N/A | 3 |
| 数式の結果の #REF! | 2 |
| 文字の “#N/A” | 2 |
| 空のセル | 4 |
| 普通の品名 | 77 |
ファイルは、ECMA-376 の形(t="e" と値の要素)で XML を書いた物と、それを LibreOffice 26.2.4.2 で保存し直した物の2つです。どちらも同じ結果でした。Excel では開いていません。
| 読み方 | 品名の列の数え方 |
|---|---|
| read_excel の既定 | 空 23・“#N/A” と一致 0 |
| keep_default_na=False | 空 17・“#N/A” と一致 2・空の文字 4 |
| na_filter=False | keep_default_na=False と同じ |
| dtype=str・dtype=object | 既定と同じ |
| openpyxl(data_only=True) | 型 e の #N/A 15・#REF! 2、型 s の “#N/A” 2 |
| XML を直に読む | t="e" の #N/A 15・#REF! 2 |
既定の読み方の空23は、エラー値17・文字の “#N/A” 2・空のセル4の和です。空になった後では、どれが何かを見分けられません。
keep_default_na=False を付けると、“#N/A” と一致する数は0から2になります。直ったように見えますが、2は文字の “#N/A” で、エラー値の15セルは空のままです。
pandas 3.0.5 では、どの読み方でも警告を出しませんでした。3.1.0 では、engine を指定しない read_excel に、既定の engine が将来 calamine へ変わる予告の警告が出ます。
既定の読み方は、国の列の “NA”(ナミビアの国の符号として入れた3セル)と、備考の列の “N/A” 5セル・“NULL” 1セルも空にしました。
2026年10月、お客様から受け取った単価のマスタ(Excel)を検品したとき、品名が #N/A の行を pandas で数え、「0行」と出しました。
同じころ、照合に使う正解の一覧も pandas で読んで作っていました。一覧の全行を照らすと、品名の #N/A が、正解の側だけ空になっていました。
正解の一覧は XML を直に読む方式に、検品の数は openpyxl で数える方式に替えました。数え直すと、品名が #N/A の行は約2万4千行ありました。
0という数はもっともらしく見え、数え方の誤りは、全行の照合まで表に出ませんでした。
read_excel の既定と keep_default_na=False の2通りで読み、同じセルがどう読まれるかを比べます。
| 既定と keep_default_na=False(表では False)での見え方 | 疑う所 |
|---|---|
| 既定で空、False でも空 | エラー値のセル(型 e) |
| 既定で空、False では “#N/A” などの文字 | 既定で空と読む文字の一覧 |
| 既定で空、False では空の文字 | 空のセルか、計算結果の無い数式 |
弊社が試した読み方(engine が openpyxl=pandas 3.0.5 の既定)では、keep_default_na=False で空(NaN)が残ったのは、エラー値のセルだけでした。
空のセルと計算結果の無い数式は、空の文字(“”)になりました。
ただし、エラー値の種類(#N/A か #REF! か)は、pandas では分かりません。種類ごとに数えるには、次の直し方に進みます。
この記事のために書いたコードの手順は次のとおりです。
data_only=True で開き、セルの型(data_type)が e のセルを、列とエラー値の種類ごとに数えるt="e" のセルから数え、2つが一致することを確かめるopenpyxl の文書は、data_only は、数式のセルが数式を持つか、Excel が最後にシートを読んだときに保存された値を持つかを決める、と書いています。
data_only を付けないと、数式のセルは数式の文字で返ります。弊社の実測(XML を書いた方のファイル)では、型 e と読めたのは値だけの3セルで、数式の14セルは数式の文字でした。
import collections
import re
import sys
import zipfile
import xml.etree.ElementTree as ET
import pandas as pd
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
NS = {"m": "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
"r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"p": "http://schemas.openxmlformats.org/package/2006/relationships"}
def read_keep_errors(path, sheet, header_row=1):
"""エラー値のセルを "#N/A" などの文字のまま残して読む(空のセルは None)。"""
wb = load_workbook(path, read_only=True, data_only=True)
rows = [[c.value for c in row] for row in wb[sheet].iter_rows(min_row=header_row)]
wb.close()
width = max(len(r) for r in rows)
rows = [r + [None] * (width - len(r)) for r in rows]
return pd.DataFrame(rows[1:], columns=rows[0], dtype=object)
def count_errors_openpyxl(path, sheet):
"""列の文字とエラー値の種類ごとに、エラー値のセル(型 e)を数える。"""
wb = load_workbook(path, read_only=True, data_only=True)
n = collections.Counter()
for row in wb[sheet].iter_rows():
for c in row:
if getattr(c, "data_type", None) == "e":
n[(get_column_letter(c.column), c.value)] += 1
wb.close()
return n
def count_errors_xml(path, sheet):
"""同じ数を、シートの XML の t="e" のセルから数える(openpyxl を使わない)。"""
z = zipfile.ZipFile(path)
wb = ET.fromstring(z.read("xl/workbook.xml"))
rid = next(s.get(f"{{{NS['r']}}}id") for s in wb.iter(f"{{{NS['m']}}}sheet") if s.get("name") == sheet)
rels = ET.fromstring(z.read("xl/_rels/workbook.xml.rels"))
target = next(r.get("Target") for r in rels.iter(f"{{{NS['p']}}}Relationship") if r.get("Id") == rid)
part = target.lstrip("/") if target.startswith("/") else "xl/" + target
n = collections.Counter()
with z.open(part) as f:
for _, el in ET.iterparse(f):
if el.tag == f"{{{NS['m']}}}c":
if el.get("t") == "e":
v = el.find("m:v", NS)
n[(re.match(r"[A-Z]+", el.get("r")).group(), v.text if v is not None else None)] += 1
el.clear()
return n
if __name__ == "__main__":
path, sheet = sys.argv[1], sys.argv[2]
a, b = count_errors_openpyxl(path, sheet), count_errors_xml(path, sheet)
df = pd.read_excel(path, sheet_name=sheet)
print("列 エラー値 openpyxl XML")
for key in sorted(set(a) | set(b)):
print(*key, a[key], b[key])
print("pandas の既定で空と読んだセル(列ごと):", df.isna().sum().to_dict())
sys.exit(0 if a == b else 1)
使い方は python excel_errors.py マスタ.xlsx シート名 です。列とエラー値の種類ごとに openpyxl と XML の数を並べ、食い違えば終了コード1を返します。
事例で替えた先は、正解の一覧が XML を直に読む方式、検品の数が openpyxl で数える方式でした。このコードはこの記事のために書いたもので、2つを並べて照らす形にまとめています。
弊社は 2026年11月1日に、上の実測の2つのファイルと、openpyxl で書いたファイルで、このコードを確かめました。
確かめたのは、2つの数え方が #N/A 15・#REF! 2 で一致すること、read_keep_errors がエラー値を “#N/A” などの文字で残し、空のセルを None にすることです。
data_only=False で開くように壊した版では、openpyxl の数が #N/A 3・#REF! 0 になり、終了コード1を返しました。照らし合わせが、読み方の誤りを拾います。
read_keep_errors の表では、エラー値の “#N/A” と文字の “#N/A” は同じ文字になります。2つを分けて数えるときは、型 e だけを数える count_errors_openpyxl を使います。
事例で踏んだのは1つ目と2つ目です。3つ目からは、この記事のための 2026年11月1日の実測で確かめたものです。
3つ目の、計算結果を持たないファイルができる理由は、Python で値を書いた Excel の計算結果が古いままの理由にまとめています。
短い時間で確かめるなら、ファイルを上のコードに通します。openpyxl と XML の数が、列とエラー値の種類ごとに一致すれば、終了コードは0です。
0件と報告する前には、XML の t="e" のセルが本当に0かを、このコードの出力で見ます。
ただし、2つの数え方はどちらも、ファイルに残った計算結果を読みます。計算結果の無い数式のセルは、どちらも数えません。
弊社の事例で誤りが見つかったのは、一覧の全行を照らしたときでした。数の照合に加えて行ごとに照らすと、どの行が抜けたかまで分かります。
読むファイルが少なく、エラー値を数える列が決まっているなら、自力で足ります。上のコードは、pandas と openpyxl と Python の標準のライブラリだけで動きます。
自力の壁は次の3つです。
外注の分かれ目は、Excel から数えた数を、別の読み方と全行で照らす工程を、自社で持ち続けられるかです。外注するなら、見積もりを比べる前に次の3点を確かめてください。
ゴムマリでは、2つ以上の台帳・帳票を全件突き合わせ、差分と要確認の箇所だけを返す仕事を、データ突合・照合チェックとして承ります。
お渡しするのは、全件の突合結果(一致・不一致・要確認の別)と、どの項目がどう食い違うかを確認すべき理由つきで示した差分の一覧です。
料金は20万円から、期間は1週間です。進め方は相談からご連絡ください。
手元の Excel を1本選び、エラー値がありそうな列を、read_excel の既定と keep_default_na=False の2通りで読んでください。
keep_default_na=False でも空が残る列には、弊社が試した読み方(engine が openpyxl)では、エラー値のセルがあります。engine を calamine にすると、エラー値は空の文字になり、この見分け方は使えません。
そのうえで、上のコードで種類ごとに数え、XML の数と合うかを見ます。
帳票を自動で作るときに、数字が正しいことを仕組みで確かめる方法は、既存Excel様式を自動生成するときの品質保証にまとめています。
関連記事: Python で値を書いた Excel の計算結果が古いままの理由/既存Excel様式を自動生成するときの品質保証
ワークスアプリケーションズで大手企業向けERPの営業と導入を担当し、外資系SaaS企業と国内大手放送局で販路と新規事業の立ち上げ、AI学習用データを扱うAIスタートアップ2社でマーケティングと営業を担った。2024年10月から生成AIの実装に入り、いまは大手企業の現場で、AIエージェントを要件定義から運用まで手がけている。
FAQ
文字として入った "#N/A" は残りますが、エラー値のセルは残りません。弊社が pandas 3.0.5 で試すと、エラー値のセルは keep_default_na=False・na_filter=False・dtype=str のどれを付けても空でした。エラー値は、openpyxl を data_only=True で開いて読みます。
残りません。弊社が見た pandas 3.0.5 のソースでは、エラー値のセルは、読み込みの途中で空の値に置き換わります。na_values と keep_default_na の文書は、文字を空として読むかを決める引数として書かれています。外して効くのは、文字の "#N/A" です。
足りません。既定の読み方の isna は、エラー値・空のセル・"NA" などの文字をまとめて数えます。弊社の実測では、品名の列の空23は、エラー値17・文字の "#N/A" 2・空のセル4の和でした。エラー値の種類(#N/A か #REF! か)も分かりません。
ファイルが数式の計算結果を持っていない恐れがあります。openpyxl で数式を書いたファイルは、計算結果を持ちません。弊社が試したファイルでは、計算結果の無い =NA() のセルを、openpyxl の data_only=True は None、pandas は空と読みました。
Related
Excel の台帳を Notion へ移すには、表の形とキーを決め、型を決めてリレーションと一緒に取り込み、数式と集計を作り直してから、件数・キー・値・先頭のゼロ・空の値・リレーション・計算の結果の7つで照合します。Notion の公式ヘルプと API の文書の決まりと、弊社が管理表の約2千明細を移し、Notion で計算した残が移したすべてのロットで元の表と一致した照合の手順でまとめます。
活用ガイドNotion AI に書類を作らせて照合するときは、手順を Notion AI への指示書、読めば決まる処理を添付のコード、1回ごとの結果を記録のデータベースに分けます。弊社の導入の事例の試作では、同じ題材で、文章の指示は発注番号を3回のうち2回誤り、添付のコードは2回とも誤りませんでした。分け方の問い、進める手順、誤りを仕込む照合の進め方を、弊社の記録でまとめます。