File size: 6,761 Bytes
fe69607
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960f8ac
fe69607
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960f8ac
fe69607
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960f8ac
fe69607
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960f8ac
 
 
 
 
 
 
 
 
 
 
 
 
fe69607
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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