Jupyter 风格指南#

文档中的 Notebook 应遵循这些指南。pymc-examples 中的所有 Notebook 必须严格遵守此指南,对于 pymc 上的 Notebook,风格可以更宽松,因为并非所有内容都可用。

文档网站由 Sphinx 生成,Sphinx 使用 MYSTMYST-NB 来解析 Notebook。

提示

有一个关于 为 PyMC 示例库贡献内容 的网络研讨会

模板 Notebook#

有一个 模板 Jupyter Notebook 可用于新的 Notebook。

通用指南#

  • 在可以使用完整单词时,请勿使用缩写或首字母缩略词。例如,写“random variables”而不是“RVs”。

  • 解释每个步骤背后的理由。

  • 注明引用的文本或代码的出处,并链接到相关参考文献。

  • 保持 Notebook 短小精悍:针对初级或中级用户的内容,Notebook 保持在 20/30 个单元格;高级水平的 Notebook 可以更长。

MyST 指南#

使用 MyST 可以利用 Notebook 中 Markdown 单元格的所有 Sphinx 功能。所有 Markdown 都应是有效的 MyST(请注意,MyST 是 recommonmark 的超集)。本指南不广泛地教授或涵盖 MyST,仅提供一些主观的指南。

  • 永远不要使用 URL 链接来引用其他 Notebook、PyMC 文档或其他 Python 库文档。当链接到其他 Notebook 时,始终使用指向 第一个单元格 中目标的 ref 类型交叉引用。

    注意

    在版本化文档中使用 URL 链接会破坏自引用!同时,它们也不如 Sphinx 交叉引用那么健壮。

    交叉引用示例

    引用当前项目内的目标

    即,pymc-examples 中的 Notebook 引用 pymc-examples 中的其他 Notebook。

    模式

    {ref}`explicit text <anchor_id>`
    

    示例来源

    {ref}`Kronecker product <GP-Kron>`
    

    渲染示例:Kronecker 乘积

    引用其他项目的目标

    这里“其他项目”指的是任何独立于当前项目构建的 Sphinx 文档站点。因此,这包括从 PyMC 文档链接到 pymc-examples Notebook,反之亦然,或者链接到其他库,如 ArviZ、NumPy、Matplotlib 等。

    模式

    {ref}`explicit text <key:anchor_id>`
    

    示例来源

    {ref}`how to use InferenceData <arviz:working_with_InferenceData>`
    

    渲染示例:如何使用 InferenceData

    其中模式中的 key(示例中的 arviz)是在 conf.pyintersphinx_mapping 变量中定义的键之一,如 arviznumpympl... 对于主 PyMC 仓库,它位于 docs/source/conf.py 中,对于 pymc-examples,它位于 examples/conf.py 中。

    要识别要使用的 anchor_id,您需要查看文档的源代码,或使用 sphobjinv

    引用 Python 对象

    模式

    {type}`import.path`  # to show full import path
    {type}`~import.path`  # to show only object name
    

    其中 type 是函数的 func、方法的 meth、类的 class、属性的 prop 等。

    示例来源

    {class}`~pymc.gp.HSGP`
    

    渲染示例:HSGP

  • 如果单元格的输出(甚至代码和输出)对于理解 Notebook 不是必需的,或者它非常长并且会中断阅读流程,请考虑使用 切换按钮 隐藏它

  • 考虑使用 Markdown 图形 为 Notebook 中使用的图像添加标题。

  • 尽可能使用术语表。如果您使用术语表中定义的术语,请在首次以重要方式出现该术语时链接到它。使用 此语法 添加术语引用。链接到术语表源,应在其中添加新术语。

变量名#

  • 最重要的是,在 Notebook 中保持变量名的一致性。对于同一变量使用多个名称的 Notebook 将不会被合并。

  • 尽可能使用有意义的变量名。我们的用户来自不同的背景,并非所有人都熟悉相同的命名约定。

    • 也请注释维度。Notebook 是发布供阅读的,因此即使形状是从输入派生的,或者您不喜欢使用命名维度并且在您的个人代码中不使用它们,Notebook 也必须使用维度,即使只是注释而不是设置形状。这使代码更易于理解,尤其是对于新手而言。

  • 有时使用希腊字母来表示变量是有意义的,例如在编写方程式时,因为这使它们更易于阅读。在这种情况下,使用 LaTeX 插入希腊字母,例如 $\theta$,而不是使用 Unicode,例如 θ

  • 如果您需要在代码中使用希腊字母变量名,请拼写出来,而不是使用 Unicode。例如,theta,而不是 θ

  • 当使用非有意义的名称(如单个字母)时,在首次引入它们的方程式下方添加带有 1-2 句话描述每个变量的要点。

选择变量名有时可能很困难、乏味或令人恼火。如果以下下拉菜单对您有所帮助,您可以专注于编写实际内容

变量名建议

模型和采样结果

  • 对采样结果使用 idata,始终包含 InferenceData 类型的变量。

  • 将 inferenceData 组存储为变量,以简化操作采样结果的代码的编写和阅读。使用下划线分隔的 3-5 个单词的缩写或组名。abbrebiation/group_name 的一些示例:post/posteriorconst/constant_datapost_pred/posterior_predictiveobs_data/observed_data

  • 对于统计信息和诊断,使用 ArviZ 函数名作为变量名:ess = az.ess(...)loo = az.loo(...)

  • 如果 Notebook 中有多个模型,请为每个模型分配一个前缀,并在整个 Notebook 中使用它来标识哪些变量映射到每个模型。以著名的八所学校为例,使用 centerednon_centered 模型来比较参数化,使用 centered_model (pm.Model 对象)、centered_idatacentered_postcentered_ess... 和 non_centered_modelnon_centered_idata...

维度和随机变量名

  • 使用单数维度名称,遵循 ArviZ chaindraw。例如 clusteraxiscomponentforesttime...

  • 如果您想不出表示观测数量的维度(如时间)的有意义的名称,请回退到 obs_id

  • 对于矩阵维度,由于 xarray 不允许重复的维度名称,请添加 _bis 后缀。即 param, param_bis

  • 对于堆叠 chaindraw 产生的维度,请使用 sample,即 .stack(sample=("chain", "draw"))

  • 我们经常需要将分类变量编码为整数。在编码的变量名称中添加 _idx。即从 floorcountyfloor_idxcounty_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]
            ...
    

第一个单元格#

所有示例 Notebook 的第一个单元格都应具有 MyST 目标、级别 1 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 合并前的审查过程导致日期偏差几天,则没有问题)。这将允许用户查看哪些 Notebook 最近已更新,并将帮助 PyMC 团队确保没有 Notebook 过时太久。

重要提示

MyST 目标(notebook_name)= 位)用于在 Notebook 之间链接。它必须是 Notebook 特定的,例如其文件名。请勿复制粘贴此内容并保持 notebook_name 不修改

标签可以是任何内容,但我们要求您尝试使用现有标签,以避免标签列表变得太长。

每个 Notebook 都应具有一个或两个类别,指示

  • Notebook 的级别(必需)

    • beginner(站立的乌鸦图标)

    • intermediate(飞翔的鸽子图标)

    • advanced(龙图标)

  • diataxis 类型(对于旧 Notebook 是可选的)

    • 教程

    • 操作指南

    • 解释

    • 参考

作者应列出编写、改编或更新 Notebook 的人员,不包括那些仅重新执行 Notebook 且代码或措辞几乎没有更改的人员。此处应仅添加作者姓名,因为这只是 Notebook 的元数据,自我推销链接和有关更改的详细信息应添加到“作者”部分,请参阅 作者身份和署名 了解更多详细信息。

额外依赖项#

如果 Notebook 使用了非 PyMC 依赖项的库,则应指示这些额外依赖项,并提供有关如何安装它们的一些建议。这确保读者事先知道他们需要安装什么,并且例如可以决定是在本地运行还是在 Binder 上运行。

为了方便 Notebook 编写者和维护者,pymc-examples 包含一个模板,用于警告额外的依赖项,并在下拉菜单中提供特定的安装说明。

因此,具有额外依赖项的 Notebook 应

  1. 使用 myst_substitutions 类别将额外的依赖项列为 Notebook 元数据,然后列出 extra_dependenciespip_dependenciesconda_dependencies。此外,还有一个 extra_install_notes 用于在下拉菜单中包含自定义文本。

    • 可以从菜单中编辑 Notebook 元数据,方法是选择 编辑 ‣ 编辑 Notebook 元数据

      这将打开一个窗口,其中包含 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 替换,要么都没有定义。

  2. 在导入额外的依赖项之前,包含带有以下 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)),因为我们有时会自动重新运行 Notebook,而没有仔细检查每个 Notebook。

从文件读取#

使用 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"), ...)

pre-commit 和代码格式化#

我们在持续集成期间对 Notebook 运行一些代码质量检查。确保您的 Notebook 通过 CI 检查的最简单方法是使用 pre-commit。您可以使用以下命令安装它

pip install -U pre-commit

然后使用以下命令启用它

pre-commit install

然后,代码质量检查将在您每次提交任何更改时自动运行。要手动运行代码质量检查,您可以执行以下操作,例如

pre-commit run --files notebook1.ipynb notebook2.ipynb

notebook1.ipynbnotebook2.ipynb 替换为您修改的任何 Notebook。

注意:有时,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

作者身份和署名#

在 Notebook 内容完成后,应该有一个 ## Authors 部分,其中包含要点,以提供对 Notebook 做出贡献的人员的署名。一般模式应该是

## Authors

* <verb> by <author> in <date> ([repo#PR](https://link-to.pr))

其中 <author> 应该是姓名(允许多人),可以格式化为指向该人员个人网站或 GitHub 个人资料的超链接,<date> 最好是月份和年份。

<verb> 部分应旨在描述所做更改,例如“updated”、“re-executed”、“authored”或“adapted”,但不限于任何内容。

做出重大贡献的作者也应包含在 post 元数据中,如 第一个单元格 中所示。对此没有通用且严格的指南,如有疑问,请添加您自己并请审阅者提供第二意见。这样做的主要原因是底部的作者部分旨在记录 Notebook 发生的所有更改,而顶部的元数据用于呈现引文建议,因此例如,重新执行不需要更改的 Notebook 是一项有价值的贡献,它将在此部分和 GitHub 上记录,但不符合引用的作者身份标准。另一方面,更新 Notebook 的措辞和渲染以使其更清晰,对读者更友好,即使 Notebook 未重新执行,也应在两个位置都添加。

一些例子

## Authors

* Authored by Chris Fonnesbeck in May, 2017 ([pymc#2124](https://github.com/pymc-devs/pymc/pull/2124))
* Updated by Colin Carroll in June, 2018 ([pymc#3049](https://github.com/pymc-devs/pymc/pull/3049))
* Updated by Alex Andorra in January, 2020 ([pymc#3765](https://github.com/pymc-devs/pymc/pull/3765))
* Updated by Oriol Abril in June, 2020 ([pymc#3963](https://github.com/pymc-devs/pymc/pull/3963))
* Updated by Farhan Reynaldo in November 2021 ([pymc-examples#246](https://github.com/pymc-devs/pymc-examples/pull/246))

## Authors

* Adapted from chapter 5 of Bayesian Data Analysis 3rd Edition {cite:p}`gelman2013bayesian`
  by Demetri Pananos and Junpeng Lao in June, 2018 ([pymc#3054](https://github.com/pymc-devs/pymc/pull/3054))
* Reexecuted by Ravin Kumar with PyMC 3.6 in March, 2019 ([pymc#3397](https://github.com/pymc-devs/pymc/pull/3397))
* Reexecuted by Alex Andorra and Michael Osthege with PyMC 3.9 in June, 2020 ([pymc#3955](https://github.com/pymc-devs/pymc/pull/3955))
* Updated by Raúl Maldonado in 2021 ([pymc-examples#24](https://github.com/pymc-devs/pymc-examples/pull/24), [pymc-examples#45](https://github.com/pymc-devs/pymc-examples/pull/45) and [pymc-examples#147](https://github.com/pymc-devs/pymc-examples/pull/147))

参考文献#

参考文献应以 BibTeX 格式添加到 references.bib 文件中,并在 Notebook 文本中相关位置使用 sphinxcontrib-bibtex 引用。

.bib 文件中的参考文献的 ID 应类似于 authorlastnameYEARkeywordlibraryYEARkeyword(对于文档页面),并且应按此 ID 按字母顺序排序,以便于在文件中查找参考文献并防止添加重复的参考文献。

参考文献可以在单个 Notebook 中引用两次。两种常见的参考文献格式是

{cite:p}`bibtex_id`  # shows the reference author and year between parenthesis
{cite:t}`bibtex_id`  # textual cite, shows author and year without parenthesis

可以在文本本身内内联添加。在 Notebook 的末尾,使用以下 Markdown 添加书目

## References

:::{bibliography}
:filter: docname in docnames
:::

或者,如果您想添加文本中未引用的额外参考文献,请使用

## References

:::{bibliography}
:filter: docname in docnames

extra_bibtex_id_1
extra_bibtex_id_2
:::

水印#

watermark 是一个库,它可以自动打印 Python 和您用于运行 NB 的软件包的版本 – 再现性至上!

如果您安装了我们的 requirements-dev.txt,则此库应在您的虚拟环境中。否则,运行 pip install watermark

首先,添加一个 Markdown 单元格,标题仅为 ## Watermark,使其显示在目录中。这是倒数第二个部分,位于尾声/页脚上方。然后,添加一个代码单元格以打印 Notebook 中使用的 Python 和软件包的版本。这是 Notebook 中的最后一个代码单元格。

p 标志是可选的(或者可能需要将不同的库作为输入),但如果未显式导入 PyTensor 或 xarray,则应添加该标志。这也将由 pre-commit 检查(因为我们有时都会忘记做事情 😳)。

## Watermark
%load_ext watermark
%watermark -n -u -v -iv -w -p pytensor,xarray

尾声#

Notebook 中的最后一个单元格应是 Markdown 单元格,内容完全如下

:::{include} ../page_footer.md
:::

唯一的例外是不在通常位置的 Notebook,因此需要更新页面页脚的路径才能使 include 工作。


您现在已准备就绪 🎉。您可以推送您的更改,打开 pull request,并在合并后,以圆满完成工作的自豪感休息 👏。非常感谢您对开源的贡献,我们真的非常感谢!