openalex-metrics 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openalex_metrics-0.1.0/.github/workflows/publish.yml +25 -0
- openalex_metrics-0.1.0/.github/workflows/tests.yml +22 -0
- openalex_metrics-0.1.0/CITATION.cff +17 -0
- openalex_metrics-0.1.0/LICENSE +21 -0
- openalex_metrics-0.1.0/MANIFEST.in +5 -0
- openalex_metrics-0.1.0/PKG-INFO +161 -0
- openalex_metrics-0.1.0/README.md +130 -0
- openalex_metrics-0.1.0/docs/building.md +146 -0
- openalex_metrics-0.1.0/docs/metrics.md +111 -0
- openalex_metrics-0.1.0/docs/openalex-RELEASE_NOTES.txt +302 -0
- openalex_metrics-0.1.0/docs/openalex-parquet-manifest.json +36571 -0
- openalex_metrics-0.1.0/docs/openalex-works-schema.txt +259 -0
- openalex_metrics-0.1.0/docs/querying.md +166 -0
- openalex_metrics-0.1.0/docs/scope.md +116 -0
- openalex_metrics-0.1.0/docs/snapshots.jsonl +4 -0
- openalex_metrics-0.1.0/docs/sources.yaml +72 -0
- openalex_metrics-0.1.0/examples/quickstart.py +28 -0
- openalex_metrics-0.1.0/oam/LICENSE-OpenAlex.txt +121 -0
- openalex_metrics-0.1.0/oam/__init__.py +147 -0
- openalex_metrics-0.1.0/oam/arrow.py +166 -0
- openalex_metrics-0.1.0/oam/bundle.py +540 -0
- openalex_metrics-0.1.0/oam/checks.py +294 -0
- openalex_metrics-0.1.0/oam/config.py +292 -0
- openalex_metrics-0.1.0/oam/duck.py +245 -0
- openalex_metrics-0.1.0/oam/metrics.py +336 -0
- openalex_metrics-0.1.0/oam/openalex.py +75 -0
- openalex_metrics-0.1.0/oam/query.py +229 -0
- openalex_metrics-0.1.0/oam/report.py +200 -0
- openalex_metrics-0.1.0/oam/resolve_store.py +309 -0
- openalex_metrics-0.1.0/oam/resolver.py +311 -0
- openalex_metrics-0.1.0/oam/store.py +647 -0
- openalex_metrics-0.1.0/openalex_metrics.egg-info/PKG-INFO +161 -0
- openalex_metrics-0.1.0/openalex_metrics.egg-info/SOURCES.txt +42 -0
- openalex_metrics-0.1.0/openalex_metrics.egg-info/dependency_links.txt +1 -0
- openalex_metrics-0.1.0/openalex_metrics.egg-info/requires.txt +9 -0
- openalex_metrics-0.1.0/openalex_metrics.egg-info/top_level.txt +1 -0
- openalex_metrics-0.1.0/pyproject.toml +49 -0
- openalex_metrics-0.1.0/setup.cfg +4 -0
- openalex_metrics-0.1.0/tests/conftest.py +192 -0
- openalex_metrics-0.1.0/tests/test_aggregate.py +62 -0
- openalex_metrics-0.1.0/tests/test_bundle.py +171 -0
- openalex_metrics-0.1.0/tests/test_explain_resolve.py +60 -0
- openalex_metrics-0.1.0/tests/test_ids.py +50 -0
- openalex_metrics-0.1.0/tests/test_metrics.py +81 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
pypi:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: pypi
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- name: Build
|
|
21
|
+
run: |
|
|
22
|
+
pip install build
|
|
23
|
+
python -m build
|
|
24
|
+
- name: Publish
|
|
25
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python: ["3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: pip install ".[test,build]"
|
|
21
|
+
- name: Test
|
|
22
|
+
run: pytest
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
title: "OAM: OpenAlex Metrics"
|
|
3
|
+
message: If you use OAM, please cite it using the metadata below.
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Bernard
|
|
7
|
+
given-names: Gael
|
|
8
|
+
affiliation: EPFL
|
|
9
|
+
email: gael.bernard@epfl.ch
|
|
10
|
+
repository-code: https://github.com/epfl-p-data/OpenAlexImpactMetrics
|
|
11
|
+
url: https://github.com/epfl-p-data/OpenAlexImpactMetrics
|
|
12
|
+
license: MIT
|
|
13
|
+
version: 0.1.0
|
|
14
|
+
identifiers:
|
|
15
|
+
- type: doi
|
|
16
|
+
value: 10.5281/zenodo.23493154
|
|
17
|
+
description: "Data release on Zenodo, all versions. Each snapshot has its own version DOI."
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OAM contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openalex-metrics
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Field-normalised citation impact (MNCS, PP top 10%) for any set of OpenAlex works
|
|
5
|
+
Author-email: Gael Bernard <gael.bernard@epfl.ch>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/epfl-p-data/OpenAlexImpactMetrics
|
|
8
|
+
Project-URL: Repository, https://github.com/epfl-p-data/OpenAlexImpactMetrics
|
|
9
|
+
Project-URL: Issues, https://github.com/epfl-p-data/OpenAlexImpactMetrics/issues
|
|
10
|
+
Keywords: bibliometrics,citation impact,OpenAlex,MNCS
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: duckdb>=1.2
|
|
24
|
+
Requires-Dist: pandas>=2.0
|
|
25
|
+
Requires-Dist: requests>=2.31
|
|
26
|
+
Provides-Extra: build
|
|
27
|
+
Requires-Dist: pyarrow>=14; extra == "build"
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# OAM — citation impact from OpenAlex
|
|
33
|
+
|
|
34
|
+
OAM tells you whether a set of papers was cited more or less than comparable papers. You can score a university, a researcher, a country, a funder's portfolio or a list of DOIs. It runs on your own machine, and the data comes from [OpenAlex](https://openalex.org).
|
|
35
|
+
|
|
36
|
+
## Get started
|
|
37
|
+
|
|
38
|
+
Python 3.11 or newer.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install openalex-metrics
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Download the data once. This fetches the latest release from [Zenodo](https://doi.org/10.5281/zenodo.23493154), about 12 GB, and checks every file:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import oam
|
|
48
|
+
|
|
49
|
+
oam.download()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The files go to `~/oam-data`. To put them somewhere else, call `oam.set_data_dir(path)` before downloading, or set the `OAM_DATA_DIR` environment variable. You need about 25 GB free while the archive is unpacked.
|
|
53
|
+
|
|
54
|
+
Then score a set of papers:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
ids = oam.resolve(institution="I5124864", year=(2020, 2024)) # EPFL
|
|
58
|
+
oam.aggregate(ids).round(2)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`oam.resolve` finds the papers. `oam.aggregate` scores the ones that can be compared with similar papers. To find an institution's id, search by name:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
oam.dimension("institutions", search="Lausanne")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Three examples
|
|
68
|
+
|
|
69
|
+
These numbers come from the OpenAlex snapshot of 14 October 2026.
|
|
70
|
+
|
|
71
|
+
### A university, over five years
|
|
72
|
+
|
|
73
|
+
EPFL, 2020 to 2024. Of the 36,475 works with EPFL's name on them, 22,207 are scored.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
ids = oam.resolve(institution="I5124864", year=(2020, 2024))
|
|
77
|
+
oam.aggregate(ids).round(2)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
publication_year P P_weighted TCS MCS MNCS PP_top10 PP_top1
|
|
82
|
+
2020 4433 4433.0 200062.0 45.13 1.95 21.52 3.41
|
|
83
|
+
2021 4498 4498.0 150841.0 33.54 1.80 20.08 3.11
|
|
84
|
+
2022 4349 4349.0 120403.0 27.69 1.89 20.74 2.90
|
|
85
|
+
2023 4470 4470.0 85462.0 19.12 1.74 20.25 3.02
|
|
86
|
+
2024 4457 4457.0 58542.0 13.13 1.83 21.11 3.43
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### A researcher
|
|
90
|
+
|
|
91
|
+
Jennifer Doudna, from her [ORCID](https://orcid.org/0000-0001-9161-999X). Of 755 works, 391 are scored. `by=None` gives one row for the whole set.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
ids = oam.resolve(orcid="0000-0001-9161-999X")
|
|
95
|
+
oam.aggregate(ids, by=None).round(2)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
P P_weighted TCS MCS MNCS PP_top10 PP_top1
|
|
100
|
+
391 391.0 91886.0 235.0 5.97 59.34 14.83
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### A country
|
|
104
|
+
|
|
105
|
+
Switzerland, 2020 to 2024. Of 490,707 works, 265,954 are scored.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
ids = oam.resolve(country="CH", year=(2020, 2024))
|
|
109
|
+
oam.aggregate(ids).round(2)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
publication_year P P_weighted TCS MCS MNCS PP_top10 PP_top1
|
|
114
|
+
2020 52142 52142.0 2108683.0 40.44 1.80 18.78 2.87
|
|
115
|
+
2021 55231 55231.0 1744205.0 31.58 1.78 18.79 3.02
|
|
116
|
+
2022 53311 53311.0 1206510.0 22.63 1.71 18.77 2.88
|
|
117
|
+
2023 52502 52502.0 887357.0 16.90 1.71 19.19 3.08
|
|
118
|
+
2024 52768 52768.0 605921.0 11.48 1.75 19.27 2.99
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
You can select papers the same way by author, funder, journal, field or DOI. [Querying](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/docs/querying.md) lists every option.
|
|
122
|
+
|
|
123
|
+
## Reading the numbers
|
|
124
|
+
|
|
125
|
+
- **P** is the number of papers that received a score.
|
|
126
|
+
- **MNCS** compares those papers with similar papers worldwide. The world average is 1. A value of 2 means cited twice as much.
|
|
127
|
+
- **PP_top10** is the share of papers among the 10% most cited of their group. For the world, that share is about 10%. **PP_top1** is the same for the top 1%.
|
|
128
|
+
|
|
129
|
+
[How the scores work](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/docs/metrics.md) explains the other columns, and full and fractional counting.
|
|
130
|
+
|
|
131
|
+
## How this differs from OpenAlex
|
|
132
|
+
|
|
133
|
+
OpenAlex already gives most works a citation score, the FWCI. It compares each work with everything OpenAlex indexes, and it counts citations from anything, including preprints, datasets and theses. OAM compares a paper only with peer-reviewed research papers like it, and counts only the citations that come from such papers.
|
|
134
|
+
|
|
135
|
+
## Which papers are scored
|
|
136
|
+
|
|
137
|
+
A paper is scored when it passes all seven rules. On the snapshot of 14 October 2026, 62,493,369 of 464,765,485 works pass, which is 13.45%.
|
|
138
|
+
|
|
139
|
+
1. It is in a core journal or a conference proceedings.
|
|
140
|
+
2. It has a reference list.
|
|
141
|
+
3. It has at least one institutional affiliation.
|
|
142
|
+
4. It is in English.
|
|
143
|
+
5. It has an OpenAlex subfield.
|
|
144
|
+
6. It is not paratext, such as an editorial, erratum or table of contents.
|
|
145
|
+
7. It is not retracted.
|
|
146
|
+
|
|
147
|
+
Each paper is then compared with papers of the same subfield, publication year and venue type (journal or conference).
|
|
148
|
+
|
|
149
|
+
These rules are fixed for the data you download. To score a different set of papers, for example to keep papers in every language, edit the rules and rebuild the data. [Scope](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/docs/scope.md) explains each rule, and [Building the data](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/docs/building.md) explains the rebuild.
|
|
150
|
+
|
|
151
|
+
## Data and licence
|
|
152
|
+
|
|
153
|
+
The data is built from the public OpenAlex snapshot and released under CC0. `oam.download()` installs the latest version. To keep one snapshot, pass its Zenodo record id, listed under Versions on the [Zenodo page](https://doi.org/10.5281/zenodo.23493154):
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
oam.download(record=23493155) # always the 2026-10-14 snapshot
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If you publish results, cite the version you used. Each version has its own DOI, and `oam.status()` shows which snapshot is installed.
|
|
160
|
+
|
|
161
|
+
OAM is released under the MIT licence. See [LICENSE](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/LICENSE).
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# OAM — citation impact from OpenAlex
|
|
2
|
+
|
|
3
|
+
OAM tells you whether a set of papers was cited more or less than comparable papers. You can score a university, a researcher, a country, a funder's portfolio or a list of DOIs. It runs on your own machine, and the data comes from [OpenAlex](https://openalex.org).
|
|
4
|
+
|
|
5
|
+
## Get started
|
|
6
|
+
|
|
7
|
+
Python 3.11 or newer.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install openalex-metrics
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Download the data once. This fetches the latest release from [Zenodo](https://doi.org/10.5281/zenodo.23493154), about 12 GB, and checks every file:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import oam
|
|
17
|
+
|
|
18
|
+
oam.download()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The files go to `~/oam-data`. To put them somewhere else, call `oam.set_data_dir(path)` before downloading, or set the `OAM_DATA_DIR` environment variable. You need about 25 GB free while the archive is unpacked.
|
|
22
|
+
|
|
23
|
+
Then score a set of papers:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
ids = oam.resolve(institution="I5124864", year=(2020, 2024)) # EPFL
|
|
27
|
+
oam.aggregate(ids).round(2)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`oam.resolve` finds the papers. `oam.aggregate` scores the ones that can be compared with similar papers. To find an institution's id, search by name:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
oam.dimension("institutions", search="Lausanne")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Three examples
|
|
37
|
+
|
|
38
|
+
These numbers come from the OpenAlex snapshot of 14 October 2026.
|
|
39
|
+
|
|
40
|
+
### A university, over five years
|
|
41
|
+
|
|
42
|
+
EPFL, 2020 to 2024. Of the 36,475 works with EPFL's name on them, 22,207 are scored.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
ids = oam.resolve(institution="I5124864", year=(2020, 2024))
|
|
46
|
+
oam.aggregate(ids).round(2)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
publication_year P P_weighted TCS MCS MNCS PP_top10 PP_top1
|
|
51
|
+
2020 4433 4433.0 200062.0 45.13 1.95 21.52 3.41
|
|
52
|
+
2021 4498 4498.0 150841.0 33.54 1.80 20.08 3.11
|
|
53
|
+
2022 4349 4349.0 120403.0 27.69 1.89 20.74 2.90
|
|
54
|
+
2023 4470 4470.0 85462.0 19.12 1.74 20.25 3.02
|
|
55
|
+
2024 4457 4457.0 58542.0 13.13 1.83 21.11 3.43
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### A researcher
|
|
59
|
+
|
|
60
|
+
Jennifer Doudna, from her [ORCID](https://orcid.org/0000-0001-9161-999X). Of 755 works, 391 are scored. `by=None` gives one row for the whole set.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
ids = oam.resolve(orcid="0000-0001-9161-999X")
|
|
64
|
+
oam.aggregate(ids, by=None).round(2)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
P P_weighted TCS MCS MNCS PP_top10 PP_top1
|
|
69
|
+
391 391.0 91886.0 235.0 5.97 59.34 14.83
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### A country
|
|
73
|
+
|
|
74
|
+
Switzerland, 2020 to 2024. Of 490,707 works, 265,954 are scored.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
ids = oam.resolve(country="CH", year=(2020, 2024))
|
|
78
|
+
oam.aggregate(ids).round(2)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
publication_year P P_weighted TCS MCS MNCS PP_top10 PP_top1
|
|
83
|
+
2020 52142 52142.0 2108683.0 40.44 1.80 18.78 2.87
|
|
84
|
+
2021 55231 55231.0 1744205.0 31.58 1.78 18.79 3.02
|
|
85
|
+
2022 53311 53311.0 1206510.0 22.63 1.71 18.77 2.88
|
|
86
|
+
2023 52502 52502.0 887357.0 16.90 1.71 19.19 3.08
|
|
87
|
+
2024 52768 52768.0 605921.0 11.48 1.75 19.27 2.99
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
You can select papers the same way by author, funder, journal, field or DOI. [Querying](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/docs/querying.md) lists every option.
|
|
91
|
+
|
|
92
|
+
## Reading the numbers
|
|
93
|
+
|
|
94
|
+
- **P** is the number of papers that received a score.
|
|
95
|
+
- **MNCS** compares those papers with similar papers worldwide. The world average is 1. A value of 2 means cited twice as much.
|
|
96
|
+
- **PP_top10** is the share of papers among the 10% most cited of their group. For the world, that share is about 10%. **PP_top1** is the same for the top 1%.
|
|
97
|
+
|
|
98
|
+
[How the scores work](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/docs/metrics.md) explains the other columns, and full and fractional counting.
|
|
99
|
+
|
|
100
|
+
## How this differs from OpenAlex
|
|
101
|
+
|
|
102
|
+
OpenAlex already gives most works a citation score, the FWCI. It compares each work with everything OpenAlex indexes, and it counts citations from anything, including preprints, datasets and theses. OAM compares a paper only with peer-reviewed research papers like it, and counts only the citations that come from such papers.
|
|
103
|
+
|
|
104
|
+
## Which papers are scored
|
|
105
|
+
|
|
106
|
+
A paper is scored when it passes all seven rules. On the snapshot of 14 October 2026, 62,493,369 of 464,765,485 works pass, which is 13.45%.
|
|
107
|
+
|
|
108
|
+
1. It is in a core journal or a conference proceedings.
|
|
109
|
+
2. It has a reference list.
|
|
110
|
+
3. It has at least one institutional affiliation.
|
|
111
|
+
4. It is in English.
|
|
112
|
+
5. It has an OpenAlex subfield.
|
|
113
|
+
6. It is not paratext, such as an editorial, erratum or table of contents.
|
|
114
|
+
7. It is not retracted.
|
|
115
|
+
|
|
116
|
+
Each paper is then compared with papers of the same subfield, publication year and venue type (journal or conference).
|
|
117
|
+
|
|
118
|
+
These rules are fixed for the data you download. To score a different set of papers, for example to keep papers in every language, edit the rules and rebuild the data. [Scope](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/docs/scope.md) explains each rule, and [Building the data](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/docs/building.md) explains the rebuild.
|
|
119
|
+
|
|
120
|
+
## Data and licence
|
|
121
|
+
|
|
122
|
+
The data is built from the public OpenAlex snapshot and released under CC0. `oam.download()` installs the latest version. To keep one snapshot, pass its Zenodo record id, listed under Versions on the [Zenodo page](https://doi.org/10.5281/zenodo.23493154):
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
oam.download(record=23493155) # always the 2026-10-14 snapshot
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If you publish results, cite the version you used. Each version has its own DOI, and `oam.status()` shows which snapshot is installed.
|
|
129
|
+
|
|
130
|
+
OAM is released under the MIT licence. See [LICENSE](https://github.com/epfl-p-data/OpenAlexImpactMetrics/blob/main/LICENSE).
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Building the data
|
|
2
|
+
|
|
3
|
+
Most users download the data with `oam.download()` and never build it. Build it yourself when you want:
|
|
4
|
+
|
|
5
|
+
- a different scope, for example keeping non-English papers
|
|
6
|
+
- a newer OpenAlex snapshot than the published release
|
|
7
|
+
- to publish a release for others to download
|
|
8
|
+
|
|
9
|
+
## What you need
|
|
10
|
+
|
|
11
|
+
- `pip install "openalex-metrics[build]"`. The `build` extra installs PyArrow, which step 1 uses to read the snapshot. Downloading the data does not need it.
|
|
12
|
+
- About 60 GB of free disk space during the build, and about 23 GB once it is finished
|
|
13
|
+
- A few hours. The first step downloads about 67 GB from the public OpenAlex snapshot, so its duration is set by your bandwidth: about 1 hour 40 minutes at 100 Mbit/s
|
|
14
|
+
- No account or API key. The snapshot is public
|
|
15
|
+
|
|
16
|
+
## Build
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import oam
|
|
20
|
+
|
|
21
|
+
oam.set_data_dir("/Volumes/data/oam") # optional: somewhere with enough space
|
|
22
|
+
oam.build()
|
|
23
|
+
oam.status()
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`oam.build()` can be interrupted and called again; it continues where it stopped. Everything it prints is also saved under `logs/` in the data directory.
|
|
27
|
+
|
|
28
|
+
It runs three steps in order:
|
|
29
|
+
|
|
30
|
+
| Step | What it does | Time |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `"store"` | Reads the OpenAlex snapshot, applies the scope, keeps the citations between in-scope papers | Your bandwidth |
|
|
33
|
+
| `"metrics"` | Computes citation counts, bin averages and every paper's scores | About 2 minutes |
|
|
34
|
+
| `"resolve"` | Builds the lookup tables `oam.resolve` uses | About 25 minutes |
|
|
35
|
+
|
|
36
|
+
To run one step on its own, pass `steps="metrics"`, or a list such as `steps=["metrics", "resolve"]`.
|
|
37
|
+
|
|
38
|
+
The download uses 8 parallel connections. If your connection is fast, raise it until the speed in the log stops improving; each connection uses about 1 GB of memory:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
oam.build(workers=16)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Check the result
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
oam.check() # consistency checks, a few seconds
|
|
48
|
+
oam.profile() # what the data contains: scope funnel, citations, world averages
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`oam.profile()` must show a world MNCS of **1.0000**. That confirms each paper was divided by its own bin's average.
|
|
52
|
+
|
|
53
|
+
## Change the scope
|
|
54
|
+
|
|
55
|
+
1. Edit `SCOPE` in [`oam/config.py`](../oam/config.py). Each rule is a line of SQL. [Scope](scope.md) documents the defaults and how to write a rule.
|
|
56
|
+
2. Rebuild from scratch:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
oam.build(reset=True)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A scope cannot be changed on existing data: the build stored only the papers and citations the old scope kept. `oam.build()` detects an edited scope and stops with a message rather than mixing the two.
|
|
63
|
+
|
|
64
|
+
## Move to a new snapshot
|
|
65
|
+
|
|
66
|
+
OpenAlex publishes a new snapshot about once a quarter. To use it:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
oam.build(reset=True, refresh_manifest=True)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`refresh_manifest=True` fetches the list of files of the current snapshot. `reset=True` deletes the data built from the previous one. Data from two snapshots cannot be merged.
|
|
73
|
+
|
|
74
|
+
## Publish a release
|
|
75
|
+
|
|
76
|
+
From a finished build, write the archive `oam.download()` installs:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
oam.export("dist") # dist/oam-<snapshot>.tar, 11.9 GiB for the 2026-10-14 snapshot
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then upload the file to [Zenodo](https://zenodo.org) under the CC0 licence, as a new version of the [existing record](https://doi.org/10.5281/zenodo.23493154). Set the version to the snapshot date. Keep the file name `oam.export` wrote: `oam.download()` finds the file whose name starts with `oam-` and ends with `.tar`, and reads the snapshot date from it.
|
|
83
|
+
|
|
84
|
+
Nothing in OAM needs to change. `ZENODO_RECORD` in [`oam/config.py`](../oam/config.py) holds the record's all-versions id, `23493154`, which Zenodo resolves to the newest version, so every install of OAM downloads the new snapshot from then on.
|
|
85
|
+
|
|
86
|
+
The archive records its snapshot date, its scope and a checksum of every file. `oam.download()` checks all three. It refuses to mix two snapshots in one data directory unless called with `replace=True`.
|
|
87
|
+
|
|
88
|
+
To share a build without Zenodo, send the archive file itself. The recipient installs it with:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
oam.download(source="oam-2026-10-14.tar")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
To publish under a record of your own, set `OAM_ZENODO_RECORD` to its all-versions id, or pass `record=` to `oam.download()`.
|
|
95
|
+
|
|
96
|
+
## Other settings
|
|
97
|
+
|
|
98
|
+
| Environment variable | Default | Effect |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `OAM_DATA_DIR` | `~/oam-data`, or `./data` in a clone | Where the data is stored |
|
|
101
|
+
| `OAM_ZENODO_RECORD` | `23493154`, the latest version | The Zenodo record `oam.download()` fetches. A version's own id pins that snapshot |
|
|
102
|
+
| `OAM_MEMORY_LIMIT` | `20GB` | Memory DuckDB may use in the metrics and resolve steps |
|
|
103
|
+
| `OAM_THREADS` | `8` | Threads DuckDB may use |
|
|
104
|
+
|
|
105
|
+
Set them in the environment before starting Python.
|
|
106
|
+
|
|
107
|
+
To try the whole build on a few files first. The result is not usable for scoring:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
oam.set_data_dir("/tmp/oam-trial")
|
|
111
|
+
oam.build(steps="store", limit=3, smallest=True)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
To keep the xpack records, a third of OpenAlex that the default build leaves out of the comparison, use `oam.build(include_xpack=True)`. See [Scope](scope.md#what-is-not-configurable).
|
|
115
|
+
|
|
116
|
+
## Release checklist
|
|
117
|
+
|
|
118
|
+
The uploads themselves are done by hand.
|
|
119
|
+
|
|
120
|
+
1. `oam.export("dist")` from a finished build, then install that archive into an empty directory and re-run the examples in the README.
|
|
121
|
+
2. On the [Zenodo record](https://doi.org/10.5281/zenodo.23493154), click New version. Set the version and the collected date to the snapshot date, the publication date to today, and update the snapshot figures in the description.
|
|
122
|
+
3. Upload `dist/oam-<snapshot>.tar`, keeping the file name `oam.export` wrote. The browser upload often stalls on a file this size; the API is more reliable. Create a token with the `deposit:write` and `deposit:actions` scopes, and run these with the draft's id from the address bar:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
API=https://zenodo.org/api/records/<draft id>/draft/files
|
|
126
|
+
NAME=oam-<snapshot>.tar
|
|
127
|
+
curl -f -X POST -H "Authorization: Bearer $ZENODO_TOKEN" -H "Content-Type: application/json" -d "[{\"key\":\"$NAME\"}]" "$API"
|
|
128
|
+
curl -f -X PUT -H "Authorization: Bearer $ZENODO_TOKEN" -H "Content-Type: application/octet-stream" --upload-file "dist/$NAME" "$API/$NAME/content"
|
|
129
|
+
curl -f -X POST -H "Authorization: Bearer $ZENODO_TOKEN" "$API/$NAME/commit"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The checksum Zenodo shows must equal `md5 dist/oam-<snapshot>.tar`. Then publish.
|
|
133
|
+
4. Add the snapshot to the README figures. `ZENODO_RECORD` does not change: it already points at the newest version.
|
|
134
|
+
|
|
135
|
+
A new version of the package is only needed when the code changes. A new snapshot does not need one: `oam.download()` always fetches the latest version on Zenodo.
|
|
136
|
+
|
|
137
|
+
## Release the package
|
|
138
|
+
|
|
139
|
+
Publishing happens from GitHub, with no token stored in the repository. The first time, on [PyPI](https://pypi.org), add a pending publisher for the project `openalex-metrics`: owner `epfl-p-data`, repository `OpenAlexImpactMetrics`, workflow `publish.yml`, environment `pypi`.
|
|
140
|
+
|
|
141
|
+
Then, for each release:
|
|
142
|
+
|
|
143
|
+
1. Set `version` in [`pyproject.toml`](../pyproject.toml) and in [`CITATION.cff`](../CITATION.cff), and push.
|
|
144
|
+
2. On GitHub, create a release named `v` followed by that version, for example `v0.1.0`.
|
|
145
|
+
|
|
146
|
+
The [publish workflow](../.github/workflows/publish.yml) builds the package and uploads it to PyPI.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# How the scores work
|
|
2
|
+
|
|
3
|
+
OAM compares every paper with papers like it, then summarises those comparisons for a set of papers. This page starts with one paper, then moves to a set.
|
|
4
|
+
|
|
5
|
+
## Comparable papers: the bin
|
|
6
|
+
|
|
7
|
+
A paper is compared with the papers that share three things with it:
|
|
8
|
+
|
|
9
|
+
- its **subfield**, as assigned by OpenAlex (for example Artificial Intelligence)
|
|
10
|
+
- its **publication year**
|
|
11
|
+
- its **venue type**: journal article or conference paper
|
|
12
|
+
|
|
13
|
+
That group is the paper's **bin**. Citation habits differ a lot between fields and years, so comparing within the bin is what makes a mathematics paper from 2014 comparable with a biology paper from 2022.
|
|
14
|
+
|
|
15
|
+
## One paper
|
|
16
|
+
|
|
17
|
+
Take `W2103094658`, a 2014 conference paper in Artificial Intelligence. It has 37 citations. Its bin holds 14,122 papers, which have about 18 citations on average.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
oam.get_metrics(["W2103094658"])
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
citations ncs percentile decile is_top10 is_top1
|
|
25
|
+
37 2.05 0.94 1 True False
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Column | Meaning | This paper |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `citations` | Citations received so far | 37 |
|
|
31
|
+
| `ncs` | Citations divided by the bin's average. 1 means exactly average | 2.05: twice the average |
|
|
32
|
+
| `percentile` | Share of the bin with as many citations or fewer, from 0 to 1 | 0.94: ahead of about 94% of its bin |
|
|
33
|
+
| `decile` | Rank in tenths of the bin: 1 is the top tenth, 10 the bottom | 1: top tenth |
|
|
34
|
+
| `is_top10` | Among the 10% most cited papers of the bin | yes |
|
|
35
|
+
| `is_top1` | Among the 1% most cited papers of the bin | no |
|
|
36
|
+
|
|
37
|
+
`ncs` stands for *normalised citation score*. It is the number the set-level MNCS is built from.
|
|
38
|
+
|
|
39
|
+
## A set of papers
|
|
40
|
+
|
|
41
|
+
For a set of papers — an institution, a lab, a funder's portfolio — OAM summarises the per-paper scores:
|
|
42
|
+
|
|
43
|
+
| Column | Meaning |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `P` | Number of papers scored |
|
|
46
|
+
| `MNCS` | Average `ncs`. 1 is the world average |
|
|
47
|
+
| `PP_top10` | Percentage of papers with `is_top10`. About 10 for the world |
|
|
48
|
+
| `PP_top1` | Percentage of papers with `is_top1`. About 1 for the world |
|
|
49
|
+
| `TCS` | Total citations |
|
|
50
|
+
| `MCS` | Average citations per paper, not normalised |
|
|
51
|
+
| `P_weighted` | Sum of the paper weights. Equals `P` unless you use fractional counting (below) |
|
|
52
|
+
|
|
53
|
+
EPFL in 2014:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
ids = oam.resolve(institution="I5124864", year=2014)
|
|
57
|
+
oam.aggregate(ids)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
publication_year P P_weighted TCS MCS MNCS PP_top10 PP_top1
|
|
62
|
+
2014 4439 4439.0 264771 59.65 1.962 21.83 3.18
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`aggregate` returns one row per publication year by default. Pass `by=None` for a single row covering the whole set.
|
|
66
|
+
|
|
67
|
+
- MNCS 1.96: on average, an EPFL paper from 2014 was cited 1.96 times as much as a typical paper of its bin.
|
|
68
|
+
- PP(top 10%) 21.8%: more than twice the world share.
|
|
69
|
+
- PP(top 1%) 3.2%: about three times the world share.
|
|
70
|
+
|
|
71
|
+
The percentile and the decile are per-paper columns. The summary does not average them; PP(top 10%) and PP(top 1%) are how the ranking shows up at the set level.
|
|
72
|
+
|
|
73
|
+
The world values, measured on the 2014–2023 papers: MNCS 1.00, PP(top 10%) 10.27%, PP(top 1%) 1.01%. The top-10% share is slightly above 10 because papers tied at the threshold are all counted in.
|
|
74
|
+
|
|
75
|
+
## Full and fractional counting
|
|
76
|
+
|
|
77
|
+
By default every paper counts once, whatever the number of institutions on it. This is **full counting**, and it is what the EPFL numbers above use.
|
|
78
|
+
|
|
79
|
+
With **fractional counting**, a paper counts as `1 / number of institutions`. A paper written with nine other institutions counts as a tenth of a paper for EPFL. Use it when you compare institutions and do not want a shared paper to be counted in full by everyone who shares it.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
oam.aggregate(ids, counting="full") # the default
|
|
83
|
+
oam.aggregate(ids, counting="frac")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
EPFL in 2014, fractional counting:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
publication_year P P_weighted TCS MCS MNCS PP_top10 PP_top1
|
|
90
|
+
2014 4439 2118.3 108728 51.33 1.738 19.15 2.72
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`P` is still 4,439 papers. `P_weighted` is the sum of the fractions, 2,118. Every other column is a weighted average, so papers shared with many institutions weigh less. Here MNCS falls from 1.96 to 1.74, because EPFL's papers with five or more institutions are also its most cited: their average `ncs` is 2.67, against 1.75 for the others.
|
|
94
|
+
|
|
95
|
+
## Which papers are scored
|
|
96
|
+
|
|
97
|
+
EPFL's name is on 6,223 works from 2014, and 4,439 are scored. Two things remove the others.
|
|
98
|
+
|
|
99
|
+
**Scope.** Only papers inside the scope get a score: by default, English-language articles in a core journal or a conference proceedings, with an institutional affiliation, a subfield and a reference list, and not retracted or front matter. 4,511 of the 6,223 pass. The rest are preprints, datasets, book chapters and similar. Scope also decides which citations count: a citation is only counted when the citing paper is itself in scope. See [Scope](scope.md) for the rules, and [Querying](querying.md#which-papers-are-scored) to see which rule removed which paper.
|
|
100
|
+
|
|
101
|
+
**Small bins.** A bin with fewer than 500 papers has an unstable average. Its papers still get an `ncs`, with `bin_is_reliable` set to false, but `aggregate` leaves them out. 72 of the 4,511 are in such bins. To keep them, pass `reliable_only=False`.
|
|
102
|
+
|
|
103
|
+
## Citation window
|
|
104
|
+
|
|
105
|
+
By default a paper's citations are everything it has received up to the snapshot. A paper from 2014 has had twelve years to collect citations and a paper from 2023 only three, although both are compared within their own year, so the scores stay comparable.
|
|
106
|
+
|
|
107
|
+
To count only the citations received within three years of publication, pass `window=3`. This is the usual choice when you compare recent years with older ones.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
oam.aggregate(ids, window=3)
|
|
111
|
+
```
|