Carlos Isaac
Zainea Maya

Tunjos para la IA
Programa de cursos

Razonamiento, validación e inteligencia artificial en ciencias sociales y humanidades.

Curso 4 · Oficio · ocho sesiones · híbrido

Prácticas

Guías de las prácticas y cuaderno de apoyo.

Cuaderno de apoyo con kappa, alfa, consultas y exportación del libro

Opcional. Todo el curso se puede hacer con la interfaz web de un modelo y una hoja de cálculo. Este cuaderno es para quien quiera calcular el acuerdo con exactitud, ejecutar consultas al corpus como las de un programa de análisis cualitativo, exportar el libro de códigos y, si lo desea, llamar a un modelo por programa dejando registro. Está pensado para que lo use alguien sin experiencia previa en Python, copiando bloque a bloque. Cada bloque dice antes qué hace y por qué.

Preparación

Instale Python 3.11 o superior. En una terminal:

pip install pandas

Solo el último bloque, la llamada opcional a un modelo, necesita además:

pip install openai python-dotenv

El formato de trabajo

Todo el cuaderno trabaja sobre dos archivos de texto separados por comas, que se pueden crear y editar en cualquier hoja de cálculo.

fragmentos.csv, un fragmento por fila:

id,fuente,texto
1,entrevista-03,"Yo le compro a la misma señora desde hace años."
2,entrevista-03,"Los sábados vengo más por ver gente que por comprar."

codificacion.csv, una fila por asignación de código, con quién lo asignó:

id,codigo,codificador
1,confianza en el vendedor,yo
1,confianza en el vendedor,modelo
2,encuentro vecinal,yo
2,encuentro vecinal,modelo

Un fragmento puede tener varias filas por codificador si la codificación es múltiple. Para la matriz de acuerdo de la semana 6 se usa un código por fragmento y codificador.

Semana 6. Kappa de Cohen

Kappa compara el acuerdo observado entre dos codificadores con el que se esperaría por azar dadas las distribuciones de cada uno. El bloque lee las asignaciones de dos codificadores, alinea los fragmentos que ambos codificaron, y calcula po, pe y kappa. Un código fuera del libro cuenta como categoría aparte, como pide la práctica.

import pandas as pd

cod = pd.read_csv("codificacion.csv")

def tabla_dos(cod, a="yo", b="modelo"):
    """Devuelve un DataFrame con una fila por fragmento y una columna por codificador."""
    t = cod.pivot_table(index="id", columns="codificador", values="codigo", aggfunc="first")
    t = t.dropna(subset=[a, b])
    return t[[a, b]]

def kappa_cohen(t):
    a, b = t.columns
    n = len(t)
    po = (t[a] == t[b]).mean()
    cats = sorted(set(t[a]) | set(t[b]))
    pe = sum((t[a] == c).sum() * (t[b] == c).sum() for c in cats) / (n * n)
    k = (po - pe) / (1 - pe) if pe != 1 else float("nan")
    return {"n": n, "categorias": len(cats), "po": round(po, 3), "pe": round(pe, 3), "kappa": round(k, 3)}

t = tabla_dos(cod)
print(kappa_cohen(t))

Para reportar con coma decimal, como pide el curso:

r = kappa_cohen(t)
print(f"Acuerdo simple: {r['po']*100:.1f} %".replace(".", ","))
print(f"kappa = ({r['po']} - {r['pe']}) / (1 - {r['pe']}) = {r['kappa']}".replace(".", ","))

Semana 6. Alfa nominal de Krippendorff

Alfa generaliza la idea a cualquier número de codificadores y admite fragmentos que no todos codificaron. Para datos nominales, el bloque calcula el desacuerdo observado y el esperado a partir de la matriz de coincidencias, siguiendo la definición de Krippendorff (2018, capítulo sobre fiabilidad). Con dos codificadores y datos completos, el valor suele quedar cerca de kappa.

import pandas as pd
from collections import Counter

def alfa_krippendorff_nominal(cod):
    """cod: DataFrame con columnas id, codigo, codificador. Un código por fragmento y codificador."""
    t = cod.pivot_table(index="id", columns="codificador", values="codigo", aggfunc="first")
    # matriz de coincidencias: pares ordenados de valores dentro de cada unidad
    coinc = Counter()
    n_total = 0
    for _, fila in t.iterrows():
        vals = [v for v in fila.tolist() if pd.notna(v)]
        m = len(vals)
        if m < 2:
            continue
        n_total += m
        for i in range(m):
            for j in range(m):
                if i != j:
                    coinc[(vals[i], vals[j])] += 1 / (m - 1)
    if n_total == 0:
        return float("nan")
    n_c = Counter()
    for (c, k), v in coinc.items():
        n_c[c] += v
    do = sum(v for (c, k), v in coinc.items() if c != k) / n_total
    de = sum(n_c[c] * n_c[k] for c in n_c for k in n_c if c != k) / (n_total * (n_total - 1))
    return round(1 - do / de, 3) if de else float("nan")

cod = pd.read_csv("codificacion.csv")
print("alfa =", alfa_krippendorff_nominal(cod))

Prueba del bloque sobre el caso de doce fragmentos del simulador de la semana 3: con los códigos del modelo y una codificación de referencia (fragmentos 3, 4, 8, 9 y 12 en desacuerdo), kappa da 0,54 y alfa queda en el mismo orden. Si su resultado difiere mucho entre las dos medidas con datos completos, revise que no haya fragmentos duplicados en el archivo.

Para un tercer codificador basta añadir sus filas a codificacion.csv con otro valor en la columna codificador; alfa lo incorpora sin cambios, y kappa se calcula por pares.

Semana 5. Consultas al corpus como las de un CAQDAS

Las consultas de recuperación, coocurrencia y matriz por fuente son operaciones sobre el registro: dado el libro y las asignaciones, el resultado es único. Estos bloques las ejecutan sobre los dos archivos. Lo que devuelven es lo que se le pasa al modelo, numerado, en el protocolo de la semana 5.

import pandas as pd

frag = pd.read_csv("fragmentos.csv")
cod = pd.read_csv("codificacion.csv")
mias = cod[cod["codificador"] == "yo"]

def recuperar(codigo):
    """Todos los fragmentos con un código, con su texto."""
    ids = mias.loc[mias["codigo"] == codigo, "id"].unique()
    return frag[frag["id"].isin(ids)]

def coocurrencia(c1, c2):
    """Fragmentos que tienen los dos códigos a la vez."""
    a = set(mias.loc[mias["codigo"] == c1, "id"])
    b = set(mias.loc[mias["codigo"] == c2, "id"])
    return frag[frag["id"].isin(a & b)]

def matriz_por_fuente():
    """Cuántos fragmentos de cada fuente llevan cada código."""
    m = mias.merge(frag[["id", "fuente"]], on="id")
    return pd.crosstab(m["codigo"], m["fuente"])

print(recuperar("confianza en el vendedor"))
print(coocurrencia("confianza en el vendedor", "encuentro vecinal"))
print(matriz_por_fuente())

Para pasarle al modelo solo los fragmentos recuperados, numerados:

def para_el_modelo(df):
    return "\n".join(f"[{r.id}] {r.texto}" for r in df.itertuples())

print(para_el_modelo(coocurrencia("confianza en el vendedor", "encuentro vecinal")))

Semana 7. Curva de códigos nuevos por lote

La curva cuenta, para lotes sucesivos de fragmentos, cuántos códigos aparecen por primera vez. Es un instrumento de registro: cuenta códigos, no mide la saturación de propiedades. El bloque la calcula con lotes de diez.

import pandas as pd

cod = pd.read_csv("codificacion.csv")
mias = cod[cod["codificador"] == "yo"].sort_values("id")
vistos, curva = set(), []
for inicio in range(mias["id"].min(), mias["id"].max() + 1, 10):
    lote = mias[(mias["id"] >= inicio) & (mias["id"] < inicio + 10)]
    nuevos = sorted(set(lote["codigo"]) - vistos)
    vistos |= set(nuevos)
    curva.append({"lote": f"{inicio}-{inicio+9}", "nuevos": len(nuevos), "acumulados": len(vistos), "nombres": "; ".join(nuevos)})
print(pd.DataFrame(curva).to_string(index=False))

Semana 8. Exportar el libro de códigos

El libro se mantiene en libro.csv con las columnas de la plantilla, y el bloque lo convierte en la tabla markdown de plantillas/libro-de-codigos.md, con los fragmentos de anclaje tomados del archivo de codificación.

codigo,definicion,incluye,excluye,origen,decision,criterio,responde
confianza en el vendedor,"Relación sostenida ...","compras repetidas ...","confianza del vendedor al comprador ...",modelo,corregí,"la definición era unidireccional",yo
import pandas as pd

libro = pd.read_csv("libro.csv")
cod = pd.read_csv("codificacion.csv")
mias = cod[cod["codificador"] == "yo"]
anclaje = mias.groupby("codigo")["id"].apply(lambda s: ", ".join(str(i) for i in sorted(s)))
libro["fragmentos de anclaje"] = libro["codigo"].map(anclaje).fillna("")
cols = ["codigo", "definicion", "incluye", "excluye", "fragmentos de anclaje", "origen", "decision", "criterio", "responde"]
with open("libro-de-codigos.md", "w", encoding="utf-8") as f:
    f.write("| " + " | ".join(cols) + " |\n|" + "---|" * len(cols) + "\n")
    for r in libro[cols].itertuples(index=False):
        f.write("| " + " | ".join(str(x).replace("|", "/") for x in r) + " |\n")
print("libro-de-codigos.md escrito")

Las cifras de la sección 4 de la plantilla salen de:

print(libro.groupby(["origen", "decision"]).size())

Opcional. Llamar a un modelo por programa con registro

Para quien prefiera no usar la interfaz web. La biblioteca openai sirve para cualquier proveedor compatible con su interfaz; cambie base_url según el caso. El curso no exige un proveedor y varios ofrecen cuota gratuita. La clave se guarda en un archivo .env que no se comparte ni se entrega.

API_KEY=su_clave
BASE_URL=https://api.openai.com/v1
MODEL=nombre_del_modelo
import os, json, datetime
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI(api_key=os.getenv("API_KEY"), base_url=os.getenv("BASE_URL"))
MODEL = os.getenv("MODEL")

def preguntar(prompt, registro="registro.jsonl"):
    """Envía un prompt, devuelve la respuesta y guarda ambos con fecha."""
    r = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
        temperature=0.2,
    )
    texto = r.choices[0].message.content
    with open(registro, "a", encoding="utf-8") as f:
        f.write(json.dumps({
            "fecha": datetime.datetime.now().isoformat(),
            "modelo": MODEL,
            "prompt": prompt,
            "respuesta": texto,
        }, ensure_ascii=False) + "\n")
    return texto

Aplicar el libro fijo a treinta fragmentos (práctica 6) y guardar la salida en el formato de codificacion.csv:

import pandas as pd

frag = pd.read_csv("fragmentos.csv")
libro_txt = open("libro.md", encoding="utf-8").read()
lote = frag[frag["id"].between(1, 30)]
prompt = f"""Este es un libro de códigos:
{libro_txt}

Codifica los siguientes fragmentos aplicando exactamente este libro. Un solo
código por fragmento. No propongas códigos nuevos: si ninguno cabe, escribe
"ninguno". Responde solo con líneas de la forma: id;codigo

{chr(10).join(f'[{r.id}] {r.texto}' for r in lote.itertuples())}"""
salida = preguntar(prompt)
filas = [l.split(";") for l in salida.strip().splitlines() if ";" in l]
pd.DataFrame([{"id": int(i.strip("[] ")), "codigo": c.strip(), "codificador": "modelo"} for i, c in filas]) \
  .to_csv("codificacion-modelo.csv", index=False)

Luego se concatena con las filas propias y se corre el bloque de kappa. Cada llamada queda en registro.jsonl, que es el anexo literal que piden las plantillas.

Nota sobre costos y privacidad

Use cuotas gratuitas y material sin datos personales de terceros. Si su proyecto incluye material sensible, haga las prácticas con una muestra anonimizada o con material público, y procese el material solo con servicios cuyas condiciones excluyan el uso de los datos para entrenamiento. Exterioridad y gobernanza de la IA en ciencias sociales trata estos problemas en detalle: qué derechos pesan sobre cada pieza del material (autoría, datos personales, material sensible, condiciones de las plataformas), qué uso es defendible y hasta dónde llega la anonimización; su primer producto es una matriz de riesgos y derechos del propio material.


Práctica 3. Codificación abierta asistida

Duración estimada: 4 horas, de las cuales 1 en la sesión sincrónica de la semana 3; se continúa en la semana 4. Requisitos: el corpus propio en un archivo de texto plano, un fragmento por párrafo, numerado, sin datos personales de terceros; acceso a un modelo de lenguaje de uso gratuito en su versión web (cualquiera sirve; el curso no exige uno en particular); la plantilla plantillas/registro-de-frontera-codificacion.md y la lista de verificación de la semana 2. No requiere programar. Para quien quiera, el cuaderno cuaderno-apoyo.md indica cómo hacer lo mismo por programa y cómo exportar el libro.

Objetivo

Producir el libro de códigos abierto del corpus propio con un modelo como codificador auxiliar, de modo que cada código tenga origen, anclaje, decisión y criterio, y que el estudiante pueda distinguir lo que habría visto solo de lo que el modelo le mostró.

Antes de la sesión

  1. Numere los fragmentos. Un fragmento es una unidad con sentido propio: una respuesta de entrevista, un párrafo de prensa, una descripción de imagen. Entre veinte y sesenta es el tamaño del curso.
  2. Revise que no haya nombres, direcciones ni datos que identifiquen a una persona. Si su material es sensible, trabaje con una muestra anonimizada o con material público; Exterioridad y gobernanza de la IA en ciencias sociales trata estos problemas en detalle: qué derechos pesan sobre cada pieza del material (autoría, datos personales, material sensible, condiciones de las plataformas), qué uso es defendible y hasta dónde llega la anonimización; su primer producto es una matriz de riesgos y derechos del propio material.
  3. Tenga a mano la lista de verificación de la semana 2, en especial la regla de generación de su tradición. Lo que le va a pedir al modelo es esa regla.

Ronda 0. Codificar a mano antes de la sesión

Codifique los fragmentos 1 a 5 sin el modelo. Para cada uno, haga microanálisis breve (qué sentidos abre cada término que le llame la atención) y proponga uno o dos códigos, con preferencia por códigos in vivo. Guarde esto en un archivo aparte y no lo mire durante la ronda 1. Este paso es el que permite, después, saber qué habría visto usted solo.

Ronda 1. El modelo propone

Instrucción base para teoría fundamentada; adáptela si su tradición es otra, conservando las tres exigencias: anclaje literal, sin vocabulario de la literatura, alternativas descartables.

Voy a darte fragmentos numerados de [tipo de material] sobre [tema, en una
frase]. Para cada fragmento:
1. Haz microanálisis: enumera dos o tres sentidos posibles de los términos
   que más pesan en el fragmento, sin elegir todavía.
2. Propón uno o dos códigos. Usa las palabras del hablante cuando sea posible
   (código in vivo). No uses categorías de la literatura en ciencias sociales
   ni términos abstractos que no estén en el fragmento.
3. Para cada código, cita el fragmento literal que lo sostiene, entre
   comillas, y di qué término del fragmento da origen al nombre.
4. Si un código ya lo usaste en un fragmento anterior, dilo y explica en qué
   se parece el caso nuevo.
Trabaja fragmento por fragmento y numera cada decisión. No agrupes todavía.

Fragmentos:
[1] ...
[2] ...

Empiece con los fragmentos 1 a 5. Copie la salida completa a un anexo con fecha.

Ronda 2. El investigador decide

Compare la salida del modelo con su codificación a mano, fragmento a fragmento, y llene el registro de frontera por código. Preguntas de apoyo:

  • ¿El fragmento que el modelo cita está literalmente en el corpus? Verifíquelo con el archivo abierto; el modelo compone citas a veces.
  • ¿El nombre sale de un término del fragmento o viene de fuera? Si viene de fuera, ¿la tradición declarada lo permite?
  • Si acepto, ¿puedo decir por qué sin repetir lo que dijo el modelo?
  • Si corrijo, ¿qué sé yo que el modelo no sabía: del tema, del contexto, de quien habla?
  • Si rechazo, ¿es porque está mal o porque no puedo verificarlo? Anote cuál.
  • ¿Hay un código que yo puse y el modelo no? Entra al libro con origen "yo".

Para cada código la decisión es una de cuatro: aceptar, corregir, rechazar, reformular como pregunta para un memo. "Dejar pasar" no existe en el registro.

Ronda 3. Continuar con el corpus

Envíe al modelo los fragmentos siguientes en lotes de diez, con la misma instrucción y, al principio de cada lote, la lista de códigos ya aceptados con sus definiciones, para que el modelo compare contra ella. Observe si el modelo reutiliza los códigos aceptados o vuelve a proponer los que usted rechazó con otro nombre. Registre cada lote igual que el primero.

Ronda 4. Primer memo

Al terminar, elija el código que más dudas le produjo y escriba un memo con plantillas/memo-analitico.md, con la sección de procedencia completa. Si el nombre vino del modelo, ejecute la prueba de anclaje: pídale el término del corpus del que sale y verifíquelo.

Para el código "[nombre]", que propusiste en los fragmentos [números], indica
qué término o expresión literal de esos fragmentos da origen al nombre. Si el
nombre no sale de ningún término literal, dilo así, sin inventar una cita.

Después de la sesión

  1. Complete el registro para todo el corpus.
  2. Llene "los casos difíciles", "aceptaciones sin criterio" e "instrucciones que el modelo ignoró". No omita ninguna aceptación sin criterio: anotarlas es parte de la nota.
  3. Llene el balance: qué vio el modelo que usted no, qué vio usted que el modelo no, qué forzó el modelo y con qué señal lo notó.
  4. Arme el libro de códigos abierto con plantillas/libro-de-codigos.md, sección 2, y entréguelo en la semana 4 con el registro y el memo.

Qué se evalúa

La precisión del registro, no la elegancia del libro. Un libro con doce códigos in vivo bien anclados, tres rechazos razonados y una aceptación sin criterio anotada vale más que uno con treinta categorías que nadie puso a prueba.

Variante con material público

Quien no tenga corpus propio listo puede hacer la práctica con los doce fragmentos del simulador de la semana 3 (apps/simulador-codificacion.html), que son inventados y no tienen datos personales, y con textos de dominio público (cartas, crónicas, testimonios publicados antes de 1930). El registro se hace igual.

Variante para grupos

Dos estudiantes codifican los mismos veinte fragmentos con el mismo modelo y la misma instrucción, y comparan registros. Las filas en que uno aceptó y el otro rechazó son el material de discusión de la sesión, y anticipan la matriz de acuerdo de la semana 6.


Práctica 6. Matriz de acuerdo y análisis de desacuerdos

Duración estimada: 3 horas, de las cuales 1 en la sesión sincrónica de la semana 6. Requisitos: el libro de códigos abierto fijado en la semana 4 (no se modifica durante la práctica); treinta fragmentos del corpus, numerados; acceso a un modelo de lenguaje en su versión web; la plantilla plantillas/matriz-de-acuerdo.md. Para el cálculo: la mini app de la semana 3 calcula kappa para sus doce fragmentos fijos; para los treinta propios, el cuaderno cuaderno-apoyo.md calcula kappa y alfa con dos bloques copiables, o se calcula a mano con la tabla de la sección 3.

Objetivo

Medir el acuerdo entre el investigador y el modelo al aplicar un mismo libro de códigos a los mismos fragmentos, interpretarlo como consistencia y no como validez, y convertir cada desacuerdo en una decisión sobre el libro.

Codificación a ciegas antes de la sesión

  1. Elija treinta fragmentos que no haya codificado con el modelo en la práctica 3, o, si todo el corpus pasó ya por el modelo, treinta fragmentos elegidos al azar con un intervalo (cada segundo o tercer fragmento).
  2. Codifique los treinta a mano con el libro fijo, un código por fragmento (el que predomina). Si el fragmento no cabe en ningún código, anote "ninguno" y la razón. Guarde el resultado con fecha.
  3. No le pida nada al modelo antes de terminar este paso. La matriz mide acuerdo solo si usted codificó sin haber visto la salida del modelo.

En la sesión

Paso 1. El modelo aplica el libro

Este es un libro de códigos con [n] códigos. Para cada uno, nombre,
definición, qué incluye y qué excluye:
[pegue el libro completo]

Codifica los siguientes fragmentos numerados aplicando exactamente este libro.
Asigna a cada fragmento un solo código, el que predomina. No propongas
códigos nuevos: si ningún código cabe, escribe "ninguno" y explica por qué.
Responde con una tabla: número de fragmento, código, frase del fragmento que
justifica la elección.

Fragmentos:
[1] ...

Copie la salida al anexo con fecha. Si el modelo propuso un código nuevo a pesar de la instrucción, anótelo: cuenta como categoría aparte en el cálculo y como señal de forzamiento en el análisis.

Paso 2. La matriz y el cálculo

Llene la sección 1 de la plantilla con los dos códigos por fragmento. Calcule:

  • Acuerdo simple: fragmentos en acuerdo dividido por treinta.
  • Kappa de Cohen: con el cuaderno, o a mano con la tabla de abajo.
  • Alfa de Krippendorff nominal: con el cuaderno.

Cálculo a mano de kappa para quien no use el cuaderno. Construya la tabla de marginales:

Código Veces que lo usé yo (a) Veces que lo usó el modelo (b) a × b
…
Suma 30 30 S

El acuerdo esperado por azar es pe = S / (30 × 30). El acuerdo observado po es el acuerdo simple. Kappa = (po - pe) / (1 - pe). Reporte po, pe y kappa con dos decimales y coma decimal.

Paso 3. La explicación del modelo

Para cada desacuerdo, pídale al modelo que explique su elección, sin decirle cuál fue la suya:

En el fragmento [n] asignaste el código "[X]". Explica, citando el fragmento,
por qué ese y no "[Y]" según las definiciones del libro.

Copie la explicación literal a la columna correspondiente. Léala con cuidado: a veces la explicación muestra que la definición de Y es estrecha; a veces muestra que el modelo aplicó la palabra y no el sentido.

Paso 4. Leer los desacuerdos uno a uno

Clasifique cada desacuerdo con los tipos de la plantilla (A a G), decida quién tiene razón y con qué criterio, y derive una decisión sobre el libro: nueva regla, definición ampliada, fusión, ejemplo negativo, anotación de forzamiento, corrección propia. La decisión no se aplica al libro durante la práctica; se aplica después, y queda en la sección 5.

Paso 5. Acuerdos sospechosos

Repase los fragmentos en acuerdo y busque al menos uno en que la coincidencia pueda deberse a que usted y el modelo comparten la literatura y no a lo que el fragmento dice. Si no encuentra ninguno, dígalo y diga por qué lo cree.

Después de la sesión

  1. Complete las secciones 5, 6 y 7 de la plantilla.
  2. Escriba la frase final con los valores obtenidos: qué mide el acuerdo y qué no mide.
  3. Aplique al libro de códigos los cambios de la sección 5 y anótelos en el registro de frontera con origen "matriz de acuerdo".
  4. Entregue la matriz con el anexo de prompts y salidas literales.

Qué se evalúa

El orden del procedimiento (a ciegas, libro fijo, misma instrucción), el cálculo con su interpretación, y sobre todo el análisis de desacuerdos: que cada uno produzca una decisión. Un kappa de 0,45 bien leído vale más que uno de 0,85 reportado como prueba de validez.

Variante con un tercer codificador

Si dos estudiantes comparten corpus, o si un colega que conoce el campo acepta codificar los treinta fragmentos, la matriz se hace a tres: investigador, modelo y tercero. El cuaderno calcula alfa para cualquier número de codificadores. Los fragmentos en que el modelo coincide con uno de los dos humanos y no con el otro son el mejor material para discutir qué es triangulación y qué no.