---
name: correlation_matrix_duckdb
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def correlation_matrix_duckdb(db_path: str, table: str, columns: list = None, strong_threshold: float = 0.7) -> dict"
description: "Matriz de correlacion de Pearson entre columnas numericas de una tabla DuckDB calculada con push-down SQL (funcion nativa corr()), sin traer filas a RAM. Apta para tablas grandes donde no quieres muestrear en Python."
tags: [eda, correlation, duckdb, pearson, datascience, push-down]
uses_functions: [duckdb_table_schema_py_infra, duckdb_query_readonly_py_infra]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
  - name: db_path
    desc: "Ruta al archivo DuckDB. Debe existir; el modo read_only NO crea la base."
  - name: table
    desc: "Nombre de la tabla. Se valida contra ^[A-Za-z_][A-Za-z0-9_]*$ y se interpola citado (DuckDB no admite parametros para identificadores)."
  - name: columns
    desc: "Lista de columnas numericas a correlacionar. None (default) = autodescubre las columnas de tipo numerico DuckDB leyendo el schema."
  - name: strong_threshold
    desc: "Umbral en valor absoluto para marcar una pareja como fuerte (default 0.7). Pares con abs(corr) >= threshold se devuelven en `strong`."
output: "dict. En exito {status:'ok', columns:[...], matrix:{a:{b:corr}}, pairs:[{a,b,corr}], strong:[{a,b,corr}]} con corr float o None (columna constante / <2 valores -> corr() = NULL); strong omite los None y va ordenado por abs(corr) desc. En error {status:'error', error:str} (no lanza)."
tested: true
tests: ["correla dos columnas linealmente dependientes y aparece en strong", "columna constante no rompe y queda fuera de strong", "tabla con menos de dos columnas numericas devuelve error", "columns explicitas respetan el orden y la matriz es simetrica"]
test_file_path: "python/functions/datascience/correlation_matrix_duckdb_test.py"
file_path: "python/functions/datascience/correlation_matrix_duckdb.py"
---

## Ejemplo

```python
import duckdb
from datascience import correlation_matrix_duckdb

# Crear una tabla DuckDB de prueba con 3 columnas numericas (col_a y col_b correladas).
db = "/tmp/corr_demo.duckdb"
con = duckdb.connect(db)
con.execute("CREATE TABLE m AS SELECT i AS col_a, 2*i AS col_b, (i*7) % 5 AS col_c FROM range(100) t(i)")
con.close()

res = correlation_matrix_duckdb(db, "m")
print(res["status"])                 # ok
print(round(res["matrix"]["col_a"]["col_b"], 3))   # ~1.0
print([(p["a"], p["b"]) for p in res["strong"]])   # [('col_a', 'col_b')]
```

## Cuando usarla

Cuando necesitas la correlacion de Pearson entre muchas columnas numericas de una
tabla con MUCHAS filas y NO quieres muestrear ni traerla a RAM con pandas/numpy. Todo
el calculo se hace push-down en el motor de DuckDB con la funcion nativa `corr()`.
Util en el flujo `eda` para detectar pares fuertemente correlados (multicolinealidad)
antes de modelar, o para resumir relaciones lineales en datasets que no caben en memoria.

## Gotchas

- Funcion impura: lee un archivo DuckDB del disco (read_only, nunca lo modifica).
- Solo correlacion de PEARSON (lineal). Para monotona usa `spearman_corr_py_datascience`;
  para asociacion categorica `cramers_v_py_datascience`.
- `corr()` de DuckDB ignora las filas con NULL POR PAREJA (pairwise complete): cada
  coeficiente usa solo las filas donde ambas columnas son no-NULL, asi que distintos
  pares pueden basarse en distinto numero de filas.
- Una columna constante o con menos de 2 valores distintos da varianza cero: DuckDB
  devuelve `NaN` (y `NULL` si la tabla esta vacia). Ambos casos se normalizan a
  `corr: None`, de modo que ese par se omite de `strong` y la matriz nunca contiene
  `NaN` (no rompe ni el orden de `strong`).
- Tabla vacia -> matriz de None (salvo la diagonal 1.0). Menos de 2 columnas numericas
  -> `{status:'error'}`.
- La query se ejecuta con `sandbox=False` en `duckdb_query_readonly` (uso interno
  confiable: el SQL lo construye esta funcion, no un cliente externo).
