From 967b0ed03dc6e7dcae47b72d6c221f938a608b31 Mon Sep 17 00:00:00 2001 From: Keith Galli Date: Sun, 2 Jun 2024 16:04:34 -0600 Subject: [PATCH 1/7] Code after the first video --- sales/app.py | 24 ++++++++++++++++++++++++ sales/requirements.txt | 9 +++++++++ 2 files changed, 33 insertions(+) create mode 100644 sales/app.py create mode 100644 sales/requirements.txt diff --git a/sales/app.py b/sales/app.py new file mode 100644 index 0000000..b18afe6 --- /dev/null +++ b/sales/app.py @@ -0,0 +1,24 @@ +from pathlib import Path +import pandas as pd + +import plotly.express as px +from shiny import reactive +from shiny.express import render, input, ui +from shinywidgets import render_plotly + +ui.page_opts(title="Sales Dashboard - Video 1 of 5", fillable=True) + +ui.input_numeric("n", "Number of Items", 5, min=0, max=20) + +@reactive.calc +def dat(): + infile = Path(__file__).parent / "data/sales.csv" + return pd.read_csv(infile) + +with ui.layout_columns(): + + @render_plotly + def plot1(): + df = dat() + top_sales = df.groupby('product')['quantity_ordered'].sum().nlargest(input.n()).reset_index() + return px.bar(top_sales, x='product', y='quantity_ordered') diff --git a/sales/requirements.txt b/sales/requirements.txt new file mode 100644 index 0000000..39088db --- /dev/null +++ b/sales/requirements.txt @@ -0,0 +1,9 @@ +pandas +plotly +folium +matplotlib +shiny +shinywidgets +seaborn +altair +anywidget \ No newline at end of file From cb8c531746a313fad76d8a98f7cb9d7c026e5840 Mon Sep 17 00:00:00 2001 From: Keith Galli Date: Sun, 2 Jun 2024 16:06:28 -0600 Subject: [PATCH 2/7] Removed original requirements.txt and moved into sales folder --- requirements.txt | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 7d5ce4e..0000000 --- a/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -pandas -plotly -folium -matplotlib -shiny -shinywidgets -seaborn -altair -anywidget From 682da59dd705156bbb7fa6657df6a759f920c87e Mon Sep 17 00:00:00 2001 From: Keith Galli Date: Mon, 3 Jun 2024 17:04:04 -0600 Subject: [PATCH 3/7] Initial sales by city code --- sales/app.py | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/sales/app.py b/sales/app.py index b18afe6..9a12566 100644 --- a/sales/app.py +++ b/sales/app.py @@ -1,5 +1,6 @@ from pathlib import Path import pandas as pd +import calendar import plotly.express as px from shiny import reactive @@ -13,12 +14,37 @@ @reactive.calc def dat(): infile = Path(__file__).parent / "data/sales.csv" - return pd.read_csv(infile) + df = pd.read_csv(infile) + df['order_date'] = pd.to_datetime(df['order_date']) + df['month'] = df['order_date'].dt.month_name() + return df -with ui.layout_columns(): +# @render_plotly +# def plot1(): +# df = dat() +# top_sales = df.groupby('product')['quantity_ordered'].sum().nlargest(input.n()).reset_index() +# return px.bar(top_sales, x='product', y='quantity_ordered') - @render_plotly - def plot1(): - df = dat() - top_sales = df.groupby('product')['quantity_ordered'].sum().nlargest(input.n()).reset_index() - return px.bar(top_sales, x='product', y='quantity_ordered') +ui.input_selectize( + "city", + "Select a City:", + ['Dallas (TX)', 'Boston (MA)', 'Los Angeles (CA)', 'San Francisco (CA)', 'Seattle (WA)', 'Atlanta (GA)', 'New York City (NY)', 'Portland (OR)', 'Austin (TX)', 'Portland (ME)'], + multiple=True, +) + +@render_plotly +def sales_over_time(): + df = dat() + print(list(df.city.unique())) + sales = df.groupby(['city', 'month'])['quantity_ordered'].sum().reset_index() + sales_by_city = sales[sales['city'] == "Boston (MA)"] + month_orders = calendar.month_name[1:] + fig = px.bar(sales_by_city, x='month', y='quantity_ordered', category_orders={'month': month_orders}) + return fig + + +with ui.card(): + ui.card_header("Sample Sales Data") + @render.data_frame + def sample_sales_data(): + return dat().head(100) From d668f80afdf791885d503b7bb91ebe5ee1dd4407 Mon Sep 17 00:00:00 2001 From: Keith Galli Date: Mon, 3 Jun 2024 17:21:59 -0600 Subject: [PATCH 4/7] Implemented basic reactivity components --- sales/app.py | 64 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/sales/app.py b/sales/app.py index 9a12566..f540b6f 100644 --- a/sales/app.py +++ b/sales/app.py @@ -7,44 +7,72 @@ from shiny.express import render, input, ui from shinywidgets import render_plotly -ui.page_opts(title="Sales Dashboard - Video 1 of 5", fillable=True) +ui.page_opts(title="Sales Dashboard - Video 2 of 5", fillable=False) + +ui.input_checkbox("bar_color", "Make Bars Red?", False) ui.input_numeric("n", "Number of Items", 5, min=0, max=20) +@reactive.calc +def color(): + return "red" if input.bar_color() else "blue" + @reactive.calc def dat(): infile = Path(__file__).parent / "data/sales.csv" df = pd.read_csv(infile) - df['order_date'] = pd.to_datetime(df['order_date']) - df['month'] = df['order_date'].dt.month_name() + df["order_date"] = pd.to_datetime(df["order_date"]) + df["month"] = df["order_date"].dt.month_name() return df -# @render_plotly -# def plot1(): -# df = dat() -# top_sales = df.groupby('product')['quantity_ordered'].sum().nlargest(input.n()).reset_index() -# return px.bar(top_sales, x='product', y='quantity_ordered') - -ui.input_selectize( - "city", - "Select a City:", - ['Dallas (TX)', 'Boston (MA)', 'Los Angeles (CA)', 'San Francisco (CA)', 'Seattle (WA)', 'Atlanta (GA)', 'New York City (NY)', 'Portland (OR)', 'Austin (TX)', 'Portland (ME)'], - multiple=True, +@render_plotly +def plot1(): + df = dat() + top_sales = df.groupby('product')['quantity_ordered'].sum().nlargest(input.n()).reset_index() + fig = px.bar(top_sales, x='product', y='quantity_ordered') + fig.update_traces(marker_color=color()) + return fig + +ui.input_selectize( + "city", + "Select a City:", + [ + "Dallas (TX)", + "Boston (MA)", + "Los Angeles (CA)", + "San Francisco (CA)", + "Seattle (WA)", + "Atlanta (GA)", + "New York City (NY)", + "Portland (OR)", + "Austin (TX)", + "Portland (ME)", + ], + multiple=False, + selected='Boston (MA)' ) + @render_plotly def sales_over_time(): df = dat() print(list(df.city.unique())) - sales = df.groupby(['city', 'month'])['quantity_ordered'].sum().reset_index() - sales_by_city = sales[sales['city'] == "Boston (MA)"] + sales = df.groupby(["city", "month"])["quantity_ordered"].sum().reset_index() + sales_by_city = sales[sales["city"] == input.city()] month_orders = calendar.month_name[1:] - fig = px.bar(sales_by_city, x='month', y='quantity_ordered', category_orders={'month': month_orders}) + fig = px.bar( + sales_by_city, + x="month", + y="quantity_ordered", + title=f"Sales over Time -- {input.city()}", + category_orders={"month": month_orders}, + ) + fig.update_traces(marker_color=color()) return fig - with ui.card(): ui.card_header("Sample Sales Data") + @render.data_frame def sample_sales_data(): return dat().head(100) From 317210736f875f6e6c9a91d69b9b3b620e9eeab2 Mon Sep 17 00:00:00 2001 From: Keith Galli Date: Fri, 21 Jun 2024 15:35:38 -0400 Subject: [PATCH 5/7] Finished video #3 code --- sales/app.py | 141 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 53 deletions(-) diff --git a/sales/app.py b/sales/app.py index f540b6f..ba1404a 100644 --- a/sales/app.py +++ b/sales/app.py @@ -7,15 +7,7 @@ from shiny.express import render, input, ui from shinywidgets import render_plotly -ui.page_opts(title="Sales Dashboard - Video 2 of 5", fillable=False) - -ui.input_checkbox("bar_color", "Make Bars Red?", False) - -ui.input_numeric("n", "Number of Items", 5, min=0, max=20) - -@reactive.calc -def color(): - return "red" if input.bar_color() else "blue" +ui.page_opts(title="Sales Dashboard - Video 3 of 5", fillable=False) @reactive.calc def dat(): @@ -23,52 +15,95 @@ def dat(): df = pd.read_csv(infile) df["order_date"] = pd.to_datetime(df["order_date"]) df["month"] = df["order_date"].dt.month_name() + df['value'] = df['quantity_ordered'] * df['price_each'] return df -@render_plotly -def plot1(): - df = dat() - top_sales = df.groupby('product')['quantity_ordered'].sum().nlargest(input.n()).reset_index() - fig = px.bar(top_sales, x='product', y='quantity_ordered') - fig.update_traces(marker_color=color()) - return fig - -ui.input_selectize( - "city", - "Select a City:", - [ - "Dallas (TX)", - "Boston (MA)", - "Los Angeles (CA)", - "San Francisco (CA)", - "Seattle (WA)", - "Atlanta (GA)", - "New York City (NY)", - "Portland (OR)", - "Austin (TX)", - "Portland (ME)", - ], - multiple=False, - selected='Boston (MA)' -) - - -@render_plotly -def sales_over_time(): - df = dat() - print(list(df.city.unique())) - sales = df.groupby(["city", "month"])["quantity_ordered"].sum().reset_index() - sales_by_city = sales[sales["city"] == input.city()] - month_orders = calendar.month_name[1:] - fig = px.bar( - sales_by_city, - x="month", - y="quantity_ordered", - title=f"Sales over Time -- {input.city()}", - category_orders={"month": month_orders}, - ) - fig.update_traces(marker_color=color()) - return fig +with ui.card(): + ui.card_header("Sales by City 2023") + + with ui.layout_sidebar(): + with ui.sidebar(bg="#f8f8f8", open='open'): + ui.input_selectize( + "city", + "Select a City:", + [ + "Dallas (TX)", + "Boston (MA)", + "Los Angeles (CA)", + "San Francisco (CA)", + "Seattle (WA)", + "Atlanta (GA)", + "New York City (NY)", + "Portland (OR)", + "Austin (TX)", + "Portland (ME)", + ], + multiple=False, + selected='Boston (MA)' + ) + + @render_plotly + def sales_over_time(): + df = dat() + print(list(df.city.unique())) + sales = df.groupby(["city", "month"])["quantity_ordered"].sum().reset_index() + sales_by_city = sales[sales["city"] == input.city()] + month_orders = calendar.month_name[1:] + fig = px.bar( + sales_by_city, + x="month", + y="quantity_ordered", + title=f"Sales over Time -- {input.city()}", + category_orders={"month": month_orders}, + ) + return fig + +with ui.layout_column_wrap(width=1/2): + with ui.navset_card_underline(id="tab", footer=ui.input_numeric("n", "Number of Items", 5, min=0, max=20)): + with ui.nav_panel("Top Sellers"): + + @render_plotly + def plot_top_sellers(): + df = dat() + top_sales = df.groupby('product')['quantity_ordered'].sum().nlargest(input.n()).reset_index() + fig = px.bar(top_sales, x='product', y='quantity_ordered') + return fig + + with ui.nav_panel("Top Sellers Value ($)"): + + @render_plotly + def plot_top_sellers_value(): + df = dat() + top_sales = df.groupby('product')['value'].sum().nlargest(input.n()).reset_index() + fig = px.bar(top_sales, x='product', y='value') + return fig + + with ui.nav_panel("Lowest Sellers"): + + @render_plotly + def plot_lowest_sellers(): + df = dat() + top_sales = df.groupby('product')['quantity_ordered'].sum().nsmallest(input.n()).reset_index() + fig = px.bar(top_sales, x='product', y='quantity_ordered') + return fig + + with ui.nav_panel("Lowest Sellers Value ($)"): + @render_plotly + def plot_lowest_sellers_value(): + df = dat() + top_sales = df.groupby('product')['value'].sum().nsmallest(input.n()).reset_index() + fig = px.bar(top_sales, x='product', y='value') + return fig + + + with ui.card(): + ui.card_header("Sales by Time of Day Heatmap") + "Heatmap" + +with ui.card(): + ui.card_header("Sales by Location Map") + "Content Here" + with ui.card(): ui.card_header("Sample Sales Data") From 6c9bc3a141859a8b4c07d3089d4bc39f0c406668 Mon Sep 17 00:00:00 2001 From: Keith Galli Date: Tue, 25 Jun 2024 16:27:29 -0400 Subject: [PATCH 6/7] prep video 4 --- sales/app.py | 261 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 212 insertions(+), 49 deletions(-) diff --git a/sales/app.py b/sales/app.py index ba1404a..319f3a7 100644 --- a/sales/app.py +++ b/sales/app.py @@ -1,13 +1,50 @@ from pathlib import Path import pandas as pd +import numpy as np import calendar +import matplotlib.pyplot as plt + +import altair as alt + +import folium +from folium.plugins import HeatMap + +import seaborn as sns + import plotly.express as px from shiny import reactive from shiny.express import render, input, ui -from shinywidgets import render_plotly +from shinywidgets import render_plotly, render_altair, render_widget + +ui.page_opts(title="Sales Dashboard - Video 4 of 5", fillable=False) + +def apply_common_styles(fig): + # Set the background to transparent + fig.update_layout( + { + "plot_bgcolor": "rgba(0,0,0,0)", # Transparent plot background + "paper_bgcolor": "rgba(0,0,0,0)", # Transparent figure background + "font": {"family": "Arial, sans-serif", "size": 12, "color": "darkblue"}, + } + ) + return fig + +import ipyleaflet as ipyl + +city_centers = { + "London": (51.5074, 0.1278), + "Paris": (48.8566, 2.3522), + "New York": (40.7128, -74.0060) +} + +ui.input_select("center", "Center", choices=list(city_centers.keys())) + +@render_widget +def map(): + print("TYPE TYPE", type(ipyl.Map(zoom=4))) + return ipyl.Map(zoom=4) -ui.page_opts(title="Sales Dashboard - Video 3 of 5", fillable=False) @reactive.calc def dat(): @@ -15,17 +52,18 @@ def dat(): df = pd.read_csv(infile) df["order_date"] = pd.to_datetime(df["order_date"]) df["month"] = df["order_date"].dt.month_name() + df["hour"] = df["order_date"].dt.hour df['value'] = df['quantity_ordered'] * df['price_each'] return df -with ui.card(): - ui.card_header("Sales by City 2023") - with ui.layout_sidebar(): - with ui.sidebar(bg="#f8f8f8", open='open'): - ui.input_selectize( - "city", - "Select a City:", +with ui.card(full_screen=True): + ui.card_header("Sales by City in 2023") + with ui.layout_sidebar(): + with ui.sidebar(open="open", bg="#f8f8f8"): + ui.input_select( + "select", + "Select an option below:", [ "Dallas (TX)", "Boston (MA)", @@ -38,76 +76,201 @@ def dat(): "Austin (TX)", "Portland (ME)", ], - multiple=False, - selected='Boston (MA)' ) - @render_plotly + @render_altair def sales_over_time(): df = dat() - print(list(df.city.unique())) - sales = df.groupby(["city", "month"])["quantity_ordered"].sum().reset_index() - sales_by_city = sales[sales["city"] == input.city()] - month_orders = calendar.month_name[1:] - fig = px.bar( - sales_by_city, - x="month", - y="quantity_ordered", - title=f"Sales over Time -- {input.city()}", - category_orders={"month": month_orders}, + # Define the order of the months to ensure they appear chronologically + month_order = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ] + + # Creating a month order for sorting in Altair + df["month"] = pd.Categorical( + df["month"], categories=month_order, ordered=True + ) + counts = ( + df.groupby(["month", "city"], observed=True)["quantity_ordered"] + .sum() + .reset_index() + ) + city_counts = counts[counts["city"] == input.select()] + # Create the Altair chart + chart = ( + alt.Chart(city_counts) + .mark_bar() + .encode( + x="month", + y="quantity_ordered", + tooltip=["city", "month", "quantity_ordered"], + ) + .properties(title=f"Sales Over Time in {input.select()}") + .configure_axis(labelAngle=0) # Adjust label angle if needed ) - return fig + return chart -with ui.layout_column_wrap(width=1/2): - with ui.navset_card_underline(id="tab", footer=ui.input_numeric("n", "Number of Items", 5, min=0, max=20)): + +with ui.layout_columns(widths=1 / 2): + with ui.navset_card_underline(): with ui.nav_panel("Top Sellers"): + ui.input_numeric( + "items", "Number of Items", 5, min=1, max=10, step=None, width=0.5 + ) @render_plotly - def plot_top_sellers(): + def top_sellers(): df = dat() - top_sales = df.groupby('product')['quantity_ordered'].sum().nlargest(input.n()).reset_index() - fig = px.bar(top_sales, x='product', y='quantity_ordered') + top_5_products = ( + df.groupby("product")["quantity_ordered"] + .sum() + .nlargest(input.items()) + .reset_index() + ) + + fig = px.bar(top_5_products, x="product", y="quantity_ordered") + + fig.update_traces( + marker_color=top_5_products["quantity_ordered"], + marker_colorscale="Blues", + ) + apply_common_styles(fig) return fig with ui.nav_panel("Top Sellers Value ($)"): @render_plotly - def plot_top_sellers_value(): + def top_sellers_value(): df = dat() - top_sales = df.groupby('product')['value'].sum().nlargest(input.n()).reset_index() - fig = px.bar(top_sales, x='product', y='value') + top_5_products = ( + df.groupby("product")["value"] + .sum() + .nlargest(input.items()) + .reset_index() + ) + + fig = px.bar(top_5_products, x="product", y="value") + + fig.update_traces( + marker_color=top_5_products["value"], marker_colorscale="Blues" + ) + apply_common_styles(fig) return fig with ui.nav_panel("Lowest Sellers"): @render_plotly - def plot_lowest_sellers(): + def lowest_sellers(): df = dat() - top_sales = df.groupby('product')['quantity_ordered'].sum().nsmallest(input.n()).reset_index() - fig = px.bar(top_sales, x='product', y='quantity_ordered') + top_5_products = ( + df.groupby("product")["quantity_ordered"] + .sum() + .nsmallest(input.items()) + .reset_index() + .sort_values("quantity_ordered", ascending=False) + ) + + fig = px.bar(top_5_products, x="product", y="quantity_ordered") + + fig.update_traces( + marker_color=top_5_products["quantity_ordered"], + marker_colorscale="Reds", + ) + apply_common_styles(fig) return fig with ui.nav_panel("Lowest Sellers Value ($)"): + @render_plotly - def plot_lowest_sellers_value(): + def lowest_sellers_value(): df = dat() - top_sales = df.groupby('product')['value'].sum().nsmallest(input.n()).reset_index() - fig = px.bar(top_sales, x='product', y='value') - return fig + top_5_products = ( + df.groupby("product")["value"] + .sum() + .nsmallest(input.items()) + .reset_index() + .sort_values("value", ascending=False) + ) + + fig = px.bar(top_5_products, x="product", y="value") + fig.update_traces( + marker_color=top_5_products["value"], marker_colorscale="Reds" + ) + apply_common_styles(fig) + return fig with ui.card(): - ui.card_header("Sales by Time of Day Heatmap") - "Heatmap" -with ui.card(): - ui.card_header("Sales by Location Map") - "Content Here" - + @render.plot + def time_heatmap(): + # Step 1: Extract the hour from the order_date + df = dat() + + # Step 2: Aggregate data by hour + hourly_counts = df["hour"].value_counts().sort_index() + + # Step 3: Prepare data for heatmap (optional: create a dummy dimension if needed) + # For simplicity, let's assume you want to view by hours (24 hrs) and just map counts directly. + heatmap_data = np.zeros((24, 1)) # 24 hours, 1 dummy column + for hour in hourly_counts.index: + heatmap_data[hour, 0] = hourly_counts[hour] + + # Convert heatmap data to integer if needed + heatmap_data = heatmap_data.astype(int) + + # Step 4: Plot with Seaborn + plt.figure(figsize=(10, 8)) + sns.heatmap( + heatmap_data, + annot=True, + fmt="d", + cmap="coolwarm", + cbar=False, + xticklabels=[], + yticklabels=[f"{i}:00" for i in range(24)], + ) + plt.title("Number of Orders by Hour of Day") + plt.ylabel("Hour of Day") + plt.xlabel("Order Count") + + +with ui.navset_card_underline(): + with ui.nav_panel("Sales Locations"): + + @render.ui + def heatmap(): + # Create a map centered roughly in the middle of the US + df = dat() + map = folium.Map(location=[37.0902, -95.7129], zoom_start=4) + + # Prepare the data for the HeatMap; each entry in heatmap_data is (lat, lon, weight) + heatmap_data = [ + (lat, long, qty) + for lat, long, qty in zip(df["lat"], df["long"], df["quantity_ordered"]) + ] + # Add HeatMap to the map + HeatMap(heatmap_data).add_to(map) + + print("TYPE OF FOLIUM", type(map)) + print(map._repr_html_()) + return map + -with ui.card(): - ui.card_header("Sample Sales Data") +with ui.navset_card_underline(): + with ui.nav_panel("Dataframe Sample"): - @render.data_frame - def sample_sales_data(): - return dat().head(100) + @render.data_frame + def frame(): + return dat().head(1000) From 9375b961feeb50b73d173689ee3ae5846c2e3051 Mon Sep 17 00:00:00 2001 From: Keith Galli Date: Tue, 25 Jun 2024 18:01:56 -0400 Subject: [PATCH 7/7] Clean up some code --- sales/app.py | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/sales/app.py b/sales/app.py index 319f3a7..e2df280 100644 --- a/sales/app.py +++ b/sales/app.py @@ -30,22 +30,6 @@ def apply_common_styles(fig): ) return fig -import ipyleaflet as ipyl - -city_centers = { - "London": (51.5074, 0.1278), - "Paris": (48.8566, 2.3522), - "New York": (40.7128, -74.0060) -} - -ui.input_select("center", "Center", choices=list(city_centers.keys())) - -@render_widget -def map(): - print("TYPE TYPE", type(ipyl.Map(zoom=4))) - return ipyl.Map(zoom=4) - - @reactive.calc def dat(): infile = Path(__file__).parent / "data/sales.csv" @@ -81,21 +65,8 @@ def dat(): @render_altair def sales_over_time(): df = dat() - # Define the order of the months to ensure they appear chronologically - month_order = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ] + + month_order = calendar.month_name[1:] # Creating a month order for sorting in Altair df["month"] = pd.Categorical( @@ -263,8 +234,6 @@ def heatmap(): # Add HeatMap to the map HeatMap(heatmap_data).add_to(map) - print("TYPE OF FOLIUM", type(map)) - print(map._repr_html_()) return map