sábado, 10 de septiembre de 2022

Código (Python)

 ¿Cómo funciona t-tester? Veamos el código de este programa con las explicaciones detalladas.

1) importamos librerías tkinter para la interfaz (en windows), csv para trabajar con archivos de datos (valores separados por coma), matplotlib para interpretación gráfica de datos. 

from tkinter import *
import tkinter as tk
import csv
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt

2) la ventana principal de la interfaz 

raiz = Tk()
raiz.title("Tester -tt BETA 1.1")
raiz.resizable(0,0)
raiz.iconbitmap("logoico.ico")
raiz.geometry("800x400")
raiz.config(bg="white")

3) campo azul (del receptor de datos)

miframe = Frame()# creamos un frame. Pero lo tenemos que meter dentro de la raiz.
miframe.pack(side = "left") # empaquetarlo en raiz
# para rellenado ver Curso Python. Interfaces gráficas II. Vídeo 43
# https://youtu.be/M80CzDC1Crc
miframe.config(bg="lightcyan2")
miframe.config(width="200", height = "400")
miframe.config(bd=1)
miframe.config(relief="ridge")

4) receptor de datos (del campo azul)

texto1 = Label(miframe, text ="TEST PRINCIPAL", bd = 0, bg = "lightcyan2", fg = "grey50", font=("calibri",12))
texto1.place(x = 10, y = 160)
opcion1 = IntVar() # Como StrinVar pero en entero
R1 = Radiobutton(miframe, text="NEGATIVO", value=0, var=opcion1, bg = "lightcyan2")
R2 = Radiobutton(miframe, text="POSITIVO", value=1, var=opcion1, bg = "lightcyan2")
R1.place(x = 10, y = 180)
R2.place(x = 90, y = 180)

texto2 = Label(miframe, text ="NUEVO TEST", bd = 0, bg = "lightcyan2", fg = "red", font=("calibri",12))
texto2.place(x = 10, y = 220)
opcion2 = IntVar() # Como StrinVar pero en entero
R3 = Radiobutton(miframe, text="NEGATIVO", value=0, var=opcion2, bg = "lightcyan2")
R4 = Radiobutton(miframe, text="POSITIVO", value=1, var=opcion2, bg = "lightcyan2")
R3.place(x = 10, y = 240)
R4.place(x = 90, y = 240)

5) motor principal del algoritmo (se refiere a la creación y el relleno del fichero)

# MOTOR PRINCIPAL DEL ALGORITMO
#declaración de variables enteras
p1 = int();
p2 = int();

def codigoknopa():
    p1 = opcion1.get();
    p2 = opcion2.get();
    #declaramos copias de variables para trabajar a parte sin perder los datos
    n1 = p1
    n2 = p2
    cad3 = [int(n1),int(n2)] # para cada paciente dos datos. Test bueno 
    # en primera coordenada y test a probar en segunda
    # escribimos en el archivo nuevos datos
   fichero2 = open("listanueva2.csv", "a")
    caract = str(cad3)
    carnuvo = caract.replace("[", "")
    carnuvo = carnuvo.replace("]", "")
    fichero2.write(carnuvo)
    fichero2.write("\n")
    fichero2.close()
    # tras escritura en el archivo de nuevos datos este se guarda y se cierra
    # ahora abrimos este y lo pasamos a una cadena en el programa
    list3 = [];
    with open('listanueva2.csv', newline='') as File: 
        reader = csv.reader(File)
        for row in reader:
            for c in range(2):
                row[c] = int(row[c])
            list3.append(row)

6) componente matemático (seguimos en la misma línea de la función "codigocnopa")

    # creamos valores de verdaderos y falsos positivos y negativos 
    # !!! EN ESTE ALGORITMO NE SE USA REGLA BAYES. SE CALCULAN LOS
    # DATOS DE LA LISTA Y finalmente solo hay que dividir entre tamaño de 
    # la muestra para obtener la proporción (regla Laplace)

    vn = 0; # verdadero negativo
    vp = 0; # verdadero positivo
    fn = 0; # falso negativo
    fp = 0; # falso positivo
    p = 0;# los positivos test principal
    n = 0;# los negativos test principal
    m = len(list3); # tamaño de la muestra 

    for i in range(m):
        if list3[i][0] == 0: # analiza 1a columna [0] y se da negativo
            n = n + 1; # PERSONA SANA
            if list3[i][1] ==0: # segunda columna [1] los sintomas negativos
                vn = vn + 1; # VERDADERO NEGATIVO
            else:# si en la segunda columna se da el caso positivo
                fp = fp + 1; # es un negativo pero con sintomas. Falso positivo
        else :# es un caso positivo, la primera columna [0] es 1
            p = p + 1; # cálculo de los positivos reales
            if list3[i][1] ==1: # con síntomas positivos
                vp = vp + 1; # test es un verdadero positivo
            else:
                fn = fn + 1; # es un falso negativo

    # la proporci-on de los mal diagnosticados respecto la muestra total
    prmd = (fn+fp)/m
    # sencibilidad delos test. Capacidad de obtener el resultado POSITIVO en el caso 
    # si el test real es POSITIVO p(P/E)

    if p!= 0:
        sens = round(vp/p,3)
    else:
        sens = 0
    # especificidad vn/n p(N/S)
    if n!=0:
        espe = round(vn/n,3)
    else:
        espe = 0
    # proporcion positivos de la muestra
    posmues = round(p/m,2)
    cad4 = float(prmd) # dicha proporcion
    fichero3 = open("listanueva3.csv", "a")
    caract3 = str(cad4)
    carnuvo3 = caract3.replace("[", "")
    carnuvo3 = carnuvo3.replace("]", "")
    fichero3.write(carnuvo3)
    fichero3.write("\n")
    fichero3.close()

    # pasamos el archivo a una cadena en el programa

    list4 = [];
    with open('listanueva3.csv', newline='') as File: 
        reader = csv.reader(File)
        for row in reader: 
            list4.append(row)

    vn = round(vn/m,3);
    vp = round(vp/m,3);
    fn = round(fn/m,3);
    fp = round(fp/m,3);

    ####!!! error conceptual??? si hacemos probabilidad de un verdadero positivo 
    #### nos puede salir cosa rara. Ya que hay muy poco positivos, la intersección
    #### es mas pequeña aun y comparando con el total tiene valor muy pequeño
    #### esto no da info buena. 

7) interpretación de datos por la interfaz (seguimos en la misma línea de la función "cofigocnopa")

    texto8 = Label(miframe2, text ="composición de casos", bd = 0, bg = "white", fg = "grey50", font=        ("calibri",12))
    texto8.place(x = 10, y = 80)
    fig1 = plt.figure(figsize=(4,4), dpi = 100)
    fig1.set_size_inches(2,2)
    valores = [fn*m,vn*m,fp*m,vp*m]
    nombres =["FN","VN","FP","VP"]
    plt.pie(valores, explode = (0.04,0.04,0.04,0.04), labels = nombres, colors =                                ["Brown","Darkseagreen","IndianRed","Mediumseagreen"])
    plt.axis("equal")
    canvasbar = FigureCanvasTkAgg(fig1, master = miframe2)
    canvasbar.draw()
    canvasbar.get_tk_widget().place(x = 10, y = 100)    

    # los datos obtenidos del algoritmo

    texto6 = Label(miframe2, text =str(fn), bd = 0, bg = "white", fg = "red", font=("calibri",12))
    texto6.place(x = 110, y = 10)
    texto7 = Label(miframe2, text =str(fp), bd = 0, bg = "white", fg = "red", font=("calibri",12))
    texto7.place(x = 110, y = 30)
    texto12 = Label(miframe2, text =str(sens), bd = 0, bg = "white", fg = "red", font=("calibri",12))
    texto12.place(x = 340, y = 10)
    texto13 = Label(miframe2, text =str(espe), bd = 0, bg = "white", fg = "red", font=("calibri",12))
    texto13.place(x = 340, y = 30)
    texto14 = Label(miframe2, text =str(posmues), bd = 0, bg = "white", fg = "red", font=("calibri",12))
    texto14.place(x = 490, y = 10)

    ## desarrollo de mal diagnosticado histórico

    texto9 = Label(miframe2, text ="desarrollo MD histórico", bd = 0, bg = "white", fg = "grey50",             font=  ("calibri",12))
    texto9.place(x = 250, y = 80) 
    m = len(list4); # tamaño de la muestra 
    x = [0]*(m)
    y = [0]*(m)
    for i in range(m):
        x[i] = i
        y[i] = float(list4[i][0])
    fig2 = plt.figure(figsize=(6,4), dpi = 50)
    a = fig2.add_subplot()
    a.plot(x,y, marker = "")# entre comillas puede venir . o
    #    a.legend("")
    a.grid()
    # Creating Canvas
    canv = FigureCanvasTkAgg(fig2, master = miframe2)
    canv.draw()
    get_widz = canv.get_tk_widget().place(x = 220, y= 100)


8) interfaz del botón "añadir" con referencia al comando "codigocnopa" 

knopa = Button(miframe, text = "añadir", command= codigoknopa, ).place(x = 70, y = 270)
miframe2 = Frame()# creamos un frame. Pero lo tenemos que meter dentro de la raiz.
miframe2.pack(side = "left") # empaquetarlo en raiz
# para rellenado ver Curso Python. Interfaces gráficas II. Vídeo 43
# https://youtu.be/M80CzDC1Crc
miframe2.config(bg="white")
miframe2.config(width="600", height = "400")
miframe2.config(bd=1)
miframe2.config(relief="ridge")
texto3 = Label(miframe2, text ="proporción F-", bd = 0, bg = "white", fg = "grey50", font=("calibri",12))
texto3.place(x = 10, y = 10)
texto4 = Label(miframe2, text ="proporción F+", bd = 0, bg = "white", fg = "grey50", font=("calibri",12))
texto4.place(x = 10, y = 30)
texto10 = Label(miframe2, text ="Sensibilidad p(P/E)", bd = 0, bg = "white", fg = "grey50", font=("calibri",12))
texto10.place(x = 190, y = 10)
texto11 = Label(miframe2, text ="Especificidad p(N/S)", bd = 0, bg = "white", fg = "grey50", font=("calibri",12))
texto11.place(x = 190, y = 30)
texto15 = Label(miframe2, text ="+ Muestra", bd = 0, bg = "white", fg = "grey50", font=("calibri",12))
texto15.place(x = 400, y = 10)

9) botón de información 

### BUTTOB INFORMACIÓN
def codigoknopa2():
    info = open("info.txt",'r')
    mensaje = info.read()
    info.close()
    def clear()
        text_widget.destroy()
        knopa3.destroy()
    text_widget = tk.Text(miframe2)
    # Pack it into our tkinter application
    text_widget.place(x = 10, y = 10, width = 550, height = 320)
    # Insert text into the text widget
    # tk.END specifies insertion after the last character in our buffe
    text_widget.insert(tk.END, mensaje)
    knopa3 = Button(miframe2, text = "cerrar", command= clear,  borderwidth=0)
    knopa3.place(x = 10, y = 365)
knopa2 = Button(miframe, text = "info", command= codigoknopa2,  borderwidth=0).place(x = 10, y = 365)

10) botón de eliminación de todos los datos guardados

def codigoknopa4():
    mens = messagebox.askokcancel(message="Se van a borrar todos los datos acomulados del experimento. Antes de continuar se aconceja hacer copias de archivos listanueva2 y listanueva3 en la carpeta del programa ¿Desea continuar?", title="¡Advertencia!")
    if mens == True:   
        d1 = open("listanueva2.csv",'w')
        d2 = open("listanueva3.csv",'w')
        d1.write('')
        d1.close()
        d2.write('')
        d2.close()

knopa4 = Button(miframe, text = "Borrar", command= codigoknopa4,  borderwidth=0).place(x = 40, y = 365)

11) boocle para mantener la venta de la interfaz abierta

raiz.mainloop()# un boocle infinito que mantiene la ventana

Componente matemático

 Ahora exponemos el contenido más importante del programa. Las predicciones se basan en teoremas probabilísticos. 

F+ (Falso positivo)

Queremos decidir si nuestro test a probar es bueno o malo. Una manera de hacerlo es ver la proporción de falsos positivos. Es decir, ¿Cuál es la probabilidad de dar positivo en nuevo test si el resultado de test principal es negativo? 

En lenguaje de probabilidad esto se escribe como p(P/N) o sea, probabilidad de dar positivo si realmente no lo es. Hay una fórmula de calcularlo: probabilidad condicionada


 Pero si trabajamos con datos explícitos podemos calcular este valor como la proporción de los positivos del test a probar entre todos los negativos del test principal. 



El resto de valores se calculan de mismo modo. Falta mencionar una vez más que estos datos son mas fiables si hacemos más repeticiones de los experimentos. ley de los grandes números 

sábado, 18 de septiembre de 2021

Ejemplo de aplicacion. El Vidente y la Moneda.

Ejemplo 1. 

En este ejemplo vamos a intentar predecir en que lado cae una moneda lanzada. Como test a probar vamos a usar nuestra intuición y como test principal la simple observación (es un test válido 100%). Como positivo vamos a tener cruz y como negativo cara. 

Para hacer un experimento objetivo necesitamos primero decidir (intuir), o sea aplicar el test a evaluar y luego aplicar al test principal. Se puede añadir los datos en tiempo real o se puede redactar los resultados en un papel y luego añadir estos datos desde papel en el programa. 

Como el resultado obtenemos una lista de datos siguiente

1, 0
1, 0
1, 0
1, 0
0, 0
0, 1
0, 0
...........etc

En la primera columna es el resultado del test real y en la segunda es la predicción.

Es interesante observar los resultados en tiempo real al añadir los datos (al aumentar la muestra). Para ello pueden ver este video.


Finalmente el programa presenta siguientes resultados. 


Los falsos negativos y los falsos positivos en suma dan casi 0.5 o sea la mitad de casos. Cosa de esperar obviamente. La sensibilidad, o sea, la probabilidad de que intuimos caso + en el caso que es + realmente está entorno a 0.5 igual que Especificidad. Capacidad de predecir que es cara en el caso si resultado real es cara. Ahora un dato importante. El tamaño de la muestra es de 62 y en experimento global se ha obtenido bastante mas + que C. En ideal para una muestra mas grande este valor debe aproximarse a 0.5.

Composición de casos. En esta gráfica se observa que los casos falsos tienen casi el mismo área que los casos acertados. (rojos contra verdes) Sin embargo se observa una grán desproporción entre positivos reales (VP+FN)  y negativos reales (FP+VN). Area de los primeros es mucho mas grande que de los segundos. Este dato tiene que ver con el dato anterior (+ Muestra) y puede indicar una de las dos o dos cosas a la vez: 

1) La muestra es demasiado pequeña. Con una muestra mas grande la probabilidad de obtener + debe ser igual a la de C.

2) La muestra no se ha tomado de forma aleatoria. Es decir los datos elaborados están influidos por algún factor. 

Aunque el test principal puede ser no lo suficiente bueno esto no afecta a capacidad de evaluar el otro testn respecto ESTE test. En la última gráfica al principio vemos bastantes fallos caometidos (incapacidad de predecir valor verdadero) pero finalmente se van obteniendo suficientes casos acertados para bajar esta gráfica (que es el promedio de casos falsos) casi hasta el valor 0.5. Aun así con ma nuestra de 62 no se alcanza este valor y se queda por encima. En caso ideal (en muestra infinita) la gráfica debe aproximarse a esta asíntota de 0.5.