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.
@@ -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.4
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.4
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')]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='InsideForest',
5
- version='0.2.4',
5
+ version='0.2.6',
6
6
  packages=find_packages(),
7
7
  license='MIT',
8
8
  author=[('Jose Carlos Del Valle', 'jcval94@gmail.com'),
File without changes
File without changes