BibexPy 2.0.0__py3-none-any.whl
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.
- bibex_core/MergeDB.py +1189 -0
- bibex_core/__init__.py +13 -0
- bibex_core/modules/__init__.py +35 -0
- bibex_core/modules/api_utils.py +1172 -0
- bibex_core/modules/c1_utils.py +423 -0
- bibex_core/modules/file_utils.py +71 -0
- bibex_core/modules/ml_utils.py +332 -0
- bibex_core/modules/post_process.py +50 -0
- bibex_core/modules/stats_utils.py +216 -0
- bibex_core/scp2xlsx.py +235 -0
- bibex_core/wos2xlsx.py +159 -0
- bibex_core/xlsx2vos.py +185 -0
- bibexpy/__init__.py +9 -0
- bibexpy/__main__.py +6 -0
- bibexpy/_server/__init__.py +1 -0
- bibexpy/_server/config.py +92 -0
- bibexpy/_server/jobs/__init__.py +3 -0
- bibexpy/_server/jobs/runner.py +301 -0
- bibexpy/_server/main.py +211 -0
- bibexpy/_server/models/__init__.py +0 -0
- bibexpy/_server/models/project.py +25 -0
- bibexpy/_server/routers/__init__.py +0 -0
- bibexpy/_server/routers/audit.py +65 -0
- bibexpy/_server/routers/convert.py +101 -0
- bibexpy/_server/routers/disambiguate.py +177 -0
- bibexpy/_server/routers/downloads.py +26 -0
- bibexpy/_server/routers/enrich.py +42 -0
- bibexpy/_server/routers/export.py +116 -0
- bibexpy/_server/routers/export_folder.py +97 -0
- bibexpy/_server/routers/filter.py +131 -0
- bibexpy/_server/routers/jobs.py +72 -0
- bibexpy/_server/routers/merge.py +437 -0
- bibexpy/_server/routers/prepare.py +241 -0
- bibexpy/_server/routers/projects.py +49 -0
- bibexpy/_server/routers/quality.py +288 -0
- bibexpy/_server/routers/records.py +204 -0
- bibexpy/_server/routers/report.py +111 -0
- bibexpy/_server/routers/settings.py +383 -0
- bibexpy/_server/routers/system.py +209 -0
- bibexpy/_server/routers/tools.py +257 -0
- bibexpy/_server/routers/upload.py +110 -0
- bibexpy/_server/services/__init__.py +0 -0
- bibexpy/_server/services/analyses.py +364 -0
- bibexpy/_server/services/audit.py +340 -0
- bibexpy/_server/services/bibex_adapter.py +47 -0
- bibexpy/_server/services/bibtex_writer.py +104 -0
- bibexpy/_server/services/converter.py +205 -0
- bibexpy/_server/services/disambiguation/__init__.py +19 -0
- bibexpy/_server/services/disambiguation/blocking.py +581 -0
- bibexpy/_server/services/disambiguation/cache.py +47 -0
- bibexpy/_server/services/disambiguation/deepseek_client.py +203 -0
- bibexpy/_server/services/disambiguation/orcid.py +261 -0
- bibexpy/_server/services/disambiguation/pipeline.py +985 -0
- bibexpy/_server/services/disambiguation/similarity.py +91 -0
- bibexpy/_server/services/enricher.py +338 -0
- bibexpy/_server/services/exporter.py +112 -0
- bibexpy/_server/services/filter_engine.py +300 -0
- bibexpy/_server/services/merger.py +202 -0
- bibexpy/_server/services/methodology.py +102 -0
- bibexpy/_server/services/report_export.py +300 -0
- bibexpy/_server/services/ris_writer.py +62 -0
- bibexpy/_server/services/smart_merger.py +967 -0
- bibexpy/_server/services/storage.py +165 -0
- bibexpy/_web/404/index.html +1 -0
- bibexpy/_web/404.html +1 -0
- bibexpy/_web/_next/static/chunks/117-7ad5186126fa076d.js +2 -0
- bibexpy/_web/_next/static/chunks/156-75beffb8ae92a7f3.js +1 -0
- bibexpy/_web/_next/static/chunks/169-301c04fcec22686f.js +4 -0
- bibexpy/_web/_next/static/chunks/2-ecc1c36a5632ac61.js +1 -0
- bibexpy/_web/_next/static/chunks/244-be343e0f8aa00fb5.js +1 -0
- bibexpy/_web/_next/static/chunks/367-b7457c2b95f6932b.js +1 -0
- bibexpy/_web/_next/static/chunks/382-9cd163eed632e241.js +1 -0
- bibexpy/_web/_next/static/chunks/392-afa8bfa4c1a9893d.js +1 -0
- bibexpy/_web/_next/static/chunks/507-63607ded9f83c1aa.js +1 -0
- bibexpy/_web/_next/static/chunks/624-cae565632f47217d.js +1 -0
- bibexpy/_web/_next/static/chunks/739-a00c6b9c0f11813e.js +1 -0
- bibexpy/_web/_next/static/chunks/862-8e1bd5f93c082c02.js +1 -0
- bibexpy/_web/_next/static/chunks/app/_not-found/page-873ba59b5eb7c342.js +1 -0
- bibexpy/_web/_next/static/chunks/app/layout-0d5fc68ac8971a04.js +1 -0
- bibexpy/_web/_next/static/chunks/app/page-cf2bea5edc74459a.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/[id]/convert/page-ab6dd645ae113b64.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/[id]/disambiguate/page-dd98482cc47344e9.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/[id]/enrich/page-4727e431468514e9.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/[id]/export/page-f9005d164bb22cb0.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/[id]/layout-07755fda55b3ae1f.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/[id]/merge/page-45b95318ea7beccf.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/[id]/records/page-bfad80457da9eb0b.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/[id]/report/page-f05767e74158e995.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/[id]/upload/page-8df78ca9cdecbcb8.js +1 -0
- bibexpy/_web/_next/static/chunks/app/projects/page-99960711ac6a13e9.js +1 -0
- bibexpy/_web/_next/static/chunks/app/settings/page-002df809d6c5a227.js +1 -0
- bibexpy/_web/_next/static/chunks/app/tools/page-27eee1c6a590859f.js +1 -0
- bibexpy/_web/_next/static/chunks/fd9d1056-f2718be92181f426.js +1 -0
- bibexpy/_web/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
- bibexpy/_web/_next/static/chunks/main-app-d138a6e2f8c847f6.js +1 -0
- bibexpy/_web/_next/static/chunks/main-c4b9ddbf514503ef.js +1 -0
- bibexpy/_web/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
- bibexpy/_web/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
- bibexpy/_web/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- bibexpy/_web/_next/static/chunks/webpack-c81f7fd28659d64f.js +1 -0
- bibexpy/_web/_next/static/css/1c4638f3e39cf127.css +3 -0
- bibexpy/_web/_next/static/ypx2IQAkZiSaFiKWnzZGB/_buildManifest.js +1 -0
- bibexpy/_web/_next/static/ypx2IQAkZiSaFiKWnzZGB/_ssgManifest.js +1 -0
- bibexpy/_web/images/PROMPTS.md +83 -0
- bibexpy/_web/images/about-illustration.png +0 -0
- bibexpy/_web/images/authors/alperen-sahin.jpg +0 -0
- bibexpy/_web/images/authors/burak-can-kara.jpg +0 -0
- bibexpy/_web/images/authors/taskin-dirsehan.jpg +0 -0
- bibexpy/_web/images/bibexpy-logo-full.png +0 -0
- bibexpy/_web/images/bibexpy-logo-header.png +0 -0
- bibexpy/_web/images/bibexpy-logo-in.png +0 -0
- bibexpy/_web/images/bibexpy-logo.webp +0 -0
- bibexpy/_web/images/hero-illustration.png +0 -0
- bibexpy/_web/images/loader.gif +0 -0
- bibexpy/_web/images/workflow-diagram.png +0 -0
- bibexpy/_web/index.html +1 -0
- bibexpy/_web/index.txt +11 -0
- bibexpy/_web/projects/_/convert/index.html +1 -0
- bibexpy/_web/projects/_/convert/index.txt +12 -0
- bibexpy/_web/projects/_/disambiguate/index.html +1 -0
- bibexpy/_web/projects/_/disambiguate/index.txt +12 -0
- bibexpy/_web/projects/_/enrich/index.html +1 -0
- bibexpy/_web/projects/_/enrich/index.txt +12 -0
- bibexpy/_web/projects/_/export/index.html +1 -0
- bibexpy/_web/projects/_/export/index.txt +12 -0
- bibexpy/_web/projects/_/merge/index.html +1 -0
- bibexpy/_web/projects/_/merge/index.txt +12 -0
- bibexpy/_web/projects/_/records/index.html +1 -0
- bibexpy/_web/projects/_/records/index.txt +12 -0
- bibexpy/_web/projects/_/report/index.html +1 -0
- bibexpy/_web/projects/_/report/index.txt +12 -0
- bibexpy/_web/projects/_/upload/index.html +1 -0
- bibexpy/_web/projects/_/upload/index.txt +12 -0
- bibexpy/_web/projects/index.html +1 -0
- bibexpy/_web/projects/index.txt +11 -0
- bibexpy/_web/settings/index.html +1 -0
- bibexpy/_web/settings/index.txt +11 -0
- bibexpy/_web/tools/README.md +65 -0
- bibexpy/_web/tools/bibexcel.svg +6 -0
- bibexpy/_web/tools/bibliometrix.png +0 -0
- bibexpy/_web/tools/biblioshiny.svg +12 -0
- bibexpy/_web/tools/citavi.svg +6 -0
- bibexpy/_web/tools/citespace.svg +7 -0
- bibexpy/_web/tools/citnetexplorer.svg +8 -0
- bibexpy/_web/tools/endnote.svg +6 -0
- bibexpy/_web/tools/excel.svg +1 -0
- bibexpy/_web/tools/gephi.svg +66 -0
- bibexpy/_web/tools/histcite.svg +7 -0
- bibexpy/_web/tools/index.html +1 -0
- bibexpy/_web/tools/index.txt +11 -0
- bibexpy/_web/tools/jabref.svg +122 -0
- bibexpy/_web/tools/latex.svg +1 -0
- bibexpy/_web/tools/mendeley.svg +1 -0
- bibexpy/_web/tools/openrefine.svg +15 -0
- bibexpy/_web/tools/overleaf.svg +1 -0
- bibexpy/_web/tools/papers.svg +7 -0
- bibexpy/_web/tools/powerbi.svg +1 -0
- bibexpy/_web/tools/python.svg +1 -0
- bibexpy/_web/tools/r.svg +1 -0
- bibexpy/_web/tools/refworks.svg +5 -0
- bibexpy/_web/tools/scite.svg +5 -0
- bibexpy/_web/tools/tableau.svg +1 -0
- bibexpy/_web/tools/vosviewer.png +0 -0
- bibexpy/_web/tools/zotero.svg +1 -0
- bibexpy/cli.py +242 -0
- bibexpy-2.0.0.dist-info/METADATA +105 -0
- bibexpy-2.0.0.dist-info/RECORD +171 -0
- bibexpy-2.0.0.dist-info/WHEEL +5 -0
- bibexpy-2.0.0.dist-info/entry_points.txt +2 -0
- bibexpy-2.0.0.dist-info/licenses/LICENSE +674 -0
- bibexpy-2.0.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
|
4
|
+
from sklearn.multioutput import MultiOutputClassifier
|
|
5
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
6
|
+
from sklearn.preprocessing import MultiLabelBinarizer
|
|
7
|
+
import re
|
|
8
|
+
from colorama import Fore, Style
|
|
9
|
+
|
|
10
|
+
# NLTK OPSIYONELDIR. Import veya veri (punkt/stopwords) yoksa builtin fallback'lere
|
|
11
|
+
# dusulur; bu, cevrimdisi makinede ve ilk-calistirmada cokmemeyi garanti eder.
|
|
12
|
+
# Veri indirme import-zamaninda DEGIL, yalniz ilk gercek kullanimda ve sessizce
|
|
13
|
+
# denenir (paket import edilince ag erisimi/indirme tetiklenmez).
|
|
14
|
+
try:
|
|
15
|
+
import nltk # type: ignore
|
|
16
|
+
except Exception: # pragma: no cover
|
|
17
|
+
nltk = None
|
|
18
|
+
|
|
19
|
+
# NLTK yoksa kullanilan builtin Ingilizce stopword listesi.
|
|
20
|
+
_FALLBACK_STOPWORDS = frozenset((
|
|
21
|
+
"a about above after again against all am an and any are aren't as at be because been "
|
|
22
|
+
"before being below between both but by can cannot could couldn't did didn't do does "
|
|
23
|
+
"doesn't doing don't down during each few for from further had hadn't has hasn't have "
|
|
24
|
+
"haven't having he her here hers herself him himself his how i if in into is isn't it "
|
|
25
|
+
"its itself just me more most my myself no nor not now of off on once only or other our "
|
|
26
|
+
"ours ourselves out over own same she should shouldn't so some such than that the their "
|
|
27
|
+
"theirs them themselves then there these they this those through to too under until up "
|
|
28
|
+
"very was wasn't we were weren't what when where which while who whom why will with won't "
|
|
29
|
+
"would wouldn't you your yours yourself yourselves"
|
|
30
|
+
).split())
|
|
31
|
+
|
|
32
|
+
_nltk_ready = None # None=denenmedi; True/False=sonuc (bir kez denenir)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _ensure_nltk() -> bool:
|
|
36
|
+
"""punkt+stopwords mevcut mu? Ilk cagride sessizce indirmeyi dener.
|
|
37
|
+
Basarisizsa (cevrimdisi/izinsiz) False doner ve builtin fallback kullanilir."""
|
|
38
|
+
global _nltk_ready
|
|
39
|
+
if _nltk_ready is not None:
|
|
40
|
+
return _nltk_ready
|
|
41
|
+
if nltk is None:
|
|
42
|
+
_nltk_ready = False
|
|
43
|
+
return _nltk_ready
|
|
44
|
+
try:
|
|
45
|
+
try:
|
|
46
|
+
nltk.data.find('tokenizers/punkt')
|
|
47
|
+
nltk.data.find('corpora/stopwords')
|
|
48
|
+
except LookupError:
|
|
49
|
+
for pkg in ('punkt', 'punkt_tab', 'stopwords'):
|
|
50
|
+
try:
|
|
51
|
+
nltk.download(pkg, quiet=True)
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
nltk.data.find('tokenizers/punkt')
|
|
55
|
+
nltk.data.find('corpora/stopwords')
|
|
56
|
+
_nltk_ready = True
|
|
57
|
+
except Exception:
|
|
58
|
+
_nltk_ready = False
|
|
59
|
+
return _nltk_ready
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _tokenize(text: str) -> list:
|
|
63
|
+
"""NLTK varsa word_tokenize; yoksa basit regex tokenizer."""
|
|
64
|
+
if _ensure_nltk():
|
|
65
|
+
try:
|
|
66
|
+
from nltk.tokenize import word_tokenize
|
|
67
|
+
return word_tokenize(text)
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
return re.findall(r"[a-z]+", text)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _stopword_set() -> set:
|
|
74
|
+
"""NLTK varsa Ingilizce stopword'leri; yoksa builtin fallback."""
|
|
75
|
+
if _ensure_nltk():
|
|
76
|
+
try:
|
|
77
|
+
from nltk.corpus import stopwords
|
|
78
|
+
return set(stopwords.words('english'))
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
return set(_FALLBACK_STOPWORDS)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def preprocess_text(text):
|
|
85
|
+
"""Preprocess text data for ML"""
|
|
86
|
+
if pd.isna(text):
|
|
87
|
+
return ""
|
|
88
|
+
text = str(text).lower()
|
|
89
|
+
text = re.sub(r'[^a-zA-Z\s]', ' ', text)
|
|
90
|
+
text = ' '.join(text.split())
|
|
91
|
+
tokens = _tokenize(text)
|
|
92
|
+
stop_words = _stopword_set()
|
|
93
|
+
tokens = [token for token in tokens if token not in stop_words]
|
|
94
|
+
return ' '.join(tokens)
|
|
95
|
+
|
|
96
|
+
def prepare_training_data(df, text_columns=['TI', 'AB']):
|
|
97
|
+
"""Prepare text data for training"""
|
|
98
|
+
# Combine and preprocess text columns
|
|
99
|
+
combined_text = df[text_columns].fillna('').agg(' '.join, axis=1)
|
|
100
|
+
processed_text = combined_text.apply(preprocess_text)
|
|
101
|
+
|
|
102
|
+
return processed_text
|
|
103
|
+
|
|
104
|
+
def train_keyword_model(train_df, text_columns=['TI', 'AB']):
|
|
105
|
+
"""Train model for keyword prediction"""
|
|
106
|
+
# Prepare text data
|
|
107
|
+
X = prepare_training_data(train_df, text_columns)
|
|
108
|
+
|
|
109
|
+
# Prepare keywords data
|
|
110
|
+
mlb = MultiLabelBinarizer()
|
|
111
|
+
keywords = train_df['DE'].fillna('').str.split(';')
|
|
112
|
+
keywords = [[kw.strip() for kw in kws if kw.strip()] for kws in keywords]
|
|
113
|
+
y = mlb.fit_transform(keywords)
|
|
114
|
+
|
|
115
|
+
# Create and train TF-IDF vectorizer
|
|
116
|
+
vectorizer = TfidfVectorizer(max_features=5000)
|
|
117
|
+
X_tfidf = vectorizer.fit_transform(X)
|
|
118
|
+
|
|
119
|
+
# Train model
|
|
120
|
+
model = MultiOutputClassifier(RandomForestClassifier(n_estimators=100))
|
|
121
|
+
model.fit(X_tfidf, y)
|
|
122
|
+
|
|
123
|
+
return vectorizer, model, mlb
|
|
124
|
+
|
|
125
|
+
def train_subject_model(train_df, text_columns=['TI', 'AB']):
|
|
126
|
+
"""Train model for subject category prediction"""
|
|
127
|
+
# Prepare text data
|
|
128
|
+
X = prepare_training_data(train_df, text_columns)
|
|
129
|
+
|
|
130
|
+
# Prepare subject categories data
|
|
131
|
+
mlb = MultiLabelBinarizer()
|
|
132
|
+
subjects = train_df['SC'].fillna('').str.split(';')
|
|
133
|
+
subjects = [[subj.strip() for subj in subjs if subj.strip()] for subjs in subjects]
|
|
134
|
+
y = mlb.fit_transform(subjects)
|
|
135
|
+
|
|
136
|
+
# Create and train TF-IDF vectorizer
|
|
137
|
+
vectorizer = TfidfVectorizer(max_features=5000)
|
|
138
|
+
X_tfidf = vectorizer.fit_transform(X)
|
|
139
|
+
|
|
140
|
+
# Train model
|
|
141
|
+
model = MultiOutputClassifier(RandomForestClassifier(n_estimators=100))
|
|
142
|
+
model.fit(X_tfidf, y)
|
|
143
|
+
|
|
144
|
+
return vectorizer, model, mlb
|
|
145
|
+
|
|
146
|
+
def predict_keywords(text, vectorizer, model, mlb, threshold=0.3):
|
|
147
|
+
"""Predict keywords for given text"""
|
|
148
|
+
# Preprocess text
|
|
149
|
+
processed_text = preprocess_text(text)
|
|
150
|
+
|
|
151
|
+
# Transform text using vectorizer
|
|
152
|
+
X_tfidf = vectorizer.transform([processed_text])
|
|
153
|
+
|
|
154
|
+
# Predict probabilities
|
|
155
|
+
y_pred_proba = model.predict_proba(X_tfidf)
|
|
156
|
+
|
|
157
|
+
# Get keywords above threshold
|
|
158
|
+
keywords = []
|
|
159
|
+
for i, proba in enumerate(zip(*[estimator.predict_proba(X_tfidf)[0] for estimator in model.estimators_])):
|
|
160
|
+
if max(proba) >= threshold:
|
|
161
|
+
keywords.append(mlb.classes_[i])
|
|
162
|
+
|
|
163
|
+
return '; '.join(keywords) if keywords else None
|
|
164
|
+
|
|
165
|
+
def predict_subjects(text, vectorizer, model, mlb, threshold=0.3):
|
|
166
|
+
"""Predict subject categories for given text"""
|
|
167
|
+
# Preprocess text
|
|
168
|
+
processed_text = preprocess_text(text)
|
|
169
|
+
|
|
170
|
+
# Transform text using vectorizer
|
|
171
|
+
X_tfidf = vectorizer.transform([processed_text])
|
|
172
|
+
|
|
173
|
+
# Predict probabilities
|
|
174
|
+
y_pred_proba = model.predict_proba(X_tfidf)
|
|
175
|
+
|
|
176
|
+
# Get subjects above threshold
|
|
177
|
+
subjects = []
|
|
178
|
+
for i, proba in enumerate(zip(*[estimator.predict_proba(X_tfidf)[0] for estimator in model.estimators_])):
|
|
179
|
+
if max(proba) >= threshold:
|
|
180
|
+
subjects.append(mlb.classes_[i])
|
|
181
|
+
|
|
182
|
+
return '; '.join(subjects) if subjects else None
|
|
183
|
+
|
|
184
|
+
def train_keywords_plus_model(train_df, text_columns=['TI', 'AB']):
|
|
185
|
+
"""Train model for Keywords Plus prediction"""
|
|
186
|
+
# Prepare text data
|
|
187
|
+
X = prepare_training_data(train_df, text_columns)
|
|
188
|
+
|
|
189
|
+
# Prepare Keywords Plus data
|
|
190
|
+
mlb = MultiLabelBinarizer()
|
|
191
|
+
keywords_plus = train_df['ID'].fillna('').str.split(';')
|
|
192
|
+
keywords_plus = [[kw.strip() for kw in kws if kw.strip()] for kws in keywords_plus]
|
|
193
|
+
y = mlb.fit_transform(keywords_plus)
|
|
194
|
+
|
|
195
|
+
# Create and train TF-IDF vectorizer
|
|
196
|
+
vectorizer = TfidfVectorizer(max_features=5000)
|
|
197
|
+
X_tfidf = vectorizer.fit_transform(X)
|
|
198
|
+
|
|
199
|
+
# Train model
|
|
200
|
+
model = MultiOutputClassifier(RandomForestClassifier(n_estimators=100))
|
|
201
|
+
model.fit(X_tfidf, y)
|
|
202
|
+
|
|
203
|
+
return vectorizer, model, mlb
|
|
204
|
+
|
|
205
|
+
def predict_keywords_plus(text, vectorizer, model, mlb, threshold=0.3):
|
|
206
|
+
"""Predict Keywords Plus for given text"""
|
|
207
|
+
# Preprocess text
|
|
208
|
+
processed_text = preprocess_text(text)
|
|
209
|
+
|
|
210
|
+
# Transform text using vectorizer
|
|
211
|
+
X_tfidf = vectorizer.transform([processed_text])
|
|
212
|
+
|
|
213
|
+
# Predict probabilities
|
|
214
|
+
y_pred_proba = model.predict_proba(X_tfidf)
|
|
215
|
+
|
|
216
|
+
# Get keywords above threshold
|
|
217
|
+
keywords_plus = []
|
|
218
|
+
for i, proba in enumerate(zip(*[estimator.predict_proba(X_tfidf)[0] for estimator in model.estimators_])):
|
|
219
|
+
if max(proba) >= threshold:
|
|
220
|
+
keywords_plus.append(mlb.classes_[i])
|
|
221
|
+
|
|
222
|
+
return '; '.join(keywords_plus) if keywords_plus else None
|
|
223
|
+
|
|
224
|
+
def enrich_metadata_ml(df):
|
|
225
|
+
"""Enrich metadata using ML models"""
|
|
226
|
+
print("\nStarting ML-based metadata enrichment...")
|
|
227
|
+
|
|
228
|
+
# Create copies of the dataframe for training
|
|
229
|
+
train_df_keywords = df[df['DE'].notna()].copy()
|
|
230
|
+
train_df_keywords_plus = df[df['ID'].notna()].copy() # New training set for Keywords Plus
|
|
231
|
+
train_df_subjects = df[df['SC'].notna()].copy()
|
|
232
|
+
|
|
233
|
+
# Create a copy of the input dataframe
|
|
234
|
+
enriched_df = df.copy()
|
|
235
|
+
|
|
236
|
+
# Initialize counters for ID field
|
|
237
|
+
original_empty_id = df['ID'].isna().sum()
|
|
238
|
+
|
|
239
|
+
# Train models if enough training data is available
|
|
240
|
+
if len(train_df_keywords) > 10:
|
|
241
|
+
print(f"\n{Fore.CYAN}Training keyword prediction model...{Style.RESET_ALL}")
|
|
242
|
+
print(f"Using {len(train_df_keywords)} records for training")
|
|
243
|
+
kw_vectorizer, kw_model, kw_mlb = train_keyword_model(train_df_keywords)
|
|
244
|
+
|
|
245
|
+
# Predict missing keywords
|
|
246
|
+
print(f"\n{Fore.CYAN}Predicting missing keywords...{Style.RESET_ALL}")
|
|
247
|
+
mask = enriched_df['DE'].isna()
|
|
248
|
+
total = mask.sum()
|
|
249
|
+
completed = 0
|
|
250
|
+
|
|
251
|
+
for idx in enriched_df[mask].index:
|
|
252
|
+
text = ' '.join([
|
|
253
|
+
str(enriched_df.loc[idx, 'TI']) if pd.notna(enriched_df.loc[idx, 'TI']) else '',
|
|
254
|
+
str(enriched_df.loc[idx, 'AB']) if pd.notna(enriched_df.loc[idx, 'AB']) else ''
|
|
255
|
+
])
|
|
256
|
+
if text.strip():
|
|
257
|
+
predicted_keywords = predict_keywords(text, kw_vectorizer, kw_model, kw_mlb)
|
|
258
|
+
if predicted_keywords:
|
|
259
|
+
enriched_df.loc[idx, 'DE'] = predicted_keywords
|
|
260
|
+
completed += 1
|
|
261
|
+
if completed % 10 == 0:
|
|
262
|
+
print(f"Progress: {completed}/{total} records processed", end='\r')
|
|
263
|
+
print() # New line after progress
|
|
264
|
+
|
|
265
|
+
# Train Keywords Plus model if enough training data is available
|
|
266
|
+
if len(train_df_keywords_plus) > 10:
|
|
267
|
+
print(f"\n{Fore.CYAN}Training Keywords Plus prediction model...{Style.RESET_ALL}")
|
|
268
|
+
print(f"Using {len(train_df_keywords_plus)} records for training")
|
|
269
|
+
kw_plus_vectorizer, kw_plus_model, kw_plus_mlb = train_keywords_plus_model(train_df_keywords_plus)
|
|
270
|
+
|
|
271
|
+
# Predict missing Keywords Plus
|
|
272
|
+
print(f"\n{Fore.CYAN}Predicting missing Keywords Plus...{Style.RESET_ALL}")
|
|
273
|
+
mask = enriched_df['ID'].isna()
|
|
274
|
+
total = mask.sum()
|
|
275
|
+
completed = 0
|
|
276
|
+
|
|
277
|
+
for idx in enriched_df[mask].index:
|
|
278
|
+
text = ' '.join([
|
|
279
|
+
str(enriched_df.loc[idx, 'TI']) if pd.notna(enriched_df.loc[idx, 'TI']) else '',
|
|
280
|
+
str(enriched_df.loc[idx, 'AB']) if pd.notna(enriched_df.loc[idx, 'AB']) else ''
|
|
281
|
+
])
|
|
282
|
+
if text.strip():
|
|
283
|
+
predicted_keywords_plus = predict_keywords_plus(text, kw_plus_vectorizer, kw_plus_model, kw_plus_mlb)
|
|
284
|
+
if predicted_keywords_plus:
|
|
285
|
+
enriched_df.loc[idx, 'ID'] = predicted_keywords_plus
|
|
286
|
+
completed += 1
|
|
287
|
+
if completed % 10 == 0:
|
|
288
|
+
print(f"Progress: {completed}/{total} records processed", end='\r')
|
|
289
|
+
print() # New line after progress
|
|
290
|
+
|
|
291
|
+
# Train models if enough training data is available
|
|
292
|
+
if len(train_df_subjects) > 10:
|
|
293
|
+
print(f"\n{Fore.CYAN}Training subject category prediction model...{Style.RESET_ALL}")
|
|
294
|
+
print(f"Using {len(train_df_subjects)} records for training")
|
|
295
|
+
subj_vectorizer, subj_model, subj_mlb = train_subject_model(train_df_subjects)
|
|
296
|
+
|
|
297
|
+
# Predict missing subject categories
|
|
298
|
+
print(f"\n{Fore.CYAN}Predicting missing subject categories...{Style.RESET_ALL}")
|
|
299
|
+
mask = enriched_df['SC'].isna()
|
|
300
|
+
total = mask.sum()
|
|
301
|
+
completed = 0
|
|
302
|
+
|
|
303
|
+
for idx in enriched_df[mask].index:
|
|
304
|
+
text = ' '.join([
|
|
305
|
+
str(enriched_df.loc[idx, 'TI']) if pd.notna(enriched_df.loc[idx, 'TI']) else '',
|
|
306
|
+
str(enriched_df.loc[idx, 'AB']) if pd.notna(enriched_df.loc[idx, 'AB']) else ''
|
|
307
|
+
])
|
|
308
|
+
if text.strip():
|
|
309
|
+
predicted_subjects = predict_subjects(text, subj_vectorizer, subj_model, subj_mlb)
|
|
310
|
+
if predicted_subjects:
|
|
311
|
+
enriched_df.loc[idx, 'SC'] = predicted_subjects
|
|
312
|
+
enriched_df.loc[idx, 'WC'] = predicted_subjects # Copy to WC as well
|
|
313
|
+
completed += 1
|
|
314
|
+
if completed % 10 == 0:
|
|
315
|
+
print(f"Progress: {completed}/{total} records processed", end='\r')
|
|
316
|
+
print() # New line after progress
|
|
317
|
+
|
|
318
|
+
# Calculate enrichment statistics
|
|
319
|
+
stats = {
|
|
320
|
+
'total_records': len(df),
|
|
321
|
+
'original_empty_keywords': df['DE'].isna().sum(),
|
|
322
|
+
'original_empty_subjects': df['SC'].isna().sum(),
|
|
323
|
+
'original_empty_id': original_empty_id,
|
|
324
|
+
'enriched_empty_keywords': enriched_df['DE'].isna().sum(),
|
|
325
|
+
'enriched_empty_subjects': enriched_df['SC'].isna().sum(),
|
|
326
|
+
'enriched_empty_id': enriched_df['ID'].isna().sum(),
|
|
327
|
+
'keywords_filled': df['DE'].isna().sum() - enriched_df['DE'].isna().sum(),
|
|
328
|
+
'subjects_filled': df['SC'].isna().sum() - enriched_df['SC'].isna().sum(),
|
|
329
|
+
'id_filled': original_empty_id - enriched_df['ID'].isna().sum()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return enriched_df, stats
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
def copy_cr_raw_to_cr(file_path: str) -> bool:
|
|
5
|
+
"""
|
|
6
|
+
Copies CR_raw column to CR column
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
file_path (str): Path to Excel file
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
bool: True if successful, False otherwise
|
|
13
|
+
"""
|
|
14
|
+
try:
|
|
15
|
+
# Read Excel file
|
|
16
|
+
df = pd.read_excel(file_path)
|
|
17
|
+
|
|
18
|
+
# If CR_raw column exists and CR column is empty or doesn't exist
|
|
19
|
+
if 'CR_raw' in df.columns:
|
|
20
|
+
df['CR'] = df['CR_raw']
|
|
21
|
+
|
|
22
|
+
# Save changes
|
|
23
|
+
df.to_excel(file_path, index=False)
|
|
24
|
+
print(f"\nCR_raw column copied to CR: {os.path.basename(file_path)}")
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print(f"\nError: Failed to process file {file_path}: {str(e)}")
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
def process_merged_files(result_dir: str) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Copies CR_raw column to CR column in Merged_Bib.xlsx and
|
|
34
|
+
Merged_API_Enriched_Bib.xlsx files in Cell_Files folder
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
result_dir (str): Path to result directory
|
|
38
|
+
"""
|
|
39
|
+
cell_files_dir = os.path.join(result_dir, "Cell_Files")
|
|
40
|
+
|
|
41
|
+
if os.path.exists(cell_files_dir):
|
|
42
|
+
# Files to process
|
|
43
|
+
files_to_process = [
|
|
44
|
+
os.path.join(cell_files_dir, "Merged_Bib.xlsx"),
|
|
45
|
+
os.path.join(cell_files_dir, "Merged_API_Enriched_Bib.xlsx")
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
for file_path in files_to_process:
|
|
49
|
+
if os.path.exists(file_path):
|
|
50
|
+
copy_cr_raw_to_cr(file_path)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
def generate_detailed_statistics(wos_df: pd.DataFrame, scopus_df: pd.DataFrame, merged_df: pd.DataFrame) -> dict:
|
|
5
|
+
"""Generate detailed statistics"""
|
|
6
|
+
# Basic statistics
|
|
7
|
+
basic_stats = {
|
|
8
|
+
'General Statistics': {
|
|
9
|
+
'Total WoS Records': len(wos_df),
|
|
10
|
+
'Total Scopus Records': len(scopus_df),
|
|
11
|
+
'Merged Records': len(merged_df),
|
|
12
|
+
'Merge Rate': f"{(len(merged_df) / (len(wos_df) + len(scopus_df)) * 100):.2f}%"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# Column statistics
|
|
17
|
+
column_stats = {
|
|
18
|
+
'Column Statistics': {
|
|
19
|
+
'WoS Column Count': len(wos_df.columns),
|
|
20
|
+
'Scopus Column Count': len(scopus_df.columns),
|
|
21
|
+
'Merged Column Count': len(merged_df.columns),
|
|
22
|
+
'Common Column Count': len(set(wos_df.columns) & set(scopus_df.columns))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Data quality statistics
|
|
27
|
+
quality_stats = {
|
|
28
|
+
'Data Quality': {
|
|
29
|
+
'WoS Empty Cell Rate': f"{(wos_df.isna().sum().sum() / (len(wos_df) * len(wos_df.columns)) * 100):.2f}%",
|
|
30
|
+
'Scopus Empty Cell Rate': f"{(scopus_df.isna().sum().sum() / (len(scopus_df) * len(scopus_df.columns)) * 100):.2f}%",
|
|
31
|
+
'Merged Empty Cell Rate': f"{(merged_df.isna().sum().sum() / (len(merged_df) * len(merged_df.columns)) * 100):.2f}%"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Year-based statistics
|
|
36
|
+
if 'Year' in merged_df.columns:
|
|
37
|
+
year_stats = merged_df['Year'].value_counts().sort_index().to_dict()
|
|
38
|
+
year_distribution = {'Year Distribution': year_stats}
|
|
39
|
+
else:
|
|
40
|
+
year_distribution = {'Year Distribution': {'Year information not found': 0}}
|
|
41
|
+
|
|
42
|
+
return {**basic_stats, **column_stats, **quality_stats, **year_distribution}
|
|
43
|
+
|
|
44
|
+
def generate_metadata_statistics(df: pd.DataFrame) -> pd.DataFrame:
|
|
45
|
+
"""Generate quality statistics for metadata fields"""
|
|
46
|
+
metadata_desc = {
|
|
47
|
+
'AU': 'Author',
|
|
48
|
+
'DT': 'Document Type',
|
|
49
|
+
'PY': 'Publication Year',
|
|
50
|
+
'TI': 'Title',
|
|
51
|
+
'TC': 'Total Citation',
|
|
52
|
+
'SO': 'Journal',
|
|
53
|
+
'AB': 'Abstract',
|
|
54
|
+
'C1': 'Affiliation',
|
|
55
|
+
'DI': 'DOI',
|
|
56
|
+
'RP': 'Corresponding Author',
|
|
57
|
+
'ID': 'Keywords Plus',
|
|
58
|
+
'WC': 'Science Categories',
|
|
59
|
+
'DE': 'Keywords',
|
|
60
|
+
'LA': 'Language',
|
|
61
|
+
'CR': 'Cited References'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
stats = []
|
|
65
|
+
total_docs = len(df)
|
|
66
|
+
|
|
67
|
+
for field, desc in metadata_desc.items():
|
|
68
|
+
if field in df.columns:
|
|
69
|
+
missing = df[field].isna().sum() + df[field].eq('').sum()
|
|
70
|
+
missing_pct = (missing / total_docs) * 100
|
|
71
|
+
|
|
72
|
+
# Determine status based on missing percentage
|
|
73
|
+
if missing_pct == 0:
|
|
74
|
+
status = 'Excellent'
|
|
75
|
+
elif missing_pct < 1:
|
|
76
|
+
status = 'Very Good'
|
|
77
|
+
elif missing_pct < 5:
|
|
78
|
+
status = 'Good'
|
|
79
|
+
elif missing_pct < 20:
|
|
80
|
+
status = 'Acceptable'
|
|
81
|
+
elif missing_pct < 50:
|
|
82
|
+
status = 'Poor'
|
|
83
|
+
elif missing_pct < 90:
|
|
84
|
+
status = 'Critical'
|
|
85
|
+
else:
|
|
86
|
+
status = 'Completely Missing'
|
|
87
|
+
|
|
88
|
+
stats.append({
|
|
89
|
+
'Field': field,
|
|
90
|
+
'Description': desc,
|
|
91
|
+
'Missing Count': missing,
|
|
92
|
+
'Missing %': round(missing_pct, 2),
|
|
93
|
+
'Status': status
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return pd.DataFrame(stats)
|
|
97
|
+
|
|
98
|
+
def generate_metadata_comparison(simple_df: pd.DataFrame, enhanced_df: pd.DataFrame) -> pd.DataFrame:
|
|
99
|
+
"""Compare metadata statistics between two merge methods"""
|
|
100
|
+
metadata_desc = {
|
|
101
|
+
'AU': 'Author',
|
|
102
|
+
'DT': 'Document Type',
|
|
103
|
+
'PY': 'Publication Year',
|
|
104
|
+
'TI': 'Title',
|
|
105
|
+
'TC': 'Total Citation',
|
|
106
|
+
'SO': 'Journal',
|
|
107
|
+
'AB': 'Abstract',
|
|
108
|
+
'C1': 'Affiliation',
|
|
109
|
+
'DI': 'DOI',
|
|
110
|
+
'RP': 'Corresponding Author',
|
|
111
|
+
'ID': 'Keywords Plus',
|
|
112
|
+
'WC': 'Science Categories',
|
|
113
|
+
'DE': 'Keywords',
|
|
114
|
+
'LA': 'Language',
|
|
115
|
+
'CR': 'Cited References'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
comparison_stats = []
|
|
119
|
+
|
|
120
|
+
for field, desc in metadata_desc.items():
|
|
121
|
+
if field in simple_df.columns and field in enhanced_df.columns:
|
|
122
|
+
simple_missing = simple_df[field].isna().sum() + simple_df[field].eq('').sum()
|
|
123
|
+
enhanced_missing = enhanced_df[field].isna().sum() + enhanced_df[field].eq('').sum()
|
|
124
|
+
|
|
125
|
+
simple_missing_pct = (simple_missing / len(simple_df)) * 100
|
|
126
|
+
enhanced_missing_pct = (enhanced_missing / len(enhanced_df)) * 100
|
|
127
|
+
|
|
128
|
+
# Calculate improvement rate
|
|
129
|
+
improvement = simple_missing_pct - enhanced_missing_pct
|
|
130
|
+
improvement_pct = (improvement / simple_missing_pct * 100) if simple_missing_pct > 0 else 0
|
|
131
|
+
|
|
132
|
+
# Determine status based on enhanced method results
|
|
133
|
+
if enhanced_missing_pct == 0:
|
|
134
|
+
status = 'Excellent'
|
|
135
|
+
elif enhanced_missing_pct < 1:
|
|
136
|
+
status = 'Very Good'
|
|
137
|
+
elif enhanced_missing_pct < 5:
|
|
138
|
+
status = 'Good'
|
|
139
|
+
elif enhanced_missing_pct < 20:
|
|
140
|
+
status = 'Acceptable'
|
|
141
|
+
elif enhanced_missing_pct < 50:
|
|
142
|
+
status = 'Poor'
|
|
143
|
+
elif enhanced_missing_pct < 90:
|
|
144
|
+
status = 'Critical'
|
|
145
|
+
else:
|
|
146
|
+
status = 'Completely Missing'
|
|
147
|
+
|
|
148
|
+
comparison_stats.append({
|
|
149
|
+
'Field': field,
|
|
150
|
+
'Description': desc,
|
|
151
|
+
'Simple Missing': simple_missing,
|
|
152
|
+
'Simple Missing %': round(simple_missing_pct, 2),
|
|
153
|
+
'Enhanced Missing': enhanced_missing,
|
|
154
|
+
'Enhanced Missing %': round(enhanced_missing_pct, 2),
|
|
155
|
+
'Improvement': round(improvement, 2),
|
|
156
|
+
'Improvement %': round(improvement_pct, 2),
|
|
157
|
+
'Status': status
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
return pd.DataFrame(comparison_stats)
|
|
161
|
+
|
|
162
|
+
def compare_merge_methods(simple_stats: dict, enhanced_stats: dict,
|
|
163
|
+
simple_df: pd.DataFrame, enhanced_df: pd.DataFrame) -> dict:
|
|
164
|
+
"""Compare two merge methods and provide detailed analysis"""
|
|
165
|
+
|
|
166
|
+
# 1. Basic Comparison Metrics
|
|
167
|
+
basic_comparison = {
|
|
168
|
+
'Record Counts and Rates': {
|
|
169
|
+
'Simple Merge Records': simple_stats['Total Records'],
|
|
170
|
+
'Enhanced Merge Records': enhanced_stats['Total Records'],
|
|
171
|
+
'Record Difference': enhanced_stats['Total Records'] - simple_stats['Total Records'],
|
|
172
|
+
'Improvement Rate': f"{((enhanced_stats['Total Records'] - simple_stats['Total Records']) / simple_stats['Total Records'] * 100):.2f}%"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# 2. Data Quality Metrics
|
|
177
|
+
simple_empty = simple_df.isna().sum().sum()
|
|
178
|
+
enhanced_empty = enhanced_df.isna().sum().sum()
|
|
179
|
+
simple_total = len(simple_df) * len(simple_df.columns)
|
|
180
|
+
enhanced_total = len(enhanced_df) * len(enhanced_df.columns)
|
|
181
|
+
|
|
182
|
+
quality_metrics = {
|
|
183
|
+
'Data Quality Metrics': {
|
|
184
|
+
'Simple Merge Completeness': f"{(1 - simple_empty / simple_total) * 100:.2f}%",
|
|
185
|
+
'Enhanced Merge Completeness': f"{(1 - enhanced_empty / enhanced_total) * 100:.2f}%",
|
|
186
|
+
'Empty Cell Improvement Rate': f"{((simple_empty/simple_total - enhanced_empty/enhanced_total) / (simple_empty/simple_total) * 100):.2f}%",
|
|
187
|
+
'Total Improved Cells': simple_empty - enhanced_empty
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# 3. Field-Based Detailed Analysis
|
|
192
|
+
field_improvements = {}
|
|
193
|
+
for col in enhanced_df.columns:
|
|
194
|
+
if col in simple_df.columns:
|
|
195
|
+
simple_empty = simple_df[col].isna().sum()
|
|
196
|
+
enhanced_empty = enhanced_df[col].isna().sum()
|
|
197
|
+
simple_unique = simple_df[col].nunique()
|
|
198
|
+
enhanced_unique = enhanced_df[col].nunique()
|
|
199
|
+
|
|
200
|
+
if simple_empty != enhanced_empty or simple_unique != enhanced_unique:
|
|
201
|
+
improvement = {
|
|
202
|
+
'Simple Empty Cells': simple_empty,
|
|
203
|
+
'Enhanced Empty Cells': enhanced_empty,
|
|
204
|
+
'Empty Cell Improvement': simple_empty - enhanced_empty,
|
|
205
|
+
'Improvement Rate': f"{((simple_empty - enhanced_empty) / simple_empty * 100):.2f}%" if simple_empty > 0 else "0%",
|
|
206
|
+
'Simple Unique Values': simple_unique,
|
|
207
|
+
'Enhanced Unique Values': enhanced_unique,
|
|
208
|
+
'Enrichment': enhanced_unique - simple_unique
|
|
209
|
+
}
|
|
210
|
+
field_improvements[col] = improvement
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
'Basic Comparison': basic_comparison,
|
|
214
|
+
'Data Quality': quality_metrics,
|
|
215
|
+
'Field-Based Improvements': field_improvements
|
|
216
|
+
}
|