Pandas 2.2 中文文档(八十三)

原文:pandas.pydata.org/docs/

pandas 维护

原文:pandas.pydata.org/docs/development/maintaining.html

本指南适用于 pandas 的维护者。对于希望了解 pandas 开发流程以及成为维护者所需步骤的贡献者也可能感兴趣。

主要的贡献指南可在贡献给 pandas 找到。

角色

pandas 使用两个级别的权限:分类核心 团队成员。

分类成员可以标记和关闭问题和拉取请求。

核心团队成员可以标记和关闭问题和拉取请求,并且可以合并拉取请求。

GitHub 发布了完整的权限列表

任务

pandas 主要是一个志愿者项目,因此这些任务不应被视为分类和维护者的“期望”。相反,它们是对成为维护者意味着什么的一般描述。

  • 对新提交的问题进行分类(参见问题分类)

  • 查看新提交的拉取请求

  • 回应现有问题和拉取请求���更新

  • 推动对停滞的问题和拉取请求的讨论和决策

  • 提供关于 API 设计问题的经验/智慧,以确保一致性和可维护性

  • 项目组织(运行/参加开发者会议,代表 pandas)

matthewrocklin.com/blog/2019/05/18/maintainer 可能是有趣的背景阅读。

问题分类

对社区报告的问题进行分类是解决问题的重要第一步,即使是部分贡献也是帮助维护 pandas 的好方法。只有在完成以下所有步骤后才能删除“需要分类”标签。

这是一个处理新开立问题的典型工作流程。

  1. 感谢报告者提出问题

    问题跟踪器是许多人与 pandas 项目本身的第一次互动,不仅仅是使用库。因此,我们希望它是一个友好、愉快的体验。

  2. 是否提供了必要信息?

    理想情况下,报告者会填写问题模板,但许多人没有这样做。如果关键信息(如他们使用的 pandas 版本)缺失,请随时要求并将问题标记为“需要信息”。报告应遵循错误报告和增强请求中的准则。如果他们没有遵循模板,您可能需要提供链接。

    确保标题准确反映问题。如果不清楚,请自行编辑。

  3. 这是一个重复的问题吗?

    我们有许多未解决的问题。如果新问题明显是重复的,请将新问题标记为“重复”并关闭该问题,并附上指向原始问题的链接。请确保仍然感谢报告者,并鼓励他们参与原始问题,并尝试修复它。

    如果新问题提供了相关信息,例如更好或略有不同的示例,请将其作为评论或对原始帖子的编辑添加到原始问题中。

  4. 问题是否最小化且可重现

    对于 bug 报告,我们要求报告者提供一个最小可重现示例。参见matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports以获取良好的解释。如果示例无法重现,或者显然不是最小的,请随时询问报告者是否可以提供示例或简化提供的示例。请承认编写最小可重现示例是一项艰苦的工作。如果报告者遇到困难,您可以尝试自己编写一个,然后我们将编辑原始帖子以包含它。

    如果无法提供可重现的示例,请添加“需要信息”标签。

    如果提供了可重现的示例,但你看到了一个简化,那么请编辑原始帖子,使用你更简单的可重现示例。

    确保问题存在于主分支上,并且具有“需要分类”标签,直到所有步骤完成。一旦验证问题存在于主分支上,请在问题上添加评论,以便其他人知道已经确认。

  5. 这是一个明确定义的功能请求吗?

    一般来说,pandas 更喜欢在问题中讨论和设计新功能,然后再提交拉取请求。鼓励提交者包含新功能的建议 API。让他们编写完整的文档字符串是确定具体细节的好方法。

    使用“需要讨论”标记标记新功能请求,因为我们需要几位 pandas 维护者的讨论,然后才能决定该提议是否适用于 pandas。

  6. 这是一个使用问题吗?

    我们更喜欢使用问题在 StackOverflow 上提问,并使用 pandas 标签。stackoverflow.com/questions/tagged/pandas

    如果很容易回答,请随时链接到相关文档部分,让他们知道将来这种问题应该在 StackOverflow 上提出,并关闭问题。

  7. 我应该添加哪些标签和里程碑?

    应用相关的标签。这有点艺术,需要经验。查看类似问题以了解如何标记事物的感觉。

    如果问题明确定义且修复似乎相对简单,请将问题标记为“好的首次问题”。

    完成上述步骤后,请确保删除“需要分类”标签。 ## 调查回归

回归是无意中破坏先前正常工��代码的错误。调查回归的常见方法是使用git bisect,该方法找到引入错误的第一个提交。

例如:用户报告说pd.Series([1, 1]).sum()在 pandas 版本1.5.0中返回3,而在版本1.4.0中返回2。首先,在你的 pandas 目录中创建一个名为t.py的文件,其中包含

import pandas as pd
assert pd.Series([1, 1]).sum() == 2 

然后运行:

git bisect start
git bisect good v1.4.0
git bisect bad v1.5.0
git bisect run bash -c "python setup.py build_ext -j 4; python t.py" 

这找到了更改行为的第一个提交。每一步都必须重新构建 C 扩展,因此搜索可能需要一段时间。

退出 bisect 并重新构建当前版本:

git bisect reset
python setup.py build_ext -j 4 

在相应问题下报告您的发现,并通知提交者以获取他们的意见。

注意

在上面的bisect run命令中,如果t.py0退出,则将提交视为良好,否则视为有问题。当引发异常是期望的行为时,请使用适当的try/except语句包装代码。更多示例请参见GH 35685。 ## 关闭问题

在这里要谨慎:许多人认为关闭问题意味着我们认为对话已经结束。通常最好给报告者一些时间来回应或自行关闭他们的问题,如果确定该行为不是 bug,或者该功能不在范围内。有时候报告者就此消失了,我们会在对话结束后关闭问题。如果您认为问题应该关闭但不完全确定,请应用“关闭候选”标签,并等待其他维护者查看。 ## 审查拉取请求

任何人都可以审查拉取请求:常规贡献者、分类者或核心团队成员。但只有核心团队成员可以在拉取请求准备就绪时合并拉取请求。

在审查拉取请求时,请检查以下内容。

  • 测试应该放在一个明智的位置:在与密切相关的测试相同的文件中。

  • 新的公共 API 应该包含在 doc/source/reference/ 的某处。

  • 新/更改的 API 应在文档字符串中使用 versionaddedversionchanged 指令。

  • 用户可见的更改应该在适当的文件中有一个 whatsnew。

  • 回归测试应该引用原始 GitHub 问题编号,如 # GH-1234

  • 拉取请求应该被标记并分配适当的里程碑(下一个补丁发布用于回归修复和小 bug 修复,否则下一个次要里程碑)

  • 更改应符合我们的版本政策。 ## 回溯

pandas 支持点发布(例如 1.4.3)的目标是:

  1. 在第一个次要版本发布中引入的新功能中修复错误。
  • 例如,如果一个新功能在1.4中添加并包含一个错误,修复可以在1.4.3中应用。
  1. 修复以前在几个次要版本中工作的错误。核心团队成员之间应该达成一致意见,认为回溯是合适的。
  • 例如,如果一个功能在1.2版本中工作,在1.3版本后停止工作,修复可以在1.4.3中应用。

由于 pandas 的次要版本发布是基于 GitHub 分支的(例如 1.4 的点版本是基于 1.4.x 分支的),“回溯”意味着将拉取请求修复合并到 main 分支和下一个点发布的关联的正确的次要分支。

默认情况下,如果一个拉取请求被分配到下一个点发布里程碑,在 GitHub 界面内,一旦拉取请求被合并,@meeseeksdev 机器人应该自动进行回溯处理。将会创建一个新的拉取请求将拉取请求回溯到正确的版本分支。有时由于合并冲突,需要手动创建拉取请求来解决代码冲突。

如果机器人没有自动开始回溯过程,您也可以在合并的拉取请求中写一个 GitHub 评论来触发回溯:

@meeseeksdev backport version-branch 

这将触发一个工作流程,将给定的更改回溯到一个分支(例如 @meeseeksdev backport 1.4.x)

清理旧问题

每个在 pandas 中的开放问题都有成本。开放的问题使得查找重复更加困难,并且可能会使得在 pandas 中需要做什么更加困难。也就是说,关闭问题不是一个独立的目标。我们的目标是使 pandas 尽可能完美,而这最好是通过确保我们的开放问题的质量是高的。

偶尔,错误被修复了,但问题没有在拉取请求中链接。在这些情况下,评论“已修复,但可能需要测试。”并将问题标记为“好的第一个问题”和“需要测试”。

如果一个较旧的问题没有遵循我们的问题模板,请编辑原始帖子以包含一个最小的示例,实际输出和期望输出。问题报告的统一性是有价值的。

如果一个较旧的问题缺乏可重现的示例,请将其标记为“需要信息”并要求提供一个(如果可能的话,自己编写一个)。如果不久提供了一个合理的,关闭它符合关闭问题中的政策。

清理旧的拉取请求

偶尔,贡献者无法完成拉取请求。如果已经过去一段时间(比如说两周)自上次审查要求更改以来,温柔地询问他们是否仍然有兴趣继续进行。如果又过了大约两周没有回应,感谢他们的工作,然后要么:

  • 关闭拉取请求;

  • 将工作推送到贡献者的分支以完成工作(如果你是pandas-core的一部分)。这对于推动重要的 PR 完成,或者解决小的合并冲突非常有帮助。

如果关闭拉取请求,请在原始问题上评论“在 #1234 上有一个搁置的 PR,可能会有帮助。”,并且如果 PR 相对接近被接受,则可能会标记问题为“好的第一个问题”。

成为一个 pandas 的维护者

完整的流程概述在我们的治理文件中。简而言之,我们很乐意向在问题跟踪器上提供帮助的任何人提供分类权限。

添加维护者的必需步骤是:

  1. 联系贡献者并询问他们是否有兴趣加入。

  2. 如果接受邀请,请将贡献者添加到适当的GitHub 团队中。

  • pandas-core 是为核心团队成员设计的
  • pandas-triage 是为 pandas 三审成员设计的

如果要添加到 pandas-core,还有两个额外步骤:

  1. 将贡献者添加到 pandas Google 群组。

  2. 创建一个拉取请求,将贡献者的 GitHub 句柄添加到 pandas-dev/pandas/web/pandas/config.yml

当前核心团队成员名单在 pandas-dev/pandas

合并拉取请求

只有核心团队成员可以合并拉取请求。我们有一些准则。

  1. 通常不应自行合并自己的拉取请求,除非经过批准。例外情况包括修复 CI 的小更改(例如固定软件包版本)。如果其他核心团队成员批准了自行合并的更改,那么这样做是可以的,前提是您对更改非常有信心。

  2. 不应合并正在讨论中的拉取请求,或者有任何核心维护者给出 -1 投票的拉取请求。pandas 通过共识运作。

  3. 对于较大的更改,最好至少有两名核心团队成员的 +1。

除了 关闭问题 中列出的��目外,您还应验证拉取请求是否分配了正确的里程碑。

通常会通过我们的机器人将具有补丁发布里程碑的合并的拉取请求回溯。请验证机器人是否注意到了合并(通常会在一分钟内留下评论)。如果需要手动回溯,请执行此操作,并在手动完成后删除“需要回溯”标签。如果在标记之前忘记分配里程碑,您可以请求机器人回溯它:

@Meeseeksdev backport <branch> 
```## 基准机器

团队目前拥有专用硬件用于托管 pandas 的 ASV 性能基准网站。结果发布到 [`asv-runner.github.io/asv-collection/pandas/`](https://asv-runner.github.io/asv-collection/pandas/)

### 配置

机器可以使用 [Ansible](http://docs.ansible.com/ansible/latest/index.html) playbook 在 [tomaugspurger/asv-runner](https://github.com/tomaugspurger/asv-runner) 中进行配置。

### 发布

结果发布到另一个 GitHub 存储库,[tomaugspurger/asv-collection](https://github.com/tomaugspurger/asv-collection)。最后,我们在文档服务器上有一个 cron 作业从 [tomaugspurger/asv-collection](https://github.com/tomaugspurger/asv-collection) 拉取,以从 `/speed` 提供服务。请向 Tom 或 Joris 请求访问 web 服务器。

### 调试

基准由 Airflow 安排。它有一个仪表板用于查看和调试结果。您需要设置一个 SSH 隧道来查看它们

> ssh -L 8080:localhost:8080 pandas@panda.likescandy.com  ## 发布流程

发布过程会为用户提供 pandas 的快照(git 提交),并附带特定版本号。发布后,新的 pandas 版本将在以下位置可用:

+   具有 [新标签](https://github.com/pandas-dev/pandas/tags) 的 Git 存储库

+[GitHub 发布](https://github.com/pandas-dev/pandas/releases)中的源代码分发

+   [PyPI](https://pypi.org/project/pandas/)中的 Pip 软件包

+[conda-forge](https://anaconda.org/conda-forge/pandas)中的 Conda/Mamba 软件包

发布新版本的 pandas 的流程在下一节详细说明。

说明中包含需要替换为要发布版本的`<version>`(例如`1.5.2`)的部分。还有要发布的分支`<branch>`,这取决于要发布的版本是新版本的发布候选版本还是其他任何版本。发布候选版本从`main`发布,而其他版本从它们的分支发布(例如`1.5.x`)。

### 先决条件

为了能够发布新的 pandas 版本,需要以下权限:

+   合并权限到[pandas](https://github.com/pandas-dev/pandas/)[pandas-feedstock](https://github.com/conda-forge/pandas-feedstock/)存储库。对于后者,打开一个 PR,将您的 GitHub 用户名添加到 conda-forge 配方中。

+   允许推送到`main`分支的权限在 pandas 存储库中,以推送新标签。

+   [写入权限到 PyPI](https://github.com/conda-forge/pandas-feedstock/pulls)+   访问我们的网站/文档服务器。将您的公钥与基础设施委员会共享,以添加到主服务器用户的`authorized_keys`文件中。

+   访问社交媒体账户,发布公告。

### 预发布

1.  与核心团队就下一个主题达成一致:

    +   发布日期(主要/次要版本通常每 6 个月发布一次,补丁版本每月发布一次,直到 x.x.5,即在下一个主要/次要版本之前)

    +   阻碍因素(必须包含在发布中的问题和 PR)

    +   在发布后的下一个版本

1.  更新和清理要发布版本的发布说明,包括:

    +   设置发布的最终日期

    +   删除任何未使用的项目符号

    +   确保没有格式问题、拼写错误等。

1.  确保正在发布的分支的最后提交的 CI 状态为绿色。

1.  如果不是发布候选版本,请确保所有要回溯到正在发布的分支的拉取请求已合并。

1.  为正在发布的版本之后的版本创建一个新问题和里程碑。如果发布是一个发布候选版本,通常我们会想要为下一个主要/次要版本和下一个补丁版本创建问题和里程碑。在补丁版本的里程碑中,我们添加描述`on-merge: backport to <branch>`,这样标记的 PR 将由我们的机器人自动回溯到发布分支。

1.  将正在发布的里程碑中的所有问题和 PR 的里程碑更改为下一个里程碑。

### 发布

1.  在要发布分支的最后提交中创建一个空提交和一个标签:

    ```py
    git checkout <branch>
    git pull --ff-only upstream <branch>
    git clean -xdf
    git commit --allow-empty --author="Pandas Development Team <[[email protected]](/cdn-cgi/l/email-protection)>" -m "RLS: <version>"
    git tag -a v<version> -m "Version <version>"  # NOTE that the tag is v1.5.2 with "v" not 1.5.2
    git push upstream <branch> --follow-tags 
    ```

新版本的文档将通过 CI 中的文档作业自动构建和发布,当标签被推送时将触发该作业。

1.  仅当发布为发布候选版时,我们想要为其创建一个新的分支,即在创建标签后立即。例如,如果我们发布 pandas 1.4.0rc0,则希望创建分支 1.4.x 来将提交后退到 1.4 版本。以及创建一个标签来标记 1.5.0 的开发开始(假设它是下一个版本):

    ```py
    git checkout -b 1.4.x
    git push upstream 1.4.x
    git checkout main
    git commit --allow-empty -m "Start 1.5.0"
    git tag -a v1.5.0.dev0 -m "DEV: Start 1.5.0"
    git push upstream main --follow-tags 
    ```

1.[wheel staging area](https://anaconda.org/scientific-python-nightly-wheels/pandas) 下载源分发和 wheels。务必确保没有丢失任何 wheels(例如由于构建失败)。

    使用你想要下载 wheels/sdist 的版本运行 scripts/download_wheels.sh 应该可以解决问题。这个脚本将在你的 pandas 克隆中创建一个 `dist` 文件夹,并将下载的 wheels 和 sdist 放在那里:

    ```py
    scripts/download_wheels.sh <VERSION> 
    ```

1.  创建一个 [新的 GitHub 发布](https://github.com/pandas-dev/pandas/releases/new)+   标签:`<version>`

    +   标题:`Pandas <version>`

    +   描述:复制相同类型上次发布的描述(发布候选版、主要/次要或补丁发布)

    +   文件:刚生成的 `pandas-<version>.tar.gz` 源分发

    +   将设置为预发布:仅检查发布候选版

    +   设置为最新发布:留选中,除非发布了较旧版本的修补程序发布(例如,在发布了 1.5 之后发布了 1.4.51.  将 wheels 上传到 PyPI:

    ```py
    twine upload pandas/dist/pandas-<version>*.{whl,tar.gz} --skip-existing 
    ```

1.  GitHub 发布几个小时后将触发一个 [自动 conda-forge PR](https://github.com/conda-forge/pandas-feedstock/pulls)。(如果你不想等待,可以打开一个标题为 `@conda-forge-admin,请更新版本` 的问题来触发机器人。)当 CI 变为绿色时合并它,它将生成 conda-forge 软件包。

    如果需要手动进行 PR,则通常需要更改版本、sha256 和构建字段。如果自上次发布以来配方中的任何其他内容都发生了变化,则这些更改应该在 `ci/meta.yaml` 中可用。

### 发布后

1.  通过登录到我们的 Web 服务器,并编辑 `/var/www/html/pandas-docs/stable` 将其指向主要和次要发布的 `version/<latest-version>`,或将 `version/<minor>` 指向 `version/<patch>` 来更新到稳定文档的符号链接。确切的说明如下(用你发布的版本的适当版本号替换示例版本号):

    > +   登录到服务器并使用正确的用户。
    > +   
    > +   cd /var/www/html/pandas-docs/
    > +   
    > +   ln -sfn version/2.1 stable(用于主要或次要发布)
    > +   
    > +   ln -sfn version/2.0.3 version/2.0(用于补丁发布)

1.  如果发布主要或次要版本,请在我们的源代码中打开一个 PR 来更新 `web/pandas/versions.json`,以在文档下拉菜单中显示所需版本。

1.  关闭已发布版本的里程碑和问题。

1.  为下一次发布创建一个新问题,并提供预计发布日期。

1.  提交一个 PR,用于下一个版本的发布说明占位符。例如,查看[1.5.3 的 PR](https://github.com/pandas-dev/pandas/pull/49843/files)。请注意,要使用的模板取决于是主要版本、次要版本还是补丁版本。

1.  在官方渠道宣布新版本发布(使用以前的公告作为参考):

    > +   pandas-dev 和 pydata 邮件列表
    > +   
    > +   推特、长毛象、电报和领英

1.  更新此版本发布说明以纠正任何不正确的内容,并更新自上次发布以来的任何更改。

## 角色

pandas 使用两个级别的权限:**分类****核心**团队成员。

分类成员可以为问题和拉取请求打标签并关闭。

核心团队成员可以为问题和拉取请求打标签和关闭,并可以合并拉取请求。

GitHub 发布了完整的[权限列表](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-roles-for-an-organization)## 任务

pandas 在很大程度上是一个志愿者项目,因此这些任务不应被视为分类和维护者的“期望”。相反,它们是对维护者身份的一般描述。

+   对新提交的问题进行分类(参见问题分类)

+   审查新打开的拉取请求

+   回应现有问题和拉取请求的更新

+   推动讨论和决策停滞的问题和拉取请求

+   提供关于 API 设计问题的经验/智慧,以确保一致性和可维护性

+   项目组织(组织/参加开发者会议,代表 pandas)

[`matthewrocklin.com/blog/2019/05/18/maintainer`](https://matthewrocklin.com/blog/2019/05/18/maintainer) 可能是有趣的背景阅读。

## 问题分类

问题分类是解决社区报告的问题的重要第一步,即使是部分贡献也是帮助维护 pandas 的好方法。只有在完成以下所有步骤后才能删除“需要分类”标签。

以下是对新打开问题进行分类的典型工作流程。

1.  **感谢报告人提出问题**

    问题跟踪器是许多人与 pandas 项目本身的第一次互动,不仅仅是使用库。因此,我们希望它是一个友好、愉快的体验。

1.  **提供了必要的信息吗?**

    理想情况下,报告人会填写问题模板,但很多人没有这样做。如果关键信息(如他们使用的 pandas 版本)缺失,请随时要求并为问题打上“需要信息”标签。报告应遵循错误报告和增强请求中的准则。如果他们没有遵循模板,您可能需要链接到该页面。

    确保标题准确反映问题。如果不清楚,自行编辑。

1.  **这是一个重复的问题吗?**

    我们有许多未解决的问题。如果新问题明显是重复的,请将新问题标记为“重复”,并关闭问题并附上指向原始问题的链接。确保仍然要感谢报告者,并鼓励他们参与到原始问题中,并可能尝试解决它。

    如果新问题提供了相关信息,例如更好或略有不同的示例,请将其作为评论或对原始帖子的编辑添加到原始问题中。

1.  **问题是最小且可重现的吗**?

    对于 bug 报告,我们要求报告者提供一个最小可重现的示例。查看 [`matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports`](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) 以获得很好的解释。如果示例不可重现,或者明显不是最小的,请随时询问报告者是否可以提供示例或简化所提供的示例。确实承认编写最小可重现示例是一项艰苦的工作。如果报告者遇到困难,您可以尝试自己编写一个,然后我们将编辑原始帖子以包含它。

    如果无法提供可重现的示例,请添加“需要信息”标签。

    如果提供了可重现的示例,但您看到了简化,则使用您的简化可重现示例编辑原始帖子。

    确保问题存在于主分支上,并且它有“需要分类”标签,直到所有步骤都完成为止。确认问题存在于主分支后,在问题上添加评论,让其他人知道已经确认过。

1.  **这是一个明确定义的功能请求吗?**

    一般来说,pandas 更喜欢在拉取请求之前在问题中讨论和设计新功能。鼓励提交者包含新功能的建议 API。让他们编写完整的文档字符串是确定具体细节的好方法。

    将新功能请求标记为“需要讨论”,因为我们需要几个 pandas 维护者的讨论,然后才能决定该提议是否符合 pandas 的范围。

1.  **这是一个使用问题吗?**

    我们更喜欢使用问题在 StackOverflow 上提出使用问题,并使用 pandas 标签。[`stackoverflow.com/questions/tagged/pandas`](https://stackoverflow.com/questions/tagged/pandas)

    如果很容易回答,请随意链接到相关文档部分,并告诉他们今后这种问题应该在 StackOverflow 上提问,并关闭问题。

1.  **我应该添加哪些标签和里程碑?**

    应用相关的标签。这有点艺术性,并且随着经验而来。查看类似的问题以了解如何标记事物的感觉。

    如果问题明确定义,并且修复似乎相对简单,请将问题标记为“好的第一个问题”。

    完成上述步骤后,请确保删除“需要分类”标签。

## 调查回归

回归是无意中破坏以前正常工作代码的 bug。调查回归的常见方法是使用 [git bisect](https://git-scm.com/docs/git-bisect),它可以找到引入 bug 的第一个提交。

例如:一个用户报告说 `pd.Series([1, 1]).sum()` 在 pandas 版本 `1.5.0` 中返回 `3`,而在版本 `1.4.0` 中返回 `2`。首先,在您的 pandas 目录中创建一个名为 `t.py` 的文件,其中包含

```py
import pandas as pd
assert pd.Series([1, 1]).sum() == 2 

然后运行:

git bisect start
git bisect good v1.4.0
git bisect bad v1.5.0
git bisect run bash -c "python setup.py build_ext -j 4; python t.py" 

这找到了改变行为的第一个提交。C 扩展必须在每一步重建,所以搜索可能需要一段时间。

退出二分查找并重新构建当前版本:

git bisect reset
python setup.py build_ext -j 4 

报告您的发现到相应的问题下,并提醒提交作者提供他们的意见。

注意

在上面的 bisect run 命令中,如果 t.py0 退出,那么提交将被视为好的,否则为坏的。当引发异常是期望的行为时,请使用适当的 try/except 语句包装代码。更多示例请参见 GH 35685

关闭问题

这里需要特别小心:许多人认为关闭一个问题意味着我们认为对话已经结束了。通常最好给报告者一些时间来回复或自行关闭他们的问题,如果确定行为不是 bug,或者功能超出了范围。有时候报告者确实会消失,我们会在对话结束后关闭问题。如果你认为一个问题应该关闭但并不完全确定,请应用“关闭候选”标签,并等待其他维护人员查看。

审查拉取请求

任何人都可以审查拉取请求:常规贡献者、筛选者或核心团队成员。但只有核心团队成员可以在准备就绪时合并拉取请求。

在审查拉取请求时,有一些要检查的事项。

  • 测试应该放在一个合理的位置:与相关测试放在同一个文件中。

  • 新的公共 API 应该包含在 doc/source/reference/ 的某处。

  • 新的/更改的 API 应该在文档字符串中使用 versionaddedversionchanged 指令。

  • 面向用户的更改应该在适当的文件中有一个 whatsnew。

  • 回归测试应该引用原始的 GitHub 问题编号,如 # GH-1234

  • 拉取请求应该被标记并分配适当的里程碑(下一个补丁发布对于回归修复和小 bug 修复,否则下一个次要里程碑)

  • 更改应符合我们的 版本政策。

回溯

pandas 支持点发布(例如 1.4.3),目的是:

  1. 修复在第一个次要版本发布中引入的新功能中的错误。
  • 例如,如果一个新功能在 1.4 中添加并包含一个 bug,修复可以应用于 1.4.3
  1. 修复在前几个次要版本中曾经有效的 bug。核心团队成员之间应该达成一致意见,认为回溯是合适的。
  • 例如,如果一个功能在 1.2 中有效,但从 1.3 开始停止工作,则修复可以应用于 1.4.3

由于 pandas 的小版本发布是基于 GitHub 分支的(例如 1.4 的点版本发布基于 1.4.x 分支),“迁移”意味着将一个拉取请求修复合并到 main 分支,并纠正与下一个点版本发布相关联的正确的次分支。

默认情况下,如果一个拉取请求被分配到下一个点版本发布的里程碑中,则一旦拉取请求被合并,backporting 过程应该由 @meeseeksdev 机器人自动进行。将会创建一个新的拉取请求将拉取请求迁移到正确的版本分支。有时由于合并冲突,需要手动创建一个拉取请求来解决代码冲突。

如果机器人没有自动启动迁移流程,您也可以在合并的拉取请求中编写一个 GitHub 评论来触发迁移:

@meeseeksdev backport version-branch 

这将触发一个工作流程,将一个给定的更改迁移到一个分支(例如 @meeseeksdev 迁移到 1.4.x)。

清理旧的问题

pandas 中的每一个未解决问题都是有成本的。未解决的问题会使查找重复问题变得更加困难,并且可能会使人们更难知道 pandas 需要做什么。尽管如此,关闭问题并不是一个目标。我们的目标是使 pandas 变得尽可能好,并且最好的方法是确保我们未解决的问题的质量很高。

有时,问题被修复了,但问题没有在拉取请求中链接。在这种情况下,评论“这个问题已经解决,但是可能需要一个测试。”并将问题标记为“好的第一个问题”和“需要测试”。

如果一个较早的问题没有遵循我们的问题模板,请编辑原始帖子以包含一个最小的示例、实际输出和期望输出。问题报告的统一性是有价值的。

如果一个较早的问题缺乏可重现的示例,请将其标记为“需要信息”并要求他们提供一个(如果可能的话,自己编写一个)。如果在合理的时间内没有提供一个示例,根据关闭问题中的政策关闭它。

清理旧的拉取请求

有时,贡献者无法完成一个拉取请求。如果已经过去了一段时间(比如两周)自上次审查请求更改以来,温柔地询问他们是否还有兴趣继续工作。如果再过两周左右没有回应,感谢他们的工作,然后要么:

  • 关闭拉取请求;

  • 将更改推送到贡献者的分支以推动他们的工作完成(如果您是 pandas-core 的一部分)。这对于推动一个重要的 PR 完成,或者修复一个小的合并冲突都是有帮助的。

如果关闭拉取请求,请在原始问题上评论“在 #1234 处有一个停滞的 PR 可能会有帮助。”,如果 PR 相对接近被接受,则可能标记问题为“好的第一个问题”。

成为 pandas 的维护者

完整流程在我们的治理文件中有详细说明。总而言之,我们很乐意将分类权限授予通过在问题跟踪器上提供帮助表现出兴趣的任何人。

添加维护者所需的步骤为:

  1. 联系贡献者,并询问他们加入的意愿。

  2. 如果接受了邀请,将贡献者添加到适当的 GitHub 团队

  • pandas-core 适用于核心团队成员
  • pandas-triage 适用于 pandas 分类成员

如果添加到 pandas-core,还有两个额外的步骤:

  1. 将贡献者添加到 pandas Google 群组。

  2. 创建拉取请求,将贡献者的 GitHub 句柄添加到 pandas-dev/pandas/web/pandas/config.yml

当前的核心团队成员列表位于 pandas-dev/pandas

合并拉取请求

只有核心团队成员可以合并拉取请求。我们有一些指导方针。

  1. 通常情况下,不应在未经批准的情况下自我合并自己的拉取请求。例外情况包括对 CI 进行微小更改(例如固定软件包版本)。如果对更改非常自信,经其他核心团队成员批准后自我合并是可以的。

  2. 不应合并正在讨论的拉取请求,或者从核心维护者那里获得-1票的拉取请求。pandas 通过共识运作。

  3. 对于较大的更改,至少需要两名核心团队成员的+1

除了 关闭问题 中列出的项目外,您还应验证拉取请求是否分配了正确的里程碑。

使用补丁发布里程碑合并的拉取请求通常将由我们的机器人回溯。请验证机器人是否注意到了合并(通常会在一分钟内留下评论)。如果需要手动回溯,请执行该操作,并在手动执行后删除“需要回溯”标签。如果在标记之前忘记分配里程碑,则可以请求机器人回溯它:

@Meeseeksdev backport <branch> 

基准机器

该团队目前拥有专用硬件,用于托管 pandas 的 ASV 性能基准的网站。结果将发布到 asv-runner.github.io/asv-collection/pandas/

配置

该机器可以使用 Ansibletomaugspurger/asv-runner 中配置。

发布

结果将发布到另一个 GitHub 存储库,tomaugspurger/asv-collection。最后,我们的文档服务器上有一个 cron 任务,从 tomaugspurger/asv-collection 拉取,以从 /speed 提供服务。请向 Tom 或 Joris 询问访问 Web 服务器的权限。

调试

基准测试由 Airflow 安排。它有一个用于查看和调试结果的仪表板。您需要设置一个 SSH 隧道来查看它们

ssh -L 8080:localhost:8080 pandas@panda.likescandy.com

配置

机器可以使用tomaugspurger/asv-runner中的 Ansible 剧本进行配置。

发布

结果发布到另一个 GitHub 存储库,tomaugspurger/asv-collection。最后,我们在文档服务器上有一个 cron 作业从tomaugspurger/asv-collection拉取数据,以从/speed提供服务。请向 Tom 或 Joris 索取访问网页服务器的权限。

调试

基准测试由 Airflow 安排。它有一个用于查看和调试结果的仪表板。您需要设置一个 SSH 隧道来查看它们

ssh -L 8080:localhost:8080 pandas@panda.likescandy.com

发布流程

发布过程使 pandas 的快照(git 提交)以特定版本号提供给用户。发布后,新的 pandas 版本将在以下位置可用:

发布新版本 pandas 的流程在下一节详细说明。

说明中包含需要用版本替换的<version>(例如1.5.2)。还有要发布的分支<branch>,这取决于要发布的版本是新版本的候选版本,还是其他任何版本。候选版本从main发布,而其他版本从它们的分支发布(例如1.5.x)。

先决条件

为了能够发布新的 pandas 版本,需要以下权限:

  • 合并权限到pandaspandas-feedstock存储库。对于后者,请打开一个 PR,将您的 GitHub 用户名添加到 conda-forge recipe 中。

  • 推送到 pandas 存储库中main的权限,以推送新标签。

  • PyPI 的写权限

  • 访问我们的网站/文档服务器。与基础设施委员会共享您的公钥,以将其添加到主服务器用户的authorized_keys文件中。

  • 访问社交媒体账户,发布公告。

预发布

  1. 与核心团队就以下主题达成一致:

    • 发布日期(主/次要版本通常每 6 个月发布一次,补丁版本每月发布一次,直到 x.x.5,即在下一个主/次要版本之前)

    • 阻碍因素(必须包含在发布中的问题和 PR)

    • 发布后的下一个版本

  2. 更新并清理即将发布的版本的发布说明,包括:

    • 设置发布的最终日期

    • 删除任何未使用的项目符号

    • 确保没有格式问题、拼写错误等。

  3. 确保正在发布的分支的最后提交的 CI 是绿色的。

  4. 如果不是候选版本,请确保所有反向移植的拉取请求已合并到正在发布的分支中。

  5. 为正在发布的版本之后的版本创建一个新的问题和里程碑。如果发布是一个候选版本,我们通常会想要为下一个主要/次要版本和下一个补丁版本创建问题和里程碑。在补丁版本的里程碑中,我们添加描述on-merge: backport to <branch>,这样标记的 PR 就会被我们的机器人自动反向移植到发布分支。

  6. 将正在发布的里程碑中的所有问题和 PR 的里程碑更改为下一个里程碑。

发布

  1. 在要发布的分支的最后提交中创建一个空提交和一个标签:

    git checkout <branch>
    git pull --ff-only upstream <branch>
    git clean -xdf
    git commit --allow-empty --author="Pandas Development Team <[[email protected]](/cdn-cgi/l/email-protection)>" -m "RLS: <version>"
    git tag -a v<version> -m "Version <version>"  # NOTE that the tag is v1.5.2 with "v" not 1.5.2
    git push upstream <branch> --follow-tags 
    

新版本的文档将通过 CI 中的文档作业自动构建和发布,在推送标签时会触发该作业。

  1. 只有在发布版本是候选版本的情况下,我们才希望立即为其创建一个新的分支,即在创建标签后。例如,如果我们要发布 pandas 1.4.0rc0,我们希望创建分支 1.4.x 来将提交反向移植到 1.4 版本。同时,创建一个标签来标记 1.5.0 的开发开始(假设它是下一个版本):

    git checkout -b 1.4.x
    git push upstream 1.4.x
    git checkout main
    git commit --allow-empty -m "Start 1.5.0"
    git tag -a v1.5.0.dev0 -m "DEV: Start 1.5.0"
    git push upstream main --follow-tags 
    
  2. wheel staging area下载源分发和轮子。务必确保没有轮子丢失(例如由于构建失败)。

    使用您想要下载轮子/源分发包的版本运行 scripts/download_wheels.sh 应该可以解决问题。此脚本将在您的 pandas 克隆内创建一个dist文件夹,并将下载的轮子和源分发包放入其中:

    scripts/download_wheels.sh <VERSION> 
    
  3. 创建一个新的 GitHub 发布

    • 标签:<版本>

    • 标题:Pandas <版本>

    • 描述:复制相同类型的最后一个发布的描述(候选版本、主要/次要版本或补丁版本)

    • 文件:刚刚生成的pandas-<版本>.tar.gz源分发包

    • 将其设置为预发布:仅检查发布候选版本

    • 设置为最新发布:保留勾选,除非发布旧版本的补丁版本(例如,在 1.5 发布后发布 1.4.5)

  4. 将轮子上传到 PyPI:

    twine upload pandas/dist/pandas-<version>*.{whl,tar.gz} --skip-existing 
    
  5. GitHub 发布后几个小时将触发一个自动的 conda-forge PR。(如果不想等待,您可以打开一个标题为@conda-forge-admin,请更新版本的问题来触发机器人。)一旦 CI 变绿,就合并它,它将生成 conda-forge 软件包。

    如果需要手动进行 PR,通常需要更改版本、sha256 和构建字段。如果自上次发布以来配方中的其他内容发生了变化,这些变化应该在ci/meta.yaml中可用。

发布后

  1. 通过登录到我们的 Web 服务器,编辑/var/www/html/pandas-docs/stable,将符号链接更新为主要和次要版本的version/<latest-version>,或者将version/<minor>更新为version/<patch>来更新稳定文档。具体的说明是(用适当的版本号替换示例版本号):

    • 登录服务器并使用正确的用户。
    • cd /var/www/html/pandas-docs/
    • ln -sfn version/2.1 stable(用于主要或次要版本的发布)
    • ln -sfn version/2.0.3 version/2.0(用于补丁版本的发布)
  2. 如果发布主要或次要版本,请在我们的源代码中打开一个 PR 来更新web/pandas/versions.json,以在文档下拉菜单中显示所需的版本。

  3. 关闭发布版本的里程碑和问题。

  4. 为下一个发布创建一个新问题,包括预计的发布日期。

  5. 打开一个 PR,占位符为下一个版本的发布说明。例如查看1.5.3 的 PR。请注意,要使用的模板取决于是主要、次要还是补丁版本的发布。

  6. 在官方渠道宣布新版本发布(参考以前的公告):

    • pandas-dev 和 pydata 邮件列表
    • Twitter、Mastodon、Telegram 和 LinkedIn
  7. 更新此发布说明以修复任何不正确的内容,并更新自上次发布以来的任何更改。

先决条件

为了能够发布新的 pandas 版本,需要以下权限:

  • 合并对pandaspandas-feedstock存储库的权限。对于后者,打开一个 PR,将您的 GitHub 用户名添加到 conda-forge 配方中。

  • 在 pandas 存储库中推送到main的权限,以推送新标签。

  • PyPI 的写入权限

  • 访问我们的网站/文档服务器。与基础设施委员会分享您的公钥,以添加到主服务器用户的authorized_keys文件中。

  • 访问社交媒体账号,发布公告。

预发布

  1. 与核心团队就下一个主题达成一致:

    • 发布日期(主要/次要版本通常每 6 个月发布一次,补丁版本每月发布一次,直到 x.x.5,即在下一个主要/次要版本之前)

    • 阻碍因素(必须包含在发布中的问题和 PR)

    • 在即将发布的版本之后的下一个版本。

  2. 更新和清理即将发布版本的发布说明,包括:

    • 设置发布的最终日期

    • 删除任何未使用的项目符号

    • 确保没有格式问题、拼写错误等。

  3. 确保 CI 在发布分支的最后一次提交中是绿色的。

  4. 如果不是发布候选版,请确保所有回溯到要发布的分支的 pull request 都已合并。

  5. 为正在发布的版本创建一个新问题和里程碑。如果发布是一个发布候选版,通常我们会想要为下一个主/次要版本和下一个补丁发布创建问题和里程碑。在补丁发布的里程碑中,我们添加描述on-merge: backport to <branch>,这样标记的 PR 就会被我们的机器人自动回溯到发布分支。

  6. 将正在发布的里程碑中的所有问题和 PR 的里程碑更改为下一个里程碑。

发布

  1. 创建一个空提交和一个标签在要发布的分支的最后一个提交上:

    git checkout <branch>
    git pull --ff-only upstream <branch>
    git clean -xdf
    git commit --allow-empty --author="Pandas Development Team <[[email protected]](/cdn-cgi/l/email-protection)>" -m "RLS: <version>"
    git tag -a v<version> -m "Version <version>"  # NOTE that the tag is v1.5.2 with "v" not 1.5.2
    git push upstream <branch> --follow-tags 
    

新版本的文档将通过 CI 中的文档作业自动构建和发布,当标签被推送时,它将被触发。

  1. 只有当发布是发布候选版时,我们才想要立即在创建标签后为其创建一个新分支。例如,如果我们发布 pandas 1.4.0rc0,我们希望创建 1.4.x 分支以将提交回溯到 1.4 版本。以及创建一个标签来标记 1.5.0 的开发开始(假设它是下一个版本):

    git checkout -b 1.4.x
    git push upstream 1.4.x
    git checkout main
    git commit --allow-empty -m "Start 1.5.0"
    git tag -a v1.5.0.dev0 -m "DEV: Start 1.5.0"
    git push upstream main --follow-tags 
    
  2. wheel staging area下载源分发和轮子。请务必确保没有缺少任何轮子(例如,由于构建失败)。

    使用您要下载轮子/源分发的版本运行scripts/download_wheels.sh应该就可以了。此脚本将在 pandas 的克隆中创建一个dist文件夹,并将下载的轮子和源分发放在其中:

    scripts/download_wheels.sh <VERSION> 
    
  3. 创建一个新的 GitHub 发布

    • 标签:<version>

    • 标题:Pandas <version>

    • 描述:复制相同类型的上一个发布的描述(发布候选版、主/次要发布或补丁发布)

    • 文件:刚刚生成的pandas-<version>.tar.gz源分发

    • 设置为预发布:仅在发布候选版时检查

    • 设置为最新发布:勾选,除非发布的是较旧版本的补丁发布(例如,在 1.5 发布后发布 1.4.5)

  4. 将轮子上传到 PyPI:

    twine upload pandas/dist/pandas-<version>*.{whl,tar.gz} --skip-existing 
    
  5. 几个小时后,GitHub 发布将触发一个自动的 conda-forge PR。(如果不想等待,您可以打开一个标题为@conda-forge-admin, please update version的问题来触发机器人。)一旦 CI 绿灯,就合并它,它将生成 conda-forge 包。

    如果需要进行手动 PR,通常需要更改版本、sha256 和构建字段。如果食谱中的其他内容自上次发布以来发生了变化,则这些变化应该在ci/meta.yaml中可用。

发布后

  1. 通过登录到我们的 Web 服务器并编辑 /var/www/html/pandas-docs/stable 来更新到稳定文档的符号链接,指向主要和次要版本的 version/<latest-version>,或者将 version/<minor> 指向 version/<patch> 以适应补丁版本。确切的说明如下(将示例版本号替换为您发布的版本的适当版本号):

    • 登录服务器并使用正确的用户。
    • cd /var/www/html/pandas-docs/
    • ln -sfn version/2.1 stable(用于主要或次要版本发布)
    • ln -sfn version/2.0.3 version/2.0(用于补丁版本发布)
  2. 如果发布主要或次要版本,请在我们的源代码中打开一个 PR 来更新 web/pandas/versions.json,以便在文档下拉菜单中显示所需的版本。

  3. 关闭发布版本的里程碑和问题。

  4. 为下一个版本创建一个新的问题,包括预计发布日期。

  5. 打开一个 PR,用于下一个版本的发布说明的占位符。参见1.5.3 的 PR的示例。请注意,要使用的模板取决于是主要版本、次要版本还是补丁版本。

  6. 在官方渠道宣布新版本发布(参考先前的公告):

    • pandas-dev 和 pydata 邮件列表
    • Twitter、Mastodon、Telegram 和 LinkedIn
  7. 更新本次发布的说明以修正任何不正确的地方,并更新自上次发布以来的任何更改。

内部情况

原文:pandas.pydata.org/docs/development/internals.html

本节将介绍一些 pandas 内部情况。 主要面向 pandas 本身的开发者。

索引

在 pandas 中,有几个实现的对象可以作为轴标签的有效容器:

  • Index:通用的“有序集”对象,一个对象 dtype 的 ndarray,假设其内容为空。 标签必须是可散列的(并且可能是不可变的)和唯一的。 填充一个字典,标签到位置在 Cython 中执行O(1)查找。

  • MultiIndex:标准的分层索引对象

  • DatetimeIndex:带有Timestamp包装元素的索引对象(impl 是 int64 值)

  • TimedeltaIndex:一个带有Timedelta包装元素的索引对象(impl 是 int64 值)

  • PeriodIndex:带有周期元素的索引对象

有一些函数可以方便地创建常规索引:

  • date_range():从时间规则或 DateOffset 生成的固定频率日期范围。 一个由 Python datetime 对象组成的 ndarray

  • period_range():从时间规则或 DateOffset 生成的固定频率日期范围。一个由Period对象组成的 ndarray,表示时间跨度

警告

不支持自定义Index子类,应该使用ExtensionArray接口来实现自定义行为。

多重索引

在内部,MultiIndex由几个部分组成:级别,整数代码和级别名称

In [1]: index = pd.MultiIndex.from_product(
 ...:    [range(3), ["one", "two"]], names=["first", "second"]
 ...: )
 ...: 

In [2]: index
Out[2]: 
MultiIndex([(0, 'one'),
 (0, 'two'),
 (1, 'one'),
 (1, 'two'),
 (2, 'one'),
 (2, 'two')],
 names=['first', 'second'])

In [3]: index.levels
Out[3]: FrozenList([[0, 1, 2], ['one', 'two']])

In [4]: index.codes
Out[4]: FrozenList([[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]])

In [5]: index.names
Out[5]: FrozenList(['first', 'second']) 

你可能会猜到,这些代码确定了在索引的每一层中用于标识该位置的唯一元素。值得注意的是,排序性仅仅由整数代码确定,并且不会检查(或关心)级别本身是否已排序。幸运的是,构造函数from_tuples()from_arrays()确保了这一点,但如果您自己计算级别和代码,请务必小心。

pandas 使用自定义类型扩展了 NumPy 的类型系统,例如 Categorical 或带有时区的日期时间,因此我们有多个“值”的概念。对于 1-D 容器(Index 类和 Series)我们有以下约定:

  • cls._values 指的是“最佳可能”的数组。这可能是一个 ndarrayExtensionArray

所以,例如,Series[category]._values是一个Categorical

子类化 pandas 数据结构

此部分已移至 子类化 pandas 数据结构。

索引

在 pandas 中,已实现了一些对象,它们可以作为轴标签的有效容器:

  • Index:通用的“有序集合”对象,一个对象数据类型的 ndarray,不假设其内容。标签必须是可散列的(并且可能是不可变的)和唯一的。填充一个字典,将标签映射到 Cython 中的位置,以进行O(1)的查找。

  • MultiIndex:标准的分层索引对象

  • DatetimeIndex:一个带有 Timestamp 的索引对象(实现是 int64 值)

  • TimedeltaIndex:一个带有 Timedelta 的索引对象(实现是 int64 值)

  • PeriodIndex:一个带有周期元素的索引对象

有一些函数可以轻松创建常规索引:

  • date_range():从时间规则或 DateOffset 生成的固定频率日期范围。一个 Python datetime 对象的 ndarray

  • period_range():从时间规则或日期偏移量生成的固定频率日期范围。一个Period对象的 ndarray,表示时间段

警告

不支持自定义Index子类,应该使用ExtensionArray接口实现自定义行为。

多重索引

内部,MultiIndex由几个部分组成:级别、整数代码和级别名称

In [1]: index = pd.MultiIndex.from_product(
 ...:    [range(3), ["one", "two"]], names=["first", "second"]
 ...: )
 ...: 

In [2]: index
Out[2]: 
MultiIndex([(0, 'one'),
 (0, 'two'),
 (1, 'one'),
 (1, 'two'),
 (2, 'one'),
 (2, 'two')],
 names=['first', 'second'])

In [3]: index.levels
Out[3]: FrozenList([[0, 1, 2], ['one', 'two']])

In [4]: index.codes
Out[4]: FrozenList([[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]])

In [5]: index.names
Out[5]: FrozenList(['first', 'second']) 

你可能猜到,代码确定了索引每一层中与该位置相关联的唯一元素。需要注意的是,排序性完全由整数代码确定,并不检查(或关心)层级本身是否已排序。幸运的是,构造函数from_tuples()from_arrays()确保了这一点,但如果你自己计算层级和代码,请务必小心。

pandas 通过自定义类型(例如Categorical或带有时区的日期时间)扩展了 NumPy 的类型系统,因此我们有多种“值”的概念。对于一维容器(Index类和Series),我们有以下约定:

  • cls._values是“最佳可能”数组。这可以是一个ndarrayExtensionArray

因此,例如,Series[category]._values是一个Categorical

多重索引

内部,MultiIndex由几个部分组成:级别、整数代码和级别名称

In [1]: index = pd.MultiIndex.from_product(
 ...:    [range(3), ["one", "two"]], names=["first", "second"]
 ...: )
 ...: 

In [2]: index
Out[2]: 
MultiIndex([(0, 'one'),
 (0, 'two'),
 (1, 'one'),
 (1, 'two'),
 (2, 'one'),
 (2, 'two')],
 names=['first', 'second'])

In [3]: index.levels
Out[3]: FrozenList([[0, 1, 2], ['one', 'two']])

In [4]: index.codes
Out[4]: FrozenList([[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]])

In [5]: index.names
Out[5]: FrozenList(['first', 'second']) 

你可能猜到,代码确定了索引每一层中与该位置相关联的唯一元素。需要注意的是,排序性完全由整数代码确定,并不检查(或关心)层级本身是否已排序。幸运的是,构造函数from_tuples()from_arrays()确保了这一点,但如果你自己计算层级和代码,请务必小心。

pandas 通过自定义类型(如Categorical或带有时区的日期时间)扩展了 NumPy 的类型系统,因此我们有多种“值”的概念。对于一维容器(Index类和Series),我们有以下约定:

  • cls._values 是“最佳可能”的数组。这可以是ndarrayExtensionArray

因此,例如,Series[category]._values 是一个Categorical

继承 pandas 数据结构

本节已移至继承 pandas 数据结构。

写时复制

原文:pandas.pydata.org/docs/development/copy_on_write.html

写时复制是一种机制,用于简化索引 API 并通过避免拷贝来提高性能。写时复制意味着任何以任何方式派生自另一个 DataFrame 或 Series 的 DataFrame 或 Series 总是表现为副本。关于如何高效使用写时复制的说明可以在这里找到。

引用跟踪

为了确定在向 DataFrame 写入数据时是否需要进行复制,我们必须知道值是否与另一个 DataFrame 共享。pandas 在内部跟踪所有与另一个块共享值的 Blocks,以便能够在需要触发复制时进行判断。引用跟踪机制是在块级别实现的。

我们使用自定义的引用跟踪对象,BlockValuesRefs,它跟踪每个块,这些块的值彼此共享内存。引用是通过弱引用进行持有的。每一对共享某些内存的块都应该指向同一个BlockValuesRefs对象。如果一个块超出范围,对该块的引用就会消失。因此,引用跟踪对象始终知道有多少个块是活动的并且共享内存。

每当一个 DataFrameSeries 对象与另一个对象共享数据时,每个对象都必须具有自己的 BlockManager 和 Block 对象。因此,换句话说,一个 Block 实例(由 DataFrame 持有,不一定适用于中间对象)应该始终只用于一个 DataFrame/Series 对象。例如,当您想要为另一个对象使用相同的 Block 时,您可以使用 block.copy(deep=False) 创建 Block 实例的浅拷贝(这将创建一个具有相同底层值的新 Block 实例,并且会正确设置引用)。

在写入值之前,我们可以向引用跟踪对象询问是否有另一个活动的块与我们共享数据。如果实际上有另一个活动的块,我们可以在写入之前触发复制。

引用跟踪

为了确定在向 DataFrame 写入数据时是否需要进行复制,我们必须知道值是否与另一个 DataFrame 共享。pandas 在内部跟踪所有与另一个块共享值的 Blocks,以便能够在需要触发复制时进行判断。引用跟踪机制是在块级别实现的。

我们使用一个自定义的引用跟踪对象,BlockValuesRefs,用于跟踪每个块,其值彼此共享内存。引用通过弱引用进行保持。每对共享某些内存的块应指向同一个BlockValuesRefs对象。如果一个块超出范围,对该块的引用将失效。因此,引用跟踪对象始终知道有多少块是活动的并共享内存。

每当一个DataFrameSeries对象与另一个对象共享数据时,要求每个对象都有自己的 BlockManager 和 Block 对象。因此,换句话说,一个 Block 实例(由 DataFrame 持有,不一定是用于中间对象)应始终仅用于单个 DataFrame/Series 对象。例如,当您想要为另一个对象使用相同的 Block 时,您可以使用block.copy(deep=False)创建 Block 实例的浅拷贝(这将创建一个具有相同基础值的新 Block 实例,并且将正确设置引用)。

我们可以在写入值之前询问引用跟踪对象是否有另一个与我们共享数据的块存在。如果实际上有另一个活动的块存在,则我们可以在写入之前触发一次复制。

调试 C 扩展

原文:pandas.pydata.org/docs/development/debugging_extensions.html

Pandas 使用 Cython 和 C/C++ 扩展模块来优化性能。不幸的是,标准 Python 调试器不允许您进入这些扩展。可以使用Cython 调试器来调试 Cython 扩展,可以使用您平台编译器附带的工具来调试 C/C++ 扩展。

对于有限或没有 C/C++ 经验的 Python 开发人员来说,这可能是一项艰巨的任务。核心开发者威尔·艾德(Will Ayd)编写了一系列三部分的博客文章,以帮助引导您从标准 Python 调试器进入这些其他工具:

  1. Python 基础调试第 1 部分 - Python
  2. Python 基础调试第 2 部分 - Python 扩展
  3. Python 基础调试第 3 部分 - Cython 扩展

本地调试

默认情况下,从源代码构建 pandas 将生成一个发布构建。要生成开发构建,您可以键入:

pip install -ve . --no-build-isolation --config-settings=builddir="debug" --config-settings=setup-args="-Dbuildtype=debug" 

注意

conda 环境使用旨在生成发布版的标志更新 CFLAGS/CPPFLAGS。如果使用 conda,则可能需要设置CFLAGS="$CFLAGS -O0"CPPFLAGS="$CPPFLAGS -O0",以确保为调试关闭优化。

通过指定builddir="debug",所有目标将构建并放置在相对于项目根目录的调试目录中。这有助于保持调试和发布工件分开;当然,您也可以选择不同的目录名称或完全省略,如果您不想分开构建类型的话。

使用 Docker

为简化调试过程,pandas 创建了一个带有 Python 调试版和预先安装的 gdb/Cython 调试器的 Docker 映像。您可以通过docker pull pandas/pandas-debug来获取此映像的访问权限,也可以从tooling/debug文件夹中构建它。

然后,您可以通过以下方式将您的 pandas 仓库挂载到此映像中:

docker  run  --rm  -it  -w  /data  -v  ${PWD}:/data  pandas/pandas-debug 

在映像内,您可以使用 meson 来构建/安装 pandas,并使用以下命令将构建工件放置到debug文件夹中:

python  -m  pip  install  -ve  .  --no-build-isolation  --config-settings=builddir="debug"  --config-settings=setup-args="-Dbuildtype=debug" 

如果计划使用 cygdb,该应用程序所需的文件将放置在构建文件夹中。因此,您首先必须cd到构建文件夹,然后启动该应用程序。

cd  debug
cygdb 

在调试器内,您可以使用cygdb 命令来导航 Cython 扩展。

编辑器支持

meson 构建系统会自动生成一个编译数据库,并将其放置在构建目录中。许多语言服务器和集成开发环境可以利用这些信息,在您输入时提供代码补全、跳转到定义和错误检查支持。

每个语言服务器/集成开发环境选择查找编译数据库的方式可能有所不同。如果有疑问,您可能希望在项目根目录创建一个符号链接,指向构建目录中的编译数据库。假设您使用debug作为目录名称,您可以运行:

ln -s debug/compile_commands.json . 

本地调试

默认情况下,从源代码构建 pandas 会生成一个发布版本。要生成开发版本,您可以输入:

pip install -ve . --no-build-isolation --config-settings=builddir="debug" --config-settings=setup-args="-Dbuildtype=debug" 

注意

conda 环境会使用旨在生成发布版本的标志更新 CFLAGS/CPPFLAGS。如果使用 conda,您可能需要设置CFLAGS="$CFLAGS -O0"CPPFLAGS="$CPPFLAGS -O0"以确保在调试时关闭优化。

通过指定builddir="debug",所有目标将被构建并放置在相对于项目根目录的 debug 目录中。这有助于保持调试和发布产物的分离;当然,您可以选择不同的目录名称或者完全省略,如果您不在乎分离构建类型。

使用 Docker

为了简化调试过程,pandas 创建了一个带有 Python 调试版本和预安装的 gdb/Cython 调试器的 Docker 镜像。您可以通过docker pull pandas/pandas-debug获取此镜像的访问权限,或者在本地从tooling/debug文件夹构建它。

然后,您可以通过以下方式将您的 pandas 仓库挂载到这个镜像中:

docker  run  --rm  -it  -w  /data  -v  ${PWD}:/data  pandas/pandas-debug 

在镜像内部,您可以使用 meson 来构建/安装 pandas,并将构建产物放入一个debug文件夹中,命令如下:

python  -m  pip  install  -ve  .  --no-build-isolation  --config-settings=builddir="debug"  --config-settings=setup-args="-Dbuildtype=debug" 

如果打算使用 cygdb,那么该应用程序所需的文件将放在构建文件夹中。因此,您必须首先cd到构建文件夹,然后启动该应用程序。

cd  debug
cygdb 

在调试器中,您可以使用cygdb 命令来浏览 cython 扩展。

编辑器支持

meson 构建系统会自动生成一个编译数据库,并将其放置在构建目录中。许多语言服务器和集成开发环境可以利用这些信息,在您输入时提供代码补全、跳转到定义和错误检查支持。

每个语言服务器/集成开发环境选择查找编译数据库的方式可能有所不同。如果有疑问,您可能希望在项目根目录创建一个符号链接,指向构建目录中的编译数据库。假设您使用debug作为目录名称,您可以运行:

ln -s debug/compile_commands.json . 

扩展 pandas

原文:pandas.pydata.org/docs/development/extending.html

虽然 pandas 提供���丰富的方法、容器和数据类型,但您的需求可能无法完全满足。pandas 提供了几种扩展 pandas 的选项。

注册自定义访问器

库可以使用装饰器pandas.api.extensions.register_dataframe_accessor()pandas.api.extensions.register_series_accessor()pandas.api.extensions.register_index_accessor(),向 pandas 对象添加额外的“命名空间”。所有这些都遵循类似的约定:您装饰一个类,提供要添加的属性名称。类的__init__方法获取被装饰的对象。例如:

@pd.api.extensions.register_dataframe_accessor("geo")
class GeoAccessor:
    def __init__(self, pandas_obj):
        self._validate(pandas_obj)
        self._obj = pandas_obj

    @staticmethod
    def _validate(obj):
        # verify there is a column latitude and a column longitude
        if "latitude" not in obj.columns or "longitude" not in obj.columns:
            raise AttributeError("Must have 'latitude' and 'longitude'.")

    @property
    def center(self):
        # return the geographic center point of this DataFrame
        lat = self._obj.latitude
        lon = self._obj.longitude
        return (float(lon.mean()), float(lat.mean()))

    def plot(self):
        # plot this array's data on a map, e.g., using Cartopy
        pass 

现在用户可以使用geo命名空间访问您的方法:

>>> ds = pd.DataFrame(
...     {"longitude": np.linspace(0, 10), "latitude": np.linspace(0, 20)}
... )
>>> ds.geo.center
(5.0, 10.0)
>>> ds.geo.plot()
# plots data on a map 

这可以是一种方便的方式来扩展 pandas 对象,而无需对其进行子类化。如果您编写了自定义访问器,请发起拉取请求将其添加到我们的生态系统页面。

我们强烈建议在访问器的__init__中验证数据。在我们的GeoAccessor中,我们验证数据是否包含预期的列,当验证失败时会引发AttributeError。对于Series访问器,如果访问器仅适用于特定的数据类型,应验证dtype。 ## 扩展类型

注意

pandas.api.extensions.ExtensionDtypepandas.api.extensions.ExtensionArray的 API 在 pandas 1.5 之前是实验性的。从版本 1.5 开始,未来的更改将遵循 pandas 弃用政策。

pandas 定义了一个接口,用于实现扩展 NumPy 类型系统的数据类型和数组。pandas 本身使用扩展系统来处理一些不内置于 NumPy 中的类型(分类、周期、间隔、带时区的日期时间)。

库可以定义自定义数组和数据类型。当 pandas 遇到这些对象时,它们将被正确处理(即不会转换为对象的 ndarray)。许多方法,如pandas.isna(),将分派到扩展类型的实现。

如果您正在构建实现该接口的库,请在 生态系统页面 上宣传它。

接口由两个类组成。

ExtensionDtype

pandas.api.extensions.ExtensionDtype 类似于 numpy.dtype 对象。它描述了数据类型。实现者负责一些独特的项目,比如名称。

特别重要的一项是 type 属性。这应该是您数据的标量类型的类。例如,如果您正在为 IP 地址数据编写扩展数组,则可能是 ipaddress.IPv4Address

请参阅 扩展 dtype 源代码 以获取接口定义。

pandas.api.extensions.ExtensionDtype 可以注册到 pandas 中,以允许通过字符串 dtype 名称进行创建。这允许使用注册的字符串名称实例化 Series.astype(),例如,'category'CategoricalDtype 的注册字符串访问器。

请参阅 扩展 dtype dtypes 以获取有关如何注册 dtypes 的更多信息。

ExtensionArray

此类提供了所有类似数组的功能。ExtensionArrays 限制为 1 维。ExtensionArray 通过 dtype 属性与 ExtensionDtype 关联。

pandas 对通过其 __new____init__ 创建扩展数组的方式没有限制,并且对如何存储数据也没有限制。我们要求您的数组可以转换为 NumPy 数组,即使这可能相对昂贵(就像对于 Categorical 一样)。

它们可以由零个、一个或多个 NumPy 数组支持。例如,pandas.Categorical 是由两个数组支持的扩展数组,一个用于代码,一个用于类别。IPv6 地址数组可以由具有两个字段的 NumPy 结构化数组支持,一个用于低 64 位,一个用于高 64 位。或者它们可以由其他某种存储类型支持,比如 Python 列表。

请参阅 扩展数组源代码 以获取接口定义。文档字符串和注释包含有关正确实现接口的指导。

ExtensionArray 运算符支持

默认情况下,类ExtensionArray没有定义任何运算符。提供 ExtensionArray 运算符支持的两种方法:

  1. 在你的ExtensionArray子类上定义每个运算符。

  2. 使用一个依赖于 ExtensionArray 的基础元素(标量)上已经定义的运算符的 pandas 中的运算符实现。

注意

无论采用哪种方法,如果希望在与 NumPy 数组进行二元运算时调用你的实现,可能需要设置__array_priority__

对于第一种方法,你需要定义所选运算符,例如,__add____le__等,你希望你的ExtensionArray子类支持。

第二种方法假设ExtensionArray的基础元素(即标量类型)已经定义了各自的运算符。换句话说,如果你的名为MyExtensionArrayExtensionArray被实现为每个元素都是MyExtensionElement类的实例,那么如果为MyExtensionElement定义了运算符,第二种方法将自动为MyExtensionArray定义运算符。

一个混合类,ExtensionScalarOpsMixin支持这第二种方法。如果开发一个ExtensionArray子类,例如MyExtensionArray,只需将ExtensionScalarOpsMixin作为MyExtensionArray的父类之一,并调用方法_add_arithmetic_ops()和/或_add_comparison_ops()将运算符连接到你的MyExtensionArray类中,如下所示:

from pandas.api.extensions import ExtensionArray, ExtensionScalarOpsMixin

class MyExtensionArray(ExtensionArray, ExtensionScalarOpsMixin):
    pass

MyExtensionArray._add_arithmetic_ops()
MyExtensionArray._add_comparison_ops() 

注意

由于pandas会自动逐个元素调用底层运算符,这可能不如直接在ExtensionArray上实现相关运算符的性能好。

对于算术运算,此实现将尝试使用元素级操作的结果重建一个新的ExtensionArray。是否成功取决于操作是否返回对ExtensionArray有效的结果。如果无法重建ExtensionArray,则返回包含返回标量的 ndarray。

为了方便实现和与 pandas 和 NumPy ndarray 之间的操作一致性,我们建议在你的二元运算中处理 Series 和 Indexes。相反,你应该检测这些情况并返回NotImplemented。当 pandas 遇到像op(Series, ExtensionArray)这样的操作时,pandas 会

  1. Series中解包数组(Series.array

  2. 调用result = op(values, ExtensionArray)

  3. 将结果重新封装在Series中 ### NumPy 通用函数

Series实现了__array_ufunc__。作为实现的一部分,pandas 从 Series中拆箱ExtensionArray,应用 ufunc,并在必要时重新装箱。

如果适用,强烈建议您在扩展数组中实现__array_ufunc__,以避免强制转换为 ndarray。参见NumPy 文档中的示例。

作为您的实现的一部分,当在inputs中检测到 pandas 容器(SeriesDataFrameIndex)时,我们要求您转交给 pandas。如果有任何一个存在,则应返回NotImplemented。pandas 将负责从容器中解包数组并重新调用 ufunc,以解包输入。

我们提供了一个测试套件,用于确保您的扩展数组满足预期的行为。要使用测试套件,您必须提供几个 pytest fixtures 并继承基本测试类。所需的 fixtures 在pandas-dev/pandas中找到。

要使用测试,必须对其进行子类化:

from pandas.tests.extension import base

class TestConstructors(base.BaseConstructorsTests):
    pass 

pandas-dev/pandas中查看所有可用测试的列表。### 与 Apache Arrow 兼容性

ExtensionArray可以通过实现两个方法支持转换为/从pyarrow数组(因此支持例如序列化到 Parquet 文件格式):ExtensionArray.__arrow_array__ExtensionDtype.__from_arrow__

ExtensionArray.__arrow_array__确保pyarrow知道如何将特定的扩展数组转换为pyarrow.Array(即使作为 pandas DataFrame 中的列包含):

class MyExtensionArray(ExtensionArray):
    ...

    def __arrow_array__(self, type=None):
        # convert the underlying array values to a pyarrow Array
        import pyarrow

        return pyarrow.array(..., type=type) 

然后,ExtensionDtype.__from_arrow__方法控制从 pyarrow 返回到 pandas ExtensionArray 的转换。该方法仅接收一个 pyarrow ArrayChunkedArray作为参数,并且预期返回此 dtype 和传递值的适当 pandasExtensionArray

class ExtensionDtype:
    ...

    def __from_arrow__(self, array: pyarrow.Array/ChunkedArray) -> ExtensionArray:
        ... 

Arrow 文档中查看更多信息。

这些方法已经为 pandas 中包含的可空整数和字符串扩展 dtype 实现,并确保与 pyarrow 和 Parquet 文件格式的往返。## 子类化 pandas 数据结构

警告

在考虑子类化 pandas 数据结构之前,有一些更简单的替代方案。

  1. 使用 pipe 进行可扩展的方法链。

  2. 使用组合。参见这里

  3. 通过注册访问器进行扩展

  4. 通过扩展类型扩展

本节描述了如何对pandas数据结构进行子类化以满足更具体的需求。有两点需要注意:

  1. 覆盖构造函数属性。

  2. 定义原始属性

注意

你可以在geopandas项目中找到一个很好的例子。

覆盖构造函数属性

每个数据结构都有几个构造函数属性,用于返回操作的结果作为一个新的数据结构。通过覆盖这些属性,你可以通过pandas数据操作保留子类。

子类上可以定义 3 种可能的构造函数属性:

  • DataFrame/Series._constructor:当一个操作结果与原始数据具有相同的维度时使用。

  • DataFrame._constructor_sliced:当一个DataFrame(子类)操作结果应该是一个Series(子类)时使用。

  • Series._constructor_expanddim:当一个Series(子类)操作结果应该是一个DataFrame(子类)时使用,例如Series.to_frame()

下面的示例显示了如何定义SubclassedSeriesSubclassedDataFrame覆盖构造函数属性。

class SubclassedSeries(pd.Series):
    @property
    def _constructor(self):
        return SubclassedSeries

    @property
    def _constructor_expanddim(self):
        return SubclassedDataFrame

class SubclassedDataFrame(pd.DataFrame):
    @property
    def _constructor(self):
        return SubclassedDataFrame

    @property
    def _constructor_sliced(self):
        return SubclassedSeries 
>>> s = SubclassedSeries([1, 2, 3])
>>> type(s)
<class '__main__.SubclassedSeries'>

>>> to_framed = s.to_frame()
>>> type(to_framed)
<class '__main__.SubclassedDataFrame'>

>>> df = SubclassedDataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
>>> df
 A  B  C
0  1  4  7
1  2  5  8
2  3  6  9

>>> type(df)
<class '__main__.SubclassedDataFrame'>

>>> sliced1 = df[["A", "B"]]
>>> sliced1
 A  B
0  1  4
1  2  5
2  3  6

>>> type(sliced1)
<class '__main__.SubclassedDataFrame'>

>>> sliced2 = df["A"]
>>> sliced2
0    1
1    2
2    3
Name: A, dtype: int64

>>> type(sliced2)
<class '__main__.SubclassedSeries'> 

定义原始属性

要让原始数据结构具有额外的属性,你应该让pandas知道添加了哪些属性。pandas将未知属性映射到数据名称,覆盖__getattribute__。定义原始属性可以通过以下两种方式之一完成:

  1. 为临时属性定义_internal_names_internal_names_set,这些属性不会传递给操作结果。

  2. 为普通属性定义_metadata,这些属性将传递给操作结果。

下面是一个示例,定义了两个原始属性,“internal_cache”作为临时属性,以及“added_property”作为普通属性

class SubclassedDataFrame2(pd.DataFrame):

    # temporary properties
    _internal_names = pd.DataFrame._internal_names + ["internal_cache"]
    _internal_names_set = set(_internal_names)

    # normal properties
    _metadata = ["added_property"]

    @property
    def _constructor(self):
        return SubclassedDataFrame2 
>>> df = SubclassedDataFrame2({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
>>> df
 A  B  C
0  1  4  7
1  2  5  8
2  3  6  9

>>> df.internal_cache = "cached"
>>> df.added_property = "property"

>>> df.internal_cache
cached
>>> df.added_property
property

# properties defined in _internal_names is reset after manipulation
>>> df[["A", "B"]].internal_cache
AttributeError: 'SubclassedDataFrame2' object has no attribute 'internal_cache'

# properties defined in _metadata are retained
>>> df[["A", "B"]].added_property
property 
```## 绘图后端

pandas 可以通过第三方绘图后端进行扩展。主要思想是让用户选择一个基于 Matplotlib 提供的绘图后端之外的绘图后端。例如:

```py
>>> pd.set_option("plotting.backend", "backend.module")
>>> pd.Series([1, 2, 3]).plot() 

这将更或多或少等同于:

>>> import backend.module
>>> backend.module.plot(pd.Series([1, 2, 3])) 

后端模块可以使用其他可视化工具(Bokeh、Altair 等)来生成图表。

实现绘图后端的库应该使用入口点来使其后端对 pandas 可发现。关键是"pandas_plotting_backends"。例如,pandas 将默认的“matplotlib”后端注册如下。

# in setup.py
setup(  # noqa: F821
    ...,
    entry_points={
        "pandas_plotting_backends": [
            "matplotlib = pandas:plotting._matplotlib",
        ],
    },
) 

如何实现第三方绘图后端的更多信息,请参见pandas-dev/pandas。## 与第三方类型的算术

为了控制自定义类型与 pandas 类型之间的算术操作方式,实现__pandas_priority__。类似于 numpy 的__array_priority__语义,DataFrameSeriesIndex对象上的算术方法将委托给other,如果它具有较高值的__pandas_priority__属性。

默认情况下,pandas 对象尝试与其他对象进行操作,即使它们不是 pandas 已知的类型:

>>> pd.Series([1, 2]) + [10, 20]
0    11
1    22
dtype: int64 

在上面的例子中,如果[10, 20]是可以理解为列表的自定义类型,pandas 对象仍然会以相同的方式与其进行操作。

在某些情况下,将操作委托给另一种类型是有用的。例如,考虑我实现了一个自定义列表对象,并且我希望将我的自定义列表与 pandas Series 相加的结果是我的列表的一个实例,而不是前面示例中所见的 Series。通过定义我自定义列表的__pandas_priority__属性,并将其设置为较高的值,比我想要与之进行操作的 pandas 对象的优先级更高,现在可以实现这一点。

DataFrameSeriesIndex__pandas_priority__分别为400030002000。基本的ExtensionArray.__pandas_priority__1000

class CustomList(list):
    __pandas_priority__ = 5000

    def __radd__(self, other):
        # return `self` and not the addition for simplicity
        return self

custom = CustomList()
series = pd.Series([1, 2, 3])

# Series refuses to add custom, since it's an unknown type with higher priority
assert series.__add__(custom) is NotImplemented

# This will cause the custom class `__radd__` being used instead
assert series + custom is custom 
```## 注册自定义访问器

库可以使用装饰器`pandas.api.extensions.register_dataframe_accessor()`、`pandas.api.extensions.register_series_accessor()`和`pandas.api.extensions.register_index_accessor()`来为 pandas 对象添加额外的“命名空间”。所有这些都遵循类似的约定:您装饰一个类,提供要添加的属性名称。类的`__init__`方法获取被装饰的对象。例如:

```py
@pd.api.extensions.register_dataframe_accessor("geo")
class GeoAccessor:
    def __init__(self, pandas_obj):
        self._validate(pandas_obj)
        self._obj = pandas_obj

    @staticmethod
    def _validate(obj):
        # verify there is a column latitude and a column longitude
        if "latitude" not in obj.columns or "longitude" not in obj.columns:
            raise AttributeError("Must have 'latitude' and 'longitude'.")

    @property
    def center(self):
        # return the geographic center point of this DataFrame
        lat = self._obj.latitude
        lon = self._obj.longitude
        return (float(lon.mean()), float(lat.mean()))

    def plot(self):
        # plot this array's data on a map, e.g., using Cartopy
        pass 

现在用户可以使用geo命名空间访问您的方法:

>>> ds = pd.DataFrame(
...     {"longitude": np.linspace(0, 10), "latitude": np.linspace(0, 20)}
... )
>>> ds.geo.center
(5.0, 10.0)
>>> ds.geo.plot()
# plots data on a map 

这可以是一种方便的方法,用于扩展 pandas 对象而不是将它们子类化。如果您编写了一个自定义访问器,请提交一个拉取请求将其添加到我们的 生态系统 页面。

我们强烈建议在访问器的 __init__ 中验证数据。在我们的 GeoAccessor 中,我们验证数据包含预期的列,当验证失败时引发 AttributeError。对于 Series 访问器,如果访问器仅适用于某些 dtype,则应验证 dtype

扩展类型

注意

pandas.api.extensions.ExtensionDtypepandas.api.extensions.ExtensionArray API 在 pandas 1.5 之前是实验性的。从 1.5 版本开始,未来的更改将遵循 pandas 弃用策略。

pandas 定义了一种接口,用于实现扩展 NumPy 的类型系统的数据类型和数组。pandas 本身使用扩展系统来处理一些不内置于 NumPy 中的类型(分类、周期、区间、带时区的日期时间)。

库可以定义自定义数组和数据类型。当 pandas 遇到这些对象时,它们将被正确处理(即不会转换为对象的 ndarray)。许多方法,如 pandas.isna(),将分派到扩展类型的实现。

如果您正在构建一个实现该接口的库,请在 生态系统页面 上宣传它。

接口由两个类组成。

ExtensionDtype

一个 pandas.api.extensions.ExtensionDtype 类似于一个 numpy.dtype 对象。它描述了数据类型。实现者负责一些唯一的项目,如名称。

特别重要的一项是 type 属性。这应该是您数据的标量类型的类。例如,如果您正在为 IP 地址数据编写扩展数组,则可能是 ipaddress.IPv4Address

有关接口定义,请参阅 扩展 dtype 源

pandas.api.extensions.ExtensionDtype 可以注册到 pandas 中,以允许通过字符串 dtype 名称进行创建。这允许使用注册的字符串名称,例如'category'CategoricalDtype的注册字符串访问器,来实例化Series.astype()

查看扩展 dtype dtypes以获取有关如何注册 dtypes 的更多信息。

ExtensionArray

此类提供所有类似数组的功能。ExtensionArrays 限制为 1 维。通过dtype属性,ExtensionArray 与 ExtensionDtype 相关联。

pandas 对通过其__new____init__创建扩展数组没有任何限制,并且不限制您存储数据的方式。我们要求您的数组可以转换为 NumPy 数组,即使这可能相对昂贵(就像Categorical一样)。

它们可以由零个、一个或多个 NumPy 数组支持。例如,pandas.Categorical是由两个数组支持的扩展数组,一个用于代码,一个用于类别。一个 IPv6 地址数组可以由一个具有两个字段的 NumPy 结构化数组支持,一个用于低 64 位,一个用于高 64 位。或者它们可以由其他存储类型支持,比如 Python 列表。

查看扩展数组源代码以获取接口定义。文档字符串和注释包含了正确实现接口的指导。

ExtensionArray 操作符支持

默认情况下,类ExtensionArray没有定义任何操作符。提供 ExtensionArray 操作符支持有两种方法:

  1. 在您的ExtensionArray子类上定义每个操作符。

  2. 使用 pandas 中依赖于底层元素(标量)已定义的操作符的操作符实现。

注意

无论采取哪种方法,如果希望在与 NumPy 数组进行二进制操作时调用您的实现,可能需要设置__array_priority__

对于第一种方法,您需要定义所选操作符,例如__add____le__等,以便您的ExtensionArray子类支持。

第二种方法假设ExtensionArray的底层元素(即标量类型)已经定义了各个运算符。换句话说,如果你的ExtensionArray命名为MyExtensionArray,并且每个元素都是MyExtensionElement类的一个实例,那么如果为MyExtensionElement定义了运算符,第二种方法将自动为MyExtensionArray定义运算符。

一个混合类,ExtensionScalarOpsMixin支持这第二种方法。如果开发一个ExtensionArray子类,例如MyExtensionArray,只需将ExtensionScalarOpsMixin包含为MyExtensionArray的父类,并调用方法_add_arithmetic_ops()和/或_add_comparison_ops()将运算符挂接到你的MyExtensionArray类中,如下所示:

from pandas.api.extensions import ExtensionArray, ExtensionScalarOpsMixin

class MyExtensionArray(ExtensionArray, ExtensionScalarOpsMixin):
    pass

MyExtensionArray._add_arithmetic_ops()
MyExtensionArray._add_comparison_ops() 

注意

由于pandas自动逐个调用每个元素上的底层运算符,这可能不如直接在ExtensionArray上实现相关运算符的版本性能好。

对于算术运算,这个实现将尝试用逐元素操作的结果重构一个新的ExtensionArray。是否成功取决于操作是否返回了对ExtensionArray有效的结果。如果无法重构ExtensionArray,则返回一个包含返回标量的 ndarray。

为了便于实现并与 pandas 和 NumPy ndarrays 之间的操作保持一致,我们建议不要在二进制操作中处理 Series 和 Indexes。相反,你应该检测这些情况并返回NotImplemented。当 pandas 遇到像op(Series, ExtensionArray)这样的操作时,pandas 将

  1. Series中解包数组(Series.array

  2. 调用result = op(values, ExtensionArray)

  3. Series中重新封装结果 ### NumPy 通用函数

Series实现了__array_ufunc__。作为实现的一部分,pandas 从Series中解包ExtensionArray,应用 ufunc,并在必要时重新封装它。

如果适用,我们强烈建议你在你的扩展数组中实现__array_ufunc__,以避免强制转换为 ndarray。请参阅NumPy 文档以获取示例。

作为您实现的一部分,当检测到 pandas 容器(SeriesDataFrameIndex)时,我们要求您将其推迟到 pandas。如果其中任何一个存在,则应返回NotImplemented。pandas 将负责从容器中解包数组并重新调用 ufunc 以获取未包装的输入。 ### 测试扩展数组

我们提供了一个测试套件,用于确保您的扩展数组满足预期的行为。要使用测试套件,必须提供几个 pytest fixtures 并继承基础测试类。所需的固定装置在pandas-dev/pandas中找到。

要使用测试,需要对其进行子类化:

from pandas.tests.extension import base

class TestConstructors(base.BaseConstructorsTests):
    pass 

参见pandas-dev/pandas以获取所有可用测试的列表。 ### 与 Apache Arrow 的兼容性

通过实现两种方法,ExtensionArray可以支持与pyarrow数组的转换(因此支持例如序列化为 Parquet 文件格式):ExtensionArray.__arrow_array__ExtensionDtype.__from_arrow__

ExtensionArray.__arrow_array__确保pyarrow知道如何将特定扩展数组转换为pyarrow.Array(即使作为 pandas DataFrame 中的列包含):

class MyExtensionArray(ExtensionArray):
    ...

    def __arrow_array__(self, type=None):
        # convert the underlying array values to a pyarrow Array
        import pyarrow

        return pyarrow.array(..., type=type) 

ExtensionDtype.__from_arrow__方法然后控制了从 pyarrow 回到 pandas ExtensionArray 的转换。此方法仅接收一个 pyarrow ArrayChunkedArray作为参数,并预期返回此 dtype 和传递值的适当 pandas ExtensionArray

class ExtensionDtype:
    ...

    def __from_arrow__(self, array: pyarrow.Array/ChunkedArray) -> ExtensionArray:
        ... 

查看更多内容请参阅Arrow 文档

这些方法已经为包含在 pandas 中的可空整数和字符串扩展 dtype 实现,并确保与 pyarrow 和 Parquet 文件格式的往返。

ExtensionDtype

pandas.api.extensions.ExtensionDtype类似于numpy.dtype对象。它描述了数据类型。实现者需要负责一些独特的项目,如名称。

特别重要的一项是type属性。这应该是您的数据的标量类型的类。例如,如果您正在为 IP 地址数据编写扩展数组,则可能是ipaddress.IPv4Address

请参阅扩展 dtype 源以获取接口定义。

pandas.api.extensions.ExtensionDtype 可以注册到 pandas 中,以允许通过字符串 dtype 名称进行创建。这允许使用注册的字符串名称实例化 Series.astype(),例如 'category'CategoricalDtype 的注册字符串访问器。

查看 扩展 dtype dtypes 以获取有关如何注册 dtypes 的更多信息。

ExtensionArray

该类提供了所有类似数组的功能。ExtensionArrays 限制为 1 维。通过 dtype 属性,ExtensionArray 与 ExtensionDtype 相关联。

pandas 对通过其 __new____init__ 创建扩展数组没有任何限制,并且不对数据存储方式施加任何限制。我们要求您的数组可以转换为 NumPy 数组,即使这可能相对昂贵(比如对于 Categorical)。

它们可以由零个、一个或多个 NumPy 数组支持。例如,pandas.Categorical 是由两个数组支持的扩展数组,一个用于代码,一个用于类别。一个 IPv6 地址数组可以由一个具有两个字段的 NumPy 结构化数组支持,一个用于低 64 位,一个用于高 64 位。或者它们可以由其他存储类型支持,比如 Python 列表。

查看 扩展数组源代码 以获取接口定义。文档字符串和注释包含了正确实现接口的指导。

ExtensionArray 运算符支持

默认情况下,对于类 ExtensionArray 没有定义运算符。提供您的 ExtensionArray 运算符支持有两种方法:

  1. 在您的 ExtensionArray 子类上定义每个运算符。

  2. 使用 pandas 中依赖于底层元素(标量)已定义的运算符的运算符实现。

注意

无论采用哪种方法,如果希望在与 NumPy 数组进行二元操作时调用您的实现,可能需要设置 __array_priority__

对于第一种方法,您可以定义所选运算符,例如 __add____le__ 等,您希望您的 ExtensionArray 子类支持。

第二种方法假定ExtensionArray的基础元素(即标量类型)已经定义了各个运算符。换句话说,如果你的ExtensionArray名为MyExtensionArray,并且实现为每个元素都是MyExtensionElement类的实例,那么如果为MyExtensionElement定义了运算符,第二种方法将自动为MyExtensionArray定义运算符。

混合类ExtensionScalarOpsMixin支持这种第二种方法。例如,如果开发一个ExtensionArray子类,比如MyExtensionArray,只需将ExtensionScalarOpsMixin包含为MyExtensionArray的父类,并调用方法_add_arithmetic_ops()和/或_add_comparison_ops()将运算符挂接到你的MyExtensionArray类中,如下所示:

from pandas.api.extensions import ExtensionArray, ExtensionScalarOpsMixin

class MyExtensionArray(ExtensionArray, ExtensionScalarOpsMixin):
    pass

MyExtensionArray._add_arithmetic_ops()
MyExtensionArray._add_comparison_ops() 

注意

由于pandas自动逐个调用每个元素上的基础运算符,这可能不如直接在ExtensionArray上实现相关运算符的版本性能好。

对于算术运算,此实现将尝试使用元素级操作的结果重构一个新的ExtensionArray。是否成功取决于操作是否返回适用于ExtensionArray的结果。如果无法重建ExtensionArray,则返回包含返回标量的 ndarray。

为了方便实现和与 pandas 和 NumPy ndarray 之间的操作一致,我们建议在你的二进制运算中处理 Series 和 Indexes。相反,你应该检测这些情况并返回NotImplemented。当 pandas 遇到类似op(Series, ExtensionArray)的操作时,pandas 将

  1. SeriesSeries.array)中解封数组。

  2. 调用result = op(values, ExtensionArray)

  3. Series中重新装箱结果。

NumPy 通用函数

Series实现了__array_ufunc__。作为实现的一部分,pandas 从Series中解封ExtensionArray,应用 ufunc,并在必要时重新装箱。

如果适用,我们强烈建议你在扩展数组中实现__array_ufunc__以避免强制转换为 ndarray。参见NumPy 文档中的示例。

作为你实现的一部分,我们要求当在inputs中检测到一个 pandas 容器(SeriesDataFrameIndex)时,你应该委托给 pandas。如果其中任何一个存在,你应该返回NotImplemented。pandas 将负责从容器中解封数组并重新调用 ufunc。

测试扩展数组

我们为确保您的扩展数组满足预期行为提供了一个测试套件。要使用测试套件,您必须提供几个 pytest 固定装置并继承基本测试类。所需的固定装置可以在pandas-dev/pandas中找到。

要使用测试,必须将其子类化:

from pandas.tests.extension import base

class TestConstructors(base.BaseConstructorsTests):
    pass 

查看所有可用测试的列表,请参阅pandas-dev/pandas

与 Apache Arrow 的兼容性

一个ExtensionArray可以通过实现两种方法来支持与pyarrow数组的转换(从而支持例如序列化为 Parquet 文件格式):ExtensionArray.__arrow_array__ExtensionDtype.__from_arrow__

ExtensionArray.__arrow_array__ 确保 pyarrow 知道如何将特定的扩展数组转换为 pyarrow.Array(当作为 pandas DataFrame 中的列包含时也是如此):

class MyExtensionArray(ExtensionArray):
    ...

    def __arrow_array__(self, type=None):
        # convert the underlying array values to a pyarrow Array
        import pyarrow

        return pyarrow.array(..., type=type) 

ExtensionDtype.__from_arrow__ 方法然后控制从 pyarrow 到 pandas ExtensionArray 的转换。此方法接收一个 pyarrow ArrayChunkedArray作为唯一参数,并期望返回适用于此 dtype 和传递的值的适当 pandas ExtensionArray

class ExtensionDtype:
    ...

    def __from_arrow__(self, array: pyarrow.Array/ChunkedArray) -> ExtensionArray:
        ... 

Arrow 文档中了解更多信息。

这些方法已经针对 pandas 中包含的可空整数和字符串扩展 dtype 进行了实现,并确保与 pyarrow 和 Parquet 文件格式的往返。

子类化 pandas 数据结构

警告

在考虑子类化pandas数据结构之前,有一些更简单的替代方法。

  1. 使用 pipe 进行可扩展的方法链

  2. 使用组合。参见此处

  3. 通过注册访问器进行扩展

  4. 通过扩展类型进行扩展

本节描述如何对pandas数据结构进行子类化以满足更具体的需求。有两点需要注意:

  1. 覆盖构造函数属性。

  2. 定义原始属性

注意

你可以在geopandas项目中找到一个很好的例子。

覆盖构造函数属性

每个数据结构都有几个构造函数属性,用于返回作为操作结果的新数据结构。通过覆盖这些属性,您可以通过pandas数据操作保留子类。

有 3 个可能需要在子类上定义的构造函数属性:

  • DataFrame/Series._constructor: 用于当操作结果与原始数据具有相同维度时。

  • DataFrame._constructor_sliced: 用于当DataFrame(子类)操作的结果应该是一个Series(子类)时。

  • Series._constructor_expanddim: 用于当Series(子类)操作的结果应该是一个DataFrame(子类)时,例如Series.to_frame()

以下示例展示了如何定义SubclassedSeriesSubclassedDataFrame,覆盖构造函数属性。

class SubclassedSeries(pd.Series):
    @property
    def _constructor(self):
        return SubclassedSeries

    @property
    def _constructor_expanddim(self):
        return SubclassedDataFrame

class SubclassedDataFrame(pd.DataFrame):
    @property
    def _constructor(self):
        return SubclassedDataFrame

    @property
    def _constructor_sliced(self):
        return SubclassedSeries 
>>> s = SubclassedSeries([1, 2, 3])
>>> type(s)
<class '__main__.SubclassedSeries'>

>>> to_framed = s.to_frame()
>>> type(to_framed)
<class '__main__.SubclassedDataFrame'>

>>> df = SubclassedDataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
>>> df
 A  B  C
0  1  4  7
1  2  5  8
2  3  6  9

>>> type(df)
<class '__main__.SubclassedDataFrame'>

>>> sliced1 = df[["A", "B"]]
>>> sliced1
 A  B
0  1  4
1  2  5
2  3  6

>>> type(sliced1)
<class '__main__.SubclassedDataFrame'>

>>> sliced2 = df["A"]
>>> sliced2
0    1
1    2
2    3
Name: A, dtype: int64

>>> type(sliced2)
<class '__main__.SubclassedSeries'> 

定义原始属性

要让原始数据结构具有额外属性,你应该让pandas知道添加了哪些属性。pandas将未知属性映射到数据名称,覆盖__getattribute__。定义原始属性可以通过以下两种方式之一完成:

  1. 为临时属性定义_internal_names_internal_names_set,这些属性不会传递给操作结果。

  2. 为将传递给操作结果的普通属性定义_metadata

以下是一个示例,定义了两个原始属性,“internal_cache”作为临时属性,“added_property”作为普通属性

class SubclassedDataFrame2(pd.DataFrame):

    # temporary properties
    _internal_names = pd.DataFrame._internal_names + ["internal_cache"]
    _internal_names_set = set(_internal_names)

    # normal properties
    _metadata = ["added_property"]

    @property
    def _constructor(self):
        return SubclassedDataFrame2 
>>> df = SubclassedDataFrame2({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
>>> df
 A  B  C
0  1  4  7
1  2  5  8
2  3  6  9

>>> df.internal_cache = "cached"
>>> df.added_property = "property"

>>> df.internal_cache
cached
>>> df.added_property
property

# properties defined in _internal_names is reset after manipulation
>>> df[["A", "B"]].internal_cache
AttributeError: 'SubclassedDataFrame2' object has no attribute 'internal_cache'

# properties defined in _metadata are retained
>>> df[["A", "B"]].added_property
property 

覆盖构造函数属性

每个数据结构都有几个构造函数属性,用于返回操作结果的新数据结构。通过覆盖这些属性,你可以通过pandas数据操作保留子类。

子类上可以定义 3 种可能的构造函数属性:

  • DataFrame/Series._constructor:当操作结果与原始数据具有相同维度时使用。

  • DataFrame._constructor_sliced:当DataFrame(子类)操作结果应为Series(子类)时使用。

  • Series._constructor_expanddim:当Series(子类)操作结果应为DataFrame(子类),例如Series.to_frame()时使用。

以下示例展示了如何定义SubclassedSeriesSubclassedDataFrame,覆盖构造函数属性。

class SubclassedSeries(pd.Series):
    @property
    def _constructor(self):
        return SubclassedSeries

    @property
    def _constructor_expanddim(self):
        return SubclassedDataFrame

class SubclassedDataFrame(pd.DataFrame):
    @property
    def _constructor(self):
        return SubclassedDataFrame

    @property
    def _constructor_sliced(self):
        return SubclassedSeries 
>>> s = SubclassedSeries([1, 2, 3])
>>> type(s)
<class '__main__.SubclassedSeries'>

>>> to_framed = s.to_frame()
>>> type(to_framed)
<class '__main__.SubclassedDataFrame'>

>>> df = SubclassedDataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
>>> df
 A  B  C
0  1  4  7
1  2  5  8
2  3  6  9

>>> type(df)
<class '__main__.SubclassedDataFrame'>

>>> sliced1 = df[["A", "B"]]
>>> sliced1
 A  B
0  1  4
1  2  5
2  3  6

>>> type(sliced1)
<class '__main__.SubclassedDataFrame'>

>>> sliced2 = df["A"]
>>> sliced2
0    1
1    2
2    3
Name: A, dtype: int64

>>> type(sliced2)
<class '__main__.SubclassedSeries'> 

定义原始属性

要让原始数据结构具有额外属性,你应该让pandas知道添加了哪些属性。pandas将未知属性映射到数据名称,覆盖__getattribute__。定义原始属性可以通过以下两种方式之一完成:

  1. 为临时属性定义_internal_names_internal_names_set,这些属性不会传递给操作结果。

  2. 为将传递给操作结果的普通属性定义_metadata

以下是一个示例,定义了两个原始属性,“internal_cache”作为临时属性,“added_property”作为普通属性

class SubclassedDataFrame2(pd.DataFrame):

    # temporary properties
    _internal_names = pd.DataFrame._internal_names + ["internal_cache"]
    _internal_names_set = set(_internal_names)

    # normal properties
    _metadata = ["added_property"]

    @property
    def _constructor(self):
        return SubclassedDataFrame2 
>>> df = SubclassedDataFrame2({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
>>> df
 A  B  C
0  1  4  7
1  2  5  8
2  3  6  9

>>> df.internal_cache = "cached"
>>> df.added_property = "property"

>>> df.internal_cache
cached
>>> df.added_property
property

# properties defined in _internal_names is reset after manipulation
>>> df[["A", "B"]].internal_cache
AttributeError: 'SubclassedDataFrame2' object has no attribute 'internal_cache'

# properties defined in _metadata are retained
>>> df[["A", "B"]].added_property
property 

绘图后端

pandas 可以通过第三方绘图后端进行扩展。主要思想是让用户选择一个基于 Matplotlib 提供的绘图后端之外的绘图后端。例如:

>>> pd.set_option("plotting.backend", "backend.module")
>>> pd.Series([1, 2, 3]).plot() 

这基本上等同于:

>>> import backend.module
>>> backend.module.plot(pd.Series([1, 2, 3])) 

然后,后端模块可以使用其他可视化工具(Bokeh、Altair 等)生成图表。

实现绘图后端的库应该使用入口点来使其后端可被 pandas 发现。关键是"pandas_plotting_backends"。例如,pandas 将默认的“matplotlib”后端注册如下。

# in setup.py
setup(  # noqa: F821
    ...,
    entry_points={
        "pandas_plotting_backends": [
            "matplotlib = pandas:plotting._matplotlib",
        ],
    },
) 

关于如何实现第三方绘图后端的更多信息,请参阅 pandas-dev/pandas

使用第三方类型进行算术运算

为了控制自定义类型与 pandas 类型之间的算术运算方式,请实现 __pandas_priority__。类似于 numpy 的 __array_priority__ 语义,DataFrameSeriesIndex 对象上的算术方法将委托给 other,如果它具有一个属性 __pandas_priority__,其值比较高。

默认情况下,pandas 对象尝试与其他对象进行操作,即使它们不是 pandas 所知的类型:

>>> pd.Series([1, 2]) + [10, 20]
0    11
1    22
dtype: int64 

在上面的示例中,如果 [10, 20] 是一个可以被理解为列表的自定义类型,pandas 对象仍然会以相同的方式与其进行操作。

在某些情况下,将操作委托给另一种类型是有用的。例如,考虑我实现了一个自定义列表对象,我希望将我的自定义列表与 pandas Series 相加的结果是我的列表的一个实例,而不是前面示例中看到的 Series。通过定义我的自定义列表的 __pandas_priority__ 属性,并将其设置为比我想要操作的 pandas 对象的优先级更高的值,现在可以实现这一点。

DataFrameSeriesIndex__pandas_priority__ 分别为 400030002000。基础的 ExtensionArray.__pandas_priority__1000

class CustomList(list):
    __pandas_priority__ = 5000

    def __radd__(self, other):
        # return `self` and not the addition for simplicity
        return self

custom = CustomList()
series = pd.Series([1, 2, 3])

# Series refuses to add custom, since it's an unknown type with higher priority
assert series.__add__(custom) is NotImplemented

# This will cause the custom class `__radd__` being used instead
assert series + custom is custom 

A", “B”]].internal_cache
AttributeError: ‘SubclassedDataFrame2’ object has no attribute ‘internal_cache’

properties defined in _metadata are retained

df[[“A”, “B”]].added_property
property


## 绘图后端

pandas 可以通过第三方绘图后端进行扩展。主要思想是让用户选择一个基于 Matplotlib 提供的绘图后端之外的绘图后端。例如:

```py
>>> pd.set_option("plotting.backend", "backend.module")
>>> pd.Series([1, 2, 3]).plot() 

这基本上等同于:

>>> import backend.module
>>> backend.module.plot(pd.Series([1, 2, 3])) 

然后,后端模块可以使用其他可视化工具(Bokeh、Altair 等)生成图表。

实现绘图后端的库应该使用入口点来使其后端可被 pandas 发现。关键是"pandas_plotting_backends"。例如,pandas 将默认的“matplotlib”后端注册如下。

# in setup.py
setup(  # noqa: F821
    ...,
    entry_points={
        "pandas_plotting_backends": [
            "matplotlib = pandas:plotting._matplotlib",
        ],
    },
) 

关于如何实现第三方绘图后端的更多信息,请参阅 pandas-dev/pandas

使用第三方类型进行算术运算

为了控制自定义类型与 pandas 类型之间的算术运算方式,请实现 __pandas_priority__。类似于 numpy 的 __array_priority__ 语义,DataFrameSeriesIndex 对象上的算术方法将委托给 other,如果它具有一个属性 __pandas_priority__,其值比较高。

默认情况下,pandas 对象尝试与其他对象进行操作,即使它们不是 pandas 所知的类型:

>>> pd.Series([1, 2]) + [10, 20]
0    11
1    22
dtype: int64 

在上面的示例中,如果 [10, 20] 是一个可以被理解为列表的自定义类型,pandas 对象仍然会以相同的方式与其进行操作。

在某些情况下,将操作委托给另一种类型是有用的。例如,考虑我实现了一个自定义列表对象,我希望将我的自定义列表与 pandas Series 相加的结果是我的列表的一个实例,而不是前面示例中看到的 Series。通过定义我的自定义列表的 __pandas_priority__ 属性,并将其设置为比我想要操作的 pandas 对象的优先级更高的值,现在可以实现这一点。

DataFrameSeriesIndex__pandas_priority__ 分别为 400030002000。基础的 ExtensionArray.__pandas_priority__1000

class CustomList(list):
    __pandas_priority__ = 5000

    def __radd__(self, other):
        # return `self` and not the addition for simplicity
        return self

custom = CustomList()
series = pd.Series([1, 2, 3])

# Series refuses to add custom, since it's an unknown type with higher priority
assert series.__add__(custom) is NotImplemented

# This will cause the custom class `__radd__` being used instead
assert series + custom is custom 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值