Isotype

Python
code
Visualization
Isotype
Author

Marco Aguirre

Published

September 10, 2025

0.1 Características

  • Uso de pictogramas simples y gráficos elementales.
  • Lenguaje visual universal, independiente de idiomas.
  • Inspirado en el funcionalismo gráfico de la Bauhaus y las vanguardias del siglo XX.
  • La comprensión se logra en tres pasos: captar lo esencial, lo secundario y los detalles.
  • Visualización de datos estadísticos: tablas y gráficos comparativos.
  • Educación y divulgación: explicar fenómenos sociales, económicos o históricos.
  • Diseño gráfico y editorial: base de la infografía moderna.

0.2 Errores a Evitar

  • Escalar pictogramas para “mostrar más” (tamaño ≠ cantidad); en Isotype se repiten iconos del mismo tamaño.
  • No definir la unidad visual: falta de leyenda “1 icono = N unidades”.
  • Iconografía inconsistente: cambiar de pictograma para la misma categoría.
  • Efectos decorativos (3D, degradados, sombras) que restan legibilidad.
  • Iconos culturalmente ambiguos: metáforas no universales.

1 Librería altair (Vega-Altair)

Logo de Altair

La librería altair es un paquete de Python diseñado para crear visualizaciones interactivas y declarativas basadas en la gramática de gráficos de Vega y Vega-Lite.

1.1 Usos principales

  • Exploración de datos de manera visual y rápida.
  • Creación de gráficos interactivos en notebooks, aplicaciones web y dashboards.
  • Informes y storytelling con datos, gracias a su integración con Jupyter, Streamlit, Panel y Quarto.

1.2 Características principales

  • Sintaxis declarativa y concisa: describes el gráfico indicando las columnas de tus datos y el tipo de marca (barras, puntos, líneas, etc.).
  • Admite interactividad integrada como zoom, selección y filtrado.
  • Compatible con pandas DataFrames y librerías de análisis como NumPy.
  • Exporta visualizaciones en formato JSON para reproducibilidad.

1.3 Instalación

Para instalar Altair y sus dependencias más comunes:

pip install "altair[all]"


# Dataset: Base de Artículos Publicados 2015–2023

**Fuente:**  
[Secretaría de Educación Superior, Ciencia, Tecnología e Innovación (SENESCYT)](https://cloud-pro.senescyt.gob.ec/index.php/s/qfDbtQxawojJ2CG?openfile=true)

**Descripción:**  
Este dataset contiene la base estadística de artículos científicos publicados por las universidades y escuelas politécnicas de Ecuador en **revistas indexadas** durante el periodo **2015 – 2023**.  

::: {#20c93db1 .cell execution_count=30}
``` {.python .cell-code}
import pandas as pd
from wordcloud import WordCloud, STOPWORDS
import matplotlib.pyplot as plt
df = pd.read_excel("Base_estadistica_articulos_UEP_15_23.xlsx", skiprows=12)

:::

import pandas as pd
import numpy as np
import altair as alt

# ---- Filtrar y calcular top 10 ----
df_filtered = df[df['NOMBRE UNIVERSIDAD'] == "UNIVERSIDAD SAN FRANCISCO DE QUITO"]["CAMPO DETALLADO"].dropna().astype(str)
top = df_filtered.value_counts().head(10).reset_index()
top.columns = ["CAMPO DETALLADO", "FRECUENCIA"]

# ---- Parámetros del isotype ----
UNIDAD = 50  # 1 ícono = 50 publicaciones

# Emojis por categoría (puedes ajustar a gusto)
emoji_map = {
    "FÍSICA": "🔭",
    "MEDICINA": "💊",
    "BIOLOGÍA": "🧬",
    "MEDIO AMBIENTE": "🌱",
    "ESTUDIOS SOCIALES Y CULTURALES": "📚",
    "CONSTRUCCIÓN E INGENIERÍA CIVIL": "🏗️",
    "BIOMEDICINA": "🧪",
    "SALUD PÚBLICA": "🏥",
    "COMPUTACIÓN": "💻",
    "QUÍMICA": "⚗️"
}

# Calcular número de iconos
top["ICONOS"] = np.maximum((top["FRECUENCIA"] // UNIDAD).astype(int), 1)

# Expandir filas: una por ícono
rows = []
for _, r in top.iterrows():
    for i in range(int(r["ICONOS"])):
        rows.append({
            "CAMPO DETALLADO": r["CAMPO DETALLADO"],
            "y": i  # eje vertical
        })

source = pd.DataFrame(rows)

# Gráfico vertical
chart = (
    alt.Chart(source)
    .mark_text(size=25, baseline='middle')
    .encode(
        alt.X('CAMPO DETALLADO:O', sort=top["CAMPO DETALLADO"].tolist()).axis(None),
        alt.Y('y:O').axis(None),
        alt.Text('emoji:N')
    )
    .transform_calculate(
        emoji=f"{emoji_map}".replace("'", '"') + "[datum['CAMPO DETALLADO']]"
    )
    .properties(width=650, height=500)
)

# Pie con la equivalencia
nota = alt.Chart(pd.DataFrame({"t": [f"1 icono = {UNIDAD} publicaciones"]})).mark_text(
    align="left", dx=5, dy=15
).encode(text="t:N")

chart & nota
c:\Users\meagu\anaconda3\Lib\site-packages\altair\utils\core.py:395: FutureWarning: the convert_dtype parameter is deprecated and will be removed in a future version.  Do ``ser.astype(object).apply()`` instead if you want ``convert_dtype=False``.
  col = df[col_name].apply(to_list_if_array, convert_dtype=False)
c:\Users\meagu\anaconda3\Lib\site-packages\altair\utils\core.py:395: FutureWarning: the convert_dtype parameter is deprecated and will be removed in a future version.  Do ``ser.astype(object).apply()`` instead if you want ``convert_dtype=False``.
  col = df[col_name].apply(to_list_if_array, convert_dtype=False)

1.4 Iteración 2

import pandas as pd
import numpy as np
import altair as alt

# ---- Filtrar y calcular top 10 ----
df_filtered = df[df['NOMBRE UNIVERSIDAD'] == "UNIVERSIDAD SAN FRANCISCO DE QUITO"]["CAMPO DETALLADO"].dropna().astype(str)
top = df_filtered.value_counts().head(10).reset_index()
top.columns = ["CAMPO DETALLADO", "FRECUENCIA"]

# ---- Parámetros del isotype ----
UNIDAD = 50  # 1 ícono = 50 publicaciones

# Emojis por categoría
emoji_map = {
    "FÍSICA": "🔭",
    "MEDICINA": "💊",
    "BIOLOGÍA": "🧬",
    "MEDIO AMBIENTE": "🌱",
    "ESTUDIOS SOCIALES Y CULTURALES": "📚",
    "CONSTRUCCIÓN E INGENIERÍA CIVIL": "🏗️",
    "BIOMEDICINA": "🧪",
    "SALUD PÚBLICA": "🏥",
    "COMPUTACIÓN": "💻",
    "QUÍMICA": "⚗️"
}

# Calcular número de iconos
top["ICONOS"] = np.maximum((top["FRECUENCIA"] // UNIDAD).astype(int), 1)

# Expandir filas: una por ícono
rows = []
for _, r in top.iterrows():
    for i in range(int(r["ICONOS"])):
        rows.append({
            "CAMPO DETALLADO": r["CAMPO DETALLADO"],
            "x": i  # eje horizontal
        })

source = pd.DataFrame(rows)

# Gráfico horizontal con eje X
chart = (
    alt.Chart(source)
    .mark_text(size=25, baseline='middle')
    .encode(
        alt.X('x:O', axis=alt.Axis(title=f"Número de íconos (1 = {UNIDAD} publicaciones)")),
        alt.Y('CAMPO DETALLADO:O', sort=top["CAMPO DETALLADO"].tolist()),
        alt.Text('emoji:N')
    )
    .transform_calculate(
        emoji=f"{emoji_map}".replace("'", '"') + "[datum['CAMPO DETALLADO']]"
    )
    .properties(width=800, height=400)
)

chart
c:\Users\meagu\anaconda3\Lib\site-packages\altair\utils\core.py:395: FutureWarning: the convert_dtype parameter is deprecated and will be removed in a future version.  Do ``ser.astype(object).apply()`` instead if you want ``convert_dtype=False``.
  col = df[col_name].apply(to_list_if_array, convert_dtype=False)

1.5 Iteración 3

# Gráfico horizontal SIN eje X y SIN líneas
chart = (
    alt.Chart(source)
    .mark_text(size=25, baseline='middle')
    .encode(
        alt.X('x:O', axis=None),   # ❌ elimina por completo el eje X
        alt.Y(
            'CAMPO DETALLADO:O',
            sort=top["CAMPO DETALLADO"].tolist(),
            axis=alt.Axis(ticks=False, domain=False, grid=False)
        ),
        alt.Text('emoji:N')
    )
    .transform_calculate(
        emoji=f"{emoji_map}".replace("'", '"') + "[datum['CAMPO DETALLADO']]"
    )
    .properties(width=800, height=400)
)

chart
c:\Users\meagu\anaconda3\Lib\site-packages\altair\utils\core.py:395: FutureWarning: the convert_dtype parameter is deprecated and will be removed in a future version.  Do ``ser.astype(object).apply()`` instead if you want ``convert_dtype=False``.
  col = df[col_name].apply(to_list_if_array, convert_dtype=False)
# Gráfico horizontal SIN eje X y SIN nombre/líneas en el eje Y
chart = (
    alt.Chart(source)
    .mark_text(size=25, baseline='middle')
    .encode(
        alt.X('x:O', axis=None),  # ❌ elimina el eje X completo
        alt.Y(
            'CAMPO DETALLADO:O',
            sort=top["CAMPO DETALLADO"].tolist(),
            axis=alt.Axis(
                ticks=False,
                domain=False,
                grid=False,
                title=None   # ❌ sin nombre del eje Y
            )
        ),
        alt.Text('emoji:N')
    )
    .transform_calculate(
        emoji=f"{emoji_map}".replace("'", '"') + "[datum['CAMPO DETALLADO']]"
    )
    .properties(width=800, height=400)
)

chart
c:\Users\meagu\anaconda3\Lib\site-packages\altair\utils\core.py:395: FutureWarning: the convert_dtype parameter is deprecated and will be removed in a future version.  Do ``ser.astype(object).apply()`` instead if you want ``convert_dtype=False``.
  col = df[col_name].apply(to_list_if_array, convert_dtype=False)