jiboia 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jiboia-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Lucas M.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
jiboia-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: jiboia
3
+ Version: 0.1.0
4
+ Summary: jiboia is a python package for automatically normalizing DataFrames and optimizing types efficiently.
5
+ Author-email: Lucas Muffato <lucasmuffato.eng@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Lucas M.
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in
18
+ all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ THE SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/lmuffato/jiboia
29
+ Project-URL: Bug_Tracker, https://github.com/lmuffato/jiboia/issues
30
+ Requires-Python: >=3.9
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: pandas>=2.0
34
+ Dynamic: license-file
35
+
36
+ # Jiboia
37
+
38
+ **Jiboia** is a Python package for **automatically normalizing and optimizing DataFrames** efficiently.
39
+
40
+ Key features:
41
+ - **String normalization**:
42
+ - Removes extra spaces.
43
+ - Strips leading and trailing spaces.
44
+ - Detects data pollution (e.g., columns that should be numeric but contain strings).
45
+ - **Type conversion**:
46
+ - Numeric strings and floats ending in `.0` → integers (`int8`, `int16`, `int32`, …).
47
+ - Converts floats and integers to the most memory-efficient type.
48
+ - Converts strings in various date formats to `datetime` (`yyyy?mm?dd`, `dd?mm?yyyy`, `yyyymmd`, `dd?mm?yy`).
49
+ - Converts time strings (`hhmm UTC`, `hh:mm:ss`, `hh:mm:ss.s`) to `timedelta`.
50
+ - **Null standardization** → converts different null representations to `pd.NA`.
51
+ - **Automatic CSV detection**:
52
+ - Detects delimiter.
53
+ - Detects encoding.
54
+ - **Memory optimization**:
55
+ - Provides memory usage information for DataFrames.
56
+ - Converts columns to the most compact types possible.
57
+
58
+ ---
59
+
60
+ ## Example Usage
61
+
62
+ ```python
63
+ import jiboia as jb
64
+
65
+ df = pd.read_csv("data.csv")
66
+ df = jb.normalize_df(df)
67
+
68
+ ```
jiboia-0.1.0/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Jiboia
2
+
3
+ **Jiboia** is a Python package for **automatically normalizing and optimizing DataFrames** efficiently.
4
+
5
+ Key features:
6
+ - **String normalization**:
7
+ - Removes extra spaces.
8
+ - Strips leading and trailing spaces.
9
+ - Detects data pollution (e.g., columns that should be numeric but contain strings).
10
+ - **Type conversion**:
11
+ - Numeric strings and floats ending in `.0` → integers (`int8`, `int16`, `int32`, …).
12
+ - Converts floats and integers to the most memory-efficient type.
13
+ - Converts strings in various date formats to `datetime` (`yyyy?mm?dd`, `dd?mm?yyyy`, `yyyymmd`, `dd?mm?yy`).
14
+ - Converts time strings (`hhmm UTC`, `hh:mm:ss`, `hh:mm:ss.s`) to `timedelta`.
15
+ - **Null standardization** → converts different null representations to `pd.NA`.
16
+ - **Automatic CSV detection**:
17
+ - Detects delimiter.
18
+ - Detects encoding.
19
+ - **Memory optimization**:
20
+ - Provides memory usage information for DataFrames.
21
+ - Converts columns to the most compact types possible.
22
+
23
+ ---
24
+
25
+ ## Example Usage
26
+
27
+ ```python
28
+ import jiboia as jb
29
+
30
+ df = pd.read_csv("data.csv")
31
+ df = jb.normalize_df(df)
32
+
33
+ ```
@@ -0,0 +1,87 @@
1
+ from .analysis_utils import AnalysisUtils
2
+ from .normalize_date_utils import NormalizeDateUtils
3
+ from .normalize_null_utils import NormalizeNullUtils
4
+ from .normalize_int_utils import NormalizeIntUtils
5
+ from .normalize_float_utils import NormalizeFloatUtils
6
+ from .normalize_string_utils import NormalizeStringUtils
7
+ from .normalize_df_utils import NormalizeDfUtils
8
+ from .file_analysis_utils import FileAnalysisUtils
9
+ from .regex_patterns import (
10
+ RegexPatternsCombine,
11
+ RAW_INVALID_VALUES,
12
+ DATE_REGEX_PATTERNS,
13
+ TIME_REGEX_PATTERNS,
14
+ DATE_TIME_REGEX_PATTERNS,
15
+ RAW_INVALID_VALUES
16
+ )
17
+
18
+
19
+ class Jiboia:
20
+
21
+ # DATAFRAME
22
+ normalize_df = staticmethod(NormalizeDfUtils.normalize_df)
23
+
24
+ # TYPES:
25
+ infer_column_types = staticmethod(AnalysisUtils.infer_column_types)
26
+
27
+ # STRING
28
+ normalize_string_by_columns = staticmethod(NormalizeStringUtils.normalize_string_by_columns)
29
+ # normalize_category = staticmethod(NormalizeStringUtils.normalize_category)
30
+ # normalize_unique_values = staticmethod(NormalizeStringUtils.normalize_unique_values)
31
+
32
+ # NULL
33
+ normalize_null_by_columns = staticmethod(NormalizeNullUtils.normalize_null_by_columns)
34
+
35
+ # BOOL
36
+ # normalize_bool = staticmethod(GPUNormalizeBooleanUtils.normalize_bool)
37
+
38
+ # NUMBER
39
+ normalize_int_by_columns = staticmethod(NormalizeIntUtils.normalize_int_by_columns)
40
+ normalize_float_by_columns = staticmethod(NormalizeFloatUtils.normalize_float_by_columns)
41
+
42
+
43
+ # DATE
44
+ normalize_str_date_to_date_standard_str_by_columns = staticmethod(NormalizeDateUtils.normalize_str_date_to_date_standard_str_by_columns)
45
+ # normalize_datetime = staticmethod(NormalizeDateUtils.normalize_datetime)
46
+ # is_date_column_by_samples = staticmethod(NormalizeDateUtils.is_date_column_by_samples)
47
+ # is_date = staticmethod(NormalizeDateUtils.is_date)
48
+ # to_date_iso = staticmethod(NormalizeDateUtils.to_date_iso)
49
+ # to_datetime = staticmethod(NormalizeDateUtils.to_datetime)
50
+
51
+
52
+ # TIME
53
+ normalize_str_time_to_hh_mm_ss_by_columns = staticmethod(NormalizeDateUtils.normalize_str_time_to_hh_mm_ss_by_columns)
54
+
55
+ # normalize_time = staticmethod(GPUNormalizeTimeUtils.normalize_time)
56
+ # is_time = staticmethod(GPUNormalizeTimeUtils.is_time)
57
+
58
+ # # TIMEDELTA
59
+ # normalize_timedelta = staticmethod(GPUNormalizeTimeUtils.normalize_timedelta)
60
+
61
+ # # DATETIME
62
+ # create_datetime_column_from_date_and_time = staticmethod(GPUNormalizeDateTimeUtils.create_datetime_column_from_date_and_time)
63
+ # create_day_week_column = staticmethod(GPUNormalizeDateTimeUtils.create_day_week_column)
64
+ # create_month_br_category_column = staticmethod(GPUNormalizeDateTimeUtils.create_month_br_category_column)
65
+ # create_day_week_br_category_column = staticmethod(GPUNormalizeDateTimeUtils.create_day_week_br_category_column)
66
+ # create_hour_category_column = staticmethod(GPUNormalizeDateTimeUtils.create_hour_category_column)
67
+
68
+ # ANALYSIS
69
+ # drop_columns = staticmethod(AnalysisUtils.drop_columns)
70
+ # cudf_size_info = staticmethod(AnalysisUtils.cudf_size_info)
71
+ # pandasdf_size_info = staticmethod(AnalysisUtils.pandasdf_size_info)
72
+ # get_gpu_memory_info = staticmethod(AnalysisUtils.get_gpu_memory_info)
73
+ # is_vram_use_limit = staticmethod(AnalysisUtils.is_vram_use_limit)
74
+ # get_index_samples = staticmethod(AnalysisUtils.get_index_samples)
75
+ # infer_by_sample = staticmethod(AnalysisUtils.infer_by_sample)
76
+
77
+ # # FILE
78
+ get_csv_sep_delimiter = staticmethod(FileAnalysisUtils.get_csv_sep_delimiter)
79
+ # get_csv_info = staticmethod(FileAnalysisUtils.get_csv_info)
80
+ # convert_csv_to_utf8 = staticmethod(FileAnalysisUtils.convert_csv_to_utf8)
81
+ # convert_all_csvs_to_utf8 = staticmethod(FileAnalysisUtils.convert_all_csvs_to_utf8)
82
+ # read_csv_files = staticmethod(FileAnalysisUtils.read_csv_files)
83
+
84
+ # instância única para ser usada como namespace
85
+ jiboia = Jiboia()
86
+
87
+ __all__ = ["jiboia", "Jiboia"]
@@ -0,0 +1,323 @@
1
+ from csv import Dialect
2
+ from datetime import datetime, time
3
+ from pandas.api.types import CategoricalDtype
4
+ from typing import Literal, Optional
5
+ from .regex_patterns import (
6
+ DATE_REGEX_PATTERNS,
7
+ TIME_REGEX_PATTERNS,
8
+ DATE_TIME_REGEX_PATTERNS,
9
+ )
10
+ import csv
11
+ import numpy as np
12
+ import pandas as pd
13
+ import re
14
+
15
+
16
+ class AnalysisUtils:
17
+ data_types = Literal['int', 'float', 'bool', 'date', 'time', 'datetime', 'str']
18
+
19
+ @staticmethod
20
+ def _number_cleaned(value: object) -> str:
21
+
22
+ value_str: str = str(value).strip()
23
+
24
+ if '.' in value_str and ',' in value_str:
25
+ # formato 1000,00 -> 1000.00
26
+ value_str: str = value_str.replace('.', '').replace(',', '.')
27
+
28
+ if ',' in value_str:
29
+ # formato 1.000,00 -> 1000.00
30
+ value_str: str = value_str.replace(',', '.')
31
+
32
+ return value_str
33
+
34
+
35
+ @staticmethod
36
+ def _is_zero(value: object) -> bool:
37
+ cleaned_number_str: str = AnalysisUtils._number_cleaned(value)
38
+
39
+ try:
40
+ float_value: float = float(cleaned_number_str)
41
+
42
+ if float_value == 0.0:
43
+ return True
44
+ else:
45
+ return False
46
+ except ValueError:
47
+ return False
48
+
49
+
50
+ @staticmethod
51
+ def _is_int(value: object) -> bool:
52
+ cleaned_number_str: str = AnalysisUtils._number_cleaned(value)
53
+
54
+ try:
55
+ float_value: float = float(cleaned_number_str)
56
+
57
+ if float_value.is_integer():
58
+ return True
59
+ else:
60
+ return False
61
+ except ValueError:
62
+ return False
63
+
64
+
65
+ @staticmethod
66
+ def _is_float(value: object) -> bool:
67
+ '''
68
+ Retorna True se o valor puder ser interpretado como float,
69
+ incluindo formatos com separadores de milhar e vírgula como separador decimal.
70
+ '''
71
+ cleaned_number_str: str = AnalysisUtils._number_cleaned(value)
72
+
73
+ try:
74
+ float_value: float = float(cleaned_number_str)
75
+
76
+ if not float_value.is_integer():
77
+ return True
78
+ else:
79
+ return False
80
+ except ValueError:
81
+ return False
82
+
83
+
84
+ @staticmethod
85
+ def _is_bool(value: object) -> bool:
86
+ """
87
+ Retorna True se o valor puder ser interpretado como booleano.
88
+ Considera 'true', 'false', '1', '0' (case-insensitive), além de bool nativo.
89
+ """
90
+ value_str = str(value).strip().lower()
91
+ return value_str in {'True', 'False', 'true', 'false', }
92
+
93
+
94
+ @staticmethod
95
+ def _is_date(value: object) -> bool:
96
+ value_str: str = str(value)
97
+
98
+ for pattern, _ in DATE_REGEX_PATTERNS:
99
+ if re.fullmatch(pattern, value_str):
100
+ return True
101
+
102
+ return False
103
+
104
+
105
+ def _is_time(value: object) -> bool:
106
+ value_str = str(value)
107
+
108
+ for pattern, _ in TIME_REGEX_PATTERNS:
109
+ if re.fullmatch(pattern, value_str):
110
+ return True
111
+
112
+ return False
113
+
114
+
115
+ @staticmethod
116
+ def _is_datetime(value: object) -> bool:
117
+ """
118
+ Verifica se o valor corresponde a um dos formatos datetime definidos em DATE_TIME_REGEX_PATTERNS.
119
+ """
120
+
121
+ value_str = str(value)
122
+
123
+ for pattern, date_format in DATE_TIME_REGEX_PATTERNS:
124
+ if re.fullmatch(pattern, value_str):
125
+ try:
126
+ datetime.strptime(value_str, date_format)
127
+ return True
128
+ except ValueError:
129
+ continue
130
+
131
+ return False
132
+
133
+
134
+ @staticmethod
135
+ def _detect_type(value: object) -> Optional[Literal['int', 'float', 'bool', 'date', 'time', 'str', 'zero']]:
136
+ if pd.isna(value):
137
+ return None
138
+
139
+ elif AnalysisUtils._is_zero(value):
140
+ return 'zero'
141
+
142
+ elif AnalysisUtils._is_int(value):
143
+ return 'int'
144
+
145
+ elif AnalysisUtils._is_float(value):
146
+ return 'float'
147
+
148
+ elif AnalysisUtils._is_time(value):
149
+ return 'time'
150
+
151
+ elif AnalysisUtils._is_date(value):
152
+ return 'date'
153
+
154
+ elif AnalysisUtils._is_bool(value):
155
+ return 'bool'
156
+
157
+ elif AnalysisUtils._is_datetime(value):
158
+ return 'datetime'
159
+
160
+ else:
161
+ return 'str'
162
+
163
+
164
+ @staticmethod
165
+ def get_dominant_type(
166
+ non_null_rows_sample: pd.Series,
167
+ without_zero: bool = False
168
+ ) -> Literal['int', 'float', 'bool', 'date', 'time', 'datetime', 'str', 'None', 'zero']:
169
+
170
+ type_frequency: pd.Series = (
171
+ non_null_rows_sample
172
+ .apply(AnalysisUtils._detect_type)
173
+ .value_counts()
174
+ )
175
+
176
+ if without_zero and 'zero' in type_frequency:
177
+ type_frequency: pd.Series = type_frequency.drop(labels='zero')
178
+
179
+ # Verifica a frequência de "float"
180
+ float_count = type_frequency.get('float', 0)
181
+ total_count = len(non_null_rows_sample)
182
+
183
+ # TODO: Rever isso para analisar toda a coluna em caso de float e int
184
+ # Se pelo menos 1% dos valores forem float, considera o tipo dominante como float
185
+ if float_count / total_count >= 0.1:
186
+ dominant_type = 'float'
187
+ else:
188
+ # Caso contrário, pega o tipo mais frequente
189
+ dominant_type = type_frequency.idxmax()
190
+
191
+ return dominant_type
192
+
193
+
194
+ @staticmethod
195
+ def sample_size(
196
+ current_df: pd.DataFrame,
197
+ rows_percent: int = 2
198
+ ) -> int:
199
+ if not (0 <= rows_percent <= 100):
200
+ raise ValueError('rows_percent deve estar entre 0 e 100')
201
+
202
+ row_count: int = len(current_df)
203
+ sample_size: int = max(1, int((rows_percent / 100) * row_count))
204
+
205
+ return sample_size
206
+
207
+
208
+ @staticmethod
209
+ def infer_column_types(
210
+ current_df: pd.DataFrame,
211
+ rows_percent: int = 2,
212
+ ) -> pd.DataFrame:
213
+
214
+ sample_size: int = AnalysisUtils.sample_size(current_df, rows_percent)
215
+
216
+ columns: list[str] = current_df.columns.tolist()
217
+
218
+ inferred_types: list[str] = []
219
+
220
+ for column in columns:
221
+
222
+ non_null_rows_sample: pd.Series = (
223
+ current_df[column]
224
+ .dropna()
225
+ .head(sample_size)
226
+ )
227
+
228
+ if non_null_rows_sample.empty:
229
+ dominant_type: str = 'None'
230
+ else:
231
+ dominant_type: str = AnalysisUtils.get_dominant_type(non_null_rows_sample)
232
+
233
+ # Reanalisa as colunas numéricas caso haja dúvida
234
+ if dominant_type == 'zero':
235
+ full_non_null_column: pd.Series = current_df[column].dropna()
236
+ dominant_type = AnalysisUtils.get_dominant_type(
237
+ non_null_rows_sample=full_non_null_column,
238
+ without_zero=True
239
+ )
240
+
241
+ inferred_types.append(dominant_type)
242
+
243
+ df_types: pd.DataFrame = pd.DataFrame({
244
+ 'column_name': columns,
245
+ 'type': inferred_types
246
+ })
247
+ print(df_types)
248
+
249
+ return df_types
250
+
251
+
252
+ @staticmethod
253
+ def get_sep_delimiter(
254
+ file_path: str,
255
+ encoding: Optional[str] = 'latin1',
256
+ skiprows: Optional[int] = 0
257
+ ) -> str:
258
+ sample_characters: int = 2048
259
+
260
+ with open(file_path, 'r', encoding=encoding) as file:
261
+ for _ in range(skiprows):
262
+ next(file) # pula as linhas iniciais
263
+
264
+ sample: str = file.read(sample_characters)
265
+ sniffer: csv.Sniffer = csv.Sniffer()
266
+ dialect: Dialect = sniffer.sniff(sample)
267
+
268
+ # with open(file_path, 'r', encoding=encoding) as file:
269
+ # sample: str = file.read(sample_characters)
270
+ # sniffer: csv.Sniffer = csv.Sniffer()
271
+ # dialect: Dialect = sniffer.sniff(sample)
272
+
273
+ print(f'sep_delimiter: "{dialect.delimiter}"')
274
+ return dialect.delimiter
275
+
276
+
277
+ @staticmethod
278
+ def get_frequency_by_column(current_df: pd.DataFrame, column: str) -> pd.DataFrame:
279
+ '''
280
+ Gera um current_df com a frequencia de valores de uma coluna
281
+ '''
282
+ current_df_frequency: pd.DataFrame = (
283
+ current_df[column]
284
+ .value_counts()
285
+ .reset_index()
286
+ )
287
+
288
+ current_df_frequency.columns = [column, 'frequency']
289
+
290
+ current_df_frequency: pd.DataFrame = (
291
+ current_df_frequency
292
+ .sort_values(
293
+ by='frequency',
294
+ ascending=False
295
+ )
296
+ )
297
+
298
+ return current_df_frequency
299
+
300
+
301
+ @staticmethod
302
+ def get_unique_values(current_df: pd.DataFrame, column: str) -> pd.DataFrame:
303
+ '''
304
+ Retorna um dataframe com os valores únicos de uma coluna
305
+ '''
306
+ df_unique = pd.DataFrame({
307
+ column: current_df[column].dropna().unique()
308
+ })
309
+
310
+ df_unique = df_unique.sort_values(
311
+ by=column,
312
+ ascending=True
313
+ ).reset_index(drop=True)
314
+
315
+ return df_unique
316
+
317
+ @staticmethod
318
+ def df_size_info(current_df: pd.DataFrame) -> None:
319
+ return {
320
+ "rows": current_df.shape[0],
321
+ "columns": current_df.shape[1],
322
+ "RAM MB": round(current_df.memory_usage(index=True, deep=True).sum() / (1024 * 1024), 2)
323
+ }
@@ -0,0 +1,45 @@
1
+ from csv import Dialect
2
+ from datetime import datetime, time
3
+ from pandas.api.types import CategoricalDtype
4
+ from typing import Literal, Optional
5
+ from .regex_patterns import (
6
+ DATE_REGEX_PATTERNS,
7
+ TIME_REGEX_PATTERNS,
8
+ DATE_TIME_REGEX_PATTERNS,
9
+ )
10
+ import csv
11
+ import numpy as np
12
+ import pandas as pd
13
+ import re
14
+
15
+
16
+ import chardet
17
+
18
+
19
+ class FileAnalysisUtils:
20
+ @staticmethod
21
+ def get_csv_sep_delimiter(
22
+ file_path: str,
23
+ encoding: Optional[str] = 'latin1',
24
+ skiprows: Optional[int] = 0
25
+ ) -> str:
26
+ sample_characters: int = 2048
27
+
28
+ with open(file_path, 'r', encoding=encoding) as file:
29
+ for _ in range(skiprows):
30
+ next(file) # pula as linhas iniciais
31
+
32
+ sample: str = file.read(sample_characters)
33
+ sniffer: csv.Sniffer = csv.Sniffer()
34
+ dialect: Dialect = sniffer.sniff(sample)
35
+
36
+ with open(file_path, "rb") as f:
37
+ rawdata = f.read(100000) # lê parte do arquivo
38
+ result = chardet.detect(rawdata)
39
+ print(result)
40
+
41
+ print(f'sep_delimiter: "{dialect.delimiter}"')
42
+ return dialect.delimiter
43
+
44
+
45
+