urielplus 1.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.
- urielplus/__init__.py +1 -0
- urielplus/base_uriel.py +272 -0
- urielplus/urielplus.py +362 -0
- urielplus/urielplus_databases.py +564 -0
- urielplus/urielplus_imputation.py +825 -0
- urielplus/urielplus_querying.py +1036 -0
- urielplus-1.0.0.dist-info/LICENSE.txt +425 -0
- urielplus-1.0.0.dist-info/METADATA +183 -0
- urielplus-1.0.0.dist-info/RECORD +11 -0
- urielplus-1.0.0.dist-info/WHEEL +5 -0
- urielplus-1.0.0.dist-info/top_level.txt +1 -0
urielplus/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
name='urielplus'
|
urielplus/base_uriel.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
class BaseURIEL:
|
|
8
|
+
"""
|
|
9
|
+
Configuration options:
|
|
10
|
+
cache (bool, optional): Whether to cache distance languages and changes to databases.
|
|
11
|
+
Defaults to False.
|
|
12
|
+
|
|
13
|
+
aggregation (str, optional): Whether to perform a union ('U') or average ('A') operation on data for aggregation and distance
|
|
14
|
+
calculations.
|
|
15
|
+
Defaults to 'U'.
|
|
16
|
+
|
|
17
|
+
fill_with_base_lang (bool, optional): Whether to fill missing values during aggregation using parent language data.
|
|
18
|
+
Defaults to False.
|
|
19
|
+
|
|
20
|
+
distance_metric (str, optional): The distance metric to use for distance calculations ("angular" or "cosine").
|
|
21
|
+
Defaults to "angular".
|
|
22
|
+
"""
|
|
23
|
+
cache = False
|
|
24
|
+
aggregation = 'U'
|
|
25
|
+
fill_with_base_lang = True
|
|
26
|
+
distance_metric = "angular"
|
|
27
|
+
|
|
28
|
+
def __init__(self, feats, langs, data, sources):
|
|
29
|
+
self.files = ["family_features.npz", "features.npz", "geocoord_features.npz"]
|
|
30
|
+
self.cur_dir = os.path.dirname(os.path.abspath(__file__))
|
|
31
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
32
|
+
|
|
33
|
+
self.feats = feats
|
|
34
|
+
self.langs = langs
|
|
35
|
+
self.data = data
|
|
36
|
+
self.sources = sources
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_cache(self):
|
|
40
|
+
"""
|
|
41
|
+
Returns whether to cache distance languages and changes to databases.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
bool: True if caching is enabled, False otherwise.
|
|
45
|
+
"""
|
|
46
|
+
return self.cache
|
|
47
|
+
|
|
48
|
+
def set_cache(self, cache):
|
|
49
|
+
"""
|
|
50
|
+
Sets whether to cache distance languages and changes to databases.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
cache (bool): True to enable caching, False otherwise.
|
|
54
|
+
|
|
55
|
+
Logging:
|
|
56
|
+
Error: Logs an error if the provided cache value is not a valid boolean value (True or False).
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
if isinstance(cache, bool):
|
|
60
|
+
self.cache = cache
|
|
61
|
+
else:
|
|
62
|
+
logging.error(f"Invalid boolean value: {cache}. Valid boolean values are True and False.")
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_aggregation(self):
|
|
67
|
+
"""
|
|
68
|
+
Returns whether to perform a union ('U') or average ('A') operation on data for aggregation and distance calculations.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: 'U' if aggregation is union, 'A' if aggregation is average.
|
|
72
|
+
"""
|
|
73
|
+
return self.aggregation
|
|
74
|
+
|
|
75
|
+
def set_aggregation(self, aggregation):
|
|
76
|
+
"""
|
|
77
|
+
Sets whether to perform a union ('U') or average ('A') operation on data for aggregation and distance calculations.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
aggregation (str): Whether to perform a union ('U') or average ('A') operation on data for aggregation and distance calculations..
|
|
81
|
+
|
|
82
|
+
Logging:
|
|
83
|
+
Error: Logs an error if the provided strategy value is invalid.
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
aggregations = ['U', 'A']
|
|
87
|
+
if aggregation in aggregations:
|
|
88
|
+
self.aggregation = aggregation
|
|
89
|
+
else:
|
|
90
|
+
logging.error(f"Invalid aggregation: {aggregation}. Valid aggregations are {aggregations}.")
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_fill_with_base_lang(self):
|
|
95
|
+
"""
|
|
96
|
+
Returns whether to fill missing values during aggregation using parent language data.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
bool: True if filling missing values with parent language data is enabled, False otherwise.
|
|
100
|
+
"""
|
|
101
|
+
return self.fill_with_base_lang
|
|
102
|
+
|
|
103
|
+
def set_fill_with_base_lang(self, fill_with_base_lang):
|
|
104
|
+
"""
|
|
105
|
+
Sets whether to fill missing values during aggregation using parent language data.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
fill_with_base_lang (bool): True to enable filling with base language, False otherwise.
|
|
109
|
+
|
|
110
|
+
Logging:
|
|
111
|
+
Error: Logs an error if the provided fill_with_base_lang value is not a valid boolean value (True or False).
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
if isinstance(fill_with_base_lang, bool):
|
|
115
|
+
self.fill_with_base_lang = fill_with_base_lang
|
|
116
|
+
self.dialects = self.get_dialects()
|
|
117
|
+
else:
|
|
118
|
+
logging.error(f"Invalid boolean value: {fill_with_base_lang}. Valid boolean values are True and False.")
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_distance_metric(self):
|
|
123
|
+
"""
|
|
124
|
+
Returns the distance metric to use for distance calculations.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
str: The distance metric to use for distance calculations.
|
|
128
|
+
"""
|
|
129
|
+
return self.distance_metric
|
|
130
|
+
|
|
131
|
+
def set_distance_metric(self, distance_metric):
|
|
132
|
+
"""
|
|
133
|
+
Sets the distance metric to use for distance calculations.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
distance_metric (bool): The distance metric to use for distance calculations.
|
|
137
|
+
|
|
138
|
+
Logging:
|
|
139
|
+
Error: Logs an error if the provided distance metric value is invalid.
|
|
140
|
+
|
|
141
|
+
"""
|
|
142
|
+
distance_metrics = ["angular", "cosine"]
|
|
143
|
+
if distance_metric in distance_metrics:
|
|
144
|
+
self.distance_metric = distance_metric
|
|
145
|
+
else:
|
|
146
|
+
logging.error(f"Invalid distance metric: {distance_metric}. Valid distance metrics are {distance_metrics}.")
|
|
147
|
+
sys.exit(1)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def is_iso_code(self, lang):
|
|
151
|
+
"""
|
|
152
|
+
Checks if a provided language code is in ISO 639-3 code format.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
lang (str): The language code to check.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
bool: True if the code is in ISO 639-3 code format (3 alphabetic characters); otherwise, False.
|
|
159
|
+
"""
|
|
160
|
+
return (len(lang) == 3 and lang.isalpha())
|
|
161
|
+
|
|
162
|
+
def is_iso_codes(self):
|
|
163
|
+
"""
|
|
164
|
+
Checks if all the languages in URIEL+ are represented in ISO 639-3 code format.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
bool: True if all languages are in ISO 639-3 code format; otherwise, False.
|
|
168
|
+
"""
|
|
169
|
+
return all(self.is_iso_code(lang) for langs in self.langs for lang in langs)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def is_glottocode(self, lang):
|
|
173
|
+
"""
|
|
174
|
+
Checks if a provided language code is in Glottocode format.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
lang (str): The language code to check.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
bool: True if the code is in Glottocode format (4 alphabetic characters followed by 4 numeric characters); otherwise, False.
|
|
181
|
+
"""
|
|
182
|
+
return (len(lang) == 8 and lang[:4].isalpha() and lang[4:].isnumeric())
|
|
183
|
+
|
|
184
|
+
def is_glottocodes(self):
|
|
185
|
+
"""
|
|
186
|
+
Checks if all the languages in URIEL+ are represented in Glottocode format.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
bool: True if all languages are in Glottocode format; otherwise, False.
|
|
190
|
+
"""
|
|
191
|
+
return all(self.is_glottocode(lang) for langs in self.langs for lang in langs)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def get_english_dialects(self):
|
|
196
|
+
"""
|
|
197
|
+
Returns a list of English dialects.
|
|
198
|
+
|
|
199
|
+
This function identifies English dialects based on the presence of Macro-English and the absence of Guinea Coast Croele English and
|
|
200
|
+
Pacific Creole English in a language's phylogeny vector language representation (ISO 639-3 or Glottocode).
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
list: A list of code representations for English dialects.
|
|
204
|
+
|
|
205
|
+
Logging:
|
|
206
|
+
Error: If the languages in URIEL+ are not all in either ISO 639-3 or Glottocode representation.
|
|
207
|
+
"""
|
|
208
|
+
if not self.is_glottocodes() and not self.is_iso_codes():
|
|
209
|
+
logging.error("Cannot retrieve English dialects if languages in URIEL+ are not all of either ISO 639-3 or Glottocode language representation.")
|
|
210
|
+
sys.exit(1)
|
|
211
|
+
eng_dialects = []
|
|
212
|
+
|
|
213
|
+
feat_indices = []
|
|
214
|
+
for feat in ["F_Macro-English", "F_Guinea Coast Creole English", "F_Pacific Creole English"]:
|
|
215
|
+
feat_indices.append(np.where(self.feats[0] == feat)[0][0])
|
|
216
|
+
|
|
217
|
+
for lang in self.langs[0]:
|
|
218
|
+
lang_index = np.where(self.langs[0] == lang)[0][0]
|
|
219
|
+
if 1.0 in self.data[0][lang_index][feat_indices[0]] and 0.0 in self.data[0][lang_index][feat_indices[1]] and 0.0 in self.data[0][lang_index][feat_indices[2]]:
|
|
220
|
+
eng_dialects.append(lang)
|
|
221
|
+
|
|
222
|
+
if self.is_glottocodes():
|
|
223
|
+
eng_dialects.remove("stan1293")
|
|
224
|
+
elif self.is_iso_codes():
|
|
225
|
+
eng_dialects.remove("eng")
|
|
226
|
+
|
|
227
|
+
return eng_dialects
|
|
228
|
+
|
|
229
|
+
def get_dialects(self):
|
|
230
|
+
"""
|
|
231
|
+
Returns a dictionary of dialects, with keys being the base languages and values being a list of the dialects.
|
|
232
|
+
|
|
233
|
+
This function identifies dialects for specific languages (e.g., Spanish, French, English) based on the current
|
|
234
|
+
language representation (ISO 639-3 or Glottocode).
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
dict: A dictionary where keys are indices of base languages, and values are lists of dialect language codes.
|
|
238
|
+
|
|
239
|
+
Logging:
|
|
240
|
+
Error: If the languages in URIEL+ are not all in either ISO 639-3 or Glottocode representation.
|
|
241
|
+
"""
|
|
242
|
+
if not self.is_glottocodes() and not self.is_iso_codes():
|
|
243
|
+
logging.error("Cannot retrieve English dialects if languages in URIEL+ are not all of either ISO 639-3 or Glottocode language representation.")
|
|
244
|
+
sys.exit(1)
|
|
245
|
+
if self.is_glottocodes():
|
|
246
|
+
SPANISH_DIALECTS = ["lore1243"]
|
|
247
|
+
FRENCH_DIALECTS = ["caju1236", "gulf1242"]
|
|
248
|
+
ENGLISH_DIALECTS = self.get_english_dialects()
|
|
249
|
+
GERMAN_DIALECTS = ["colo1254", "hutt1235", "midd1318", "midd1343", "nort2627", "north2628", "penn1240", "uppe1400"]
|
|
250
|
+
MALAY_DIALECTS = ["ambo1250", "baba1267", "baca1243", "bali1279", "band1353", "bera1262", "buki1247", "cent2053", "coco1260", "jamb1236", "keda1251", "kota1275", "kupa1239", "lara1260", "maka1305", "mala1479", "mala1480", "mala1481", "nege1240", "nort2828", "papu1250", "patt1249", "saba1263", "sril1245", "teng1267"]
|
|
251
|
+
ARABIC_DIALECTS = ["alge1239", "alge1240", "anda1287", "baha1259", "chad1249", "cypr1248", "dhof1235", "east2690", "egyp1253", "gulf1241", "hadr1236", "hija1235", "jude1264", "jude1265", "jude1266", "jude1267", "khor1274", "liby1240", "meso1252", "moro1292", "najd1235", "nort3139", "nort3142", "oman1239", "said1239", "sana1295", "suda1236", "taiz1242", "taji1248", "tuni1259", "uzbe1248"]
|
|
252
|
+
DIALECTS = {np.where(self.langs[1] == "stan1288")[0][0]: SPANISH_DIALECTS,
|
|
253
|
+
np.where(self.langs[1] == "stan1290")[0][0]: FRENCH_DIALECTS,
|
|
254
|
+
np.where(self.langs[1] == "stan1293")[0][0]: ENGLISH_DIALECTS,
|
|
255
|
+
np.where(self.langs[1] == "stan1295")[0][0]: GERMAN_DIALECTS,
|
|
256
|
+
np.where(self.langs[1] == "stan1306")[0][0]: MALAY_DIALECTS,
|
|
257
|
+
np.where(self.langs[1] == "stan1318")[0][0]: ARABIC_DIALECTS}
|
|
258
|
+
elif self.is_iso_codes():
|
|
259
|
+
SPANISH_DIALECTS = ["spq"]
|
|
260
|
+
FRENCH_DIALECTS = ["frc"]
|
|
261
|
+
ENGLISH_DIALECTS = self.get_english_dialects()
|
|
262
|
+
GERMAN_DIALECTS = ["gct", "geh", "gml", "gmh", "nds", "frs", "pdc", "sxu"]
|
|
263
|
+
MALAY_DIALECTS = ["abs", "mbf", "btj", "mhp", "bpq", "bve", "bvu", "pse", "coa", "jax", "meo", "mqg", "mkn", "lrt", "mfp", "zlm", "xdy", "xmm", "zmi", "max", "pmy", "mfa", "msi", "sci", "vkt"]
|
|
264
|
+
ARABIC_DIALECTS = ["arq", "aao", "xaa", "abv", "shu", "acy", "adf", "avl", "arz", "afb", "ayh", "acw", "yud", "aju", "yhd", "jye", "ayl", "acm", "ary", "ars", "apc", "ayp", "acx", "aec", "ayn", "apd", "acq", "abh", "aeb", "auz"]
|
|
265
|
+
DIALECTS = {np.where(self.langs[1] == "spa")[0][0]: SPANISH_DIALECTS,
|
|
266
|
+
np.where(self.langs[1] == "fra")[0][0]: FRENCH_DIALECTS,
|
|
267
|
+
np.where(self.langs[1] == "eng")[0][0]: ENGLISH_DIALECTS,
|
|
268
|
+
np.where(self.langs[1] == "deu")[0][0]: GERMAN_DIALECTS,
|
|
269
|
+
np.where(self.langs[1] == "zsm")[0][0]: MALAY_DIALECTS,
|
|
270
|
+
np.where(self.langs[1] == "arb")[0][0]: ARABIC_DIALECTS}
|
|
271
|
+
|
|
272
|
+
return DIALECTS
|
urielplus/urielplus.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
from .urielplus_databases import URIELPlusDatabases
|
|
2
|
+
from .urielplus_imputation import URIELPlusImputation
|
|
3
|
+
from .urielplus_querying import URIELPlusQuerying
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
13
|
+
|
|
14
|
+
'''
|
|
15
|
+
URIEL+ library for integrating new and updated databases into URIEL and robust distance calculations.
|
|
16
|
+
|
|
17
|
+
Authors: Aditya Khan, Mason Shipton, David Anugraha, Kaiyao Duan, Phuong H. Hoang, Eric Khiu, A. Seza Doğruöz,
|
|
18
|
+
En-Shiun Annie Lee
|
|
19
|
+
|
|
20
|
+
Last modified: October 17, 2024
|
|
21
|
+
'''
|
|
22
|
+
|
|
23
|
+
class URIELPlus(URIELPlusDatabases, URIELPlusImputation, URIELPlusQuerying):
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""
|
|
26
|
+
Initializes the URIEL+ class, setting up vector identifications of languages, and instantiating the classes
|
|
27
|
+
needed for integrating databases, imputing missing values, and querying the knowledge base.
|
|
28
|
+
|
|
29
|
+
Logging:
|
|
30
|
+
Info: Logs information when a file is missing in the data directory and copied from the old_data
|
|
31
|
+
directory.
|
|
32
|
+
|
|
33
|
+
Error: Logs an error if a file is not found in the old_data directory.
|
|
34
|
+
"""
|
|
35
|
+
self.files = ["family_features.npz", "features.npz", "geocoord_features.npz"]
|
|
36
|
+
self.cur_dir = os.path.dirname(os.path.abspath(__file__))
|
|
37
|
+
self.loaded_features = []
|
|
38
|
+
|
|
39
|
+
for file in self.files:
|
|
40
|
+
file_path = os.path.join(self.cur_dir, "data", file)
|
|
41
|
+
if not os.path.isfile(file_path):
|
|
42
|
+
logging.info(f"{file_path} is missing in \"data\". Copying from \"old_data\"...")
|
|
43
|
+
|
|
44
|
+
old_file_path = os.path.join(self.cur_dir, "data", "old_data", file)
|
|
45
|
+
try:
|
|
46
|
+
shutil.copy(old_file_path, file_path)
|
|
47
|
+
except FileNotFoundError:
|
|
48
|
+
logging.error(f"{file} not found in \"old_data\".")
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
with np.load(file_path, allow_pickle=True) as l:
|
|
51
|
+
self.loaded_features.append(dict(l))
|
|
52
|
+
|
|
53
|
+
self.feats = [l["feats"] for l in self.loaded_features]
|
|
54
|
+
self.langs = [l["langs"] for l in self.loaded_features]
|
|
55
|
+
self.data = [l["data"] for l in self.loaded_features]
|
|
56
|
+
self.sources = [l["sources"] for l in self.loaded_features]
|
|
57
|
+
|
|
58
|
+
self.databases = URIELPlusDatabases(self.feats, self.langs, self.data, self.sources)
|
|
59
|
+
self.imputation = URIELPlusImputation(self.feats, self.langs, self.data, self.sources)
|
|
60
|
+
self.querying = URIELPlusQuerying(self.feats, self.langs, self.data, self.sources)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_loaded_features(self, l_name):
|
|
65
|
+
"""
|
|
66
|
+
Returns the URIEL+ loaded features associated with the provided name, if the name is valid.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
l_name (str): The name of the loaded features to return. Valid options are "phylogeny", "typological",
|
|
70
|
+
or "geography".
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
np.ndarray: The corresponding loaded features as a NumPy array.
|
|
74
|
+
|
|
75
|
+
Logging:
|
|
76
|
+
Error: Logs an error if the provided loaded features name is invalid.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
l_map = {
|
|
80
|
+
"phylogeny": self.loaded_features[0],
|
|
81
|
+
"typological": self.loaded_features[1],
|
|
82
|
+
"geography": self.loaded_features[2],
|
|
83
|
+
}
|
|
84
|
+
if l_name in l_map:
|
|
85
|
+
return l_map[l_name]
|
|
86
|
+
logging.error(f"Unknown loaded features: {l_name}. Valid loaded features are {list(l_map.keys())}.")
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
The following three functions return loaded features representing phylogeny, typological, and geography
|
|
91
|
+
vectors, respectively.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
np.ndarray: The corresponding loaded features as a NumPy array.
|
|
95
|
+
"""
|
|
96
|
+
def get_phylogeny_loaded_features(self):
|
|
97
|
+
"""Returns the phylogeny loaded features."""
|
|
98
|
+
return self.loaded_features[0]
|
|
99
|
+
|
|
100
|
+
def get_typological_loaded_features(self):
|
|
101
|
+
"""Returns the typological loaded features."""
|
|
102
|
+
return self.loaded_features[1]
|
|
103
|
+
|
|
104
|
+
def get_geography_loaded_features(self):
|
|
105
|
+
"""Returns the geography loaded features."""
|
|
106
|
+
return self.loaded_features[2]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def set_loaded_features(self, l_name, file):
|
|
110
|
+
"""
|
|
111
|
+
Updates the loaded features associated with the provided name by loading data from the provided file.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
l_name (str): The name of the loaded_features to update. Valid options are "phylogeny", "typological",
|
|
115
|
+
or "geography".
|
|
116
|
+
file (str): The file name to load the loaded features data from.
|
|
117
|
+
|
|
118
|
+
Logging:
|
|
119
|
+
Error: Logs an error if the provided loaded features name is invalid or if the file loading fails.
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
l_map = {
|
|
123
|
+
"phylogeny": 0,
|
|
124
|
+
"typological": 1,
|
|
125
|
+
"geography": 2,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if l_name in l_map:
|
|
129
|
+
file_path = os.path.join(self.cur_dir, "data", file)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
with np.load(file_path, allow_pickle=True) as l:
|
|
133
|
+
l_idx = l_map[l_name]
|
|
134
|
+
self.loaded_features[l_idx] = dict(l)
|
|
135
|
+
self.feats[l_idx] = l["feats"]
|
|
136
|
+
self.langs[l_idx] = l["langs"]
|
|
137
|
+
self.data[l_idx] = l["data"]
|
|
138
|
+
self.sources[l_idx] = l["sources"]
|
|
139
|
+
self.files[l_idx] = file
|
|
140
|
+
self.databases = URIELPlusDatabases(self.feats, self.langs, self.data, self.sources)
|
|
141
|
+
self.imputation = URIELPlusImputation(self.feats, self.langs, self.data, self.sources)
|
|
142
|
+
self.querying = URIELPlusQuerying(self.feats, self.langs, self.data, self.sources)
|
|
143
|
+
logging.info(f"{l_name} loaded features updated successfully from {file}.")
|
|
144
|
+
except FileNotFoundError:
|
|
145
|
+
logging.error(f"File not found: {file_path}. Failed to update {l_name} loaded features.")
|
|
146
|
+
sys.exit(1)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logging.error(f"An error occurred while loading the file {file}: {e}")
|
|
149
|
+
sys.exit(1)
|
|
150
|
+
else:
|
|
151
|
+
logging.error(f"Unknown loaded features: {l_name}. Valid loaded features are {list(l_map.keys())}.")
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
The following three functions updates loaded features representing phylogeny, typological, and geography
|
|
156
|
+
vectors, respectively.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
file (str): The file name to load the loaded features data from.
|
|
160
|
+
"""
|
|
161
|
+
def set_phylogeny_loaded_features(self, file):
|
|
162
|
+
"""Updates the phylogeny loaded features."""
|
|
163
|
+
self.set_loaded_features(self, "phylogeny", file)
|
|
164
|
+
|
|
165
|
+
def set_typological_loaded_features(self, file):
|
|
166
|
+
"""Updates the typological loaded features."""
|
|
167
|
+
self.set_loaded_features(self, "typological", file)
|
|
168
|
+
|
|
169
|
+
def set_geography_loaded_features(self, file):
|
|
170
|
+
"""Updates the geography loaded features."""
|
|
171
|
+
self.set_loaded_features(self, "geography", file)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_arrays(self, l_name):
|
|
176
|
+
"""
|
|
177
|
+
Returns the arrays within the URIEL+ loaded features associated with the provided name, if the name is
|
|
178
|
+
valid.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
l_name (str): The name of the loaded features to return. Valid options are "phylogeny", "typological",
|
|
182
|
+
or "geography".
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
tuple: The arrays within the corresponding loaded features as NumPy arrays.
|
|
186
|
+
"""
|
|
187
|
+
loaded_features = self.get_loaded_features(l_name)
|
|
188
|
+
return loaded_features["feats"], loaded_features["data"], loaded_features["langs"], loaded_features["sources"]
|
|
189
|
+
|
|
190
|
+
"""
|
|
191
|
+
The following three functions return all the arrays within loaded features representing
|
|
192
|
+
phylogeny, typological, and geography vectors, respectively.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
tuple: The arrays within the corresponding loaded features as NumPy arrays.
|
|
196
|
+
"""
|
|
197
|
+
def get_phylogeny_arrays(self):
|
|
198
|
+
"""Returns the phylogeny arrays."""
|
|
199
|
+
return self.get_arrays("phylogeny")
|
|
200
|
+
|
|
201
|
+
def get_typological_arrays(self):
|
|
202
|
+
"""Returns the typological arrays."""
|
|
203
|
+
return self.get_arrays("typological")
|
|
204
|
+
|
|
205
|
+
def get_geography_arrays(self):
|
|
206
|
+
"""Returns the geography arrays."""
|
|
207
|
+
return self.get_arrays("geography")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
The following four functions return all the arrays from all loaded features representing
|
|
212
|
+
features, languages, feature data, and sources, respectively.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
list: A list of NumPy arrays containing the corresponding arrays from each loaded features.
|
|
216
|
+
"""
|
|
217
|
+
def get_features_arrays(self):
|
|
218
|
+
"""Returns the features arrays."""
|
|
219
|
+
return self.feats
|
|
220
|
+
|
|
221
|
+
def get_languages_arrays(self):
|
|
222
|
+
"""Returns the languages arrays."""
|
|
223
|
+
return self.langs
|
|
224
|
+
|
|
225
|
+
def get_data_arrays(self):
|
|
226
|
+
"""Returns the data arrays."""
|
|
227
|
+
return self.data
|
|
228
|
+
|
|
229
|
+
def get_sources_arrays(self):
|
|
230
|
+
"""Returns the sources arrays."""
|
|
231
|
+
return self.sources
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
"""
|
|
235
|
+
The following functions return the array corresponding with a specific loaded features
|
|
236
|
+
and one of either features, languages, data, or sources arrays.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
np.ndarray: A NumPy array of either the features, languages, data, or sources of a specific loaded
|
|
240
|
+
features.
|
|
241
|
+
"""
|
|
242
|
+
def get_phylogeny_features_array(self):
|
|
243
|
+
"""Returns the features array of the phylogeny loaded features."""
|
|
244
|
+
return self.feats[0]
|
|
245
|
+
|
|
246
|
+
def get_typological_features_array(self):
|
|
247
|
+
"""Returns the features array of the typological loaded features."""
|
|
248
|
+
return self.feats[1]
|
|
249
|
+
|
|
250
|
+
def get_geography_features_array(self):
|
|
251
|
+
"""Returns the features array of the geography loaded features."""
|
|
252
|
+
return self.feats[2]
|
|
253
|
+
|
|
254
|
+
def get_phylogeny_languages_array(self):
|
|
255
|
+
"""Returns the languages array of the phylogeny loaded features."""
|
|
256
|
+
return self.langs[0]
|
|
257
|
+
|
|
258
|
+
def get_typological_languages_array(self):
|
|
259
|
+
"""Returns the languages array of the typological loaded features."""
|
|
260
|
+
return self.langs[1]
|
|
261
|
+
|
|
262
|
+
def get_geography_languages_array(self):
|
|
263
|
+
"""Returns the languages array of the geography loaded features."""
|
|
264
|
+
return self.langs[2]
|
|
265
|
+
|
|
266
|
+
def get_phylogeny_data_array(self):
|
|
267
|
+
"""Returns the data array of the phylogeny loaded features."""
|
|
268
|
+
return self.data[0]
|
|
269
|
+
|
|
270
|
+
def get_typological_data_array(self):
|
|
271
|
+
"""Returns the data array of the typological loaded features."""
|
|
272
|
+
return self.data[1]
|
|
273
|
+
|
|
274
|
+
def get_geography_data_array(self):
|
|
275
|
+
"""Returns the data array of the geography loaded features."""
|
|
276
|
+
return self.data[2]
|
|
277
|
+
|
|
278
|
+
def get_phylogeny_sources_array(self):
|
|
279
|
+
"""Returns the sources array of the phylogeny loaded features."""
|
|
280
|
+
return self.sources[0]
|
|
281
|
+
|
|
282
|
+
def get_typological_sources_array(self):
|
|
283
|
+
"""Returns the sources array of the typological loaded features."""
|
|
284
|
+
return self.sources[1]
|
|
285
|
+
|
|
286
|
+
def get_geography_sources_array(self):
|
|
287
|
+
"""Returns the sources array of the geography loaded features."""
|
|
288
|
+
return self.sources[2]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def query_yes_no(self, question, default="yes"):
|
|
292
|
+
"""
|
|
293
|
+
Prompts the user with a yes/no question and returns their response.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
question (str): The question to ask the user.
|
|
297
|
+
default (str): The default answer if the user just hits Enter. It must be "yes", "no", or None.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
bool: True if the user answered "yes"; False if the user answered "no".
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
ValueError: If the default answer is not "yes", "no", or None.
|
|
304
|
+
"""
|
|
305
|
+
valid = {"yes": True, "y": True, "ye": True,
|
|
306
|
+
"no": False, "n": False}
|
|
307
|
+
if default is None:
|
|
308
|
+
prompt = " [y/n] "
|
|
309
|
+
elif default == "yes":
|
|
310
|
+
prompt = " [Y/n] "
|
|
311
|
+
elif default == "no":
|
|
312
|
+
prompt = " [y/N] "
|
|
313
|
+
else:
|
|
314
|
+
logging.error("invalid default answer: '%s'" % default)
|
|
315
|
+
sys.exit(1)
|
|
316
|
+
|
|
317
|
+
while True:
|
|
318
|
+
sys.stdout.write(question + prompt)
|
|
319
|
+
choice = input().lower()
|
|
320
|
+
if default is not None and choice == '':
|
|
321
|
+
return valid[default]
|
|
322
|
+
elif choice in valid:
|
|
323
|
+
return valid[choice]
|
|
324
|
+
else:
|
|
325
|
+
sys.stdout.write("Please respond with \"yes\" or \"no\" "
|
|
326
|
+
"(or 'y' or 'n').\n")
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def reset(self):
|
|
330
|
+
"""
|
|
331
|
+
Restores the URIEL knowledge base by copying necessary files to the main data directory.
|
|
332
|
+
|
|
333
|
+
The function prompts if the user wants to revert to URIEL, and if yes, then moves all old data files back
|
|
334
|
+
to the main directory.
|
|
335
|
+
"""
|
|
336
|
+
files_to_copy = ["family_features.npz",
|
|
337
|
+
"features.npz", "geocoord_features.npz"]
|
|
338
|
+
cont = self.query_yes_no(f"Resetting to URIEL involves copying the files {files_to_copy} into the data directory. Any files with the same name will be replaced. Continue?")
|
|
339
|
+
if not cont:
|
|
340
|
+
return
|
|
341
|
+
for file in files_to_copy:
|
|
342
|
+
from_file_path = os.path.join(self.cur_dir, "data", "old_data", file)
|
|
343
|
+
to_file_path = os.path.join(self.cur_dir, "data", file)
|
|
344
|
+
try:
|
|
345
|
+
shutil.copy(from_file_path, to_file_path)
|
|
346
|
+
except Exception as e:
|
|
347
|
+
logging.error(f"Difficulty copying {from_file_path} to {to_file_path}: {e}")
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
self.loaded_features = []
|
|
350
|
+
for file in self.files:
|
|
351
|
+
file_path = os.path.join(self.cur_dir, "data", file)
|
|
352
|
+
with np.load(file_path, allow_pickle=True) as l:
|
|
353
|
+
self.loaded_features.append(dict(l))
|
|
354
|
+
|
|
355
|
+
self.feats = [l["feats"] for l in self.loaded_features]
|
|
356
|
+
self.langs = [l["langs"] for l in self.loaded_features]
|
|
357
|
+
self.data = [l["data"] for l in self.loaded_features]
|
|
358
|
+
self.sources = [l["sources"] for l in self.loaded_features]
|
|
359
|
+
|
|
360
|
+
self.databases = URIELPlusDatabases(self.feats, self.langs, self.data, self.sources)
|
|
361
|
+
self.imputation = URIELPlusImputation(self.feats, self.langs, self.data, self.sources)
|
|
362
|
+
self.querying = URIELPlusQuerying(self.feats, self.langs, self.data, self.sources)
|