Alright everyone in the Figure Friday Week 20 community! 
This week, I jumped in early with my approach:
I want to show you this application, an interactive dashboard I put together so we can explore and compare dams in the United States that are a bit different from the rest – kind of like unique figures in a collection! To find them, I used a machine learning model called Isolation Forest. Imagine this model grabs the data and randomly separates it. The dams that are rare or distinct get isolated faster, like they’re easier to “catch” than the normal ones! So, the app shows them to us on an interactive map where we can filter by state and pick each dam to see its details.
With this tool, we can:
- See where these “singular” dams are located on the map. They’re colored based on their potential hazard level and their size depends on how “rare” they are!
- Filter the dams by state using a dropdown menu.
- Click on a dam on the map to learn more about it: inspection dates, what it’s used for, its classification, etc.
- Compare how a specific dam is with the average of others that are similar (with the same hazard level), using a radar-type chart!
- Download the data we’ve filtered to analyze it more in-depth if we want.
What’s the point of finding these distinct dams? Well, they can give us interesting clues for further investigation. Maybe they’re errors in the data, or perhaps dams with very special operating conditions, or simply unusual characteristics that deserve our attention, even if they don’t mean they’re dangerous!
The main idea is that together we can identify and compare these dams that aren’t “typical.” Heads up! Being “singular” doesn’t always mean there’s a problem, but that they’re statistically different, and this can be useful for learning and, who knows, maybe finding interesting things that deserve a closer look!
I hope you like it and find it useful for continuing to learn together! 
the code:
import dash
from dash import dcc, html, Input, Output, State, callback_context
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import dash_bootstrap_components as dbc
# Load data
df = pd.read_csv("represas_con_anomalias.csv") # Replace with the correct path
# Filter only dams with anomalies
df_anomalias = df[df['Is_Anomaly'] == 1]
# Get unique states for dropdown
estados_unicos = sorted(df_anomalias['State'].unique())
# Define metrics to compare in radar chart
metricas = [
'Dam Height (Ft)',
'NID Height (Ft)',
'Dam Length (Ft)',
'NID Storage (Acre-Ft)',
'Normal Storage (Acre-Ft)',
'Surface Area (Acres)',
'Drainage Area (Sq Miles)',
]
hazard_colors = {
'High': '#440154', # Morado oscuro
'Significant': '#FDE725', # Amarillo brillante (para destacar un riesgo considerable)
'Low': '#21918C', # Verde azulado
'Undetermined': '#90D7EC' # Azul claro (para indicar incertidumbre)
}
# Initialize Dash application with Bootstrap
app = dash.Dash(
__name__,
suppress_callback_exceptions=True,
external_stylesheets=[dbc.themes.SANDSTONE] # Modern Bootstrap theme
)
app.title = "US Dams Anomalies Dashboard"
# Dashboard layout using Bootstrap
app.layout = dbc.Container([
dbc.Row([
dbc.Col([
html.H1("🏞️ Distinct US Dams: A Comparative View",
className="text-center my-4 text-primary")
]),
]),
# Card with general dataset information
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardBody([
html.H5(f"Total of Distintc US Dams: {len(df_anomalias)}", className="card-title"),
html.P(f"Shown here are US dams flagged for singularities using the Isolation Forest machine learning model, designed to isolate atypical data points for analysis, not safety assessment.",
className="card-text"),
])
], className="mb-4 shadow border border-primary")
])
]),
# State filter dropdown and info message as floating elements
dbc.Row([
# State filter dropdown as a stylish button
dbc.Col([
dbc.DropdownMenu(label="Filter by State: All States",
id="state-dropdown-button",
className="shadow-lg",
children=[
dbc.DropdownMenuItem("All States", id="all-states", active=True),
dbc.DropdownMenuItem(divider=True),
*[dbc.DropdownMenuItem(state, id=f"state-{state}") for state in estados_unicos],
],color="primary",
toggle_style={"font-weight": "bold", "border-radius": "10px",
"padding": "12px 20px",
"box-shadow": "0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08)"},
toggle_class_name="d-flex align-items-center"),
# Hidden dropdown that stores the actual selected value
dcc.Dropdown(id='state-dropdown',
options=[{'label': 'All States', 'value': 'all'},
*[{'label': state, 'value': state} for state in estados_unicos]],
value='all',style={'display': 'none'}),], width=4, className="mb-3 d-flex align-items-center"),
# Information message with improved styling
dbc.Col([
html.Div(
html.P(
[html.I(className="fas fa-info-circle me-2"),
"Click on any dam on the map to view detailed comparative analysis."],
className="text-muted fst-italic mb-0 px-3 py-2 rounded-pill bg-light shadow-sm"),
className="d-flex justify-content-center align-items-center h-100"
)
], width=8, className="mb-3"),
], className="mb-4"),
# Two charts in the same row
dbc.Row([
# Left column for the map
dbc.Col([
dbc.Card([
dbc.CardHeader(html.H4("🌐 Location of Singular Dams", className="text-center")),
dbc.CardBody([
dcc.Graph(id='mapa-represas', style={'height': '70vh'}
),
])
], className="shadow border border-primary"),
html.Hr(),
# Agregar un botón de descarga para los datos filtrados
html.Button("Download Filter data", id="btn-download"),
dcc.Download(id="download-dataframe-csv"),
], width=7),
# Right column for information and radar chart
dbc.Col([
dbc.Card([
dbc.CardHeader(html.H4("↔️ Comparing Distinct Features", className="text-center")),
dbc.CardBody([
html.Div(id='info-represa-seleccionada', className="mb-3"),
dcc.Graph(id='radar-chart', style={'height': '50vh'}
),
])
], className="shadow border border-primary")
], width=5),
], className="mb-4"),
# Footer with additional information
dbc.Row([
dbc.Col([
html.Div([
html.P([html.I(className="fas fa-info-circle me-2"),
"US National Inventory of Dams Dashboard © 2025 source:Data is Plutal"],
className="text-muted fst-italic mb-0 px-3 py-2 rounded-pill bg-light shadow-sm")
],className="d-flex justify-content-center align-items-center h-100")
], className="mb-3")
]),
# Stores to save state and dam selection
dcc.Store(id='represa-seleccionada'),
dcc.Store(id='estado-seleccionado', data='all'),
], fluid=True, className="px-4 py-3 bg-light")
# Callback to update the state-dropdown based on the button clicks
@app.callback(
[Output('state-dropdown', 'value'),
Output('estado-seleccionado', 'data'),
Output('state-dropdown-button', 'label')],
[Input('all-states', 'n_clicks')] +
[Input(f'state-{state}', 'n_clicks') for state in estados_unicos],
[State('estado-seleccionado', 'data')]
)
def actualizar_estado(*args):
# Determine which item was clicked
ctx = dash.callback_context
if not ctx.triggered:
# No clicks yet, return default
return 'all', 'all', "Filter by State: All States"
# Get the ID of the component that triggered the callback
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
if button_id == 'all-states':
return 'all', 'all', "Filter by State: All States"
# Remove the 'state-' prefix to get the state name
for state in estados_unicos:
if button_id == f'state-{state}':
return state, state, f"Filter by State: {state}"
# If we get here, no specific button was matched
return dash.no_update, dash.no_update, dash.no_update
# Callback to update the map based on selected state
@app.callback(
Output('mapa-represas', 'figure'),
[Input('estado-seleccionado', 'data'),
Input('represa-seleccionada', 'data')]
)
def actualizar_mapa(estado_seleccionado, represa_seleccionada):
# Filter by state if a specific state is selected
if estado_seleccionado != 'all':
datos_mapa = df_anomalias[df_anomalias['State'] == estado_seleccionado]
else:
datos_mapa = df_anomalias
# Create scatter map
fig = px.scatter_map(
datos_mapa,
lat='Latitude',
lon='Longitude',
hover_name='Dam Name',
custom_data=['NID ID','County','Owner Names', 'Primary Owner Type', 'Year Completed'],
color='Hazard Potential Classification',
color_discrete_map=hazard_colors,
size='Anomaly_Score',
size_max=15,
zoom=3,
map_style='light',
opacity=0.8,
)
fig.update_traces(hovertemplate="<b>%{hovertext}</b><br>" +
"NID ID: %{customdata[0]}<br>" +
"County: %{customdata[1]}<br>" +
"Owner: %{customdata[2]}<br>" +
"Owner Type: %{customdata[3]}<br>" +
"Completed: %{customdata[4]}<extra></extra>")
# Highlight selected dam if there is one
if represa_seleccionada:
represa = df_anomalias[df_anomalias['NID ID'] == represa_seleccionada]
if not represa.empty and (estado_seleccionado == 'all' or represa['State'].values[0] == estado_seleccionado):
fig.add_trace(go.Scattermap(
lat=[represa['Latitude'].values[0]],
lon=[represa['Longitude'].values[0]],
mode='markers',
marker=dict(size=35, color='red', opacity=1),
hoverinfo='none',
showlegend=False
))
# Adjust the center of the map based on the data
if len(datos_mapa) > 0:
lat_centro = datos_mapa['Latitude'].mean()
lon_centro = datos_mapa['Longitude'].mean()
zoom_level = 3 if estado_seleccionado == 'all' else 5
else:
lat_centro = 39 # Default center of US
lon_centro = -98
zoom_level = 3
fig.update_layout(
margin=dict(l=0, r=0, t=0, b=0),
mapbox=dict(center=dict(lat=lat_centro, lon=lon_centro), zoom=zoom_level),
clickmode='event+select',
legend=dict(orientation="h",
yanchor="bottom",y=-0.20,xanchor="center",x=0.5)
)
return fig
# Callback to update selected dam when clicked on map
@app.callback(
Output('represa-seleccionada', 'data'),
[Input('mapa-represas', 'clickData')],
[State('represa-seleccionada', 'data')]
)
def actualizar_represa_seleccionada(click_data, represa_actual):
if click_data is None:
return represa_actual
# Get the NID ID of the clicked dam
nid_id = click_data['points'][0]['customdata'][0]
return nid_id
# Callback to display selected dam information
@app.callback(
Output('info-represa-seleccionada', 'children'),
[Input('represa-seleccionada', 'data')]
)
def mostrar_info_represa(represa_seleccionada):
if not represa_seleccionada:
return dbc.Alert(
"Select a dam on the map to view its comparative analysis",
color="info",
className="text-center"
)
represa = df[df['NID ID'] == represa_seleccionada]
if represa.empty:
return dbc.Alert(
"No information found for the selected dam",
color="warning",
className="text-center"
)
# Create a table with the dam's basic information using Bootstrap
info = [
html.H4(f"{represa['Dam Name'].values[0]}", className="text-center text-primary mb-3"),
dbc.Table([
html.Tbody([
html.Tr([
html.Td("Last Inspection Date:", className="font-weight-bold"),
html.Td(represa['Last Inspection Date'].values[0])
]),
html.Tr([
html.Td("Inspection Frequency:", className="font-weight-bold"),
html.Td(represa['Inspection Frequency'].values[0])
]),
html.Tr([
html.Td("Primary Purpose:", className="font-weight-bold"),
html.Td(represa['Primary Purpose'].values[0])
]),
html.Tr([
html.Td("Hazard Classification:", className="font-weight-bold"),
html.Td(represa['Hazard Potential Classification'].values[0])
]),
html.Tr([
html.Td("Condition Assessment:", className="font-weight-bold"),
html.Td(represa['Condition Assessment'].values[0] if not pd.isna(represa['Condition Assessment'].values[0]) else "Not available")
]),
html.Tr([
html.Td("Anomaly Score:", className="font-weight-bold"),
html.Td(
html.Span(
f"{represa['Anomaly_Score'].values[0]:.4f}",
className="badge bg-danger text-white p-2" if represa['Anomaly_Score'].values[0] > 0.7
else "badge bg-warning text-dark p-2" if represa['Anomaly_Score'].values[0] > 0.4
else "badge bg-success text-white p-2"
)
)
]),
])
], bordered=True, hover=True, size="sm", className="mb-0")
]
return html.Div(info)
#Callback to update radar chart
@app.callback(
Output('radar-chart', 'figure'),
[Input('represa-seleccionada', 'data')]
)
def actualizar_radar_chart(represa_seleccionada):
if not represa_seleccionada:
# If no dam is selected, show empty chart
fig = go.Figure()
fig.update_layout(
title="Select a dam to view comparison metrics",
xaxis=dict(visible=False),
yaxis=dict(visible=False),
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
font=dict(color="#2C3E50")
)
return fig
represa = df[df['NID ID'] == represa_seleccionada]
if represa.empty:
return go.Figure()
# Get hazard category of selected dam
categoria_peligro = represa['Hazard Potential Classification'].values[0]
# Calculate average metrics for all dams in the same category
df_categoria = df[df['Hazard Potential Classification'] == categoria_peligro]
# Prepare data for radar chart
datos_radar = []
# Normalize values for each metric
valores_represa = []
valores_promedio = []
etiquetas_metricas = []
for metrica in metricas:
# Create shorter label for the chart
etiqueta_corta = metrica.replace(' (Ft)', '').replace(' (Acre-Ft)', '').replace(' (Acres)', '').replace(' (Sq Miles)', '')
etiquetas_metricas.append(etiqueta_corta)
# Value of selected dam (with NaN handling)
valor_represa = represa[metrica].values[0] if not pd.isna(represa[metrica].values[0]) else 0
# Average value of the category (with NaN handling)
valor_promedio = df_categoria[metrica].mean() if not pd.isna(df_categoria[metrica].mean()) else 0
# Add values to lists
valores_represa.append(valor_represa)
valores_promedio.append(valor_promedio)
# Normalize values to be on a comparable scale
max_valores = [max(a, b) for a, b in zip(valores_represa, valores_promedio)]
max_valores = [val if val > 0 else 1 for val in max_valores] # Avoid division by zero
valores_represa_norm = [val / max_val for val, max_val in zip(valores_represa, max_valores)]
valores_promedio_norm = [val / max_val for val, max_val in zip(valores_promedio, max_valores)]
# Create radar chart with more attractive colors
fig = go.Figure()
fig.add_trace(go.Scatterpolar(
r=valores_represa_norm,
theta=etiquetas_metricas,
fill='toself',
name=f"{represa['Dam Name'].values[0]}",
line=dict(color='#3498DB'),
fillcolor='rgba(52, 152, 219, 0.3)'
))
fig.add_trace(go.Scatterpolar(
r=valores_promedio_norm,
theta=etiquetas_metricas,
fill='toself',
name=f"Average - {categoria_peligro}",
line=dict(color='#E74C3C'),
fillcolor='rgba(231, 76, 60, 0.3)'
))
fig.update_layout(
polar=dict(
radialaxis=dict(
visible=True,
range=[0, 1],
showticklabels=False,
showline=False,
ticks='',
gridcolor='#95a5a6'
),
angularaxis=dict(
gridcolor='#95a5a6'
),
bgcolor='rgba(255, 255, 255, 0.9)'
),
font=dict(color="#2C3E50"),
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)',
showlegend=True,
legend=dict(
orientation="h",
yanchor="bottom",
y=-0.2,
xanchor="center",
x=0.5
)
)
return fig
# Y el callback correspondiente
@app.callback(
Output("download-dataframe-csv", "data"),
Input("btn-download", "n_clicks"),
State("estado-seleccionado", "data"),
prevent_initial_call=True,
)
def func(n_clicks, estado_seleccionado):
if estado_seleccionado != 'all':
df_filtrado = df_anomalias[df_anomalias['State'] == estado_seleccionado]
else:
df_filtrado = df_anomalias
return dcc.send_data_frame(df_filtrado.to_csv, "represas_filtradas.csv")
if __name__ == '__main__':
app.run_server(debug=True, jupyter_mode='external')
the app: