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,329 @@
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
+ import gzip
6
+ import re
7
+ from pathlib import Path
8
+ from typing import Literal
9
+
10
+ import pandas as pd
11
+ from loguru import logger
12
+ from sentence_transformers import SentenceTransformer
13
+ from transformers import AutoTokenizer
14
+
15
+ from bertrend import DATA_PATH
16
+ from bertrend.config.parameters import MIN_CHARS_DEFAULT
17
+
18
+ # Define column names
19
+ TEXT_COLUMN = "text"
20
+ TIMESTAMP_COLUMN = "timestamp"
21
+ GROUPED_TIMESTAMP_COLUMN = "grouped_timestamp"
22
+ URL_COLUMN = "url"
23
+ TITLE_COLUMN = "title"
24
+ DOCUMENT_ID_COLUMN = "document_id"
25
+ SOURCE_COLUMN = "source"
26
+ CITATION_COUNT_COL = "citation_count"
27
+
28
+
29
+ def find_compatible_files(path: Path, extensions: list[str]) -> list[Path]:
30
+ return [f.relative_to(path) for f in path.rglob("*") if f.suffix[1:] in extensions]
31
+
32
+
33
+ def load_data(
34
+ selected_file: Path,
35
+ language: str = "French",
36
+ ) -> pd.DataFrame | None:
37
+ """
38
+ Load and preprocess data (adds additional columns if needed) from a selected file.
39
+
40
+ Args:
41
+ selected_file (Path): A path to the selected file
42
+ language (str): The language of the text data ('French' or 'English').
43
+
44
+ Returns:
45
+ pd.DataFrame: The loaded and preprocessed DataFrame.
46
+ """
47
+ df = _file_to_pd(selected_file)
48
+ if df is None:
49
+ return None
50
+
51
+ # Convert timestamp column to datetime
52
+ df[TIMESTAMP_COLUMN] = pd.to_datetime(df[TIMESTAMP_COLUMN], errors="coerce")
53
+
54
+ # Drop rows with invalid timestamps
55
+ df = df.dropna(subset=[TIMESTAMP_COLUMN])
56
+
57
+ df = df.sort_values(by=TIMESTAMP_COLUMN, ascending=True).reset_index(drop=True)
58
+ df[DOCUMENT_ID_COLUMN] = df.index
59
+
60
+ if URL_COLUMN in df.columns:
61
+ df[SOURCE_COLUMN] = df[URL_COLUMN].apply(
62
+ lambda x: x.split("/")[2] if pd.notna(x) else None
63
+ )
64
+ else:
65
+ df[SOURCE_COLUMN] = None
66
+ df[URL_COLUMN] = None
67
+
68
+ if language == "French":
69
+ df[TEXT_COLUMN] = df[TEXT_COLUMN].apply(preprocess_french_text)
70
+
71
+ if TITLE_COLUMN in df.columns:
72
+ df.drop_duplicates(subset=[TITLE_COLUMN], keep="first", inplace=True)
73
+ else:
74
+ # Title colupmn used in topic explorations
75
+ df[TITLE_COLUMN] = ""
76
+
77
+ return df
78
+
79
+
80
+ def split_data(
81
+ df: pd.DataFrame,
82
+ min_chars: int = MIN_CHARS_DEFAULT,
83
+ split_by_paragraph: Literal["no", "yes", "enhanced"] = "yes",
84
+ embedding_model_name: str = None,
85
+ ) -> pd.DataFrame | None:
86
+ """
87
+ Load and preprocess data from a selected file.
88
+
89
+ Args:
90
+ df (pd.DataFrame): A dataframe containing loaded documents
91
+ min_chars (int): The minimum number of characters required for a text to be included.
92
+ split_by_paragraph (Literal): Way to split the text data by paragraphs (no, yes, enhanced)
93
+ embedding_model_name (str): Name of the embedding model (if done locally), only useful if split_by_paragraph is 'enhanced'.
94
+
95
+ Returns:
96
+ pd.DataFrame: The split DataFrame.
97
+ """
98
+ if split_by_paragraph == "yes":
99
+ new_rows = []
100
+ for _, row in df.iterrows():
101
+ # Attempt splitting by \n\n first
102
+ paragraphs = re.split(r"\n\n", row[TEXT_COLUMN])
103
+ if len(paragraphs) == 1: # If no split occurred, attempt splitting by \n
104
+ paragraphs = re.split(r"\n", row[TEXT_COLUMN])
105
+ for paragraph in paragraphs:
106
+ new_row = row.copy()
107
+ new_row[TEXT_COLUMN] = paragraph
108
+ new_row["source"] = row["source"]
109
+ new_rows.append(new_row)
110
+ df = pd.DataFrame(new_rows)
111
+ elif split_by_paragraph == "enhanced":
112
+ # FIXME: requires access to the tokenizer
113
+ df = enhanced_split_df_by_paragraphs(
114
+ dataset=df, embedding_model_name=embedding_model_name
115
+ )
116
+
117
+ if min_chars > 0:
118
+ df = df[df[TEXT_COLUMN].str.len() >= min_chars]
119
+
120
+ df = df[df[TEXT_COLUMN].str.strip() != ""].reset_index(drop=True)
121
+
122
+ return df
123
+
124
+
125
+ def group_by_days(
126
+ df: pd.DataFrame, day_granularity: int = 1
127
+ ) -> dict[pd.Timestamp, pd.DataFrame]:
128
+ """
129
+ Group a DataFrame by a specified number of days.
130
+
131
+ Args:
132
+ df (pd.DataFrame): The input DataFrame containing a TIMESTAMP_COLUMN column.
133
+ day_granularity (int): The number of days to group by (default is 1).
134
+
135
+ Returns:
136
+ Dict[pd.Timestamp, pd.DataFrame]: A dictionary where each key is the timestamp group and the value is the corresponding DataFrame.
137
+ """
138
+ df[TIMESTAMP_COLUMN] = pd.to_datetime(df[TIMESTAMP_COLUMN])
139
+ grouped = df.groupby(pd.Grouper(key=TIMESTAMP_COLUMN, freq=f"{day_granularity}D"))
140
+ dict_of_dfs = {name: group for name, group in grouped}
141
+ return dict_of_dfs
142
+
143
+
144
+ def _file_to_pd(file_name: Path, base_dir: Path = None) -> pd.DataFrame | None:
145
+ """
146
+ Read data in various formats and convert it to a DataFrame.
147
+
148
+ Args:
149
+ file_name (str): The name of the file to read.
150
+ base_dir (Path, optional): The base directory of the file.
151
+
152
+ Returns:
153
+ pd.DataFrame: The loaded data.
154
+ """
155
+ file_ext = file_name.suffix.lower()
156
+ if file_ext == ".csv":
157
+ return pd.read_csv(DATA_PATH / file_name)
158
+ elif file_ext == ".parquet":
159
+ return pd.read_parquet(DATA_PATH / file_name)
160
+ elif file_ext == ".json":
161
+ return pd.read_json(DATA_PATH / file_name)
162
+ elif file_ext == ".jsonl" or file_ext == ".jsonlines":
163
+ return pd.read_json(DATA_PATH / file_name, lines=True)
164
+ elif file_ext == ".jsonl.gz" or file_ext == ".jsonlines.gz":
165
+ with gzip.open(file_name, "rt") as f_in:
166
+ return pd.read_json(f_in, lines=True)
167
+ elif file_ext == ".xslsx":
168
+ return pd.read_excel(DATA_PATH / file_name)
169
+ else:
170
+ return None
171
+
172
+
173
+ def clean_dataset(dataset: pd.DataFrame, length_criteria: int) -> pd.DataFrame:
174
+ """
175
+ Clean the dataset by removing short texts.
176
+
177
+ Args:
178
+ dataset (pd.DataFrame): The dataset to clean.
179
+ length_criteria (int): The minimum length of text to keep.
180
+
181
+ Returns:
182
+ pd.DataFrame: The cleaned dataset.
183
+ """
184
+ cleaned_dataset = dataset.loc[dataset[TEXT_COLUMN].str.len() >= length_criteria]
185
+ return cleaned_dataset
186
+
187
+
188
+ def enhanced_split_df_by_paragraphs(
189
+ dataset: pd.DataFrame, embedding_model_name: str = None
190
+ ) -> pd.DataFrame:
191
+ """
192
+ Split texts into multiple paragraphs and return a concatenation of all extracts as a new DataFrame.
193
+ If enhanced is True, it also considers the embedding model's max sequence length.
194
+
195
+ Args:
196
+ dataset (pd.DataFrame): The dataset to split.
197
+ embedding_model_name (str): The name of the embedding model to use (only required if enhanced is True)
198
+
199
+ Returns:
200
+ pd.DataFrame: The dataset with texts split into paragraphs.
201
+ """
202
+ if embedding_model_name is None:
203
+ raise ValueError("Tokenizer is required for enhanced splitting.")
204
+
205
+ tokenizer = AutoTokenizer.from_pretrained(embedding_model_name)
206
+ max_length = SentenceTransformer(embedding_model_name).get_max_seq_length()
207
+ # Correcting the max seq length anomaly in certain embedding models description
208
+ if max_length == 514:
209
+ max_length = 512
210
+
211
+ df = dataset.copy()
212
+ new_rows = []
213
+
214
+ for _, row in df.iterrows():
215
+ doc = row[TEXT_COLUMN]
216
+ timestamp = row[TIMESTAMP_COLUMN]
217
+
218
+ paragraphs = re.split(r"\n+", doc)
219
+
220
+ for paragraph in paragraphs:
221
+ sentences = re.split(r"(?<=[.!?])\s+", paragraph)
222
+
223
+ current_doc = ""
224
+ for sentence in sentences:
225
+ sentence_ids = tokenizer.encode(
226
+ sentence, padding=False, truncation=False, add_special_tokens=False
227
+ )
228
+
229
+ if len(sentence_ids) > max_length:
230
+ sentence_chunks = [
231
+ sentence[i : i + max_length]
232
+ for i in range(0, len(sentence), max_length)
233
+ ]
234
+ for chunk in sentence_chunks:
235
+ new_row = row.copy()
236
+ new_row[TEXT_COLUMN] = chunk
237
+ new_row["is_split"] = True
238
+ new_row["num_tokens"] = len(
239
+ tokenizer.encode(
240
+ chunk,
241
+ padding=False,
242
+ truncation=False,
243
+ add_special_tokens=False,
244
+ )
245
+ )
246
+ new_rows.append(new_row)
247
+ else:
248
+ ids = tokenizer.encode(
249
+ current_doc + " " + sentence,
250
+ padding=False,
251
+ truncation=False,
252
+ add_special_tokens=False,
253
+ )
254
+ num_tokens = len(ids)
255
+
256
+ if num_tokens <= max_length:
257
+ current_doc += " " + sentence
258
+ else:
259
+ if current_doc.strip():
260
+ new_row = row.copy()
261
+ new_row[TEXT_COLUMN] = current_doc.strip()
262
+ new_row["is_split"] = True
263
+ new_row["num_tokens"] = len(
264
+ tokenizer.encode(
265
+ current_doc.strip(),
266
+ padding=False,
267
+ truncation=False,
268
+ add_special_tokens=False,
269
+ )
270
+ )
271
+ new_rows.append(new_row)
272
+ current_doc = sentence
273
+
274
+ if current_doc.strip():
275
+ new_row = row.copy()
276
+ new_row[TEXT_COLUMN] = current_doc.strip()
277
+ new_row["is_split"] = True
278
+ new_row["num_tokens"] = len(
279
+ tokenizer.encode(
280
+ current_doc.strip(),
281
+ padding=False,
282
+ truncation=False,
283
+ add_special_tokens=False,
284
+ )
285
+ )
286
+ new_rows.append(new_row)
287
+
288
+ return pd.DataFrame(new_rows)
289
+
290
+
291
+ def preprocess_french_text(text: str) -> str:
292
+ """
293
+ Preprocess French text by normalizing apostrophes, replacing hyphens and similar characters with spaces,
294
+ removing specific prefixes, removing unwanted punctuations (excluding apostrophes, periods, commas, and specific other punctuation),
295
+ replacing special characters with a space (preserving accented characters, utils Latin extensions, and newlines),
296
+ normalizing superscripts and subscripts,
297
+ splitting words containing capitals in the middle (while avoiding splitting fully capitalized words),
298
+ and replacing multiple spaces with a single space.
299
+
300
+ Args:
301
+ text (str): The input French text to preprocess.
302
+
303
+ Returns:
304
+ str: The preprocessed French text.
305
+ """
306
+ # Normalize different apostrophe variations to a standard apostrophe
307
+ text = text.replace("’", "'")
308
+
309
+ # Replace hyphens and similar characters with spaces
310
+ text = re.sub(r"\b(-|/|;|:)", " ", text)
311
+
312
+ # Replace special characters with a space (preserving specified punctuation, accented characters, utils Latin extensions, and newlines)
313
+ text = re.sub(r"[^\w\s\nàâçéèêëîïôûùüÿñæœ.,\'\"\(\)\[\]]", " ", text)
314
+
315
+ # Normalize superscripts and subscripts for both numbers and letters
316
+ superscript_map = str.maketrans(
317
+ "⁰¹²³⁴⁵⁶⁷⁸⁹ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖᵠʳˢᵗᵘᵛʷˣʸᶻ", "0123456789abcdefghijklmnopqrstuvwxyz"
318
+ )
319
+ subscript_map = str.maketrans("₀₁₂₃₄₅₆₇₈₉ₐₑᵢₒᵣᵤᵥₓ", "0123456789aeioruvx")
320
+ text = text.translate(superscript_map)
321
+ text = text.translate(subscript_map)
322
+
323
+ # Split words that contain capitals in the middle but avoid splitting fully capitalized words
324
+ text = re.sub(r"(?<=[a-z])(?=[A-Z])", " ", text)
325
+
326
+ # Replace multiple spaces with a single space
327
+ text = re.sub(r"[ \t]+", " ", text)
328
+
329
+ return text
@@ -0,0 +1,15 @@
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
+ Lead Developers:
7
+ - Jérôme Picault
8
+ - Guillaume Grosjean
9
+ - Allaa Boutaleb
10
+
11
+ Other Developers:
12
+ -
13
+
14
+ Further Contributions by:
15
+ -