An error occurred while loading the file. Please try again.
-
Rémy Decoupes authored232b5049
---
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
---
{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** | [{width="100px"}](https://hal.science/TETIS/) | [{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** | [{width="100px"}](https://entrepot.recherche.data.gouv.fr/dataverse/tetis) | [{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