mostlyai-engine 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.
- mostlyai/engine/__init__.py +27 -0
- mostlyai/engine/_common.py +612 -0
- mostlyai/engine/_dtypes.py +58 -0
- mostlyai/engine/_encoding_types/__init__.py +13 -0
- mostlyai/engine/_encoding_types/language/__init__.py +13 -0
- mostlyai/engine/_encoding_types/language/text.py +41 -0
- mostlyai/engine/_encoding_types/tabular/__init__.py +13 -0
- mostlyai/engine/_encoding_types/tabular/categorical.py +115 -0
- mostlyai/engine/_encoding_types/tabular/character.py +155 -0
- mostlyai/engine/_encoding_types/tabular/datetime.py +254 -0
- mostlyai/engine/_encoding_types/tabular/itt.py +352 -0
- mostlyai/engine/_encoding_types/tabular/lat_long.py +353 -0
- mostlyai/engine/_encoding_types/tabular/numeric.py +548 -0
- mostlyai/engine/_language/__init__.py +17 -0
- mostlyai/engine/_language/common.py +110 -0
- mostlyai/engine/_language/encoding.py +162 -0
- mostlyai/engine/_language/engine/__init__.py +13 -0
- mostlyai/engine/_language/engine/base.py +51 -0
- mostlyai/engine/_language/engine/hf_engine.py +136 -0
- mostlyai/engine/_language/engine/vllm_engine.py +187 -0
- mostlyai/engine/_language/formatron_utils.py +114 -0
- mostlyai/engine/_language/generation.py +309 -0
- mostlyai/engine/_language/lstm.py +139 -0
- mostlyai/engine/_language/tokenizer_utils.py +144 -0
- mostlyai/engine/_language/training.py +718 -0
- mostlyai/engine/_memory.py +69 -0
- mostlyai/engine/_tabular/__init__.py +13 -0
- mostlyai/engine/_tabular/argn.py +1382 -0
- mostlyai/engine/_tabular/common.py +29 -0
- mostlyai/engine/_tabular/encoding.py +406 -0
- mostlyai/engine/_tabular/fairness.py +245 -0
- mostlyai/engine/_tabular/generation.py +1157 -0
- mostlyai/engine/_tabular/training.py +856 -0
- mostlyai/engine/_training_utils.py +159 -0
- mostlyai/engine/_workspace.py +225 -0
- mostlyai/engine/analysis.py +571 -0
- mostlyai/engine/domain.py +171 -0
- mostlyai/engine/encoding.py +42 -0
- mostlyai/engine/generation.py +110 -0
- mostlyai/engine/splitting.py +257 -0
- mostlyai/engine/training.py +104 -0
- mostlyai_engine-1.0.0.dist-info/METADATA +179 -0
- mostlyai_engine-1.0.0.dist-info/RECORD +45 -0
- mostlyai_engine-1.0.0.dist-info/WHEEL +4 -0
- mostlyai_engine-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# Copyright 2025 MOSTLY AI
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from itertools import chain
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import pandas as pd
|
|
20
|
+
from numpy.typing import NDArray
|
|
21
|
+
|
|
22
|
+
from mostlyai.engine._common import safe_convert_string
|
|
23
|
+
from mostlyai.engine._encoding_types.tabular.categorical import (
|
|
24
|
+
CATEGORICAL_UNKNOWN_TOKEN,
|
|
25
|
+
encode_categorical,
|
|
26
|
+
)
|
|
27
|
+
from mostlyai.engine._encoding_types.tabular.character import (
|
|
28
|
+
decode_character,
|
|
29
|
+
encode_character,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
MAX_UNIQUE_VALUES_PER_QUAD = 10_000
|
|
33
|
+
RARE_CATEGORY_THRESHOLD = 20
|
|
34
|
+
MIN_UNIQUE_QUAD_VALUES = 5
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GeoPositionSymbols(Enum):
|
|
38
|
+
A = ("0", "0") # NORTH-WEST
|
|
39
|
+
B = ("1", "0") # NORTH-EAST
|
|
40
|
+
C = ("0", "1") # SOUTH-WEST
|
|
41
|
+
D = ("1", "1") # SOUTH-EAST
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
GeoPositionSymbolsReverseLookup = {s.value: s.name for s in GeoPositionSymbols}
|
|
45
|
+
GEOPOSITION_PRECISION = 100_000
|
|
46
|
+
GEOPOSITION_BINARY_REPRESENTATION_LENGTH = 28
|
|
47
|
+
GEOPOSITION_MAX_LATITUDE = 90 * GEOPOSITION_PRECISION
|
|
48
|
+
GEOPOSITION_MAX_LONGITUDE = 180 * GEOPOSITION_PRECISION
|
|
49
|
+
GEOPOSITION_STEP_SIZE = 2
|
|
50
|
+
GEOPOSITION_SIGNS_LENGTH = 2
|
|
51
|
+
NUMBER_OF_GEOPOSITION_QUADS = 8
|
|
52
|
+
GEOPOSITION_FIRST_QUAD_LENGTH = 12
|
|
53
|
+
GEOPOSITION_FIRST_QUAD_ONSET = GEOPOSITION_FIRST_QUAD_LENGTH + GEOPOSITION_SIGNS_LENGTH
|
|
54
|
+
GEOPOSITION_QUADTILE_LENGTH = GEOPOSITION_FIRST_QUAD_ONSET + NUMBER_OF_GEOPOSITION_QUADS * GEOPOSITION_STEP_SIZE
|
|
55
|
+
NONE_QUADTILE_ENTRY = "++AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
|
56
|
+
PLACEHOLDER_QUADTILE_ENTRY = "++AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
|
57
|
+
GEOPOSITION_OUTPUT_FORMAT = "{latitude:.5f}, {longitude:.5f}"
|
|
58
|
+
QUADTILE = "QUADTILE"
|
|
59
|
+
QUAD_COLUMNS = [
|
|
60
|
+
f"QUAD{GEOPOSITION_FIRST_QUAD_LENGTH + GEOPOSITION_STEP_SIZE * i}" for i in range(NUMBER_OF_GEOPOSITION_QUADS)
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _latlong_int_to_binary(latlong: NDArray) -> NDArray:
|
|
65
|
+
"""
|
|
66
|
+
Transform an array of int64 tuples representing latlong (with 10^-5 precision) to
|
|
67
|
+
an array of quadtile strings (28 characters for each). Note: excluding the signs.
|
|
68
|
+
|
|
69
|
+
For example: [[3306060 3300000], [3312120 3300000]] ->
|
|
70
|
+
['AAAAAADDAADAADBDCADACBCABBAA' 'AAAAAADDAADABCACDACBDBDBBAAA']
|
|
71
|
+
|
|
72
|
+
:param latlong: Numpy array of (lat, long) int64 tuples (non-negative numbers)
|
|
73
|
+
:return: Numpy array of object (quadtile strings)
|
|
74
|
+
"""
|
|
75
|
+
str_repr = np.vectorize(np.binary_repr, otypes=[str])(
|
|
76
|
+
latlong[:, :],
|
|
77
|
+
width=GEOPOSITION_BINARY_REPRESENTATION_LENGTH,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def _latlong_to_binary_single(latitude: str, longitude: str) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Transform latitude and longitude (in binary string format) to quadtile
|
|
83
|
+
"""
|
|
84
|
+
quad = [GeoPositionSymbolsReverseLookup[(lat, long)] for lat, long in zip(list(latitude), list(longitude))]
|
|
85
|
+
return "".join(quad)
|
|
86
|
+
|
|
87
|
+
quads_transform = np.frompyfunc(_latlong_to_binary_single, nin=2, nout=1)
|
|
88
|
+
quads = quads_transform(str_repr[:, 0], str_repr[:, 1])
|
|
89
|
+
|
|
90
|
+
return quads
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _latlong_to_binary(geo_positions: NDArray) -> list[str]:
|
|
94
|
+
"""
|
|
95
|
+
Transform geoposition values (latitude and longitude) into quadtile string representations.
|
|
96
|
+
|
|
97
|
+
For example: [[33.0606 33. ], [33.12121 33. ]] ->
|
|
98
|
+
['++AAAAAADDAADAADBDCADACBCABBAA' '++AAAAAADDAADABCACDACBDBDBBAAA']
|
|
99
|
+
|
|
100
|
+
:param geo_positions: Numpy array of (lat, long) tuples
|
|
101
|
+
:return:
|
|
102
|
+
"""
|
|
103
|
+
# operate on integer values to get rid of the values below certain precision
|
|
104
|
+
numeric = (geo_positions * GEOPOSITION_PRECISION).astype(int)
|
|
105
|
+
|
|
106
|
+
# get the sign and work on the absolute values
|
|
107
|
+
signs = np.array(["-", "+"])[(numeric >= 0).astype(int)]
|
|
108
|
+
numeric = np.abs(numeric)
|
|
109
|
+
|
|
110
|
+
# apply ceiling to the values
|
|
111
|
+
numeric[numeric[:, 0] > GEOPOSITION_MAX_LATITUDE, 0] = GEOPOSITION_MAX_LATITUDE
|
|
112
|
+
numeric[numeric[:, 1] > GEOPOSITION_MAX_LONGITUDE, 1] = GEOPOSITION_MAX_LONGITUDE
|
|
113
|
+
|
|
114
|
+
# transform numeric to binary (quadtile strings)
|
|
115
|
+
quads = _latlong_int_to_binary(numeric)
|
|
116
|
+
|
|
117
|
+
# build final strings, adding signs
|
|
118
|
+
combine_strings = np.frompyfunc(lambda sign1, sign2, quad: sign1 + sign2 + quad, 3, 1)
|
|
119
|
+
quadtiles = combine_strings(signs[:, 0], signs[:, 1], quads)
|
|
120
|
+
|
|
121
|
+
return quadtiles
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _binary_to_latlong(data: pd.Series) -> pd.Series:
|
|
125
|
+
"""
|
|
126
|
+
Transform quadtile column into latlong format.
|
|
127
|
+
For example ("-+AAACBAACBACBCBCBCBAACBAAAAAA") -> ("-90, 180")
|
|
128
|
+
|
|
129
|
+
:param data: Quadtile as pandas series
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def _binary_to_latlong_single(
|
|
133
|
+
quadtile: str,
|
|
134
|
+
) -> tuple[float | None, float | None]:
|
|
135
|
+
"""
|
|
136
|
+
Transform from a quadtile to its (lat, long) float representation
|
|
137
|
+
For example: '-+AAAAAABCCBBBDADBBCCADDCBCCBB' -> (30.87, 133.11331)
|
|
138
|
+
"""
|
|
139
|
+
if not quadtile or not len(quadtile) == len(PLACEHOLDER_QUADTILE_ENTRY):
|
|
140
|
+
return None, None
|
|
141
|
+
|
|
142
|
+
latitude_sign = 1 if quadtile[0] == "+" else -1
|
|
143
|
+
longitude_sign = 1 if quadtile[1] == "+" else -1
|
|
144
|
+
|
|
145
|
+
latitude_bin = []
|
|
146
|
+
longitude_bin = []
|
|
147
|
+
for quadtile_character in quadtile[2:]:
|
|
148
|
+
lat, long = GeoPositionSymbols[quadtile_character].value
|
|
149
|
+
latitude_bin.append(lat)
|
|
150
|
+
longitude_bin.append(long)
|
|
151
|
+
|
|
152
|
+
latitude = (int("".join(latitude_bin), 2) * latitude_sign) / GEOPOSITION_PRECISION
|
|
153
|
+
longitude = (int("".join(longitude_bin), 2) * longitude_sign) / GEOPOSITION_PRECISION
|
|
154
|
+
|
|
155
|
+
return latitude, longitude
|
|
156
|
+
|
|
157
|
+
quads_reverse_transform = np.frompyfunc(_binary_to_latlong_single, nin=1, nout=2)
|
|
158
|
+
latitudes, longitudes = quads_reverse_transform(data.to_numpy())
|
|
159
|
+
|
|
160
|
+
latlong_to_string = np.frompyfunc(
|
|
161
|
+
lambda lat, long: GEOPOSITION_OUTPUT_FORMAT.format(latitude=lat, longitude=long),
|
|
162
|
+
nin=2,
|
|
163
|
+
nout=1,
|
|
164
|
+
)
|
|
165
|
+
geo_positions = latlong_to_string(latitudes, longitudes)
|
|
166
|
+
|
|
167
|
+
geo_positions_data = pd.Series(data=geo_positions, name=data.name, index=data.index)
|
|
168
|
+
|
|
169
|
+
return geo_positions_data
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def split_sub_columns_latlong(
|
|
173
|
+
data: pd.Series,
|
|
174
|
+
) -> pd.DataFrame:
|
|
175
|
+
latitude_longitude = split_str_to_latlong(data)
|
|
176
|
+
isnan = np.isnan(latitude_longitude)
|
|
177
|
+
latitude_longitude[isnan] = 0.0
|
|
178
|
+
# mark invalid rows
|
|
179
|
+
invalid_entry = np.any(isnan, axis=1).values
|
|
180
|
+
|
|
181
|
+
quadtile = _latlong_to_binary(latitude_longitude.to_numpy())
|
|
182
|
+
|
|
183
|
+
def _geoposition_split_quads(quadtile: str) -> tuple:
|
|
184
|
+
"""
|
|
185
|
+
Split the complete quadtile (e.g. '-+AAAAAABCCBBBDADBBCCADDCBCCBB') into its
|
|
186
|
+
sub-quadtiles: QUAD12, ..., QUAD28
|
|
187
|
+
"""
|
|
188
|
+
quads = [
|
|
189
|
+
quadtile[0:x]
|
|
190
|
+
for x in range(
|
|
191
|
+
GEOPOSITION_FIRST_QUAD_ONSET,
|
|
192
|
+
GEOPOSITION_QUADTILE_LENGTH + GEOPOSITION_STEP_SIZE,
|
|
193
|
+
GEOPOSITION_STEP_SIZE,
|
|
194
|
+
)
|
|
195
|
+
]
|
|
196
|
+
return tuple(quads)
|
|
197
|
+
|
|
198
|
+
split_quadtile = np.frompyfunc(_geoposition_split_quads, nin=1, nout=NUMBER_OF_GEOPOSITION_QUADS + 1)
|
|
199
|
+
quads = split_quadtile(quadtile)
|
|
200
|
+
|
|
201
|
+
# return a dataframe containing the sub quads as columns
|
|
202
|
+
transformed_data = pd.DataFrame(quads).T
|
|
203
|
+
transformed_data.columns = QUAD_COLUMNS + [
|
|
204
|
+
QUADTILE,
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
# filter (set to empty string) the invalid entries for quads
|
|
208
|
+
transformed_data[invalid_entry] = ""
|
|
209
|
+
# filter invalid entries for quadtile by setting them to special string
|
|
210
|
+
transformed_data.loc[invalid_entry, QUADTILE] = NONE_QUADTILE_ENTRY
|
|
211
|
+
# insert a binary "nan" column at the beginning of the returned DataFrame
|
|
212
|
+
transformed_data.insert(0, "nan", isnan.any(axis=1).astype(int))
|
|
213
|
+
return transformed_data
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def split_str_to_latlong(data: pd.Series) -> pd.DataFrame:
|
|
217
|
+
"""
|
|
218
|
+
Split the string input into two float columns
|
|
219
|
+
"-13.1345, 89.331" - > | -13.1345 | 89.331 |
|
|
220
|
+
Anything that is not a match for a number is converted into nan
|
|
221
|
+
|
|
222
|
+
:param data: stringified lat, long Series
|
|
223
|
+
|
|
224
|
+
return: a DataFrame of two (lat, long) columns, accordingly
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
latitude_longitude = (
|
|
228
|
+
(
|
|
229
|
+
pd.concat([data, pd.Series(pd.NA)]) # ensure at least one row
|
|
230
|
+
.reset_index(drop=True)
|
|
231
|
+
.fillna("")
|
|
232
|
+
.astype(str)
|
|
233
|
+
+ "," # ensure at least one comma per row
|
|
234
|
+
)
|
|
235
|
+
.str.split(",", expand=True)
|
|
236
|
+
.iloc[:, 0:2] # select the first two columns: lat, long
|
|
237
|
+
.apply(lambda x: pd.to_numeric(x, downcast="float", errors="coerce"))
|
|
238
|
+
.head(-1) # drop previously added row
|
|
239
|
+
)
|
|
240
|
+
return latitude_longitude
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def analyze_latlong(values: pd.Series, root_keys: pd.Series, _: pd.Series | None = None) -> dict:
|
|
244
|
+
values = safe_convert_string(values)
|
|
245
|
+
df_split = split_sub_columns_latlong(values)
|
|
246
|
+
df = pd.concat([root_keys, df_split], axis=1)
|
|
247
|
+
|
|
248
|
+
quad_partials = [
|
|
249
|
+
name for name in list(df.columns) if name not in (root_keys.name, "QUADTILE", "nan")
|
|
250
|
+
] # ["QUAD_12", "QUAD_14", ...]
|
|
251
|
+
has_nan = bool(df_split["nan"].any())
|
|
252
|
+
|
|
253
|
+
# mapping of values and their counts (per root key) for each of the quads
|
|
254
|
+
quad_codes = {
|
|
255
|
+
quad_partial: df[[quad_partial, root_keys.name]].drop_duplicates()[quad_partial].value_counts().to_dict()
|
|
256
|
+
for quad_partial in quad_partials
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
df_non_na_quadtiles = df_split["QUADTILE"][df_split["nan"] == 0]
|
|
260
|
+
if df_non_na_quadtiles.empty: # in the edge-case if all are NaN
|
|
261
|
+
df_non_na_quadtiles = pd.Series([PLACEHOLDER_QUADTILE_ENTRY])
|
|
262
|
+
chars_df = df_non_na_quadtiles.str.split("", expand=True).drop([0, GEOPOSITION_QUADTILE_LENGTH + 1], axis=1)
|
|
263
|
+
chars_df.columns = [f"P{idx}" for idx in range(GEOPOSITION_QUADTILE_LENGTH)]
|
|
264
|
+
characters = {sub_col: list(chars_df[sub_col].unique()) for sub_col in chars_df.columns}
|
|
265
|
+
|
|
266
|
+
stats = {"has_nan": has_nan, "quad_codes": quad_codes, "characters": characters}
|
|
267
|
+
return stats
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def analyze_reduce_latlong(stats_list: list[dict], value_protection: bool = True) -> dict:
|
|
271
|
+
# check if there are missing values
|
|
272
|
+
has_nan = any([j["has_nan"] for j in stats_list])
|
|
273
|
+
unk_cat_aliases = [""] # na / unknown (unseen) category
|
|
274
|
+
|
|
275
|
+
quads = list(stats_list[0]["quad_codes"].keys()) if len(stats_list) else []
|
|
276
|
+
quad_codes = {}
|
|
277
|
+
|
|
278
|
+
for quad in quads:
|
|
279
|
+
keys = list(
|
|
280
|
+
set(chain.from_iterable([stats["quad_codes"][quad].keys() for stats in stats_list]))
|
|
281
|
+
) # all the possible values for a given quad
|
|
282
|
+
totals = {
|
|
283
|
+
key: sum([stats["quad_codes"][quad].get(key, 0) for stats in stats_list]) for key in keys if key
|
|
284
|
+
} # mapped to total counts per each value
|
|
285
|
+
totals = {
|
|
286
|
+
val: cnt for val, cnt in totals.items() if cnt >= RARE_CATEGORY_THRESHOLD
|
|
287
|
+
} # keep only those, which appear at least {rare_category_threshold} times
|
|
288
|
+
categories = ([CATEGORICAL_UNKNOWN_TOKEN] + [cat for cat in totals.keys() if cat not in unk_cat_aliases])[
|
|
289
|
+
:MAX_UNIQUE_VALUES_PER_QUAD
|
|
290
|
+
] # UNK + remaining possible values
|
|
291
|
+
if len(categories) - 1 < MIN_UNIQUE_QUAD_VALUES: # excluding unknown category
|
|
292
|
+
continue # do not include quads having unique values less than {min_unique_quad_values}
|
|
293
|
+
quad_codes[quad] = {category: i for i, category in enumerate(categories)}
|
|
294
|
+
|
|
295
|
+
# combine all the characters stats
|
|
296
|
+
chars_all: dict[str, list] = {f"P{idx}": [] for idx in range(GEOPOSITION_QUADTILE_LENGTH)}
|
|
297
|
+
for stats in stats_list:
|
|
298
|
+
for k, v in stats["characters"].items():
|
|
299
|
+
chars_all[k] += list(set(v) - set(chars_all[k]))
|
|
300
|
+
|
|
301
|
+
# create character stats
|
|
302
|
+
quadtile_characters = {
|
|
303
|
+
"max_string_length": GEOPOSITION_QUADTILE_LENGTH,
|
|
304
|
+
"has_nan": False,
|
|
305
|
+
"codes": {
|
|
306
|
+
f"P{idx}": {char: code for code, char in enumerate(sorted(chars_all[f"P{idx}"]))}
|
|
307
|
+
for idx in range(GEOPOSITION_QUADTILE_LENGTH)
|
|
308
|
+
},
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
# determine cardinalities
|
|
312
|
+
cardinalities = {quad: len(quad_codes[quad]) for quad in quads if quad in quad_codes}
|
|
313
|
+
cardinalities.update({k: len(v) for k, v in quadtile_characters["codes"].items()}) # type: ignore
|
|
314
|
+
if has_nan:
|
|
315
|
+
cardinalities["nan"] = 2 # binary
|
|
316
|
+
|
|
317
|
+
stats = {
|
|
318
|
+
"has_nan": has_nan,
|
|
319
|
+
"cardinalities": cardinalities,
|
|
320
|
+
"quad_codes": quad_codes,
|
|
321
|
+
"quadtile_characters": quadtile_characters,
|
|
322
|
+
}
|
|
323
|
+
return stats
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def encode_latlong(
|
|
327
|
+
values: pd.Series,
|
|
328
|
+
column_stats: dict,
|
|
329
|
+
context_keys: pd.Series | None = None,
|
|
330
|
+
) -> pd.DataFrame:
|
|
331
|
+
values = safe_convert_string(values)
|
|
332
|
+
# split to sub_columns
|
|
333
|
+
quads = split_sub_columns_latlong(values)
|
|
334
|
+
encoded_quads = pd.DataFrame() # empty DF to include all the ModelEncodingType.tabular_categorical quads
|
|
335
|
+
for quad, value_counts in column_stats["quad_codes"].items():
|
|
336
|
+
stats = {"codes": value_counts}
|
|
337
|
+
encoded_quads[quad] = encode_categorical(quads[quad], stats)
|
|
338
|
+
|
|
339
|
+
encoded_quadtile = encode_character(quads["QUADTILE"], column_stats["quadtile_characters"])
|
|
340
|
+
if column_stats["has_nan"]:
|
|
341
|
+
encoded_quadtile["nan"] = quads["nan"]
|
|
342
|
+
df = pd.concat([encoded_quads, encoded_quadtile], axis=1)
|
|
343
|
+
return df
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def decode_latlong(df_encoded: pd.DataFrame, stats: dict) -> pd.Series:
|
|
347
|
+
redundant_columns = [col for col in df_encoded.columns if not col.startswith("P")]
|
|
348
|
+
df = df_encoded.drop(redundant_columns, axis=1)
|
|
349
|
+
decoded_latlong = decode_character(df, stats["quadtile_characters"])
|
|
350
|
+
decoded_latlong = _binary_to_latlong(decoded_latlong)
|
|
351
|
+
if "nan" in df_encoded.columns:
|
|
352
|
+
decoded_latlong[df_encoded["nan"] == 1] = pd.NA
|
|
353
|
+
return decoded_latlong
|