Spaces:
Sleeping
Sleeping
| ####################### | |
| # Import libraries | |
| import streamlit as st | |
| import pandas as pd | |
| import altair as alt | |
| import plotly.express as px | |
| ####################### | |
| # Page configuration | |
| st.set_page_config( | |
| page_title="US Population Dashboard", | |
| page_icon="π", | |
| layout="wide", | |
| initial_sidebar_state="expanded") | |
| alt.themes.enable("dark") | |
| ####################### | |
| # CSS styling | |
| st.markdown(""" | |
| <style> | |
| [data-testid="block-container"] { | |
| padding-left: 2rem; | |
| padding-right: 2rem; | |
| padding-top: 1rem; | |
| padding-bottom: 0rem; | |
| margin-bottom: -7rem; | |
| } | |
| [data-testid="stVerticalBlock"] { | |
| padding-left: 0rem; | |
| padding-right: 0rem; | |
| } | |
| [data-testid="stMetric"] { | |
| background-color: #393939; | |
| text-align: center; | |
| padding: 15px 0; | |
| } | |
| [data-testid="stMetricLabel"] { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| [data-testid="stMetricDeltaIcon-Up"] { | |
| position: relative; | |
| left: 38%; | |
| -webkit-transform: translateX(-50%); | |
| -ms-transform: translateX(-50%); | |
| transform: translateX(-50%); | |
| } | |
| [data-testid="stMetricDeltaIcon-Down"] { | |
| position: relative; | |
| left: 38%; | |
| -webkit-transform: translateX(-50%); | |
| -ms-transform: translateX(-50%); | |
| transform: translateX(-50%); | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| ####################### | |
| # Load data | |
| df_reshaped = pd.read_csv('us-population-2010-2019-reshaped.csv') | |
| ####################### | |
| # Sidebar | |
| with st.sidebar: | |
| st.title('π US Population Dashboard') | |
| year_list = list(df_reshaped.year.unique())[::-1] | |
| selected_year = st.selectbox('Select a year', year_list) | |
| df_selected_year = df_reshaped[df_reshaped.year == selected_year] | |
| df_selected_year_sorted = df_selected_year.sort_values(by="population", ascending=False) | |
| color_theme_list = ['blues', 'cividis', 'greens', 'inferno', 'magma', 'plasma', 'reds', 'rainbow', 'turbo', 'viridis'] | |
| selected_color_theme = st.selectbox('Select a color theme', color_theme_list) | |
| ####################### | |
| # Plots | |
| # Heatmap | |
| def make_heatmap(input_df, input_y, input_x, input_color, input_color_theme): | |
| heatmap = alt.Chart(input_df).mark_rect().encode( | |
| y=alt.Y(f'{input_y}:O', axis=alt.Axis(title="Year", titleFontSize=18, titlePadding=15, titleFontWeight=900, labelAngle=0)), | |
| x=alt.X(f'{input_x}:O', axis=alt.Axis(title="", titleFontSize=18, titlePadding=15, titleFontWeight=900)), | |
| color=alt.Color(f'max({input_color}):Q', | |
| legend=None, | |
| scale=alt.Scale(scheme=input_color_theme)), | |
| stroke=alt.value('black'), | |
| strokeWidth=alt.value(0.25), | |
| ).properties(width=900 | |
| ).configure_axis( | |
| labelFontSize=12, | |
| titleFontSize=12 | |
| ) | |
| # height=300 | |
| return heatmap | |
| # Choropleth map | |
| def make_choropleth(input_df, input_id, input_column, input_color_theme): | |
| choropleth = px.choropleth(input_df, locations=input_id, color=input_column, locationmode="USA-states", | |
| color_continuous_scale=input_color_theme, | |
| range_color=(0, max(df_selected_year.population)), | |
| scope="usa", | |
| labels={'population':'Population'} | |
| ) | |
| choropleth.update_layout( | |
| template='plotly_dark', | |
| plot_bgcolor='rgba(0, 0, 0, 0)', | |
| paper_bgcolor='rgba(0, 0, 0, 0)', | |
| margin=dict(l=0, r=0, t=0, b=0), | |
| height=350 | |
| ) | |
| return choropleth | |
| # Donut chart | |
| def make_donut(input_response, input_text, input_color): | |
| if input_color == 'blue': | |
| chart_color = ['#29b5e8', '#155F7A'] | |
| if input_color == 'green': | |
| chart_color = ['#27AE60', '#12783D'] | |
| if input_color == 'orange': | |
| chart_color = ['#F39C12', '#875A12'] | |
| if input_color == 'red': | |
| chart_color = ['#E74C3C', '#781F16'] | |
| source = pd.DataFrame({ | |
| "Topic": ['', input_text], | |
| "% value": [100-input_response, input_response] | |
| }) | |
| source_bg = pd.DataFrame({ | |
| "Topic": ['', input_text], | |
| "% value": [100, 0] | |
| }) | |
| plot = alt.Chart(source).mark_arc(innerRadius=45, cornerRadius=25).encode( | |
| theta="% value", | |
| color= alt.Color("Topic:N", | |
| scale=alt.Scale( | |
| #domain=['A', 'B'], | |
| domain=[input_text, ''], | |
| # range=['#29b5e8', '#155F7A']), # 31333F | |
| range=chart_color), | |
| legend=None), | |
| ).properties(width=130, height=130) | |
| text = plot.mark_text(align='center', color="#29b5e8", font="Lato", fontSize=32, fontWeight=700, fontStyle="italic").encode(text=alt.value(f'{input_response} %')) | |
| plot_bg = alt.Chart(source_bg).mark_arc(innerRadius=45, cornerRadius=20).encode( | |
| theta="% value", | |
| color= alt.Color("Topic:N", | |
| scale=alt.Scale( | |
| # domain=['A', 'B'], | |
| domain=[input_text, ''], | |
| range=chart_color), # 31333F | |
| legend=None), | |
| ).properties(width=130, height=130) | |
| return plot_bg + plot + text | |
| # Convert population to text | |
| def format_number(num): | |
| if num > 1000000: | |
| if not num % 1000000: | |
| return f'{num // 1000000} M' | |
| return f'{round(num / 1000000, 1)} M' | |
| return f'{num // 1000} K' | |
| # Calculation year-over-year population migrations | |
| def calculate_population_difference(input_df, input_year): | |
| selected_year_data = input_df[input_df['year'] == input_year].reset_index() | |
| previous_year_data = input_df[input_df['year'] == input_year - 1].reset_index() | |
| selected_year_data['population_difference'] = selected_year_data.population.sub(previous_year_data.population, fill_value=0) | |
| return pd.concat([selected_year_data.states, selected_year_data.id, selected_year_data.population, selected_year_data.population_difference], axis=1).sort_values(by="population_difference", ascending=False) | |
| ####################### | |
| # Dashboard Main Panel | |
| col = st.columns((1.5, 4.5, 2), gap='medium') | |
| with col[0]: | |
| st.markdown('#### Gains/Losses') | |
| df_population_difference_sorted = calculate_population_difference(df_reshaped, selected_year) | |
| if selected_year > 2010: | |
| first_state_name = df_population_difference_sorted.states.iloc[0] | |
| first_state_population = format_number(df_population_difference_sorted.population.iloc[0]) | |
| first_state_delta = format_number(df_population_difference_sorted.population_difference.iloc[0]) | |
| else: | |
| first_state_name = '-' | |
| first_state_population = '-' | |
| first_state_delta = '' | |
| st.metric(label=first_state_name, value=first_state_population, delta=first_state_delta) | |
| if selected_year > 2010: | |
| last_state_name = df_population_difference_sorted.states.iloc[-1] | |
| last_state_population = format_number(df_population_difference_sorted.population.iloc[-1]) | |
| last_state_delta = format_number(df_population_difference_sorted.population_difference.iloc[-1]) | |
| else: | |
| last_state_name = '-' | |
| last_state_population = '-' | |
| last_state_delta = '' | |
| st.metric(label=last_state_name, value=last_state_population, delta=last_state_delta) | |
| st.markdown('#### States Migration') | |
| if selected_year > 2010: | |
| # Filter states with population difference > 50000 | |
| # df_greater_50000 = df_population_difference_sorted[df_population_difference_sorted.population_difference_absolute > 50000] | |
| df_greater_50000 = df_population_difference_sorted[df_population_difference_sorted.population_difference > 50000] | |
| df_less_50000 = df_population_difference_sorted[df_population_difference_sorted.population_difference < -50000] | |
| # % of States with population difference > 50000 | |
| states_migration_greater = round((len(df_greater_50000)/df_population_difference_sorted.states.nunique())*100) | |
| states_migration_less = round((len(df_less_50000)/df_population_difference_sorted.states.nunique())*100) | |
| donut_chart_greater = make_donut(states_migration_greater, 'Inbound Migration', 'green') | |
| donut_chart_less = make_donut(states_migration_less, 'Outbound Migration', 'red') | |
| else: | |
| states_migration_greater = 0 | |
| states_migration_less = 0 | |
| donut_chart_greater = make_donut(states_migration_greater, 'Inbound Migration', 'green') | |
| donut_chart_less = make_donut(states_migration_less, 'Outbound Migration', 'red') | |
| migrations_col = st.columns((0.2, 1, 0.2)) | |
| with migrations_col[1]: | |
| st.write('Inbound') | |
| st.altair_chart(donut_chart_greater) | |
| st.write('Outbound') | |
| st.altair_chart(donut_chart_less) | |
| with col[1]: | |
| st.markdown('#### Total Population') | |
| choropleth = make_choropleth(df_selected_year, 'states_code', 'population', selected_color_theme) | |
| st.plotly_chart(choropleth, use_container_width=True) | |
| heatmap = make_heatmap(df_reshaped, 'year', 'states', 'population', selected_color_theme) | |
| st.altair_chart(heatmap, use_container_width=True) | |
| with col[2]: | |
| st.markdown('#### Top States') | |
| st.dataframe(df_selected_year_sorted, | |
| column_order=("states", "population"), | |
| hide_index=True, | |
| width=None, | |
| column_config={ | |
| "states": st.column_config.TextColumn( | |
| "States", | |
| ), | |
| "population": st.column_config.ProgressColumn( | |
| "Population", | |
| format="%f", | |
| min_value=0, | |
| max_value=max(df_selected_year_sorted.population), | |
| )} | |
| ) | |
| with st.expander('About', expanded=True): | |
| st.write(''' | |
| - Data: [U.S. Census Bureau](https://www.census.gov/data/datasets/time-series/demo/popest/2010s-state-total.html). | |
| - :orange[**Gains/Losses**]: states with high inbound/ outbound migration for selected year | |
| - :orange[**States Migration**]: percentage of states with annual inbound/ outbound migration > 50,000 | |
| ''') | |