Jupyter 风格指南#

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

文档网站由 Sphinx 生成,它使用 MYSTMYST-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/posteriorconst/constant_datapost_pred/posterior_predictiveobs_data/observed_data

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

  • 如果在笔记本中有多个模型,为每个模型分配一个前缀,并在整个过程中使用它来识别哪些变量映射到每个模型。以著名的八个学校为例,比较 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。例如,从 floorcounty 变为 floor_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]
            ...
    

第一个单元格#

所有示例笔记本的第一个单元格应包含一个 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 包含了一个模板,该模板会警告关于额外依赖项的问题,并在下拉菜单中提供特定的安装说明。

因此,带有额外依赖的笔记本应:

  1. 使用 myst_substitutions 类别将额外的依赖项列为笔记本元数据,然后使用 extra_dependenciespip_dependenciesconda_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 替换,要么都不定义。

  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)),因为我们有时会自动重新运行笔记本,而不会仔细检查每一个。

从文件读取#

使用 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.ipynbnotebook2.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

作者身份和归属#

在笔记本内容结束后,应有一个 ## 作者 部分,使用项目符号来归功于为笔记本做出贡献的人。一般模式应为:

## Authors

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

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

<verb> 部分应旨在描述所做的更改,例如“更新”、“重新执行”、“编写”或“改编”,但它不受任何限制。

对有重大贡献的作者也应按照 第一个单元格 中的指示包含在帖子元数据中。关于这一点没有普遍和严格的指导方针,如果有疑问,可以添加自己并向审阅者寻求第二意见。这样做的原因是,这里的作者部分旨在记录对笔记本所做的所有更改,而顶部的元数据用于呈现引用推荐,例如,重新执行一个不需要更改的笔记本是一项有价值的贡献,这将在本节和GitHub上记录,但不符合引用作者的标准。另一方面,更新笔记本的措辞和呈现方式以使其更清晰、更友好地面向读者,则应在两个地方都添加,即使笔记本没有重新执行。

一些例子:

## 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 文件中,并在笔记本文本中使用 sphinxcontrib-bibtex 在相关位置引用。

.bib 文件中的参考文献应使用类似于 authorlastnameYEARkeywordlibraryYEARkeyword 的 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
:::

唯一的例外是那些不在通常位置的笔记本,因此需要更新页面页脚的路径以使包含功能正常工作。


你现在已经准备好了 🎉。你可以推送你的更改,打开一个拉取请求,一旦它被合并,就可以安心地休息,感受工作完成的满足感 👏。非常感谢你对开源项目的贡献,我们真的非常感激!