コンテンツへスキップ

定性コーディングのためのインタビュー記録の準備

インタビューとフォーカスグループを自分のコンピューターで文字起こしし、テキストを匿名化し、参加者ごとに一つのケースを設定してから、コーディングして NVivo にエクスポートします。

インタビューとフォーカスグループの録音は、五つのステップでコーディング済みの抜粋になります。すなわち、話者ラベルとタイムスタンプ付きの文字起こし、匿名化、記録全体か抜粋かの選択、参加者ごとに一つのケースを付けた読み込み、そしてコーディングとエクスポートです。Potato は録音を自分のコンピューター上で WhisperX または faster-whisper により文字起こしし、QDA モードでメモとケースを使ってテキストをコーディングし、コーディング済みのプロジェクトを REFI-QDA の .qdpx ファイルとして NVivo、ATLAS.ti、MAXQDA にエクスポートします。Potato は記録の匿名化を行わないため、テキストを読み込む前に、チームが名前やその他の識別情報を置き換えます。

文字起こしは録音された発話をテキストに変換する作業であり、文字起こしに盛り込む詳細の水準は分析上の判断です。Oliver、Serovich、Mason(2005)は二つの流儀を挙げています(文献 2)。自然化された文字起こしは、すべての発話をできるだけ詳細に捉えます。脱自然化された文字起こしは、文法を直し、言いよどみや間を取り除き、標準的でないアクセントを標準化します。話者分離は誰がいつ話したかをラベル付けし、匿名化は参加者を特定しうる詳細を取り除くか置き換えます。Potato では、ケースは一人の参加者に属するアイテムをまとめるもので、メモは記録やハイライトした箇所に付ける分析上のノートです。REFI-QDA は、NVivo、ATLAS.ti、MAXQDA が読み書きする交換標準です。

このガイドは、コーディングの前後の準備を扱います。二人のコーダーでインタビュー記録をコーディングしアルファを報告するでは二人のコーダー間の信頼性を扱い、Whisper の文字起こしをアノテーションする方法では音声認識の出力を音声と照らして修正する方法を扱います。質的研究のためのコードブック開発ではコードの書き方を扱い、定性データ分析ソフトウェアの比較ではツールの選択を扱います。

最初に決めておく文字起こしの規約

McLellan、MacQueen、Neidig(2003)は、あらゆる種類の定性データ、場面、理論的アプローチに合う単一の文字起こし形式は存在せず、準備の不十分な記録は分析を遅らせたり損なったりしうると述べています(文献 1)。書面の規約があれば、すべての文字起こし担当者とすべての修正作業が同じルールに従います。チームは、最初の録音を文字起こしする前に規約に合意します。規約は三つの判断を含みます。

  • 詳細の水準。 フィラー、間、重なった発話、笑いを記すかどうかと、その表記法を規約で定めます。
  • 話者ラベル。 各話者には、Interviewer や P07 のような役割または参加者 ID を付け、実名は決して使いません。
  • タイムスタンプ。 各ターンの冒頭の時刻により、コーダーは録音中の該当箇所を探せます。

Potato のローカル文字起こしは Whisper モデルが生成したテキストを書き出すだけで、間、重なり、フィラーを記すようモデルに指示する Potato の設定はありません。そのため、自然化された文字起こしには、録音と照らした修正作業が必要です。Whisper の文字起こしをアノテーションする方法は、Potato での修正作業を設定します。

自分のコンピューターでの録音の文字起こし

同意書や倫理審査の承認は、インタビューの録音を誰が聞けるか、また音声をどこに送れるかを制限していることがよくあります。potato transcripts --transcribe は、sherpa-onnx による話者分離と、WhisperX がインストールされていれば WhisperX、そうでなければ faster-whisper による音声認識を実行します。transcribe エクストラは faster-whisper と sherpa-onnx をインストールし、WhisperX は別途インストールするもの(pip install whisperx)です。3つのツールすべてが、クラウドサービスを使わずに自分のコンピューターで動きます。

bash
pip install 'potato-annotation[transcribe]'
potato transcripts ./recordings --transcribe --asr-model small.en --diarize --num-speakers 2 -o data/transcripts.json

このコマンドは、録音ごとに一つのアイテムを書き出し、各ターンの話者、開始時刻、終了時刻、テキストを含めます。Whisper のモデルは tiny から large-v3 まであり、デフォルトは base です。大きいモデルほど動作は遅くなり、文字起こしは正確になります。Potato のヘルプテキストは、二人のインタビューでは --num-speakers で話者数を指定すると話者分離が格段によくなると述べています。二つの声を持つ 21 秒の合成インタビューでのテストでは、次の概要が出力されました。

text
Scanned 1 file(s):
  int07.wav                                local ASR + diarization     5 turns     21.4s  2 speaker(s): SPEAKER_00, SPEAKER_01

Potato 2.14.0 は、local ASR の代わりにエンジン名を表示し、ASR (whisperx) または ASR (faster-whisper) のようになります。

話者分離は、話者を SPEAKER_00、SPEAKER_01 のように、決まった順序なしに命名します。テストでは SPEAKER_01 がインタビュアーでした。下の準備スクリプトが、各ラベルを役割または参加者 ID に対応づけます。Potato 自身のソースは、クラスタリングに基づく話者分離はクロストークに弱いと記しています。コーディングを始める前に、すべてのフォーカスグループの話者ラベルを録音と照らして確認してください。

モデルの重みは初回の使用時にダウンロードされます。二つの話者分離モデルのファイルは合わせて約 47 MB で、Whisper のダウンロードはモデルのサイズとともに大きくなります。重みがマシンに置かれれば、エアギャップとオフラインのデプロイで説明されているとおり、文字起こしはエアギャップ環境で動きます。オフラインのマシンの場合は、CTranslate2 形式の Whisper モデルのディレクトリをコピーし、そのディレクトリのパスを --asr-model に渡し、話者分離モデルを入れたフォルダーを POTATO_MODEL_CACHE に設定してください。

Potato は、各録音の隣に生の文字起こしを <name>.whisper.json としてキャッシュし、このキャッシュはすべての名前をそのまま保持します。キャッシュのファイルは録音と一緒に保管するか、--asr-cache を渡して、同じように保護されたフォルダーに書き出してください。Whisper が無音の部分にテキストを作り出さないように、文字起こしの前に音声区間検出が実行されます。この検出は、小さな声やささやき声の発話を警告なしに落とすこともあります。文字起こしが録音より短く返ってきた場合は、--asr-no-vad を付けてコマンドをもう一度実行してください。WhisperX は常に音声区間検出を実行するため、WhisperX がインストールされている場合は、--asr-no-vad を有効にするために --asr-engine faster-whisper を追加してください。

コーディング前の記録の匿名化

Potato には、記録内の名前、場所、その他の識別情報を見つけたり置き換えたりするツールはありません。したがって匿名化は、Potato がテキストを読み込む前に行います。コーダーの画面、メモ、引用レポート、.qdpx のエクスポートは、すべて読み込まれたままの記録を再現するためです。Potato が行う唯一のテキストの除去は、プロジェクトの終わりに実行されます。python -m potato.publish --scrub-pii は、公開されたデータセット内のメールアドレスと電話番号を [EMAIL] と [PHONE] に置き換えますが、このオプションは名前を検出しません。

UK Data Service は、内容を削除するのではなく、識別情報を仮名や「[colleague 1]」のような説明的なタグに置き換えることを推奨しています。同じガイダンスは、各置き換えを記録間で一貫して適用すること、および変更の記録を残すことも求めています。またガイダンスは、過度の匿名化が文脈の詳細を失わせ、データの解釈上の価値を下げるため、節度も求めています(文献 4)。Saunders、Kitzinger、Kitzinger(2015)は、遷延性意識障害および最小意識状態にある患者の家族へのインタビューを例に、このトレードオフを説明しています(文献 3)。著者らは病院名を「Hospital 1」のようなラベルに置き換え、一部の保護がデータの豊かさの一部を分析から失わせたと報告しています。

次のセクションの準備スクリプトは、置換テーブル pseudonyms.csv をすべての記録に適用し、このテーブルは匿名化の記録も兼ねます。このテーブルは仮名を実名に対応づけるため、Potato が提供するプロジェクトフォルダーの外に保管してください。テーブルが捉えるのは、テーブルに載っている表記だけです。したがって、置き換えの後でも、人がすべての記録を読み、職場や珍しい職業のような間接的な識別情報を判断します。テスト用の記録では、テーブルが参加者の姉の名前を Ana に、通りの名前を「[street name]」に置き換えましたが、姉が働くパン屋は、読み手の判断に委ねられて残りました。読み込み後は、Potato の全文検索で記録内の各名前を、コーパス全体から検索してください。どの検索でも、記録は一つも返ってこないはずです。

記録全体か、抜粋か

コーダーが目にする単位は、データファイル内の一つのアイテムです。記録全体を一つのアイテムとすればインタビューの流れがコーダーの前に保たれ、.qdpx のエクスポートはインタビューごとに一つのソースを書き出します。プロジェクトは代わりに、各記録を参加者の回答ごとに一つのアイテムのような抜粋に分割することもでき、その場合エクスポートは抜粋ごとに一つのソースを書き出します。抜粋が向いているのは、次の二つの状況です。

  • コーダー間信頼性。 一致度は両方のコーダーが回答した単位について計算され、固定された抜粋なら両方のコーダーに同じ単位が与えられます。二人のコーダーのチュートリアルは、この理由で記録を回答ごとに分割しています。
  • フォーカスグループ。 Potato は各アイテムを最大一つのケースにしか分類しないため、グループの記録を一つのアイテムとして読み込むと、一つのケースにしか属しません。参加者のターンごとに一つのアイテムにすれば、各参加者のターンがその参加者のケースを形成できます。

記録を話者ラベル付きテキストに平坦化する

potato transcripts の出力は、各記録をターンの入れ子のリストとして保存し、.qdpx のエクスポートは、テキストが単純な文字列でないアイテムをスキップします。そのため、NVivo、ATLAS.ti、MAXQDA に向かうプロジェクトは、まず各記録を話者ラベル付きテキストに平坦化します。三つの小さな CSV ファイルが、スクリプトが適用する判断を保持します。

text
speakers.csv        transcript,speaker,name
                    int07,SPEAKER_01,Interviewer
                    int07,SPEAKER_00,P07
participants.csv    participant_id,site,age_group
                    P07,rural,35-49
pseudonyms.csv      original,replacement
                    Maria,Ana
                    Main Street,[street name]

このスクリプトは、同じ話者の連続するセグメントを一つのターンに統合し、各ターンの冒頭にタイムスタンプを書き込み、置換テーブルを適用し、参加者の属性をアイテムにコピーします。--excerpts を付けると、スクリプトは代わりに参加者のターンごとに一つのアイテムを書き出します。

python
"""Turn `potato transcripts` output into speaker-labeled text for coding.
 
Usage:
  python prepare_transcripts.py data/transcripts.json speakers.csv participants.csv \
      pseudonyms.csv > data/interviews.jsonl
  python prepare_transcripts.py ... --excerpts > data/excerpts.jsonl
"""
import csv
import json
import re
import sys
 
transcripts, speakers_csv, participants_csv, pseudonyms_csv = sys.argv[1:5]
excerpts = "--excerpts" in sys.argv
 
names = {(r["transcript"], r["speaker"]): r["name"] for r in csv.DictReader(open(speakers_csv))}
people = {r["participant_id"]: r for r in csv.DictReader(open(participants_csv))}
swaps = [(re.compile(rf"\b{re.escape(r['original'])}\b"), r["replacement"])
         for r in csv.DictReader(open(pseudonyms_csv))]
 
 
def clock(seconds):
    m, s = divmod(int(seconds), 60)
    return f"{m // 60:02d}:{m % 60:02d}:{s:02d}"
 
 
def pseudonymize(text):
    for pattern, replacement in swaps:
        text = pattern.sub(replacement, text)
    return text
 
 
for item in json.load(open(transcripts, encoding="utf-8")):
    tid = item["id"]
    turns = []  # merge consecutive segments from the same speaker into one turn
    for t in item["conversation"]["turns"]:
        name = names.get((tid, t.get("speaker")), t.get("speaker") or "Unknown")
        if turns and turns[-1]["name"] == name:
            turns[-1]["text"] += " " + t["text"]
        else:
            turns.append({"name": name, "start": t.get("start") or 0, "text": t["text"]})
    for t in turns:
        t["text"] = pseudonymize(t["text"])
 
    if excerpts:  # one item per participant turn, for focus groups
        for n, t in enumerate(turns):
            if t["name"] in people:
                print(json.dumps({"id": f"{tid}-{n:03d}", "transcript": tid,
                                  **people[t["name"]],
                                  "text": f"[{clock(t['start'])}] {t['name']}: {t['text']}"}))
        continue
 
    participant = next(t["name"] for t in turns if t["name"] in people)
    text = "\n\n".join(f"[{clock(t['start'])}] {t['name']}: {t['text']}" for t in turns)
    print(json.dumps({"id": tid, **people[participant], "text": text}))

テスト用の録音では、スクリプトは一つのアイテムを書き出しました。ここではテキストを短縮して示します。

json
{"id": "int07", "participant_id": "P07", "site": "rural", "age_group": "35-49", "text": "[00:00:00] Interviewer: Thanks for joining, can you tell me how you get to the clinic?\n\n[00:00:04] P07: I take two buses, and it takes most of the morning, my sister Ana drives me when she can, but she works at the bakery on [street name].\n\n..."}

すでにファイルとして存在する記録も、同じスクリプトを通ります。--transcribe を付けない場合、potato transcripts は、文字起こし形式に一覧されているとおり、WhisperX JSON、SubRip、WebVTT、Praat TextGrid、ELAN EAF を含む 21 の文字起こしおよび字幕の形式を読み込みます。別の QDA プログラムでコーディングされたプロジェクトは、プロジェクトのインポートで説明されているとおり、potato import で取り込めます。

QDA モードでのコーディング

以下の設定は、参加者ごとに一つのケースを付けた、記録全体のオープンコーディングを設定します。この設定は、Potato 2.13.1 で python -m potato.validate_cli config.yaml --strict に合格します。

yaml
annotation_task_name: Clinic access interviews
task_dir: .
output_annotation_dir: annotation_output/
data_files:
  - data/interviews.jsonl
item_properties:
  id_key: id
  text_key: text
user_config:
  allow_all_users: true
  users: []
 
# Open coding by one analyst: a codebook that grows while reading, memos, cases, search
qda_mode:
  enabled: true
 
# One case per participant, with the participant's site and age group on the case
cases:
  enabled: true
  key: participant_id
  attributes: [site, age_group]
 
annotation_ui:
  memos: true
 
search:
  enabled: true
  annotator_claim: true
 
annotation_schemes:
  - annotation_type: span
    name: codes
    description: Highlight a passage and apply a code, or press i to create a code from the highlighted words.
    codebook: true
    labels: [travel to the clinic, cost of care, family support, delaying care]

QDA モードはコードブックを open に設定します。アナリストは読みながらコードを追加し、四つのラベルはコードブックの種にすぎません。箇所を選択して i を押すと、選択した語からインビボ・コードが作成されます。後でコードの名前を変更したり統合したりしても、以前のコーディングは古い名前のまま残ります。これは定性コーディングのための共有コードブックで説明されています。search ブロックは、上の名前の確認に使う全文検索をオンにします。サーバーは potato start config.yaml -p 8000 で起動します。

cases ブロックは、アイテムを participant_id でグループ化し、各ケースに site と age_group を保存します。Potato 2.13.1 は、アイテムのフィールドからのみケースを構築し、アイテムを手動でケースに割り当てる方法を提供しません。したがって、すべてのアイテムに参加者 ID が必要です。テスト用プロジェクトのサーバーログには Cases auto-detect: 1 case(s), 1 instance(s) assigned と報告されました。コーディングの進行中は、管理 API のエンドポイント /admin/api/code_crosstab?attribute=site が、ケースとケース属性で説明されているとおり、ケース属性ごとにコードを数えます。

メモは、各記録の「ノート」パネルで開きます。新しいメモは非公開として始まります。ほかのチームメンバーにメモを読んでもらいたいコーダーは、分析メモで説明されているとおり、メモの公開設定メニューで Shared を選びます。QDA モードは、コーパス全体を一人のアナリストが扱うことを前提にしています。同じ抜粋を二人のコーダーが扱う場合は、二人のコーダーのチュートリアルと質的研究におけるコーダー間信頼性に従ってください。言語モデルを二人目のコーダーとして使う場合は、LLM を使った定性コーディングを参照してください。

コーディング済みの記録のエクスポート

三つのエクスポートが、通常の成果物をカバーします。

bash
python -m potato.export -c config.yaml -f qdpx -o export/
python -m potato.export -c config.yaml -f quotation_report --option include_memos=true -o export/
python -m potato.export -c config.yaml -f csv -o export/

最初のコマンドは、記録ごとに一つのテキストソース、階層のないフラットなリストとしてのコード、およびコーダー付きのすべてのコーディングを含む .qdpx ファイルを書き出します。このエクスポートは、メモとケースを含みません。設定が cases.enabled と annotation_ui.memos を明示的に設定しているため、エクスポートはメモとケースに言及する警告を表示します。二つ目のコマンドは、ハイライトした箇所ごとに一行、コード、引用、文字オフセット、記録 ID、コーダーを示し、その後にメモごとに一行を続ける quotations.csv を書き出します。三つ目のコマンドは、すべてのコーダーのアノテーションを、R や Python 向けの表として書き出します。ケース属性は participants.csv に残り、データファイルの id と participant_id のフィールドが、その属性を引用レポートに結び付けます。

よくある質問

インタビューの記録は逐語記録にすべきですか、それともクリーンな逐語記録にすべきですか?

詳細の水準は、分析から選んでください。Oliver、Serovich、Mason(2005)は、自然化された完全な逐語の文字起こしは、会話分析で最も一般的だと述べています。参加者があるトピックについて何を語ったかを扱う研究は、通常、脱自然化されたクリーンな記録で進められます。Potato のローカル文字起こしは、Whisper モデルのテキストを書き出します。したがって、逐語の規約には、録音と照らした修正作業が必要です。

インタビューの記録はいつ匿名化すべきですか?

Potato は読み込まれたままのテキストを表示してエクスポートするため、コーディングを始める前に記録を匿名化してください。Potato には匿名化のツールがありません。読み込む前に、一貫した仮名の置換テーブルを適用し、各記録を読んで間接的な識別情報を確認し、読み込んだコーパスで元の名前をすべて検索してください。

faster-whisper は、音声をアップロードせずにインタビューを文字起こしできますか?

はい。faster-whisper は Whisper を自分のコンピューターで実行し、potato transcripts --transcribe は、WhisperX がインストールされていない場合に faster-whisper を使って、録音のフォルダーを Potato のデータファイルに変換します。WhisperX がインストールされていれば、このコマンドは代わりに WhisperX を自分のコンピューターで実行し、--asr-engine faster-whisper を付けると faster-whisper が使われます。モデルの重みは初回の使用時にダウンロードされ、最初のダウンロードの後、文字起こしにネットワークは不要です。

関連資料

参考文献

  1. McLellan, E., MacQueen, K. M., & Neidig, J. L. (2003). Beyond the Qualitative Interview: Data Preparation and Transcription. Field Methods, 15(1), 63-84. https://doi.org/10.1177/1525822X02239573
  2. Oliver, D. G., Serovich, J. M., & Mason, T. L. (2005). Constraints and Opportunities with Interview Transcription: Towards Reflection in Qualitative Research. Social Forces, 84(2), 1273-1289. https://doi.org/10.1353/sof.2006.0023
  3. Saunders, B., Kitzinger, J., & Kitzinger, C. (2015). Anonymising interview data: challenges and compromise in practice. Qualitative Research, 15(5), 616-632. https://doi.org/10.1177/1468794114550439
  4. UK Data Service. Anonymisation for text data. Retrieved November 2, 2026, from https://ukdataservice.ac.uk/learning-hub/data-producer-support/preparing-data-for-sharing-and-reuse/anonymising-data-and-protecting-participants/anonymisationfortextdata/