####################################################### # 1. Getting setup -- using our HF template ####################################################### # We have a few options for how to proceed. I'll start by showing the process in # PL and then I'll move to my local installation of my template so that I can make # sure I am pushing code at various intervals so folks can check that out. # NOTE: during this process, you can click on "Always Rerun" for automatic updates. # See the class notes on this with some photos for reference! # **this has to be implemented!** ################################################################### # 2. Review of where we got to last time, in template app.py file ################################################################### # Let's start by copying things we did last time import streamlit as st import altair as alt # Let's recall a plot that we made with Altair in Jupyterlab: # Make sure we copy the URL as well! mobility_url = 'https://raw.githubusercontent.com/UIUC-iSchool-DataViz/is445_data/main/mobility.csv' st.title('This is my fancy app for HuggingFace!!') scatters = alt.Chart(mobility_url).mark_point().encode( x='Mobility:Q', # "Q for quantiative" #y='Population:Q', y=alt.Y('Population:Q', scale=alt.Scale(type='log')), color=alt.Color('Income:Q', scale=alt.Scale(scheme='sinebow'),bin=alt.Bin(maxbins=5)) ) st.header('More complex Dashboards') brush = alt.selection_interval(encodings=['x','y']) chart1 = alt.Chart(mobility_url).mark_rect().encode( alt.X("Student_teacher_ratio:Q", bin=alt.Bin(maxbins=10)), alt.Y("State:O"), alt.Color("count()") ).properties( height=400 ).add_params( brush ) chart2 = alt.Chart(mobility_url).mark_bar().encode( alt.X("Mobility:Q", bin=True,axis=alt.Axis(title='Mobility Score')), alt.Y('count()', axis=alt.Axis(title='Mobility Score Distribution')) ).transform_filter( brush ) chart = (chart1.properties(width=300) | chart2.properties(width=300)) tab1, tab2 = st.tabs(["Mobility interactive", "Scatter plot"]) with tab1: st.altair_chart(chart, theme=None, use_container_width=True) with tab2: st.altair_chart(scatters, theme=None, use_container_width=True) ################################################ # 3. Adding features, Pushing to HF ################################################ st.header('Requirements, README file, Pushing to HuggingFace') ### 3.1 Make a plot ### # Let's say we want to add in some matplotlib plots from some data we read # in with Pandas. import pandas as pd df = pd.read_csv(mobility_url) # There are a few ways to show the dataframe if we want our viewer to see the table: #df st.write(df) # Now, let's plot with matplotlib: import matplotlib.pyplot as plt fig, ax = plt.subplots() df['Seg_income'].plot(kind='hist', ax=ax) #plt.show() # but wait! this doesn't work! # We need to use the streamlit-specific way of showing matplotlib plots: https://docs.streamlit.io/develop/api-reference/charts/st.pyplot st.pyplot(fig) ### 3.2 Push these changes to HF -- requirements.txt ### # In order to push these changes to HF and have things actually show up we need to # add the packages we've added to our requirements.txt file. st.write('''The requirements.txt file contains all the packages needed for our app to run. These include (for our application):''') st.code(''' streamlit==1.39.0 altair numpy pandas matplotlib ''') # NOTE: for any package you want to use in your app.py file, you must include it in # the requirements.txt file! # Note #2: we specified a version of streamlit so we can use some specific widgets ### 3.3 Push these changes to HF -- README.md ### # While we're doing this, let's also take a look at the README.md file! st.header('Build in HF: README.md & requirements.txt files') st.code(''' --- title: Prep notebook -- My Streamlit App emoji: 🏢 colorFrom: blue colorTo: gray sdk: streamlit sdk_version: 1.39.0 app_file: app.py pinned: false license: mit --- ''') st.write("Note: the sdk version has to match what is in your requirements.txt (and with whatever widgets you want to be able to use).") # Some important things to note here: st.write('Some important items to note about these:') st.markdown(''' * the "emoji" is what will show up as an identifier on your homepage * the sdk *must* be streamlit * the "app_file" *must* link to the app file you are developing in ''') ################################################ # 4. TODO Quick intro to widgets ################################################ st.header('Widgets in Streamlit apps') ### 4.1 Widget basics: A few widget examples ### st.markdown(""" These will be very similar to how we used the `ipywidgets` package in Jupyter notebooks. """) st.markdown(""" We won't go over all of them, but you can check out the [list of widgets](https://docs.streamlit.io/develop/api-reference/widgets) linked. """) st.markdown("""Let's try a few!""") st.subheader('Feedback Widget') st.markdown(""" For example, we could try the [feedback widget](https://docs.streamlit.io/develop/api-reference/widgets/st.feedback). """ ) st.markdown(""" If we check out the docs for this widget, we see some familiar looking functions like `on_change` and the example they give looks very similar to an "observation" function that we built before using widgets: """) st.code( """ sentiment_mapping = ["one", "two", "three", "four", "five"] selected = st.feedback("stars") if selected is not None: st.markdown(f"You selected {sentiment_mapping[selected]} star(s).") """) # Let's give this a shot! st.write("How great are you feeling right now?") sentiment_mapping = ["one", "two", "three", "four", "five"] # map to these numers selected = st.feedback("stars") if selected is not None: # make sure we have a selection st.markdown(f"You selected {sentiment_mapping[selected]} star(s).") if selected < 1: st.markdown('Sorry to hear you are so sad :(') elif selected < 3: st.markdown('A solid medium is great!') else: st.markdown('Fantastic you are having such a great day!') st.subheader('Radio Buttons') st.markdown(""" Let's try out a [radio button](https://docs.streamlit.io/develop/api-reference/widgets/st.radio) example. """) favoriteViz = st.radio( "What's your visualization tool so far?", [":rainbow[Streamlit]", "vega-lite :sparkles:", "matplotlib :material/Home:"], captions=[ "New and cool!", "So sparkly.", "Familiar and comforting.", ], ) if favoriteViz == ":rainbow[Streamlit]": st.write("You selected Streamlit!") else: st.write("You didn't select Streamlit but that's ok, Data Viz still likes you :grin:") st.markdown(""" Note here that we made use of text highlight [colors](https://docs.streamlit.io/develop/api-reference/text/st.markdown) and [emoji's](https://streamlit-emoji-shortcodes-streamlit-app-gwckff.streamlit.app/) and [icons](https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded). """) ### 4.2 Connecting widgets with plots ### st.subheader('Connecting Widgets and Plots') st.markdown(""" There are actually [many types of charts](https://docs.streamlit.io/develop/api-reference/charts) supported in Streamlit (including the Streamlit-based "Simple Charts"), though we will just mainly be focusing on [Altair-related](https://docs.streamlit.io/develop/api-reference/charts/st.altair_chart) plots and their interactivity options since we'll also be making use of these when we move to building Jekyll webpages. """) st.markdown("""Since `matplotlib` is relatively familiar though, let's do a quick example using `pandas` and `matplotlib` to plot as Streamlit [does support `matplotlib`](https://docs.streamlit.io/develop/api-reference/charts/st.pyplot) as a plotting engine. """) st.markdown("""First, let's just make a simple plot with `pandas` and `matplotlib`. Let's re-do the matplotlib plots we did before with the mobility dataset with some interactivity. """) import pandas as pd import numpy as np # first, let's make a static plot: st.write("We'll start with a static plot:") # read in dataset df = pd.read_csv("https://raw.githubusercontent.com/UIUC-iSchool-DataViz/is445_data/main/mobility.csv") # make bins along student-teacher ratio bins = np.linspace(df['Student_teacher_ratio'].min(),df['Student_teacher_ratio'].max(), 10) # make pivot table table = df.pivot_table(index='State', columns=pd.cut(df['Student_teacher_ratio'], bins), aggfunc='size') # our plotting code before was: st.code(""" import matplotlib.pyplot as plt fig,ax = plt.subplots(figsize=(10,8)) ax.imshow(table.values, cmap='hot', interpolation='nearest') ax.set_yticks(range(len(table.index))) ax.set_yticklabels(table.index) plt.show() """) st.write("Let's translate it into something that will work with Streamlit:") import matplotlib.pyplot as plt fig,ax = plt.subplots() # this changed ax.imshow(table.values, cmap='hot', interpolation='nearest') ax.set_yticks(range(len(table.index))) ax.set_yticklabels(table.index) st.pyplot(fig) # this is different st.markdown("""But this is too big! The trick is that we can save this as a buffer: """) from io import BytesIO fig,ax = plt.subplots(figsize=(4,8)) # this changed ax.imshow(table.values, cmap='hot', interpolation='nearest') ax.set_yticks(range(len(table.index))) ax.set_yticklabels(table.index) buf = BytesIO() fig.tight_layout() fig.savefig(buf, format="png") st.image(buf, width = 500) # can mess around with width, figsize/etc st.write("Now, let's make this interactive.") st.markdown("""We'll first use the [multiselect](https://docs.streamlit.io/develop/api-reference/widgets/st.multiselect) tool in order to allow for multiple state selection. """) # vertical alignment so they end up side by side fig_col, controls_col = st.columns([2,1], vertical_alignment='center') # multi-select states_selected = controls_col.multiselect('Which states do you want to view?', table.index.values) if len(states_selected) > 0: df_subset = df[df['State'].isin(states_selected)] # changed # make pivot table -- changed table_sub = df_subset.pivot_table(index='State', columns=pd.cut(df_subset['Student_teacher_ratio'], bins), aggfunc='size') base_size = 4 # this resizing doesn't 100% work great #factor = len(table.index)*1.0/df['State'].nunique() #if factor == 0: factor = 1 # for non-selections #fig,ax = plt.subplots(figsize=(base_size,2*base_size*factor)) # this changed too for different size fig,ax = plt.subplots(figsize=(base_size,2*base_size)) # this changed too for different size # extent is (xmin, xmax, ymax (buttom), ymin (top)) extent = [bins.min(), bins.max(), 0, len(table_sub.index)] ax.imshow(table_sub.values, cmap='hot', interpolation='nearest', extent=extent) ax.set_yticks(range(len(table_sub.index))) ax.set_yticklabels(table_sub.index) #ax.set_xticklabels(bins) buf = BytesIO() fig.tight_layout() fig.savefig(buf, format="png") fig_col.image(buf, width = 400) # changed here to fit better else: fig,ax = plt.subplots(figsize=(4,8)) # this changed extent = [bins.min(), bins.max(), 0, len(table.index)] ax.imshow(table.values, cmap='hot', interpolation='nearest', extent=extent) ax.set_yticks(range(len(table.index))) ax.set_yticklabels(table.index) #ax.set_xticklabels(bins) buf = BytesIO() fig.tight_layout() fig.savefig(buf, format="png") fig_col.image(buf, width = 500) # can mess around with width, figsize/etc st.markdown(""" Now let's add more in by including a [range slider](https://docs.streamlit.io/develop/api-reference/widgets/st.slider) widget. """) # vertical alignment so they end up side by side fig_col2, controls_col2 = st.columns([2,1], vertical_alignment='center') # multi-select states_selected2 = controls_col2.multiselect('Which states do you want to view?', table.index.values, key='unik1155') # had to pass unique key to have double widgets with same value # range slider -- added student_teacher_ratio_range = controls_col2.slider("Range of student teacher ratio:", df['Student_teacher_ratio'].min(), df['Student_teacher_ratio'].max(), (0.25*df['Student_teacher_ratio'].mean(), 0.75*df['Student_teacher_ratio'].mean())) # note all the "2's" here, probably will just update the original one if len(states_selected2) > 0: # here we set a default value for the slider, so no need to have a tag min_range = student_teacher_ratio_range[0] # added max_range = student_teacher_ratio_range[1] # added df_subset2 = df[(df['State'].isin(states_selected2)) & (df['Student_teacher_ratio'] >= min_range) & (df['Student_teacher_ratio']<=max_range)] # changed # just 10 bins over the full range --> changed bins2 = 10 #np.linspace(df['Student_teacher_ratio'].min(),df['Student_teacher_ratio'].max(), 10) # make pivot table -- changed table_sub2 = df_subset2.pivot_table(index='State', columns=pd.cut(df_subset2['Student_teacher_ratio'], bins2), aggfunc='size') base_size = 4 fig2,ax2 = plt.subplots(figsize=(base_size,2*base_size)) # this changed too for different size extent2 = [df_subset2['Student_teacher_ratio'].min(), df_subset2['Student_teacher_ratio'].max(), 0, len(table_sub2.index)] ax2.imshow(table_sub2.values, cmap='hot', interpolation='nearest', extent=extent2) ax2.set_yticks(range(len(table_sub2.index))) ax2.set_yticklabels(table_sub2.index) #ax2.set_xticklabels() buf2 = BytesIO() fig2.tight_layout() fig2.savefig(buf2, format="png") fig_col2.image(buf2, width = 400) # changed here to fit better else: min_range = student_teacher_ratio_range[0] # added max_range = student_teacher_ratio_range[1] # added df_subset2 = df[(df['Student_teacher_ratio'] >= min_range) & (df['Student_teacher_ratio']<=max_range)] # changed # just 10 bins over the full range --> changed bins2 = 10 #np.linspace(df['Student_teacher_ratio'].min(),df['Student_teacher_ratio'].max(), 10) # make pivot table -- changed table_sub2 = df_subset2.pivot_table(index='State', columns=pd.cut(df_subset2['Student_teacher_ratio'], bins2), aggfunc='size') base_size = 4 fig2,ax2 = plt.subplots(figsize=(base_size,2*base_size)) # this changed too for different size extent2 = [df_subset2['Student_teacher_ratio'].min(), df_subset2['Student_teacher_ratio'].max(), 0, len(table_sub2.index)] ax2.imshow(table_sub2.values, cmap='hot', interpolation='nearest', extent=extent2) ax2.set_yticks(range(len(table_sub2.index))) ax2.set_yticklabels(table_sub2.index) #ax2.set_xticklabels() buf2 = BytesIO() fig2.tight_layout() fig2.savefig(buf2, format="png") fig_col2.image(buf2, width = 400) # changed here to fit better st.header('Push final page to HF') st.markdown("""When ready, do:""") st.code(""" git add -A git commit -m "final push of day 1" git push """)