Excelのテンプレート(見積書・工程表・日報など)を openpyxl で作るとき、数式が本当に正しい値を出すかを、Excelを開かずに確かめたいことがあります。
そこで使えるのが pycel です。xlsx の数式を Python で計算してくれるので、assert で期待値と比べるテストが書けます。
ただ、pycel には まだ用意されていない関数 があります。この記事では、実際にテンプレートを作るなかで引っかかった関数と、pycel でも Excel・Googleスプレッドシートでも同じ結果になる置き換え方をまとめます。
確認した環境:Python 3.12、pycel 1.0b30、openpyxl。
基本の形
from pycel import ExcelCompiler
xc = ExcelCompiler("estimate_sample.xlsx")
# 例:合計のセルが、手で計算した値と同じか
assert xc.evaluate("見積!F30") == 1386000
evaluate("シート名!セル") で、そのセルの数式を計算した値が返ります。依存しているセルも、たどって計算してくれます。
pycel 1.0b30 で使えなかった関数
小さなブックを作って、1つずつ試しました。
| 数式 | 結果 |
|---|---|
=WORKDAY(A1,A2) |
UnknownFunction |
=NETWORKDAYS(A1,A1+10) |
UnknownFunction |
=TIME(8,30,0) |
UnknownFunction |
=COUNTA(A1:A3) |
UnknownFunction |
=WEEKDAY(A1,2) |
エラー(第2引数を受け取れない) |
=WEEKDAY(A1) |
動く |
=SUMIFS(...) =COUNTIFS(...)
|
動く |
=INDEX(...,MATCH(...,0)) |
動く |
=ROUNDUP(42/14,0) |
動く(3.0) |
確認に使ったコードはこれだけです。
import openpyxl, datetime
from pycel import ExcelCompiler
wb = openpyxl.Workbook(); s = wb.active; s.title = "S"
s["A1"] = datetime.date(2026, 10, 23); s["A2"] = 3
tests = {"B1": "=WORKDAY(A1,A2)", "B2": "=COUNTA(A1:A3)", "B3": "=WEEKDAY(A1,2)"}
for k, v in tests.items():
s[k] = v
wb.save("t.xlsx")
xc = ExcelCompiler("t.xlsx")
for k, v in tests.items():
try:
print(k, v, xc.evaluate("S!" + k))
except Exception as e:
print(k, v, "ERR", type(e).__name__)
置き換え方
WEEKDAY(日付,2) → MOD で月曜=1 にする
WEEKDAY(日付) は日曜=1〜土曜=7 です。月曜=1〜日曜=7 にしたいときは、次のように書き換えます。
=MOD(WEEKDAY(A1)+5,7)+1
2026/10/9(金)で 5 になることを確かめました。土日の判定は「この値が 6 以上」でできます。
COUNTA → COUNTIFS(範囲,"<>")
=COUNTIFS(A1:A60,"<>")
空でないセルを数えます。
TIME → 分を 1440 で割る
=(8*60+30)/1440
TIME(8,30,0) と同じ 0.354166… になります。
WORKDAY・NETWORKDAYS → 「稼働日カレンダー」シートと INDEX/MATCH
ここがいちばん手間がかかります。工程表の「開始日から○稼働日後の終了予定日」を出すのに、非表示のカレンダーシートを用意しました。
- A列:日付(1日ずつ、数年分)
- B列:稼働日なら 1、休みなら 0
- C列:B列の累計
B1: =IF(OR(MOD(WEEKDAY(A1)+5,7)+1>=6,COUNTIFS(休工日!$A:$A,A1)>0),0,1)
C1: =B1
C2: =C1+B2
終了予定日は「開始日の累計+日数−1 になる最初の日」です。
=INDEX(Cal!A:A, MATCH(INDEX(Cal!C:C, MATCH(開始日,Cal!A:A,0)) + 日数 - 1, Cal!C:C, 0))
2026/10/9(金)開始・3稼働日・10/12 を休工日にすると、10/10〜12 を飛ばして 10/14 になりました。手で数えた日付と同じです。
この形は pycel でも Excel でも Googleスプレッドシートでも同じ結果になります。ただし、開始日が休みの日のときの扱い(次の稼働日から数えるかなど)は、別に決めておく必要があります。
ほかに気をつけたこと
-
累計の列が長いと再帰が深くなる:1,500行ほどの累計が続くと、pycel の計算で Python の再帰の上限に当たりました。テストの最初で
sys.setrecursionlimit()を引き上げて回避しています。 -
空のセルを条件に使う式:テンプレートによっては、空のセルが条件に入ると pycel 側で止まることがありました。
A1+0やB1&""のように型をそろえると通りました(Excel での結果は変わりません)。 - Googleスプレッドシートの条件付き書式は、ほかのシートを参照できない:pycel とは別の話ですが、「今日の列に色」のような書式は、同じシートのセルを参照する形にしました。
まとめ
- pycel を使うと、Excel を開かずに数式の値をテストできる
- WORKDAY・NETWORKDAYS・TIME・COUNTA・WEEKDAY(,2) は pycel 1.0b30 では計算できなかった
- MOD・COUNTIFS・分÷1440・稼働日カレンダー+INDEX/MATCH に置き換えると、pycel・Excel・Googleスプレッドシートで同じ結果になった
関数の対応状況は pycel のバージョンで変わる可能性があります。使う前に、小さなブックで1つずつ試すのがおすすめです。
※この記事は、AI(Claude)を活用して作成し、記事中のコードと計算結果は実際に動かして確認しています。