-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAnalisis.py
More file actions
235 lines (177 loc) · 10.2 KB
/
Copy pathAnalisis.py
File metadata and controls
235 lines (177 loc) · 10.2 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import argparse
import logging
import os
import sys
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import chi2_contingency
# Configuración global
sns.set_theme(style="whitegrid")
plt.rcParams.update({'figure.autolayout': True})
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
REQUIRED_COLUMNS = ['categoria_patron', 'sesgo_psicologico']
def validate_dataframe(df: pd.DataFrame) -> pd.DataFrame:
missing = [c for c in REQUIRED_COLUMNS if c not in df.columns]
if missing:
raise ValueError(f"Faltan columnas requeridas en el dataset: {missing}")
# Advertencias si faltan las columnas reales de tu CSV
if 'videojuego' not in df.columns:
logging.warning("Falta la columna 'videojuego'. No habrá gráficos por juego individual.")
if 'modelo' not in df.columns:
logging.warning("Falta la columna 'modelo'. No habrá gráficos por F2P o Suscripción.")
logging.info('Recuento de nulos por columna:\n%s', df.isna().sum().to_string())
# Normalizar nulos
df['categoria_patron'] = df['categoria_patron'].fillna('Desconocido').astype(str)
df['sesgo_psicologico'] = df['sesgo_psicologico'].fillna('Desconocido').astype(str)
if 'retorica_color' in df.columns:
df['retorica_color'] = df['retorica_color'].fillna('Desconocido').astype(str)
if 'retorica_tamano' in df.columns:
df['retorica_tamano'] = df['retorica_tamano'].fillna('Desconocido').astype(str)
if 'alerta_sonora' in df.columns:
df['alerta_sonora'] = df['alerta_sonora'].fillna('No').astype(str)
return df
def reporte_basico(df: pd.DataFrame, output_dir: str):
print('\n--- RESUMEN BÁSICO (GENERAL) ---')
print(f"Total de registros: {len(df)}")
print(f"Filas duplicadas: {df.duplicated().sum()}")
combo = df.groupby(['categoria_patron', 'sesgo_psicologico']).size().sort_values(ascending=False)
print('\n--- TOP 15: Combinaciones (Patrón + Sesgo) ---')
print(combo.head(15).to_string())
# Exportaciones pesadas a CSV
combo.to_csv(os.path.join(output_dir, 'combo_categoria_sesgo_general.csv'), index=True)
crosstab = pd.crosstab(df['categoria_patron'], df['sesgo_psicologico'], margins=True)
crosstab_norm = pd.crosstab(df['categoria_patron'], df['sesgo_psicologico'], normalize='index')
crosstab.to_csv(os.path.join(output_dir, 'crosstab_absoluto_general.csv'))
crosstab_norm.to_csv(os.path.join(output_dir, 'crosstab_norm_filas_general.csv'))
def grafico_barras(series: pd.Series, title: str, xlabel: str, ylabel: str, filename: str, color: str):
if series.empty:
return
plt.figure(figsize=(10, 6))
series.plot(kind='barh', color=color)
plt.title(title, fontsize=14, weight='bold')
plt.xlabel(xlabel, fontsize=12)
plt.ylabel(ylabel, fontsize=12)
plt.savefig(filename, dpi=600, bbox_inches='tight')
plt.close()
def generar_visuales_segmento(df_subset: pd.DataFrame, nombre_segmento: str, output_dir: str):
if df_subset.empty:
return
patron_counts = df_subset['categoria_patron'].value_counts().sort_values(ascending=True)
sesgo_counts = df_subset['sesgo_psicologico'].value_counts().sort_values(ascending=True)
# Limpiar el nombre para los archivos
nombre_archivo = nombre_segmento.replace(" ", "_").replace("/", "-")
grafico_barras(patron_counts, f'Frecuencia de Patrones Oscuros: {nombre_segmento}', 'Número de Impactos', 'Categoría', os.path.join(output_dir, f'patrones_{nombre_archivo}.png'), '#2b8cbe')
grafico_barras(sesgo_counts, f'Sesgos Psicológicos Explotados: {nombre_segmento}', 'Número de Observaciones', 'Sesgo', os.path.join(output_dir, f'sesgos_{nombre_archivo}.png'), '#e6550d')
if 'retorica_color' in df_subset.columns:
color_counts = df_subset['retorica_color'].value_counts().sort_values(ascending=True)
grafico_barras(color_counts, f'Retórica Visual: Colores: {nombre_segmento}', 'Número de Observaciones', 'Color', os.path.join(output_dir, f'retorica_color_{nombre_archivo}.png'), '#31a354')
if 'retorica_tamano' in df_subset.columns:
tamano_counts = df_subset['retorica_tamano'].value_counts().sort_values(ascending=True)
grafico_barras(tamano_counts, f'Retórica Visual: Tamaños: {nombre_segmento}', 'Número de Observaciones', 'Tamaño', os.path.join(output_dir, f'retorica_tamano_{nombre_archivo}.png'), '#756bb1')
if 'alerta_sonora' in df_subset.columns:
sonido_counts = df_subset['alerta_sonora'].value_counts().sort_values(ascending=True)
grafico_barras(sonido_counts, f'Alertas Sonoras: {nombre_segmento}', 'Número de Observaciones', 'Alerta Sonora', os.path.join(output_dir, f'alerta_sonora_{nombre_archivo}.png'), '#de2d26')
def generar_informes_y_graficos(df: pd.DataFrame, output_dir: str):
print('\n--- GENERANDO GRÁFICOS PNG ---')
# 1. Dataset General
generar_visuales_segmento(df, 'General', output_dir)
print("- PNGs de la muestra General creados.")
# 2. Por Modelo de Monetización (F2P vs Suscripcion)
if 'modelo' in df.columns:
modelos = df['modelo'].dropna().unique()
for modelo in modelos:
df_modelo = df[df['modelo'] == modelo]
generar_visuales_segmento(df_modelo, f'Modelo_{modelo}', output_dir)
print(f"- PNGs por Modelo de Monetización ({', '.join(modelos)}) creados.")
# 3. Por Juego Individual
if 'videojuego' in df.columns:
juegos = df['videojuego'].dropna().unique()
for juego in juegos:
df_juego = df[df['videojuego'] == juego]
generar_visuales_segmento(df_juego, f'Juego_{juego}', output_dir)
print(f"- PNGs por Juego individual ({', '.join(juegos)}) creados.")
# Mapa de calor global
numeric_cols = df.select_dtypes(include='number')
if numeric_cols.shape[1] > 1:
corr = numeric_cols.corr()
plt.figure(figsize=(8, 6))
sns.heatmap(corr, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Mapa de Correlaciones General')
plt.savefig(os.path.join(output_dir, 'correlation_heatmap_general.png'), dpi=600, bbox_inches='tight')
plt.close()
def apoyo_confianza_lift(df: pd.DataFrame, output_dir: str):
total = len(df)
if total == 0:
return
marg_p = df['categoria_patron'].value_counts()
marg_s = df['sesgo_psicologico'].value_counts()
combo = df.groupby(['categoria_patron', 'sesgo_psicologico']).size().reset_index(name='n')
combo['soporte'] = combo['n'] / total
combo['conf_patron_a_sesgo'] = combo['n'] / combo['categoria_patron'].map(marg_p)
combo['conf_sesgo_a_patron'] = combo['n'] / combo['sesgo_psicologico'].map(marg_s)
prob_p = combo['categoria_patron'].map(marg_p) / total
prob_s = combo['sesgo_psicologico'].map(marg_s) / total
combo['lift'] = combo['soporte'] / (prob_p * prob_s)
combo = combo.sort_values(by=['lift', 'conf_patron_a_sesgo', 'soporte'], ascending=False)
combo.to_csv(os.path.join(output_dir, 'reglas_asociacion_completas.csv'), index=False)
print('\n--- TOP 20: REGLAS DE ASOCIACIÓN GENERAL (Ordenadas por Lift) ---')
print(combo.head(20).to_string(index=False, formatters={
'soporte': '{:.3f}'.format,
'conf_patron_a_sesgo': '{:.3f}'.format,
'conf_sesgo_a_patron': '{:.3f}'.format,
'lift': '{:.3f}'.format,
}))
def estadistico_chi_cuadrado(df: pd.DataFrame, output_dir: str):
cross = pd.crosstab(df['categoria_patron'], df['sesgo_psicologico'])
if cross.empty or cross.shape[0] < 2 or cross.shape[1] < 2:
logging.warning("Matriz insuficiente para calcular Chi-cuadrado.")
return
chi2, p, dof, expected = chi2_contingency(cross)
total = cross.values.sum()
min_dim = min(cross.shape[0] - 1, cross.shape[1] - 1)
cramer_v = np.sqrt(chi2 / (total * min_dim)) if total > 0 and min_dim > 0 else np.nan
print('\n=== CONCLUSIÓN ESTADÍSTICA (CHI-CUADRADO GENERAL) ===')
print(f'Chi2: {chi2:.2f} | P-value: {p:.6g} | Grados de libertad: {dof} | Cramer V: {cramer_v:.3f}')
if p < 0.05:
print('-> Resultado: SIGNIFICATIVO. Hay relación real entre patrón y sesgo en el cómputo global.')
else:
print('-> Resultado: NO SIGNIFICATIVO. Parecen independientes a nivel global.')
expected_df = pd.DataFrame(expected, index=cross.index, columns=cross.columns)
row_mass = cross.sum(axis=1) / total
col_mass = cross.sum(axis=0) / total
denominator = np.sqrt(expected_df * (1 - row_mass.values.reshape(-1, 1)) * (1 - col_mass.values.reshape(1, -1)))
adj_residuals = (cross - expected_df) / denominator.replace(0, np.nan)
expected_df.to_csv(os.path.join(output_dir, 'chi2_matriz_esperada.csv'))
adj_residuals.to_csv(os.path.join(output_dir, 'chi2_residuales_ajustados.csv'))
def main():
directorio_script = os.path.dirname(os.path.abspath(__file__))
ruta_input_defecto = os.path.join(directorio_script, 'Datos.csv')
ruta_output_defecto = os.path.join(directorio_script, 'output')
parser = argparse.ArgumentParser(description='Auditoría de Patrones Oscuros. Segmentación automática de imágenes.')
parser.add_argument('--input', default=ruta_input_defecto, help='Ruta al CSV (separador ;).')
parser.add_argument('--output', default=ruta_output_defecto, help='Carpeta para exportar CSVs y PNGs.')
args = parser.parse_args()
if not os.path.isabs(args.input):
args.input = os.path.join(directorio_script, args.input)
if not os.path.isabs(args.output):
args.output = os.path.join(directorio_script, args.output)
if not os.path.exists(args.input):
logging.error('El archivo "%s" no existe. Abortando.', args.input)
sys.exit(1)
os.makedirs(args.output, exist_ok=True)
logging.info('Procesando datos...')
try:
df = pd.read_csv(args.input, sep=';')
df = validate_dataframe(df)
except Exception as e:
logging.error('Fallo crítico al preparar los datos: %s', e)
sys.exit(1)
reporte_basico(df, args.output)
generar_informes_y_graficos(df, args.output)
apoyo_confianza_lift(df, args.output)
estadistico_chi_cuadrado(df, args.output)
print(f'\n[+] Ejecución completada. Tienes tus PNGs segmentados y los CSVs duros en: {args.output}')
if __name__ == '__main__':
main()