GeoCosmos2.1 / utils /filter.py
emidiosouza's picture
fix: ajustes de filtros de data e de limite de itens categóricos
960f8ac
import pandas as pd
import streamlit as st
from pandas.api.types import (
is_categorical_dtype,
is_datetime64_any_dtype,
is_numeric_dtype,
is_object_dtype,
)
def make_hashable(x):
"""
Converte recursivamente listas (e, se necessário, dicionários) em tipos hashable.
"""
if isinstance(x, list):
return tuple(make_hashable(e) for e in x)
# Se precisar, trate dicionários também:
if isinstance(x, dict):
return tuple(sorted((k, make_hashable(v)) for k, v in x.items()))
return x
def flatten_unique_values(series: pd.Series) -> list:
"""
Achata os valores únicos de uma série.
Se um valor for uma tupla, extrai cada item individualmente.
"""
unique_values_set = set()
for val in series.dropna().unique():
# Se o valor for uma tupla, adicione cada item separadamente
if isinstance(val, tuple):
unique_values_set.update(val)
else:
unique_values_set.add(val)
return list(unique_values_set)
def filter_dataframe(df: pd.DataFrame, ignore_unique_limit = ["Alvo ou depósito"]) -> pd.DataFrame:
df = df.copy()
# Primeiro, converta todos os valores da DataFrame para hashable
for col in df.columns:
df[col] = df[col].apply(make_hashable)
# Tenta converter strings para datetime e remover fuso horário
for col in df.columns:
if is_object_dtype(df[col]):
try:
df[col] = pd.to_datetime(df[col], format="%d-%m-%Y")
except Exception:
pass
if is_datetime64_any_dtype(df[col]):
df[col] = df[col].dt.tz_localize(None)
modification_container = st.container()
with modification_container:
to_filter_columns = st.multiselect(
"Filtrar por valor",
[column for column in df.columns if column != 'id'],
placeholder="Selecione um ou mais itens para filtrar"
)
for column in to_filter_columns:
left, right = st.columns((1, 20))
left.write("↳")
# Para colunas categóricas ou com poucos valores únicos, use multiselect
if is_categorical_dtype(df[column]) or df[column].nunique() < 100 or column in ignore_unique_limit:
raw_unique_values = df[column].dropna().unique()
# Verifica se há valores do tipo tupla (decorrentes de listas convertidas)
if any(isinstance(val, tuple) for val in raw_unique_values):
unique_values = flatten_unique_values(df[column])
is_flattened = True
else:
unique_values = list(raw_unique_values)
is_flattened = False
user_cat_input = right.multiselect(
f"Valores para {column}",
unique_values,
default=[], # Sem valores pré-selecionados
placeholder="Escolha uma opção"
)
if user_cat_input: # Filtrar apenas se houver seleção
if is_flattened:
df = df[df[column].apply(
lambda x: any(item in x for item in user_cat_input) if isinstance(x, tuple) else x in user_cat_input
)]
else:
df = df[df[column].isin(user_cat_input)]
elif is_numeric_dtype(df[column]):
_min = float(df[column].min())
_max = float(df[column].max())
step = (_max - _min) / 100
user_num_input = right.slider(
f"Valores para {column}",
min_value=_min,
max_value=_max,
value=(_min, _max),
step=step,
)
df = df[df[column].between(*user_num_input)]
elif is_datetime64_any_dtype(df[column]):
user_date_input = right.date_input(
f"Valores para {column}",
value=(
df[column].min(),
df[column].max(),
),
format="YYYY-MM-DD",
)
if isinstance(user_date_input, tuple) and len(user_date_input) == 2:
start_date, end_date = map(pd.to_datetime, user_date_input)
df = df[df[column].between(start_date, end_date)]
else:
# Para colunas de texto
raw_unique_values = df[column].dropna().unique()
if any(isinstance(val, tuple) for val in raw_unique_values):
unique_values = flatten_unique_values(df[column])
is_flattened = True
else:
unique_values = list(raw_unique_values)
is_flattened = False
if len(unique_values) < 100:
user_text_input = right.multiselect(
f"Valores para {column}",
unique_values,
default=[],
placeholder="Escolha uma opção",
)
if user_text_input:
if is_flattened:
df = df[df[column].apply(
lambda x: any(item in x for item in user_text_input) if isinstance(x, tuple) else x in user_text_input
)]
else:
df = df[df[column].isin(user_text_input)]
else:
user_text_input = right.text_input(
f"Substring ou regex em {column}",
help="""
**Pesquise palavras ou padrões usando regex:**
- **Múltiplos termos:** `maçã|banana` (busca "maçã" ou "banana").
- **Início da palavra:** `^carro` (encontra "carro", "carroça", etc.).
- **Fim da palavra:** `casa$` (encontra "minha casa", "tua casa", etc.).
- **Números:** `\d+` (encontra qualquer número, como "123", "2024").
- **Número específico:** `123` (encontra exatamente o número "123").
- **Palavras e números:** `carro|123` (encontra "carro" ou "123").
Deixe vazio para não filtrar.
"""
)
if user_text_input:
df = df[df[column].astype(str).str.contains(user_text_input, na=False)]
return df