Files
fn_registry/python/functions/datascience/detect_latlon_columns.md
T
egutierrez cd658cc703 feat(eda): primitivas geoespaciales del grupo eda (detección lat/lon + extensión + scatter)
Tres funciones puras nuevas del dominio datascience (tags eda + geospatial) que
sostienen el capítulo GEOSPATIAL del AutomaticEDA, delegadas a fn-constructor:

- detect_latlon_columns: identifica el par (lat, lon) por nombre de columna +
  rango de valores ([-90,90] / [-180,180]) desde profile['columns']. Devuelve
  {lat_col, lon_col, confidence, reason}. 9 tests.
- analyze_geo_extent: bbox, centroide, span haversine, conteo por zona/país
  (lookup offline con bounding boxes embebidos, KISS sin geopandas) y
  hemisferios. 7 tests.
- build_geo_scatter: prepara los puntos del scatter en orden [lon, lat] con
  downsampling determinista por paso fijo + aspect equirectangular 1/cos(lat)
  clampado. 6 tests.

Registradas en datascience/__init__.py. Todas pure, params_schema completo,
.md autosuficiente (Ejemplo + Cuando usarla + Gotchas).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:29:33 +02:00

5.5 KiB

name, id, kind, lang, domain, version, purity, signature, description, tags, params, output, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path
name id kind lang domain version purity signature description tags params output uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path
detect_latlon_columns detect_latlon_columns_py_datascience function py datascience 1.0.0 pure def detect_latlon_columns(columns: list, samples: dict | None = None) -> dict Detecta un par (latitud, longitud) entre las columnas de un TableProfile del grupo eda combinando heuristica de nombre (latitude/longitude/lat/lon/lng + x/y debiles) con validacion de rango obligatoria (latitud en [-90,90], longitud en [-180,180]). Lee defensivamente con .get; NUNCA lanza. Usa el sub-bloque numeric.min/max o, si falta, la lista de samples opcional. Devuelve SIEMPRE un dict {lat_col, lon_col, confidence, reason}; si no hay par valido, las columnas van a None y confidence a 0.0.
eda
geospatial
profiling
latlon
coordinates
detection
datascience
name desc
columns Lista de dicts ColumnProfile (el campo `columns` de un TableProfile del grupo eda). Cada dict se lee con .get; solo `name` (str) es obligatorio. Se consultan `inferred_type` (p.ej. 'numeric') y el sub-dict `numeric` con `min`/`max` (floats) para validar el rango. Entradas no-dict o sin name se ignoran sin lanzar.
name desc
samples Opcional {nombre_columna: [valores...]} para validar el rango cuando una columna no trae numeric.min/max. Los valores nulos se ignoran; si algun valor no nulo no es numerico la columna no se considera coordenada. Si es None u omitido, solo se usa el bloque numeric.
Dict SIEMPRE presente con la forma {lat_col: str|None, lon_col: str|None, confidence: float en [0,1], reason: str en espanol}. En exito, lat_col y lon_col nombran columnas distintas; confidence ~1.0 para par con nombre fuerte (latitude/longitude/lat/lon/lng) + rango valido y ~0.7 para par debil (x/y) + rango. En fallo, ambas columnas None, confidence 0.0 y reason explica por que (sin columnas, nombre sin match, rango fuera de bounds, falta uno de los dos ejes...).
false
true
test_par_latitude_longitude_fuerte
test_par_lat_lon_abreviado
test_par_x_y_debil_con_rango_valido
test_nombre_lat_lon_pero_rango_fuera_no_detecta
test_par_fuerte_prevalece_sobre_debil
test_entradas_vacias_o_invalidas_no_lanzan
test_solo_latitud_sin_longitud_no_detecta
test_deteccion_por_samples_cuando_falta_numeric
test_samples_fuera_de_rango_descarta
python/functions/datascience/detect_latlon_columns_test.py python/functions/datascience/detect_latlon_columns.py

Ejemplo

import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from datascience.detect_latlon_columns import detect_latlon_columns

# Columnas tal y como vienen en profile['columns'] de un TableProfile del grupo eda:
columns = [
    {"name": "id", "inferred_type": "numeric", "numeric": {"min": 1, "max": 9999}},
    {"name": "latitude", "inferred_type": "numeric", "numeric": {"min": -45.0, "max": 45.0}},
    {"name": "longitude", "inferred_type": "numeric", "numeric": {"min": -120.0, "max": 120.0}},
]
res = detect_latlon_columns(columns)
print(res["lat_col"], res["lon_col"], res["confidence"])
# latitude longitude 1.0

# Sin bloque numeric, validando el rango con samples:
cols2 = [{"name": "lat"}, {"name": "lon"}]
samples = {"lat": [10.5, 20.0, 30.25], "lon": [-40.0, 50.5, 60.0]}
print(detect_latlon_columns(cols2, samples)["lat_col"])  # lat

Cuando usarla

  • Usala al perfilar una tabla en AutomaticEDA para decidir si tiene geometria de puntos: cuando detect_latlon_columns devuelve un par con confidence alta, el capitulo geospatial puede dibujar un mapa, calcular un bounding box o proponer un cluster espacial.
  • Antes de un analisis geoespacial (alpha shape, convex hull, joins por proximidad) para localizar automaticamente que columnas son la latitud y la longitud sin pedirlo al usuario.
  • Cuando recibas un TableProfile del grupo eda y quieras enrutar columnas a sub-analisis por tipo semantico: este es el detector del par lat/lon, complementario a infer_semantic_type.

Gotchas

  • Funcion pura, sin I/O y determinista. Lectura defensiva con .get: NUNCA lanza. Cualquier input malformado (None, no-lista, entradas no-dict, claves ausentes) devuelve el dict de fallo con lat_col/lon_col en None y confidence 0.0.
  • El nombre solo no basta: una columna latitude cuyo rango se sale de [-90, 90] se descarta (no es coordenada real). Igual para longitude fuera de [-180, 180]. La validacion de rango es obligatoria.
  • El rango de latitud [-90, 90] es un subconjunto del de longitud [-180, 180], por eso el nombre es necesario para desambiguar cual eje es cual; una columna numerica en [-90, 90] sin nombre que sugiera lat/lon no se detecta.
  • Los nombres genericos x/y (y x_coord/y_coord) son candidatos debiles: solo forman par si el rango encaja y existe la otra mitad (un x/lon para la y, un y/lat para la x). Un y suelto sin pareja devuelve None.
  • Requiere AMBOS ejes para considerar exito. Si solo encuentra latitud o solo longitud, devuelve el dict de fallo (no media coordenada).
  • samples solo se consulta cuando falta numeric.min/numeric.max. Si una columna trae el bloque numeric, ese manda aunque pases samples para ella.
  • El matching de nombre es por subcadena normalizada (se quitan _, - y espacios), asi que nombres como plate (contiene "lat") podrian marcarse como candidatos por nombre — pero solo pasarian si su rango cae en [-90, 90] y hay una longitud pareja, filtro que en la practica descarta los falsos positivos.