Por:
Yaneth Gil | Offray Luna
Proyecto Piamed fase 2
Narrativas de datos sobre el uso racional de medicamentos: Construcción de la línea base por Yaneth Gil y Offray Luna se distribuye bajo una Licencia Creative Commons Atribución-CompartirIgual 4.0 Internacional.
Basada en una obra en http://beta.piamed.org/repos.fossil/uso-racional/home.
El siguiente documento es una narrativa de datos para construir un argumento que ayude al uso racional de medicamentos específicamente ARAS e IECAS (por las siglas para Antagonistas del Receptor de Angiotensina y Inhibidores de la Enzima Convertidora de Angiotensina, respectivamente). En esta se vinculan tanto texto como gráficas, algoritmos y consultas a bases de datos, para construir una línea base que nos permita resolver varias preguntas referidas a los hábitos de prescripción actuales.
Debido al creciente auge de las narrativa de datos pero su relativo desconocimiento en la forma como se producen y publican, creemos conveniente que este documento tenga un caracter tutorial y por tanto se muestran los pasos que progresivamente permiten la construcción de la narrativa desde elementos más básicos a otros más automatizados y complejos, para finalmente pasar a elementos más interpretativos y analíticos. Consecuentemente, hemos divido el documento en varias partes:
Hasta donde los autores tienen conocimiento, este es un documento nóvel en Iberoamérica, en el sentido de que, si bien se alinea con la tendencia mundial actual de construir narrativas de datos y liberar información para una mejor comprensión y gestión compartida de un mundo complejo, lo hace dentro del marco del proyecto Piamed, que se propuso la formulación y ejecución de un proyecto de innovación abierta y comunitaria de información para el acceso a medicamentos, y desde allí se ocupó de las labores administrativas, legales, tecnológicas, académicas, que permiten la construcción del presente documento, como uno de los 4 subproyectos claves del mismo. El proyecto realizó la recolección y curaduría de la información, la articulación a un marco legal que permite su liberación en el contexto colombiano dentro de una estrategia de Gobierno en línea y Open Data, la disposición de infraestructura tecnológica de software libre y de código abierto para su tratamiento, auditoría y reproducción, que integra en un único documento interactivo, diferentes actividades asociadas a la exploración e interpretación de los datos: como son la consulta de datos, visualización, exportación en versión pdf y publicación en línea. El documento y los datos que lo soportan ha sido liberado con una licencia abierta, de manera que permita a otros lectores, asumir también el papel de auditores y autores y formularse sus propios cuestionamientos y descubrimientos frente a la información.
Palabras clave: Narrativas de datos, uso racional de medicamentos, innovación abierta y comunitaria, Datos Abiertos, Gobierno en línea, Gobierno Abierto, Ciencia Abierta, Ciencia Reproducible.
Supondremos que el lector ya tiene instalado el IPython Notebook, que es la herramienta con la que haremos la narrativa y nos servira para integrar otras, que iremos explicando en la medida en que avancemos en el documento. También supodremos que está trabajando en un entorno Unix como Mac o Gnu/Linux (Ubuntu, Debian, Arch), pues, si bien el documento puede ser leído desde cualquier navegador y existe soporte para las herramientas en Windows, la instalación y uso es más fluido en los anteriores sistemas operativos.
Empezaremos por agregar el sorpote para bases de datos usando el módulo IPython-SQL. Desde la consola de comandos ejecutamos:
sudo pip install ipython-sql
Para probar que la instalación ha funcionado adecuadamente haremos una consulta pequeña en una base de datos minimalista que ya viene integrada con python (y por tanto con IPython), llamada sqlite, siguiendo el ejemplo de acá.
# cargamos la extensión de sql
%load_ext sql
# Nos conectamos a una base de datos en sqlite
%sql sqlite://
# Ahora creamos una tabla de pruebas pequeña
%sql \
CREATE TABLE escritor (nombres, apellidos, muerte); \
INSERT INTO escritor VALUES ('Gabriel', 'García Marquez', 2014); \
INSERT INTO escritor VALUES ('Bertold', 'Bretch', 1956);
Vamos a ver la tabla creda
%sql select * from escritor
Ahora que sabemos que el soporte para bases de datos funciona, haremos ya las consultas y conexiones a una base de datos preexistente en MySQL, que contiene la información curada y anonimizada de prescripción en 7 hospitales de Cundinamarca.
# Importamos los módulos de soporte para bases a datos desde Pandas
import pandas.io.sql as pdsql
import MySQLdb
# Importamos dataframe y pandas que nos permitirá trabajar con estructuras matriciales de datos
from pandas import DataFrame
import pandas as pd
# Importamos el soporte de graficación
from matplotlib import pyplot
# colocamos las gráficas dentro del notebook
%matplotlib inline
# Importamos el soporte para operaciones numéricas y arreglos vía numpy
import numpy as np
# Definimos el total de hospitales para este levantamiento
# de información, que es una constante
totalHospitales = 7
Si bien se espera liberar la información dentro de las estrategías de gobierno en línea y open data, por lo pronto los datos de ingreso a la base de datos ha sido almacenada en un archivo separado, de modo que sólo quienes tienen acceso a dicho archivo pueden hacer las consultas y acceder a la información. En la siguiente línea abrirmos ese archivo y lo evaluamos de modo tal que todos los datos queden almacenados en el diccionario de datos llamado datosIngreso
.
datosIngreso = eval(open("ingresoDb.py", "r").read())
# Establecemos la conexión a la base de datos
conexion = MySQLdb.connect(host = datosIngreso["host"],
port = datosIngreso["port"],
user = datosIngreso["user"],
passwd = datosIngreso["password"],
db = datosIngreso["db"])
# Definamos unas consultas del número de pacientes (Población total) en
# cada uno de los hospitales
consultaSqlTotalPacientes = """
SELECT COUNT(DISTINCT id_paciente)
FROM e_consolidado
WHERE fecha_folio_hc
BETWEEN '2013-10-01' AND '2014-03-31'
AND tipo_ingreso=1
AND institucion=1;
"""
# Seleccionemos los datos a partir de esta consulta
totalPacientesHospital1 = pdsql.read_sql(consultaSqlTotalPacientes, conexion)
totalPacientesHospital1
Ahora bien, esta consulta es para un sólo hospital, pero quisiéramos hacerla mucho más modular para que los disintintos hospitales puedan ser consultados más fácilmente. En ese caso es mejor convertir la consulta en una función.
def sqlTotalPacientesHospital(n):
# Parte comun de la consulta
comun = """
SELECT COUNT(DISTINCT id_paciente)
FROM e_consolidado
WHERE fecha_folio_hc
BETWEEN '2013-10-01' AND '2014-03-31'
AND tipo_ingreso=1
AND institucion=
"""
# Parte variable de la consulta
hospital = n
# Unimos la parte común y la variable para tener nuestro resultado final
return comun + str(hospital) + ";"
Con esta nueva consulta parametrizada podemos también parametrizar la consulta desde pandas:
def totalPacientesHospital(n):
return pdsql.read_sql(sqlTotalPacientesHospital(n), conexion)
Así para extractar el valor dentro la consulta que habíamos hecho antes, invocamos simplemente en:
totalPacientesHospital(1).ix[0,0]
y se tiene la ventaja de poder invocarla fácilmente para otro hospital:
totalPacientesHospital(2).ix[0,0]
e incluso podemos construir una estructura de datos que guarde la información para todos los hospitales:
pacientesPorHospital = {}
for i in range(1,totalHospitales + 1):
pacientesPorHospital[i] = totalPacientesHospital(i).ix[0,0]
pacientesPorHospital
A partir del anterior diccionario podemos definir un dataframe que nos da ciertas facilidades de consulta y graficación:
dfPacientesPorHospital = DataFrame(pacientesPorHospital, index=[0])
dfPacientesPorHospital.plot(kind='bar', stacked=False, title= u'Pacientes por hospital')
## Se define la totalidad de pacientes por Sexo de acuerdo a la siguiente convención:
## 1 Hombre
## 2 Mujer
sqlPacientesPorSexo = """
SELECT sexo,
COUNT(DISTINCT id_paciente)
FROM e_consolidado
WHERE fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND institucion = '1'
AND tipo_ingreso = '1'
GROUP BY sexo;"""
# Seleccionemos los datos a partir de esta consulta
totalPacientesPorSexoHospital1 = pdsql.read_sql(sqlPacientesPorSexo, conexion)
totalPacientesPorSexoHospital1
# Automatizamos la consulta para que use variables para la institución en lugar de valores fijos
def sqlTotalPacientesPorSexoHospital(n):
# Parte comun de la consulta
comun = """
SELECT sexo,
COUNT(DISTINCT id_paciente)
FROM e_consolidado
WHERE fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND institucion = '%i'
AND tipo_ingreso = '1'
GROUP BY sexo;"""
# Unimos la parte común y la variable para tener nuestro resultado final
return comun %(n)
De esta manera podemos definir una consulta que nos retorne los pacientes por hospital, agrupados por sexo:
def totalPacientesPorSexoHospital(n):
return pdsql.read_sql(sqlTotalPacientesPorSexoHospital(n), conexion)
pacientesPorSexoHospital = {}
for i in range(1,totalHospitales + 1):
pacientesPorSexoHospital[i] = totalPacientesPorSexoHospital(i)
# pacientesPorSexoHospital
# Consolidado Pacientes Por Sexo- Tenemos que dividir esto en dos
# ciclos disintos e integrarlos al final. No sabemos si es una limitación
# de pandas (PREGUNTAR en stackoverflow).
dfPacientesPorSexoHospital1 = pacientesPorSexoHospital[1]
for i in range(2,4):
dfPacientesPorSexoHospital1 = pd.merge(dfPacientesPorSexoHospital1, pacientesPorSexoHospital[i], on='sexo', how='outer')
dfPacientesPorSexoHospital2 = pacientesPorSexoHospital[4]
for i in range(5,8):
dfPacientesPorSexoHospital2 = pd.merge(dfPacientesPorSexoHospital2, pacientesPorSexoHospital[i], on='sexo', how='outer')
dfPacientesPorSexoHospital = pd.merge(dfPacientesPorSexoHospital1, dfPacientesPorSexoHospital2, on='sexo', how='outer')
dfPacientesPorSexoHospital
Ya que tenemos el data frame que guarda nuestros consolidados, vamos a extraer los datos, de modo que podamos manipularlos de manera más discreta y enviarselos, por ejemplo a los gráficos que haremos a partir de ellos.
listaPacientesPorSexoHospital = np.array(dfPacientesPorSexoHospital.get_values()).tolist()
totalHombres = listaPacientesPorSexoHospital[0][1:8]
totalMujeres = listaPacientesPorSexoHospital[1][1:8]
# Basados en: http://people.duke.edu/~ccc14/pcfb/numpympl/MatplotlibBarPlots.html#simple-bar-plot
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
## the data
N = totalHospitales
menMeans = totalHombres
womenMeans = totalMujeres
## necessary variables
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
## the bars
rects1 = ax.bar(ind, menMeans, width,
color='black')
rects2 = ax.bar(ind+width, womenMeans, width,
color='red')
# axes and labels
ax.set_xlim(-width,len(ind)+width)
ax.set_ylim(0,20000)
ax.set_ylabel(u'Número de pacientes')
ax.set_title(u'Número de pacientes agrupados por sexo')
xTickMarks = ['Hospital '+str(i) for i in range(1,8)]
ax.set_xticks(ind+width)
xtickNames = ax.set_xticklabels(xTickMarks)
plt.setp(xtickNames, rotation=45, fontsize=10)
## add a legend
ax.legend( (rects1[0], rects2[0]), ('Hombres', 'Mujeres') )
plt.show()
## Consultas médicas por mes
totalConsultasporMes = """
SELECT MONTH(fecha_folio_hc),YEAR(fecha_folio_hc), COUNT(DISTINCT CONCAT(fecha_folio_hc,id_paciente))
FROM e_consolidado
WHERE fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND institucion ='1'
AND tipo_ingreso ='1'
GROUP BY MONTH(fecha_folio_hc),YEAR(fecha_folio_hc)
ORDER BY YEAR(fecha_folio_hc);"""
totalConsultasporMesHospital1 = pdsql.read_sql(totalConsultasporMes, conexion)
totalConsultasporMesHospital1
#automatizar
## Los medicamentos antihipertensivos identificados en los hospitales se han agrupado
## por grupo farmacológico. La siguiente consulta tiene como objetivo identificar
## cuantos estan en tratamiento con cada grupo farmacológico
pacientesporGrupoAntihipertensivo = """
SELECT Antihipertensivo, COUNT(DISTINCT id_paciente)
FROM e_consolidado
WHERE fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND institucion ='1'
AND tipo_ingreso ='1'
AND Antihipertensivo <>''
GROUP BY Antihipertensivo;"""
dfPacientesGrupoAntihipertensivoHospital1 = pdsql.read_sql(pacientesporGrupoAntihipertensivo, conexion)
dfPacientesGrupoAntihipertensivoHospital1
# Gráfica de torta. Base tomada de:
# http://matplotlib.org/1.2.1/examples/pylab_examples/pie_demo.html
"""
Make a pie chart - see
http://matplotlib.sf.net/matplotlib.pylab.html#-pie for the docstring.
This example shows a basic pie chart with labels optional features,
like autolabeling the percentage, offsetting a slice with "explode",
adding a shadow, and changing the starting angle.
"""
# make a square figure and axes
figure(1, figsize=(6,6))
ax = axes([0.1, 0.1, 0.8, 0.8])
# The slices will be ordered and plotted counter-clockwise.
labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
fracs = [15, 30, 45, 10]
explode=(0, 0.05, 0, 0)
pie(fracs, explode=explode, labels=labels,
autopct='%1.1f%%', shadow=True, startangle=90)
# The default startangle is 0, which would start
# the Frogs slice on the x-axis. With startangle=90,
# everything is rotated counter-clockwise by 90 degrees,
# so the plotting starts on the positive y-axis.
title('Raining Hogs and Dogs', bbox={'facecolor':'0.8', 'pad':5})
show()
#automatizar
## Calculamos el total de médicos por institución durante el periodo
## (Octubre de 2013 a Marzo de 2014)
medicosPeriodo = """
SELECT COUNT(DISTINCT medico)
FROM e_consolidado
WHERE fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND institucion ='1'
AND tipo_ingreso ='1';"""
medicosPeriodoHospital1 = pdsql.read_sql(medicosPeriodo, conexion)
medicosPeriodoHospital1
#Sin embargo se identifica que existen especialidades no médicas en la
#base de datos, dado que otros profesionales de la salud también utilizan
#la historia clínica, por tanto se deben identificar cuales son esas
#profesiones distintas para excluirlas del conteo
Especialidades = """
SELECT DISTINCT especialidad
FROM e_consolidado
WHERE fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND tipo_ingreso = '1'
AND especialidad <>'';"""
diferentesEspecialidadesdb = pdsql.read_sql(Especialidades, conexion)
diferentesEspecialidadesdb
##Se realiza nuevamente la consulta excluyendo las especialidades
## no médicas (Enfermera jefe, Optometría, Enfermeria jefe, Odontología pediátrica,
## Endodoncia, Nutrición, Odontología integral del adulto, Enfermeria en Hospitalización
##Enfermeria promocion y prevencion, Enfermeria, Higienista oral, Auxiliar de enfermeria
especialidadesMedicas = """
SELECT COUNT(DISTINCT medico)
FROM e_consolidado
WHERE fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND tipo_ingreso = '1'
AND especialidad not REGEXP 'enferme|optome|odontol|endodon|nutric|higienis'
AND institucion = '1';"""
medicosPeriodo = pdsql.read_sql(especialidadesMedicas, conexion)
medicosPeriodo
#automatizar
##Considerando que la consulta medicosPeriodo corresponde a la totalidad de medicos
##durante el periodo comprendido entre octubre de 2013 y marzo de 2014 calculamos
## ahora la cantidad de medicos por mes para establecer el promedio de médicos de
## la institución dado que existe alta rotación de personal
sqlMedicosPorMes = """
SELECT MONTH(fecha_folio_hc), YEAR(fecha_folio_hc),
COUNT(DISTINCT medico)
FROM e_consolidado
WHERE fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND tipo_ingreso = '1'
AND especialidad not REGEXP 'enferme|optome|odontol|endodon|nutric|higienis'
AND institucion = '1'
GROUP BY MONTH(fecha_folio_hc), YEAR(fecha_folio_hc)
ORDER BY YEAR(fecha_folio_hc);"""
totalMedicosPorMes = pdsql.read_sql(sqlMedicosPorMes, conexion)
totalMedicosPorMes
##Automatizar
## Seria conveniente poder estimar la rotación de medicos durante el periodo
##Establecemos la totalidad de médicos por especialidad. Esto nos ayudará a
##definir cuales son los médicos que más prescriben antihipertensivos y así
##priorizar los grupos que requieren mayor intervención
sqlMedicosPorEspecialidad = """
SELECT especialidad,
COUNT(DISTINCT medico)
FROM e_consolidado
WHERE fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND tipo_ingreso = '1'
AND especialidad not REGEXP 'enferme|optome|odontol|endodon|nutric|higienis'
AND institucion = '1'
GROUP BY especialidad
ORDER BY especialidad;"""
totalMedicosPorEspecialidad = pdsql.read_sql(sqlMedicosPorEspecialidad , conexion)
totalMedicosPorEspecialidad
##Automatizar
##Considerar las siguientes opciones: Agrupar por principales especialidades y otras o
##mostrar el detalle y agrupar las que corresponden a la misma pero cambia denominación
##entre hospitales
##Ahora identificamos la totalidad de pacientes con cada medicamento antihipertensivo
sqlPacientesPorAntihipertensivo = """
SELECT med_nombre,
COUNT(DISTINCT id_paciente)
FROM e_consolidado
WHERE antihipertensivo NOT LIKE ''
AND fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND institucion = '1'
AND tipo_ingreso = '1'
GROUP BY med_nombre;"""
totalPacientesPorAntihipertensivo = pdsql.read_sql(sqlPacientesPorAntihipertensivo , conexion)
totalPacientesPorAntihipertensivo
##Afinamos la consulta para que nos agrupe por principio activo (ej: total de
##pacientes con Enalapril, sin considerar concentración) y excluya inyectables
##dado que los medicamentos en esta presentación no son administrados de
##manera ambulatoria
Calculo de DDD para cada uno de los medicamentos
##1-Extracción de la concentración de la columna medicamento
A continuacion se detallan otras variables marcadoras de prescripcion racional, relacionadas con el uso concomitante de ARA II e IECA e incluso el uso simultaneo de IECA. La formulacion concomitante de medicamentos que actuan sobre el mismo sistema, en este caso Bloqueadores del sistema Renina-Angiotensina corresponde a un problema relacionado con los medicamentos conocido comunmente como Duplicidad.
## Pacientes a quienes se les prescribieron 2 IECA (Captopril y Enalapril) de manera simultanea (en la misma fecha)
PacientesIECA_IECA = """
SELECT COUNT(DISTINCT ec.id_paciente)
FROM e_consolidado AS ec
JOIN (SELECT id_paciente, fecha_folio_hc
FROM e_consolidado
WHERE med_nombre LIKE '%capto%') AS t
ON ec.id_paciente = t.id_paciente AND ec.fecha_folio_hc = t.fecha_folio_hc
WHERE (ec.med_nombre LIKE '%enalap%'
AND ec.fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND ec.tipo_ingreso=1
AND ec.institucion=1);"""
TotalPacientesIECA_IECA_Hospital1 = pdsql.read_sql(PacientesIECA_IECA, conexion)
TotalPacientesIECA_IECA_Hospital1
def pacientesIECA_IECA(n):
#Parte comun de la consulta
comun = """
SELECT COUNT(DISTINCT ec.id_paciente)
FROM e_consolidado
AS ec
JOIN (SELECT id_paciente, fecha_folio_hc
FROM e_consolidado
WHERE med_nombre like '%capto%')
AS t
ON ec.id_paciente = t.id_paciente AND ec.fecha_folio_hc = t.fecha_folio_hc
WHERE (ec.med_nombre like '%enalap%' AND ec.fecha_folio_hc
BETWEEN '2013-10-01' AND '2014-03-31'
AND ec.tipo_ingreso=1 AND ec.institucion= """
#Parte variable de la consulta
hospital = n
# Unimos la parte comun y la variable para tener nuestro resultado final
return comun + str(hospital) + ") ;"
def totalPacientesIECA_IECA_Hospital(n):
return pdsql.read_sql(pacientesIECA_IECA(n),conexion)
totalPacientesIECA_IECA_Hospital(1)
## Pacientes a quienes se les prescribió 1 IECA (Captopril o Enalapril) y 1 ARA
##(Losartan o Losartan + Hidroclorotiazida) de manera simultanea (en la misma fecha)
PacientesIECA_ARA0 = """
SELECT COUNT(DISTINCT ec.id_paciente)
FROM e_consolidado AS ec
JOIN (SELECT id_paciente, fecha_folio_hc
FROM e_consolidado
WHERE med_nombre REGEXP 'capto|enalap') AS t
ON ec.id_paciente = t.id_paciente AND ec.fecha_folio_hc = t.fecha_folio_hc
WHERE (ec.med_nombre REGEXP 'losart'
AND ec.fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND ec.tipo_ingreso=1
AND ec.institucion=1);"""
totalPacientesIECA_ARA0_Hospital1 = pdsql.read_sql(PacientesIECA_ARA0, conexion)
totalPacientesIECA_ARA0_Hospital1
## Pacientes a quienes se les prescribió 1 IECA (Captopril) y 1 ARA (Losartan o Losartan + Hidroclorotiazida) de manera simultanea (en la misma fecha)
PacientesIECA_ARA = """
SELECT COUNT(DISTINCT ec.id_paciente)
FROM e_consolidado AS ec
JOIN (SELECT id_paciente, fecha_folio_hc
FROM e_consolidado
WHERE med_nombre LIKE '%capto%') AS t
ON ec.id_paciente = t.id_paciente AND ec.fecha_folio_hc = t.fecha_folio_hc
WHERE (ec.med_nombre REGEXP 'losart'
AND ec.fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND ec.tipo_ingreso=1
AND ec.institucion=1);"""
totalPacientesIECA_ARA_Hospital1 = pdsql.read_sql(PacientesIECA_ARA, conexion)
totalPacientesIECA_ARA_Hospital1
##automatizar
## Pacientes a quienes se les prescribió 1 IECA (Enalapril) y 1 ARA (Losartan o Losartan + Hidroclorotiazida)
## de manera simultanea (en la misma fecha)
PacientesIECA_ARA2 = """
SELECT COUNT(DISTINCT ec.id_paciente)
FROM e_consolidado AS ec
JOIN (SELECT id_paciente, fecha_folio_hc
FROM e_consolidado
WHERE med_nombre LIKE '%enala%') AS t
ON ec.id_paciente = t.id_paciente AND ec.fecha_folio_hc = t.fecha_folio_hc
WHERE (ec.med_nombre REGEXP 'losart'
AND ec.fecha_folio_hc BETWEEN '2013-10-01' AND '2014-03-31'
AND ec.tipo_ingreso=1
AND ec.institucion=1);"""
totalPacientesIECA_ARA2_Hospital1 = pdsql.read_sql(PacientesIECA_ARA2, conexion)
totalPacientesIECA_ARA2_Hospital1
Acá unos enlaces extra que sirvieron durante la elaboración de este documento.