InsideForest 0.2.4__tar.gz → 0.2.6__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.
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest/regions.py +102 -4
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest/trees.py +81 -4
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest.egg-info/PKG-INFO +1 -1
- {insideforest-0.2.4 → insideforest-0.2.6}/PKG-INFO +1 -1
- {insideforest-0.2.4 → insideforest-0.2.6}/setup.py +1 -1
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest/__init__.py +0 -0
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest/descrip.py +0 -0
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest/labels.py +0 -0
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest/models.py +0 -0
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest.egg-info/SOURCES.txt +0 -0
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest.egg-info/dependency_links.txt +0 -0
- {insideforest-0.2.4 → insideforest-0.2.6}/InsideForest.egg-info/top_level.txt +0 -0
- {insideforest-0.2.4 → insideforest-0.2.6}/README.md +0 -0
- {insideforest-0.2.4 → insideforest-0.2.6}/setup.cfg +0 -0
|
@@ -34,6 +34,36 @@ class regions:
|
|
|
34
34
|
m_medios = [(df_p1.iloc[i] + df_p2.iloc[i]) / 2 for i in range(len(df_p1))]
|
|
35
35
|
return pd.DataFrame(m_medios)
|
|
36
36
|
|
|
37
|
+
def mean_distance_ndim_fast(self, df_sep_dm_agg, verbose):
|
|
38
|
+
"""
|
|
39
|
+
Versión optimizada de mean_distance_ndim que calcula (linf + lsup)/2
|
|
40
|
+
en forma vectorizada usando NumPy.
|
|
41
|
+
|
|
42
|
+
Parámetros:
|
|
43
|
+
- df_sep_dm_agg: DataFrame con índices multi-nivel que permiten extraer
|
|
44
|
+
'linf' y 'lsup' usando xs.
|
|
45
|
+
|
|
46
|
+
Retorna:
|
|
47
|
+
- DataFrame con la media de linf y lsup por fila y dimensión.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Extraemos linf y lsup
|
|
51
|
+
df_p1 = df_sep_dm_agg.xs('linf', axis=1, level=0)
|
|
52
|
+
df_p2 = df_sep_dm_agg.xs('lsup', axis=1, level=0)
|
|
53
|
+
|
|
54
|
+
# Operación vectorizada con NumPy:
|
|
55
|
+
# (df_p1 + df_p2) / 2
|
|
56
|
+
m_medios_values = (df_p1.values + df_p2.values) / 2.0
|
|
57
|
+
|
|
58
|
+
# Reconstruimos el DataFrame con el mismo índice y columnas que df_p1
|
|
59
|
+
df_result = pd.DataFrame(
|
|
60
|
+
m_medios_values,
|
|
61
|
+
index=df_p1.index,
|
|
62
|
+
columns=df_p1.columns
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return df_result
|
|
66
|
+
|
|
37
67
|
def posiciones_valores_frecuentes(self, lista):
|
|
38
68
|
frecuentes = Counter(lista).most_common()
|
|
39
69
|
if len(set(lista)) == len(lista):
|
|
@@ -94,15 +124,78 @@ class regions:
|
|
|
94
124
|
(index[0], col_name)] = value
|
|
95
125
|
return pd.concat([df_lilu, df_sep_dm[['ponderador']]], axis=1)
|
|
96
126
|
|
|
127
|
+
def fill_na_pond_fastest(self, df_sep_dm, df, features_val, verbose):
|
|
128
|
+
"""
|
|
129
|
+
Versión ultra-optimizada de fill_na_pond para reemplazar -inf e inf usando operaciones vectorizadas avanzadas.
|
|
130
|
+
|
|
131
|
+
Parámetros:
|
|
132
|
+
- df_sep_dm: DataFrame con columnas multi-nivel ('linf', 'lsup', 'ponderador', etc.).
|
|
133
|
+
- df: DataFrame original para extraer límites de cada dimensión.
|
|
134
|
+
- features_val: Lista de características/dimensiones presentes en df.
|
|
135
|
+
|
|
136
|
+
Retorna:
|
|
137
|
+
- DataFrame con los mismos valores que el original, pero reemplazando -inf e inf
|
|
138
|
+
por los límites correspondientes en las columnas 'linf' y 'lsup'.
|
|
139
|
+
Incluye la columna 'ponderador'.
|
|
140
|
+
"""
|
|
141
|
+
# Extraer las columnas 'linf' y 'lsup'
|
|
142
|
+
df_lilu = df_sep_dm[['linf', 'lsup']].copy()
|
|
143
|
+
|
|
144
|
+
# Calcular los límites de reemplazo para cada dimensión
|
|
145
|
+
lsup_limit = df[features_val].max() + 1 # Límite superior
|
|
146
|
+
linf_limit = df[features_val].min() - 1 # Límite inferior
|
|
147
|
+
|
|
148
|
+
# Asegurarse de que el orden de features_val coincide con el orden de las columnas
|
|
149
|
+
# Obtener los nombres de las dimensiones desde las columnas MultiIndex
|
|
150
|
+
linf_features = df_lilu['linf'].columns.tolist()
|
|
151
|
+
lsup_features = df_lilu['lsup'].columns.tolist()
|
|
152
|
+
|
|
153
|
+
# Crear DataFrames de reemplazo alineados con las columnas
|
|
154
|
+
# Cada columna tendrá un único valor de reemplazo correspondiente
|
|
155
|
+
# Reemplazamos todos los -inf y inf en una sola operación vectorizada
|
|
156
|
+
|
|
157
|
+
# Para 'linf' columns
|
|
158
|
+
linf_repl_df = pd.DataFrame(
|
|
159
|
+
np.tile(linf_limit.values, (df_lilu['linf'].shape[0], 1)),
|
|
160
|
+
columns=df_lilu['linf'].columns,
|
|
161
|
+
index=df_lilu.index
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Para 'lsup' columns
|
|
165
|
+
lsup_repl_df = pd.DataFrame(
|
|
166
|
+
np.tile(lsup_limit.values, (df_lilu['lsup'].shape[0], 1)),
|
|
167
|
+
columns=df_lilu['lsup'].columns,
|
|
168
|
+
index=df_lilu.index
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Crear máscaras para identificar dónde están los -inf y inf
|
|
172
|
+
mask_linf = np.isinf(df_lilu['linf'].values)
|
|
173
|
+
mask_lsup = np.isinf(df_lilu['lsup'].values)
|
|
174
|
+
|
|
175
|
+
# Aplicar las máscaras y reemplazar los valores
|
|
176
|
+
# Usamos donde para asignar los valores de reemplazo donde la máscara es True
|
|
177
|
+
df_lilu['linf'] = np.where(mask_linf, linf_repl_df.values, df_lilu['linf'].values)
|
|
178
|
+
df_lilu['lsup'] = np.where(mask_lsup, lsup_repl_df.values, df_lilu['lsup'].values)
|
|
179
|
+
|
|
180
|
+
# Concatenar la columna 'ponderador' de vuelta al DataFrame
|
|
181
|
+
df_replaced = pd.concat([df_lilu, df_sep_dm[['ponderador']]], axis=1)
|
|
182
|
+
|
|
183
|
+
return df_replaced
|
|
184
|
+
|
|
185
|
+
|
|
97
186
|
|
|
98
187
|
def get_agg_regions(self, df_eval, df, verbose=False):
|
|
99
188
|
features_val = sorted(df_eval['dimension'].unique())
|
|
100
189
|
aleatorio1 = features_val[0]
|
|
101
190
|
df_sep_dm = pd.pivot_table(df_eval, index='rectangulo', columns='dimension')
|
|
102
191
|
|
|
103
|
-
df_sep_dm = self.fill_na_pond(df_sep_dm, df, features_val)
|
|
192
|
+
# df_sep_dm = self.fill_na_pond(df_sep_dm, df, features_val)
|
|
193
|
+
|
|
194
|
+
df_sep_dm = self.fill_na_pond_fastest(df_sep_dm, df, features_val,None)
|
|
104
195
|
|
|
105
|
-
df_m_medios = self.mean_distance_ndim(df_sep_dm)
|
|
196
|
+
# df_m_medios = self.mean_distance_ndim(df_sep_dm)
|
|
197
|
+
df_m_medios = self.mean_distance_ndim_fast(df_sep_dm, None)
|
|
198
|
+
|
|
106
199
|
scaler = StandardScaler()
|
|
107
200
|
X_feat = scaler.fit_transform(df_m_medios.values)
|
|
108
201
|
epsil = self.get_eps_multiple_groups_opt(X_feat)
|
|
@@ -140,13 +233,18 @@ class regions:
|
|
|
140
233
|
df_sep_outl.loc[:,'ponderador'] = df_sep_outl.loc[:,'ponderador'].values*max_l_val
|
|
141
234
|
return pd.concat([df_sep_dm_agg,df_sep_outl])
|
|
142
235
|
|
|
143
|
-
def prio_ranges(self, separacion_dim, df):
|
|
236
|
+
def prio_ranges(self, separacion_dim, df, verbose=0):
|
|
144
237
|
# aquí se usa DBS
|
|
238
|
+
if verbose==1:
|
|
239
|
+
print("Agregando regiones con DBSCAN")
|
|
145
240
|
df_res = [self.get_agg_regions(df_, df) for df_ in separacion_dim]
|
|
241
|
+
|
|
146
242
|
prio_ = [df_['ponderador'].values[0][0] for df_ in df_res]
|
|
243
|
+
|
|
147
244
|
df_reres = [x[0] for x in sorted([(a, b) for a,b in zip(df_res,prio_)],
|
|
148
245
|
key=lambda x: -x[1])]
|
|
149
|
-
cols_ = [df_['linf'].columns.tolist() for df_ in df_reres]
|
|
246
|
+
# cols_ = [df_['linf'].columns.tolist() for df_ in df_reres]
|
|
247
|
+
|
|
150
248
|
return df_reres
|
|
151
249
|
|
|
152
250
|
|
|
@@ -249,6 +249,81 @@ class trees:
|
|
|
249
249
|
|
|
250
250
|
|
|
251
251
|
|
|
252
|
+
def get_summary_optimizado(self, data1, df_full_arboles, var_obj, no_branch_lim=100, verbose=0):
|
|
253
|
+
# 1) Calculamos el pivot que resume por N_regla, N_arbol, feature, operador, etc.
|
|
254
|
+
agrupacion = pd.pivot_table(
|
|
255
|
+
df_full_arboles,
|
|
256
|
+
index=['N_regla', 'N_arbol', 'feature', 'operador'],
|
|
257
|
+
values=['rangos', 'Importancia'],
|
|
258
|
+
aggfunc=['min', 'max', 'mean']
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# 2) Extraemos los valores de min, max y mean
|
|
262
|
+
agrupacion_min = agrupacion['min'].reset_index()
|
|
263
|
+
agrupacion_min = agrupacion_min[agrupacion_min['operador'] == '<=']
|
|
264
|
+
|
|
265
|
+
agrupacion_max = agrupacion['max'].reset_index()
|
|
266
|
+
agrupacion_max = agrupacion_max[agrupacion_max['operador'] == '>']
|
|
267
|
+
|
|
268
|
+
# (Podríamos usar agrupacion_mean si lo necesitas luego; en el ejemplo no se reusa directamente)
|
|
269
|
+
agrupacion_mean = agrupacion['mean'].reset_index()
|
|
270
|
+
|
|
271
|
+
# 3) Concatenamos las filas con operador <= y >, y ordenamos
|
|
272
|
+
agrupacion = pd.concat([agrupacion_min, agrupacion_max]).sort_values(['N_arbol', 'N_regla'])
|
|
273
|
+
|
|
274
|
+
# 4) Seleccionamos los top 100 árboles
|
|
275
|
+
top_100_arboles = agrupacion['N_arbol'].unique()[:no_branch_lim]
|
|
276
|
+
|
|
277
|
+
# 5) Iteramos por cada árbol y regla para construir una única máscara booleana por regla
|
|
278
|
+
reglas = []
|
|
279
|
+
|
|
280
|
+
for arbol_num in tqdm(top_100_arboles, disable=(verbose == 0), desc="Procesando ramas"):
|
|
281
|
+
# Imprimimos (opcional) según el valor de verbose
|
|
282
|
+
if arbol_num % 50 == 0 and verbose == 1:
|
|
283
|
+
print(f"Procesando rama del árbol: {arbol_num}")
|
|
284
|
+
|
|
285
|
+
# Subconjunto del pivot para este árbol
|
|
286
|
+
ag_arbol = agrupacion[agrupacion['N_arbol'] == arbol_num]
|
|
287
|
+
|
|
288
|
+
# Recorremos cada regla de ese árbol
|
|
289
|
+
for regla_num in ag_arbol['N_regla'].unique():
|
|
290
|
+
ag_regla = ag_arbol[ag_arbol['N_regla'] == regla_num]
|
|
291
|
+
|
|
292
|
+
# Obtenemos pares (feature, valor) según operador
|
|
293
|
+
men_ = ag_regla[ag_regla['operador'] == '<='][['feature', 'rangos']].values
|
|
294
|
+
may_ = ag_regla[ag_regla['operador'] == '>'][['feature', 'rangos']].values
|
|
295
|
+
|
|
296
|
+
# Construimos la máscara booleana para filtrar data1 en un único paso
|
|
297
|
+
mask = np.ones(len(data1), dtype=bool)
|
|
298
|
+
|
|
299
|
+
# Agregamos condiciones de <=
|
|
300
|
+
for col, val in men_:
|
|
301
|
+
mask &= (data1[col] <= val)
|
|
302
|
+
|
|
303
|
+
# Agregamos condiciones de >
|
|
304
|
+
for col, val in may_:
|
|
305
|
+
mask &= (data1[col] > val)
|
|
306
|
+
|
|
307
|
+
# Calculamos n_sample y ef_sample
|
|
308
|
+
n_sample = mask.sum() # número de filas que cumplen todas las condiciones
|
|
309
|
+
# Evitamos error en caso de n_sample = 0
|
|
310
|
+
ef_sample = data1.loc[mask, var_obj].mean() if n_sample > 0 else 0
|
|
311
|
+
|
|
312
|
+
# Creamos una copia para esa regla, asignando los valores calculados
|
|
313
|
+
ag_regla_copy = ag_regla.copy()
|
|
314
|
+
ag_regla_copy['n_sample'] = n_sample
|
|
315
|
+
ag_regla_copy['ef_sample'] = ef_sample
|
|
316
|
+
|
|
317
|
+
reglas.append(ag_regla_copy)
|
|
318
|
+
|
|
319
|
+
# 6) Concatenamos todos los resultados y ordenamos por las métricas solicitadas
|
|
320
|
+
resultado = pd.concat(reglas, ignore_index=True)
|
|
321
|
+
resultado = resultado.sort_values(by=['ef_sample', 'n_sample'], ascending=False)
|
|
322
|
+
|
|
323
|
+
return resultado
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
252
327
|
def get_rect_coords(self, df):
|
|
253
328
|
limits = {}
|
|
254
329
|
for i, row in df.iterrows():
|
|
@@ -322,7 +397,7 @@ class trees:
|
|
|
322
397
|
separacion_dim = self.get_dfs_dim(rectangles_)
|
|
323
398
|
return separacion_dim
|
|
324
399
|
|
|
325
|
-
def get_branches(self, df, var_obj, regr, verbose=0):
|
|
400
|
+
def get_branches(self, df, var_obj, regr, no_trees_search=100, verbose=0):
|
|
326
401
|
"""
|
|
327
402
|
Función principal para extraer los rectángulos (reglas) de los árboles.
|
|
328
403
|
:param df: DataFrame original
|
|
@@ -346,11 +421,13 @@ class trees:
|
|
|
346
421
|
if verbose==1:
|
|
347
422
|
print("Obtenemos un resumen de los árboles")
|
|
348
423
|
|
|
349
|
-
df_summ = self.get_summary(df, df_full_arboles, var_obj, verbose)
|
|
350
|
-
|
|
424
|
+
# df_summ = self.get_summary(df, df_full_arboles, var_obj, verbose)
|
|
425
|
+
df_summ = self.get_summary_optimizado(df, df_full_arboles, var_obj, no_trees_search, verbose)
|
|
426
|
+
|
|
351
427
|
if verbose==1:
|
|
352
428
|
print("Generamos el df final con forma de rectángulo")
|
|
353
429
|
# Extraemos las reglas (extract_rectangles)
|
|
354
430
|
separacion_dim = self.extract_rectangles(df_summ)
|
|
355
431
|
|
|
356
|
-
return separacion_dim
|
|
432
|
+
return separacion_dim
|
|
433
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: InsideForest
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A comprehensive library for describing and analyzing data insights via AI
|
|
5
5
|
Home-page: https://github.com/jcval94/InsideForest.git
|
|
6
6
|
Author: [('Jose Carlos Del Valle', 'jcval94@gmail.com'), ('ChatGPT', 'chat.openai.com/chat')]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: InsideForest
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A comprehensive library for describing and analyzing data insights via AI
|
|
5
5
|
Home-page: https://github.com/jcval94/InsideForest.git
|
|
6
6
|
Author: [('Jose Carlos Del Valle', 'jcval94@gmail.com'), ('ChatGPT', 'chat.openai.com/chat')]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|