rapport_impact_datapapers_tetis.qmd 45.56 KiB
---
title: "Analyse de l'impact des data papers"
license: "CC BY"
date: 2024-01-31
author:
    - name: Rémy Decoupes
      orcid: 0000-0003-0863-9581
      corresponding: true
      email: remy.decoupes@inrae.fr
output:
  pdf_document:
    latex_engine: pdflatex
bibliography: bib.bib
---
![](readme.ressources/logo-header-tetis.png){fig-align="center" width="100px"}
L'objectif de cette étude est d'analyser l'impact des data papers sur la réutilisation des données mise à disposition par l'UMR TETIS. Pour cela, nous proposons d'analyser les indicateurs de réutilisation des jeux de données (consultation, téléchargement et citation) dépôsés sur Dataverse.Cirad.fr (CIRAD) ou Entrepôt.Recherche.Data.Gouv.Fr (INRAE) en comparant ceux accompagnés d'un data paper à ceux sans data paper.
Pour se faire, plusieurs API (*Application Programming Interface*) sont utilisées. Hal (pour INRAE) et Agritrop (pour le CIRAD) constituent la source des data papers, alors que les indicateurs pour les jeux de données sont obtenus via Dataverse.Cirad.fr (CIRAD) et Entrepôt.Recherche.Data.Gouv.Fr (INRAE).
En résumé des expérimentations menées par cette étude, il apparaît que les data papers TETIS (*14*) utilisent majoritairement le Dataverse CIRAD. L'utilisation de Entrepôt.Recherche.Data.Gouv.Fr est plus récente (*2021*), avec un nombre moyen de téléchargements par jeux de données de *10* pour INRAE (*30* jeux) et *55* pour le CIRAD (*120* jeux). Par ailleurs, les jeux de données associés à un data paper sont beaucoup plus téléchargés, en médiane, on observe plus de *100* téléchargements contre *5* pour les jeux sans data papers. 
|                          | INRAE                                                                                                                          | CIRAD                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
|---------------|----------------------|-----------------------------------|
| **Data Papers**          | [![INRAE Logo](readme.ressources/hal-logo.png){width="100px"}](https://hal.science/TETIS/)                                     | [![CIRAD Logo](readme.ressources/agritrop-logo.png){width="100px"}](https://agritrop.cirad.fr/cgi/search/archive/advanced?screen=Search&dataset=archive&crd_mot_merge=ALL&crd_mot=&crd_mot_sujet_merge=ALL&crd_mot_sujet=&crd_titre_recherche_merge=ALL&crd_titre_recherche=&crd_auteur_merge=ALL&crd_auteur=&crd_affil_merge=ALL&crd_affil=TETIS&date=&type=article&publication_merge=ANY&publication=scientific+brief&crd_issn_merge=ALL&crd_issn=&crd_issn_match=EQ&id_number_merge=ALL&id_number=&id_number_match=EQ&crd_desc_mat=&crd_desc_mat2=&crd_desc_mat3=&crd_desc_geo=&crd_desc_geo2=&crd_desc_geo3=&crd_motscles_libres1_merge=ALL&crd_motscles_libres1=&crd_motscles_libres2_merge=ALL&crd_motscles_libres2=&subjects_merge=ANY&satisfyall=ALL&order=-date%2Fcreators_name%2Ftitle&_action_search=Rechercher) |
| **Entrepôts de Données** | [![Data.gouv.fr Logo](readme.ressources/rdg-logo.png){width="100px"}](https://entrepot.recherche.data.gouv.fr/dataverse/tetis) | [![Dataverse CIRAD](readme.ressources/dataverse-cirad-logo.png){width="100px"}](https://dataverse.cirad.fr/dataverse/tetis)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
: {tbl-colwidths="\[20, 30, 30\]"}
# Sommaire
1.  [Analyse bibliométrique des articles de type `data paper`](#analyse-bibliométrique-des-articles-de-type-data-paper)
    1.1 [Collection TETIS/INRAE via Hal](#collection-tetisinrae-via-hal)
    1.2 [Collection TETIS/CIRAD via Agritrop](#collection-tetiscirad-via-agritrop)
    1.3 [Fusion des sources de données](#fusion-des-sources-de-données)
2.  [Analyse bibliométrique des entrepôts de données (dit dataverse)](#analyse-bibliométrique-des-entrepôts-de-données-dit-dataverse)
    2.1 [Collection TETIS/INRAE via Entrepôt de Recherche Data Gouv](#collection-tetisinrae-via-entrepôt-de-recherche-data-gouv)
    2.2 [Collection TETIS/CIRAD via Dataverse CIRAD](#collection-tetiscirad-via-dataverse-cirad)
3.  [Analyse des jeux de données décrits par les data papers](#analyse-des-jeux-de-données-décrits-par-les-data-papers)
    3.1 [Répartition Dataverse CIRAD / RDG INRAE dans les data papers](#répartition-dataverse-cirad--rdg-inrae-dans-les-data-papers)
    3.2 [Comparaison du nombre de téléchargements avec ou sans data paper](#comparaison-du-nombre-de-téléchargements-avec-ou-sans-data-paper)
4.  [Conclusion](#conclusion)
5.  [Annexe](#annexe)
    5.1 [Champs HAL utilisés](#champs-hal-utilisés)
    5.2 [Liste des data papers TETIS](#liste-des-data-papers-tetis)
    5.3 [Liste des jeux de données TETIS sour Entrepôt Recherche Data Gouv](#liste-des-jeux-de-données-tetis-sour-entrepôt-recherche-data-gouv)
{{< pagebreak >}}
| Version | Date       | Description de la Modification           | Auteur          |
|---------|------------|-----------------------------------------|-----------------|
| 1.0.   | 2024-01-25 | Version initiale du document             | Rémy Decoupes    |
| 1.1.   | 2024-01-31 | Ajout de data papers non Scientific data ou Data in Brief.           |     |
{{< pagebreak >}}
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
```{python} import pandas as pd import seaborn as sns sns.set(style="whitegrid") sns.set_palette("pastel") import matplotlib.pyplot as plt import requests ``` # 1. Analyse bibliométrique des articles de type `data paper` {#analyse-bibliométrique-des-articles-de-type-data-paper} ## 1.1 Collection TETIS/INRAE via [Hal](https://hal.science/TETIS/) {#collection-tetisinrae-via-hal} ```{python} my_lab = "TETIS" hal_collection_url = f"https://api.archives-ouvertes.fr/search/{my_lab}" free_text_search = "*" hal_api_params = { 'q': f"{free_text_search}", #free text search "wt":"json", # format de sortie "fl": "docid,halId_s,title_s,authFullName_s,submittedDate_s,abstract_s,journalDate_s,publicationDate_s, producedDate_s, docType_s, doiId_s, journalPublisher_s, journalTitle_s, journalIssn_s, researchData_s, docSubType_s", # champs retournés "sort": "submittedDate_s desc", 'rows': 3000, # nombre de documents récupérés sans pagination } reponse = requests.get(hal_collection_url, params=hal_api_params) # print(f"GET URL: {reponse.url}") df = pd.DataFrame(reponse.json()["response"]["docs"]) df["submittedDate_s"] = pd.to_datetime(df["submittedDate_s"]) df["journalDate_s"] = pd.to_datetime(df["journalDate_s"], errors="coerce") # df["publicationDate_s"] = pd.to_datetime(df["publicationDate_s"], format='mixed') df["producedDate_s"] = pd.to_datetime(df["producedDate_s"], format='mixed') df_subtype_datapaper = df[df["docSubType_s"] == "DATAPAPER"] df_subtype_datapaper_filtered = df_subtype_datapaper[df_subtype_datapaper['producedDate_s'] >= '2019-01-01'] journal_counts = df_subtype_datapaper_filtered["journalTitle_s"].value_counts() ``` En interrogeant l'API, les data papers publiés dans la collection de TETIS peuvent être exportés à travers le champs Hal `docSubType_s: "DATAPAPER"`. `{python} len(df_subtype_datapaper_filtered["journalTitle_s"])` data papers ont été publiés depuis 2019. La @fig-hal-inrae montre la série temporelle de publication de data papers depuis 2019. Il est à noter qu'il y a une accélération du nombre de datapaers depuis fin 2022. Les 2 revues, **Scientific Data** et **Data in Brief** sont les plus utilisées. ```{python} #| label: fig-hal-inrae #| fig-cap: Histogramme des publications de data papers de l'UMR TETIS sous Hal depuis 2019 plt.figure(figsize=(12, 6)) sns.histplot(data=df_subtype_datapaper_filtered, x="producedDate_s", stat="count", binwidth=365, hue="journalTitle_s", multiple="stack") # plt.title('Histogramme des data/software papers par date de publication par les revues') plt.yticks(range(0, 10, 1)) plt.xlabel('Date de publication') plt.ylabel('Nombre de data ou software papers') plt.xticks(rotation=45); ``` ## 1.2 Collection TETIS/CIRAD via [Agritrop](https://agritrop.cirad.fr/cgi/search/archive/advanced?...) {#collection-tetiscirad-via-agritrop} ```{python} file_path = 'external_data/2023-01-22_agritrop_data_papers.txt' with open(file_path, 'r', encoding='utf-8') as file: data_lines = file.readlines() list_of_citations = [] for line in data_lines: fields = line.split(".") if fields[0] != "\n": citation_dict = {
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
'titre': str(fields[0]).replace(" ", ""), 'authors': str(fields[1]).replace(" ", ""), 'date': str(fields[2]).replace(" ", ""), 'journal': str(fields[3]).replace(" ", "").split(",")[0], 'DOI': '.'.join(fields[4:]).split(" <")[0] } list_of_citations.append(citation_dict) df_citations = pd.DataFrame(list_of_citations) df_citations["journal"] = df_citations["journal"].str.strip() df_citations = df_citations[df_citations['journal'] != 'Scientific Reports'] ``` Agritrop ne semble pas proposer d'API. Il est donc nécessaire de filtrer manuellement, à travers le site web, les résultats d'Agritrop via `Affiliation : TETIS` et titre de revues de data papers : `Scientific data` et `Data in brief`. En effet, aucun champs ne permet de filtrer sur les articles de type data papers. `{python} len(df_citations["titre"])` data papers ont été téléversés sur Agritrop depuis 2019. Seuls ces deux journaux ont été sélectionnés pour cette étude car ce sont les seuls présent dans la collection Hal de TETIS. Dans la version 1.1 du document, le data paper de @jolivot_harmonized_2021 publié dans *Earth System Science Data* a été ajouté manuellement à la collection. Pour plus d'information, le document CIRAD de Laurence @dedieu_revues_nodate propose une liste exhaustive des journaux acceptant les data papers. ```{python} #| label: fig-agritrop-cirad #| fig-cap: Histogramme des publications de data papers Agritrop depuis 2019 plt.figure(figsize=(12, 6)) sns.histplot(data=df_citations, x="date", stat="count", hue="journal", multiple="stack") # plt.title('Histogramme des data/software papers par date de publication par les revues') plt.gca().invert_xaxis() plt.yticks(range(0, 10, 1)) plt.xlabel('Date de publication') plt.ylabel('Nombre de data papers') plt.xticks(rotation=45); ``` La @fig-agritrop-cirad propose l'historique de dépôts de data papers sur Agritrop. ## 1.3 Fusion des sources de données {#fusion-des-sources-de-données} ```{python} df_subtype_datapaper_filtered.rename(columns={'title_s': 'titre', 'doiId_s': 'DOI', 'authFullName_s': 'authors', 'journalTitle_s': 'journal'}, inplace=True) df_subtype_datapaper_filtered["source"] = "hal" df_subtype_datapaper_filtered['titre'] = df_subtype_datapaper_filtered['titre'].apply(lambda x: x[0] if x else None) df_subtype_datapaper_filtered['DOI'] = 'https://doi.org/' + df_subtype_datapaper_filtered['DOI'] df_citations["source"] = "agritrop" concatenated_df = pd.concat([df_subtype_datapaper_filtered, df_citations]) # concatenated_df.to_csv("./external_data/test2.csv") df_no_duplicates = concatenated_df.drop_duplicates('DOI', keep='first') ``` ```{python} #| label: tbl-agritrop-hal-noduplicate #| tbl-cap: Liste des articles présent uniquement dans l'une des collections from IPython.display import HTML, display # HTML(pd.concat([df_subtype_datapaper_filtered, df_citations]).drop_duplicates(subset="DOI", keep=False)[["source", "titre"]].to_html(index=False)) display(pd.concat([df_subtype_datapaper_filtered, df_citations]).drop_duplicates(subset="DOI", keep=False).reset_index()[["source", "titre"]]) ``` Après fusion des deux sources puis suppression des articles présent dans les deux plateformes, le nombre de data papers publiés depuis 2019 est `{python} len(df_no_duplicates["DOI"])`. Les articles seulement présents dans Hal ou Agritrop sont détaillé dans la @tbl-agritrop-hal-noduplicate. ```{python} #| label: fig-agritrop-hal-pie #| fig-cap: Répartition de l'ensemble des publications par revue journal_counts = df_no_duplicates["journal"].value_counts() plt.figure(figsize=(3, 3)) plt.pie(journal_counts, labels=journal_counts.index, autopct=lambda p: '{:.0f} ({:.1f}%)'.format(p * sum(journal_counts) / 100, p), startangle=140, colors=plt.cm.Paired.colors); ``` La liste complète des data papers est proposée en Annexe (@tbl-list-datapepers).
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
```{python} import requests def get_citation_count(doi): # Construct the CrossRef Metadata API URL doi = doi.split("https://doi.org/")[1] api_url = f'https://api.crossref.org/works/{doi}' # Perform the request to the CrossRef Metadata API max_retries = 5 delay = 2 for attempt in range(1, max_retries + 1): try: response = requests.get(api_url) if response.status_code == 200: break except: print(f"Could not get crossref {doi}") # Check if the request was successful (status code 200) if response.status_code == 200: # Parse the response JSON data = response.json() # Extract the citation count, adjust this based on the actual structure of the data citation_count = data.get('message', {}).get('is-referenced-by-count', 0) return citation_count else: print(f"Request to CrossRef Metadata API failed with status code {response.status_code}") return None df_no_duplicates["citation_count"] = df_no_duplicates["DOI"].apply(get_citation_count) date_formats = ["%Y-%m-%d", "%Y-%m", "%Y", None] df_no_duplicates['publicationDate_s_year'] = pd.to_datetime(df_no_duplicates['publicationDate_s'], format="mixed").dt.strftime("%Y") # Extract the year from the datetime objects and create a new column "year" df_no_duplicates['year'] = df_no_duplicates['publicationDate_s_year'].combine_first(df_no_duplicates['date']) ``` ```{python} #| label: tbl-list-citation #| tbl-cap: Liste des data papers TETIS ayant reçu au moins une citation #| tbl-colwidths: [70,10,10] from IPython.display import Markdown from tabulate import tabulate sorted_table = df_no_duplicates[df_no_duplicates["citation_count"] > 0][["titre", "citation_count", "year"]].sort_values(by="year", ascending=True).reset_index(drop=True) Markdown(tabulate(sorted_table, headers=sorted_table.keys(), showindex=False)) ``` En ce qui concerne les citations des data papers, nous les obtenons avec l'API de [CrossRef](https://www.crossref.org/), agence d'attribution de DOI. La @tbl-list-citation montre les data papers TETIS ayant au moins une citation. `{python} round(df_no_duplicates[df_no_duplicates["citation_count"] > 0]["titre"].count()/len(df_no_duplicates["titre"]) * 100, 2)`% des data papers TETIS ont obtenu au moins 1 citation. # 2. Analyse bibliométrique des entrepôts de données (dit dataverse) {#analyse-bibliométrique-des-entrepôts-de-données-dit-dataverse} ## 2.1 Collection TETIS/INRAE via [Entrepôt de Recherche Data Gouv](https://entrepot.recherche.data.gouv.fr/dataverse/tetis) {#collection-tetisinrae-via-entrepôt-de-recherche-data-gouv} ```{python} import configparser import requests import pandas as pd credential_file = "credentials.ini" credential_config = configparser.ConfigParser() credential_config.read(credential_file)
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
API_TOKEN_data_inrae = credential_config['DATA_INRAE']['API_TOKEN'] url_rdg = "https://entrepot.recherche.data.gouv.fr/" headers_data_inrae = {'Accept': 'application/json', 'X-Dataverse-key': API_TOKEN_data_inrae } API_TOKEN_data_cirad = credential_config['CIRAD']['API_TOKEN'] url_cirad = "https://dataverse.cirad.fr/" headers_data_cirad = {'Accept': 'application/json', 'X-Dataverse-key': API_TOKEN_data_cirad } def storage_szie_of_dataverse(dataverse, url=url_rdg, header=headers_data_inrae): url_dataverse = url + "api/dataverses/" + dataverse reponse = requests.get(url_dataverse + "/storagesize", headers=header).json()["data"] return reponse # get dataset ID from a dataverse def datasetID_from_dataverse(dataverse, url=url_rdg, header=headers_data_inrae): url_dataverse = url + "/api/dataverses/" + dataverse try: reponse = requests.get(url_dataverse + "/contents", headers=header).json()["data"] except: print(requests.get(url_dataverse + "/contents", headers=header).json()) reponse = {} return reponse # get all data from a dataverse def datasets_info_from_datasetID(dataverse_content, url=url_rdg, header=headers_data_inrae): """ """ list_datasets = [] list_dataverses = [] for dataset in dataverse_content: try: dataset_info = requests.get(url + "/api/datasets/" + str(dataset["id"]), headers=header).json()["data"] list_datasets.append(dataset_info) except: # it's not a dataset but a dataverse list_dataverses.append(str(dataset["title"]).replace(" ", "_")) return list_datasets, list_dataverses # get view (total and uniques), download (unique & totla) and citation of a dataset def get_metrics_of_datasets(doi): api_metrics = ["viewsTotal", "viewsUnique", "downloadsTotal", "downloadsUnique", "citations"] dic_api_metrics = {} for metric in api_metrics: url = url_rdg + "/api/datasets/:persistentId/makeDataCount/" + metric + "?persistentId=" + doi try: reponse = requests.get(url, headers=headers_data_inrae).json()["data"][metric] except: reponse = 0 dic_api_metrics[metric] = reponse # print(dic_api_metrics) return dic_api_metrics # Le CIRAD n'a pas activé les fonctions "makeDataCount", on est obligé de travailler en global sur tout le dataverse def nombre_datasets(dataverse, cirad_inrae): if cirad_inrae == "INRAE": url_endpoint = url_rdg header = headers_data_inrae else: url_endpoint = url_cirad header = headers_data_cirad url = f"{url_endpoint}/api/info/metrics/datasets/?parentAlias={dataverse}" try: reponse = requests.get(url, headers=header).json()["data"]["count"] except: reponse = np.nan return reponse
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
def time_series_upload(dataverse, cirad_inrae): if cirad_inrae == "INRAE": url_endpoint = url_rdg header = headers_data_inrae else: url_endpoint = url_cirad header = headers_data_cirad url = f"{url_endpoint}/api/info/metrics/datasets/monthly/?parentAlias={dataverse}" try: reponse = requests.get(url, headers=header).json()["data"] except: reponse = np.nan return reponse def time_series_download(dataverse, cirad_inrae): if cirad_inrae == "INRAE": url_endpoint = url_rdg header = headers_data_inrae else: url_endpoint = url_cirad header = headers_data_cirad url = f"{url_endpoint}/api/info/metrics/downloads/monthly/?parentAlias={dataverse}" try: reponse = requests.get(url, headers=header).json()["data"] except: reponse = np.nan return reponse def time_serie_dynamique_depot_telechargement(cirad_inrae): df_cirad_timeseries = pd.DataFrame(time_series_upload("tetis", cirad_inrae)) df_cirad_timeseries.rename(columns={'count': 'upload_count'}, inplace=True) df_cirad_timeseries_2 = pd.DataFrame(time_series_download("tetis", cirad_inrae)) df_cirad_timeseries_2.rename(columns={'count': 'download_count'}, inplace=True) df_cirad_timeseries = pd.merge(df_cirad_timeseries, df_cirad_timeseries_2, on='date', how='inner') df_cirad_timeseries["ratio_dataset_download"] = df_cirad_timeseries["download_count"] / df_cirad_timeseries["upload_count"] fig, ax1 = plt.subplots(figsize=(12, 6)) # Barplot pour upload_count sur le premier axe y sns.barplot(x='date', y='upload_count', data=df_cirad_timeseries, color='skyblue', ax=ax1, label='Nb de jeux de données TETIS') # Rotation des étiquettes de l'axe des x ax1.set_xticklabels(ax1.get_xticklabels(), rotation=45, ha='right') # Ajouter une légende pour le premier axe y ax1.legend(loc='upper left') ax1.set_ylabel('Nb de datasets & Nb de téléchargement moyen par dataset') # Créer le deuxième axe y ax2 = ax1.twinx() # Barplot pour download_count sur le deuxième axe y sns.barplot(x='date', y='download_count', data=df_cirad_timeseries, color='lightcoral', ax=ax2, label='Nombre de téléchargement d\'un jeu de données TETIS', alpha=0.7) # Ajouter une légende pour le deuxième axe y ax2.legend(loc='upper right') ax2.set_ylabel('Nb de téléchargement') sns.lineplot(x='date', y='ratio_dataset_download', data=df_cirad_timeseries, color='green', label='Ratio de téléchargement', ax=ax1) # Ajouter des étiquettes et un titre plt.xlabel('Mois') plt.title('Histogramme du Upload Count et Download Count par mois') tick_positions = range(0, len(df_cirad_timeseries['date'].unique()), 4) plt.xticks(tick_positions, df_cirad_timeseries['date'].unique()[tick_positions]);
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
``` ```{python} list_datasets_tetis, list_dataverse_tetis = datasets_info_from_datasetID(datasetID_from_dataverse("tetis")) df_rdg_datasets = pd.json_normalize(list_datasets_tetis, max_level=10) df_rdg_datasets = df_rdg_datasets[df_rdg_datasets['latestVersion.datasetPersistentId'].notna()] df_rdg_datasets["title"] = df_rdg_datasets["latestVersion.metadataBlocks.citation.fields"].apply(lambda x: x[0]["value"]) ``` Le nombre de jeux de données décrit dans la collection TETIS de l'entrepôt de Recherche.Data.Gouv est `{python} len(df_rdg_datasets)`, pour un volume total de `{python} storage_szie_of_dataverse("tetis")["message"].split(": ")[1]`. La liste complète est disponible en annexe (@tbl-df-rdg). ```{python} #| label: fig-rdg-global #| fig-cap: Analyse globale de la dynamique des dépôts de jeux de données ainsi que leurs téléchargement time_serie_dynamique_depot_telechargement("INRAE") ``` La @fig-rdg-global propose 3 variables d'intérêt pour suivre la dynamique globale de la collection TETIS. La première (en bleu) est le nombre de dépôts cumulés. La deuxième (en rouge) représente, en cumulé également, le nombre total des téléchargements de jeux de données TETIS. Enfin, en vert, nous affichons la moyenne du nombre de téléchargements par jeu de données. On observe une augmententation plutôt lente de cette moyenne. En début de 2024, la moyenne est de \~10 téléchargements par jeu de données. ```{python} #| label: fig-rdg-histogramme #| fig-cap: Histogramme des soumissions de jeux de données sous RDG import datetime plt.figure(figsize=(16, 10)) df_rdg_datasets["publicationDate"] = pd.to_datetime(df_rdg_datasets["publicationDate"]) # Tracer l'histogramme sns.histplot(data=df_rdg_datasets, x="publicationDate", stat="count", binwidth=30) # Ajouter des lignes verticales et des étiquettes plt.axvline(datetime.datetime(2022, 5, 24), color="red", linewidth=4) plt.text(datetime.datetime(2022, 5, 24), plt.gca().get_ylim()[1]*0.95, 'Data Party 1', color="red") plt.axvline(datetime.datetime(2023, 6, 8), color="red", linewidth=4) plt.text(datetime.datetime(2023, 6, 8), plt.gca().get_ylim()[1]*0.95, 'Data Party 2', color="red") plt.axvline(datetime.datetime(2022, 4, 1), color="orange", linewidth=4) plt.text(datetime.datetime(2022, 4, 1), plt.gca().get_ylim()[1]*0.95, 'Artisols', color="orange"); ``` Plusieurs évènements autour de la gestion de la donnée à TETIS ont marqué l'évolution des soumissions de jeux de données dans la collection TETIS. La @fig-rdg-histogramme propose un histogramme de ces soumissions avec trois marqueurs temporels. Le premier est la date de mise à disposition des données produites par le projet Artisols. Le second et troisième sont les deux data parties organisées à TETIS. On observe que beaucoup de dépôts ont été réalisés après la première data party. La seconde, axée sur les Plan de Gestion des Données et la gestion des codes, n'a visiblement pas entraîné de nouveaux dépôts. ```{python} #| label: fig-moustache-plot-rdg #| fig-cap: Distribution statistique des consultations (total et unique), des téléchargements (total et unique) ainsi que du nombre de citation df_rdg_datasets[['viewsTotal', 'viewsUnique', 'downloadsTotal', 'downloadsUnique', 'citations']] = \ df_rdg_datasets["latestVersion.datasetPersistentId"].apply(lambda x: pd.Series(get_metrics_of_datasets(x))) selected_columns = ['viewsTotal', 'viewsUnique', 'downloadsTotal', 'downloadsUnique', 'citations'] subset_df = df_rdg_datasets[selected_columns] fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(12, 15)); # Tracer les boîtes à moustaches pour les vues sns.boxplot(data=subset_df[['viewsTotal', 'viewsUnique']], ax=axes[0], palette=["skyblue", "lightgreen"]); axes[0].set_title('Boîte à moustaches des vues'); axes[0].set_ylabel('Nombre de vues'); # Tracer les boîtes à moustaches pour les téléchargements sns.boxplot(data=subset_df[['downloadsTotal', 'downloadsUnique']], ax=axes[1], palette=["orange", "lightcoral"]); axes[1].set_title('Boîte à moustaches des téléchargements'); axes[1].set_ylabel('Nombre de téléchargements'); # Tracer le moustache plot pour les citations sns.boxplot(data=subset_df[['citations']], ax=axes[2], palette=["lightpink"]); axes[2].set_title('Boîte à moustaches des citations');
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
axes[2].set_ylabel('Nombre de citations'); # Ajuster l'espacement entre les sous-graphiques plt.tight_layout(); ``` La @fig-moustache-plot-rdg montre les distributions statistiques des nombres de consultations, téléchargements et citation des jeux de données de la collection TETIS. Nous pouvons observer une grande variabilité de ces métriques pour les jeux de données. Certains jeux de données semblent très consultés & téléchargés comparativement à la moyenne des autres. Cette observation est confirmée lorsque l'on compare la moyenne du nombre de téléchargement par jeu de données de la @fig-rdg-global (aux alentours de 10) et la médianne de la @fig-moustache-plot-rdg (inférieur à 5). Quelques jeux de données sont donc très téléchargés. ## 2.2 Collection TETIS/CIRAD via [Dataverse CIRAD](https://dataverse.cirad.fr/dataverse/tetis) {#collection-tetiscirad-via-dataverse-cirad} La version de l'API actuelle du dataverse CIRAD ([5.13](https://guides.dataverse.org/en/5.13/api/metrics.html)) ne permet pas, malheureusement, d'obtenir les informations à l'échelle du jeu de données. En effet, seule une analyse globale (au niveau de la collection TETIS) est possible. ```{python} #| label: fig-dataversecirad-global #| fig-cap: Analyse globale de la dynamique des dépôts de jeux de données ainsi que leurs téléchargement time_serie_dynamique_depot_telechargement("Cirad") ``` Comme illustré par la @fig-dataversecirad-global, la pratique de dépôt de jeux de données par le CIRAD est plus ancienne (2017 au lieu de 2021 pour INRAE). Le nombre de jeux de données est aussi bien plus important (120 Cirad pour 30 INRAE), ainsi que le nombre de téléchargement (6000 Cirad pour 250 INRAE). La moyenne du nombre de téléchargement par jeux de données est largement supérieur (55 Cirad pour 10 INRAE). Comme indiqué en introduction de cette section, il est impossible d'accèder aux indicateurs à l'échelle des jeux de données. Il est donc impossible de tracer la distribution statistique du nombre de téléchargements afin de comparer la médianne et la moyenne. # 3. Analyse des jeux de données décrits par les data papers {#analyse-des-jeux-de-données-décrits-par-les-data-papers} ```{python} import os import requests from bs4 import BeautifulSoup import re from PyPDF2 import PdfReader def extract_dois_from_hal(hal_id): # Construire l'URL à partir de l'identifiant hal url = f"https://hal.science/{hal_id}" # Envoyer une requête à l'URL response = requests.get(url) # Vérifier si la requête a réussi (code d'état 200) if response.status_code == 200: # Analyser le contenu HTML de la page soup = BeautifulSoup(response.text, 'html.parser') # Trouver la balise <a> avec l'attribut download contenant ".pdf" pdf_link = soup.find('a', {'download': True, 'href': lambda x: x and x.endswith('.pdf')}) if pdf_link: # Extraire le lien PDF pdf_url = pdf_link['href'] # Télécharger le fichier PDF dans le répertoire "external_data" pdf_filename = os.path.join("external_data", os.path.basename(pdf_url)) response_pdf = requests.get(pdf_url) with open(pdf_filename, "wb") as pdf_file: pdf_file.write(response_pdf.content) # Lire le fichier PDF with open(pdf_filename, "rb") as pdf_file: pdf_reader = PdfReader(pdf_file) pdf_text = "\n".join(page.extract_text() for page in pdf_reader.pages) # Chercher les motifs DOI # 10.15454 : RDG inrae # 10.18167 : Dataverse CIRAD doi_pattern = re.compile(r'(https:..doi.org.(10.15454|10.18167).\S+)') matches = doi_pattern.findall(pdf_text) dois = [match[0] for match in matches] return list(set(dois)) else:
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
# print("Aucun lien PDF trouvé sur la page.") return [] else: # print(f"Échec de la récupération de la page. Code d'état : {response.status_code}") return [] def get_cirad_download_count(doi): # Remove the prefix doi_suffix = doi.replace('https://doi.org/', '') # Build the Dataverse CIRAD URL cirad_url = f'https://dataverse.cirad.fr/dataset.xhtml?persistentId=doi:{doi_suffix}' headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'} response = requests.get(cirad_url, headers=headers) # Check if the request was successful (status code 200) if response.status_code == 200: # Parse the HTML content of the page soup = BeautifulSoup(response.text, 'html.parser') # Find the metrics count block metrics_block = soup.find('div', {'id': 'metrics-body'}) count_block = metrics_block.find('div', {'class': 'metrics-count-block'}) # Extract the download count download_count_text = count_block.text.strip().split()[0] # Assuming the count is the first part of the text download_count = int(download_count_text) if download_count_text.isdigit() else None return download_count else: # If the request fails, return None return None def get_download_count_from_list(list_doi): inrae_prefix = "10.15454" cirad_prefix = "10.18167" download_count = 0 nom_entrepot = "None" return_dict = {} for doi in list_doi: if inrae_prefix in doi: doi = doi.replace("https://doi.org/", "doi:") download_count = download_count + get_metrics_of_datasets(doi)["downloadsTotal"] nom_entrepot = "RDG/INRAE" elif cirad_prefix in doi: download_count = download_count + get_cirad_download_count(doi) nom_entrepot = "CIRAD" else: download_count = np.nan nom_entrepot = "None" return_dict["Nombre_téléchargement"] = download_count return_dict["nom_entrepot"] = nom_entrepot return return_dict ``` ```{python} df_no_duplicates['entrepot'] = df_no_duplicates['halId_s'].apply(extract_dois_from_hal) inrae_prefix = "10.15454" cirad_prefix = "10.18167" df_no_duplicates[['Nombre_téléchargement', 'nom_entrepot']] = df_no_duplicates['entrepot'].apply(lambda x: pd.Series(get_download_count_from_list(x))) ``` La méthode utilisée pour cette section est de télécharger les data papers référencés par [le paragraphe 1.3](#fusion-des-sources-de-données) puis d'en extraire les liens vers les entrepôts dataverse CIRAD et Recherche.Data.Gouv. Avec cette liste, il est alors possible d'obtenir (soit par API soit par Web scrapping), le nombre de téléchargements. La liste des publications, fusionnées de Agritrop et de Hal, possède les liens DOI vers les data papers. Cependant, les redirections (DOI landing page vers le site de l'éditeur) ainsi que les pages Web dynamiques des éditeurs rendent le scrapping difficile (le contenu de ces pages web sont générés dynamiquement en fonction de la navigation de l'utilisateur). Aussi, nous avons été contraint de ne travailler que sur les documents Hal, soit `{python} round(df_no_duplicates['halId_s'].count() / len(df_no_duplicates) * 100, 2)`% des data papers. En effet, à partir de leur HalID, il est alors facile de télécharger le fichier PDF et de l'analyser.
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
Ainsi, après extraction du texte des fichiers PDF, deux motifs d'URL ont été cherchés, l'un correspond au prefix DOI de Recherche.Data.Gouv (`{python} inrae_prefix`) et l'autre au prefix CIRAD (`{python} cirad_prefix`). Ainsi pour chaque article Hal, nous avons la liste des liens DOI vers les entrepôts CIRAD ou Recherche.Data.Gouv. ## 3.1 Répartition Dataverse CIRAD / RDG INRAE dans les data papers {#répartition-dataverse-cirad--rdg-inrae-dans-les-data-papers} ```{python} #| label: fig-entrepot-names #| fig-cap: Nombre de liens vers les entrepôts RDG/INRAE (Recherche.Data.Gouv), CIRAD (Dataverse.Cirad) et None (lorsqu'aucun lien n'a été trouvé) counts = df_no_duplicates["nom_entrepot"].value_counts() colors = sns.color_palette("pastel") counts.plot(kind='bar', color=colors, edgecolor='black'); plt.xlabel('Nom de l\'entrepôt'); plt.ylabel('Nombre de liens'); ``` Parmi les data papers sur Hal (e.g. `{python} df_no_duplicates['halId_s'].isna().count()`), `{python} df_no_duplicates[df_no_duplicates["nom_entrepot"] == "None"]["nom_entrepot"].count()` n'ont pas de liens vers les entrepôts INRAE ou CIRAD. Par ailleurs, l'entrepôt CIRAD est le plus utilisé comme illustré par @fig-entrepot-names. ## 3.2 Comparaison du nombre de téléchargements avec ou sans data paper{#comparaison-du-nombre-de-téléchargements-avec-ou-sans-data-paper} ```{python} #| label: fig-boxplot-avec-sans-datapeaper #| fig-cap: Boîtes moustache du nombre de téléchargements avec et sans Data Paper variable_1 = subset_df['downloadsTotal'] variable_2 = df_no_duplicates[df_no_duplicates['Nombre_téléchargement'] != 0]['Nombre_téléchargement'] # Create a DataFrame for the boxplot data = pd.DataFrame({'Sans data paper': variable_1, 'Avec data paper': variable_2}) # Create a boxplot sns.boxplot(data=data); # Add labels and title # plt.title('Boxplot of Téléchargements avec et sans Data Paper') plt.xlabel('Jeux de données'); plt.ylabel('Nombre de téléchargements'); ``` Afin de voir l'impact sur la réutilisation des jeux de données accompagnés ou non par un data paper, nous proposons de comparer l'indicateur *Nombre de téléchargement* entre deux listes de jeux de données. La première contient l'ensemble des jeux de données de la collection TETIS sur Recherche.Data.Gouv (dont 1 provenant de l'unique Data paper TETIS (@schaeffer_labeled_2022) dont les données sont déposées dans cet entrepôt). Le second est la liste des jeux de données cités par les data papers de TETIS. La @fig-boxplot-avec-sans-datapeaper illustre cette comparaison en affichant la boîte à moustache de ces deux distributions. Nous pouvons observer que la médianne des téléchargement des jeux de données accompagnés par un data paper est nettement supérieur (aux alentours de 110 pour 5). Même les jeux de données parmi les 5% les moins téléchargés de la distribution des data papers, restent nettement supérieur aux 5% les plus téléchargés des sans data papers. La limite à cette comparaison est que les jeux de données sans data paper proviennent de la collection INRAE, nettement plus récente que celle de CIRAD, et dont les jeux de données sont moins téléchargées (cf [Comparaison collections CIRAD / INRAE](#collection-tetiscirad-via-dataverse-cirad)) # Conclusion {#conclusion} Les data papers sont utiles à l'Open Science en aidant à améliorer la reproductibilité de la recherche. En effet, ils permettent de fournir une documentation complète pour décrire l'origine, la pertinence du jeu de données (pour sa communauté de recherche) et proposent des potentiels cas de réutilisation. De plus, ils permettent de donner une visibilité importante à la production de données ou logiciels de TETIS. En effet, cette étude montre que les jeux de données accompagnés par des data papers sont beaucoup plus téléchargés (*110* contre *5* téléchargements en mediane). Les data papers constituent donc un moyen efficace pour porter à connaissance notre production de données à nos communautés de recherche. Le CIRAD a entrepris une démarche d'ouverture de ses données depuis une plusieurs années. Cette politique porte clairement ses fruits, elle a permis de mettre à disposition plus de *120* jeux de données (contre *30* pour INRAE) avec un total de *6000* téléchargements (contre *250* INRAE). Depuis 2021, des initiatives similaires sont en cours à INRAE (soutenu notamment par les Référents Données Opérationnels (RDO) de TETIS), il est nécessaire de les poursuivre. Plusieurs pistes de reflexion peuvent être menées pour accompagner davantage la réutilisation de notre production de données. Tout d'abord, d'autres indicateurs que le nombre de téléchargements doivent être pris en compte pour évaluer le taux de réutilisation (Est-ce que les jeux de données des data papers ne sont pas automatiquement moissonnés par des plateformes ce qui a pour effet d'augmenter le nombre de téléchargements ? Si oui, comment le mesurer ?). En complément des data papers, quel type de promotion pouvons-nous mettre en place ? Nous pouvons envisager le dépôt des jeux de données dans des entrepôts communautaires (comme HuggingFace pour les modèles d'Intelligence Articficielle à travers le [groupe TETIS](https://huggingface.co/UMR-TETIS) par exemple). Nous pouvons également organiser des [Hackathons](https://mood-h2020.eu/time-for-a-mood-hack-antimicrobial-resistance-hackathon/) comme cela a été fait pour le projet MOOD. Afin de répondre à ces questions, il serait pertinent de conduire une enquête (sondage) auprès du personnel TETIS afin d'ajouter d'autres indicateurs d'impact. Nous pourrions également recenser les méthodes utilisées à TETIS pour promouvoir la réutilisation de nos jeux de données (avec ou sans data paper). {{< pagebreak >}} # Annexe {#annexe} ## 5.1 Champs HAL utilisés {#champs-hal-utilisés} | Champ | Description | |--------------------|----------------------------------------------------| | docid | Identifiant du document dans la base de données HAL. | | halId_s | Identifiant HAL unique associé au document. |
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
| title_s | Titre du document. | | authFullName_s | Noms complets des auteurs. | | submittedDate_s | Date de soumission du document. | | abstract_s | Résumé du document. | | journalDate_s | Date de publication dans le journal. | | publicationDate_s | Date de publication du document. | | producedDate_s | Date de production du document. | | docType_s | Type de document (ART pour article, COUV pour couverture, etc.). | | doiId_s | Identifiant DOI associé au document. | | journalPublisher_s | Éditeur du journal dans lequel le document a été publié. | | journalTitle_s | Titre du journal dans lequel le document a été publié. | | journalIssn_s | ISSN du journal dans lequel le document a été publié. | | researchData_s | Identifiants (DOI) des données de recherche associées. | | docSubType_s | Sous doc type: data paper, preprint, ... | ## 5.2 Liste des data papers TETIS {#liste-des-data-papers-tetis} ### 5.2.1 Agritrop & HAL ```{python} #| label: tbl-list-datapepers #| tbl-cap: Liste des data papers TETIS (Agritrop & Hal) #| tbl-colwidths: [45, 45] # display(df_no_duplicates.reset_index()[["titre", "authors"]]) table_to_display = df_no_duplicates.reset_index()[["titre", "authors"]] Markdown(tabulate(table_to_display, headers=table_to_display.keys(), showindex=False)) ``` ### 5.2.2 Seulement Hal | Année de publi | Référence | |----------------|----------------------------| | 2019 | @rabatel_padi-web_2019 | | 2020 | @roche_covid-19_2020 | | 2020 | @stephane_land_2020 | | 2021 | @lentschat_food_2021 | | 2022 | @valentin_elaboration_2022 | | 2022 | @schaeffer_labeled_2022 | | 2022 | @roche_leap4fnssa_2022 | | 2022 | @lentschat_partial_2022 | | 2023 | @auzoux_experimental_2023 | | 2023 | @madec_vegann_2023 | | 2023 | @arinik_annotated_2023 | ## 5.3 Liste des jeux de données TETIS sour Entrepôt Recherche Data Gouv {#liste-des-jeux-de-données-tetis-sour-entrepôt-recherche-data-gouv} ```{python} #| label: tbl-df-rdg #| tbl-cap: Liste des jeux de données TETIS / INRAE display(df_rdg_datasets[["title"]]) # Markdown(tabulate(df_rdg_datasets[["title"]], headers="title", showindex=False)) ``` ## 5.4 Carte des terrains d'étude des data papers ```{python} # Use a pipeline as a high-level helper from transformers import pipeline import requests import folium pipe = pipeline("token-classification", model="dbmdz/bert-large-cased-finetuned-conll03-english", aggregation_strategy="simple") def extract_location(abstract): try: abstract = abstract[0] except: return [] list_of_locations = []
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
ner = pipe(abstract) for entity in ner: if "LOC" in entity["entity_group"]: list_of_locations.append(entity["word"]) return list_of_locations def geocode_with_photon(locations): # Endpoint de l'API Photon pour le géocodage endpoint = "https://photon.komoot.io/api" list_bbox = [] for location in locations: # Paramètres de la requête params = { 'q': location, 'limit': 1 # Limiter à un résultat } # Envoi de la requête à l'API Photon response = requests.get(endpoint, params=params) # Traitement de la réponse JSON data = response.json() # Vérification s'il y a des résultats if len(data) != 0: # Récupération des coordonnées de la bounding box try: bbox_coordinates = data['features'][0]['properties']['extent'] # Renvoyer les coordonnées de la bounding box list_bbox.append(bbox_coordinates) except: pass return list_bbox def plot_map_with_extent(list_bbox): # Calculate center of the bounding box bbox_montpellier_center_map = [3.8070597, 43.653358, 3.9413208, 43.5667083] bbox = bbox_montpellier_center_map center_lat = (bbox[1] + bbox[3]) / 2 center_lon = (bbox[0] + bbox[2]) / 2 # Create a folium map centered at the calculated center m = folium.Map(location=[center_lat, center_lon], zoom_start=1) for bbox in list_bbox: # Add a rectangle to represent the bounding box folium.Rectangle(bounds=[(bbox[1], bbox[0]), (bbox[3], bbox[2])], color='red', fill=True).add_to(m) return m df_no_duplicates["lieux"] = df_no_duplicates["abstract_s"].apply(extract_location) df_no_duplicates["list_bbox"] = df_no_duplicates["lieux"].apply(geocode_with_photon) list_bbox = [item for sublist in df_no_duplicates['list_bbox'] for item in sublist] # map = plot_map_with_extent(list_bbox) ``` ```{python} #| label: fig-map-terrain-etude #| fig-cap: Lieux présents dans les abstract des data papers from mpl_toolkits.basemap import Basemap m = Basemap(projection='mill', llcrnrlat=-90, urcrnrlat=90, llcrnrlon=-180, urcrnrlon=180, resolution='c') for bbox in list_bbox: lons, lats = zip(*[(bbox[0], bbox[1]), (bbox[2], bbox[1]), (bbox[2], bbox[3]), (bbox[0], bbox[3]), (bbox[0], bbox[1])]) x, y = m(lons, lats) m.plot(x, y, marker=None, color='r', linewidth=2)
841842843844845846847848849850851852853
# Show the map m.drawcountries() m.drawcoastlines() plt.show() ``` Pour finir ce rapport, travaillant à TETIS, il est impossible de ne pas ajouter une carte. La @fig-map-terrain-etude propose de visualiser les étendus géographiques des lieux présent dans les *Abstracts* des data papers. Pour obtenir la liste des étendues spatiales, nous appliquons un modèle extraction d'entités nommées basé sur le modèle de langue pré-entrainé *BERT*. Ce modèle extrait une liste des lieux que nous géocodons avec l'API de *Photon* utilisant les données d'*OpenStreetMap*. {{< pagebreak >}} # Bibliography