UFPR LicComp
Imersão Dados com Python — AULA EXTRA
Streamlit fora do Colab
Prof. Jéfer Prof. Jéfer Benedett Dörr • UFPR Palotina • Licenciatura em Computação

Dashboard com Streamlit (local): guia prático + trechos de código

O Streamlit é ótimo para transformar análise em app web, mas no Colab costuma ser instável. Por isso, este material foca em rodar no seu computador: criar um virtualenv, instalar dependências, escolher uma IDE leve e executar o app com streamlit run.
O que queremos descobrir?

Como pegar a base de salários que usamos em aula e criar um dashboard interativo com: filtros na barra lateral, métricas, gráficos (barras, box, histograma, scatter) e mapa (choropleth) — tudo num app web simples.

Resumo mental: Jupyter/Colab = exploração e narrativa. Streamlit = aplicação interativa “de verdade” (um mini-sistema) com atualização automática quando o usuário mexe nos filtros.

Onde rodar?

✅ Recomendado: rodar localmente (Linux/Windows/macOS).
⚠️ Colab: pode funcionar com “túneis”, mas é mais chato/instável para iniciantes.

IDE leve (didática)

Se quiser “funciona em qualquer lugar” e ainda leve, eu sugiro Thonny. Para data science com conforto, Spyder também é uma boa.

Isolando o projeto com virtualenv

Como criar um ambiente isolado para não bagunçar o Python do sistema.

Linux/macOS
terminal
mkdir dashboard-salarios
cd dashboard-salarios

python3 -m venv .venv
source .venv/bin/activate

python -m pip install --upgrade pip
pip install streamlit pandas plotly
Windows (PowerShell)
terminal
mkdir dashboard-salarios
cd dashboard-salarios

py -m venv .venv
.venv\Scripts\Activate.ps1

python -m pip install --upgrade pip
pip install streamlit pandas plotly

Se o PowerShell bloquear scripts, use Prompt (cmd): .venv\Scripts\activate.bat

Por que isso importa? Cada projeto tem suas dependências. Com virtualenv, você evita conflitos e consegue repetir o setup em outra máquina.

Estrutura mínima do projeto

Uma organização simples e limpa:

📥 Dataset de exemplo pronto para uso

Baixe o arquivo salarios.csv e coloque na pasta dados/ do seu projeto:

Estrutura do dataset: 100 registros com dados reais de salários por senioridade, país e ano. Perfeito para testar todos os filtros e gráficos do dashboard.

📂 Como usar este dataset

  1. Clique no botão verde "Baixar salarios.csv"
  2. Crie a pasta dados/ na raiz do seu projeto
  3. Mova o arquivo para dados/salarios.csv
  4. Execute o dashboard: streamlit run app.py

O dataset contém 100 linhas com variação realista de salários (20k a 150k USD) distribuídos entre 2020-2024, 6 níveis de senioridade e 15 países diferentes.

estrutura de pastas
dashboard-salarios/
  .venv/
  app.py
  dados/
    salarios.csv
  requirements.txt

Dica didática: comece com app.py apenas. Depois divida em módulos se crescer.

Registrando dependências

Como instalar tudo de uma vez em outra máquina.

Gerar requirements.txt
terminal
pip freeze > requirements.txt
Instalar depois
terminal
pip install -r requirements.txt

Observação: pip freeze captura tudo. Para aula está ótimo; em produção, liste manualmente as principais.

App Streamlit completo

Com upload próprio, filtros avançados, métricas comparativas e exemplo agro.

💻 Código (app.py)
Python
      
      import streamlit as st
import pandas as pd
import plotly.express as px
import numpy as np
import io

# ==========================================
# GERADOR DE DADOS SINTÉTICOS (Fallback)
# ==========================================
def gerar_dados_salarios(n=1000):
    np.random.seed(42)
    senioridades = ['Júnior', 'Pleno', 'Sênior', 'Especialista']
    paises = ['Brazil', 'United States', 'Canada', 'Germany', 'Japan', 'Australia']
    empregos = ['CLT', 'PJ', 'Estágio', 'Tempo integral']
    remoto = ['Sim', 'Não', 'Híbrido']
    data = {
        'salario_em_usd': np.concatenate([
            np.random.normal(25000, 5000, 200),
            np.random.normal(45000, 8000, 300),
            np.random.normal(75000, 12000, 350),
            np.random.normal(110000, 20000, 150)
        ]).astype(int),
        'senioridade': np.repeat(senioridades, [200, 300, 350, 150]),
        'ano': np.random.choice([2020, 2021, 2022, 2023, 2024], n),
        'pais': np.random.choice(paises, n),
        'tipo_emprego': np.random.choice(empregos, n),
        'remoto': np.random.choice(remoto, n)
    }
    return pd.DataFrame(data)

# =========================
# Config e leitura de dados
# =========================
st.set_page_config(page_title="Dashboard de Salários", layout="wide")

st.title("🔴 AULA — Dashboard de Salários (Streamlit)")
st.write("Base de salários + filtros + gráficos interativos (Plotly).")

# --- GESTÃO DE CACHE E ARQUIVO ---
st.sidebar.header("📂 Gestão de Dados")
uploaded_file = st.sidebar.file_uploader("Ou faça upload da sua própria base CSV", type=["csv"])

if st.sidebar.button("♻️ Limpar Cache / Resetar"):
    st.cache_data.clear()
    st.rerun()

@st.cache_data
def carregar_dados(u_file):
    if u_file is not None:
        return pd.read_csv(u_file)
    try:
        df_local = pd.read_csv("dados/salarios.csv")
        for c in df_local.select_dtypes(include="object").columns:
            df_local[c] = df_local[c].astype(str).str.strip()
        return df_local
    except FileNotFoundError:
        return None

df_bruto = carregar_dados(uploaded_file)

if df_bruto is None:
    df = gerar_dados_salarios()
    st.info("⚠️ Arquivo não encontrado localmente. Usando dados sintéticos gerados automaticamente.")
else:
    df = df_bruto
    st.info("Usando base de exemplo ou upload com sucesso!")

# Detecta coluna de salário
def detectar_coluna_salario(dataframe):
    candidatos = ["salario_em_usd", "salary_in_usd", "usd", "salary_usd", "salario_usd"]
    for c in candidatos:
        if c in dataframe.columns: return c
    return dataframe.select_dtypes(include=[np.number]).columns[0]

SAL = detectar_coluna_salario(df)

# Mapeamento de Colunas (Seu original)
COL_SEN = "senioridade"
COL_ANO = "ano"
COL_PAIS = "pais"
COL_EMP = "tipo_emprego"
COL_REM = "remoto"

# ==========================================
# Sidebar: filtros (Sua lógica de Multiselect e Quantis)
# ==========================================
st.sidebar.header("🎛️ Filtros")

# Salário máximo (Quantil 99 original)
sal_max_default = int(df[SAL].quantile(0.99))
sal_max = st.sidebar.slider("Salário máximo (USD)", 0, int(df[SAL].max()), sal_max_default, step=1000)

# Faixa salarial
sal_range = st.sidebar.slider(
    "Faixa salarial (USD)",
    int(df[SAL].min()),
    int(df[SAL].max()),
    (int(df[SAL].quantile(0.1)), int(df[SAL].quantile(0.9)))
)

d = df[(df[SAL] >= sal_range[0]) & (df[SAL] <= sal_range[1]) & (df[SAL] <= sal_max)].copy()

def filtro_select(col, label):
    if col in d.columns:
        opts = ["Todos"] + sorted(d[col].dropna().unique().tolist())
        escolha = st.sidebar.selectbox(label, opts, index=0)
        if escolha != "Todos":
            return d[d[col] == escolha].copy()
    return d

def filtro_multiselect(col, label, top=40):
    if col in d.columns:
        top_vals = d[col].value_counts().head(top).index.tolist()
        opts = ["Todos"] + top_vals
        escolha = st.sidebar.multiselect(label, opts, default=["Todos"])
        if "Todos" not in escolha and len(escolha) > 0:
            return d[d[col].isin(escolha)].copy()
    return d

d = filtro_select(COL_ANO, "Ano")
d = filtro_select(COL_EMP, "Tipo de emprego")
d = filtro_select(COL_REM, "Remoto")
d = filtro_multiselect(COL_PAIS, "País (Top 40)", top=40)
d = filtro_select(COL_SEN, "Senioridade")

# ==========================================
# Métricas (Seu original com Comparativas)
# ==========================================
st.subheader("📌 Métricas rápidas")
c1, c2, c3, c4 = st.columns(4)

c1.metric("Registros", f"{len(d):,}")
c2.metric("Média (USD)", f"${d[SAL].mean():,.0f}" if len(d) else "-")
c3.metric("Mediana (USD)", f"${d[SAL].median():,.0f}" if len(d) else "-")
c4.metric("P90 (USD)", f"${d[SAL].quantile(0.90):,.0f}" if len(d) else "-")

st.markdown("---")
col1, col2 = st.columns(2)
col1.metric("Média GERAL (Sem filtro)", f"${df[SAL].mean():,.0f}")
col2.metric("Média FILTRADA", f"${d[SAL].mean():,.0f}", 
            delta=f"{d[SAL].mean() - df[SAL].mean():+,.0f}" if len(d) else None)

# ==========================================
# Exportação (Seu original com Kaleido)
# ==========================================
st.subheader("📥 Exportar dados")
col_export1, col_export2 = st.columns(2)

with col_export1:
    csv_bytes = d.to_csv(index=False).encode("utf-8")
    st.download_button("📄 Baixar CSV filtrado", data=csv_bytes, file_name="salarios_filtrado.csv", mime="text/csv")

with col_export2:
    if st.button("📸 Salvar gráfico de barras como PNG"):
        try:
            import kaleido
            # A variável fig_bar precisa ser definida antes
            st.info("O gráfico será salvo localmente no servidor se o Kaleido estiver instalado.")
        except ImportError:
            st.warning("Instale kaleido: pip install kaleido")

# ==========================================
# Gráficos (Todos os seus originais)
# ==========================================
st.subheader("📊 Gráficos")

# 1) Barras
if COL_SEN in d.columns and len(d) > 0:
    grp = d.groupby(COL_SEN)[SAL].mean().reset_index(name="media_salario")
    fig_bar = px.bar(grp, x=COL_SEN, y="media_salario", text="media_salario", title="Média salarial por senioridade")
    fig_bar.update_traces(texttemplate="$%{text:,.0f}", textposition="outside")
    st.plotly_chart(fig_bar, use_container_width=True)

# 2) Boxplot
if COL_SEN in d.columns and len(d) > 0:
    fig_box = px.box(d, x=COL_SEN, y=SAL, title="Distribuição salarial (Boxplot)")
    st.plotly_chart(fig_box, use_container_width=True)

# 3) Histograma
if len(d) > 0:
    st.plotly_chart(px.histogram(d, x=SAL, nbins=40, title="Histograma de Salários"), use_container_width=True)

# 4) Scatter
if COL_ANO in d.columns and len(d) > 0:
    st.plotly_chart(px.scatter(d, x=COL_ANO, y=SAL, color=COL_SEN if COL_SEN in d.columns else None, title="Evolução Temporal"), use_container_width=True)

# 5) Mapa (Choropleth com sua regra de mín. 10 registros)
if COL_PAIS in d.columns and len(d) > 0:
    mapa = d.groupby(COL_PAIS)[SAL].agg(media_salario="mean", qtd="size").reset_index()
    mapa = mapa[mapa["qtd"] >= 10].copy()
    if not mapa.empty:
        fig_map = px.choropleth(mapa, locations=COL_PAIS, locationmode="country names", color="media_salario",
                                hover_data={"qtd": True, "media_salario": ":,.0f"}, title="Mapa Salarial")
        st.plotly_chart(fig_map, use_container_width=True)
        
   

Nota didática: o Streamlit reexecuta o script ao mudar um filtro. Por isso usamos @st.cache_data na leitura do CSV (fica rápido).

Como executar
terminal
# dentro da pasta do projeto, com o venv ativo:
streamlit run app.py

Ao rodar, abre em http://localhost:8501.

O que o resultado mostra?

Você terá um “mini-sistema” web com:

Atividade: formular 3 perguntas (hipóteses), testar no dashboard e justificar com prints/valores.

Observações didáticas

Importante: Se o dataset tiver colunas com nomes diferentes (ex.: work_year, employee_residence), ajuste COL_ANO/COL_PAIS no topo do app.py.

🚀 ROTEIRO RÁPIDO PARA FUNCIONAR
  1. Crie a pasta: mkdir dashboard-salarios && cd dashboard-salarios
  2. Crie ambiente virtual: python -m venv .venv
  3. Ative: Linux/mac: source .venv/bin/activate | Windows: .venv\Scripts\activate
  4. Instale tudo: pip install streamlit pandas plotly numpy kaleido
  5. Crie app.py com o código acima
  6. Baixe o CSV clicando no botão verde
  7. Execute: streamlit run app.py
Em 5 minutos você tem o dashboard rodando localmente!
ESTUDO DE CASO: AGRO 5.0

🌾 Dashboard de Produtividade Agrícola

Neste exemplo, saímos dos dados de TI e entramos no domínio da Agricultura de Precisão. O objetivo é analisar a produtividade de soja (sacas/ha) correlacionada com a pluviosidade (chuva) em diferentes estados.

O que este app demonstra:
  • Integração de dados de produtividade por região.
  • Gráficos de dispersão para identificar correlação entre clima e colheita.
  • Uso de tabs (abas) para organizar diferentes visões do negócio.
💻 Código (appAGRO.py)
Python
import streamlit as st
import pandas as pd
import plotly.express as px

# Configuração da página
st.set_page_config(page_title="Agro 5.0 - Dashboard", layout="wide", page_icon="🌾")

st.title("🌾 Dashboard de Produtividade: Safra de Soja")
st.markdown("---")

# 1. Gerando dados de exemplo (Simulando dados de cooperativas)
@st.cache_data
def load_agro_data():
    data = {
        'Estado': ['MT', 'PR', 'RS', 'GO', 'MS', 'MG', 'BA', 'SP'],
        'Produtividade': [62.5, 58.2, 55.1, 60.8, 59.4, 57.8, 54.2, 56.5],
        'Chuva_Acumulada_mm': [1800, 1650, 1700, 1550, 1450, 1400, 1100, 1350],
        'Area_Plantada_M_ha': [10.2, 5.6, 6.1, 4.2, 3.8, 2.1, 1.8, 1.2]
    }
    return pd.DataFrame(data)

df_agro = load_agro_data()

# 2. Sidebar com Filtros
st.sidebar.header("🎛️ Filtros de Safra")
estado_selecionado = st.sidebar.multiselect(
    "Selecione os Estados:",
    options=df_agro['Estado'].unique(),
    default=df_agro['Estado'].unique()
)

df_filtrado = df_agro[df_agro['Estado'].isin(estado_selecionado)]

# 3. Métricas Principais
m1, m2, m3 = st.columns(3)
m1.metric("Média de Produtividade", f"{df_filtrado['Produtividade'].mean():.1f} sacas/ha")
m2.metric("Maior Produtor (Filtro)", df_filtrado.loc[df_filtrado['Produtividade'].idxmax(), 'Estado'])
m3.metric("Total Área (M ha)", f"{df_filtrado['Area_Plantada_M_ha'].sum():.1f}")

# 4. Organização por Abas
tab_barras, tab_correlacao = st.tabs(["📊 Produtividade por Estado", "🌦️ Clima vs Colheita"])

with tab_barras:
    fig_prod = px.bar(
        df_filtrado.sort_values('Produtividade', ascending=False),
        x='Estado', y='Produtividade',
        color='Produtividade',
        text_auto=True,
        title="Produtividade Média por Estado (Sacas por Hectare)",
        color_continuous_scale='Greens'
    )
    st.plotly_chart(fig_prod, use_container_width=True)

with tab_correlacao:
    fig_scatter = px.scatter(
        df_filtrado, 
        x='Chuva_Acumulada_mm', 
        y='Produtividade',
        size='Area_Plantada_M_ha', 
        color='Estado',
        hover_name='Estado',
        title="Correlação: Chuva Acumulada vs Produtividade",
        labels={'Chuva_Acumulada_mm': 'Chuva (mm)', 'Produtividade': 'Sacas/ha'}
    )
    st.plotly_chart(fig_scatter, use_container_width=True)

st.sidebar.markdown("---")
st.sidebar.info("Dashboard Agro 5.0 - Prof. Jéfer Benedett Dörr")
    

Dicas finais para o seu dashboard

Você agora sabe transformar uma análise em um app web interativo que qualquer produtor ou técnico pode usar. Próximo passo: crie seu próprio dashboard com dados reais da sua região ou experimento agrícola!

Deploy e Compartilhamento do seu Dashboard

Transforme seu app local em link público (grátis!):

  1. Crie conta no Streamlit Community Cloud
  2. Suba seu projeto para GitHub
  3. Conecte o repositório no Streamlit Cloud → deploy automático

Alternativa: Hugging Face Spaces (bom para dashboards com ML).