Skip to content

making changes to file call defined functions and explicit calls to m… #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app_modules/functions_app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pandas as pd
from .helper_modules.geodata import add_state_abbreviations, merge_with_fips, merge_with_geolocation
from .query_gbq import query_table
from app_modules.helper_modules.geodata import add_state_abbreviations, merge_with_fips, merge_with_geolocation
from app_modules.query_gbq import query_table
from google.api_core.exceptions import GoogleAPIError


Expand Down
180 changes: 51 additions & 129 deletions app_modules/visualizations_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,61 @@
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import requests
from .functions_app import (
from app_modules.functions_app import (
prep_bird_flu_data,
prep_wild_bird_data,
prep_egg_price_data,
prep_stock_price_data,
)


# === New Function: CREATE GEOSPATIAL PLOT ===
def create_geospatial(data):
"""
Creates a geospatial plot using latitude, longitude, and flock size.
"""
fig = px.scatter_mapbox(
data,
lat="lat",
lon="lng",
size="Flock Size",
color="Flock Size",
color_continuous_scale="Viridis",
mapbox_style="carto-positron",
zoom=3,
title="Geospatial Visualization"
)
return fig


# === New Function: CREATE TIME SERIES PLOT ===
def create_time_series(egg_price_df, stock_price_df):
"""
Creates a time series plot comparing egg prices and stock prices.
"""
fig = make_subplots(specs=[[{"secondary_y": True}]])

fig.add_trace(
go.Scatter(x=egg_price_df.index, y=egg_price_df["Avg_Price"], name="Egg Price"),
secondary_y=False,
)

fig.add_trace(
go.Scatter(x=stock_price_df.index, y=stock_price_df["Close/Last"], name="Stock Price"),
secondary_y=True,
)

fig.update_layout(
title="Egg Prices vs Stock Prices",
xaxis_title="Date",
height=500,
)

fig.update_yaxes(title_text="Egg Price (USD)", secondary_y=False)
fig.update_yaxes(title_text="Stock Price (USD)", secondary_y=True)

return fig


# === 1. EGG PRICE vs STOCK PRICE TIME SERIES ===
def show_price_comparison(egg_df, stock_df, stock_name="Selected Stock"):
Expand Down Expand Up @@ -67,20 +114,19 @@ def show_bird_flu_trends():

# === 3. COMBINED OVERVIEW ===
def show_combined_dashboard():

#Loading data
# Loading data
egg_df = prep_egg_price_data("egg_prices")
calm_df, _, _ = prep_stock_price_data("calmaine")
for col in ["Close_Last", "Open", "High", "Low"]:
calm_df[col] = calm_df[col].replace(r'[\$,]', '', regex=True).astype(float)
flu_df = prep_bird_flu_data("bird_flu")

#prepping birdflu
# Prepping bird flu
flu_df.rename(columns={"Outbreak Date": "Date"}, inplace=True)
flu_df = flu_df.groupby("Date")["Flock Size"].sum().reset_index()
flu_df = flu_df.set_index("Date").resample("M").sum().reset_index()

# resample stocks
# Resample stocks
calm_df = calm_df.set_index("Date").resample("M").mean().reset_index()

fig = make_subplots(specs=[[{"secondary_y": True}]])
Expand All @@ -106,127 +152,3 @@ def show_combined_dashboard():
fig.update_yaxes(title_text="Stock Price (USD)", secondary_y=True)

st.plotly_chart(fig, use_container_width=True)

def show_wild_bird_map(wild_bird_df, bird_flu_df):
"""
Displays a cumulative-progressive map:
State color = chicken deaths (Sum of Flock Size)
Circles = wild bird infections (Sum of Wild Birds)
Data accumulates progressively from Jan 2022.
Requires https://raw.githubusercontent.com/advanced-computing/chicken_egg/main/app_data/us_states.geojson
"""

# Cargar el GeoJSON de estados
url = "https://raw.githubusercontent.com/advanced-computing/chicken_egg/main/app_data/us_states.geojson"
response = requests.get(url)
geojson = response.json()

# Normalizar nombres de estado en tus datos
wild_df = wild_bird_df.copy()
flock_df = bird_flu_df.copy()
wild_df["State"] = wild_df["State"].str.title()
flock_df["State"] = flock_df["State"].str.title()

# Filtrar GeoJSON para incluir solo estados en los datasets
geojson_states = [f["properties"]["NAME"] for f in geojson["features"]]
used_states = set(wild_df["State"].unique()).union(set(flock_df["State"].unique()))
valid_states = sorted(set(geojson_states).intersection(used_states))
geojson["features"] = [f for f in geojson["features"] if f["properties"]["NAME"] in valid_states]

# Preparar columnas de mes
wild_df['Month'] = pd.to_datetime(wild_df['Date Detected'], errors='coerce').dt.to_period("M").dt.to_timestamp()
flock_df['Month'] = pd.to_datetime(flock_df['Outbreak Date'], errors='coerce').dt.to_period("M").dt.to_timestamp()

# Agrupar por mes y estado
wild_grouped = wild_df.groupby(['Month', 'State']).size().reset_index(name='Wild Count')
flock_grouped = flock_df.groupby(['Month', 'State']).agg({
'Flock Size': 'sum',
'lat': 'mean',
'lng': 'mean'
}).reset_index()

# Unir ambos
merged = pd.merge(flock_grouped, wild_grouped, on=['Month', 'State'], how='left')
merged['Wild Count'] = merged['Wild Count'].fillna(0)
merged['Month_str'] = merged['Month'].dt.strftime("%b %Y")

# Timeline
months_sorted = sorted(merged['Month'].dropna().unique())
month_strs = [pd.to_datetime(m).strftime("%b %Y") for m in months_sorted]

selected_label = st.select_slider("🗓️ Progressive Timeline (Cumulative to...)", options=month_strs, value=month_strs[0])
selected_cutoff = pd.to_datetime(selected_label)

# Acumulado hasta la fecha seleccionada
cumulative_view = merged[merged['Month'] <= selected_cutoff]

if cumulative_view.empty:
st.info("No data available up to this date.")
return

# Agrupar por estado acumulado
view = cumulative_view.groupby("State").agg({
"Flock Size": "sum",
"Wild Count": "sum",
"lat": "mean",
"lng": "mean"
}).reset_index()

# Hover info
view["Hover"] = (
"State: " + view["State"] +
"<br>Wild Infections: " + view["Wild Count"].astype(int).astype(str) +
"<br>Flock Deaths: " + view["Flock Size"].astype(int).astype(str)
)

# Pintar estados por Flock Size (choropleth)
fig = px.choropleth_mapbox(
view,
geojson=geojson,
locations="State",
featureidkey="properties.NAME",
color="Flock Size",
color_continuous_scale="YlOrRd",
range_color=(0, view["Flock Size"].max()),
mapbox_style="carto-darkmatter",
zoom=3,
center={"lat": 37.8, "lon": -96},
opacity=0.6,
labels={"Flock Size": "Flock Deaths"},
height=600
)

# Añadir globos por Wild Count
fig.add_scattermapbox(
lat=view["lat"],
lon=view["lng"],
mode="markers",
marker=px.scatter_mapbox(
view,
lat="lat",
lon="lng",
size="Wild Count",
size_max=50
).data[0].marker,
text=view["State"] + "<br>Wild Cases: " + view["Wild Count"].astype(int).astype(str),
hoverinfo="text",
name="Wild Bird Infections"
)

fig.update_layout(
title=f"📍 Wild Bird Infections & Chicken Deaths – Cumulative until {selected_label}",
margin={"r": 0, "t": 50, "l": 0, "b": 0},
paper_bgcolor="#111111",
font_color="white",
legend_title_text="Chicken Deaths (State Color)",
)

# Descripción superior
st.markdown("""
**Map Explanation**
- **State Color**: Number of chickens lost due to outbreaks (Flock Size)
- **Bubble Size**: Number of wild bird infections detected
- Use the slider to view how both have progressed from Jan 2022 until now.
""")

st.plotly_chart(fig, use_container_width=True)
22 changes: 21 additions & 1 deletion app_tests/test_data_prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
from app_modules.functions_app import (
prep_bird_flu_data,
prep_egg_price_data,
prep_stock_price_data
prep_stock_price_data,
prep_wild_bird_data, # Added import for prep_wild_bird_data
)
from app_tests.test_helper_data_prep import (
create_stock_ex,
create_egg_price_ex,
create_bird_flu_ex,
create_wild_bird_ex, # Added import for create_wild_bird_ex
)

@pytest.mark.parametrize(
Expand All @@ -18,6 +20,7 @@
(prep_stock_price_data, pd.DataFrame({"Open": [100, 101, 102]}), KeyError), # Missing 'Close/Last'
(prep_egg_price_data, pd.DataFrame({"Price": [2.5, 3.0, 3.2]}), ValueError), # Missing 'Year'
(prep_bird_flu_data, pd.DataFrame({"Flock Size": [10, 20]}), KeyError), # Missing 'State'
(prep_wild_bird_data, pd.DataFrame({"County": ["A", "B"]}), KeyError), # Missing 'State'
]
)
def test_prep_functions_raise_errors(func, df, expected_exception):
Expand Down Expand Up @@ -51,3 +54,20 @@ def test_bird_flu_flock_size_is_numeric():
bird_flu_df = pd.read_csv(StringIO(create_bird_flu_ex()))
df = prep_bird_flu_data(bird_flu_df)
assert pd.api.types.is_numeric_dtype(df["Flock Size"]), "Flock Size should be numeric."

def test_wild_bird_data_has_lat_lng():
"""
Test that prep_wild_bird_data returns a DataFrame with 'lat' and 'lng' columns.
"""
wild_bird_df = pd.read_csv(StringIO(create_wild_bird_ex()))
df = prep_wild_bird_data(wild_bird_df)
assert "lat" in df.columns, "DataFrame must have 'lat' column."
assert "lng" in df.columns, "DataFrame must have 'lng' column."

def test_wild_bird_data_missing_columns():
"""
Test that prep_wild_bird_data raises an error if required columns are missing.
"""
wild_bird_df = pd.DataFrame({"County": ["A", "B"]}) # Missing 'State'
with pytest.raises(KeyError):
prep_wild_bird_data(wild_bird_df)
10 changes: 10 additions & 0 deletions app_tests/test_helper_data_prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,13 @@ def create_bird_flu_ex():
Alabama,Montgomery,1000,32.3668052,-86.2999689
Georgia,Clarke,2000,33.9519347,-83.357567
"""

def create_wild_bird_ex():
"""
Returns example data similar to wild bird data
"""
return """State,County,Observation Count,lat,lng
Alabama,Montgomery,50,32.3668052,-86.2999689
Georgia,Clarke,75,33.9519347,-83.357567
Florida,Miami-Dade,100,25.7616798,-80.1917902
"""
69 changes: 68 additions & 1 deletion app_tests/test_viz.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import pytest
import pandas as pd
from app_modules.visualizations_app import create_geospatial, create_time_series
from app_modules.visualizations_app import (
create_geospatial,
create_time_series,
show_price_comparison,
show_bird_flu_trends,
show_combined_dashboard,
)
import streamlit as st


def test_create_geospatial():
sample_data = pd.DataFrame({
Expand All @@ -13,6 +21,7 @@ def test_create_geospatial():

assert fig and len(fig.data) > 0, "Geospatial plot should contain data points."


def test_create_time_series():
egg_price_df = pd.DataFrame({
'Date': pd.date_range(start='2020-01-01', periods=5),
Expand All @@ -27,3 +36,61 @@ def test_create_time_series():
fig = create_time_series(egg_price_df, stock_price_df)

assert fig and len(fig.data) == 2, "Time series should contain two lines."


def test_show_price_comparison(mocker):
egg_price_df = pd.DataFrame({
'Date': pd.date_range(start='2020-01-01', periods=5),
'Avg_Price': [1, 2, 3, 4, 5]
}).set_index('Date')

stock_price_df = pd.DataFrame({
'Date': pd.date_range(start='2020-01-01', periods=5),
'Close_Last': [10, 20, 30, 40, 50]
})

mocker.patch("streamlit.plotly_chart") # Mock Streamlit's plotly_chart
show_price_comparison(egg_price_df, stock_price_df, stock_name="Test Stock")

st.plotly_chart.assert_called_once() # Ensure the chart was rendered


def test_show_bird_flu_trends(mocker):
mocker.patch("app_modules.visualizations_app.prep_bird_flu_data", return_value=pd.DataFrame({
'Outbreak Date': pd.date_range(start='2020-01-01', periods=5),
'Flock Size': [10, 20, 30, 40, 50]
}))

mocker.patch("streamlit.plotly_chart") # Mock Streamlit's plotly_chart
show_bird_flu_trends()

st.plotly_chart.assert_called_once() # Ensure the chart was rendered


def test_show_combined_dashboard(mocker):
mocker.patch("app_modules.visualizations_app.prep_egg_price_data", return_value=pd.DataFrame({
'Date': pd.date_range(start='2020-01-01', periods=5),
'Avg_Price': [1, 2, 3, 4, 5]
}))

mocker.patch("app_modules.visualizations_app.prep_stock_price_data", return_value=(
pd.DataFrame({
'Date': pd.date_range(start='2020-01-01', periods=5),
'Close_Last': [10, 20, 30, 40, 50],
'Open': [5, 15, 25, 35, 45],
'High': [15, 25, 35, 45, 55],
'Low': [0, 10, 20, 30, 40]
}),
None,
None
))

mocker.patch("app_modules.visualizations_app.prep_bird_flu_data", return_value=pd.DataFrame({
'Outbreak Date': pd.date_range(start='2020-01-01', periods=5),
'Flock Size': [10, 20, 30, 40, 50]
}))

mocker.patch("streamlit.plotly_chart") # Mock Streamlit's plotly_chart
show_combined_dashboard()

st.plotly_chart.assert_called() # Ensure charts were rendered