Comprueba los recuentos de respuestas, las respuestas vacías y los duplicados antes de contabilizar una encuesta CSV
Separa los ID duplicados de las respuestas vacías en 10 respuestas sintéticas. Indica el denominador de respuestas válidas y guarda en un archivo nuevo los recuentos y porcentajes por opción, además de los motivos de exclusión.
Contenido revisado 2026.09.19Incluye archivos de ejemplo
Ver el índice
La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English
Para quién esPrincipiantes que quieren comprobar por sí mismos los recuentos y porcentajes de respuestas después de exportar una encuesta a CSV.
Preparación
Instala Python 3.12 o posterior y comprueba el intérprete con python --version. No se necesitan paquetes adicionales.
Extrae el ZIP del ejemplo en una carpeta nueva y comprueba que example.py, responses.csv y README.txt estén juntos.
Abre una terminal en la carpeta que contiene example.py. Según tu entorno, puedes usar el comando py en Windows o python3 en macOS y Linux.
Los datos de práctica son sintéticos y no contienen encuestados reales. Los ID como R001 se crearon para este ejemplo.
01Lee primero las diez respuestas por tu cuenta
Si informas el número de filas de un archivo de encuesta como el número de encuestados, pueden incluirse envíos duplicados. El porcentaje de una misma opción también cambia según si las respuestas vacías se incluyen en el denominador. Este ejemplo utiliza una sola pregunta, “Would you use it again?”, para practicar las reglas que deben definirse antes de contabilizar. Antes de ejecutar nada, busca duplicados y respuestas vacías en el archivo original.
Abre responses.csv en un editor de texto o en un programa que lea CSV. La primera línea tiene dos columnas: respondent_id,answer.
Sin contar la cabecera, confirma que hay 10 filas de datos. Comprueba que R005 aparece dos veces y que las respuestas de R004 y R009 están vacías.
Ejecuta el comando siguiente en una terminal situada en la carpeta que contiene example.py.
Abre summary.json, choices.csv y audit_rows.csv creados en outputs, en ese orden.
bash
python example.py
El código localiza responses.csv en relación con la ubicación de example.py. La salida se escribe en una carpeta outputs separada y la entrada no se modifica. Aunque ejecutes el script mediante su ruta desde otra carpeta de trabajo, leerá el mismo archivo de entrada del ejemplo.
02Distingue la regla de duplicados y los dos denominadores
R005 envió tanto ‘예’ (Yes) como ‘아니오’ (No). Este ejemplo no elige arbitrariamente el primer ni el último valor; excluye todas las filas de cualquier ID que aparezca más de una vez, porque no existe información para determinar qué respuesta es válida. Esta política se indica en el código y en duplicate_policy dentro del archivo de resumen.
Indicador
Cálculo
Resultado del ejemplo
Filas de datos originales
Filas leídas, sin contar la cabecera
10 rows
ID únicos
Cada ID se cuenta una vez
9
Filas excluidas por duplicado
Todas las filas de R005
2 rows
Encuestados únicos restantes
10 − 2
8
Respuestas válidas
8 restantes − 2 respuestas vacías
6
La tasa de finalización es 75.00%, es decir, la proporción de 6 respuestas válidas entre los 8 encuestados únicos restantes. El porcentaje de cada opción utiliza las 6 respuestas válidas como denominador. Los 9 ID únicos no se utilizan como denominador porque todas las respuestas de R005 fueron excluidas del análisis.
03Código completo con comprobaciones y recuento
read_rows comprueba las columnas, los ID y las opciones. summarize cuenta primero cuántas veces aparece cada ID y después asigna a cada fila uno de estos estados: duplicate_id, blank_answer o valid. Como las filas duplicadas se evalúan primero, una misma fila no se cuenta dos veces como exclusión por duplicado y por respuesta vacía.
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)
Cuenta las apariciones por ID y por opción con Counter, y redondea los porcentajes a dos decimales con Decimal. Se eliminan los espacios iniciales y finales de los ID y las respuestas. Las opciones desconocidas no se descartan silenciosamente; el script se detiene. La carpeta de salida solo se crea después de completar todas las comprobaciones de entrada.
04Comprueba los recuentos y porcentajes por opción
Opción
Recuento
Denominador
Porcentaje
예 (Yes)
3
6
50.00%
아니오 (No)
2
6
33.33%
잘 모르겠음 (Not sure)
1
6
16.67%
‘예’ (Yes) corresponde a las tres respuestas de R001, R003 y R007. El ‘예’ de R005 se excluyó, por lo que no son cuatro. ‘아니오’ (No) corresponde a dos respuestas, R002 y R008, y ‘잘 모르겠음’ (Not sure) a una respuesta, R006. La suma de los tres recuentos de opciones, 6, debe ser igual a valid_answer_rows.
choices.csv contiene cuatro columnas: answer, count, denominator y percent. percent guarda una cadena numérica como 50.00 sin el signo %. Al trasladarla a una tabla, recuerda que ya es un porcentaje multiplicado por 100. Evita aplicar directamente el formato de porcentaje de una hoja de cálculo y convertirlo en 5000%.
05Rastrea los motivos de exclusión hasta las filas originales
Archivo
Qué comprobar
summary.json
Filas originales, ID únicos, filas excluidas, ambos denominadores y tasas
choices.csv
Recuento por opción y denominador de respuestas válidas
audit_rows.csv
Orden de las filas de datos, ID, respuesta y estado asignado
En audit_rows.csv, comprueba que data_row 5 y 6 sean duplicate_id. Ambos deben corresponder a R005.
Comprueba que data_row 4 y 10 sean blank_answer. Los ID son R004 y R009.
Comprueba que las seis filas restantes sean valid y que su recuento coincida con summary.json.
Comprueba que el archivo responses.csv original no haya cambiado. Ejecutar el mismo comando otra vez debe detenerse conservando el outputs existente.
data_row es el orden de los datos leídos, sin contar la cabecera. Las líneas físicamente vacías se omiten al leer el CSV, por lo que no siempre coincide con los números de línea de un editor de texto. Para representar una falta de respuesta, utiliza una fila con ID y solo la respuesta vacía, como R004, en lugar de una línea completamente vacía.
06Un pequeño experimento en el que cambia el denominador
Al repetir la práctica, mueve el primer resultado con otro nombre o extrae el ZIP en una carpeta nueva. Prueba una copia en la que la respuesta vacía de R004 se cambie a ‘예’ (Yes). Después de eliminar duplicados siguen quedando 8 encuestados y 7 respuestas válidas. ‘예’ debe ser 4/7 = 57.14% y la tasa de finalización 7/8 = 87.50%.
También puedes considerar dejar vacía la respuesta de todos los encuestados únicos. Si siguen existiendo encuestados pero hay 0 respuestas válidas, la tasa de finalización es 0.00%. En cambio, el denominador de los porcentajes por opción es 0, por lo que no se calculan. JSON registra null y el CSV deja el campo vacío para distinguirlos de 0%.
07Errores de entrada que debes corregir antes de contabilizar
Motivo de detención
Cómo comprobarlo
Siguiente paso
Nombres de columnas u orden incorrectos
Comprueba que la primera línea sea respondent_id,answer
Haz que las columnas de la copia exportada sigan las reglas.
ID vacío o formato incorrecto
Comprueba que el ID sea R seguido de tres dígitos
Corrige los ID para que sigan las reglas del ejemplo sintético.
Respuesta desconocida
Comprueba si hay valores distintos de 예, 아니오 y 잘 모르겠음
Revisa los datos originales para determinar si es un error tipográfico o una opción nueva.
El número de columnas no coincide
Comprueba si faltan o sobran comas
Ejecuta una copia en la que se haya reparado la estructura CSV.
outputs ya existe
Comprueba si existe una carpeta de resultados anterior
Conserva los resultados con otro nombre y vuelve a ejecutar.
Si hay un error de entrada, el script se detiene antes de crear la carpeta de salida. Si primero eliminas una fila para evitar el error, se pierde la evidencia de la exclusión. Conserva el original, registra los cambios y las reglas en una copia y vuelve a ejecutar. Incluso si una respuesta desconocida se encuentra en una fila duplicada, la comprobación de entrada se detiene primero.
08Qué debes decidir al ampliar el ejemplo a una encuesta real
En una encuesta real, primero decide qué significa un envío duplicado. La política de tratamiento depende de si una respuesta posterior es una revisión, si varias personas usan el mismo dispositivo o si se asignó un ID incorrecto. No apliques automáticamente la regla de este ejemplo de “excluir todos los ID duplicados” a cualquier encuesta; establece un criterio adecuado al diseño de la encuesta. Un mismo ID tampoco demuestra por sí solo que se trate realmente de la misma persona.
Este código también maneja únicamente respuestas de selección única a una sola pregunta. No incluye selección múltiple, preguntas omitidas por condiciones, ponderaciones, clasificación de respuestas de texto libre, representatividad de la muestra ni significación estadística. Distingue el paso de extraer conclusiones reales de investigación del paso de comprobar la estructura del CSV, e indica conjuntamente en el informe el número analizado y las reglas de exclusión.
Todos los datos se cargan en memoria, por lo que las pruebas de rendimiento para encuestas grandes son una tarea aparte. Si ocurre un error de disco al escribir los resultados, puede quedar una salida parcial, así que comprueba el mensaje de finalización y los tres archivos. No se utilizó información personal ni conexión con un servicio real de encuestas para verificar el ejemplo.
Registro de ejecución y verificación
2026-09-19 · Windows 11 · CPython 3.12.14 · solo biblioteca estándar · ejecutado en una copia temporal
Superó 14 pruebas independientes: se confirmaron 10 filas originales, 9 ID únicos, 2 filas duplicadas excluidas, 8 restantes, 2 vacías y 6 válidas
Se confirmaron porcentajes por opción de 50.00%, 33.33% y 16.67%, y una tasa de finalización de 75.00%
Se confirmó el manejo de denominador cero para datos vacíos, todas las respuestas vacías y todos los ID duplicados
Se confirmó que las respuestas vacías dentro de un grupo duplicado no se excluyen dos veces, además de la normalización de espacios
Se confirmó el rechazo de errores de entrada, la conservación de resultados existentes y un SHA-256 idéntico del original antes y después de la ejecución
Límites de la verificación
No se utilizó ningún servicio real de encuestas ni información personal.
No se verificaron la representatividad de la muestra, la significación estadística, las ponderaciones ni la selección múltiple.
El entorno de verificación fue Windows y no se midió el rendimiento con grandes volúmenes de datos.
El código de ejemplo, los nombres de archivo y las claves de entrada se mantienen como en el original. Consulta también los comandos y los pasos de comprobación del texto traducido.
Material de práctica original · Guarda los archivos originales por separado antes de ejecutarlo.
Fuentes de referencia
Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.
En lugar de recopilar solo un título y una URL, registra en una sola fila la fecha de referencia, la fecha de publicación, la fecha de acceso, las unidades y las condiciones de uso. Incluye una plantilla CSV y una lista de comprobación que no requieren código.
Mantén una pequeña lista de referencias de investigación en CSV, normaliza el texto DOI para compararlo y genera un archivo de revisión para valores DOI duplicados o ausentes. El workflow conserva el CSV original y no afirma que un DOI sea válido solo porque esté presente.
Documenta qué significa cada columna del conjunto de datos antes del análisis, incluida su unidad, tipo de dato y valores permitidos. Un pequeño conjunto de datos sintético muestra cómo el mismo diccionario también puede servir para una validación automática sencilla.