Verifique contagens de respostas, respostas em branco e duplicatas antes de contabilizar uma pesquisa CSV
Separe IDs duplicados de respostas em branco em 10 respostas sintéticas. Informe o denominador das respostas válidas e salve em um novo arquivo as contagens e porcentagens por opção, além dos motivos de exclusão.
Conteúdo verificado 2026.09.19Inclui arquivos de exemplo
Ver o sumário
A tradução foi feita com IA. Confira o código, as unidades e os valores junto com o original. A revisão por falantes nativos de cada idioma ainda não foi concluída. English
Para quem éIniciantes que querem verificar por conta própria as contagens e porcentagens de respostas depois de exportar uma pesquisa para CSV.
Preparação
Instale o Python 3.12 ou posterior e verifique o interpretador com python --version. Nenhum pacote adicional é necessário.
Extraia o ZIP do exemplo em uma nova pasta e confirme que example.py, responses.csv e README.txt estão juntos.
Abra um terminal na pasta que contém example.py. Dependendo do ambiente, você pode usar o comando py no Windows ou python3 no macOS e Linux.
Os dados de prática são sintéticos e não contêm respondentes reais. IDs como R001 foram criados para este exemplo.
01Leia primeiro as dez respostas por conta própria
Se você informar o número de linhas de um arquivo de pesquisa como o número de respondentes, envios duplicados podem ser incluídos. A porcentagem da mesma opção também muda dependendo de as respostas em branco estarem ou não no denominador. Este exemplo usa uma única pergunta, “Would you use it again?”, para praticar as regras que devem ser definidas antes da contabilização. Antes de executar qualquer coisa, procure duplicatas e respostas em branco no arquivo original.
Abra responses.csv em um editor de texto ou programa que leia CSV. A primeira linha tem duas colunas: respondent_id,answer.
Sem contar o cabeçalho, confirme que há 10 linhas de dados. Verifique que R005 aparece duas vezes e que as respostas de R004 e R009 estão em branco.
Execute o comando abaixo em um terminal na pasta que contém example.py.
Abra summary.json, choices.csv e audit_rows.csv criados em outputs, nessa ordem.
bash
python example.py
O código localiza responses.csv com base na localização de example.py. A saída é gravada em uma pasta outputs separada, e a entrada não é alterada. Mesmo que você execute o script pelo caminho a partir de outra pasta de trabalho, ele lê o mesmo arquivo de entrada do exemplo.
02Diferencie a regra de duplicatas e os dois denominadores
R005 enviou tanto ‘예’ (Yes) quanto ‘아니오’ (No). Este exemplo não escolhe arbitrariamente o primeiro ou o último valor; ele exclui todas as linhas de qualquer ID que apareça mais de uma vez, porque não há informação para decidir qual resposta é válida. Essa política é declarada no código e em duplicate_policy no arquivo de resumo.
Indicador
Cálculo
Resultado do exemplo
Linhas de dados originais
Linhas lidas, sem o cabeçalho
10 rows
IDs únicos
Cada ID contado uma vez
9
Linhas excluídas como duplicadas
Todas as linhas de R005
2 rows
Respondentes únicos restantes
10 − 2
8
Respostas válidas
8 restantes − 2 respostas em branco
6
A taxa de conclusão é 75.00%, a proporção de 6 respostas válidas entre os 8 respondentes únicos restantes. A porcentagem de cada opção usa as 6 respostas válidas como denominador. Os 9 IDs únicos não são usados como denominador porque todas as respostas de R005 foram excluídas da análise.
03Código completo com verificações e contabilização
read_rows verifica as colunas, os IDs e as opções. summarize primeiro conta quantas vezes cada ID aparece e depois atribui a cada linha um destes status: duplicate_id, blank_answer ou valid. Como as linhas duplicadas são avaliadas primeiro, a mesma linha não é contada duas vezes como exclusão por duplicata e por resposta em branco.
example.py
"""설문 합성 CSV의 중복 ID와 무응답을 점검한 뒤 유효 응답을 집계합니다."""
import csv
import json
import re
import sys
from collections import Counter
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path
BASE = Path(__file__).resolve().parent
INPUT = BASE / "responses.csv"
OUTPUT = BASE / "outputs"
CHOICES = ["예", "아니오", "잘 모르겠음"]
def percent(numerator, denominator):
# 분모가 0이면 0%로 오해하지 않도록 계산 불가를 None으로 남깁니다.
if denominator == 0:
return None
value = Decimal(numerator) * Decimal(100) / Decimal(denominator)
return str(value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
def read_rows():
if INPUT.is_symlink() or not INPUT.is_file():
raise ValueError("responses.csv는 일반 파일이어야 합니다.")
rows = []
with INPUT.open("r", encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream, strict=True)
if reader.fieldnames != ["respondent_id", "answer"]:
raise ValueError("열 이름과 순서는 respondent_id,answer여야 합니다.")
for number, row in enumerate(reader, start=1):
if None in row or any(value is None for value in row.values()):
raise ValueError(f"데이터 {number}행: 열 수를 확인하세요.")
respondent_id, answer = row["respondent_id"].strip(), row["answer"].strip()
if not re.fullmatch(r"R[0-9]{3}", respondent_id):
raise ValueError(f"데이터 {number}행: ID는 R001처럼 R과 숫자 세 자리여야 합니다.")
if answer and answer not in CHOICES:
raise ValueError(f"데이터 {number}행: 알 수 없는 응답 {answer!r}")
rows.append({"data_row": number, "respondent_id": respondent_id, "answer": answer})
return rows
def summarize(rows):
frequencies = Counter(row["respondent_id"] for row in rows)
duplicate_ids = sorted(key for key, count in frequencies.items() if count > 1)
duplicates = set(duplicate_ids)
counts = Counter()
audit, blank_after_duplicates = [], 0
for row in rows:
# 중복 그룹에서는 첫 응답이나 마지막 응답을 임의로 고르지 않습니다.
if row["respondent_id"] in duplicates:
status = "duplicate_id"
elif not row["answer"]:
status = "blank_answer"
blank_after_duplicates += 1
else:
status = "valid"
counts[row["answer"]] += 1
audit.append({**row, "status": status})
duplicate_rows = sum(frequencies[key] for key in duplicate_ids)
eligible = len(rows) - duplicate_rows # 여기에는 빈 답변을 한 고유 ID도 포함됩니다.
valid = sum(counts.values())
summary = {
"question": "다시 이용할 의향이 있나요?",
"duplicate_policy": "exclude_all_rows_with_duplicate_id",
"raw_rows": len(rows),
"unique_ids": len(frequencies),
"duplicate_ids": duplicate_ids,
"duplicate_id_count": len(duplicate_ids),
"excluded_duplicate_rows": duplicate_rows,
"raw_blank_answer_rows": sum(not row["answer"] for row in rows),
"eligible_unique_respondents": eligible,
"blank_answer_rows_after_duplicates": blank_after_duplicates,
"valid_answer_rows": valid,
"completion_rate_percent": percent(valid, eligible),
"choices": [
{"answer": answer, "count": counts[answer], "denominator": valid,
"percent": percent(counts[answer], valid)} for answer in CHOICES
],
}
return summary, audit
def main():
if OUTPUT.exists() or OUTPUT.is_symlink():
raise FileExistsError("outputs가 이미 있습니다. 기존 결과를 옮긴 뒤 실행하세요.")
summary, audit = summarize(read_rows()) # 입력 검증과 집계를 마친 뒤 출력합니다.
OUTPUT.mkdir()
with (OUTPUT / "summary.json").open("x", encoding="utf-8") as stream:
json.dump(summary, stream, ensure_ascii=False, indent=2)
stream.write("\n")
with (OUTPUT / "choices.csv").open("x", encoding="utf-8-sig", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=["answer", "count", "denominator", "percent"])
writer.writeheader()
writer.writerows(summary["choices"])
with (OUTPUT / "audit_rows.csv").open("x", encoding="utf-8-sig", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=["data_row", "respondent_id", "answer", "status"])
writer.writeheader()
writer.writerows(audit)
print(f"원시 {summary['raw_rows']}행 / 고유 ID {summary['unique_ids']}개")
print(f"중복 제외 {summary['excluded_duplicate_rows']}행 / 남은 고유 응답자 {summary['eligible_unique_respondents']}명")
print(f"빈 응답 {summary['blank_answer_rows_after_duplicates']}행 / 유효 응답 {summary['valid_answer_rows']}행")
if not summary["valid_answer_rows"]:
print("유효 응답이 없어 선택지 비율을 계산하지 않았습니다.")
print(f"완료: {OUTPUT}")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (OSError, ValueError, csv.Error) as error:
print(f"중지: {error}", file=sys.stderr)
raise SystemExit(2)
As ocorrências por ID e por opção são contadas com Counter, e as porcentagens são arredondadas para duas casas decimais com Decimal. Espaços no início e no fim dos IDs e respostas são removidos. Opções desconhecidas não são descartadas silenciosamente; o script é interrompido. A pasta de saída só é criada depois que todas as verificações de entrada terminam.
04Verifique as contagens e porcentagens por opção
Opção
Contagem
Denominador
Porcentagem
예 (Yes)
3
6
50.00%
아니오 (No)
2
6
33.33%
잘 모르겠음 (Not sure)
1
6
16.67%
‘예’ (Yes) corresponde às três respostas de R001, R003 e R007. O ‘예’ de R005 foi excluído, portanto não são quatro. ‘아니오’ (No) corresponde a duas respostas, R002 e R008, e ‘잘 모르겠음’ (Not sure) corresponde a uma resposta, R006. A soma das contagens das três opções, 6, deve ser igual a valid_answer_rows.
choices.csv fornece quatro colunas: answer, count, denominator e percent. percent registra uma string numérica como 50.00 sem o sinal %. Ao transferi-la para uma tabela, lembre-se de que ela já é uma porcentagem multiplicada por 100. Tenha cuidado para não aplicar diretamente a formatação de porcentagem de uma planilha e transformá-la em 5000%.
05Rastreie os motivos de exclusão até as linhas originais
Arquivo
O que verificar
summary.json
Linhas originais, IDs únicos, linhas excluídas, ambos os denominadores e taxas
choices.csv
Contagem por opção e denominador de respostas válidas
audit_rows.csv
Ordem das linhas de dados, ID, resposta e status atribuído
Em audit_rows.csv, verifique que data_row 5 e 6 são duplicate_id. Ambos devem ser R005.
Verifique que data_row 4 e 10 são blank_answer. Os IDs são R004 e R009.
Verifique que as seis linhas restantes são valid e que a contagem corresponde a summary.json.
Verifique que o responses.csv original não foi alterado. Executar o mesmo comando novamente deve interromper o processo mantendo o outputs existente.
data_row representa a ordem dos dados lidos, excluindo o cabeçalho. Linhas fisicamente vazias são ignoradas durante a leitura do CSV, portanto esse número nem sempre corresponde às linhas de um editor de texto. Para representar uma ausência de resposta, use uma linha com ID e somente a resposta em branco, como R004, em vez de uma linha completamente vazia.
06Um pequeno experimento em que o denominador muda
Ao repetir a prática, mova o primeiro resultado para outro nome ou extraia o ZIP em uma nova pasta. Tente executar uma cópia em que a resposta em branco de R004 seja alterada para ‘예’ (Yes). Depois de remover duplicatas, continuam existindo 8 respondentes e passam a existir 7 respostas válidas. ‘예’ deve ser 4/7 = 57.14%, e a taxa de conclusão 7/8 = 87.50%.
Você também pode considerar deixar em branco a resposta de todos os respondentes únicos. Se ainda houver respondentes, mas existirem 0 respostas válidas, a taxa de conclusão será 0.00%. Por outro lado, o denominador das porcentagens por opção será 0, então elas não serão calculadas. O JSON registra null e o CSV deixa o campo em branco para diferenciá-los de 0%.
07Erros de entrada que devem ser corrigidos antes da contabilização
Motivo da interrupção
Como verificar
Próximo passo
Nomes de colunas ou ordem incorretos
Verifique que a primeira linha seja respondent_id,answer
Faça as colunas da cópia exportada seguirem as regras.
ID em branco ou formato incorreto
Verifique que o ID seja R seguido por três dígitos
Corrija os IDs para seguirem as regras do exemplo sintético.
Resposta desconhecida
Verifique valores diferentes de 예, 아니오 e 잘 모르겠음
Confira os dados de origem para determinar se é um erro de digitação ou uma nova opção.
Quantidade de colunas não corresponde
Verifique vírgulas extras ou ausentes
Execute com uma cópia em que a estrutura CSV tenha sido corrigida.
outputs já existe
Verifique se já existe uma pasta de resultados anterior
Mantenha os resultados com outro nome e execute novamente.
Se houver um erro de entrada, o script é interrompido antes de criar a pasta de saída. Se você excluir primeiro a linha para evitar o erro, perde a evidência da exclusão. Mantenha o original, registre as alterações e regras em uma cópia e execute novamente. Mesmo que uma resposta desconhecida esteja em uma linha duplicada, a verificação de entrada é interrompida primeiro.
08O que decidir ao expandir para uma pesquisa real
Em uma pesquisa real, primeiro decida o que significa um envio duplicado. A política de tratamento depende de uma resposta posterior ser uma revisão, de várias pessoas usarem o mesmo dispositivo ou de um ID ter sido atribuído incorretamente. Não aplique automaticamente a regra deste exemplo de “excluir todos os IDs duplicados” a qualquer pesquisa; defina uma base adequada ao desenho da pesquisa. O mesmo ID, por si só, também não prova que se trata realmente da mesma pessoa.
Este código também lida apenas com respostas de escolha única para uma pergunta. Ele não inclui múltipla escolha, perguntas puladas por condições, pesos, classificação de respostas de texto livre, representatividade da amostra ou significância estatística. Diferencie a etapa de tirar conclusões reais de pesquisa da etapa de verificar a estrutura do CSV e informe em conjunto, no relatório, a quantidade analisada e as regras de exclusão.
Todos os dados são carregados na memória, portanto testes de desempenho para pesquisas grandes são uma tarefa separada. Se ocorrer um erro de disco durante a gravação dos resultados, uma saída parcial pode permanecer, então verifique a mensagem de conclusão e os três arquivos. Nenhuma informação pessoal ou conexão com um serviço real de pesquisa foi usada para verificar o exemplo.
Registro de execução e verificação
2026-09-19 · Windows 11 · CPython 3.12.14 · somente biblioteca padrão · executado em uma cópia temporária
Passou em 14 testes independentes: foram confirmadas 10 linhas originais, 9 IDs únicos, 2 linhas duplicadas excluídas, 8 restantes, 2 em branco e 6 válidas
Foram confirmadas porcentagens por opção de 50.00%, 33.33% e 16.67%, além de taxa de conclusão de 75.00%
Foi confirmado o tratamento de denominador zero para dados vazios, todas as respostas em branco e todos os IDs duplicados
Foi confirmado que respostas em branco em um grupo duplicado não são excluídas duas vezes, além da normalização de espaços
Foram confirmados a rejeição de erros de entrada, a preservação de resultados existentes e SHA-256 idêntico do original antes e depois da execução
Limites da verificação
Nenhum serviço real de pesquisa ou informação pessoal foi utilizado.
Representatividade da amostra, significância estatística, pesos e múltipla escolha não foram verificados.
O ambiente de verificação foi Windows, e o desempenho com grandes volumes de dados não foi medido.
O código de exemplo, os nomes de arquivos e as chaves de entrada são mantidos como no original. Consulte também os comandos e os procedimentos de conferência do texto traduzido.
Material de prática original · Guarde os arquivos originais separadamente antes de executar.
Fontes de referência
As explicações e os exemplos são de elaboração própria. Os comportamentos e conceitos relacionados podem ser consultados nas fontes oficiais abaixo.
Em vez de coletar apenas um título e uma URL, registre em uma única linha a data de referência, a data de publicação, a data de acesso, as unidades e os termos de uso. Inclui um modelo CSV e uma checklist que não exigem código.
Mantenha uma pequena lista de referências de pesquisa em CSV, normalize o texto DOI para comparação e gere um arquivo de revisão para valores DOI duplicados e ausentes. O workflow preserva o CSV original e não afirma que um DOI é válido apenas porque está presente.
Documente o significado de cada coluna do conjunto de dados antes da análise, incluindo unidade, tipo de dado e valores permitidos. Um pequeno conjunto de dados sintético mostra como o mesmo dicionário também pode servir para uma validação automática simples.