Jupyter 风格指南#
这些指南应由文档中的笔记本遵循。pymc-examples 中的所有笔记本必须严格遵循此指南,而对于 pymc 中的笔记本,由于并非所有内容都可用,因此风格更为宽松。
文档网站由 Sphinx 生成,它使用 MYST 和 MYST-NB 来解析笔记本。
小技巧
有一个关于 为 PyMC 示例画廊做贡献 的网络研讨会可用。
模板笔记本#
有一个用于新笔记本的 模板 Jupyter 笔记本。
一般指南#
尽可能使用完整的单词,避免使用缩写或首字母缩略词。例如,写“随机变量”而不是“RVs”。
解释每一步的推理过程。
引用文本或代码属性,并链接到相关参考资料。
保持笔记本简短:针对初学者或中级用户的内容,建议20/30个单元格,高级水平可以使用更长的笔记本。
MyST 指南#
使用 MyST 可以在笔记本的 markdown 单元格中利用所有 Sphinx 功能。所有 markdown 都应该是有效的 MyST(注意 MyST 是 recommonmark 的超集)。本指南不全面教授或涵盖 MyST,仅提供一些有倾向性的指导方针。
切勿 使用URL链接来引用其他笔记本、PyMC文档或其他Python库文档。请改用 sphinx交叉引用。
小心
使用URL链接会破坏版本化文档中的自我引用!同时,它们不如Sphinx交叉引用健壮。
在链接到其他笔记本时,始终使用指向 第一个单元格 目标的
ref
类型交叉引用。
如果一个单元格的输出(甚至代码和输出)对于理解笔记本不是必要的,或者它非常长并且可能打断阅读的流畅性,考虑使用 切换按钮 来隐藏它。
考虑使用 Markdown Figures 为笔记本中使用的图像添加标题。
尽可能使用术语表。如果你使用了一个在术语表中定义的术语,请在首次以重要方式出现时链接到它。使用 这种语法 来添加术语引用。链接到术语表源 ,新术语应添加到此处。
变量名#
首先,保持笔记本中变量名称的一致性。使用相同变量的多个名称的笔记本将不会被合并。
尽可能使用有意义的变量名。我们的用户来自不同的背景,并非每个人都熟悉相同的命名约定。
也要注释维度。笔记本是为了阅读而发布的,所以即使形状是从输入中派生的,或者你不喜欢使用命名维度,并且不在个人代码中使用它们,笔记本也必须使用维度,即使只是注释而不是设置形状。这使得代码更容易理解,特别是对于新手。
有时使用希腊字母来指代变量是有意义的,例如在书写方程时,因为这使得它们更容易阅读。在这种情况下,使用 LaTeX 插入希腊字母,如
$ heta$
,而不是使用 Unicode 如θ
。如果你需要在代码中使用希腊字母变量名,请拼写出来而不是使用Unicode。例如,使用
theta
而不是θ
。当使用如单字母这样的无意义名称时,在首次引入这些变量的方程下方添加带有一两句描述的要点。
选择变量名称有时可能会很困难、繁琐或令人烦恼。如果有所帮助,下拉菜单中有一些建议,这样您可以专注于编写实际内容。
变量名建议
模型和采样结果
使用
idata
表示采样结果,始终包含一个类型为 InferenceData 的变量。将推断数据组存储为变量,以简化对采样结果进行操作的代码的编写和阅读。使用下划线分隔的3-5个单词的缩写或组名。一些
缩写
/组名
的例子:post
/posterior
,const
/constant_data
,post_pred
/posterior_predictive
或obs_data
/observed_data
对于统计和诊断,使用 ArviZ 函数名作为变量名:
ess = az.ess(...)
,loo = az.loo(...)
如果在笔记本中有多个模型,为每个模型分配一个前缀,并在整个过程中使用它来识别哪些变量映射到每个模型。以著名的八个学校为例,比较
centered
和non_centered
参数化模型,使用centered_model
(pm.Model 对象)、centered_idata
、centered_post
、centered_ess
… 以及non_centered_model
、non_centered_idata
…
维度和随机变量名称
使用单数维度名称,遵循 ArviZ 的
chain
和draw
。例如cluster
、axis
、component
、forest
、time
…如果你无法为表示观测次数(如时间)的维度想出一个有意义的名字,请回退到
obs_id
。对于矩阵维度,由于 xarray 不允许重复的维度名称,请添加
_bis
后缀。例如param, param_bis
。对于由堆叠
chain
和draw
产生的维度,使用sample
,即.stack(sample=("chain", "draw"))
。我们经常需要将分类变量编码为整数。在编码变量的名称后添加
_idx
。例如,从floor
和county
变为floor_idx
和county_idx
。为了避免在使用
pm.Data
时发生变量冲突和覆盖,请使用以下模式:x = np.array(...) with pm.Model(): x_ = pm.Data("x", x) ...
这避免了覆盖原始的
x
,同时保留了idata.constant_data["x"]
,并且在模型中x_
仍然可以扮演x
的角色。否则,始终尝试使用与 PyMC 随机变量给定的字符串名称相同的变量名。
绘图
Matplotlib 图形和轴。使用:
fig
用于 matplotlib 图形ax
用于单个matplotlib轴对象axs
用于 matplotlib 轴对象的数组
在手动处理多个 matplotlib 轴时,使用局部
ax
变量:fig, axs = pyplot.subplots() ax = axs[0, 1] ax.plot(...) ax.set(...) ax = axs[1, 2] ax.scatter(...)
fig, axs = pyplot.subplots() axs[0, 1].plot(...) axs[0, 1].set(...) axs[1. 2].scatter(...)
这使得在重新组织子图时编辑代码更容易,每个子图只需要一次更改,而不是每次matplotlib函数调用都需要更改。
将 numpy 的 linspace 转换为
DataArray
通常很有用,这样 xarray 可以自动处理对齐和广播,并简化计算。如果需要一个维度名称,请使用
x_plot
如果需要为原始数组和 DataArray 共存指定一个变量名,请添加
_da
后缀
因此,最终得到如下代码:
x = xr.DataArray(np.linspace(0, 10, 100), dims=["x_plot"]) # or x = np.linspace(0, 10, 100) x_da = xr.DataArray(x)
循环
使用 enumerate 时,以变量的第一个字母作为计数:
for p, person in enumerate(persons)
在循环时,如果你需要在用循环索引进行子集化后存储一个变量,将用于循环的索引变量附加到原始变量名上:
variable = np.array(...) x = np.array(...) for i in range(N): variable_i = variable[i] for j in range(K): x_j = x[j] ...
第一个单元格#
所有示例笔记本的第一个单元格应包含一个 MyST 目标,一个一级 Markdown 标题(即带有单个 #
的标题),后跟 post 指令。语法如下:
(notebook_name)=
# Notebook Title
:::{post} Aug 31, 2021
:tags: tag1, tag2, tags can have spaces, tag4
:category: level
:author: Alice Abat, Bob Barceló
:::
日期应大致对应于最新更新/执行日期(如果在合并PR前的审查过程中日期有几天偏差,这不是问题)。这将使用户能够看到哪些笔记本最近已更新,并帮助PyMC团队确保没有笔记本长时间未更新。
重要
The MyST 目标 (即 (notebook_name)=
部分) 用于在笔记本之间建立链接。它必须是特定于笔记本的,例如其文件名。不要复制粘贴此内容并保留 notebook_name
未修改
标签可以是任何内容,但我们建议您尽量使用 现有标签 以避免标签列表变得过长。
每个笔记本应有一个或两个类别,用于指示:
笔记本的级别(必需):
beginner
(站立的乌鸦图标)intermediate
(飞鸽图标)advanced
(龙图标)
the diataxis 类型(旧笔记本可选):
教程
how-to
explanation
reference
作者应列出编写、改编或更新笔记本的人员,不包括那些仅重新执行笔记本且代码或措辞变化很少的人。此处只应添加作者姓名,因为这只是笔记本的元数据,自我推广链接和更改细节应添加在“作者”部分,详见 作者身份和归属。
额外依赖项#
如果笔记本使用了不是 PyMC 依赖的库,这些额外的依赖应该与一些关于如何安装它们的建议一起指出。这确保了读者知道他们需要提前安装什么,并且可以例如决定是在本地运行还是在 binder 上运行。
为了方便笔记本编写者和维护者,pymc-examples 包含了一个模板,该模板会警告关于额外依赖项的问题,并在下拉菜单中提供特定的安装说明。
因此,带有额外依赖的笔记本应:
使用
myst_substitutions
类别将额外的依赖项列为笔记本元数据,然后使用extra_dependencies
或pip_dependencies
和conda_dependencies
。此外,还有一个extra_install_notes
用于在下拉菜单中包含自定义文本。笔记本元数据可以通过菜单
进行编辑这将打开一个窗口,其中包含可能看起来有点像这样的json格式文本:
{ "kernelspec": { "name": "python3", "display_name": "Python 3 (ipykernel)", "language": "python" }, "language_info": { "name": "python", "version": "3.9.7", "mimetype": "text/x-python", "codemirror_mode": { "name": "ipython", "version": 3 }, "pygments_lexer": "ipython3", "nbconvert_exporter": "python", "file_extension": ".py" } }
{ "kernelspec": { "name": "python3", "display_name": "Python 3 (ipykernel)", "language": "python" }, "language_info": { "name": "python", "version": "3.9.7", "mimetype": "text/x-python", "codemirror_mode": { "name": "ipython", "version": 3 }, "pygments_lexer": "ipython3", "nbconvert_exporter": "python", "file_extension": ".py" }, "myst": { "substitutions": { "extra_dependencies": "bambi seaborn" } } }
{ "kernelspec": { "name": "python3", "display_name": "Python 3 (ipykernel)", "language": "python" }, "language_info": { "name": "python", "version": "3.9.7", "mimetype": "text/x-python", "codemirror_mode": { "name": "ipython", "version": 3 }, "pygments_lexer": "ipython3", "nbconvert_exporter": "python", "file_extension": ".py" }, "myst": { "substitutions": { "pip_dependencies": "graphviz", "conda_dependencies": "python-graphviz", } } }
pip 和 conda 特定的键会覆盖
extra_installs
,因此如果使用它们,使用extra_installs
就没有意义了。要么同时定义 pip 和 conda 替换,要么都不定义。
在导入额外依赖项之前,请包含警告和安装建议模板,并附上以下markdown内容:
:::{include} ../extra_installs.md :::
代码前言#
在导入 matplotlib 和/或 ArviZ 的单元格下方(通常是第一个单元格),将 ArviZ 样式设置为 darkgrid(这必须在 matplotlib 导入的单元格之外,因为 matplotlib 设置其默认值的方式):
RANDOM_SEED = 8927
rng = np.random.default_rng(RANDOM_SEED)
az.style.use("arviz-darkgrid")
在生成合成数据时,一个好的做法是像上面那样设置一个随机种子,以提高可重复性。此外,请检查收敛性(例如 assert all(r_hat < 1.03)
),因为我们有时会自动重新运行笔记本,而不会仔细检查每一个。
从文件读取#
使用 try... except
子句来加载数据,并在 except 路径中使用 pm.get_data
。这将确保已经克隆了 pymc-examples 仓库的用户读取他们的本地数据副本,同时为没有本地副本的用户从 github 下载数据。以下是一个示例:
try:
df_all = pd.read_csv(os.path.join("..", "data", "file.csv"), ...)
except FileNotFoundError:
df_all = pd.read_csv(pm.get_data("file.csv"), ...)
预提交和代码格式化#
我们在持续集成期间对笔记本运行一些代码质量检查。确保您的笔记本通过CI检查的最简单方法是使用 pre-commit。您可以使用以下命令安装它:
pip install -U pre-commit
然后启用它
pre-commit install
然后,每次提交更改时,代码质量检查将自动运行。要手动运行代码质量检查,您可以执行,例如:
pre-commit run --files notebook1.ipynb notebook2.ipynb
将 notebook1.ipynb
和 notebook2.ipynb
替换为你修改过的任何笔记本。
注意:有时,Black 会让人感到沮丧(嗯,谁不会呢?)。在这些情况下,你可以为特定代码行禁用它的魔法:只需写 #fmt: on/off
来禁用/重新启用它,如下所示:
# fmt: off
np.array(
[
[1, 0, 0, 0],
[0, -1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, -1],
]
)
# fmt: on
参考文献#
引用应添加到 bibtex 格式的 references.bib
文件中,并在笔记本文本中使用 sphinxcontrib-bibtex 在相关位置引用。
.bib
文件中的参考文献应使用类似于 authorlastnameYEARkeyword
或 libraryYEARkeyword
的 ID 来标识文档页面,并且应按此 ID 按字母顺序排序,以便于在文件中查找参考文献并防止添加重复项。
引用可以在单个笔记本中被引用两次。两种常见的引用格式是:
{cite:p}`bibtex_id` # shows the reference author and year between parenthesis
{cite:t}`bibtex_id` # textual cite, shows author and year without parenthesis
可以在文本中直接添加。在笔记本的末尾,使用以下markdown添加参考文献
## References
:::{bibliography}
:filter: docname in docnames
:::
或者,如果你想添加在文本中未被引用的额外参考文献,请使用:
## References
:::{bibliography}
:filter: docname in docnames
extra_bibtex_id_1
extra_bibtex_id_2
:::
水印#
watermark
是一个库,它可以自动打印出你用来运行 NB 的 Python 和包的版本——可重复性万岁!
如果你安装了我们的 requirements-dev.txt
,这个库应该在你的虚拟环境中。否则,运行 pip install watermark
。
首先,添加一个Markdown单元格,仅包含 ## 水印
标题,以便它出现在目录中。这是倒数第二部分,位于结语/页脚之上。然后,添加一个代码单元格以打印笔记本中使用的Python和包的版本。这是笔记本中的最后一个 代码 单元格。
p
标志是可选的(或者可能需要不同的库作为输入),但如果未显式导入 PyTensor 或 xarray,则应添加。pre-commit
也会检查这一点(因为我们有时都会忘记做某些事情 😳)。
## Watermark
%load_ext watermark
%watermark -n -u -v -iv -w -p pytensor,xarray
尾声#
笔记本中的最后一个单元格应为包含以下内容的markdown单元格:
:::{include} ../page_footer.md
:::
唯一的例外是那些不在通常位置的笔记本,因此需要更新页面页脚的路径以使包含功能正常工作。
你现在已经准备好了 🎉。你可以推送你的更改,打开一个拉取请求,一旦它被合并,就可以安心地休息,感受工作完成的满足感 👏。非常感谢你对开源项目的贡献,我们真的非常感激!