Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

impyctfactor

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 │
└─────┴────────────────────────────────────────────┴──────┴──────────┴─────────┴──────┴─────────┘

Getting the real Journal Impact Factor

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:

  1. open JCR (in Brazil: CAPES PeriódicosLista de bases e coleções → JCR — by campus IP, or via CAFe from home);
  2. go to the Journals list, filter by category/edition/year if you like;
  3. Export → CSV;
  4. 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.

No JCR access? --wikipedia

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.

Why not just scrape the publishers?

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".

Install

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-config

The daily query budget

OpenAlex 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 on

The three query modes

-area KEYWORD — what gets published on this topic

impyctfactor -area "cryo-electron tomography"
impyctfactor -area "CRISPR" --oa diamond gold --max-apc 1500

Counts 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.

-keywords A, B, C — cover several distinct topics

impyctfactor -keywords "cryo-EM, single particle analysis, membrane protein"
impyctfactor -keywords "docking, druggability, virtual screening" --min-keywords 3

Not 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.

-similar JOURNAL — journals in the same space

impyctfactor -similar "Journal of Molecular Biology"
impyctfactor -similar 1367-4803 --top 30      # ISSN or OpenAlex id also work

Compares 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.

-abstract FILE — infer from the manuscript itself

impyctfactor -abstract manuscript.txt

A 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):

  1. topics — OpenAlex classifies your text with the same model it runs over every indexed paper; journals are scored by cosine against that profile;
  2. 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.

How precisely keywords match

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. Here druggability also matches drug, 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.

Whole-word matching, and the safety net behind it

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 kw coverage 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 biologyComputational 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:

  1. Whole-word matching by default, so a term does not silently expand into a commoner word in the first place.
  2. Per-keyword rank fusion. -keywords builds 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.

Review venues

--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.

Open-access classification

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: false earns 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.

Calibration: what the numbers are worth

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.

The contamination diagnostic

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.

Useful options

--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 (SJR) enrichment

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"

Keeping a record of a run

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

Behaviour you should know about

  • 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-inactive overrides this and the footer always says how many were dropped.
  • Responses are cached under ~/.cache/impyctfactor for 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.

Data sources

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

Development

python -m pytest tests/ -q     # 191 tests, no network required

The 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.

Layout

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

Licence

MIT.

About

Find journals to submit to — by area, keywords, similarity or abstract — with open impact metrics and open-access type (diamond/gold/hybrid/subscription).

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages