The map displays dams by Hazard Potential Classification.
Filters on the left allow users to sort by State, Hazard Potential, and Year Completed.
I randomly clicked on a dump, the details of which can be seen in the card on the right.
@Ester it’s really useful that you added the link to each summary card of the dam, after the dam is clicked in the map.
One small enhancement I would suggest is to make the dropdowns dynamically tied to each other. So if a person chooses a state and that state only has high and low hazard potential, the only options for that second dropdown would be high and low.
This is the code with the isolation forest code, s
import pandas as pd
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
import warnings
warnings.filterwarnings('ignore')
# Cargar los datos
# Ajusta el nombre del archivo según sea necesario
try:
df = pd.read_csv('nation-dams.csv')
print(f"Datos cargados correctamente. Forma: {df.shape}")
except Exception as e:
print(f"Error al cargar los datos: {e}")
# Si no se puede cargar el archivo, creamos un DataFrame vacío con las columnas necesarias
df = pd.DataFrame()
# Función para identificar y seleccionar características numéricas adecuadas
def get_numeric_features(dataframe):
# Seleccionamos columnas numéricas con menos de 30% de valores nulos
numeric_cols = dataframe.select_dtypes(include=['int64', 'float64']).columns.tolist()
valid_cols = []
for col in numeric_cols:
if dataframe[col].notnull().sum() / len(dataframe) > 0.7: # Menos de 30% de nulos
valid_cols.append(col)
# Excluimos Latitude y Longitude para el análisis de anomalías
exclude_cols = ['Latitude', 'Longitude']
valid_cols = [col for col in valid_cols if col not in exclude_cols]
return valid_cols
# Función principal para detectar anomalías por categoría de peligro
def detect_anomalies_by_hazard(df, contamination=0.05):
if df.empty:
print("El DataFrame está vacío. No se pueden detectar anomalías.")
return df
# Verificar que la columna 'Hazard Potential Classification' existe
if 'Hazard Potential Classification' not in df.columns:
print("No se encontró la columna 'Hazard Potential Classification'")
return df
# Obtener categorías únicas de peligro
hazard_categories = df['Hazard Potential Classification'].unique()
print(f"Categorías de peligro identificadas: {hazard_categories}")
# Crear un DataFrame para almacenar resultados
results = []
# Procesar cada categoría de peligro por separado
for category in hazard_categories:
print(f"\nProcesando categoría: {category}")
# Filtrar por categoría de peligro
category_df = df[df['Hazard Potential Classification'] == category]
print(f" Número de represas en esta categoría: {len(category_df)}")
# Seleccionar características numéricas relevantes
numeric_features = get_numeric_features(category_df)
print(f" Características numéricas seleccionadas: {numeric_features}")
if len(numeric_features) < 2:
print(f" No hay suficientes características numéricas para la categoría {category}")
category_df['Anomaly_Score'] = np.nan
results.append(category_df[['NID ID', 'Dam Name', 'Hazard Potential Classification', 'Anomaly_Score']])
continue
# Extraer y preprocesar datos
X = category_df[numeric_features].copy()
# Escalar datos
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X.fillna(X.mean()))
# Ajustar contaminación basado en el tamaño del conjunto de datos
actual_contamination = min(contamination, 0.2) # Máximo 20% de anomalías
# Entrenar modelo Isolation Forest
model = IsolationForest(
contamination=actual_contamination,
random_state=42,
n_jobs=-1 # Usar todos los núcleos disponibles
)
# Entrenar el modelo y predecir anomalías
model.fit(X_scaled)
# Obtener puntuaciones de anomalía (valores más negativos = más anómalos)
anomaly_scores = model.decision_function(X_scaled)
# Normalizar puntuaciones para facilitar la interpretación (0-1, donde 0 = más anómalo)
normalized_scores = (anomaly_scores - anomaly_scores.min()) / (anomaly_scores.max() - anomaly_scores.min())
normalized_scores = 1 - normalized_scores # Invertir para que valores más altos = más anómalos
# Añadir puntuaciones al DataFrame
category_df = category_df.copy()
category_df['Anomaly_Score'] = normalized_scores
# Añadir etiqueta de anomalía (-1 para anomalías, 1 para normales)
category_df['Is_Anomaly'] = model.predict(X_scaled)
category_df['Is_Anomaly'] = category_df['Is_Anomaly'].map({1: 0, -1: 1}) # Convertir a 0 (normal) y 1 (anomalía)
# Identificar top anomalías
n_top = min(10, len(category_df))
top_anomalies = category_df.sort_values('Anomaly_Score', ascending=False).head(n_top)
print(f" Top {n_top} anomalías detectadas en la categoría {category}:")
for idx, row in top_anomalies.iterrows():
print(f" - {row['Dam Name']} (ID: {row['NID ID']}): Score {row['Anomaly_Score']:.4f}")
# Guardar resultados
results.append(category_df[['NID ID', 'Dam Name', 'State', 'Hazard Potential Classification',
'Anomaly_Score', 'Is_Anomaly']])
# Combinar resultados
final_results = pd.concat(results)
print(f"\nProceso completado. {len(final_results)} represas analizadas.")
return final_results
# Ejecutar la detección de anomalías
if not df.empty:
anomaly_results = detect_anomalies_by_hazard(df)
# Fusionar los resultados con el DataFrame original
df_with_anomalies = df.merge(
anomaly_results[['NID ID', 'Anomaly_Score', 'Is_Anomaly']],
on='NID ID',
how='left'
)
# Guardar resultados
try:
df_with_anomalies.to_csv('represas_con_anomalias.csv', index=False)
print("Resultados guardados en 'represas_con_anomalias.csv'")
except Exception as e:
print(f"Error al guardar resultados: {e}")
# Mostrar estadísticas de anomalías por categoría
print("\nEstadísticas de anomalías por categoría de peligro:")
anomaly_stats = df_with_anomalies.groupby('Hazard Potential Classification')['Is_Anomaly'].agg(['sum', 'count'])
anomaly_stats['porcentaje'] = (anomaly_stats['sum'] / anomaly_stats['count'] * 100).round(2)
print(anomaly_stats)