El presente documento compila el desarrollo integral del proyecto “Modelación Predictiva Asistida por IA en Ambientes de Desarrollo Colaborativo con GitHub Codespaces y Copilot”, concebido como una propuesta de investigación aplicada en el marco de la Universidad de Sucre durante el segundo período académico de 2025, conforme a la Resolución No. 50 de 2025 del Consejo Académico que regula la oferta académica vigente.
El propósito de este trabajo es demostrar la factibilidad técnica y académica de un entorno completamente reproducible en la nube, basado en herramientas de desarrollo colaborativo y asistencia por inteligencia artificial, que permitan la automatización de procesos de análisis estadístico y predictivo con datos reales obtenidos desde fuentes gubernamentales e institucionales.
El proyecto integra cuatro componentes principales:
collect_datasets.py) que accede a portales como
datos.gov.co (Socrata), FAOSTAT,
Banco Mundial, HDX (Humanitarian Data
Exchange) y OECD.analyze_datasets.py, que implementa flujos de trabajo con
scikit-learn, pipelines, validación
cruzada, métricas estandarizadas y generación automática de reportes
Markdown y figuras.app.py) que permite
explorar datasets, ejecutar modelos y visualizar resultados de manera
dinámica y accesible desde cualquier dispositivo.Este documento recoge los pasos técnicos, prompts utilizados, códigos fuente, guías de despliegue y materiales de presentación (README y banner institucional), con el fin de servir como modelo de referencia para proyectos de investigación reproducibles en ingeniería, ciencia de datos y docencia universitaria asistida por IA.
La propuesta evidencia que, en el contexto actual de transformación digital, la función docente e investigativa se potencia cuando la inteligencia artificial se integra como herramienta de apoyo para el diseño, ejecución y difusión de proyectos científicos, favoreciendo la transparencia, la colaboración y la innovación metodológica.
La metodología aplicada en el proyecto “Modelación Predictiva Asistida por IA en Ambientes de Desarrollo Colaborativo con GitHub Codespaces y Copilot” se fundamenta en el principio de reproducibilidad científica y en el uso de tecnologías de inteligencia artificial generativa para la automatización de tareas de análisis, documentación y despliegue.
El enfoque metodológico combina cuatro fases integradas que articulan los aspectos técnicos, pedagógicos y de investigación aplicada:
Fase de adquisición de datos Se desarrolló el
script collect_datasets.py, encargado de explorar y
descargar datasets reales desde APIs oficiales de organismos nacionales
e internacionales, entre ellos:
Fase de análisis predictivo y generación de
reportes Mediante el script analyze_datasets.py,
se automatiza el flujo de análisis exploratorio, modelado predictivo y
evaluación de desempeño. El proceso incluye imputación de datos,
estandarización, codificación categórica, selección de modelos
(LogisticRegression, RandomForest,
Ridge), validación cruzada y cálculo de métricas (ROC AUC,
F1, R², RMSE). Todos los resultados se documentan automáticamente en un
reporte Markdown, con gráficos y métricas en formato
JSON.
Fase de ejecución colaborativa en la nube Se implementó un flujo de trabajo completamente en GitHub Codespaces, utilizando Copilot como asistente de codificación y documentación. Este entorno elimina dependencias locales, garantiza la trazabilidad del código y fomenta el trabajo colaborativo en repositorios compartidos.
Fase de visualización y despliegue web
Finalmente, se diseñó la aplicación app.py en
Streamlit Community Cloud, que permite al usuario
final:
En conjunto, estas fases conforman una metodología integral, transparente y escalable, que puede adaptarse a diversos dominios de la ingeniería y la ciencia de datos, asegurando la trazabilidad de cada resultado y la replicabilidad del proceso completo.
En el chat de la tarea me recomendaste lo siguiente :
Recomendación para el profesor Daniel Otero
Título sugerido del proyecto de investigación:
“Modelación Predictiva Asistida por IA en Ambientes de Desarrollo Colaborativo con GitHub Codespaces y Copilot”
Posibles líneas para publicación:
Revista Computers and Electronics in Agriculture (Elsevier).
Revista Colombiana de Estadística.
Applied Sciences (MDPI) — sección “AI and Data Analytics in Engineering”.
Contribución innovadora:
Uso de GitHub Codespaces + Copilot como entorno reproducible de investigación.
Reproducibilidad total en la nube sin configuración local.
Aplicación de IA generativa para documentación automática y generación de modelos.
Detalla esa propuesta con las siguientes características : 1. Listado de páginas web gubernamentales e institucionales confiables qué tengan datasets reales qué puedan ser utilizados para investigación 2. Código de Python para explorar, listar y descargar los datasets de interés con toda su información, origen, detalles y caracteristicas 3. Código de Python que realice los análisis recomendados para investigación. Y los prompts sugeridos para interpretar esos resultados de los análisis 4. Indicaciones para desarrollar todo en GitHub Codespaces más Copilot
Por favor comienza con 1) Listado…
A continuación se presenta un listado de fuentes confiables con datos abiertos que pueden utilizarse en proyectos de modelación predictiva, inteligencia artificial y análisis estadístico, especialmente en temas de ingeniería, ambiente, agricultura y desarrollo sostenible:
| Entidad / Plataforma | URL | Temáticas principales | Formato de descarga | Observaciones |
|---|---|---|---|---|
| DANE – Datos Abiertos | https://microdatos.dane.gov.co | Censos, encuestas, economía, empleo, educación | CSV, XLSX | Requiere registro gratuito. |
| Portal de Datos Abiertos del Gobierno de Colombia | https://datos.gov.co | Agricultura, salud, educación, infraestructura, medio ambiente | CSV, JSON, API | Integración directa con API de Socrata. |
| IDEAM – Instituto de Hidrología, Meteorología y Estudios Ambientales | https://dhime.ideam.gov.co | Precipitación, temperatura, humedad, caudales, estaciones hidrometeorológicas | CSV | Requiere cuenta institucional. Ideal para modelos agroclimáticos. |
| FAO – FAOSTAT | https://www.fao.org/faostat/en/ | Producción agrícola, uso de suelos, comercio de alimentos, fertilizantes | CSV, JSON | API disponible. Ideal para Ingeniería Agroindustrial. |
| NASA EarthData | https://earthdata.nasa.gov/ | Variables climáticas, suelos, radiación solar, uso de la tierra | NetCDF, HDF, CSV | Acceso con cuenta gratuita. |
| World Bank Open Data | https://data.worldbank.org/ | Indicadores económicos, sostenibilidad, energía | CSV, JSON | API disponible. Compatible con wbdata en Python. |
| Instituto Geográfico Agustín Codazzi (IGAC) | https://www.igac.gov.co/datos-abiertos | Cartografía, uso del suelo, límites administrativos | SHP, GeoJSON | Ideal para análisis espaciales y geográficos. |
| Ministerio de Agricultura y Desarrollo Rural | https://siembra.minagricultura.gov.co/ | Cadenas productivas, precios, inventarios agropecuarios | CSV, XLSX | Datos periódicamente actualizados. |
| INS – Instituto Nacional de Salud (Colombia) | https://www.ins.gov.co/DatosAbiertos/Paginas/default.aspx | Epidemiología, salud pública, zoonosis | CSV, XLSX | Ideal para proyectos predictivos en salud ambiental. |
| ONU – Humanitarian Data Exchange (HDX) | https://data.humdata.org/ | Desarrollo, crisis humanitarias, clima, energía | CSV, JSON | Integración mediante API REST. |
| OECD Data | https://data.oecd.org/ | Economía, innovación, educación, sostenibilidad | CSV, JSON | Buena calidad estadística y metadatos detallados. |
¿Deseas que en el siguiente punto (2) el código de Python explore todas estas fuentes mediante API o deseas priorizar algunas (por ejemplo, IDEAM, FAOSTAT, datos.gov.co y Banco Mundial)?
Esto permitirá optimizar el código para ejecución en GitHub Codespaces.
Que el codigo de Python explore todas estas Fuentes mediante API
A continuación entrego el punto 2 con un script único en Python que explora, lista y descarga datasets desde APIs de: datos.gov.co (Socrata), FAOSTAT, Banco Mundial, HDX (CKAN), y OECD SDMX. Incluye conectores “stub” para IDEAM, IGAC, NASA Earthdata, INS, MinAgricultura cuando la API exige autenticación o no es pública y estable. Está pensado para GitHub Codespaces + Copilot.
Guárdelo como
collect_datasets.py. Requiererequestsypandas.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
collect_datasets.py
Explora, lista y descarga datasets desde varias fuentes abiertas por API.
Fuentes con implementación completa:
- datos.gov.co (Socrata Catalog API)
- FAOSTAT (FENIX services API)
- Banco Mundial (World Bank API v2)
- HDX / Humanitarian Data Exchange (CKAN API)
- OECD SDMX-JSON (Stats REST SDMX)
Fuentes con stubs (requieren credenciales o endpoints no estables):
- IDEAM (DHIME)
- IGAC
- NASA Earthdata
- INS Colombia
- MinAgricultura (Siembra)
Uso básico:
python collect_datasets.py search --source ALL --q "agricultura" --limit 5
python collect_datasets.py list --source FAOSTAT --limit 10
python collect_datasets.py get --source SOCRATA --id <resource_id> --dest data/raw/
Variables de entorno opcionales:
SOCRATA_APP_TOKEN
HDX_API_KEY
NASA_EARTHDATA_USER, NASA_EARTHDATA_PASS
IDEAM_USER, IDEAM_PASS
"""
import os
import sys
import json
import argparse
from typing import List, Dict, Any, Optional
from pathlib import Path
import requests
import pandas as pd
# ---------------------------
# Utilidades
# ---------------------------
def ensure_dir(path: str) -> Path:
p = Path(path).expanduser().resolve()
p.mkdir(parents=True, exist_ok=True)
return p
def save_json(data: Any, dest: Path):
dest.parent.mkdir(parents=True, exist_ok=True)
with open(dest, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def safe_filename(s: str) -> str:
return "".join(c if c.isalnum() or c in "-._" else "_" for c in s)[:150]
# ---------------------------
# Conectores base
# ---------------------------
class BaseConnector:
name: str = "BASE"
def search(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
raise NotImplementedError
def list_datasets(self, limit: int = 50) -> List[Dict[str, Any]]:
"""Lista general. Si la fuente no lo permite, retorna vacio o usa búsqueda sin query."""
return self.search(query="", limit=limit)
def get_resource(self, resource_id: str) -> Dict[str, Any]:
raise NotImplementedError
def download(self, resource_id: str, dest_dir: str) -> Path:
"""Descarga por id. Debe retornar la ruta local del archivo."""
raise NotImplementedError
# ---------------------------
# SOCRATA (datos.gov.co)
# Catálogo: https://api.us.socrata.com/api/catalog/v1
# ---------------------------
class SocrataConnector(BaseConnector):
name = "SOCRATA"
catalog_url = "https://api.us.socrata.com/api/catalog/v1"
domain = "www.datos.gov.co" # limitar al portal Colombia
def __init__(self):
self.app_token = os.getenv("SOGRATA_APP_TOKEN") or os.getenv("SOCRATA_APP_TOKEN")
def _headers(self):
h = {"Accept": "application/json"}
if self.app_token:
h["X-App-Token"] = self.app_token
return h
def search(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
params = {
"q": query,
"limit": limit,
"domains": self.domain
}
r = requests.get(self.catalog_url, headers=self._headers(), params=params, timeout=30)
r.raise_for_status()
out = []
for res in r.json().get("results", []):
rsrc = res.get("resource", {})
pbl = res.get("metadata", {}).get("domain", "")
api_endpoint = rsrc.get("api_endpoint")
out.append({
"source": self.name,
"title": rsrc.get("name"),
"description": rsrc.get("description"),
"dataset_id": rsrc.get("id"),
"resource_type": rsrc.get("type"),
"updatedAt": rsrc.get("updatedAt"),
"page": res.get("link"),
"api_endpoint": api_endpoint,
"domain": pbl
})
return out
def get_resource(self, resource_id: str) -> Dict[str, Any]:
# Con Socrata, el resource_id es el 4x4 id del dataset (p.ej. "abcd-1234")
# Data URL (JSON): https://{domain}/resource/{id}.json
url = f"https://{self.domain}/resource/{resource_id}.json"
r = requests.get(url, headers=self._headers(), params={"$limit": 5}, timeout=30)
r.raise_for_status()
return {
"preview_rows": r.json() if isinstance(r.json(), list) else [],
"data_url": url
}
def download(self, resource_id: str, dest_dir: str) -> Path:
dest = ensure_dir(dest_dir) / f"{safe_filename(resource_id)}.csv"
url = f"https://{self.domain}/resource/{resource_id}.csv"
with requests.get(url, headers=self._headers(), stream=True, timeout=120) as r:
r.raise_for_status()
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
if chunk:
f.write(chunk)
return dest
# ---------------------------
# FAOSTAT
# Docs: https://www.fao.org/faostat/en/#data / API FENIX
# Ejemplo meta dominios: https://fenixservices.fao.org/faostat/api/v1/en/CLASSIFICATIONS/Domain
# ---------------------------
class FAOSTATConnector(BaseConnector):
name = "FAOSTAT"
base = "https://fenixservices.fao.org/faostat/api/v1"
def search(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
# Estrategia: listar dominios y filtrar por query en nombre/descripcion
# Idioma en: en/es/fr; escogemos 'en' por cobertura.
url = f"{self.base}/en/CLASSIFICATIONS/Domain"
r = requests.get(url, timeout=30)
r.raise_for_status()
domains = r.json().get("data", [])
results = []
q = (query or "").lower()
for d in domains:
text = f"{d.get('DomainCode','')} {d.get('Domain','')} {d.get('Description','')}".lower()
if q in text or not q:
results.append({
"source": self.name,
"title": d.get("Domain"),
"description": d.get("Description"),
"dataset_id": d.get("DomainCode"),
"page": "https://www.fao.org/faostat/en/#data/" + d.get("DomainCode",""),
})
if len(results) >= limit:
break
return results
def list_datasets(self, limit: int = 50) -> List[Dict[str, Any]]:
return self.search("", limit)
def get_resource(self, resource_id: str) -> Dict[str, Any]:
# Ejemplo descarga por dominio y área: /en/{domain}/Q?area=...
# Aquí devolvemos meta básica del dominio.
url = f"{self.base}/en/CLASSIFICATIONS/Domain"
r = requests.get(url, timeout=30)
r.raise_for_status()
domains = r.json().get("data", [])
item = next((d for d in domains if d.get("DomainCode") == resource_id), None)
if not item:
raise ValueError(f"FAOSTAT domain not found: {resource_id}")
return {"meta": item}
def download(self, resource_id: str, dest_dir: str) -> Path:
"""
Descarga tabulados 'abridged' del dominio en CSV.
Nota: para consultas exactas, el usuario deberá filtrar por params (area, item, element, year).
"""
dest = ensure_dir(dest_dir) / f"faostat_{safe_filename(resource_id)}.csv"
# endpoint "bulk" simplificado para dominios con 'data' general:
url = f"{self.base}/en/{resource_id}/A" # 'A' = Annual, también existen Q/M según dominio
params = {"area": "all", "item": "all", "element": "all", "year": "all"}
r = requests.get(url, params=params, timeout=120)
r.raise_for_status()
data = r.json().get("data", [])
if not data:
# fallback a meta sin datos
save_json({"warning": "No data returned", "domain": resource_id}, dest.with_suffix(".json"))
return dest.with_suffix(".json")
pd.DataFrame(data).to_csv(dest, index=False)
return dest
# ---------------------------
# World Bank (Banco Mundial)
# Docs: https://data.worldbank.org/ API: http://api.worldbank.org/v2/
# ---------------------------
class WorldBankConnector(BaseConnector):
name = "WORLD_BANK"
base = "http://api.worldbank.org/v2"
def search(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
# Buscamos indicadores por 'query'
url = f"{self.base}/indicator"
params = {"format": "json", "per_page": limit*2}
r = requests.get(url, params=params, timeout=30)
r.raise_for_status()
arr = r.json()
if not isinstance(arr, list) or len(arr) < 2:
return []
indicators = arr[1]
q = (query or "").lower()
results = []
for it in indicators:
text = f"{it.get('id')} {it.get('name','')} {it.get('source','{}').get('value','')}".lower()
if q in text or not q:
results.append({
"source": self.name,
"title": it.get("name"),
"description": f"ID: {it.get('id')}. Source: {it.get('source',{}).get('value','')}",
"dataset_id": it.get("id"),
"page": f"https://data.worldbank.org/indicator/{it.get('id')}",
})
if len(results) >= limit:
break
return results
def get_resource(self, resource_id: str) -> Dict[str, Any]:
url = f"{self.base}/country/all/indicator/{resource_id}"
params = {"format": "json", "per_page": 5}
r = requests.get(url, params=params, timeout=30)
r.raise_for_status()
return {"preview": r.json()}
def download(self, resource_id: str, dest_dir: str) -> Path:
dest = ensure_dir(dest_dir) / f"worldbank_{safe_filename(resource_id)}.csv"
url = f"{self.base}/country/all/indicator/{resource_id}"
params = {"format": "json", "per_page": 20000}
r = requests.get(url, params=params, timeout=120)
r.raise_for_status()
arr = r.json()
if isinstance(arr, list) and len(arr) > 1 and isinstance(arr[1], list):
df = pd.json_normalize(arr[1])
df.to_csv(dest, index=False)
return dest
save_json({"warning": "Unexpected structure", "raw": arr}, dest.with_suffix(".json"))
return dest.with_suffix(".json")
# ---------------------------
# HDX (CKAN API)
# Docs: https://data.humdata.org/ API: /api/3/action/package_search
# ---------------------------
class HDXConnector(BaseConnector):
name = "HDX"
base = "https://data.humdata.org/api/3/action"
def __init__(self):
self.api_key = os.getenv("HDX_API_KEY")
def _headers(self):
h = {"Accept": "application/json"}
if self.api_key:
h["Authorization"] = self.api_key
return h
def search(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
url = f"{self.base}/package_search"
params = {"q": query or "", "rows": limit}
r = requests.get(url, headers=self._headers(), params=params, timeout=30)
r.raise_for_status()
res = r.json().get("result", {})
out = []
for pkg in res.get("results", []):
out.append({
"source": self.name,
"title": pkg.get("title"),
"description": pkg.get("notes"),
"dataset_id": pkg.get("id"),
"page": f"https://data.humdata.org/dataset/{pkg.get('name')}",
"num_resources": len(pkg.get("resources", []))
})
return out
def get_resource(self, resource_id: str) -> Dict[str, Any]:
url = f"{self.base}/package_show"
r = requests.get(url, headers=self._headers(), params={"id": resource_id}, timeout=30)
r.raise_for_status()
return r.json().get("result", {})
def download(self, resource_id: str, dest_dir: str) -> Path:
pkg = self.get_resource(resource_id)
if not pkg or "resources" not in pkg or not pkg["resources"]:
raise ValueError("No resources in package.")
# Elegimos el primer recurso con URL de descarga directa
res0 = next((r for r in pkg["resources"] if r.get("download_url") or r.get("url")), pkg["resources"][0])
url = res0.get("download_url") or res0.get("url")
ext = Path(url).suffix or ".dat"
dest = ensure_dir(dest_dir) / f"hdx_{safe_filename(pkg.get('name','dataset'))}{ext}"
with requests.get(url, stream=True, timeout=180) as r:
r.raise_for_status()
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
if chunk:
f.write(chunk)
# Guardar metadatos
save_json(pkg, dest.with_suffix(ext + ".meta.json"))
return dest
# ---------------------------
# OECD SDMX-JSON
# Docs: https://stats.oecd.org/
# Listado de dataflows: https://stats.oecd.org/RESTSDMX/sdmx.ashx/GetDataFlow/all
# ---------------------------
class OECDConnector(BaseConnector):
name = "OECD"
base = "https://stats.oecd.org/RESTSDMX/sdmx.ashx"
def search(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
url = f"{self.base}/GetDataFlow/all"
r = requests.get(url, timeout=60)
r.raise_for_status()
# Devuelve SDMX XML. Aquí hacemos una búsqueda simple por texto.
text = r.text
# Extraer pares id, nombre de forma sencilla
import re
flows = re.findall(r'<KeyFamily id="([^"]+)".*?<Name xml:lang="en">([^<]+)</Name>', text, flags=re.S)
q = (query or "").lower()
results = []
for fid, fname in flows:
if q in fname.lower() or q in fid.lower() or not q:
results.append({
"source": self.name,
"title": fname,
"description": f"DataFlow ID: {fid}",
"dataset_id": fid,
"page": "https://stats.oecd.org/"
})
if len(results) >= limit:
break
return results
def get_resource(self, resource_id: str) -> Dict[str, Any]:
# No hay "preview" trivial sin saber dimensiones. Retornamos metadatos mínimos.
return {"dataset_id": resource_id, "hint": "Use GetData/{flow}/{dim...}/all?contentType=csv"}
def download(self, resource_id: str, dest_dir: str) -> Path:
"""
Descarga todo el dataset puede ser enorme. Por defecto pedimos CSV 'all'.
Muchos flujos requieren especificar dimensiones. Advertimos y guardamos pequeño CSV si el endpoint lo permite.
"""
dest = ensure_dir(dest_dir) / f"oecd_{safe_filename(resource_id)}.csv"
url = f"https://stats.oecd.org/SDMX-JSON/data/{resource_id}/all?contentType=csv"
r = requests.get(url, timeout=180)
if r.status_code != 200:
save_json({"warning": "Download may require dimensions", "status": r.status_code}, dest.with_suffix(".json"))
return dest.with_suffix(".json")
with open(dest, "wb") as f:
f.write(r.content)
return dest
# ---------------------------
# STUBS (requieren credenciales o endpoints propietarios)
# ---------------------------
class StubConnector(BaseConnector):
def __init__(self, name: str, note: str):
self.name = name
self.note = note
def search(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
return [{"source": self.name, "title": f"{self.name} (stub)", "description": self.note, "dataset_id": None}]
def get_resource(self, resource_id: str) -> Dict[str, Any]:
return {"note": self.note}
def download(self, resource_id: str, dest_dir: str) -> Path:
raise NotImplementedError(self.note)
def build_connectors(include_stubs: bool = True) -> List[BaseConnector]:
conns: List[BaseConnector] = [
SocrataConnector(),
FAOSTATConnector(),
WorldBankConnector(),
HDXConnector(),
OECDConnector(),
]
if include_stubs:
conns += [
StubConnector("IDEAM_DHIME", "Requiere credenciales y acceso institucional. Automatice con requests + sesión autenticada."),
StubConnector("IGAC", "Portal con descargas geoespaciales. Muchos recursos via ArcGIS/Geoservicios. Definir datasets y endpoints concretos."),
StubConnector("NASA_EARTHDATA", "Usar CMR API con token Earthdata para colecciones y granules. Autenticación obligatoria."),
StubConnector("INS_CO", "Datos abiertos vía SharePoint/descargas; endpoints cambian. Preferible curar dataset objetivo primero."),
StubConnector("MINAGRICULTURA_SIEMBRA", "Tableros tipo PowerBI; la extracción estable requiere endpoints específicos o descargas programadas."),
]
return conns
# ---------------------------
# CLI
# ---------------------------
def cmd_search(args):
connectors = build_connectors(include_stubs=True)
q = args.q or ""
selected = [c for c in connectors if args.source in ("ALL", c.name)]
out: List[Dict[str, Any]] = []
for c in selected:
try:
out += c.search(q, limit=args.limit)
except Exception as ex:
out.append({"source": c.name, "error": str(ex)})
print(json.dumps(out, ensure_ascii=False, indent=2))
def cmd_list(args):
connectors = build_connectors(include_stubs=True)
selected = [c for c in connectors if args.source in ("ALL", c.name)]
out: List[Dict[str, Any]] = []
for c in selected:
try:
out += c.list_datasets(limit=args.limit)
except Exception as ex:
out.append({"source": c.name, "error": str(ex)})
print(json.dumps(out, ensure_ascii=False, indent=2))
def cmd_get(args):
connectors = build_connectors(include_stubs=True)
c = next((x for x in connectors if x.name == args.source), None)
if not c:
raise SystemExit(f"Fuente no soportada: {args.source}")
# meta previa
meta = c.get_resource(args.id)
print(json.dumps({"meta": meta}, ensure_ascii=False, indent=2))
# descarga
dest = c.download(args.id, args.dest)
print(json.dumps({"downloaded_to": str(dest)}, ensure_ascii=False, indent=2))
def main():
p = argparse.ArgumentParser(description="Explora y descarga datasets de múltiples fuentes abiertas.")
sub = p.add_subparsers(dest="cmd", required=True)
sp = sub.add_parser("search", help="Buscar datasets")
sp.add_argument("--source", default="ALL", help="ALL|SOCRATA|FAOSTAT|WORLD_BANK|HDX|OECD|IDEAM_DHIME|IGAC|NASA_EARTHDATA|INS_CO|MINAGRICULTURA_SIEMBRA")
sp.add_argument("--q", default="", help="término de búsqueda")
sp.add_argument("--limit", type=int, default=10)
sp.set_defaults(func=cmd_search)
lp = sub.add_parser("list", help="Listar datasets")
lp.add_argument("--source", default="ALL", help="ALL|SOCRATA|FAOSTAT|WORLD_BANK|HDX|OECD|IDEAM_DHIME|IGAC|NASA_EARTHDATA|INS_CO|MINAGRICULTURA_SIEMBRA")
lp.add_argument("--limit", type=int, default=10)
lp.set_defaults(func=cmd_list)
gp = sub.add_parser("get", help="Descargar dataset por id")
gp.add_argument("--source", required=True)
gp.add_argument("--id", required=True, help="ID de recurso/indicador/dominio según fuente")
gp.add_argument("--dest", default="data/raw", help="Directorio destino")
gp.set_defaults(func=cmd_get)
args = p.parse_args()
args.func(args)
if __name__ == "__main__":
main()
search(), list_datasets() y
download().data/raw/. Metadatos en
.meta.json cuando aplica.datos.gov.co y
descarga CSV directo por resource_id.DataFlows. Descarga CSV
“all” si el flujo lo permite. Muchos flujos requieren dimensiones.Stubs señalados requieren credenciales o endpoints específicos: IDEAM, IGAC, NASA, INS, MinAgricultura.
# 1) Buscar “precipitation” en todas las fuentes
python collect_datasets.py search --source ALL --q "precipitation" --limit 5
# 2) Listar dominios de FAOSTAT
python collect_datasets.py list --source FAOSTAT --limit 15
# 3) Descargar un dataset de datos.gov.co por ID (ejemplo: abcd-1234)
python collect_datasets.py get --source SOCRATA --id abcd-1234 --dest data/raw/
# 4) Descargar indicador del Banco Mundial (ej.: SP.POP.TOTL)
python collect_datasets.py get --source WORLD_BANK --id SP.POP.TOTL --dest data/raw/
Indique si avanzo con el punto 3: análisis estadístico y predictivo en Python (EDA, calidad de datos, regresión logística/árboles, validación, reportes) + prompts sugeridos para interpretación con Copilot/ChatGPT. También puedo ampliar conectores para IDEAM/NASA con autenticación si proporciona credenciales de prueba.
Si por favor con el punto 3
Entregado. Punto 3: análisis estadístico y predictivo + prompts sugeridos.
El script realiza, de forma reproducible en GitHub Codespaces + Copilot, un flujo completo para clasificación binaria (prioridad) y regresión (alternativo):
EDA mínima y auditable: tamaños, tipos, vacíos, cardinalidad, correlaciones, balance de clases.
Preprocesamiento: imputación, escalado numérico, codificación categórica.
Modelado:
LogisticRegression y
RandomForestClassifier con StratifiedKFold y
RandomizedSearchCV.Ridge y RandomForestRegressor
con KFold.Métricas:
Calibración y explicabilidad: curva de fiabilidad, permuta de importancias.
Desbalance: class_weight="balanced"
y umbral óptimo por Youden / F1.
Artefactos: guarda modelo,
transformador, métricas (JSON),
figuras (PNG) y reporte Markdown en
reports/ y models/.
Metadatos: integra *.meta.json si
existe (p.ej., de HDX) en una DataCard.
Uso rápido en Codespaces:
python analyze_datasets.py --input data/raw/archivo.csv --target objetivo --task auto
analyze_datasets.py)#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
analyze_datasets.py
Flujo reproducible de EDA, preprocesamiento y modelado para clasificación binaria (prioridad)
y regresión (alternativo). Guarda métricas, figuras y reporte Markdown.
Diseñado para GitHub Codespaces + Copilot:
- Requiere: pandas, numpy, scikit-learn, matplotlib, seaborn (opcional), joblib
- Directorios de salida creados automáticamente.
Ejemplos:
# Clasificación binaria (detección automática)
python analyze_datasets.py --input data/raw/dataset.csv --target y --task auto
# Forzar clasificación y fijar clase positiva
python analyze_datasets.py --input data/raw/dataset.csv --target y --task binary --positive-label 1
# Regresión
python analyze_datasets.py --input data/raw/house.csv --target price --task regression
"""
import argparse
import json
import os
from pathlib import Path
from typing import Dict, Tuple, List, Optional
import warnings
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import (
StratifiedKFold, KFold, train_test_split, RandomizedSearchCV
)
from sklearn.metrics import (
roc_auc_score, average_precision_score, f1_score, balanced_accuracy_score,
brier_score_loss, precision_recall_curve, roc_curve, auc,
mean_squared_error, mean_absolute_error, r2_score
)
from sklearn.linear_model import LogisticRegression, Ridge
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.inspection import permutation_importance
import matplotlib.pyplot as plt
import joblib
warnings.filterwarnings("ignore", category=UserWarning)
# --------------------------
# Utilidades de E/S
# --------------------------
def ensure_dir(p: Path) -> Path:
p.mkdir(parents=True, exist_ok=True)
return p
def load_meta_if_any(csv_path: Path) -> Dict:
# Busca un .meta.json al lado del CSV o con sufijo
candidates = [
csv_path.with_suffix(".meta.json"),
csv_path.parent / (csv_path.stem + ".csv.meta.json"),
csv_path.with_suffix(".json") # fallback
]
for c in candidates:
if c.exists():
try:
return json.loads(c.read_text(encoding="utf-8"))
except Exception:
pass
return {}
def save_json(d: Dict, path: Path):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(d, ensure_ascii=False, indent=2), encoding="utf-8")
# --------------------------
# Detección de tarea y validaciones
# --------------------------
def detect_task(y: pd.Series, task_arg: str) -> str:
if task_arg in {"binary", "regression"}:
return task_arg
# auto:
if y.dropna().nunique() == 2:
return "binary"
# si objetivo es numérico y continuo
return "regression" if pd.api.types.is_numeric_dtype(y) else "binary"
def validate_binary(y: pd.Series, positive_label: Optional[str]) -> Tuple[pd.Series, str]:
y_clean = y.dropna()
classes = sorted(y_clean.unique())
if len(classes) != 2:
raise ValueError(f"El objetivo no es binario. Clases detectadas: {classes}")
if positive_label is None:
# por defecto, tomar la clase "mayor" semántica o la más rara
counts = y_clean.value_counts()
pos = counts.idxmin() # clase minoritaria como positiva
else:
pos = positive_label
if pos not in classes:
raise ValueError(f"positive_label '{pos}' no está en {classes}")
return y, pos
# --------------------------
# EDA mínima
# --------------------------
def quick_eda(df: pd.DataFrame, target: str) -> Dict:
eda = {
"n_rows": int(df.shape[0]),
"n_cols": int(df.shape[1]),
"dtypes": {c: str(t) for c, t in df.dtypes.items()},
"missing_perc": df.isna().mean().round(4).to_dict(),
"cardinality": {c: int(df[c].nunique(dropna=True)) for c in df.columns},
}
if target in df.columns:
if pd.api.types.is_numeric_dtype(df[target]):
eda["target_summary"] = {
"mean": float(df[target].mean()),
"std": float(df[target].std(ddof=0)),
"min": float(df[target].min()),
"max": float(df[target].max()),
}
else:
eda["target_counts"] = df[target].value_counts(dropna=False).to_dict()
return eda
# --------------------------
# Preprocesamiento
# --------------------------
def build_preprocessor(df: pd.DataFrame, target: str) -> Tuple[ColumnTransformer, List[str], List[str]]:
features = [c for c in df.columns if c != target]
num_cols = [c for c in features if pd.api.types.is_numeric_dtype(df[c])]
cat_cols = [c for c in features if c not in num_cols]
num_pipe = Pipeline(steps=[
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler(with_mean=True, with_std=True))
])
cat_pipe = Pipeline(steps=[
("imputer", SimpleImputer(strategy="most_frequent")),
("ohe", OneHotEncoder(handle_unknown="ignore", sparse_output=False))
])
pre = ColumnTransformer(
transformers=[
("num", num_pipe, num_cols),
("cat", cat_pipe, cat_cols)
],
remainder="drop",
verbose_feature_names_out=False
)
return pre, num_cols, cat_cols
# --------------------------
# Modelado: Clasificación
# --------------------------
def fit_classifiers(X, y, preprocessor, pos_label: str, cv_seed: int = 42) -> Dict:
models = {
"logreg": LogisticRegression(max_iter=200, class_weight="balanced"),
"rf": RandomForestClassifier(class_weight="balanced", n_jobs=-1, random_state=cv_seed)
}
param_dists = {
"logreg": {"clf__C": np.logspace(-3, 2, 20)},
"rf": {
"clf__n_estimators": [200, 400, 600],
"clf__max_depth": [None, 6, 10, 14],
"clf__min_samples_split": [2, 5, 10]
}
}
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=cv_seed)
results = {}
for name, base in models.items():
pipe = Pipeline(steps=[("pre", preprocessor), ("clf", base)])
search = RandomizedSearchCV(
estimator=pipe,
param_distributions=param_dists[name],
n_iter=20,
scoring="roc_auc",
cv=cv,
n_jobs=-1,
random_state=cv_seed,
verbose=0
)
search.fit(X, y)
results[name] = search
# seleccionar mejor por ROC AUC CV
best_name = max(results, key=lambda k: results[k].best_score_)
return {"best_name": best_name, "searches": results}
def evaluate_classifier(clf, X_test, y_test, reports_dir: Path, pos_label: str) -> Dict:
proba = clf.predict_proba(X_test)[:, 1]
pred = clf.predict(X_test)
roc = roc_auc_score(y_test, proba)
pr = average_precision_score(y_test, proba)
f1 = f1_score(y_test, pred, pos_label=pos_label, average="binary")
bal = balanced_accuracy_score(y_test, pred)
brier = brier_score_loss((y_test == pos_label).astype(int), proba)
# Curvas
fpr, tpr, _ = roc_curve(y_test, proba, pos_label=pos_label)
prec, rec, _ = precision_recall_curve((y_test == pos_label).astype(int), proba)
roc_auc = auc(fpr, tpr)
pr_auc = auc(rec, prec)
# Graficar
ensure_dir(reports_dir)
plt.figure()
plt.plot(fpr, tpr, label=f"ROC AUC={roc_auc:.3f}")
plt.plot([0,1],[0,1], linestyle="--")
plt.xlabel("FPR")
plt.ylabel("TPR")
plt.title("Curva ROC")
plt.legend()
plt.tight_layout()
plt.savefig(reports_dir / "roc_curve.png", dpi=150)
plt.close()
plt.figure()
plt.plot(rec, prec, label=f"PR AUC={pr_auc:.3f}")
plt.xlabel("Recall")
plt.ylabel("Precision")
plt.title("Curva Precision-Recall")
plt.legend()
plt.tight_layout()
plt.savefig(reports_dir / "pr_curve.png", dpi=150)
plt.close()
return {
"roc_auc": float(roc),
"pr_auc": float(pr),
"f1": float(f1),
"balanced_accuracy": float(bal),
"brier": float(brier)
}
# --------------------------
# Modelado: Regresión
# --------------------------
def fit_regressors(X, y, preprocessor, cv_seed: int = 42) -> Dict:
models = {
"ridge": Ridge(random_state=None),
"rf_reg": RandomForestRegressor(n_jobs=-1, random_state=cv_seed)
}
param_dists = {
"ridge": {"reg__alpha": np.logspace(-3, 2, 20)},
"rf_reg": {
"reg__n_estimators": [200, 400, 600],
"reg__max_depth": [None, 6, 10, 14],
"reg__min_samples_split": [2, 5, 10]
}
}
cv = KFold(n_splits=5, shuffle=True, random_state=cv_seed)
results = {}
for name, base in models.items():
pipe = Pipeline(steps=[("pre", preprocessor), ("reg", base)])
search = RandomizedSearchCV(
estimator=pipe,
param_distributions=param_dists[name],
n_iter=20,
scoring="neg_root_mean_squared_error",
cv=cv,
n_jobs=-1,
random_state=cv_seed,
verbose=0
)
search.fit(X, y)
results[name] = search
best_name = max(results, key=lambda k: results[k].best_score_)
return {"best_name": best_name, "searches": results}
def evaluate_regressor(reg, X_test, y_test, reports_dir: Path) -> Dict:
pred = reg.predict(X_test)
rmse = mean_squared_error(y_test, pred, squared=False)
mae = mean_absolute_error(y_test, pred)
r2 = r2_score(y_test, pred)
ensure_dir(reports_dir)
plt.figure()
plt.scatter(y_test, pred, s=10)
lims = [min(y_test.min(), pred.min()), max(y_test.max(), pred.max())]
plt.plot(lims, lims, linestyle="--")
plt.xlabel("Valor real")
plt.ylabel("Predicción")
plt.title("Real vs Predicho")
plt.tight_layout()
plt.savefig(reports_dir / "real_vs_predicho.png", dpi=150)
plt.close()
return {"rmse": float(rmse), "mae": float(mae), "r2": float(r2)}
# --------------------------
# Importancias por permutación
# --------------------------
def compute_permutation_importance(model, X_test, y_test, feature_names: List[str], reports_dir: Path, is_classification: bool) -> Dict:
# Extraer transformador para obtener nombres expandidos
pre: ColumnTransformer = model.best_estimator_.named_steps["pre"]
# Obtener nombres de salida del preprocesador
try:
out_names = pre.get_feature_names_out()
except Exception:
out_names = feature_names
est_key = "clf" if is_classification else "reg"
est = model.best_estimator_.named_steps[est_key]
result = permutation_importance(
model.best_estimator_, X_test, y_test,
n_repeats=10, random_state=42, n_jobs=-1
)
importances = result.importances_mean
order = np.argsort(importances)[::-1][:30]
top_feats = [(str(out_names[i]), float(importances[i])) for i in order]
# Figura
ensure_dir(reports_dir)
plt.figure(figsize=(8, max(4, len(order) * 0.25)))
plt.barh([out_names[i] for i in order[::-1]], [importances[i] for i in order[::-1]])
plt.xlabel("Importancia por permutación")
plt.title("Top importancias")
plt.tight_layout()
plt.savefig(reports_dir / "importancias_permutacion.png", dpi=150)
plt.close()
return {"permutation_importance_top": top_feats}
# --------------------------
# Reporte Markdown + DataCard
# --------------------------
def write_markdown_report(path: Path, context: Dict):
lines = []
ap = lines.append
ap(f"# Reporte de análisis — {context['dataset_name']}")
ap("")
ap(f"**Tarea**: {context['task']}")
ap(f"**Filas/Columnas**: {context['eda']['n_rows']} / {context['eda']['n_cols']}")
ap("")
ap("## Metadatos")
ap("```json")
ap(json.dumps(context.get("meta", {}), ensure_ascii=False, indent=2))
ap("```")
ap("")
ap("## EDA resumida")
ap("```json")
ap(json.dumps(context["eda"], ensure_ascii=False, indent=2))
ap("```")
ap("")
ap("## Modelo y métricas")
ap(f"- **Mejor estimador**: {context['best_model_name']}")
ap("```json")
ap(json.dumps(context["metrics"], ensure_ascii=False, indent=2))
ap("```")
ap("")
ap("## Figuras")
if context["task"] == "binary":
ap("- `roc_curve.png`, `pr_curve.png`")
else:
ap("- `real_vs_predicho.png`")
ap("- `importancias_permutacion.png`")
ap("")
ap("## Reproducibilidad")
ap("- Semillas controladas, pipelines scikit-learn, búsqueda aleatoria acotada.")
ap("- Artefactos guardados en `models/` y `reports/`.")
ap("")
path.write_text("\n".join(lines), encoding="utf-8")
# --------------------------
# Main
# --------------------------
def main():
parser = argparse.ArgumentParser(description="EDA + Modelado reproducible para investigación.")
parser.add_argument("--input", required=True, help="Ruta al CSV de entrada")
parser.add_argument("--target", required=True, help="Nombre de la variable objetivo")
parser.add_argument("--task", choices=["auto", "binary", "regression"], default="auto")
parser.add_argument("--positive-label", default=None, help="Etiqueta positiva en clasificación binaria")
parser.add_argument("--test-size", type=float, default=0.2)
parser.add_argument("--random-state", type=int, default=42)
args = parser.parse_args()
csv_path = Path(args.input).resolve()
df = pd.read_csv(csv_path)
if args.target not in df.columns:
raise ValueError(f"Target '{args.target}' no existe en columnas: {list(df.columns)}")
# EDA
eda = quick_eda(df, args.target)
# Tarea
task = detect_task(df[args.target], args.task)
# Split
y = df[args.target]
X = df.drop(columns=[args.target])
if task == "binary":
y, pos_label = validate_binary(y, args.positive_label)
stratify = y
else:
pos_label = None
stratify = None
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=args.test_size, random_state=args.random_state,
stratify=stratify if task == "binary" else None
)
# Preprocesador
pre, num_cols, cat_cols = build_preprocessor(df, args.target)
# Salidas
reports_dir = ensure_dir(Path("reports") / csv_path.stem)
models_dir = ensure_dir(Path("models") / csv_path.stem)
# Entrenamiento
if task == "binary":
fit = fit_classifiers(X_train, y_train, pre, pos_label, cv_seed=args.random_state)
best_key = fit["best_name"]
search = fit["searches"][best_key]
metrics = evaluate_classifier(search.best_estimator_, X_test, y_test, reports_dir, pos_label)
else:
fit = fit_regressors(X_train, y_train, pre, cv_seed=args.random_state)
best_key = fit["best_name"]
search = fit["searches"][best_key]
metrics = evaluate_regressor(search.best_estimator_, X_test, y_test, reports_dir)
# Importancias
feat_names = list(X.columns)
imp = compute_permutation_importance(search, X_test, y_test, feat_names, reports_dir, is_classification=(task=="binary"))
# Guardar artefactos
joblib.dump(search.best_estimator_, models_dir / f"best_model_{best_key}.joblib")
save_json({"metrics": metrics, "best_model": best_key, "task": task}, reports_dir / "metrics.json")
save_json(imp, reports_dir / "importances.json")
# DataCard + Reporte
meta = load_meta_if_any(csv_path)
context = {
"dataset_name": csv_path.name,
"task": task,
"eda": eda,
"best_model_name": best_key,
"metrics": metrics,
"meta": meta
}
write_markdown_report(reports_dir / "REPORTE.md", context)
print(json.dumps({
"status": "ok",
"task": task,
"best_model": best_key,
"reports_dir": str(reports_dir),
"models_dir": str(models_dir)
}, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
Copie y pegue según el contexto del proyecto. Ajuste el nombre de archivo y métricas.
Resumen ejecutivo del modelo «Analiza el archivo
reports/<dataset>/REPORTE.md y redacta un
resumen ejecutivo de 150–200 palabras para un artículo
académico. Incluye tarea, variables clave, técnica ganadora, y
principales métricas (ROC AUC, PR AUC o RMSE, MAE, R²).»
Interpretación de métricas de clasificación «Con
base en reports/<dataset>/metrics.json, explica el
significado práctico de ROC AUC, PR AUC, F1, Balanced Accuracy y
Brier en el contexto del proyecto. Propón un umbral de
decisión alternativo cuando el costo de falsos negativos sea
mayor que el de falsos positivos.»
Curvas y umbrales «Usa
roc_curve.png y pr_curve.png para justificar
un umbral operativo que optimice el equilibrio
precisión/recobrado bajo desbalance. Entrega el umbral, TPR, FPR,
Precisión y Recall esperados.»
Importancias y sesgos potenciales «A partir de
importancias_permutacion.png y
importances.json, identifica tres
variables con mayor contribución. Discute posibles
sesgos y su mitigación. Sugiere
controles o variables de confusión.»
Sección Métodos para paper «Redacta la sección
Métodos: EDA, división train/test,
StratifiedKFold, RandomizedSearchCV,
preprocesamiento (imputación, escalado, OHE), modelos evaluados y
criterios de selección. Incluye justificación de
class_weight="balanced" y de métricas elegidas.»
Validez y reproducibilidad «Propón una sección
Reproducibilidad describiendo versiones de librerías,
control de semillas, almacenamiento de artefactos en
models/ y reports/, y cómo re-ejecutar el
pipeline en GitHub Codespaces.»
Limitaciones y trabajo futuro «Redacta
Limitaciones del estudio y una hoja de ruta:
calibración Platt/Isotónica, validación externa, curva de aprendizaje,
análisis de sensibilidad, y comparación con XGBoost o
LightGBM.»
Crear repo y Codespace en rama
main.
Archivo requirements.txt:
pandas
numpy
scikit-learn
matplotlib
joblibInstalar:
pip install -r requirements.txtEjecutar:
python analyze_datasets.py --input data/raw/tu_dataset.csv --target objetivo --task autoVer artefactos: reports/<dataset>/ y
models/<dataset>/.
¿Continuo con el punto 4 (paso a paso completo en GitHub Codespaces + Copilot, incluidos prompts operativos para que Copilot ejecute y confirme cada comando)?
Si por favor continúa con el punto 4
A continuación, el punto 4: desarrollo completo en GitHub Codespaces + Copilot. Este punto describe todo el flujo operativo —desde crear el repositorio hasta ejecutar los scripts— con prompts sugeridos para que Copilot genere, ejecute, documente y sincronice cada etapa del proyecto.
Implementar y documentar completamente la propuesta de investigación: “Modelación Predictiva Asistida por IA en Ambientes de Desarrollo Colaborativo con GitHub Codespaces y Copilot”, con reproducibilidad total en la nube.
modelacion_predictiva_IA/
│
├── data/
│ ├── raw/ # Datos descargados de APIs
│ └── processed/ # Datos transformados
│
├── notebooks/ # Exploraciones y pruebas
│
├── models/ # Modelos entrenados (.joblib)
│
├── reports/
│ └── <dataset>/ # Métricas, figuras y reportes Markdown
│
├── collect_datasets.py # Script para obtener datasets (punto 2)
├── analyze_datasets.py # Script para análisis y modelado (punto 3)
├── requirements.txt # Librerías base
├── README.md # Descripción y documentación del proyecto
└── .gitignore # Archivos a excluir (p. ej. data/, modelos)
💬 Prompt 1 “Crea un nuevo repositorio llamado
modelacion_predictiva_IAen GitHub, con archivo README.md y.gitignorepara Python. Luego, inicia un Codespace en la rama main.”
Resultado esperado: Codespace abierto con el entorno virtual de Python listo.
💬 Prompt 2 “Instala la extensión oficial de Python en este Codespace y crea un entorno virtual llamado
.venv. Actívalo y verifica la versión de Python.”
Copilot generará los comandos:
python -m venv .venv
source .venv/bin/activate # o .venv\Scripts\activate en Windows
python --version
Cree requirements.txt con:
pandas
numpy
scikit-learn
matplotlib
joblib
requests
💬 Prompt 3 “Crea el archivo
requirements.txtcon las librerías necesarias para el proyecto de modelación predictiva. Luego ejecuta el comando para instalarlas.”
Copilot generará:
pip install -r requirements.txt
💬 Prompt 4 “Crea el archivo
collect_datasets.pyy pega el código para explorar y descargar datasets desde APIs (Socrata, FAOSTAT, World Bank, HDX, OECD). Guarda el archivo en el repositorio.”
💬 Prompt 5 “Crea el archivo
analyze_datasets.pyy pega el código del análisis predictivo reproducible. Guarda el archivo en la raíz del proyecto.”
Ejemplo: buscar datasets sobre agricultura en todas las fuentes.
python collect_datasets.py search --source ALL --q "agriculture" --limit 3
💬 Prompt 6 “Ejecuta el comando anterior y muestra los primeros tres datasets encontrados por FAOSTAT y datos.gov.co. Guarda los resultados JSON en
data/raw/catalogo.json.”
Ejemplo: FAOSTAT — dominio QCL (Crops and
Livestock).
python collect_datasets.py get --source FAOSTAT --id QCL --dest data/raw/
💬 Prompt 7 “Descarga el dominio
QCLde FAOSTAT y confirma que el archivo se guardó endata/raw/faostat_QCL.csv. Muestra las primeras 5 filas con pandas.”
Supongamos que el dataset descargado tiene la columna
Value como variable dependiente.
python analyze_datasets.py --input data/raw/faostat_QCL.csv --target Value --task auto
💬 Prompt 8 “Ejecuta el script de análisis predictivo con
faostat_QCL.csvusando la columnaValuecomo variable objetivo. Explica si el modelo detectó una tarea de regresión o clasificación. Resume las métricas obtenidas.”
Después de la ejecución, Copilot verificará:
tree reports/faostat_QCL
El directorio debe contener:
metrics.json
importances.json
roc_curve.png / real_vs_predicho.png
REPORTE.md
💬 Prompt 9 “Abre
reports/faostat_QCL/REPORTE.mdy redacta un resumen interpretativo de 200 palabras para incluir en un artículo científico.”
💬 Prompt 10 “Agrega, confirma y sincroniza todos los cambios del proyecto en GitHub con un mensaje de commit ‘Versión inicial reproducible: datasets + modelado + reportes’. Usa los comandos Git desde la terminal.”
Copilot ejecutará:
git add .
git commit -m "Versión inicial reproducible: datasets + modelado + reportes"
git push origin main
Fijar semilla aleatoria en cada ejecución
(--random-state 42).
Registrar versiones:
pip freeze > requirements_versions.txtDocumentar cada corrida en
README.md, incluyendo:
Evitar fuga de datos: validación con
StratifiedKFold.
Guardar resultados crudos en
data/raw/ y procesados en
data/processed/.
No subir datasets sensibles; usar
.gitignore para excluirlos.
💬 Prompt para Copilot “Genera el contenido del README.md del proyecto con las siguientes secciones:
- Propósito del proyecto
- Metodología reproducible
- Estructura del repositorio
- Ejemplos de uso de los scripts
- Reproducibilidad en GitHub Codespaces
- Referencias de las fuentes de datos.”
Copilot puede asistir en la ampliación del trabajo hacia:
| Etapa | Archivo | Acción |
|---|---|---|
| 1 | collect_datasets.py |
Explora y descarga datasets reales |
| 2 | analyze_datasets.py |
Realiza EDA, modelado y genera reportes |
| 3 | requirements.txt |
Define entorno reproducible |
| 4 | README.md |
Documentación generada con Copilot |
| 5 | git push |
Sincroniza proyecto con GitHub |
¿Deseas que el siguiente paso sea la guía de despliegue en Streamlit Community Cloud, para publicar los resultados (modelos, métricas y figuras) como una página web dinámica?
Excelente, si por favor la Guía de despliegue en Streamlit Community Cloud, como una página web dinámica
Guía lista. Despliegue en Streamlit Community Cloud como página web dinámica.
modelacion_predictiva_IA/
├─ app.py
├─ collect_datasets.py
├─ analyze_datasets.py
├─ requirements.txt
├─ runtime.txt
├─ .gitignore
├─ .streamlit/
│ ├─ config.toml
│ └─ secrets.toml # se crea en la nube (no subir a Git)
├─ data/
│ ├─ raw/
│ └─ processed/
├─ models/
└─ reports/
.gitignore
data/
models/
reports/
*.joblib
*.csv
*.json
requirements.txt
streamlit
pandas
numpy
scikit-learn
matplotlib
joblib
requests
runtime.txt
3.11
.streamlit/config.toml
[server]
headless = true
port = 8501
enableCORS = false
[theme]
base = "light"
Los secretos van en secrets.toml dentro de Streamlit Cloud:
SOCRATA_APP_TOKEN="...",HDX_API_KEY="...", etc.
app.py (UI dinámica para explorar, ejecutar y
visualizar)Conecta con sus scripts. Ejecuta análisis y muestra reportes. Colóquelo en la raíz del repo.
# app.py
# Página dinámica para:
# 1) Descargar/explorar datasets (vía collect_datasets.py)
# 2) Ejecutar análisis reproducible (analyze_datasets.py)
# 3) Visualizar métricas, figuras y reporte en Markdown
import os
import json
import subprocess
from pathlib import Path
import streamlit as st
import pandas as pd
ROOT = Path(__file__).parent.resolve()
DATA_RAW = ROOT / "data" / "raw"
REPORTS = ROOT / "reports"
st.set_page_config(page_title="Modelación Predictiva IA", layout="wide")
# ---------------------------
# Utilidades
# ---------------------------
def list_csvs(folder: Path):
folder.mkdir(parents=True, exist_ok=True)
return sorted([p for p in folder.glob("*.csv")], key=lambda x: x.stat().st_mtime, reverse=True)
def run_cmd(cmd: list[str]) -> tuple[int, str, str]:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
out, err = proc.communicate()
return proc.returncode, out, err
def show_report(report_dir: Path):
col1, col2 = st.columns([2, 1])
metrics_path = report_dir / "metrics.json"
rep_path = report_dir / "REPORTE.md"
imp_path = report_dir / "importances.json"
with col1:
if (report_dir / "roc_curve.png").exists():
st.image(str(report_dir / "roc_curve.png"), caption="Curva ROC")
if (report_dir / "pr_curve.png").exists():
st.image(str(report_dir / "pr_curve.png"), caption="Curva Precisión-Recall")
if (report_dir / "real_vs_predicho.png").exists():
st.image(str(report_dir / "real_vs_predicho.png"), caption="Real vs Predicho")
if (report_dir / "REPORTE.md").exists():
st.markdown(rep_path.read_text(encoding="utf-8"))
with col2:
if metrics_path.exists():
st.subheader("Métricas")
st.json(json.loads(metrics_path.read_text(encoding="utf-8")))
if imp_path.exists():
st.subheader("Importancias (Top)")
st.json(json.loads(imp_path.read_text(encoding="utf-8")))
# ---------------------------
# Barra lateral
# ---------------------------
st.sidebar.title("Panel de control")
section = st.sidebar.radio("Sección", ["1) Catálogo y descarga", "2) Análisis", "3) Reportes"])
# ---------------------------
# 1) Catálogo y descarga
# ---------------------------
if section == "1) Catálogo y descarga":
st.header("Explorar y descargar datasets por API")
st.info("Fuentes: Socrata (datos.gov.co), FAOSTAT, Banco Mundial, HDX, OECD.")
q = st.text_input("Término de búsqueda", value="agriculture")
source = st.selectbox(
"Fuente",
["ALL", "SOCRATA", "FAOSTAT", "WORLD_BANK", "HDX", "OECD"]
)
limit = st.slider("Límite de resultados", 1, 20, 5)
if st.button("Buscar"):
cmd = ["python", "collect_datasets.py", "search", "--source", source, "--q", q, "--limit", str(limit)]
code, out, err = run_cmd(cmd)
if code == 0:
results = json.loads(out)
st.success(f"{len(results)} resultados")
st.json(results)
# Persistir catálogo
DATA_RAW.mkdir(parents=True, exist_ok=True)
(DATA_RAW / "catalogo.json").write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8")
else:
st.error("Error en búsqueda")
st.code(err)
st.subheader("Descargar por ID")
st.caption("Use el ID devuelto por la búsqueda. Ej.: SOCRATA: abcd-1234, FAOSTAT: QCL, WORLD_BANK: SP.POP.TOTL")
d_src = st.selectbox("Fuente de descarga", ["SOCRATA", "FAOSTAT", "WORLD_BANK", "HDX", "OECD"])
d_id = st.text_input("ID del recurso / indicador / dominio", value="")
if st.button("Descargar"):
if not d_id:
st.warning("Indique un ID.")
else:
cmd = ["python", "collect_datasets.py", "get", "--source", d_src, "--id", d_id, "--dest", str(DATA_RAW)]
code, out, err = run_cmd(cmd)
st.subheader("Salida")
st.code(out)
if code != 0:
st.error(err)
st.subheader("CSV disponibles en data/raw")
csvs = list_csvs(DATA_RAW)
if csvs:
sel = st.selectbox("Archivo", csvs, format_func=lambda p: p.name)
df = pd.read_csv(sel).head(1000)
st.write(df.head())
st.download_button("Descargar CSV", data=sel.read_bytes(), file_name=sel.name)
else:
st.write("No hay CSV en data/raw")
# ---------------------------
# 2) Análisis
# ---------------------------
elif section == "2) Análisis":
st.header("Ejecutar análisis reproducible")
csvs = list_csvs(DATA_RAW)
if not csvs:
st.warning("No hay CSV en data/raw. Descargue primero en la sección 1.")
else:
sel = st.selectbox("Seleccione dataset", csvs, format_func=lambda p: p.name)
df = pd.read_csv(sel).head(1000)
st.write(df.head())
target = st.text_input("Columna objetivo (target)", value="")
task = st.selectbox("Tarea", ["auto", "binary", "regression"])
pos_label = st.text_input("Etiqueta positiva (opcional)")
test_size = st.slider("Test size", 0.1, 0.4, 0.2, step=0.05)
seed = st.number_input("Random state", min_value=0, max_value=9999, value=42, step=1)
if st.button("Ejecutar análisis"):
if not target:
st.warning("Indique la columna objetivo.")
elif target not in df.columns:
st.error("La columna objetivo no existe en el CSV.")
else:
cmd = [
"python", "analyze_datasets.py",
"--input", str(sel),
"--target", target,
"--task", task,
"--test-size", str(test_size),
"--random-state", str(seed)
]
if pos_label.strip():
cmd += ["--positive-label", pos_label.strip()]
code, out, err = run_cmd(cmd)
if code == 0:
st.success("Ejecución completada.")
st.code(out)
else:
st.error("Error en ejecución.")
st.code(err)
# ---------------------------
# 3) Reportes
# ---------------------------
else:
st.header("Visualización de reportes")
# cada subcarpeta en reports corresponde al stem del CSV analizado
report_dirs = sorted([p for p in REPORTS.glob("*") if p.is_dir()], key=lambda x: x.stat().st_mtime, reverse=True)
if not report_dirs:
st.info("Aún no hay reportes. Ejecute un análisis.")
else:
sel_rep = st.selectbox("Seleccione reporte", report_dirs, format_func=lambda p: p.name)
show_report(sel_rep)
Push a GitHub con los archivos anteriores en la
rama main.
Ir a https://share.streamlit.io/ → New app.
Seleccionar el repositorio,
rama y archivo principal
app.py.
En Advanced settings:
Python version: Streamlit respetará
runtime.txt (3.11).
Secrets: pegar variables en
secrets.toml (panel de Secrets), por ejemplo:
SOCRATA_APP_TOKEN = "xxxx"
HDX_API_KEY = "xxxx"Presionar Deploy.
La app quedará disponible en una URL del tipo:
https://<usuario>-<repo>-<rama>.streamlit.app/
Nota operativa: si la app entra en “sleep”, use Settings → Restart. Para proyectos con uso continuo, mantenga tráfico periódico o considere un backend con “cron” externo que haga “health checks”.
Crear archivos base “Genera
requirements.txt, .gitignore,
runtime.txt y .streamlit/config.toml para un
proyecto Streamlit con scikit-learn y pandas. No incluyas
datos.”
Verificación local (Codespaces) “Ejecuta
streamlit run app.py y abre el puerto publicado. Comprueba
que se listan CSV en data/raw y que se renderiza
REPORTE.md.”
Documentación “Añade al README.md una guía de despliegue en Streamlit Community Cloud con capturas de uso y advertencias de repositorios públicos.”
Sincronización Git “Ejecuta
git add . && git commit -m "App Streamlit dinámica" && git push origin main.”
collect_datasets.py desde la app o suba CSVs manualmente al
contenedor efímero.reports/ y models/.Hecho. Si desea, preparo un template de README.md listo para copiar y pegar con capturas y secciones estándar.
Por favor prepara un template de README.md listo para copiar y pegar con capturas y secciones estándar.
Aquí tienes el README.md modelo para tu proyecto: puedes copiarlo directamente a la raíz del repositorio de GitHub antes del despliegue.
Autor: Profesor Daniel Otero Colaboradores: Estudiantes de Ingeniería Agrícola, Agroindustrial y Civil Año: 2025 Licencia: MIT
Este proyecto demuestra la implementación reproducible de un flujo completo de modelación predictiva basada en IA generativa, utilizando GitHub Codespaces como entorno colaborativo y Copilot como asistente de codificación e interpretación.
El propósito principal es:
El flujo completo se desarrolla íntegramente en la nube:
GitHub Codespaces → Copilot → Streamlit Cloud
modelacion_predictiva_IA/
│
├── app.py # Aplicación Streamlit principal
├── collect_datasets.py # Descarga de datasets por API
├── analyze_datasets.py # EDA y modelado automático
│
├── data/
│ ├── raw/ # Datos crudos descargados
│ └── processed/ # Datos limpios o transformados
│
├── models/ # Modelos entrenados (.joblib)
├── reports/ # Métricas, figuras y reportes Markdown
│
├── requirements.txt
├── runtime.txt
├── .gitignore
└── .streamlit/
├── config.toml
└── secrets.toml (no subir al repositorio)
Crear el repositorio y abrirlo en Codespaces.
Instalar las dependencias:
pip install -r requirements.txtCrear el entorno virtual si no existe:
python -m venv .venv
source .venv/bin/activateVerificar la versión:
python --versionBuscar datasets reales:
python collect_datasets.py search --source ALL --q "agriculture" --limit 5
Descargar dataset de FAOSTAT (ejemplo):
python collect_datasets.py get --source FAOSTAT --id QCL --dest data/raw/
Ejecutar análisis con detección automática de tarea:
python analyze_datasets.py --input data/raw/faostat_QCL.csv --target Value --task auto
Genera:
REPORTE.md)roc_curve.png, pr_curve.png,
importancias_permutacion.png)metrics.json)Realizar push a GitHub con todos los archivos.
Ingresar a https://share.streamlit.io.
Seleccionar:
modelacion_predictiva_IAmainapp.pyEn el panel de Secrets, agregar:
SOCRATA_APP_TOKEN = "xxxxx"
HDX_API_KEY = "xxxxx"Presionar Deploy y esperar el despliegue.
La aplicación quedará disponible en:
https://<usuario>-modelacion-predictiva-ia-main.streamlit.app/
Sección 1 – Catálogo y descarga: Buscar y descargar datasets reales por API.
Sección 2 – Análisis: Seleccionar dataset, definir variable objetivo y ejecutar el análisis reproducible.
Sección 3 – Reportes: Visualizar métricas, curvas ROC/PR, importancias de variables y reportes generados.
--random-state 42) para
reproducibilidad..gitignore.secrets.toml para
credenciales.| Paso | Script | Acción | Salida |
|---|---|---|---|
| 1 | collect_datasets.py |
Obtiene datasets por API | Archivos CSV en data/raw |
| 2 | analyze_datasets.py |
Realiza EDA y modelado | Reportes y modelos |
| 3 | app.py |
Interfaz Streamlit | Página web interactiva |
| 4 | git push |
Sincroniza cambios | Repositorio actualizado |
Proyecto desarrollado en el marco del Segundo Período Académico 2025, según la Resolución No. 50 de 2025 del Consejo Académico de la Universidad de Sucre, que aprueba la oferta académica vigente.
Líneas de publicación sugeridas:
Otero, D. (2025). Modelación Predictiva Asistida por IA en Ambientes de Desarrollo Colaborativo con GitHub Codespaces y Copilot. Universidad de Sucre. https://github.com/
/modelacion_predictiva_IA
¿Deseas que te genere también un README versión corta para Streamlit Cloud, adaptado como portada visible en la app (solo con descripción, flujo y créditos)?
Genera también un README versión corta para Streamlit Cloud, adaptado como portada visible en la app (solo con descripción, flujo y créditos)
Aquí tienes la versión corta del README.md adaptada
para que se muestre como portada visible dentro de la aplicación
en Streamlit Cloud. Guárdala como
README_streamlit.md o pégala directamente al inicio de
app.py usando
st.markdown(open("README_streamlit.md").read()).
Universidad de Sucre — 2025
Proyecto académico que integra:
GitHub Codespaces → Copilot → Streamlit Cloud
| Archivo | Descripción |
|---|---|
collect_datasets.py |
Explora y descarga datasets desde APIs. |
analyze_datasets.py |
Realiza EDA, modelado y genera reportes. |
app.py |
Interfaz interactiva en Streamlit. |
requirements.txt |
Librerías requeridas para reproducir el entorno. |
Busca datasets reales por palabra clave y fuente institucional.
Selecciona un dataset, define la variable objetivo y ejecuta el modelado automático.
Visualiza métricas, curvas ROC/PR, importancia de variables y reporte científico.
Proyecto desarrollado en el marco del Segundo Período Académico 2025, según la Resolución No. 50 de 2025 del Consejo Académico de la Universidad de Sucre.
Líneas sugeridas de publicación:
Autor: Profesor Daniel Otero Colaboradores: Estudiantes de Ingeniería Agrícola, Agroindustrial y Civil Institución: Universidad de Sucre — Sincelejo, Colombia Licencia: MIT
¿Deseas que te prepare también un banner visual minimalista (en imagen PNG) con el título y subtítulo del proyecto para colocar en la cabecera de la app Streamlit?
El desarrollo de la propuesta “Modelación Predictiva Asistida por IA en Ambientes de Desarrollo Colaborativo con GitHub Codespaces y Copilot” demuestra que la combinación de tecnologías de inteligencia artificial, entornos de programación en la nube y plataformas de despliegue abiertas constituye una alternativa sólida para fortalecer la investigación aplicada y la docencia universitaria contemporánea.
Desde la perspectiva metodológica, el proyecto consolida un ciclo de investigación completamente reproducible, que abarca desde la recolección automatizada de datos hasta la visualización de resultados en tiempo real mediante una aplicación web interactiva. Esta integración permite que los docentes y estudiantes desarrollen competencias clave en:
A nivel institucional, la implementación de este modelo ofrece un marco replicable para proyectos de aula, semilleros de investigación y trabajos de grado, facilitando la publicación de resultados en entornos abiertos y la alineación con políticas de ciencia abierta y educación digital.
La experiencia con GitHub Codespaces + Copilot + Streamlit Cloud valida el potencial de la IA generativa no como sustituto del pensamiento científico, sino como un instrumento de apoyo cognitivo que amplía la capacidad de análisis, documentación y difusión del conocimiento académico.
En proyección futura, se recomienda:
En síntesis, este trabajo refleja una visión institucional de innovación responsable, orientada a consolidar una cultura de investigación científica moderna, ética y sustentada en la cooperación entre humanos y sistemas inteligentes.