jnaiman's picture
reorg for multipage
13a4cc8
#######################################################
# 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
""")