Find journals to submit to — by research area, by similarity to a journal you already like, or straight from your abstract — and see, for each one, an open impact metric and its open-access business model: diamond, gold, hybrid, bronze or subscription.
$ impyctfactor -area "computational linguistics" --oa diamond --top 5
Journals publishing on: computational linguistics
┏━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━┳━━━━━━━━━┓
┃ # ┃ Journal ┃ 2yMC ┃ OA type ┃ APC US$ ┃ Rel. ┃ Papers ┃
┡━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━╇━━━━━━━━━┩
│ 1 │ Computational Linguistics │ 12.81│ diamond │ none │ 0.81 │ 111/182 │
│ 2 │ Transactions of the ACL │ 12.26│ diamond │ none │ 0.86 │ 256/435 │
│ 3 │ Journal of Artificial Intelligence Research│ 4.08│ diamond │ none │ 0.43 │ 97/548 │
│ 4 │ Open Mind │ 2.71│ diamond │ none │ 0.53 │ 71/251 │
│ 5 │ Linguistics │ 1.46│ diamond │ none │ 0.49 │ 53/198 │
└─────┴────────────────────────────────────────────┴──────┴──────────┴─────────┴──────┴─────────┘
The JIF is Clarivate's, computed on Web of Science data and distributed under licence through Journal Citation Reports. There is no open API, and impyctfactor will not scrape it. But if you have JCR access, impyctfactor will show you the genuine JIF — you export it once, the tool reads it locally:
- open JCR (in Brazil: CAPES Periódicos → Lista de bases e coleções → JCR — by campus IP, or via CAFe from home);
- go to the Journals list, filter by category/edition/year if you like;
- Export → CSV;
- run impyctfactor with that file:
impyctfactor -area "structural bioinformatics" --jcr JCR_JournalResults.csv
export IMPYCTFACTOR_JCR=~/JCR_JournalResults.csv # or set it once and forget it┃ # ┃ Journal ┃ JIF ┃ Q ┃ 2yMC ┃ OA type ┃ APC US$ ┃
│ 1 │ Genome biology │ - │ - │ 9.03 │ gold │ 5,065 │
│ 2 │ Briefings in Bioinformatics │ 7.3 │ 1 │ 6.62 │ gold │ 3,885 │
│ 3 │ Bioinformatics │ 5.5 │ 1 │ 5.93 │ gold │ 3,798 │
The JIF column is the real one, with its JCR quartile in Q. A blank JIF
means the journal is not in your export — JCR caps one export at 600 rows —
not that its impact is low; the footer always reports the match rate. Pass
--jcr more than once to combine exports. --quartile Q1 Q2 and --sort jif
then work on real JCR data. The file never leaves your machine.
Wikipedia's {{Infobox journal}} template carries impact and impact-year,
sourced to JCR, and it has a real API (CC BY-SA — no scraping, no ToS problem):
impyctfactor -area "structural bioinformatics" --wikipedia┃ # ┃ Journal ┃ JIF ┃ yr ┃ 2yMC ┃ OA type ┃
│ 1 │ Genome biology │ 9.2* │ 2025 │ 9.04 │ gold │
│ 2 │ Journal of Cheminformatics │ 7.9* │ 2025 │ 7.63 │ gold │
│ 3 │ Briefings in Bioinformatics │ 7.3* │ 2025 │ 6.65 │ gold │
│ 4 │ Bioinformatics │ 6.9* │ 2020 │ 5.95 │ gold │ <- stale, shown yellow
Measured on 14 journals, 2026-07-28 — read this before trusting a value:
| Coverage | 10/14 (71%) — the rest have no article or no impact field |
| Checked against publisher-stated JIFs | 2/5 matched exactly |
| The other 3 | correct for the year they state, but the page was out of date: Bioinformatics 6.937 (2020) vs 5.5 (2025); Mol Biol Evol 11.0 (2023) vs 5.3 (2024); Genome Biol Evol 3.3 (2022) vs 3.1 (2023) |
So these are not fabricated numbers, they are potentially stale ones. Hence:
every value is marked *, the yr column shows the year it refers to, anything
older than two years is highlighted, and a --jcr value always wins. Verify
anything you rely on.
Because it does not work. Measured 2026-07-28, a scripted request for the journal's own "About" page returns:
| Publisher | Result |
|---|---|
| ACS, Wiley, Cell Press, MDPI, Oxford University Press | HTTP 403 (Cloudflare) |
| Springer, Nature, PLOS | 200, but no extractable impact factor |
| Frontiers | 200, several candidate numbers, none unambiguously the JIF |
5 of 9 hard-blocked, 1 of 9 usable-but-ambiguous. And the aggregator sites that rank highly on a web search for "journal name impact factor" are a different problem: many are stale, several are wrong, and some invent "impact factors" outright. A wrong JIF in your table is worse than a blank one, so impyctfactor will not read from them.
Without any of the above you get open approximations, described by
impyctfactor --help-metrics and measured — not asserted — under
Calibration. They are labelled
2yMC/IF*, never "JIF".
git clone <this repo> && cd impyctfactor
pip install -e ".[pretty]" # 'pretty' pulls in rich for the coloured table
# Get a free OpenAlex API key first - 30 seconds, no card:
# https://openalex.org/settings/api
# It matters: OpenAlex meters queries at $0.10/day without a key and $1/day
# with one, so the key is 10x the daily allowance. Save both settings once:
impyctfactor --mailto "you@university.edu" --api-key "YOUR_KEY" --save-configOpenAlex moved to usage-based pricing. Measured against the live API on 2026-07-30, and confirmed against their docs:
| Without a key | With a free key | |
|---|---|---|
| Daily budget | $0.10 | $1.00 |
Keyword searches ($1/1,000) |
~100/day | ~1,000/day |
List + filter ($0.10/1,000) |
~1,000/day | ~10,000/day |
| Fetching one journal by id | free | free |
The budget resets at midnight UTC. Every run prints what it used and what is left; below 100 remaining you get a warning. If it runs out, impyctfactor says so plainly with the reset time instead of retrying pointlessly — retrying cannot buy budget.
Two things protect the allowance: responses are cached for 7 days, so
repeating or refining a query is free, and the cache key deliberately excludes
your API key and e-mail, so adding or changing them does not throw away a cache
that cost real budget. Avoid --no-cache unless you mean it.
Requires Python ≥ 3.10. The only hard dependency is requests.
--mailto can also be given per run, or set as IMPYCTFACTOR_MAILTO. Precedence is
flag > environment variable > config file > unset. --save-config also
remembers --jcr, so:
impyctfactor --mailto "you@university.edu" --jcr ~/JCR_JournalResults.csv --save-config
impyctfactor -area "biophysics" # both applied automatically from now onimpyctfactor -area "cryo-electron tomography"
impyctfactor -area "CRISPR" --oa diamond gold --max-apc 1500Counts the citable items each journal actually published on your topic in the
last four years, and weights that against the journal's total output over the
same window. The score is the geometric mean of volume and specialisation, so
neither a mega-journal that publishes on everything nor a tiny niche title
automatically wins. The Papers column shows matching / total, so you can
check the reasoning yourself.
impyctfactor -keywords "cryo-EM, single particle analysis, membrane protein"
impyctfactor -keywords "docking, druggability, virtual screening" --min-keywords 3Not the same as -area with the words run together: OpenAlex's search is
conjunctive, so one query for three concepts demands all three in the same paper
and returns almost nothing. Each keyword is searched separately, which makes
coverage the headline signal — the kw column shows how many of your keywords
each journal publishes on, in bold green at full coverage. The final score is the
geometric mean of coverage and the volume/specialisation blend, so a journal that
matches one keyword loudly does not outrank one that covers them all.
Paper counts are the deduplicated union across keywords (a separate a|b
query), not the sum — summing would count a paper matching two keywords twice and
could exceed the journal's own output. --min-keywords N filters, --sort coverage ranks by it, and --evidence/--json show the per-keyword breakdown.
impyctfactor -similar "Journal of Molecular Biology"
impyctfactor -similar 1367-4803 --top 30 # ISSN or OpenAlex id also workCompares OpenAlex topic_share vectors — the fraction of each journal's output
falling in each of ~4,500 topics — by cosine similarity. Two journals score
highly when they distribute their output the same way, which is a much better
match for "same field" than a shared subject label. Because it is a cosine, a
small specialist journal can match a large one exactly.
impyctfactor -abstract manuscript.txtA plain-text file: a short first line is taken as the title, the rest as the abstract. Pass the abstract, not the whole introduction — OpenAlex's classifier accepts 2,000 characters of title plus abstract, so a longer text is truncated for topic inference (the run tells you what fraction was used, and the salient-term search still reads all of it). A 4,000-character introduction gets classified on its generic opening paragraphs, which pulls the topic profile away from what the paper is actually about. Two independent routes are combined by reciprocal rank fusion (Cormack, Clarke & Büttcher, SIGIR 2009, doi:10.1145/1571941.1572114):
- topics — OpenAlex classifies your text with the same model it runs over every indexed paper; journals are scored by cosine against that profile;
- text — the salient terms of your abstract are run as a literature search, ranking journals by how many matching papers they published.
They fail differently — the topic route is robust but coarse, the text route is specific but wording-sensitive — so fusing their rankings (rather than their incomparable scores) is more stable than either alone.
OpenAlex offers no query that is both precise and forgiving, so impyctfactor picks a level and tells you which. Measured 2026-07-30, works since 2022:
| Level | Filter | druggability |
protein cavity detection |
|---|---|---|---|
phrase |
title_and_abstract.search.exact:"…" |
1,588 | 3 |
exact |
title_and_abstract.search.exact:… |
1,588 | 333 |
loose |
title_and_abstract.search:… |
891,395 | 517 |
fulltext |
search=… |
2,672,001 | 76,541 |
phrase— the exact phrase, whole words, no stemming.exact— every word present as a whole word, no stemming. This is what "search for this word" ought to mean, and it is where the default starts.loose— OpenAlex's stemmed search. Heredruggabilityalso matchesdrug, which is how one keyword can return 891,395 items and swamp a query. Only used when whole-word matching returns too little to rank on.fulltext— anywhere in the paper, including a passing citation. Widest recall, lowest precision.
The default, --match auto, starts precise and loosens only when there is too
little to rank on — "protein cavity detection" genuinely returns 3 papers as a
phrase. The level used is printed with every run; force one with
--match phrase|exact|loose|fulltext.
OpenAlex's ordinary search stems aggressively: druggability becomes drug,
returning 891,395 items headed by "The blood–brain barrier: Structure,
regulation and drug delivery". impyctfactor does not use it by default. The
.search.exact filters match whole words with no stemming, and druggability
returns 1,588 items that actually contain the word — a 561× difference.
A verification step remains as a safety net: each keyword's matched papers are sampled and checked for the term's own first six characters. If a term still fails, it is warned about, never removed — see below.
So before ranking, every keyword is checked: sample the papers it matched and
look for the term's own first six characters in their title or abstract. Six
separates druggability from drug while still matching a word's own
inflections (ligandable satisfies ligandability).
A term that fails this check is not discarded — it is routed to the mechanism
that can still represent it. -keywords ranks on two fused routes:
- text — per-keyword searches, which give the
kwcoverage column. A term that stems away is excluded here only, because its matches are provably about a different word. - topic — OpenAlex's classifier reads the keyword set together and returns
a topic profile, which is matched against each journal's
topic_share. This runs on every keyword, so a term that cannot be string-matched still contributes its meaning.
A bare word classifies badly on its own (druggability alone →
"Chemistry and Stereochemistry Studies"), but the set read together supplies
the context: druggability, ligandability, computational biology →
Computational Drug Discovery Methods (0.99). That profile is decisive —
Frontiers in Pharmacology, which topped the broken ranking, scores 0.000
against it, while JCIM scores 0.776.
Measured on that exact query:
| Proteins | Front. Pharmacology | Top of the list | |
|---|---|---|---|
| String matching alone | #202 | #1 | Front. Pharmacology, Pharmaceuticals, J Med Chem |
| Text + topic fused | #5 | not in top 40 | Comput Biol Chem, Briefings in Bioinf, JCIM, CSBJ |
Every keyword you give is used, and the kw column always counts out of the
number you typed.
Two independent things keep one keyword from taking over a query:
- Whole-word matching by default, so a term does not silently expand into a commoner word in the first place.
- Per-keyword rank fusion.
-keywordsbuilds one ranking per keyword plus one from the topic profile and fuses them by reciprocal rank. RRF reads only positions, so a keyword matching a million papers contributes exactly as much as one matching a hundred. Within each keyword, journals rank by specialisation (share of their own output) rather than raw count, so megajournals do not head the individual lists either.
--drop-unmatched-terms will exclude a term that still fails verification; it is
off by default.
--no-review-journals drops Nature Reviews X, Trends in X, Current Opinion
in X, Annual Review of X and the like. Without the flag they stay, but any
that appear are named in a note.
It is a title test, not a measurement of contents — because both content-based signals were tried and failed (25 journals, 2026-08-04):
| Signal | Why it fails |
|---|---|
OpenAlex type:review |
Not populated. Chemical Reviews, Nature Reviews Drug Discovery and Nature Reviews Genetics each report 0 reviews and 1,000+ articles since 2022. |
| Median reference count | Overlaps. J Biological Chemistry (research) 116 and Cell 108 both exceed Trends in Pharmacological Sciences (review) 102. No threshold divides them. |
The title test matched 13 of 14 review journals with 0 of 32 research journals wrongly flagged. Two misses are deliberate, because the obvious broader pattern would cost a major research journal:
| Missed | Broadening the pattern would also flag |
|---|---|
| Physics Reports | Scientific Reports |
| Biotechnology Advances | Science Advances |
Hiding a journal you could have submitted to is the worse error, so the filter errs toward leaving review venues visible — where you can see and skip them.
| Label | Meaning | How it is decided |
|---|---|---|
diamond |
Fully OA, no author charge | DOAJ says has_apc: false and no other charges |
gold |
Fully OA, author pays an APC | DOAJ says has_apc: true, or fully OA with a known price |
hybrid |
Paywalled journal selling per-article OA | not fully OA, but a paid OA route exists |
bronze |
Free to read, no open licence | most recent articles are bronze in OpenAlex |
subscription |
No OA route found | most recent articles are closed |
Two independent signals feed the verdict: what the journal declares (DOAJ
membership and its explicit APC boolean, plus OpenAlex's is_oa/apc_usd) and
what it does (the distribution of per-article OA status across its recent
output). --evidence prints both for every row.
Three deliberate choices:
- Diamond requires a positive statement. A missing price is not evidence of
a missing charge. Only DOAJ's explicit
has_apc: falseearns the label. - Hybrid is about the offer, not the uptake. A journal selling paid OA is hybrid even if 95% of its papers stay behind the paywall.
- If DOAJ cannot be reached, no diamond verdict is issued at all. Those rows
are marked low-confidence (
?) and a warning is printed. Silently reporting a diamond journal as gold because an API returned 502 is the single worst thing this tool could do, so it is made loud instead.
The default column, 2yMC, is OpenAlex's 2yr_mean_citedness. With
--recompute you instead get IF*: the JIF formula — citations made in year
Y to articles and reviews published in Y−1 and Y−2, over the count of those
items — applied to OpenAlex data, with the denominator n printed alongside.
Measured against JIFs stated on the publishers' own pages, year-matched (retrieved 2026-07-28):
| Journal | JIF | 2yMC | IF* | 2yMC/JIF | IF*/JIF |
|---|---|---|---|---|---|
| Bioinformatics | 5.5 (2025) | 5.93 | 7.00 | 1.08 | 1.27 |
| Briefings in Bioinformatics | 7.3 (2025) | 6.62 | 8.60 | 0.91 | 1.18 |
| Nucleic Acids Research | 15.0 (2025) | 15.42 | 19.15 | 1.03 | 1.28 |
| Molecular Biology and Evolution | 5.3 (2024) | — | 8.08 | — | 1.52 |
| Genome Biology and Evolution | 3.1 (2023) | — | 4.20 | — | 1.35 |
IF* runs 20–50% above the JIF (median ratio 1.28), because OpenAlex
indexes more citing venues than Web of Science. On the three journals where the
citation year matches exactly, IF* was farther from the JIF than plain
2yMC (mean absolute deviation 0.24 against 0.07). 2yMC is only comparable
for those three rows, since OpenAlex always computes it for the previous year.
So --recompute is not the more accurate option. Use it when you need an
explicit denominator, a specific citation year, or the contamination diagnostic
below — not because you expect a better JIF estimate.
n = 5, all Oxford University Press. That is far too small and too correlated to derive a correction factor from. Treat the ratio as an order of magnitude, not a coefficient.
Filtering the denominator to type:article|review does not remove
conference and meeting abstracts, because OpenAlex types them as article.
Measured on Biophysical Journal, 2023–2024: 6,136 items carry type:article,
of which 4,465 (73%) have never been cited and 5,921 (96%) have no
abstract — the Biophysical Society's annual-meeting abstracts, deposited as
journal articles. Its recomputed value stays at 0.53, an order of magnitude
below its real JIF.
No open field separates a meeting abstract from a research article, so impyctfactor
does not guess. It reports the never-cited share of the denominator and flags
any row above 50% with !:
│ 12 │ Biophysical Journal │ 0.53 │ 6,136! │ hybrid │ ...
A flagged value is uninterpretable, not low. Do not compare it with anything.
--top N rows to show (default 20)
--sort KEY impact | jif | relevance | coverage | focus | similarity |
apc | volume | name
--match LEVEL auto (default) | phrase | loose | fulltext
--min-keywords N with -keywords, require at least N of them
--drop-unmatched-terms exclude keywords that stemmed into a different word (off by default)
--oa TYPE [TYPE...] keep only these OA types, e.g. --oa diamond gold
--max-apc USD budget ceiling (diamond journals are always kept)
--min-impact X floor on the impact metric
--jcr FILE your JCR export -> the REAL Clarivate JIF (repeatable)
--wikipedia fill missing JIFs from Wikipedia infoboxes (71% coverage,
often stale; marked * with the year, --jcr always wins)
--quartile Q1 Q2 keep only these JIF quartiles (needs --jcr)
--recompute compute IF* for the shortlist (see the caveats above)
--year YYYY citation year for --recompute
--evidence print the evidence behind every OA verdict
--include-inactive keep journals that have stopped publishing
--core-only restrict to OpenAlex "core" sources
--json [FILE] machine-readable output, including every diagnostic field
--csv FILE spreadsheet output
--tsv FILE tab-separated output ('-' for stdout)
--log FILE append the table plus a provenance header to a log file
--scimago FILE enrich with SJR and quartile from your own Scimago export
--compact drop the publisher and country columns (narrow terminals)
-v / -vv progress / debug
Filters run over a wider shortlist than --top so that --oa diamond does not
merely filter the first 20 rows; the footer reports how many were screened.
Scimago publishes SJR and subject quartiles for free, but their bulk download returns HTTP 403 to programmatic clients — a clear signal that automated retrieval is unwelcome. impyctfactor therefore never fetches it. Export the CSV yourself from https://www.scimagojr.com/journalrank.php ("Download data") and pass it:
impyctfactor -area "immunology" --scimago "scimagojr 2024.csv"impyctfactor -keywords "druggability, ligandability, computational biology" \
--tsv shortlist.tsv --log impyctfactor.log--log appends, so one file accumulates a search history. Each entry opens
with a provenance header — because a shortlist is only interpretable if you know
which terms actually produced it:
==============================================================================
impyctfactor run 2026-08-04T12:12:45
command: impyctfactor --keywords 'druggability, ligandability, computational biology' --top 4
mode: keywords
query: druggability, ligandability, computational biology
window: publications from 2022 onward
term: 'druggability' -> EXCLUDED from text matching - stems to a commoner word [891,395 items, term seen in 0/10 sampled]
term: 'ligandability' -> used [228 items, term seen in 10/10 sampled]
term: 'computational biology' -> used [10,208 items, term seen in 10/10 sampled]
topics: Computational Drug Discovery Methods (0.99)
==============================================================================
The command line is quoted so it can be pasted straight back. The table is written to the log at a fixed 160-column width, so a log stays readable regardless of the terminal it was run in.
--tsv - writes to stdout and suppresses the table, so it pipes cleanly:
impyctfactor -keywords "cryo-EM, membrane protein" --tsv - | cut -f2,3,4,7- Dead journals are excluded by default. Topic profiles outlive a journal by
decades, so a purely topical search happily returns titles that folded years
ago — European Journal of Biochemistry ceased in 2005 and nobody can submit
there. A journal is kept if OpenAlex records output in the current or previous
year;
--include-inactiveoverrides this and the footer always says how many were dropped. - Responses are cached under
~/.cache/impyctfactorfor 7 days (--cache-ttl,--no-cache). A repeated query costs no API calls. - Rate limits are per host, and adaptive. OpenAlex tolerates ~10 req/s; DOAJ
returns 502 well below that, so it is throttled separately. An HTTP 429 is
treated as "slow down", not "host is broken": the interval for that host is
doubled for the rest of the run (respecting
Retry-After) and the request is retried. Only a host that keeps failing outright is taken out of service, and that degradation is always reported in the output.
| Source | Used for | Licence |
|---|---|---|
| Your own JCR export (optional) | the real Clarivate JIF + quartile | licensed to you; read locally, never transmitted |
Wikipedia (optional, --wikipedia) |
stated JIF + the year it refers to | CC BY-SA |
| OpenAlex | journals, topics, citations, OA status | CC0 |
| DOAJ | APC / no-APC, licence, charges | CC BY-SA (metadata) |
| Scimago (optional, user-supplied) | SJR, quartile | CC BY-NC |
python -m pytest tests/ -q # 191 tests, no network requiredThe offline logic (classification, similarity, keywording, scoring, caching, rate-limit backoff, the circuit breaker, JCR/Scimago parsing) is covered by stubs; the API adapters are tested against recorded response shapes.
impyctfactor/
├── cli.py argument parsing, orchestration, filters
├── jcr.py reads your JCR export -> the real Clarivate JIF
├── wikipedia.py stated JIF from journal infoboxes, ISSN-verified
├── review_journals.py title test for review venues, with its measured limits
├── tables.py shared CSV-export parsing (delimiter, preamble, numbers)
├── discover.py the three query modes + enrichment
├── openalex.py OpenAlex client (sources, works, topics, cursor paging)
├── doaj.py DOAJ client; "not listed" vs "could not check"
├── oa_type.py diamond/gold/hybrid/bronze/subscription decision table
├── metrics.py 2yMC passthrough, IF* recomputation, sort keys
├── similarity.py cosine over topic vectors, reciprocal rank fusion
├── text.py title/abstract splitting, salient-term extraction
├── scimago.py optional SJR enrichment from a local CSV
├── render.py rich/plain table, JSON, CSV, provenance footer
├── httpcache.py disk cache, per-host throttling, retry, circuit breaker
├── models.py Journal, OAProfile, ImpactMetrics, Candidate
└── config.py settings resolution
MIT.