Кейсы и атрибуты кейсов в качественном кодировании
Группируйте фрагменты интервью по участникам, добавляйте атрибуты вроде площадки или возрастной группы и считайте коды по кейсам или по атрибутам в Potato.
Кейс в Potato объединяет объекты, которые относятся к одной единице анализа, например к одному участнику интервью или к документам одной организации. Potato строит кейсы при запуске сервера по полю в ваших данных, хранит на каждом кейсе атрибуты вроде площадки исследования или возрастной группы и позволяет администратору считать коды по кейсам или по атрибутам кейсов. QDA Mode включает кейсы по умолчанию, а в любом другом исследовании кейсы включаются одним блоком конфигурации. Кейсы появились в Potato 2.6.0, в том же релизе, что и QDA Mode.
Единица анализа — это объект, о котором исследование делает выводы. В интервью-исследовании единицей анализа обычно служит участник, а Potato кодирует фрагменты. Каждый фрагмент — это один объект в файле данных. Кейс связывает эти два уровня. С кейсами код, применённый к одному фрагменту, засчитывается участнику, который произнёс этот фрагмент, а площадка или возрастная группа участника относится к каждому фрагменту из его интервью.
Исследователи, пришедшие из NVivo или ATLAS.ti, знают эту идею под другими названиями. Справка NVivo говорит, что классификации кейсов «describe the type of case» («описывают тип кейса») и что у каждой классификации есть атрибуты, которые вы решили записывать. Среди примеров на странице справки есть возраст и местоположение. Руководство ATLAS.ti описывает группы документов как «quasi dichotomous variables» («квазидихотомические переменные») (например, группа gender::female). ATLAS.ti сравнивает коды по документам и группам документов в таблице «код — документ» (Code-Document Table). Кейс в Potato ближе всего к кейсу в NVivo. В Potato нет типов классификаций, и все кейсы проекта используют один плоский набор атрибутов. Кросс-таблица кодов, описанная ниже, играет роль таблицы «код — группа». Каждая ячейка кросс-таблицы считает фрагменты.
Определение кейсов по полям объектов
Potato читает кейсы из самих объектов. Каждый объект указывает свой кейс в одном из полей, и объекты с одинаковым значением этого поля образуют один кейс. Каждая строка файла data/excerpts.jsonl ниже — это один JSON-объект, а сам файл содержит шесть фрагментов из трёх интервью:
{"id": "p01-01", "text": "I stopped going because the bus only runs twice a day.", "participant_id": "P01", "site": "rural", "age_group": "18-34"}
{"id": "p01-02", "text": "The nurse remembered my name, and that mattered more than the wait.", "participant_id": "P01"}
{"id": "p02-01", "text": "My copay went up and I started skipping refills.", "participant_id": "P02", "site": "urban", "age_group": "55+"}
{"id": "p02-02", "text": "I never know which clinic is open on Saturdays.", "participant_id": "P02"}
{"id": "p03-01", "text": "The bus takes an hour each way, so I go only when it is urgent.", "participant_id": "P03", "site": "rural", "age_group": "35-54"}
{"id": "p03-02", "text": "The doctor explained everything twice without rushing me.", "participant_id": "P03"}При запуске сервера Potato читает у каждого объекта поле, указанное в cases.key, и относит объект к кейсу с таким именем. Potato создаёт кейс, когда имя встречается впервые. Без cases.key Potato проверяет case_id, затем participant_id, затем respondent_id и берёт первое из этих полей с непустым значением. Эту проверку Potato выполняет для каждого объекта отдельно. Поэтому файл, где в одних строках есть case_id, а в других participant_id, даёт кейсы, названные по обоим полям. Настройка cases.key предотвращает такое смешение. Для файла выше лог сервера сообщает Cases auto-detect: 3 case(s), 6 instance(s) assigned.
Potato читает только поля верхнего уровня и никогда не читает поле, вложенное под другой ключ. Объект без значения в поле кейса не принадлежит ни одному кейсу, а объект принадлежит не более чем одному кейсу. Potato заново запускает определение кейсов при каждом старте и только добавляет или обновляет записи. Если вы перезапустите Potato после добавления интервью, Potato поместит новые фрагменты в соответствующие кейсы и сохранит прежние кейсы. Potato никогда не удаляет кейс, и участник, которого вы убрали из данных, по-прежнему остаётся в списке кейсов.
Potato хранит кейсы, атрибуты кейсов и принадлежность к кейсам в <task_dir>/project.sqlite. В той же базе данных проекта хранятся кодбук и заметки. Кейсы записываются под значением annotation_task_name исследования. Если вы переименуете задачу, Potato при следующем запуске построит новый набор кейсов и оставит старый набор в файле.
Настройка
Конфигурация ниже включает QDA Mode с одной схемой span для кодов, группирует фрагменты по participant_id и копирует site и age_group на каждый кейс:
annotation_task_name: Clinic Access Interviews
task_dir: .
output_annotation_dir: annotation_output/
data_files:
- data/excerpts.jsonl
item_properties:
id_key: id
text_key: text
user_config:
allow_all_users: true
qda_mode:
enabled: true
cases:
key: participant_id
attributes: [site, age_group]
annotation_schemes:
- annotation_type: span
name: codes
description: Highlight a passage and apply a code
codebook: true
labels: [transport, cost, scheduling, provider trust]QDA Mode включает кейсы, и блок cases здесь только задаёт ключ и атрибуты. Для исследования вне QDA Mode добавьте в блок enabled: true. Все параметры, которые читает Potato, перечислены ниже:
| Параметр | По умолчанию | Описание |
|---|---|---|
cases.enabled | не задан (следует qda_mode.enabled) | true включает кейсы в любом исследовании. false выключает кейсы даже в QDA Mode. |
cases.key | не задан (проверяются case_id, participant_id, затем respondent_id) | Поле объекта, значение которого задаёт имя кейса. |
cases.auto_detect | true | Строит кейсы по данным при запуске сервера. При false Potato не строит новых кейсов, а привязки из прежних запусков не меняются. |
cases.attributes | [] | Поля объектов, которые копируются на каждый кейс как атрибуты кейса. |
Potato проверяет блок cases по закрытому списку из этих четырёх ключей. Если в конфигурации есть ключ с опечаткой, например atributes, python -m potato.validate_cli config.yaml сообщает об этом ключе и предлагает attributes.
Атрибуты кейсов
Атрибут кейса описывает участника, а не отдельный фрагмент, например площадку исследования или возрастную группу. При запуске Potato копирует каждое поле из списка cases.attributes с каждого объекта на кейс этого объекта. Поэтому поле уровня участника достаточно указать в одном фрагменте на участника. В данных выше site и age_group есть только в первом фрагменте каждого интервью. Данные, выгруженные из электронной таблицы, где поле есть в каждой строке, работают так же.
Когда фрагменты одного кейса расходятся, значение кейса задаёт фрагмент, прочитанный последним в порядке файла данных. Поле со значением null пропускается, а пустая строка считается значением и заменяет прежнее значение. Лучше совсем не указывать поле в строке, чем писать "". Potato хранит все значения атрибутов как текст, поэтому 34 и "34" оказываются одним и тем же значением.
Хотя страница разметки не показывает атрибуты кейсов, любой вошедший разметчик может прочитать все кейсы и атрибуты через /api/cases. Экспериментальное условие, сохранённое как атрибут кейса, отделено одним запросом от кодировщика, который не должен знать условие. В таком дизайне не включайте условие в файл данных, который загружает Potato, и присоедините условие к экспорту потом.
Подсчёт кодов по атрибутам кейсов
Для каждого кода и каждого значения одного атрибута кросс-таблица кодов считает фрагменты, которым присвоен этот код. Страница Админ-панель объясняет, откуда берётся ключ администратора. Администратор запрашивает таблицу у /admin/api/code_crosstab и передаёт ключ администратора в заголовке X-API-Key:
curl -H "X-API-Key: $KEY" "http://localhost:8000/admin/api/code_crosstab?attribute=site"После того как два кодировщика поработали с данными примера, ответ выглядит так:
{
"attribute_key": "site",
"codes": ["codes::cost", "codes::provider trust", "codes::transport"],
"values": ["rural", "urban"],
"cells": [
{"code": "codes::cost", "value": "urban", "count": 1},
{"code": "codes::provider trust", "value": "rural", "count": 2},
{"code": "codes::transport", "value": "rural", "count": 1}
],
"n_instances": 6,
"schema_filter": null
}Potato заполняет ячейки по пяти правилам:
- Единица подсчёта — фрагмент. Каждая ячейка считает фрагменты. Код засчитывается фрагменту один раз, если оба кодировщика применили код к этому фрагменту или если один кодировщик выделил код в этом фрагменте дважды. В примере оба кодировщика поставили
transportна фрагментp01-01, и ячейкаtransport/ruralравна 1. - Значение из кейса. Potato сначала берёт атрибут из фрагмента, а если у фрагмента нет значения, берёт значение из кейса фрагмента. У фрагмента
p01-02нет поляsite, и Potato относит фрагмент кruralпо кейсу P01. - Что считается кодом. Код называется
scheme::label. Выбранные метки и выделенные фрагменты текста засчитываются. Число со слайдера и свободный текст не засчитываются. - Знаменатель.
n_instances— это число фрагментов со значением атрибута, закодированных или нет. Фрагменты без значения ни во фрагменте, ни в кейсе в таблицу не попадают. - Одна схема. Если добавить к запросу
&schema=codes, таблица считает только коды одной схемы аннотации.
Чтобы посчитать коды по кейсам, передайте в качестве атрибута сам ключ кейса (например, attribute=participant_id). Тогда у таблицы будет по одному столбцу на участника. Поскольку поле ключа кейса есть в каждом фрагменте кейса, таблице по кейсам не нужно брать значения из кейса.
Кросс-таблица объединяет всех кодировщиков и не фильтрует ни по кодировщику, ни по кейсу. Один запрос охватывает один атрибут, а ответ приходит только в JSON. В админ-панели Potato 2.13.1 нет представления кросс-таблицы. Для подсчёта по кодировщикам используйте столбец coder отчёта с цитатами, описанного в разделе об экспорте ниже.
Чтение кейсов через API
Два эндпоинта только для чтения возвращают кейсы любой вошедшей сессии без ключа администратора:
| Метод | Путь | Возвращает |
|---|---|---|
| GET | /api/cases | Все кейсы исследования, отсортированные по имени, с атрибутами |
| GET | /api/cases/instance/<id> | Кейс одного объекта с атрибутами или {"case": null} |
Эндпоинты отвечают 401 на запрос без сессии и 503, когда кейсы выключены. Запись кейса содержит сгенерированный id, имя name из данных, attributes, created_by (cases-autodetect для найденных автоматически кейсов) и метки времени Unix в created_at и updated_at.
Экспорт кейсов
В 2.13.1 ни один экспортёр Potato не записывает кейсы. Экспорт REFI-QDA .qdpx содержит источники, коды, кодировки и кодировщиков, а кейсы и атрибуты кейсов не включает. Проект, открытый из этого файла в NVivo или ATLAS.ti, приходит без кейсов и классификаций. Potato выводит предупреждение об этом пропуске, только когда в конфигурации явно задано cases.enabled: true. Исследование, которое полагается на значение по умолчанию в QDA Mode, предупреждения не получает.
Данные кейсов лежат в project.sqlite. Два запроса через консольную утилиту sqlite3 записывают принадлежность к кейсам и атрибуты кейсов в CSV:
sqlite3 -header -csv project.sqlite "SELECT d.instance_id, c.name AS case_name FROM case_documents d JOIN cases c ON c.id = d.case_id ORDER BY c.name;" > cases.csv
sqlite3 -header -csv project.sqlite "SELECT c.name AS case_name, a.key AS attribute, a.value FROM cases c JOIN case_attributes a ON a.case_id = c.id ORDER BY c.name, a.key;" > case_attributes.csvВ файле cases.csv одна строка на фрагмент. Столбец instance_id в cases.csv соответствует столбцу instance_id отчёта с цитатами. Запишите отчёт с цитатами этой командой:
python -m potato.export -c config.yaml --format quotation_report -o export/Команда записывает в export/quotations.csv по одной строке на каждый закодированный отрывок. Столбцы содержат код, цитируемый текст, смещения символов, объект и кодировщика. Соедините отчёт с цитатами с cases.csv по instance_id, чтобы каждая закодированная цитата получила своего участника, а затем выполните соединение по case_name, чтобы добавить атрибуты.
Ограничения
- Нет ручного назначения. Кейсы появляются только в результате определения при запуске. Ни одна страница и ни один вызов API не переносят объект в другой кейс и не меняют атрибут. Чтобы изменить принадлежность к кейсу или атрибут, отредактируйте файл данных и перезапустите Potato.
- Один уровень. Кейс не может содержать другие кейсы, и объект принадлежит одному кейсу. Фрагмент фокус-группы может принадлежать группе или одному говорящему, но не обоим сразу.
- Коды привязаны к фрагментам. В QDA Mode нельзя закодировать кейс целиком.
- Согласованность по объектам. Согласованность между кодировщиками вычисляется по объектам и не группируется по кейсам.
Пример кейсов из основного репозитория
Пример запускается из исходного кода Potato. У каждого из шести фрагментов примера есть participant_id и condition, а конфигурация копирует condition на каждый кейс:
python potato/flask_server.py start examples/advanced/cases-example/config.yaml -p 8000Войдите, закодируйте несколько фрагментов и запросите /admin/api/code_crosstab?attribute=condition с ключом администратора, чтобы увидеть коды по условиям.
Что почитать дальше
- QDA Mode — переключатель, который включает кейсы вместе с кодбуком и заметками
- Кодбук — общий набор кодов, которые считает кросс-таблица
- Кодирование транскриптов интервью — путь от записей до закодированных фрагментов с одним кейсом на участника
- Админ-панель — ключ администратора и другие эндпоинты администратора
- Форматы экспорта — отчёт с цитатами и экспорт
.qdpx
Детали реализации см. в исходной документации.