AIdeaText commited on
Commit
7ce04ee
·
verified ·
1 Parent(s): cd3c5d0

Update modules/text_analysis/discourse_analysis.py

Browse files
modules/text_analysis/discourse_analysis.py CHANGED
@@ -1,267 +1,287 @@
1
- # modules/text_analysis/discourse_analysis.py
2
- # Configuración de matplotlib
3
-
4
- import streamlit as st
5
- import spacy
6
- import networkx as nx
7
- import matplotlib.pyplot as plt
8
- import pandas as pd
9
- import numpy as np
10
- import logging
11
- import io
12
- import base64
13
- from collections import Counter, defaultdict
14
- import logging
15
-
16
-
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- from .semantic_analysis import (
22
- create_concept_graph,
23
- visualize_concept_graph,
24
- identify_key_concepts
25
- )
26
-
27
-
28
- from .stopwords import (
29
- get_custom_stopwords,
30
- process_text,
31
- get_stopwords_for_spacy
32
- )
33
-
34
-
35
- #####################
36
- POS_TRANSLATIONS = {
37
- 'es': {
38
- 'ADJ': 'Adjetivo', 'ADP': 'Preposición', 'ADV': 'Adverbio', 'AUX': 'Auxiliar',
39
- 'CCONJ': 'Conjunción Coordinante', 'DET': 'Determinante', 'INTJ': 'Interjección',
40
- 'NOUN': 'Sustantivo', 'NUM': 'Número', 'PART': 'Partícula', 'PRON': 'Pronombre',
41
- 'PROPN': 'Nombre Propio', 'SCONJ': 'Conjunción Subordinante', 'SYM': 'Símbolo',
42
- 'VERB': 'Verbo', 'X': 'Otro',
43
- },
44
- 'en': {
45
- 'ADJ': 'Adjective', 'ADP': 'Preposition', 'ADV': 'Adverb', 'AUX': 'Auxiliary',
46
- 'CCONJ': 'Coordinating Conjunction', 'DET': 'Determiner', 'INTJ': 'Interjection',
47
- 'NOUN': 'Noun', 'NUM': 'Number', 'PART': 'Particle', 'PRON': 'Pronoun',
48
- 'PROPN': 'Proper Noun', 'SCONJ': 'Subordinating Conjunction', 'SYM': 'Symbol',
49
- 'VERB': 'Verb', 'X': 'Other',
50
- },
51
- 'fr': {
52
- 'ADJ': 'Adjectif', 'ADP': 'Préposition', 'ADV': 'Adverbe', 'AUX': 'Auxiliaire',
53
- 'CCONJ': 'Conjonction de Coordination', 'DET': 'Déterminant', 'INTJ': 'Interjection',
54
- 'NOUN': 'Nom', 'NUM': 'Nombre', 'PART': 'Particule', 'PRON': 'Pronom',
55
- 'PROPN': 'Nom Propre', 'SCONJ': 'Conjonction de Subordination', 'SYM': 'Symbole',
56
- 'VERB': 'Verbe', 'X': 'Autre',
57
- },
58
- 'pt': {
59
- 'ADJ': 'Adjetivo', 'ADP': 'Preposição', 'ADV': 'Advérbio', 'AUX': 'Auxiliar',
60
- 'CCONJ': 'Conjunção Coordenativa', 'DET': 'Determinante', 'INTJ': 'Interjeição',
61
- 'NOUN': 'Substantivo', 'NUM': 'Número', 'PART': 'Partícula', 'PRON': 'Pronome',
62
- 'PROPN': 'Nome Próprio', 'SCONJ': 'Conjunção Subordinativa', 'SYM': 'Símbolo',
63
- 'VERB': 'Verbo', 'X': 'Outro',
64
- }
65
- }
66
-
67
- ENTITY_LABELS = {
68
- 'es': {
69
- "Personas": "lightblue",
70
- "Lugares": "lightcoral",
71
- "Inventos": "lightgreen",
72
- "Fechas": "lightyellow",
73
- "Conceptos": "lightpink"
74
- },
75
- 'en': {
76
- "People": "lightblue",
77
- "Places": "lightcoral",
78
- "Inventions": "lightgreen",
79
- "Dates": "lightyellow",
80
- "Concepts": "lightpink"
81
- },
82
- 'fr': {
83
- "Personnes": "lightblue",
84
- "Lieux": "lightcoral",
85
- "Inventions": "lightgreen",
86
- "Dates": "lightyellow",
87
- "Concepts": "lightpink"
88
- },
89
- 'pt': {
90
- "Pessoas": "lightblue",
91
- "Lugares": "lightcoral",
92
- "Invenções": "lightgreen",
93
- "Datas": "lightyellow",
94
- "Conceitos": "lightpink"
95
- }
96
- }
97
-
98
- #################
99
-
100
- def fig_to_bytes(fig, dpi=100):
101
- """Convierte una figura de matplotlib a bytes."""
102
- try:
103
- buf = io.BytesIO()
104
- fig.savefig(buf, format='png', dpi=dpi, bbox_inches='tight') # Sin compression
105
- buf.seek(0)
106
- return buf.getvalue()
107
- except Exception as e:
108
- logger.error(f"Error en fig_to_bytes: {str(e)}")
109
- return None
110
-
111
- #################
112
- def compare_semantic_analysis(text1, text2, nlp, lang):
113
- """
114
- Realiza el análisis semántico comparativo entre dos textos
115
- """
116
- try:
117
- logger.info(f"Iniciando análisis comparativo para idioma: {lang}")
118
-
119
- # Obtener stopwords
120
- stopwords = get_custom_stopwords(lang)
121
- logger.info(f"Obtenidas {len(stopwords)} stopwords para el idioma {lang}")
122
-
123
- # Procesar los textos
124
- doc1 = nlp(text1)
125
- doc2 = nlp(text2)
126
-
127
- # Identificar conceptos clave
128
- logger.info("Identificando conceptos clave del primer texto...")
129
- key_concepts1 = identify_key_concepts(doc1, stopwords=stopwords, min_freq=2, min_length=3)
130
-
131
- logger.info("Identificando conceptos clave del segundo texto...")
132
- key_concepts2 = identify_key_concepts(doc2, stopwords=stopwords, min_freq=2, min_length=3)
133
-
134
- if not key_concepts1 or not key_concepts2:
135
- raise ValueError("No se pudieron identificar conceptos clave en uno o ambos textos")
136
-
137
- # Crear grafos
138
- logger.info("Creando grafos de conceptos...")
139
- G1 = create_concept_graph(doc1, key_concepts1)
140
- G2 = create_concept_graph(doc2, key_concepts2)
141
-
142
- # Visualizar grafos
143
- logger.info("Visualizando grafos...")
144
-
145
- # Primer grafo
146
- plt.figure(figsize=(12, 8))
147
- fig1 = visualize_concept_graph(G1, lang)
148
- plt.title("Análisis del primer texto", pad=20)
149
- plt.tight_layout()
150
-
151
- # Segundo grafo
152
- plt.figure(figsize=(12, 8))
153
- fig2 = visualize_concept_graph(G2, lang)
154
- plt.title("Análisis del segundo texto", pad=20)
155
- plt.tight_layout()
156
-
157
- logger.info("Análisis comparativo completado exitosamente")
158
- return fig1, fig2, key_concepts1, key_concepts2
159
-
160
- except Exception as e:
161
- logger.error(f"Error en compare_semantic_analysis: {str(e)}")
162
- plt.close('all') # Limpiar recursos en caso de error
163
- raise
164
- finally:
165
- plt.close('all') # Asegurar limpieza en todos los casos
166
-
167
-
168
- ############################################
169
- def create_concept_table(key_concepts):
170
- """
171
- Crea una tabla de conceptos clave con sus frecuencias
172
- Args:
173
- key_concepts: Lista de tuplas (concepto, frecuencia)
174
- Returns:
175
- pandas.DataFrame: Tabla formateada de conceptos
176
- """
177
- try:
178
- if not key_concepts:
179
- logger.warning("Lista de conceptos vacía")
180
- return pd.DataFrame(columns=['Concepto', 'Frecuencia'])
181
-
182
- df = pd.DataFrame(key_concepts, columns=['Concepto', 'Frecuencia'])
183
- df['Frecuencia'] = df['Frecuencia'].round(2)
184
- return df
185
- except Exception as e:
186
- logger.error(f"Error en create_concept_table: {str(e)}")
187
- return pd.DataFrame(columns=['Concepto', 'Frecuencia'])
188
-
189
-
190
- ##########################################################
191
-
192
- def perform_discourse_analysis(text1, text2, nlp, lang):
193
- """
194
- Realiza el análisis completo del discurso
195
- Args:
196
- text1: Primer texto a analizar
197
- text2: Segundo texto a analizar
198
- nlp: Modelo de spaCy cargado
199
- lang: Código de idioma
200
- Returns:
201
- dict: Resultados del análisis con gráficos convertidos a bytes
202
- """
203
- try:
204
- logger.info("Iniciando análisis del discurso...")
205
-
206
- # Verificar inputs
207
- if not text1 or not text2:
208
- raise ValueError("Los textos de entrada no pueden estar vacíos")
209
-
210
- if not nlp:
211
- raise ValueError("Modelo de lenguaje no inicializado")
212
-
213
- # Realizar análisis comparativo
214
- fig1, fig2, key_concepts1, key_concepts2 = compare_semantic_analysis(
215
- text1, text2, nlp, lang
216
- )
217
-
218
- logger.info("Análisis comparativo completado, convirtiendo figuras a bytes...")
219
-
220
- # Convertir figuras a bytes para almacenamiento
221
- graph1_bytes = fig_to_bytes(fig1)
222
- graph2_bytes = fig_to_bytes(fig2)
223
-
224
- logger.info(f"Figura 1 convertida a {len(graph1_bytes) if graph1_bytes else 0} bytes")
225
- logger.info(f"Figura 2 convertida a {len(graph2_bytes) if graph2_bytes else 0} bytes")
226
-
227
- # Verificar que las conversiones fueron exitosas antes de continuar
228
- if not graph1_bytes or not graph2_bytes:
229
- logger.error("Error al convertir figuras a bytes - obteniendo 0 bytes")
230
- # Opción 1: Devolver error
231
- raise ValueError("No se pudieron convertir las figuras a bytes")
232
-
233
- # Crear tablas de resultados
234
- table1 = create_concept_table(key_concepts1)
235
- table2 = create_concept_table(key_concepts2)
236
-
237
- # Cerrar figuras para liberar memoria
238
- plt.close(fig1)
239
- plt.close(fig2)
240
-
241
- result = {
242
- 'graph1': graph1_bytes, # Bytes en lugar de figura
243
- 'graph2': graph2_bytes, # Bytes en lugar de figura
244
- 'combined_graph': None, # No hay gráfico combinado por ahora
245
- 'key_concepts1': key_concepts1,
246
- 'key_concepts2': key_concepts2,
247
- 'table1': table1,
248
- 'table2': table2,
249
- 'success': True
250
- }
251
-
252
- logger.info("Análisis del discurso completado y listo para almacenamiento")
253
- return result
254
-
255
- except Exception as e:
256
- logger.error(f"Error en perform_discourse_analysis: {str(e)}")
257
- # Asegurar limpieza de recursos
258
- plt.close('all')
259
- return {
260
- 'success': False,
261
- 'error': str(e)
262
- }
263
- finally:
264
- # Asegurar limpieza en todos los casos
265
- plt.close('all')
266
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  #################################################################
 
1
+ # modules/text_analysis/discourse_analysis.py
2
+ # Configuración de matplotlib
3
+
4
+ import streamlit as st
5
+ import spacy
6
+ import networkx as nx
7
+ import matplotlib.pyplot as plt
8
+ import pandas as pd
9
+ import numpy as np
10
+ import logging
11
+ import io
12
+ import base64
13
+ from collections import Counter, defaultdict
14
+ import logging
15
+
16
+
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ from .semantic_analysis import (
22
+ create_concept_graph,
23
+ visualize_concept_graph,
24
+ identify_key_concepts
25
+ )
26
+
27
+
28
+ from .stopwords import (
29
+ get_custom_stopwords,
30
+ process_text,
31
+ get_stopwords_for_spacy
32
+ )
33
+
34
+
35
+ #####################
36
+ POS_TRANSLATIONS = {
37
+ 'es': {
38
+ 'ADJ': 'Adjetivo', 'ADP': 'Preposición', 'ADV': 'Adverbio', 'AUX': 'Auxiliar',
39
+ 'CCONJ': 'Conjunción Coordinante', 'DET': 'Determinante', 'INTJ': 'Interjección',
40
+ 'NOUN': 'Sustantivo', 'NUM': 'Número', 'PART': 'Partícula', 'PRON': 'Pronombre',
41
+ 'PROPN': 'Nombre Propio', 'SCONJ': 'Conjunción Subordinante', 'SYM': 'Símbolo',
42
+ 'VERB': 'Verbo', 'X': 'Otro',
43
+ },
44
+ 'en': {
45
+ 'ADJ': 'Adjective', 'ADP': 'Preposition', 'ADV': 'Adverb', 'AUX': 'Auxiliary',
46
+ 'CCONJ': 'Coordinating Conjunction', 'DET': 'Determiner', 'INTJ': 'Interjection',
47
+ 'NOUN': 'Noun', 'NUM': 'Number', 'PART': 'Particle', 'PRON': 'Pronoun',
48
+ 'PROPN': 'Proper Noun', 'SCONJ': 'Subordinating Conjunction', 'SYM': 'Symbol',
49
+ 'VERB': 'Verb', 'X': 'Other',
50
+ },
51
+ 'fr': {
52
+ 'ADJ': 'Adjectif', 'ADP': 'Préposition', 'ADV': 'Adverbe', 'AUX': 'Auxiliaire',
53
+ 'CCONJ': 'Conjonction de Coordination', 'DET': 'Déterminant', 'INTJ': 'Interjection',
54
+ 'NOUN': 'Nom', 'NUM': 'Nombre', 'PART': 'Particule', 'PRON': 'Pronom',
55
+ 'PROPN': 'Nom Propre', 'SCONJ': 'Conjonction de Subordination', 'SYM': 'Symbole',
56
+ 'VERB': 'Verbe', 'X': 'Autre',
57
+ },
58
+ 'pt': {
59
+ 'ADJ': 'Adjetivo', 'ADP': 'Preposição', 'ADV': 'Advérbio', 'AUX': 'Auxiliar',
60
+ 'CCONJ': 'Conjunção Coordenativa', 'DET': 'Determinante', 'INTJ': 'Interjeição',
61
+ 'NOUN': 'Substantivo', 'NUM': 'Número', 'PART': 'Partícula', 'PRON': 'Pronome',
62
+ 'PROPN': 'Nome Próprio', 'SCONJ': 'Conjunção Subordinativa', 'SYM': 'Símbolo',
63
+ 'VERB': 'Verbo', 'X': 'Outro',
64
+ }
65
+ }
66
+
67
+ ENTITY_LABELS = {
68
+ 'es': {
69
+ "Personas": "lightblue",
70
+ "Lugares": "lightcoral",
71
+ "Inventos": "lightgreen",
72
+ "Fechas": "lightyellow",
73
+ "Conceptos": "lightpink"
74
+ },
75
+ 'en': {
76
+ "People": "lightblue",
77
+ "Places": "lightcoral",
78
+ "Inventions": "lightgreen",
79
+ "Dates": "lightyellow",
80
+ "Concepts": "lightpink"
81
+ },
82
+ 'fr': {
83
+ "Personnes": "lightblue",
84
+ "Lieux": "lightcoral",
85
+ "Inventions": "lightgreen",
86
+ "Dates": "lightyellow",
87
+ "Concepts": "lightpink"
88
+ },
89
+ 'pt': {
90
+ "Pessoas": "lightblue",
91
+ "Lugares": "lightcoral",
92
+ "Invenções": "lightgreen",
93
+ "Datas": "lightyellow",
94
+ "Conceitos": "lightpink"
95
+ }
96
+ }
97
+
98
+ #################
99
+
100
+ def fig_to_bytes(fig, dpi=100):
101
+ """Convierte una figura de matplotlib a bytes."""
102
+ try:
103
+ buf = io.BytesIO()
104
+ fig.savefig(buf, format='png', dpi=dpi, bbox_inches='tight') # Sin compression
105
+ buf.seek(0)
106
+ return buf.getvalue()
107
+ except Exception as e:
108
+ logger.error(f"Error en fig_to_bytes: {str(e)}")
109
+ return None
110
+
111
+ #################
112
+ def compare_semantic_analysis(text1, text2, nlp, lang):
113
+ """
114
+ Realiza el análisis semántico comparativo entre dos textos
115
+ """
116
+ try:
117
+ # Diccionario de traducciones para los títulos de los gráficos COMPARATIVOS
118
+ COMPARE_GRAPH_TITLES = {
119
+ 'es': {
120
+ 'doc1_network': 'Relaciones entre conceptos clave del documento 1',
121
+ 'doc1_centrality': 'Centralidad de los conceptos clave del documento 1',
122
+ 'doc2_network': 'Relaciones entre conceptos clave del documento 2',
123
+ 'doc2_centrality': 'Centralidad de los conceptos clave del documento 2'
124
+ },
125
+ 'en': {
126
+ 'doc1_network': 'Key concept relationships in document 1',
127
+ 'doc1_centrality': 'Key concept centrality in document 1',
128
+ 'doc2_network': 'Key concept relationships in document 2',
129
+ 'doc2_centrality': 'Key concept centrality in document 2'
130
+ },
131
+ 'fr': {
132
+ 'doc1_network': 'Relations entre concepts clés du document 1',
133
+ 'doc1_centrality': 'Centralité des concepts clés du document 1',
134
+ 'doc2_network': 'Relations entre concepts clés du document 2',
135
+ 'doc2_centrality': 'Centralité des concepts clés du document 2'
136
+ },
137
+ 'pt': {
138
+ 'doc1_network': 'Relações entre conceitos-chave do documento 1',
139
+ 'doc1_centrality': 'Centralidade dos conceitos-chave do documento 1',
140
+ 'doc2_network': 'Relações entre conceitos-chave do documento 2',
141
+ 'doc2_centrality': 'Centralidade dos conceitos-chave do documento 2'
142
+ }
143
+ }
144
+
145
+ # Obtener traducciones (inglés por defecto)
146
+ titles = COMPARE_GRAPH_TITLES.get(lang, COMPARE_GRAPH_TITLES['en'])
147
+
148
+ logger.info(f"Iniciando análisis comparativo para idioma: {lang}")
149
+
150
+ # Resto del código permanece exactamente igual...
151
+ stopwords = get_custom_stopwords(lang)
152
+ logger.info(f"Obtenidas {len(stopwords)} stopwords para el idioma {lang}")
153
+
154
+ doc1 = nlp(text1)
155
+ doc2 = nlp(text2)
156
+
157
+ key_concepts1 = identify_key_concepts(doc1, stopwords=stopwords, min_freq=2, min_length=3)
158
+ key_concepts2 = identify_key_concepts(doc2, stopwords=stopwords, min_freq=2, min_length=3)
159
+
160
+ if not key_concepts1 or not key_concepts2:
161
+ raise ValueError("No se pudieron identificar conceptos clave en uno o ambos textos")
162
+
163
+ G1 = create_concept_graph(doc1, key_concepts1)
164
+ G2 = create_concept_graph(doc2, key_concepts2)
165
+
166
+ # Primer grafo con título traducido
167
+ plt.figure(figsize=(12, 8))
168
+ fig1 = visualize_concept_graph(G1, lang)
169
+ plt.title(titles['doc1_network'], pad=20)
170
+ plt.tight_layout()
171
+
172
+ # Segundo grafo con título traducido
173
+ plt.figure(figsize=(12, 8))
174
+ fig2 = visualize_concept_graph(G2, lang)
175
+ plt.title(titles['doc2_network'], pad=20)
176
+ plt.tight_layout()
177
+
178
+ return fig1, fig2, key_concepts1, key_concepts2
179
+
180
+ except Exception as e:
181
+ logger.error(f"Error en compare_semantic_analysis: {str(e)}")
182
+ plt.close('all')
183
+ raise
184
+ finally:
185
+ plt.close('all')
186
+
187
+
188
+ ############################################
189
+ def create_concept_table(key_concepts):
190
+ """
191
+ Crea una tabla de conceptos clave con sus frecuencias
192
+ Args:
193
+ key_concepts: Lista de tuplas (concepto, frecuencia)
194
+ Returns:
195
+ pandas.DataFrame: Tabla formateada de conceptos
196
+ """
197
+ try:
198
+ if not key_concepts:
199
+ logger.warning("Lista de conceptos vacía")
200
+ return pd.DataFrame(columns=['Concepto', 'Frecuencia'])
201
+
202
+ df = pd.DataFrame(key_concepts, columns=['Concepto', 'Frecuencia'])
203
+ df['Frecuencia'] = df['Frecuencia'].round(2)
204
+ return df
205
+ except Exception as e:
206
+ logger.error(f"Error en create_concept_table: {str(e)}")
207
+ return pd.DataFrame(columns=['Concepto', 'Frecuencia'])
208
+
209
+
210
+ ##########################################################
211
+
212
+ def perform_discourse_analysis(text1, text2, nlp, lang):
213
+ """
214
+ Realiza el análisis completo del discurso
215
+ Args:
216
+ text1: Primer texto a analizar
217
+ text2: Segundo texto a analizar
218
+ nlp: Modelo de spaCy cargado
219
+ lang: Código de idioma
220
+ Returns:
221
+ dict: Resultados del análisis con gráficos convertidos a bytes
222
+ """
223
+ try:
224
+ logger.info("Iniciando análisis del discurso...")
225
+
226
+ # Verificar inputs
227
+ if not text1 or not text2:
228
+ raise ValueError("Los textos de entrada no pueden estar vacíos")
229
+
230
+ if not nlp:
231
+ raise ValueError("Modelo de lenguaje no inicializado")
232
+
233
+ # Realizar análisis comparativo
234
+ fig1, fig2, key_concepts1, key_concepts2 = compare_semantic_analysis(
235
+ text1, text2, nlp, lang
236
+ )
237
+
238
+ logger.info("Análisis comparativo completado, convirtiendo figuras a bytes...")
239
+
240
+ # Convertir figuras a bytes para almacenamiento
241
+ graph1_bytes = fig_to_bytes(fig1)
242
+ graph2_bytes = fig_to_bytes(fig2)
243
+
244
+ logger.info(f"Figura 1 convertida a {len(graph1_bytes) if graph1_bytes else 0} bytes")
245
+ logger.info(f"Figura 2 convertida a {len(graph2_bytes) if graph2_bytes else 0} bytes")
246
+
247
+ # Verificar que las conversiones fueron exitosas antes de continuar
248
+ if not graph1_bytes or not graph2_bytes:
249
+ logger.error("Error al convertir figuras a bytes - obteniendo 0 bytes")
250
+ # Opción 1: Devolver error
251
+ raise ValueError("No se pudieron convertir las figuras a bytes")
252
+
253
+ # Crear tablas de resultados
254
+ table1 = create_concept_table(key_concepts1)
255
+ table2 = create_concept_table(key_concepts2)
256
+
257
+ # Cerrar figuras para liberar memoria
258
+ plt.close(fig1)
259
+ plt.close(fig2)
260
+
261
+ result = {
262
+ 'graph1': graph1_bytes, # Bytes en lugar de figura
263
+ 'graph2': graph2_bytes, # Bytes en lugar de figura
264
+ 'combined_graph': None, # No hay gráfico combinado por ahora
265
+ 'key_concepts1': key_concepts1,
266
+ 'key_concepts2': key_concepts2,
267
+ 'table1': table1,
268
+ 'table2': table2,
269
+ 'success': True
270
+ }
271
+
272
+ logger.info("Análisis del discurso completado y listo para almacenamiento")
273
+ return result
274
+
275
+ except Exception as e:
276
+ logger.error(f"Error en perform_discourse_analysis: {str(e)}")
277
+ # Asegurar limpieza de recursos
278
+ plt.close('all')
279
+ return {
280
+ 'success': False,
281
+ 'error': str(e)
282
+ }
283
+ finally:
284
+ # Asegurar limpieza en todos los casos
285
+ plt.close('all')
286
+
287
  #################################################################