bertrend 0.3.1__py3-none-any.whl

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.
Files changed (181) hide show
  1. bertrend/BERTopicModel.py +320 -0
  2. bertrend/BERTrend.py +1211 -0
  3. bertrend/__init__.py +59 -0
  4. bertrend/config/bertopic_default_config.toml +48 -0
  5. bertrend/config/bertrend_default_config.toml +16 -0
  6. bertrend/config/parameters.py +92 -0
  7. bertrend/config/services_default_config.toml +32 -0
  8. bertrend/demos/__init__.py +4 -0
  9. bertrend/demos/demos_utils/__init__.py +13 -0
  10. bertrend/demos/demos_utils/data_loading_component.py +267 -0
  11. bertrend/demos/demos_utils/embed_documents_component.py +52 -0
  12. bertrend/demos/demos_utils/icons.py +41 -0
  13. bertrend/demos/demos_utils/messages.py +11 -0
  14. bertrend/demos/demos_utils/parameters_component.py +162 -0
  15. bertrend/demos/demos_utils/state_utils.py +99 -0
  16. bertrend/demos/start_summarizer_app.sh +9 -0
  17. bertrend/demos/start_topic_analysis_app.sh +9 -0
  18. bertrend/demos/start_weak_signals_app.sh +9 -0
  19. bertrend/demos/summarization/__init__.py +4 -0
  20. bertrend/demos/summarization/summarizer_app.py +98 -0
  21. bertrend/demos/topic_analysis/__init__.py +4 -0
  22. bertrend/demos/topic_analysis/app.py +87 -0
  23. bertrend/demos/topic_analysis/app_utils.py +69 -0
  24. bertrend/demos/topic_analysis/demo_pages/__init__.py +4 -0
  25. bertrend/demos/topic_analysis/demo_pages/explore_topics.py +455 -0
  26. bertrend/demos/topic_analysis/demo_pages/newsletters_generation.py +178 -0
  27. bertrend/demos/topic_analysis/demo_pages/temporal_visualizations.py +573 -0
  28. bertrend/demos/topic_analysis/demo_pages/topic_visualizations.py +280 -0
  29. bertrend/demos/topic_analysis/demo_pages/training_page.py +219 -0
  30. bertrend/demos/topic_analysis/messages.py +13 -0
  31. bertrend/demos/weak_signals/__init__.py +4 -0
  32. bertrend/demos/weak_signals/app.py +510 -0
  33. bertrend/demos/weak_signals/messages.py +32 -0
  34. bertrend/demos/weak_signals/visualizations_utils.py +403 -0
  35. bertrend/llm_utils/__init__.py +4 -0
  36. bertrend/llm_utils/newsletter.css +131 -0
  37. bertrend/llm_utils/newsletter_features.py +272 -0
  38. bertrend/llm_utils/openai_client.py +235 -0
  39. bertrend/llm_utils/prompts.py +94 -0
  40. bertrend/metrics/__init__.py +4 -0
  41. bertrend/metrics/temporal_metrics_embedding.py +861 -0
  42. bertrend/metrics/topic_metrics.py +199 -0
  43. bertrend/resources/common_ngrams.json +23 -0
  44. bertrend/resources/stopwords-en.json +3 -0
  45. bertrend/resources/stopwords-fr.json +1 -0
  46. bertrend/resources/stopwords-rte.json +15 -0
  47. bertrend/services/__init__.py +4 -0
  48. bertrend/services/authentication.py +66 -0
  49. bertrend/services/embedding_client.py +154 -0
  50. bertrend/services/embedding_service.py +380 -0
  51. bertrend/services/summarizer.py +45 -0
  52. bertrend/services/summary/__init__.py +4 -0
  53. bertrend/services/summary/abstractive_summarizer.py +74 -0
  54. bertrend/services/summary/chatgpt_summarizer.py +67 -0
  55. bertrend/services/summary/extractive_summarizer.py +368 -0
  56. bertrend/services/summary/lexrank.py +126 -0
  57. bertrend/services/summary/prompts.py +43 -0
  58. bertrend/tests/__init__.py +4 -0
  59. bertrend/tests/test_abstractive_summarizer.py +140 -0
  60. bertrend/tests/test_bertrend.py +1091 -0
  61. bertrend/tests/test_cache_utils.py +111 -0
  62. bertrend/tests/test_chatgpt_summarizer.py +150 -0
  63. bertrend/tests/test_config_utils.py +54 -0
  64. bertrend/tests/test_data_loading.py +326 -0
  65. bertrend/tests/test_data_structure.py +53 -0
  66. bertrend/tests/test_embedding_client.py +546 -0
  67. bertrend/tests/test_extractive_summarizer.py +250 -0
  68. bertrend/tests/test_installation.py +183 -0
  69. bertrend/tests/test_newsletter_features.py +354 -0
  70. bertrend/tests/test_openai_client.py +210 -0
  71. bertrend/tests/test_representative_docs.py +125 -0
  72. bertrend/tests/test_summarizer.py +115 -0
  73. bertrend/tests/test_temporal_metrics_embedding.py +349 -0
  74. bertrend/tests/test_topic_description.py +159 -0
  75. bertrend/tests/test_topic_metrics.py +233 -0
  76. bertrend/tests/test_topic_model.py +285 -0
  77. bertrend/tests/test_trend_analysis_data_structure.py +174 -0
  78. bertrend/tests/test_trend_analysis_prompts.py +225 -0
  79. bertrend/tests/test_trend_analysis_visualizations.py +307 -0
  80. bertrend/tests/test_trend_analysis_weak_signals.py +598 -0
  81. bertrend/tests/test_utils_init.py +70 -0
  82. bertrend/tests/test_visualizations.py +306 -0
  83. bertrend/topic_analysis/__init__.py +4 -0
  84. bertrend/topic_analysis/data_structure.py +13 -0
  85. bertrend/topic_analysis/prompts.py +42 -0
  86. bertrend/topic_analysis/representative_docs.py +57 -0
  87. bertrend/topic_analysis/topic_description.py +66 -0
  88. bertrend/topic_analysis/visualizations.py +315 -0
  89. bertrend/trend_analysis/__init__.py +4 -0
  90. bertrend/trend_analysis/data_structure.py +78 -0
  91. bertrend/trend_analysis/prompts.py +229 -0
  92. bertrend/trend_analysis/signal_llm_template_en.html +181 -0
  93. bertrend/trend_analysis/signal_llm_template_fr.html +181 -0
  94. bertrend/trend_analysis/visualizations.py +605 -0
  95. bertrend/trend_analysis/weak_signals.py +425 -0
  96. bertrend/utils/__init__.py +31 -0
  97. bertrend/utils/cache_utils.py +31 -0
  98. bertrend/utils/config_utils.py +41 -0
  99. bertrend/utils/data_loading.py +329 -0
  100. bertrend-0.3.1.dist-info/AUTHORS.txt +15 -0
  101. bertrend-0.3.1.dist-info/LICENSE.md +373 -0
  102. bertrend-0.3.1.dist-info/METADATA +400 -0
  103. bertrend-0.3.1.dist-info/RECORD +181 -0
  104. bertrend-0.3.1.dist-info/WHEEL +4 -0
  105. bertrend_apps/__init__.py +4 -0
  106. bertrend_apps/common/__init__.py +4 -0
  107. bertrend_apps/common/crontab_utils.py +113 -0
  108. bertrend_apps/common/mail_utils.py +91 -0
  109. bertrend_apps/config/feeds/arxiv_feed.toml +15 -0
  110. bertrend_apps/config/feeds/decarbonation_feed.toml +15 -0
  111. bertrend_apps/config/feeds/llm_en_feed.toml +15 -0
  112. bertrend_apps/config/feeds/llm_feed.toml +16 -0
  113. bertrend_apps/config/feeds/nlp_feed.toml +15 -0
  114. bertrend_apps/config/feeds/rte_feed.toml +15 -0
  115. bertrend_apps/config/gmail_credentials.json +1 -0
  116. bertrend_apps/config/newsletters/arxiv_newsletter_from_last.toml +39 -0
  117. bertrend_apps/config/newsletters/arxiv_newsletter_from_scratch.toml +39 -0
  118. bertrend_apps/config/newsletters/decarbonation_newsletter_from_last.toml +38 -0
  119. bertrend_apps/config/newsletters/decarbonation_newsletter_from_scratch.toml +38 -0
  120. bertrend_apps/config/newsletters/llm_en_newsletter_from_last.toml +38 -0
  121. bertrend_apps/config/newsletters/llm_newsletter_from_last.toml +39 -0
  122. bertrend_apps/config/newsletters/nlp_newsletter_from_last.toml +38 -0
  123. bertrend_apps/config/newsletters/rte_newsletter_from_last.toml +38 -0
  124. bertrend_apps/config/newsletters/rte_newsletter_from_scratch.toml +38 -0
  125. bertrend_apps/data_provider/__init__.py +9 -0
  126. bertrend_apps/data_provider/__main__.py +237 -0
  127. bertrend_apps/data_provider/arxiv_provider.py +161 -0
  128. bertrend_apps/data_provider/bing_news_provider.py +82 -0
  129. bertrend_apps/data_provider/curebot_provider.py +105 -0
  130. bertrend_apps/data_provider/data_provider.py +194 -0
  131. bertrend_apps/data_provider/google_news_provider.py +94 -0
  132. bertrend_apps/data_provider/newscatcher_provider.py +74 -0
  133. bertrend_apps/data_provider/utils.py +165 -0
  134. bertrend_apps/exploration/__init__.py +4 -0
  135. bertrend_apps/exploration/curebot/__init__.py +4 -0
  136. bertrend_apps/exploration/curebot/app.py +45 -0
  137. bertrend_apps/exploration/curebot/app_utils.py +317 -0
  138. bertrend_apps/exploration/curebot/config.toml +33 -0
  139. bertrend_apps/exploration/curebot/import_extraction_article.ipynb +108 -0
  140. bertrend_apps/exploration/curebot/old_veille_analyse.py +298 -0
  141. bertrend_apps/exploration/curebot/prompts.py +24 -0
  142. bertrend_apps/exploration/curebot/start_newsletter_generator.sh +8 -0
  143. bertrend_apps/exploration/curebot/tabs/tab1.py +150 -0
  144. bertrend_apps/exploration/curebot/tabs/tab2.py +95 -0
  145. bertrend_apps/exploration/curebot/tabs/tab3.py +125 -0
  146. bertrend_apps/exploration/dataviz/Main_page.py +10 -0
  147. bertrend_apps/exploration/dataviz/__init__.py +4 -0
  148. bertrend_apps/exploration/dataviz/pages/1_Data_feeds.py +105 -0
  149. bertrend_apps/exploration/geolocalization/__init__.py +4 -0
  150. bertrend_apps/exploration/geolocalization/entity_utils.py +118 -0
  151. bertrend_apps/exploration/geolocalization/spacy/__init__.py +4 -0
  152. bertrend_apps/exploration/geolocalization/spacy/common_factory.py +142 -0
  153. bertrend_apps/exploration/geolocalization/spacy/dbpedia.py +206 -0
  154. bertrend_apps/exploration/geolocalization/spacy/nlp.config +37 -0
  155. bertrend_apps/exploration/geolocalization/spacy/utils.py +39 -0
  156. bertrend_apps/install_feeds_and_newsletters.py +29 -0
  157. bertrend_apps/newsletters/__init__.py +4 -0
  158. bertrend_apps/newsletters/__main__.py +277 -0
  159. bertrend_apps/prospective_demo/.streamlit/config.toml +3 -0
  160. bertrend_apps/prospective_demo/.streamlit/secrets.toml +9 -0
  161. bertrend_apps/prospective_demo/__init__.py +70 -0
  162. bertrend_apps/prospective_demo/app.py +111 -0
  163. bertrend_apps/prospective_demo/authentication.py +44 -0
  164. bertrend_apps/prospective_demo/dashboard_analysis.py +132 -0
  165. bertrend_apps/prospective_demo/dashboard_common.py +79 -0
  166. bertrend_apps/prospective_demo/dashboard_signals.py +226 -0
  167. bertrend_apps/prospective_demo/feeds_common.py +38 -0
  168. bertrend_apps/prospective_demo/feeds_config.py +261 -0
  169. bertrend_apps/prospective_demo/feeds_data.py +95 -0
  170. bertrend_apps/prospective_demo/i18n.py +136 -0
  171. bertrend_apps/prospective_demo/llm_utils.py +34 -0
  172. bertrend_apps/prospective_demo/models_info.py +361 -0
  173. bertrend_apps/prospective_demo/perf_utils.py +23 -0
  174. bertrend_apps/prospective_demo/process_new_data.py +348 -0
  175. bertrend_apps/prospective_demo/report_generation.py +214 -0
  176. bertrend_apps/prospective_demo/report_template.html +148 -0
  177. bertrend_apps/prospective_demo/report_utils.py +132 -0
  178. bertrend_apps/prospective_demo/start_demo.sh +9 -0
  179. bertrend_apps/prospective_demo/streamlit_utils.py +37 -0
  180. bertrend_apps/start_apps.sh +15 -0
  181. bertrend_apps/stop_apps.sh +11 -0
@@ -0,0 +1,573 @@
1
+ # Copyright (c) 2024, RTE (https://www.rte-france.com)
2
+ # See AUTHORS.txt
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ # This file is part of BERTrend.
5
+
6
+ from datetime import timedelta
7
+
8
+ import pandas as pd
9
+ import streamlit as st
10
+
11
+ from bertrend.demos.demos_utils.icons import ERROR_ICON, INFO_ICON
12
+ from bertrend.demos.topic_analysis.messages import (
13
+ TRAIN_MODEL_FIRST_ERROR,
14
+ REMOTE_EMBEDDING_SEVICE_TYPE_NOT_SUPPORTED_ERROR,
15
+ )
16
+ from bertrend.metrics.temporal_metrics_embedding import TempTopic
17
+ from bertrend.demos.topic_analysis.app_utils import (
18
+ compute_topics_over_time,
19
+ )
20
+ from bertrend.topic_analysis.visualizations import (
21
+ plot_topics_over_time,
22
+ plot_topic_evolution,
23
+ plot_temporal_stability_metrics,
24
+ plot_overall_topic_stability,
25
+ )
26
+ from bertrend.demos.demos_utils.state_utils import (
27
+ register_widget,
28
+ save_widget_state,
29
+ restore_widget_state,
30
+ register_multiple_widget,
31
+ SessionStateManager,
32
+ )
33
+ from bertrend.demos.weak_signals.visualizations_utils import PLOTLY_BUTTON_SAVE_CONFIG
34
+ from bertrend.utils.data_loading import (
35
+ TIMESTAMP_COLUMN,
36
+ TEXT_COLUMN,
37
+ DOCUMENT_ID_COLUMN,
38
+ )
39
+
40
+
41
+ def check_model_trained():
42
+ """Check if a model is trained and display an error if not."""
43
+ if "topic_model" not in st.session_state:
44
+ st.error(
45
+ TRAIN_MODEL_FIRST_ERROR,
46
+ icon=ERROR_ICON,
47
+ )
48
+ st.stop()
49
+
50
+
51
+ def check_embedding_type():
52
+ """Check the embedding service type. Remote embedding service currently not support these visualizations."""
53
+ if SessionStateManager.get("embedding_service_type", "remote") != "local":
54
+ st.error(
55
+ REMOTE_EMBEDDING_SEVICE_TYPE_NOT_SUPPORTED_ERROR,
56
+ icon=ERROR_ICON,
57
+ )
58
+ st.stop()
59
+
60
+
61
+ def _parameters_changed():
62
+ """Check if any of the parameters have changed."""
63
+ params_to_check = [
64
+ "window_size",
65
+ "k",
66
+ "alpha",
67
+ "double_agg",
68
+ "doc_agg",
69
+ "global_agg",
70
+ "evolution_tuning",
71
+ "global_tuning",
72
+ "granularity",
73
+ ]
74
+ return any(
75
+ st.session_state.get(f"prev_{param}") != st.session_state.get(param)
76
+ for param in params_to_check
77
+ )
78
+
79
+
80
+ def display_sidebar():
81
+ """Display the sidebar with TEMPTopic parameters."""
82
+ with st.sidebar:
83
+ register_multiple_widget(
84
+ "window_size",
85
+ "k",
86
+ "alpha",
87
+ "double_agg",
88
+ "doc_agg",
89
+ "global_agg",
90
+ "evolution_tuning",
91
+ "global_tuning",
92
+ )
93
+ with st.expander("TEMPTopic Parameters", expanded=False):
94
+ st.number_input(
95
+ "Window Size",
96
+ min_value=2,
97
+ value=2,
98
+ step=1,
99
+ key="window_size",
100
+ on_change=save_widget_state,
101
+ )
102
+ st.number_input(
103
+ "Number of Nearest Embeddings (k)",
104
+ min_value=1,
105
+ value=1,
106
+ step=1,
107
+ key="k",
108
+ on_change=save_widget_state,
109
+ help="The k-th nearest neighbor used for Topic Representation Stability calculation.",
110
+ )
111
+ st.number_input(
112
+ "Alpha (Topic vs Representation Stability Weight)",
113
+ min_value=0.0,
114
+ max_value=1.0,
115
+ value=0.8,
116
+ step=0.01,
117
+ key="alpha",
118
+ help="Closer to 1 gives more weight given to Topic Embedding Stability, Closer to 0 gives more weight to topic representation stability.",
119
+ on_change=save_widget_state,
120
+ )
121
+ st.checkbox(
122
+ "Use Double Aggregation",
123
+ value=True,
124
+ key="double_agg",
125
+ on_change=save_widget_state,
126
+ help="If unchecked, only Document Aggregation Method will be globally used.",
127
+ )
128
+ st.selectbox(
129
+ "Document Aggregation Method",
130
+ ["mean", "max"],
131
+ key="doc_agg",
132
+ on_change=save_widget_state,
133
+ )
134
+ st.selectbox(
135
+ "Global Aggregation Method",
136
+ ["max", "mean"],
137
+ key="global_agg",
138
+ on_change=save_widget_state,
139
+ )
140
+ st.checkbox(
141
+ "Use Evolution Tuning",
142
+ value=True,
143
+ key="evolution_tuning",
144
+ on_change=save_widget_state,
145
+ )
146
+ st.checkbox(
147
+ "Use Global Tuning",
148
+ value=False,
149
+ key="global_tuning",
150
+ on_change=save_widget_state,
151
+ )
152
+
153
+
154
+ def _format_timedelta(td: timedelta) -> str:
155
+ """Format a timedelta object into a string with days, hours, minutes, and seconds."""
156
+ days = td.days
157
+ hours, remainder = divmod(td.seconds, 3600)
158
+ minutes, seconds = divmod(remainder, 60)
159
+ parts = []
160
+ if days > 0:
161
+ parts.append(f"{days} day{'s' if days > 1 else ''}")
162
+ if hours > 0:
163
+ parts.append(f"{hours} hour{'s' if hours > 1 else ''}")
164
+ if minutes > 0:
165
+ parts.append(f"{minutes} minute{'s' if minutes > 1 else ''}")
166
+ if (
167
+ seconds > 0 or not parts
168
+ ): # Always show seconds if no larger units or if it's the only non-zero unit
169
+ parts.append(f"{seconds} second{'s' if seconds > 1 else ''}")
170
+ return " ".join(parts)
171
+
172
+
173
+ def select_time_granularity(max_granularity: timedelta) -> timedelta:
174
+ """Allow user to select custom time granularity within limits."""
175
+ col0, col1, col2, col3, col4 = st.columns(5)
176
+
177
+ max_days = max_granularity.days
178
+ register_multiple_widget(
179
+ "granularity_days", "granularity_hours", "granularity_minutes"
180
+ )
181
+ with col0:
182
+ st.write("Select custom time granularity")
183
+ with col1:
184
+ days = st.slider(
185
+ "Days",
186
+ min_value=0,
187
+ max_value=max_days,
188
+ value=min(1, max_days),
189
+ key="granularity_days",
190
+ on_change=save_widget_state,
191
+ )
192
+ with col2:
193
+ hours = st.slider(
194
+ "Hours",
195
+ min_value=0,
196
+ max_value=23,
197
+ value=0,
198
+ key="granularity_hours",
199
+ on_change=save_widget_state,
200
+ )
201
+ with col3:
202
+ minutes = st.slider(
203
+ "Minutes",
204
+ min_value=0,
205
+ max_value=59,
206
+ value=0,
207
+ key="granularity_minutes",
208
+ on_change=save_widget_state,
209
+ )
210
+ with col4:
211
+ seconds = st.slider(
212
+ "Seconds",
213
+ min_value=0,
214
+ max_value=59,
215
+ value=0,
216
+ key="granularity_seconds",
217
+ on_change=save_widget_state,
218
+ )
219
+
220
+ selected_granularity = timedelta(
221
+ days=days, hours=hours, minutes=minutes, seconds=seconds
222
+ )
223
+ formatted_max = _format_timedelta(max_granularity)
224
+
225
+ st.info(
226
+ f"Granularity must be greater than zero and less than or equal to {formatted_max}.",
227
+ icon=INFO_ICON,
228
+ )
229
+
230
+ if (
231
+ selected_granularity.total_seconds() == 0
232
+ or selected_granularity > max_granularity
233
+ ):
234
+ st.stop()
235
+
236
+ return pd.Timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
237
+
238
+
239
+ def _calculate_max_granularity(df: pd.DataFrame) -> timedelta:
240
+ """Calculate the maximum allowed granularity based on the timestamp range."""
241
+ max_timestamp, min_timestamp = (
242
+ df[TIMESTAMP_COLUMN].max(),
243
+ df[TIMESTAMP_COLUMN].min(),
244
+ )
245
+ time_range = max_timestamp - min_timestamp
246
+ max_granularity = time_range / 2
247
+ return max_granularity
248
+
249
+
250
+ def _group_timestamps(timestamps: pd.DataFrame, granularity: timedelta):
251
+ """Group timestamps based on a custom granularity."""
252
+ # Find the minimum timestamp
253
+ min_timestamp = timestamps.min()
254
+
255
+ # Calculate the difference from the minimum timestamp
256
+ diff = timestamps - min_timestamp
257
+
258
+ # Integer divide the difference by the granularity
259
+ groups = diff // granularity
260
+
261
+ # Calculate the new timestamps
262
+ return min_timestamp + groups * granularity
263
+
264
+
265
+ def process_data_and_fit_temptopic(time_granularity: timedelta):
266
+ """Process data and fit TempTopic with custom time granularity."""
267
+ df = st.session_state["time_filtered_df"].copy()
268
+ df[TIMESTAMP_COLUMN] = pd.to_datetime(df[TIMESTAMP_COLUMN])
269
+
270
+ # Convert time_granularity to timedelta if it's not already
271
+ if not isinstance(time_granularity, timedelta):
272
+ time_granularity = pd.Timedelta(time_granularity)
273
+
274
+ # Group timestamps using the custom function
275
+ df["grouped_timestamp"] = _group_timestamps(df[TIMESTAMP_COLUMN], time_granularity)
276
+
277
+ aggregated_df = (
278
+ df.groupby("grouped_timestamp")
279
+ .agg({TEXT_COLUMN: list, DOCUMENT_ID_COLUMN: list})
280
+ .reset_index()
281
+ )
282
+ indices = st.session_state["time_filtered_df"][DOCUMENT_ID_COLUMN]
283
+ docs = [st.session_state["split_df"][TEXT_COLUMN][i] for i in indices]
284
+
285
+ index_to_timestamp = {
286
+ idx: timestamp
287
+ for timestamp, idx_sublist in zip(
288
+ aggregated_df["grouped_timestamp"], aggregated_df[DOCUMENT_ID_COLUMN]
289
+ )
290
+ for idx in idx_sublist
291
+ }
292
+ timestamps_repeated = [
293
+ index_to_timestamp[idx].strftime("%Y-%m-%d %H:%M:%S") for idx in indices
294
+ ]
295
+
296
+ # Initialize and fit TempTopic
297
+ with st.spinner("Fitting TempTopic..."):
298
+ temptopic = TempTopic(
299
+ st.session_state["topic_model"],
300
+ docs,
301
+ st.session_state["embeddings"],
302
+ st.session_state["token_embeddings"],
303
+ st.session_state["token_strings"],
304
+ timestamps_repeated,
305
+ evolution_tuning=st.session_state.evolution_tuning,
306
+ global_tuning=st.session_state.global_tuning,
307
+ )
308
+ temptopic.fit(
309
+ window_size=st.session_state.window_size,
310
+ k=st.session_state.k,
311
+ double_agg=st.session_state.double_agg,
312
+ doc_agg=st.session_state.doc_agg,
313
+ global_agg=st.session_state.global_agg,
314
+ )
315
+
316
+ # Store the fitted TempTopic object and current parameter values
317
+ st.session_state.temptopic = temptopic
318
+ st.session_state.aggregated_df = aggregated_df
319
+ for param in [
320
+ "window_size",
321
+ "k",
322
+ "alpha",
323
+ "double_agg",
324
+ "doc_agg",
325
+ "global_agg",
326
+ "evolution_tuning",
327
+ "global_tuning",
328
+ "granularity",
329
+ ]:
330
+ st.session_state[f"prev_{param}"] = st.session_state.get(param)
331
+
332
+
333
+ def display_topic_evolution_dataframe():
334
+ """Display the Topic Evolution Dataframe."""
335
+ with st.expander("Topic Evolution Dataframe"):
336
+ columns_to_display = ["Topic", "Words", "Frequency", "Timestamp"]
337
+ columns_present = [
338
+ col
339
+ for col in columns_to_display
340
+ if col in st.session_state.temptopic.final_df.columns
341
+ ]
342
+ st.dataframe(
343
+ st.session_state.temptopic.final_df[columns_present].sort_values(
344
+ by=["Topic", "Timestamp"], ascending=[True, True]
345
+ ),
346
+ use_container_width=True,
347
+ )
348
+
349
+
350
+ def display_topic_info_dataframe():
351
+ """Display the Topic Info Dataframe."""
352
+ with st.expander("Topic Info Dataframe"):
353
+ st.dataframe(
354
+ st.session_state.temptopic.topic_model.get_topic_info(),
355
+ use_container_width=True,
356
+ )
357
+
358
+
359
+ def display_documents_per_date_dataframe():
360
+ """Display the Documents per Date Dataframe."""
361
+ with st.expander("Documents per Date Dataframe"):
362
+ st.dataframe(st.session_state.aggregated_df, use_container_width=True)
363
+
364
+
365
+ def display_temptopic_visualizations():
366
+ """Display TempTopic Visualizations."""
367
+ with st.expander("TempTopic Visualizations"):
368
+ # Create a list of topics with their representations
369
+ topic_options = sorted(
370
+ [
371
+ (
372
+ topic,
373
+ ", ".join(
374
+ st.session_state.temptopic.final_df[
375
+ st.session_state.temptopic.final_df["Topic"] == topic
376
+ ]["Words"]
377
+ .iloc[0]
378
+ .split(", ")[:3]
379
+ ),
380
+ )
381
+ for topic in st.session_state.temptopic.final_df["Topic"].unique()
382
+ ]
383
+ )
384
+
385
+ # Create a dictionary for streamlit multiselect
386
+ topic_dict = {f"Topic {topic}: {repr}": topic for topic, repr in topic_options}
387
+
388
+ selected_topics = st.multiselect(
389
+ "Topics to Show",
390
+ options=list(topic_dict.keys()),
391
+ format_func=lambda x: x,
392
+ default=None,
393
+ )
394
+
395
+ # Convert selected topics back to topic numbers
396
+ topics_to_show = [topic_dict[topic] for topic in selected_topics]
397
+
398
+ display_topic_evolution(topics_to_show)
399
+ display_overall_topic_stability(topics_to_show)
400
+ display_temporal_stability_metrics(topics_to_show)
401
+
402
+
403
+ def display_topic_evolution(topics_to_show):
404
+ """Display Topic Evolution in Time and Semantic Space."""
405
+ st.header("Topic Evolution in Time and Semantic Space")
406
+ n_neighbors = st.slider(
407
+ "UMAP n_neighbors", min_value=2, max_value=100, value=15, step=1
408
+ )
409
+ min_dist = st.slider(
410
+ "UMAP min_dist", min_value=0.0, max_value=0.99, value=0.1, step=0.01
411
+ )
412
+ metric = st.selectbox("UMAP Metric", ["cosine", "euclidean", "manhattan"])
413
+ color_palette = st.selectbox("Color Palette", ["Plotly", "D3", "Alphabet"])
414
+
415
+ fig_topic_evolution = plot_topic_evolution(
416
+ st.session_state.temptopic,
417
+ granularity=st.session_state.granularity,
418
+ topics_to_show=topics_to_show,
419
+ n_neighbors=n_neighbors,
420
+ min_dist=min_dist,
421
+ metric=metric,
422
+ color_palette=color_palette,
423
+ )
424
+ st.plotly_chart(
425
+ fig_topic_evolution, config=PLOTLY_BUTTON_SAVE_CONFIG, use_container_width=True
426
+ )
427
+
428
+ st.divider()
429
+
430
+
431
+ def display_overall_topic_stability(topics_to_show):
432
+ """Display Overall Topic Stability."""
433
+ st.header("Overall Topic Stability")
434
+ normalize_overall_stability = st.checkbox("Normalize", value=False)
435
+ overall_stability_df = st.session_state.temptopic.calculate_overall_topic_stability(
436
+ window_size=st.session_state.window_size,
437
+ k=st.session_state.k,
438
+ alpha=st.session_state.alpha,
439
+ )
440
+ fig_overall_stability = plot_overall_topic_stability(
441
+ st.session_state.temptopic,
442
+ topics_to_show=topics_to_show,
443
+ normalize=normalize_overall_stability,
444
+ darkmode=True,
445
+ )
446
+ st.plotly_chart(
447
+ fig_overall_stability,
448
+ config=PLOTLY_BUTTON_SAVE_CONFIG,
449
+ use_container_width=True,
450
+ )
451
+
452
+ st.divider()
453
+
454
+
455
+ def display_temporal_stability_metrics(topics_to_show):
456
+ """Display Temporal Stability Metrics."""
457
+ st.header("Temporal Stability Metrics")
458
+
459
+ fig_topic_stability = plot_temporal_stability_metrics(
460
+ st.session_state.temptopic,
461
+ metric="topic_stability",
462
+ topics_to_show=topics_to_show,
463
+ )
464
+ st.plotly_chart(
465
+ fig_topic_stability, config=PLOTLY_BUTTON_SAVE_CONFIG, use_container_width=True
466
+ )
467
+
468
+ fig_representation_stability = plot_temporal_stability_metrics(
469
+ st.session_state.temptopic,
470
+ metric="representation_stability",
471
+ topics_to_show=topics_to_show,
472
+ )
473
+ st.plotly_chart(
474
+ fig_representation_stability,
475
+ config=PLOTLY_BUTTON_SAVE_CONFIG,
476
+ use_container_width=True,
477
+ )
478
+
479
+
480
+ def display_topics_popularity():
481
+ """Display the popularity of topics over time."""
482
+ with st.spinner("Computing topics over time..."):
483
+ with st.expander("Popularity of topics over time"):
484
+ if TIMESTAMP_COLUMN in st.session_state["time_filtered_df"]:
485
+ st.write("## Popularity of topics over time")
486
+
487
+ # Parameters
488
+ st.text_input(
489
+ "Topics list (format 1,12,52 or 1:20)",
490
+ key="dynamic_topics_list",
491
+ value="0:10",
492
+ )
493
+
494
+ st.number_input("nr_bins", min_value=1, value=10, key="nr_bins")
495
+
496
+ # Compute topics over time
497
+ st.session_state["topics_over_time"] = compute_topics_over_time(
498
+ st.session_state["topic_model"],
499
+ st.session_state["time_filtered_df"],
500
+ nr_bins=st.session_state["nr_bins"],
501
+ )
502
+
503
+ # Visualize
504
+ st.plotly_chart(
505
+ plot_topics_over_time(
506
+ st.session_state["topics_over_time"],
507
+ st.session_state["dynamic_topics_list"],
508
+ st.session_state["topic_model"],
509
+ ),
510
+ config=PLOTLY_BUTTON_SAVE_CONFIG,
511
+ use_container_width=True,
512
+ )
513
+
514
+
515
+ def main():
516
+ """Main function to run the Streamlit topic_analysis."""
517
+ # Check if model is trained
518
+ check_model_trained()
519
+ check_embedding_type()
520
+
521
+ st.title("Temporal visualizations of topics")
522
+
523
+ # Display sidebar
524
+ display_sidebar()
525
+
526
+ # Calculate max granularity
527
+ max_granularity = _calculate_max_granularity(st.session_state["time_filtered_df"])
528
+
529
+ # Select time granularity
530
+ time_granularity = select_time_granularity(max_granularity)
531
+
532
+ col1, col2 = st.columns(2)
533
+ with col1:
534
+ # Add Apply button
535
+ apply_button = st.button("Apply Granularity and Parameters", type="primary")
536
+ with col2:
537
+ register_widget("temptopic_visualizations")
538
+ st.segmented_control(
539
+ "Show table results",
540
+ selection_mode="multi",
541
+ key="temptopic_visualizations",
542
+ on_change=save_widget_state,
543
+ options=["Topic evolution", "Topic info", "Documents per date"],
544
+ )
545
+
546
+ if apply_button:
547
+ if time_granularity is not None:
548
+ st.session_state.granularity = time_granularity
549
+ process_data_and_fit_temptopic(time_granularity)
550
+ else:
551
+ st.error("Please select a valid granularity before applying.")
552
+
553
+ # Display visualizations only if TempTopic has been fitted
554
+ if "temptopic" in st.session_state:
555
+ if "Topic evolution" in st.session_state["temptopic_visualizations"]:
556
+ display_topic_evolution_dataframe()
557
+ if "Topic info" in st.session_state["temptopic_visualizations"]:
558
+ display_topic_info_dataframe()
559
+ if "Documents per date" in st.session_state["temptopic_visualizations"]:
560
+ display_documents_per_date_dataframe()
561
+ display_temptopic_visualizations()
562
+ else:
563
+ st.info(
564
+ "Please apply granularity and parameters to view the temporal visualizations.",
565
+ icon=INFO_ICON,
566
+ )
567
+
568
+
569
+ # Restore widget state
570
+ restore_widget_state()
571
+ main()
572
+
573
+ # FIXME: Popularity of topics over time visualization is based on the number of paragraphs instead of original articles, since it's the default BERTopic method