0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

アンケートの複数回答をPythonで集計、5人で160%

0
Posted at

割合の合計は100%にそろえたい。でも、複数回答アンケートでそれをすると「何人が選んだか」が見えなくなります。今回の集計は、回答者5人で合計160%。このまま報告に使えます。

僕は、商品説明会のアンケートには「選んだ人数÷この設問の回答者数」を載せたいです。知りたいのは関心を持った人の割合。100%へそろえると、営業資料に書く意味まで変わります。

今日、6行のサンプルをPythonで集計したら、「価格」の割合が37.5%、50%、60%になりました。選んだのはどれも3人。割る数だけで、ここまで違います。

分母を「この設問に答えた人」に決める

「商品を選ぶときに重視する点」を複数選択で聞く練習用データです。

回答者ID 選んだ項目
A 価格、使いやすさ
B 価格、サポート
C 使いやすさ、サポート
D 価格
E 使いやすさ
F 未回答

提出は6人分。この設問に答えたのは5人で、選択の延べ数は8件です。今回は1人1行を前提にします。

計算 結果 読めること
3÷8 37.5% 全選択に占める価格の割合
3÷6 50.0% 提出者全体で価格を選んだ割合
3÷5 60.0% この設問の回答者で価格を選んだ割合

37.5%を「回答者の37.5%が価格を重視」と説明すると誤りです。8は人数ではありません。

今回は最後の定義を採用し、未回答の1人も別に表示します。空欄と「重視する項目がない」は区別します。「特になし」は選択肢を別に用意する扱いです。

「その他」の長い自由記述も、集計表と別に読めるように残したいです。

Pythonで人数と選択率を一緒に出す

CPython 3.14.6で実行済み。次をsurvey.pyとして保存し、python3 survey.pyで動きます。追加パッケージや入力ファイルの準備は不要です。

入力はidとchoicesの2列。選択肢は半角の;区切りにしました。サービスの出力形式に合わせて調整します。

import csv
from collections import Counter
from io import StringIO

OPTIONS = ("価格", "使いやすさ", "サポート")
DATA = """id,choices
A,価格;使いやすさ
B,価格;サポート
C,使いやすさ;サポート
D,価格
E,使いやすさ
F,
"""

def summarize(rows):
    seen = set()
    counts = Counter()
    answered = 0
    for row in rows:
        person = row["id"].strip()
        if not person or person in seen:
            raise ValueError("回答者IDが空欄または重複")
        seen.add(person)
        choices = {s.strip() for s in row["choices"].split(";")
                   if s.strip()}
        unknown = choices - set(OPTIONS)
        if unknown:
            raise ValueError(f"未登録の選択肢: {sorted(unknown)}")
        if choices:
            answered += 1
            counts.update(choices)
    return len(seen), answered, counts

rows = list(csv.DictReader(StringIO(DATA)))
submitted, answered, counts = summarize(rows)
assert (submitted, answered) == (6, 5)
assert counts == Counter({"価格": 3, "使いやすさ": 3, "サポート": 2})
assert sum(counts.values()) == 8

print(f"提出{submitted}人/設問回答{answered}人/未回答{submitted-answered}人")
if answered:
    for name in OPTIONS:
        n = counts[name]
        print(f"{name}: {n}人 / {answered}人 = {n / answered:.1%}")
    print(f"選択率の合計: {sum(counts.values()) / answered:.1%}")
else:
    print("設問回答0人: 選択率は計算しません")
print("assert: OK")

実行結果です。

提出6人/設問回答5人/未回答1人
価格: 3人 / 5人 = 60.0%
使いやすさ: 3人 / 5人 = 60.0%
サポート: 2人 / 5人 = 40.0%
選択率の合計: 160.0%
assert: OK

延べ8選択÷5人で160%。1人平均1.6項目です。同じ人が複数の項目に入るので、合計が100%を超えます。図にするなら項目ごとの棒グラフが合います。

「3人 / 5人」を割合の横に残す。

報告を受ける側にも効きます。60%だけだと、3人なのか300人なのか分かりません。この5人から市場全体の傾向までは読めません。

1人分の選択を先にまとめる理由

各行の選択肢をsetで重複のない集合にしてから、Counter.update()で件数を足します。手元の標準ライブラリの説明と実装も確認しました。

この順番に意味があります。

提出6人のうち設問回答者は5人。各行で選択の重複をまとめてから人数を数え、5人で割ると60%・60%・40%になる集計手順。

Aの欄を価格;使いやすさ;価格に変えると、そのまま数えた場合はAだけで価格が2件。setを通すと1件になり、全体の価格も3人のままでした。

全員の選択をまとめてからsetにすると、今度は別の人の「価格」まで1つになります。まとめる範囲は1行。ここを動かすと集計の単位が変わります。

同じIDが複数行あれば止めます。再回答を認めるなら、最新の提出だけ残す処理が必要です。この入力には提出日時がないので、新旧を選べません。

入力を変えた確認結果です。

入力の変更 確認した結果
Aの価格を同じセル内で2回書く 価格は3人のまま
Fの空欄を空白文字にする 設問回答は5人のまま
6人全員を未回答にする 設問回答0人、選択数0件
Aの行をもう1行足す ValueError: 回答者IDが空欄または重複
Aに「納期」を追加する ValueError: 未登録の選択肢: ['納期']

前後の空白は除きますが、「価格」と「価格面」は統合しません。未登録の表記は止めます。選択肢を変えたらOPTIONSも更新します。

実ファイルはrowsを作る1行を次へ置き換えて読みます。UTF-8のCSVを想定し、BOM付きでも確認しました。

with open("survey.csv", encoding="utf-8-sig", newline="") as f:
    rows = list(csv.DictReader(f))

直後の3つのassertはサンプル専用です。実データへ替える際は別のテストへ移します。列名はid,choicesにそろえ、列の過不足は事前に直す前提です。選択肢名に;が入る入力は扱いません。

報告書には「複数回答、設問回答5人、未回答1人」と添え、人数と割合を一緒に載せます。別の月と比べるときも、未回答を分母に入れるかをそろえます。今回持ち帰りたいのは、集計表に分母を残すこと。160%を100%へ直す作業は、これで不要です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?