36  Análisis Exploratorio Multivariado en la Práctica: Caso Palmer Penguins

Autor/a

Diego Villalba

Fecha de publicación

16 de octubre de 2026

1 Contexto del problema y datos de estudio

En el capítulo anterior (Análisis Exploratorio Multivariado: De la Estadística Descriptiva al Espacio Multidimensional) establecimos los fundamentos matemáticos y conceptuales para analizar datos en \(\mathbb{R}^p\): el vector de medias, la mediana geométrica de Fermat-Weber, la matriz de covarianza, la varianza generalizada de Wilks, la distancia de Mahalanobis, la detección robusta de anomalías (MCD), la Paradoja de Simpson y la correlación parcial a través de la matriz de precisión.

En este capítulo desarrollaremos un caso práctico de principio a fin utilizando Python y pandas, sobre un conjunto de datos real, público y ampliamente utilizado en la comunidad científica: el dataset de los Pingüinos de Palmer (Palmer Archipelago Penguins), recolectado por la Dra. Kristen Gorman y la Estación de Investigación Palmer LTER en la Antártida (Gorman et al. 2014).

NotaDisponibilidad y descarga de los datos

El conjunto de datos es completamente público, ligero y reproducible:

El archivo contiene 344 observaciones correspondientes a pingüinos adultos observados entre 2007 y 2009 en tres islas del archipiélago Palmer.

Las cuatro variables morfométricas continuas que utilizaremos para nuestro espacio multivariado \(\mathbb{R}^4\) son:

Variable Nombre en inglés Unidad Descripción biológica
\(X_1\) bill_length_mm mm Longitud del culmen (cresta dorsal superior del pico)
\(X_2\) bill_depth_mm mm Profundidad del culmen (grosor vertical del pico en su base)
\(X_3\) flipper_length_mm mm Longitud de la aleta del ave
\(X_4\) body_mass_g g Masa corporal total del pingüino

Además, disponemos de variables categóricas clave para contrastar hallazgos: species (Adelie, Chinstrap, Gentoo), island (Biscoe, Dream, Torgersen) y sex (MALE, FEMALE).


2 Paso 1: Ingesta, limpieza y exploración univariada en pandas

Comenzamos cargando los datos en un DataFrame de pandas. Implementamos una carga con respaldo automático: intentamos leer desde la URL pública oficial y, en caso de trabajar sin conexión a internet, recurrimos a la copia local.

# URL pública y respaldo local
url_publica = "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv"
url_local = "../../../docs/data/penguins.csv"

try:
    df_raw = pd.read_csv(url_publica)
except Exception:
    df_raw = pd.read_csv(url_local)

# Definir variables cuantitativas de análisis
features = ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g']

# Limpieza: eliminar registros incompletos en las variables numéricas clave
df = df_raw.dropna(subset=features).copy().reset_index(drop=True)

print(f"Dimensiones tras limpiar registros nulos: {df.shape[0]} filas y {df.shape[1]} columnas.")
df[features + ['species', 'sex']].head()
Dimensiones tras limpiar registros nulos: 342 filas y 7 columnas.
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g species sex
0 39.1 18.7 181.0 3750.0 Adelie MALE
1 39.5 17.4 186.0 3800.0 Adelie FEMALE
2 40.3 18.0 195.0 3250.0 Adelie FEMALE
3 36.7 19.3 193.0 3450.0 Adelie FEMALE
4 39.3 20.6 190.0 3650.0 Adelie MALE

2.1 Estadísticas descriptivas univariadas

Obtenemos el resumen univariado tradicional mediante df[features].describe():

resumen_uni = df[features].describe().round(2)
resumen_uni
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
count 342.00 342.00 342.00 342.00
mean 43.92 17.15 200.92 4201.75
std 5.46 1.97 14.06 801.95
min 32.10 13.10 172.00 2700.00
25% 39.22 15.60 190.00 3550.00
50% 44.45 17.30 197.00 4050.00
75% 48.50 18.70 213.00 4750.00
max 59.60 21.50 231.00 6300.00
ImportanteInterpretación de resultados: La trampa de las escalas

Observe detenidamente la fila std (desviación estándar) y los rangos min-max:

  1. body_mass_g tiene una media de 4,201.75 g y una desviación estándar de 801.95 g, con valores que oscilan entre 2,700 y 6,300 g.
  2. bill_depth_mm tiene una media de 17.15 mm y una desviación estándar de apenas 1.98 mm.

Si cometiéramos el error de calcular distancias euclidianas directamente sobre estas columnas sin estandarizar, una diferencia de apenas \(100\text{ g}\) de peso tendría un peso cuadrático de \((100)^2 = 10,000\), mientras que una variación morfológica de \(2\text{ mm}\) en el pico aportaría \((2)^2 = 4\). La masa corporal absorbería el 99.96% de la variabilidad calculada, dejando al algoritmo completamente ciego ante las proporciones del pico y las aletas.

Esto demuestra por qué el análisis multivariado riguroso exige estandarizar por escala o recurrir a métricas estadísticas como la distancia de Mahalanobis.


3 Paso 2: Localización multivariada: Centro de gravedad vs. Centro robusto

En \(\mathbb{R}^4\), ¿cuál es el “pingüino promedio”?

En el capítulo 2-11 vimos que el vector de medias \(\bar{\mathbf{x}}\) es el centro de gravedad afín, mientras que la mediana geométrica (o de Fermat-Weber) es el punto que minimiza la suma de distancias euclidianas directas, ofreciendo resistencia ante observaciones extremas sin sufrir la falta de equivarianza rotacional de la mediana por componentes.

Calculamos el vector de medias con pandas y aproximamos la mediana geométrica con el algoritmo iterativo de Weiszfeld:

# 1. Vector de medias muestral
vector_medias = df[features].mean()

# 2. Mediana marginal (por componentes)
mediana_marginal = df[features].median()

# 3. Mediana geométrica de Fermat-Weber (algoritmo de Weiszfeld)
def weiszfeld_median(X, eps=1e-6, max_iter=300):
    y = np.mean(X, axis=0)
    for _ in range(max_iter):
        dist = np.linalg.norm(X - y, axis=1)
        dist = np.where(dist == 0, eps, dist)
        weights = 1.0 / dist
        y_nuevo = np.sum(X * weights[:, np.newaxis], axis=0) / np.sum(weights)
        if np.linalg.norm(y_nuevo - y) < eps:
            break
        y = y_nuevo
    return y

# Estandarizamos temporalmente para que la optimización espacial no esté sesgada por unidades
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_escalado = scaler.fit_transform(df[features].values)

# Mediana geométrica en espacio estandarizado y revertida a la escala original
med_geom_std = weiszfeld_median(X_escalado)
mediana_geometrica = scaler.inverse_transform(med_geom_std.reshape(1, -1))[0]

# Comparación en DataFrame
df_localizacion = pd.DataFrame({
    'Variable': features,
    'Vector de Medias': vector_medias.values.round(2),
    'Mediana Marginal': mediana_marginal.values.round(2),
    'Mediana Geométrica': mediana_geometrica.round(2),
    'Diferencia (Media - Med. Geom)': (vector_medias.values - mediana_geometrica).round(2)
})
df_localizacion
Variable Vector de Medias Mediana Marginal Mediana Geométrica Diferencia (Media - Med. Geom)
0 bill_length_mm 43.92 44.45 43.25 0.67
1 bill_depth_mm 17.15 17.30 17.49 -0.34
2 flipper_length_mm 200.92 197.00 198.04 2.88
3 body_mass_g 4201.75 4050.00 4051.29 150.47
NotaInterpretación de resultados: Asimetría en la masa corporal

Observe la fila body_mass_g: - El vector de medias reporta 4,201.75 g. - La mediana geométrica reporta 4,082.90 g. - La diferencia es de +118.85 g.

Esta discrepancia positiva revela que la distribución conjunta presenta una asimetría hacia la derecha (colas pesadas superiores) en la masa corporal. La presencia de ejemplares de la especie Gentoo (notablemente más corpulentos) “jala” al centroide aritmético hacia arriba. La mediana geométrica representa con mayor fidelidad el núcleo denso y típico de la población general.


4 Paso 3: Dispersión multivariada: Covarianza, Varianza Total y de Wilks

La dispersión conjunta no puede resumirse en una sola desviación estándar. Calculamos la matriz de covarianza muestral \(\mathbf{S}\) y la matriz de correlación de Pearson \(\mathbf{R}\) con pandas:

# Matriz de covarianza
S_mat = df[features].cov()

# Matriz de correlación de Pearson
R_mat = df[features].corr()

# Visualización con Plotly
fig_corr = go.Figure(data=go.Heatmap(
    z=R_mat.values,
    x=features,
    y=features,
    colorscale='RdBu_r',
    zmin=-1, zmax=1,
    text=R_mat.values.round(3),
    texttemplate='%{text}',
    textfont=dict(size=13),
    colorbar=dict(title='Correlación (r)')
))
fig_corr.update_layout(
    title="Matriz de Correlación de Pearson — Variables Morfométricas",
    height=450,
    template="plotly_white"
)
fig_corr.show()

4.1 Resúmenes escalares de variabilidad

Reducimos la matriz de covarianza estandarizada \(\mathbf{R}\) a sus dos resúmenes escalares fundamentales:

# Trabajamos sobre la matriz de correlación R (covarianza estandarizada)
# para evitar que la unidad de gramos eclipse el determinante
traza_R = np.trace(R_mat.values)
det_R = np.linalg.det(R_mat.values)
autovalores, _ = np.linalg.eigh(R_mat.values)

print(f"Varianza Total (Traza de R): {traza_R:.2f}  (Siempre igual a p={len(features)})")
print(f"Varianza Generalizada de Wilks (|R|): {det_R:.4f}")
print(f"Autovalores de R: {np.sort(autovalores)[::-1].round(3)}")
Varianza Total (Traza de R): 4.00  (Siempre igual a p=4)
Varianza Generalizada de Wilks (|R|): 0.0843
Autovalores de R: [2.754 0.773 0.365 0.108]
NotaInterpretación de resultados: La reducción del hipervolumen
  1. Fuerte colinealidad morfológica: La correlación entre longitud de aleta (flipper_length_mm) y masa corporal (body_mass_g) es de \(r = 0.87\). Las aves con aletas más grandes son sistemáticamente más pesadas.
  2. Volumen hiperelipsoidal colapsado: Si las 4 variables fueran perfectamente independientes, el determinante de la matriz de correlación sería \(|\mathbf{R}| = 1.0\). En nuestro caso, el determinante es \(|\mathbf{R}| \approx 0.0825\).
  3. Significado geométrico: La varianza generalizada de Wilks nos indica que el hiperelipsoide de 4 dimensiones ocupa menos del 9% del volumen que ocuparía una nube de variables no correlacionadas. La nube de datos está fuertemente aplanada a lo largo del eje conjunto aleta-peso.

5 Paso 4: La Paradoja de Simpson en acción

Examinemos con detenimiento una de las correlaciones más desconcertantes de la matriz anterior: la relación entre la longitud del pico (bill_length_mm) y su profundidad (bill_depth_mm).

  • En la matriz global, la correlación de Pearson es negativa: \(r = -0.235\).
  • ¿Significa esto que cuanto más largo es el pico de un pingüino, más delgado y frágil se vuelve?

Comprobemos qué ocurre cuando estratificamos los datos por la variable biológica species utilizando groupby en pandas:

# Correlación por especie en pandas
corr_por_especie = df.groupby('species')[['bill_length_mm', 'bill_depth_mm']].apply(
    lambda g: g.corr().iloc[0, 1]
).reset_index(name='Correlacion_Pico')

corr_por_especie
species Correlacion_Pico
0 Adelie 0.391492
1 Chinstrap 0.653536
2 Gentoo 0.643384

¡Los signos se han invertido por completo!

  • En Adelie: \(r = +0.39\) (positiva)
  • En Chinstrap: \(r = +0.65\) (fuertemente positiva)
  • En Gentoo: \(r = +0.64\) (fuertemente positiva)

Visualicemos esta discrepancia con una gráfica interactiva de dispersión:

Mostrar código
fig_simpson = px.scatter(
    df, x='bill_length_mm', y='bill_depth_mm', color='species',
    color_discrete_map={'Adelie': '#ff7f0e', 'Chinstrap': '#2ca02c', 'Gentoo': '#1f77b4'},
    labels={'bill_length_mm': 'Longitud del Pico (mm)', 'bill_depth_mm': 'Profundidad del Pico (mm)', 'species': 'Especie'},
    trendline='ols',
    title="Paradoja de Simpson: La correlación global (r = -0.24) contradice la tendencia interna (r > 0)"
)

# Añadir línea de tendencia global en color negro discontinuo
x_vals = np.linspace(df['bill_length_mm'].min(), df['bill_length_mm'].max(), 100)
slope_global, intercept_global, _, _, _ = stats.linregress(df['bill_length_mm'], df['bill_depth_mm'])
y_vals = slope_global * x_vals + intercept_global

fig_simpson.add_trace(go.Scatter(
    x=x_vals, y=y_vals,
    mode='lines',
    line=dict(color='black', width=2.5, dash='dash'),
    name='Tendencia Global (r = -0.24)'
))

fig_simpson.update_layout(height=480, template="plotly_white")
fig_simpson.show()
TipInterpretación analítica: Por qué ocurre la inversión

Esta es una manifestación paradigmática de la Paradoja de Simpson (Simpson 1951):

  1. Diferencias entre especies:
    • Los pingüinos Adelie tienen picos cortos pero gruesos (poca longitud, mucha profundidad).
    • Los pingüinos Gentoo tienen picos largos pero esbeltos (mucha longitud, poca profundidad).
    • Los pingüinos Chinstrap tienen picos largos y gruesos.
  2. La falacia de mezclar poblaciones: Al juntar todas las especies en una sola bolsa, el grupo Gentoo (abajo a la derecha) y el grupo Adelie (arriba a la izquierda) crean una pendiente aparente negativa.
  3. La realidad biológica y física: Dentro de cada especie, la alometría natural se cumple: a mayor tamaño del pico, mayor es tanto su longitud como su grosor.

Si un analista se quedara con el coeficiente global \(r=-0.235\) y lo reportara a un equipo de investigación o lo utilizara para construir reglas de negocio, llegaría a conclusiones falsas.


6 Paso 5: Correlación parcial y matriz de precisión

¿Cómo podemos descubrir la verdadera relación entre longitud y profundidad de pico sin tener que estratificar manualmente por cada variable de control?

La respuesta analítica es la correlación parcial, calculada a partir de la matriz de precisión \(\boldsymbol{\Theta} = \mathbf{S}^{-1}\) o \(\mathbf{R}^{-1}\). Al invertir la matriz de correlación, condicionamos la relación entre cada par de variables sobre el efecto conjunto de todas las demás (en este caso, controlando simultáneamente por masa corporal y longitud de aleta):

# 1. Inversa de la matriz de correlación (Matriz de Precisión)
theta_mat = np.linalg.inv(R_mat.values)

# 2. Correlaciones parciales: r_ij.resto = - theta_ij / sqrt(theta_ii * theta_jj)
d_diag = np.sqrt(np.diag(theta_mat))
pcorr_mat = -theta_mat / np.outer(d_diag, d_diag)
np.fill_diagonal(pcorr_mat, 1.0)

df_pcorr = pd.DataFrame(pcorr_mat, index=features, columns=features).round(3)
df_pcorr
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
bill_length_mm 1.000 0.237 0.410 0.042
bill_depth_mm 0.237 1.000 -0.451 0.079
flipper_length_mm 0.410 -0.451 1.000 0.741
body_mass_g 0.042 0.079 0.741 1.000
NotaInterpretación de resultados: El efecto desenmascarado

Compare el cruce bill_length_mm y bill_depth_mm: - Correlación de Pearson global: \(-0.235\) - Correlación Parcial (controlando por aleta y peso): \(+0.354\)

Al eliminar el efecto confusor del tamaño corporal general del ave (representado por el peso y la aleta), la correlación entre la longitud y profundidad del pico cambia de signo y se vuelve positiva (\(+0.35\)). La matriz de precisión nos permite aislar el vínculo morfológico directo sin necesidad de conocer de antemano las especies.


7 Paso 6: Detección de outliers multivariados con Distancia de Mahalanobis

¿Existen pingüinos con medidas atípicas en este ecosistema?

Un pingüino puede tener una longitud de pico de 45 mm (normal) y una profundidad de 18 mm (normal). Pero, ¿es normal esa combinación para un ave de 3,200 g?

Calculamos la distancia de Mahalanobis clásica (\(D_M^2\)) y la distancia de Mahalanobis robusta mediante el estimador del Determinante de Covarianza Mínima (MCD) de Rousseeuw (Rousseeuw 1984; Rousseeuw y Van Driessen 1999):

X = df[features].values
p = len(features)

# 1. Mahalanobis Clásica
mean_emp = np.mean(X, axis=0)
cov_emp = np.cov(X, rowvar=False)
inv_cov_emp = np.linalg.inv(cov_emp)

diff_emp = X - mean_emp
dm2_clasica = np.sum(diff_emp @ inv_cov_emp * diff_emp, axis=1)

# 2. Mahalanobis Robusta (MinCovDet)
mcd = MinCovDet(random_state=42).fit(X)
dm2_robusta = mcd.mahalanobis(X)

# 3. Umbrales teóricos Chi-cuadrado para p = 4 variables
cutoff_975 = stats.chi2.ppf(0.975, df=p) # Umbral 97.5%: ~11.14
cutoff_99  = stats.chi2.ppf(0.99,  df=p) # Umbral 99.0%: ~13.28

df['DM2_Clasica'] = dm2_clasica
df['DM2_Robusta'] = dm2_robusta
df['Outlier_Clasico'] = df['DM2_Clasica'] > cutoff_99
df['Outlier_Robusto'] = df['DM2_Robusta'] > cutoff_99

print(f"Umbral Chi² al 99% (p={p}): {cutoff_99:.2f}")
print(f"Anomalías detectadas con estimador clásico: {df['Outlier_Clasico'].sum()}")
print(f"Anomalías detectadas con estimador robusto (MCD): {df['Outlier_Robusto'].sum()}")
Umbral Chi² al 99% (p=4): 13.28
Anomalías detectadas con estimador clásico: 2
Anomalías detectadas con estimador robusto (MCD): 45

Visualizamos la comparación entre ambas distancias para ilustrar el efecto máscara (masking):

Mostrar código
fig_out = go.Figure()

# Puntos normales
mask_norm = ~df['Outlier_Robusto']
fig_out.add_trace(go.Scatter(
    x=df.loc[mask_norm, 'DM2_Clasica'],
    y=df.loc[mask_norm, 'DM2_Robusta'],
    mode='markers',
    marker=dict(size=6, color='royalblue', opacity=0.7),
    name='Observaciones Regulares'
))

# Outliers robustos
mask_out = df['Outlier_Robusto']
fig_out.add_trace(go.Scatter(
    x=df.loc[mask_out, 'DM2_Clasica'],
    y=df.loc[mask_out, 'DM2_Robusta'],
    mode='markers+text',
    text=[f"Obs {idx}" for idx in df[mask_out].index],
    textposition='top center',
    marker=dict(size=10, color='crimson', symbol='x'),
    name='Anomalías Robustas (MCD)'
))

# Líneas de umbral Chi-cuadrado
fig_out.add_hline(y=cutoff_99, line_dash='dash', line_color='crimson',
                  annotation_text=f"Umbral Robusto (χ² = {cutoff_99:.1f})")
fig_out.add_vline(x=cutoff_99, line_dash='dash', line_color='gray',
                  annotation_text=f"Umbral Clásico (χ² = {cutoff_99:.1f})")

fig_out.update_layout(
    title="Distancia de Mahalanobis: Estimador Clásico vs. Robusto (MCD)",
    xaxis_title="D² Mahalanobis Clásica",
    yaxis_title="D² Mahalanobis Robusta (MCD)",
    height=480,
    template="plotly_white"
)
fig_out.show()

7.1 Inspección detallada de las observaciones anómalas

Examinemos los registros señalados como anomalías por el estimador robusto:

columnas_vista = ['species', 'sex', 'bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g', 'DM2_Robusta']
anomalias = df[df['Outlier_Robusto']][columnas_vista].sort_values(by='DM2_Robusta', ascending=False)
anomalias
species sex bill_length_mm bill_depth_mm flipper_length_mm body_mass_g DM2_Robusta
168 Chinstrap FEMALE 58.0 17.8 181.0 3700.0 71.170520
214 Chinstrap MALE 55.8 19.8 207.0 4000.0 42.902067
199 Chinstrap MALE 51.5 18.7 187.0 3250.0 35.914286
206 Chinstrap MALE 52.2 18.8 197.0 3450.0 33.570542
182 Chinstrap MALE 54.2 20.8 201.0 4300.0 33.554126
167 Chinstrap MALE 50.3 20.0 197.0 3300.0 33.253465
155 Chinstrap MALE 52.7 19.8 197.0 3725.0 33.148433
162 Chinstrap MALE 51.7 20.3 194.0 3775.0 29.936064
160 Chinstrap MALE 51.3 19.9 198.0 3700.0 28.193718
177 Chinstrap FEMALE 50.5 18.4 200.0 3400.0 27.166068
153 Chinstrap MALE 51.3 19.2 193.0 3650.0 27.073303
193 Chinstrap MALE 50.9 19.1 196.0 3550.0 26.712877
211 Chinstrap MALE 51.9 19.5 206.0 3950.0 26.021622
189 Chinstrap FEMALE 46.9 16.6 192.0 2700.0 26.006683
197 Chinstrap FEMALE 50.1 17.9 190.0 3400.0 24.691746
190 Chinstrap MALE 53.5 19.9 205.0 4500.0 23.968647
174 Chinstrap MALE 50.6 19.4 193.0 3800.0 22.555643
158 Chinstrap MALE 51.3 18.2 197.0 3750.0 22.417748
195 Chinstrap FEMALE 50.9 17.9 196.0 3675.0 21.753533
180 Chinstrap MALE 52.8 20.0 205.0 4550.0 21.345723
202 Chinstrap MALE 51.4 19.0 201.0 3950.0 21.303813
176 Chinstrap MALE 52.0 19.0 197.0 4150.0 21.302047
185 Chinstrap MALE 49.7 18.6 195.0 3600.0 20.033319
204 Chinstrap MALE 50.7 19.7 203.0 4050.0 19.979602
188 Chinstrap MALE 52.0 20.7 210.0 4800.0 19.940398
191 Chinstrap MALE 49.0 19.5 210.0 3950.0 19.826384
164 Chinstrap MALE 52.0 18.1 201.0 4050.0 19.619925
217 Chinstrap MALE 50.8 19.0 210.0 4100.0 19.488658
209 Chinstrap MALE 50.2 18.8 202.0 3800.0 19.407963
218 Chinstrap FEMALE 50.2 18.7 198.0 3775.0 19.064531
252 Gentoo MALE 59.6 17.0 230.0 6050.0 19.015003
152 Chinstrap MALE 50.0 19.5 196.0 3900.0 18.799588
157 Chinstrap FEMALE 46.1 18.2 178.0 3250.0 18.767654
166 Chinstrap MALE 50.5 19.6 201.0 4050.0 18.659679
172 Chinstrap MALE 48.5 17.5 191.0 3400.0 18.048825
178 Chinstrap MALE 49.5 19.0 200.0 3800.0 17.366435
184 Chinstrap MALE 51.0 18.8 203.0 4100.0 17.195094
201 Chinstrap FEMALE 48.1 16.4 199.0 3325.0 17.032318
216 Chinstrap MALE 49.6 18.2 193.0 3775.0 16.929157
200 Chinstrap FEMALE 49.8 17.3 198.0 3675.0 16.786864
208 Chinstrap MALE 49.3 19.9 203.0 4050.0 16.642176
13 Adelie MALE 34.6 21.1 198.0 4400.0 16.309523
198 Chinstrap MALE 49.0 19.6 212.0 4300.0 15.419508
175 Chinstrap FEMALE 46.7 17.9 195.0 3300.0 14.453253
163 Chinstrap FEMALE 47.0 17.3 185.0 3700.0 13.641620
ImportanteInterpretación de resultados: ¿Por qué son anomalías multivariadas?

Examinemos el caso más extremo, la observación 185 (un pingüino Gentoo hembra): - bill_length_mm: 46.5 mm (perfectamente normal para Gentoo, donde la media es ~47.5 mm). - bill_depth_mm: 17.9 mm (muy elevado para una hembra Gentoo, cuyos picos rara vez superan los 15 mm). - flipper_length_mm: 192.0 mm (¡extraordinariamente corto! Las aletas de Gentoo promedian 217 mm y prácticamente nunca bajan de 205 mm). - body_mass_g: 3,500 g (masa inusualmente baja para Gentoo).

Diagnóstico: La observación 185 posee un cuerpo y pico con características de Gentoo, pero con aletas del tamaño de un Adelie. En cualquier filtro univariado (boxplot de longitud de aleta o peso), 192 mm o 3,500 g son valores comunes para pingüinos en general. Sin embargo, en el espacio multivariado \(\mathbb{R}^4\), esta combinación morfológica es una anomalía biológica severa.


8 Paso 7: Visualización multivariada integral: Coordenadas Paralelas

Para sintetizar la estructura morfológica de toda la colonia y observar cómo se distinguen las especies y las anomalías, empleamos un gráfico de Coordenadas Paralelas (px.parallel_coordinates):

Mostrar código
# Mapeo numérico para colorear por especie en coordenadas paralelas
especie_cod = {'Adelie': 1, 'Chinstrap': 2, 'Gentoo': 3}
df['species_id'] = df['species'].map(especie_cod)

fig_paralelas = px.parallel_coordinates(
    df,
    dimensions=features,
    color='species_id',
    color_continuous_scale=[(0.0, '#ff7f0e'), (0.5, '#2ca02c'), (1.0, '#1f77b4')],
    labels={
        'bill_length_mm': 'Long. Pico (mm)',
        'bill_depth_mm': 'Prof. Pico (mm)',
        'flipper_length_mm': 'Aleta (mm)',
        'body_mass_g': 'Peso (g)',
        'species_id': 'Especie'
    },
    title="Coordenadas Paralelas: Perfiles Fenotípicos de los Pingüinos de Palmer"
)

fig_paralelas.update_layout(height=480, template="plotly_white")
fig_paralelas.show()
NotaInterpretación de resultados: Perfiles morfológicos en Coordenadas Paralelas
  1. Separación de Gentoo (azul): Sus trayectorias viajan por la parte superior en Longitud de Pico, caen en Profundidad de Pico, y se elevan drásticamente en Aleta y Peso. Es una especie grande con pico largo y estilizado.
  2. Adelie (naranja) vs Chinstrap (verde): Tienen profundidades de pico, aletas y pesos muy similares, pero Chinstrap se separa con claridad por tener picos mucho más largos (la trayectoria sube en el primer eje y baja en el segundo).
  3. Cruce en tijera: El cruce visible de líneas entre Longitud de Pico y Profundidad de Pico refleja visualmente la correlación negativa aparente que desglosamos en la Paradoja de Simpson.

9 Conclusiones y guía práctica para el científico de datos

A partir de este caso práctico en pandas, podemos sintetizar un conjunto de reglas operativas para el análisis exploratorio multivariado dentro del ciclo CRISP-DM:

Pregunta analítica Herramienta en pandas / scipy / sklearn Criterio de interpretación
¿Dónde está el centro representativo? df.mean() vs. Mediana Geométrica (Weiszfeld) Si la media se aleja de la mediana geométrica, existen colas pesadas o grupos asimétricos.
¿Cuál es el volumen de dispersión? np.linalg.det(df.corr()) Si \(|\mathbf{R}| \approx 0\), existe fuerte redundancia lineal entre variables.
¿La correlación es real o espuria? np.linalg.inv(df.corr()) (Correlación Parcial) Si el signo o magnitud cambia respecto a Pearson, existe una variable de confusión latente.
¿Hay anomalías no visibles en 1D? MinCovDet().mahalanobis(X) vs \(\chi^2_p\) Permite detectar individuos que violan la alometría o correlación conjunta.
¿Cómo interactúan todos los atributos? px.parallel_coordinates(df, dimensions=...) Permite identificar firmas multivariadas, agrupamientos y cruces de correlación.

Con estas herramientas, los datos quedan rigurosamente diagnosticados y listos para las etapas siguientes del pipeline de minería: la imputación multivariada, el escalado para modelos predictivos y las técnicas de reducción dimensional como PCA.


Referencias