0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Excelテンプレートの数式をpycelでテストする:使えない関数(WORKDAY・COUNTAなど)と置き換え方

0
Posted at

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)を活用して作成し、記事中のコードと計算結果は実際に動かして確認しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?