Skip to content

Commit 763ea94

Browse files
authored
Add function to retrive citations for arviz or methods implemented in arviz (#77)
* add function to retrive citations for arviz or methods implemented in arviz * improve docs and bibtext format * update api reference * remove execution from docstring
1 parent 27c9a57 commit 763ea94

File tree

6 files changed

+424
-0
lines changed

6 files changed

+424
-0
lines changed

docs/source/api/index.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ More coming soon...
4646
arviz_base.xarray_var_iter
4747
```
4848

49+
## How to cite ArviZ and implemented methods
50+
51+
```{eval-rst}
52+
.. autosummary::
53+
:toctree: generated/
54+
55+
arviz_base.citations
56+
```
57+
4958

5059
## Example datasets
5160
The behaviour of the functions in this section is partially controlled by the

src/arviz_base/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from arviz_base._version import __version__
99
from arviz_base.base import dict_to_dataset, generate_dims_coords, make_attrs, ndarray_to_dataarray
10+
from arviz_base.citations import citations
1011
from arviz_base.converters import convert_to_dataset, convert_to_datatree
1112
from arviz_base.datasets import clear_data_home, get_data_home, list_datasets, load_arviz_data
1213
from arviz_base.io_cmdstanpy import from_cmdstanpy
@@ -29,6 +30,7 @@
2930
__all__ = [
3031
"__version__",
3132
# base
33+
"citations",
3234
"dict_to_dataset",
3335
"generate_dims_coords",
3436
"make_attrs",

src/arviz_base/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ from arviz_base.base import (
1414
make_attrs,
1515
ndarray_to_dataarray,
1616
)
17+
from arviz_base.citations import citations
1718
from arviz_base.converters import convert_to_dataset, convert_to_datatree
1819
from arviz_base.datasets import (
1920
clear_data_home,
@@ -38,6 +39,7 @@ from arviz_base.transform import get_unconstrained_samples
3839

3940
__all__ = [
4041
"__version__",
42+
"citations",
4143
"dict_to_dataset",
4244
"generate_dims_coords",
4345
"make_attrs",

src/arviz_base/citations.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""How to cite ArviZ and its methods."""
2+
3+
import os
4+
import re
5+
6+
7+
def citations(methods=None, filepath=None, format_type="bibtex"):
8+
"""
9+
List citations for ArviZ and the methods implemented in ArviZ.
10+
11+
Parameters
12+
----------
13+
methods : List
14+
Methods implemented in ArviZ from which to retrieve citations.
15+
filepath : str, optional
16+
Specifies the location to save the file with the citations.
17+
If ``None``, the result is returned as a string.
18+
format_type : str
19+
Specifies in which format the references will be displayed.
20+
Currently, only "bibtex" is supported.
21+
22+
Examples
23+
--------
24+
>>> from arviz_base import citations
25+
>>> from arviz_stats import rhat
26+
>>> citations(methods=[rhat]) # Returns how to cite ArviZ and rhat
27+
>>> citations() # Returns how to cite ArviZ
28+
"""
29+
method_citations = [{"doi": "10.21105/joss.XXXXX"}]
30+
if methods is not None:
31+
for method in methods:
32+
_extract_ids_per_entry(method_citations, method.__doc__)
33+
34+
if format_type == "bibtex":
35+
header = _get_header(methods)
36+
citation_text = _find_bibtex_entries(header, method_citations)
37+
if filepath:
38+
with open(filepath, "w") as fw:
39+
fw.write(citation_text)
40+
else:
41+
return citation_text
42+
else:
43+
raise ValueError("Invalid value for format_type. Use 'bibtex'.")
44+
45+
46+
def _extract_ids_per_entry(data, text):
47+
entries = re.split(r"\n\s*\.\. \[\d+\] ", text.strip())
48+
49+
doi_pattern = re.compile(r"https?://doi\.org/(\S+)", re.IGNORECASE)
50+
url_pattern = re.compile(r"https?://(?!doi\.org)(\S+)", re.IGNORECASE)
51+
52+
for entry in entries:
53+
doi_match = doi_pattern.search(entry)
54+
if doi_match:
55+
doi = doi_match.group(1).rstrip(".")
56+
data.append({"doi": doi})
57+
else:
58+
urls = [url.rstrip(".") for url in url_pattern.findall(entry)]
59+
if urls:
60+
data.append({"urls": urls})
61+
return data
62+
63+
64+
def _find_bibtex_entries(header, data):
65+
ref_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "references.bib")
66+
with open(ref_path, encoding="utf-8") as fr:
67+
bibtex_text = fr.read()
68+
69+
entries = re.split(r"\n(?=@)", bibtex_text)
70+
71+
doi_field_pattern = re.compile(r'doi\s*=\s*[{"]([^}"]+)[}"]', re.IGNORECASE)
72+
url_field_pattern = re.compile(r'url\s*=\s*[{"]([^}"]+)[}"]', re.IGNORECASE)
73+
74+
references = [header]
75+
for identifier in data:
76+
found_entry = ""
77+
for entry in entries:
78+
bib_dois = doi_field_pattern.findall(entry)
79+
bib_urls = url_field_pattern.findall(entry)
80+
81+
if "doi" in identifier and any(identifier["doi"] in doi for doi in bib_dois):
82+
found_entry = entry.strip()
83+
break
84+
85+
if "urls" in identifier and any(
86+
any(url in b_url or b_url in url for b_url in bib_urls)
87+
for url in identifier["urls"]
88+
):
89+
found_entry = entry.strip()
90+
break
91+
if found_entry:
92+
if found_entry not in references:
93+
references.append(found_entry)
94+
95+
return "\n\n".join(references)
96+
97+
98+
def _get_header(methods=None):
99+
references = "Bibtex format citations for ArviZ paper\n"
100+
101+
if methods is not None:
102+
methods_str = ", ".join([method.__name__ for method in methods])
103+
references = references.strip() + f", and\nfor the following methods: {methods_str}"
104+
105+
return references

src/arviz_base/citations.pyi

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# File generated with docstub
2+
3+
import os
4+
import re
5+
from typing import List
6+
7+
from _typeshed import Incomplete
8+
9+
def citations(
10+
methods: List | None = ..., filepath: str | None = ..., format_type: str = ...
11+
) -> None: ...
12+
def _extract_ids_per_entry(data: Incomplete, text: Incomplete) -> None: ...
13+
def _find_bibtex_entries(header: Incomplete, data: Incomplete) -> None: ...
14+
def _get_header(methods: Incomplete = ...) -> None: ...

0 commit comments

Comments
 (0)