Análisis del Hipotiroidismo Congénito

data cleaning
exploratory analysis
streamlit
health
sms-api

Este proyecto analiza datos de hipotiroidismo congénito y aborda la falta de comunicación con los padres mediante un dashboard interactivo en Streamlit que no solo explora los datos, sino que también envía notificaciones SMS usando una API.

Author

LC Pallares

Published

March 24, 2025

Modified

May 19, 2025

Análisis del Hipotiroidismo Congénito: Datos y Comunicación

Resumen del Proyecto

El hipotiroidismo congénito es una condición presente desde el nacimiento que afecta la glándula tiroides, pero un problema crítico es que los padres a menudo no son informados a tiempo. Este proyecto tiene dos objetivos: analizar una base de datos sobre esta condición y desarrollar una herramienta práctica para mejorar la comunicación. Para ello, creé un dashboard en Streamlit que explora los datos y permite enviar notificaciones SMS a los padres mediante una API.

The congenital hypothyroidism (CH). CH affects 1 in 2,000-4,000 newborns worldwide. Early detection is vital. Untreated CH can lead to intellectual disability and growth delays.

Parte 1: 🔍 Procesamiento y Limpieza de Datos

El conjunto de datos médicos requirió un extenso proceso de limpieza. A continuación, se detallan los principales desafíos y soluciones implementadas:

📅 Corrección de Fechas Inconsistentes

El dataset contenía múltiples columnas de fechas (fecha_ingreso, fecha_toma_muestra, fecha_resultado, etc.). Se identificaron errores críticos: - Fechas imposibles: Diferencias negativas entre fecha_resultado y fecha_toma_muestra (ej: resultados que aparecían 20 años antes de la toma de muestra). - Causas: Errores de digitación (ej: 2022 vs 2002) y registros invertidos.

Acciones tomadas: 1. Filtrado de registros con diferencias mayores a ±30 días (umbral clínicamente relevante). 2. Corrección manual de fechas mal ingresadas cruzando con otros campos (ej: edad del paciente). 3. Eliminación de 15 registros con inconsistencias irrecuperables.

Ejemplo de dato corregido:

Antes: 
  - fecha_toma_muestra: 2022-07-17 
  - fecha_resultado: 2002-08-05 
  - días_pasados: -7286

Después: 
  - fecha_toma_muestra: 2022-07-17 
  - fecha_resultado: 2022-08-05 
  - días_pasados: +19

🧪 Limpieza de Valores de TSH Neonatal

Los resultados de TSH presentaban: - Valores atípicos: Resultados fuera del rango fisiológico (ej: >100 mUI/L). - Datos faltantes: NaN en ~8% de los registros.

Estrategia: - Rango válido definido: 0.5 - 20 mUI/L (basado en literatura médica). - Imputación de faltantes con la media (5.2 mUI/L).

👶 Normalización de Género

La columna sexo contenía categorías inconsistentes:

AMBIGUO: 1      | FEMENINO: 16,298
NO ESCRITO: 126 | MASCULINO: 17,140

Solución: 1. Reclasificación de “NO ESCRITO” mediante análisis de nombres (ej: “María” → FEMENINO). 2. Corrección manual del único caso “AMBIGUO” revisando historia clínica. 3. Resultado final: Solo categorías FEMENINO/MASCULINO.

Manejo de Datos Faltantes

  • Columnas numéricas: Imputación con media/mediana según distribución.
  • Columnas categóricas: Relleno con "DESCONOCIDO" para preservar registros.

📊 Datos Finales

Tras la limpieza, el dataset quedó con: - Registros: no se elimino ningun registro. - Variables críticas validadas: TSH neonatal, fechas, género.

Nota técnica: El código completo de limpieza está disponible bajo solicitud para fines de reproducibilidad.

Parte 2: Exploración de Datos

El análisis exploratorio reveló patrones en los datos, mientras que el dashboard en Streamlit los hace accesibles. Algunas visualizaciones incluyen:

Code
df.head(5)
id ficha_id fecha_ingreso institucion ars historia_clinica tipo_documento numero_documento ciudad departamento ... fecha_resultado_muestra_2 resultado_muestra_2 contador muestra_rechazada fecha_toma_rechazada tipo_vinculacion resultado_rechazada fecha_resultado_rechazada dias_pasados dias_pasados2
0 365048 369980 2019-05-06 VICTORIA MEDIMAS 1000 1 2000 Bogota Cundinamarca ... NaT 0.0 1 False NaT NaN NaN NaT 1 NaN
1 365049 369981 2019-05-06 VICTORIA CAPITAL SALUD 1000 2 2000 Bogota Cundinamarca ... NaT 0.0 1 False NaT NaN NaN NaT 2 NaN
2 365050 369982 2019-05-06 VICTORIA CAPITAL SALUD 1000 1 2000 Bogota Cundinamarca ... NaT 0.0 1 False NaT NaN NaN NaT 1 NaN
3 365051 369983 2019-05-06 VICTORIA CAPITAL SALUD 1000 1 2000 Bogota Cundinamarca ... NaT 0.0 1 False NaT NaN NaN NaT 0 NaN
4 365052 369984 2019-05-07 VICTORIA VINCULADO 1000 4 2000 Bogota Cundinamarca ... NaT 0.0 1 False NaT NaN NaN NaT 3 NaN

5 rows × 41 columns

Notas:

Sospecha de hipotiroidismo: TSH ≥ 15 mIU/L en la primera muestra.

Confirmación de hipotiroidismo: TSH ≥ 15 mIU/L en la primera y segunda muestra.

Code
df['sospecha_hipotiroidismo'] = df['tsh_neonatal'] >= 15
df['confirmado_hipotiroidismo'] = df['sospecha_hipotiroidismo'] & (df['resultado_muestra_2'] >= 15)
Code

print("Total de Registros", f"{df.shape[0]:,}")

print("Casos Sospechosos (TSH ≥ 15)", f"{df['sospecha_hipotiroidismo'].sum():,}")
print("Casos Confirmados", f"{df['confirmado_hipotiroidismo'].sum():,}")

# Calcular el promedio de días hasta el resultado
dias_promedio = round(df['dias_pasados'].mean(), 1)
print("Promedio Días hasta Resultado", f"{dias_promedio}")
Total de Registros 33,565
Casos Sospechosos (TSH ≥ 15) 572
Casos Confirmados 6
Promedio Días hasta Resultado 2.7
Code
# para mostar los graficos, ya que devolvian error
import plotly.offline as pyo
pyo.init_notebook_mode(connected=True)
Code
# Gráfico de pirámide de diagnóstico
stages = ['Tamizados', 'TSH ≥ 15', 'Confirmados']
values = [df.shape[0], df['sospecha_hipotiroidismo'].sum(), df['confirmado_hipotiroidismo'].sum()]

fig_funnel = go.Figure(go.Funnel(
    y=stages,
    x=values,
    textinfo="value+percent initial",
    marker={"color": ["#4682B4", "#FFA500", "#FF4500"]}
))

fig_funnel.update_layout(
    title="Pirámide de Diagnóstico de Hipotiroidismo Congénito",
    width=800,
    height=500
)

fig_funnel.show()

Pirámide de Diagnóstico de Hipotiroidismo Congénito

Code
# Filtrar valores extremos para mejor visualización
tsh_max_visual = df['tsh_neonatal'].quantile(0.99)
df_tsh_visual = df[df['tsh_neonatal'] <= tsh_max_visual]

fig_tsh_hist = px.histogram(
    df_tsh_visual, 
    x='tsh_neonatal',
    nbins=30,
    color_discrete_sequence=['#3CB371'],
    labels={'tsh_neonatal': 'TSH Neonatal (mIU/L)'}
)

# Añadir línea vertical para el umbral
fig_tsh_hist.add_vline(
    x=15, 
    line_dash="dash", 
    line_color="red",
    annotation_text=f"Umbral: 15 mIU/L",
    annotation_position="top right"
)

fig_tsh_hist.update_layout(title='Distribución de Niveles de TSH al Nacer', xaxis_title="Valor de TSH (mIU/L)", yaxis_title="Frecuencia")
fig_tsh_hist.show()

Distribución de niveles de TSH al nacer

Code
fig_box_sex = px.box(
    df_tsh_visual,
    x='sexo',
    y='tsh_neonatal',
    color='sexo',
    points="outliers",
    labels={'sexo': 'Sexo', 'tsh_neonatal': 'TSH Neonatal (mIU/L)'}
)

fig_box_sex.add_hline(
    y=15, 
    line_dash="dash", 
    line_color="red",
    annotation_text="Umbral: 15 mIU/L",
    annotation_position="top right"
)
fig_box_sex.show()

Boxplots dispercion entre sexo y TSH

Code
filtered_df = df
# Agrupar datos por mes y año
filtered_df['año_mes'] = filtered_df['fecha_nacimiento'].dt.to_period('M')

# Tendencia temporal de casos
temporal_df = filtered_df.groupby(['año_mes']).agg(
total_casos=('tsh_neonatal', 'count'),
casos_sospechosos=('sospecha_hipotiroidismo', 'sum'),
casos_confirmados=('confirmado_hipotiroidismo', 'sum'),
tsh_promedio=('tsh_neonatal', 'mean')
).reset_index()

temporal_df['año_mes'] = temporal_df['año_mes'].dt.to_timestamp()
temporal_df['tasa_confirmacion'] = temporal_df['casos_confirmados'] / temporal_df['casos_sospechosos']
temporal_df['incidencia'] = temporal_df['casos_confirmados'] / temporal_df['total_casos']

# Gráfico de línea para casos y tasa de confirmación
fig_temporal = go.Figure()

fig_temporal.add_trace(go.Scatter(
x=temporal_df['año_mes'],
y=temporal_df['casos_sospechosos'],
mode='lines+markers',
name='Casos Sospechosos',
line=dict(color='#FFA500', width=2)
))

fig_temporal.add_trace(go.Scatter(
x=temporal_df['año_mes'],
y=temporal_df['casos_confirmados'],
mode='lines+markers',
name='Casos Confirmados',
line=dict(color='#FF4500', width=2)
))

fig_temporal.add_trace(go.Scatter(
x=temporal_df['año_mes'],
y=temporal_df['tasa_confirmacion'],
mode='lines',
name='Tasa de Confirmación',
line=dict(color='#4682B4', width=2, dash='dot'),
yaxis='y2'
))

fig_temporal.update_layout(
title='Evolución Temporal de Casos de Hipotiroidismo Congénito',
xaxis_title='Fecha',
yaxis=dict(
    title='Número de Casos',
    titlefont=dict(color='#FF4500'),
    tickfont=dict(color='#FF4500')
),
yaxis2=dict(
    title='Tasa de Confirmación',
    titlefont=dict(color='#4682B4'),
    tickfont=dict(color='#4682B4'),
    anchor='x',
    overlaying='y',
    side='right',
    range=[0, 1]
),
legend=dict(
    orientation="h",
    yanchor="bottom",
    y=1.02,
    xanchor="center",
    x=0.5
)
)

fig_temporal.show()

Evolución Temporal de Casos de Hipotiroidismo Congénito

Code
# Conversión de peso a kilogramos para mejor visualización
filtered_df['peso_kg'] = filtered_df['peso'] / 1000

fig_peso_tsh = px.scatter(
    filtered_df,
    x='peso_kg',
    y='tsh_neonatal',
    color='confirmado_hipotiroidismo',
    color_discrete_map={True: '#FF4500', False: '#4682B4'},
    labels={
        'peso_kg': 'Peso al Nacer (kg)',
        'tsh_neonatal': 'TSH Neonatal (mIU/L)',
        'confirmado_hipotiroidismo': 'Hipotiroidismo Confirmado'
    },
    trendline="ols",
    opacity=0.7
)

fig_peso_tsh.add_hline(
    y=15, 
    line_dash="dash", 
    line_color="red",
    annotation_text="Umbral TSH: 15 mIU/L",
    annotation_position="top right"
)

fig_peso_tsh.show()

Relacion entre peso y Hipotiroidismo Congénito

Parte 3: Dashboard

Funcionalidad del Dashboard

  • Exploración interactiva: Los usuarios pueden filtrar y visualizar datos (ej. TSH por edad o región).
  • Notificaciones SMS: Integra una API (como Twilio) para enviar alertas a los padres con información clave, como “Su hijo/a tiene un nivel de TSH elevado. Contacte a su médico”.
  • Acceso: Disponible aquí.

Interfaz del Dashboard

Distribución de TSH

Hallazgos Preliminares

  • Los niveles de TSH varían ampliamente, lo que indica la necesidad de segmentar los casos.
  • La falta de información a los padres parece estar relacionada con datos de contacto incompletos o desactualizados.

Parte 4: Estadística Avanzada (En Desarrollo)

Esta sección está en progreso. Planeo incluir: - Modelos predictivos para identificar casos de riesgo elevado. - Análisis de correlación entre factores demográficos y retrasos en la notificación. - Evaluación de la efectividad de las notificaciones SMS en la respuesta de los padres.

Pronto actualizaré esta entrada con más detalles.

Conclusiones Preliminares

El proyecto no solo transforma datos crudos en información útil, sino que también aborda un problema real: la comunicación con los padres. El dashboard combina análisis y acción, ofreciendo una solución práctica para mejorar la atención temprana del hipotiroidismo congénito.

Próximos Pasos

  • Finalizar la sección de estadística avanzada.
  • Optimizar el dashboard con más opciones de personalización para los SMS.
  • Probar la API de SMS en un entorno real y evaluar su impacto.
Back to top