跳转到内容

质性编码的共享码本

设置谁可以添加、重命名、合并或停用代码,一次修改如何影响已编码的摘录,以及 Potato 在哪里记录代码定义和修改。

Potato 的码本是一个项目的编码者所使用的代码列表。Potato 把码本保存在项目数据库中,而不是 YAML 配置里。方案通过 codebook: true 启用码本,此后该方案的选项来自码本。编码者可以在一次会话进行到一半时添加代码,无需任何人编辑配置或重启服务器。代码可以挂在一个父代码之下,每个代码的条目都有类型化字段,其中包括定义、纳入与排除规则以及示例。Potato 会给每条保存的标注打上保存时生效的码本修订号。码本随 QDA 模式在 Potato 2.6.0 中加入,可用于标准标注、单人模式和 QDA 模式。

在质性研究中,码本列出一个团队使用的代码,并说明每个代码在什么情况下适用。MacQueen 等人(1998)认为,每个代码都采用同一固定结构的码本能给编码团队提供稳定的框架,并提高编码者间一致性。Potato 为每个代码提供同一套类型化字段,从简短定义到反例都有。码本开发指南介绍团队如何撰写和修订这些条目。

码本的存储与初始化

  • 存储。 代码保存在 <task_dir>/project.sqlite 中。备忘录和案例也使用同一个项目数据库。YAML 只保存初始标签。
  • 初始化。 服务器首次启动时,Potato 会把每个标记为 codebook: true 的方案的 labels 复制到空码本中。从那以后,数据库就是唯一的准据,服务器会忽略之后对 YAML 标签的修改。
  • 每个项目一个码本。 每个标记为 codebook: true 的方案都显示同一组代码。一个同时有 themes 方案和 quotes 方案的项目,两个方案都从同一个列表取代码。
  • 方案类型。 radio、multiselect、select 和 span 支持 codebook: true。
  • 修订号。 添加、重命名、改色、移动、删除、合并或拆分一个代码,都会让项目的码本修订号加一。Potato 会在之后保存的每条标注上记录该修订号。
  • 编码者在哪里查看码本。 标注页面右边缘的 Codebook 开关会打开一个侧边栏,列出每个代码及其简短定义。/codebook 页面显示完整码本,包括每个代码的所有字段。

配置

下面的配置以 extensible 模式为一个团队设置访谈编码。编码者可以在阅读过程中添加代码,只有裁定者可以重命名或删除代码。两个初始标签采用对象形式,对象形式的标签会为代码设定颜色和简短定义。

yaml
annotation_task_name: Clinic Access Interviews
task_dir: .
output_annotation_dir: annotation_output/
data_files:
- data/interviews.json
item_properties:
  id_key: id
  text_key: text
user_config:
  allow_all_users: true
 
codebook_mode: extensible
 
annotation_schemes:
- annotation_type: span
  name: codes
  description: Highlight a passage and apply a code
  codebook: true
  labels:
  - name: access barriers
    color: "#4682b4"
    description: Anything the speaker names as standing between them and care they wanted.
  - name: cost concerns
    color: "#b44682"
    description: Money, insurance, or the fear of a bill.
选项默认值说明
annotation_schemes[].codebookfalse让方案的选项来自项目码本。
annotation_schemes[].labels(无)在首次启动时为空码本提供初始代码。标签可以是字符串,也可以是带有 name、color 和 description(或 tooltip)的对象。description 会成为该代码的简短定义。
codebook_modeqda_mode.enabled 或 solo_mode.enabled 为 true 时为 open,否则为 fixed谁可以修改代码:fixed、extensible 或 open。两者都设置时优先于 codebook.mode。其他任何值都无法通过验证。
codebook.mode与 codebook_mode 相同codebook_mode 的嵌套写法。
codebook.enabled未设置开启或关闭侧边栏、/codebook 页面和码本 API。未设置时,只要有方案设置了 codebook: true,或处于 QDA 模式或单人模式下,码本就会开启。设置了 codebook_mode 的配置无论 codebook.enabled 怎么写,码本都是开启的。
codebook.distiller.include(或 include_types)short_def、definition、use_when、avoid_when、example、counter_example每个代码的哪些字段进入 Potato 添加到 ICL 标注提示词中的文本。
codebook.distiller.include_doc_sectionspreamble、general_instructions哪些码本级章节进入同一段提示词文本。
codebook.distiller.scopeper_codeper_code 添加每个代码的字段。whole_doc 只添加码本级章节。
codebook.distiller.procedureconcat提示词文本的组装方式。llm_summarize 和 select_icl_examples 可以通过验证,但在 2.13.1 中产生的输出与 concat 相同。
codebook.distiller.max_chars无上限把提示词文本截断到给定的字符数。
codebook_invivo_keyi在以码本为支撑的 span 方案中选中文本时,打开原位编码输入框的按键。只有第一个字符有效。

qda_mode.codebook.mode 能通过验证。Potato 2.13.1 在决定谁可以编辑时不会读取这个键。在 QDA 模式下,要改变默认的 open,请设置 codebook_mode。

谁可以修改代码

已登录的编码者能做什么取决于模式。在 extensible 和 open 模式下,裁定者可以做任何结构性修改。在 fixed 模式下,编码者和裁定者都不能添加、重命名、改色、移动或删除代码。管理员和裁定者仍然可以在侧边栏的 Curate 区域合并和拆分代码。

模式编码者可以典型用途
fixed只能应用代码信度检验轮次、众包工作、编码前已商定的码本
extensible添加代码有商定核心代码、又留有新增空间的团队编码
open添加、重命名、改色、移动和删除代码,以及编辑定义一位分析者,或共同开发码本的小团队

对于任何众包配置,Potato 都会把模式锁定为 fixed,并在配置要求更宽权限时记录一条警告。Potato 施加这一锁定,是为了防止招募来的标注者改动共享码本。锁定适用于 crowdsourcing.provider 取 expert 以外的任何值、顶层的 prolific: 或 mturk: 代码块,以及 login.type: prolific 或 mturk。无法识别的 provider 名称和空的 provider 值同样会锁定模式。

crowdsourcing.provider: expert 会保留所要求的模式,因为专家是按名字邀请的。只设置 login.type: url_direct 也会保留所要求的模式,并记录一条警告。任何拿到这类研究链接的人都可以注册,然后编辑开放的码本。

在 2.13.1 中,每种修改都有各自的途径:

修改在浏览器中通过 API谁可以
添加代码侧边栏的 "Add a code" 输入框,或原位编码键POST /api/codebookextensible 或 open
重命名、改色或移到某个父代码下2.13.1 中没有控件PATCH /api/codebook/<id>open,或 extensible 下的裁定者
删除代码2.13.1 中没有控件DELETE /api/codebook/<id>open,或 extensible 下的裁定者
把一个代码合并到另一个代码侧边栏的 Curate 区域POST /api/codebook/admin/merge管理员或裁定者,任何模式
按编码者拆分代码侧边栏的 Curate 区域POST /api/codebook/admin/split管理员或裁定者,任何模式
编辑定义或添加示例侧边栏中代码旁的铅笔图标,或 /codebookPUT /api/codebook/blocksopen,或 extensible 下的裁定者。其他人的编辑会进入队列,等待管理员或裁定者确认。

侧边栏的添加输入框创建的是顶层代码。要把代码放到某个父代码下,需通过 POST 或 PATCH 的 parent_id 字段。

从选中文本进行原位编码

在标记为 codebook: true 的 span 方案中,编码者选中一段文字并按下原位编码键。一个小输入框随即打开,名称取自所选文字(最多 60 个字符),点击 "Create & code" 会一步完成添加代码并用新代码高亮该段文字。编码者输入名称时,名称相近的已有代码会以标签形式出现。编码者选择某个标签,就会应用这个已有代码,而不会创建近似重复的代码,例如在 cost concerns 旁边再建一个 cost。

模型提议的修改

模型从不直接修改共享码本。智能体或单人模式中的模型通过 POST /api/codebook/proposals 并带上 actor_kind: "model" 来暂存一项修改。这项修改以一句话的形式在 Curate 区域等待处理,例如 "Merge «cost» into «cost concerns»"。管理员或裁定者确认或拒绝该提议。确认后的提议与人工编辑走同一条路径,并在修改日志中标记为来自模型。

代码的嵌套

带有 parent_id 的代码挂在该父代码之下,父代码也可以挂在另一个父代码之下。如果一次移动会把代码放到它自己的某个后代之下,这次移动会报错失败。在同一父代码下添加第二个同名代码也会以同样的方式失败。删除一个父代码会删除其下的所有代码。

侧边栏和 /codebook 以树形显示代码。标注表单则按树的顺序把代码显示为一个扁平列表,父代码在子代码之前。表单选项和已保存的标注都只保存代码的名称。因此,两个父代码下的同名代码会在表单上出现两次,之后也无法区分这两个代码。请让代码名称在整个码本中保持唯一。

在 2.13.1 中,codebook CSV 导出和 .qdpx 导出都会把以码本为支撑的方案的代码写成扁平列表。对于这些代码,CSV 的 parent 和 color 列为空。如果其他程序需要代码层级,就得在那个程序里重建,或把层级记录在代码名称中。

代码修改对已保存编码结果的影响

Potato 把每条编码结果以代码名称作为标签,保存在编码者的 user_state.json 中。合并或拆分从不删除数据库中的行。Potato 对码本的每次修改只作用于码本本身,从不改写已保存的标注:

  • 添加代码不会改变已保存的编码结果。
  • 重命名代码会从此改变表单。以旧名称保存的编码结果保留旧名称,导出和一致性统计也按旧名称计算这些编码结果。请在一轮编码开始前重命名,或重新编码受影响的摘录。
  • 删除代码会从表单中移除该代码及其下的所有代码。已保存的编码结果保留该名称,修改日志也保留被删除代码的名称。
  • 合并会归档源代码。被归档的代码从表单中消失,但该代码的行和历史仍留在数据库中。用源代码保存的编码结果保留源代码的名称。
  • 按编码者拆分指定一名编码者和一个新的或已有的目标代码。拆分用于两名编码者用同一个代码名称表示不同含义的情况,设计上只移动被指定编码者的使用记录。在 2.13.1 中,标注表单不会记录拆分所移动的按编码者链接。因此,只要源代码没有子代码,拆分就会创建目标代码,并对所有编码者归档源代码。已保存的编码结果保留每位编码者当时选择的名称。

Potato 通过两种方式告知编码者码本的变化。第一,在新代码加入之前保存的摘录上会出现一条横幅,列出新加入的代码。第二,侧边栏的 Review 列表收录该编码者在最近一次任意类型修改之前保存的每条摘录。列表中的每条摘录都有一个 Go 按钮。这两种提示都可以关闭,Potato 从不强制重新编码。管理员或裁定者可以在 GET /api/codebook/admin/stale 查看整个项目的同一列表。

定义的编辑有单独的计数器。对代码的简短定义、定义、"Use when" 或 "Avoid when" 字段的编辑会提升语义修订号,Potato 会在之后保存的每条标注上记录该语义修订号。分析者据此可以区分定义修改前后做出的编码结果。在 /codebook 编辑器中勾选 Minor edit (no re-review),可以保存措辞而不提升语义修订号。在 2.13.1 中,Review 列表只跟踪结构性修改。

在信度检验轮次中请设置 codebook_mode: fixed,因为 Potato 基于已保存的代码名称计算一致性,轮次中途重命名的代码会变成两个代码。标注者间一致性介绍各种系数。/admin/iaa 上的一致性时间线会把每次码本修订标注在一致性随时间的变化上。一致性漂移说明如何解读这条时间线。

每个代码条目中的类型化字段

每个代码的条目都是一组类型化字段。/codebook 是整页编辑器,为每个代码提供版本历史、版本间差异对比,以及把旧版本另存为新版本的恢复功能。"Paste markdown" 按钮会按标题把粘贴进来的码本文字转换为字段,对解析器无法识别的章节,会请用户选择类型。两个人同时保存同一个代码时不会互相覆盖。第二次保存会失败,并返回当前文本供对照协调。

字段编辑器中的标题语义字段
short_defShort definition是
definitionDefinition是
use_whenUse when是
avoid_whenAvoid when是
exampleExamples否
counter_exampleCounter-examples否
rationaleRationale否
backgroundBackground否
downstream_usageDownstream usage否
keywordsKeywords否
notesNotes否
custom用户自定义的标题否

码本整体在代码之外还有四个章节,标题分别是 Preamble、General instructions、Background 和 How codes are used downstream。侧边栏显示每个代码的简短定义,最多显示两行。如果某个代码没有简短定义,侧边栏会改为显示完整定义。

提炼器(distiller)把所选字段转换为 Potato 添加到 ICL 标注提示词中的文本,并在每次码本修改后重新生成该文本。GET /api/codebook/distilled 返回模型当前收到的文本。在上面的配置中添加一个 codebook.distiller 代码块来选择字段:

yaml
codebook:
  distiller:
    include: [definition, use_when, avoid_when, example]
    procedure: concat

用 CLI 初始化和迁移

potato codebook 会为每个 codebook: true 方案中码本尚未包含的每个标签添加一个代码,已有代码保持不变:

bash
potato codebook config.yaml

该命令会打印新建了多少个代码、多少个已经存在。由 CLI 创建的代码在每台机器上都得到相同的 ID,因为 CLI 根据项目名称、父代码和代码名称计算每个 ID。如果需要在项目的多个副本之间保持 ID 一致,请在服务器首次启动之前运行 CLI,因为服务器初始化的代码使用随机 ID。CLI 只复制名称。对象形式标签中的颜色和定义只能通过服务器首次启动时的初始化进入码本。

由于服务器只为空码本做初始化,要把新的 YAML 标签加入已有代码的码本,就得使用 CLI。若只想预览而不写入,请直接运行该模块。在 2.13.1 中,potato codebook --dry-run 会丢掉这个参数并直接写入代码。

bash
python -m potato.codebook_cli config.yaml --dry-run

API

所有路由都位于 /api/codebook 之下,并需要已登录的会话。管理路由接受管理员或裁定者。

方法路径用途
GET/api/codebook树、扁平标签列表、修订号、使用码本的方案,以及调用者的 can_add 和 can_edit
GET/api/codebook/version结构修订号和内容修订号,用于低开销轮询
GET/api/codebook/similar?name=名称与 name 相近的已有代码
GET/api/codebook/provenance?instance_id=调用者对某个条目的编码是否早于当前修订号,以及此后新增的代码
GET/api/codebook/stale调用者的 Review 列表
POST/api/codebook添加代码(name,可选 color 和 parent_id)
PATCH/api/codebook/<id>重命名(name)、改色(color)或移动(parent_id)
DELETE/api/codebook/<id>删除代码及其下的所有代码
POST/api/codebook/proposals把修改加入队列(op、payload、actor_kind)
GET/api/codebook/admin/stale所有编码者的 Review 列表(管理)
POST/api/codebook/admin/merge把 src_id 合并到 dst_id(管理)
POST/api/codebook/admin/split把 annotator 的链接从 src_id 移到 target_id 或名为 new_name 的新代码(管理)
GET/api/codebook/admin/changes修改日志(管理)
GET/api/codebook/admin/proposals待处理的提议(管理)
POST/api/codebook/admin/proposals/<id>/confirm执行一项提议(管理)
POST/api/codebook/admin/proposals/<id>/reject丢弃一项提议(管理)

定义编辑器使用 GET /api/codebook/document、GET 和 PUT /api/codebook/blocks、POST /api/codebook/parse、GET /api/codebook/history、GET /api/codebook/history/<id>、POST /api/codebook/restore 以及 GET /api/codebook/distilled。在 QDA 模式下,/qda/codebook 返回码本的只读视图。

码本导出与修改历史

codebook 导出会写出 codebook.csv,每个码本方案中的每个代码占一行,列为 schema、annotation_type、code、parent、description、color 和 n_uses。该导出读取码本数据库。编码过程中添加的代码会被包含,被合并的代码则被排除。description 保存代码的简短定义,如果代码没有简短定义,则保存完整定义。n_uses 按名称统计已保存的编码结果。以码本中已不存在的名称保存的编码结果不计入任何一行。

bash
python -m potato.export -c config.yaml --format codebook -o ./export/

quotation_report 导出列出每段已编码的文字及其代码。qdpx 导出把代码和编码结果交给另一个 QDA 程序。QDA 模式介绍这两种导出,导出格式列出各列。

历史记录保存在 project.sqlite 中。codebook_change 表记录每一次重命名、改色、移动、删除、合并、拆分,以及被确认或拒绝的提议,包括旧值和新值、操作者、操作者是人还是模型,以及修订号。在 2.13.1 中,通过 PATCH 或 DELETE 进行的重命名、改色、移动或删除,记录的操作者是 system;新增代码不出现在日志中,而是带着创建者的用户名出现在 codes 表中。GET /api/codebook/admin/changes 以 JSON 形式返回修改日志。

codebook_snapshot 保存每个定义的每一个已保存版本,codebook_revision_history 记录每次修订发生的时间。项目快照和 potato deploy pull 生成的归档都包含 project.sqlite。这两种打包文件都可以作为码本的审计轨迹,写进方法部分。

在 Potato 代码仓库中运行自带的示例:

bash
python potato/flask_server.py start examples/advanced/codebook-example/config.yaml -p 8000

该示例用一个包含四个主题的开放式多选码本,对五条关于诊所就医的患者评论进行编码。examples/advanced/codebook-document-example/ 增加了一个提炼器代码块。在该示例中打开 /codebook 即可看到定义编辑器。

延伸阅读

参考文献

MacQueen, K. M., McLellan, E., Kay, K., & Milstein, B. (1998). Codebook Development for Team-Based Qualitative Analysis. CAM Journal, 10(2), 31-36. https://doi.org/10.1177/1525822X980100020301

实现细节请参阅源文档。