np.busday_count是最优解,需转datetime64[d]、处理nat掩码、结束日+1,并手动传入节假日列表;pd.bdate_range和apply仅适用于小数据验证。

用 pd.bdate_range 手动计算工作日差值太慢又易错
直接用 pd.bdate_range 生成两个日期间所有工作日再取长度,看似直观,但遇到上万行数据时性能断崖式下降;更麻烦的是,它默认不处理节假日、周末跨年边界,且对 NaT 值会直接报 TypeError: Cannot convert input to Timestamp。实际项目里几乎没人这么干。
np.busday_count 是最稳的底层方案
它基于 NumPy 实现,原生支持数组广播,能一次性处理整列日期,且明确区分起止日是否包含(weekmask 和 holidays 可配)。关键点在于输入必须是 np.datetime64 类型,不是 pd.Timestamp 或 datetime.date。
- 先用
.dt.date或.dt.floor('D')对齐到日粒度,再转np.array(..., dtype='datetime64[D]') - 注意:
np.busday_count的begindates和enddates是左闭右开区间(即包含起始日,不包含结束日),所以若要算「从 A 到 B(含)」的工作日数,得把结束日 +1 天 - 遇到
NaT时,需提前用df['col'].notna()掩码过滤,否则返回NaN且不报错,容易漏掉异常
import numpy as np
import pandas as pd
<p>df['start'] = pd.to_datetime(df['start'])
df['end'] = pd.to_datetime(df['end'])</p><p>mask = df['start'].notna() & df['end'].notna()
dates1 = df.loc[mask, 'start'].dt.floor('D').values.astype('datetime64[D]')
dates2 = (df.loc[mask, 'end'] + pd.Timedelta(days=1)).dt.floor('D').values.astype('datetime64[D]')</p><h1>默认周一至周五,如需自定义节假日:</h1><h1>holidays = ['2023-01-22', '2023-01-23']</h1><p>workdays = np.busday_count(dates1, dates2) # 返回 ndarray</p><p>df.loc[mask, 'workday_diff'] = workdays</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8054" title="python-parallelization"><img
src="https://img.php.cn/upload/skill/000/000/081/179181013081932.jpg" alt="python-parallelization" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8054" title="python-parallelization" class="overflowclass">python-parallelization</a>
<p class="overflowclass">将顺序 Python 代码转换为并行/并发实现,用于在需要并行化 Python 代码、提升性能时使用。</p>
</div>
<a rel="nofollow" href="/xiazai/skill8054" title="python-parallelization" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
pd.DateOffset 和 apply 仅适合小数据或调试
有人想用 pd.bdate_range(start, end).size 包在 apply 里,这在几百行内可行,但每行都新建一个日期范围对象,内存和 CPU 开销极大。而且 pd.bdate_range 对 NaT 敏感,apply 中不加 try/except 就会中断整个列计算。
- 仅建议用于单条记录验证逻辑,比如检查某两个日期之间是不是真的 5 个工作日
- 如果非要用,务必加
errors='coerce'并配合isna()判断,例如:pd.bdate_range(x, y, freq='B').size if pd.notna(x) and pd.notna(y) else np.nan - 别碰
pd.offsets.BusinessDay(n=...)反向推算——它不解决「两固定日期间差值」问题,只解决「从某日往前/后推 N 个工作日」
中国节假日必须自己传 holidays 参数
Pandas 和 NumPy 都不内置任何国家节假日表。np.busday_count 的 holidays 必须是字符串列表或 datetime64[D] 数组,格式为 'YYYY-MM-DD'。用错格式(比如传 pd.Timestamp)会导致静默失败——差值全算成 0。
- 推荐用
akshare或china-calendar库获取年份节假日,转成字符串列表再传入 - 注意节假日是否已包含周末:如果原始节假日列表里有周六日,
np.busday_count仍会将其视为假日(因holidays优先级高于weekmask),无需额外去重 - 跨年计算时,确保
holidays覆盖起止日期所在全部年份,否则漏掉的假日会被当作普通工作日
真实场景里,最容易被忽略的是日期对齐粒度和 NaT 的批量处理方式——不是加个 fillna 就完事,而是得在转 datetime64[D] 前就切片干净,否则类型转换会把 NaT 变成 1970-01-01 这类幻数。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










