instate 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.
instate/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ """
2
+ instate: Predict state and language from Indian lastnames.
3
+
4
+ This package provides functions to:
5
+ 1. Look up state distributions from 2017 Indian electoral rolls
6
+ 2. Predict states and languages using neural networks
7
+
8
+ Main functions:
9
+ - get_state_distribution: Get P(state|lastname) from electoral rolls
10
+ - get_state_languages: Map states to official languages
11
+ - predict_state: Neural prediction of most likely states
12
+ - predict_language: Neural prediction of most likely languages
13
+ """
14
+
15
+ from .electoral import (
16
+ get_state_distribution,
17
+ get_state_languages,
18
+ list_available_states,
19
+ )
20
+ from .predict import predict_language, predict_state
21
+
22
+ __all__ = [
23
+ "get_state_distribution",
24
+ "get_state_languages",
25
+ "predict_state",
26
+ "predict_language",
27
+ "list_available_states",
28
+ ]
29
+
30
+ try:
31
+ from importlib.metadata import version
32
+
33
+ __version__ = version("instate")
34
+ except ImportError:
35
+ __version__ = "1.0.0"
instate/_utils.py ADDED
@@ -0,0 +1,357 @@
1
+ """
2
+ Internal utilities for instate package.
3
+
4
+ This module contains helper functions for data loading, name cleaning, and caching.
5
+ These are internal utilities and not part of the public API.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+
12
+ import pandas as pd
13
+ import requests
14
+ import torch
15
+ import torch.nn as nn
16
+ from tqdm import tqdm
17
+
18
+ # Cache for loaded data
19
+ _CACHE = {}
20
+
21
+ # URLs for downloading data
22
+ ELECTORAL_DATA_URLS = {
23
+ "v1": "https://github.com/appeler/instate/raw/main/data/instate_unique_ln_state_prop_v1.csv.gz",
24
+ }
25
+
26
+ MODEL_URLS = {
27
+ "gru": "https://dataverse.harvard.edu/api/v1/access/datafile/6981460",
28
+ }
29
+
30
+
31
+ def prepare_name_dataframe(
32
+ names: pd.DataFrame | list[str], name_column: str | None = None
33
+ ) -> pd.DataFrame:
34
+ """Convert input to DataFrame with standardized name column.
35
+
36
+ Args:
37
+ names: DataFrame or list of names
38
+ name_column: Column name if DataFrame provided
39
+
40
+ Returns:
41
+ DataFrame with names in first column
42
+ """
43
+ if isinstance(names, list):
44
+ return pd.DataFrame({"name": names})
45
+
46
+ df = names.copy()
47
+
48
+ # If no column specified, try to find one
49
+ if name_column is None:
50
+ # Look for common name columns
51
+ possible_cols = [
52
+ c
53
+ for c in df.columns
54
+ if any(n in c.lower() for n in ["name", "lastname", "surname"])
55
+ ]
56
+ if not possible_cols:
57
+ # Just use first column
58
+ name_column = df.columns[0]
59
+ else:
60
+ name_column = possible_cols[0]
61
+
62
+ # Ensure the name column is first
63
+ if name_column != df.columns[0]:
64
+ cols = [name_column] + [c for c in df.columns if c != name_column]
65
+ df = df[cols]
66
+
67
+ return df
68
+
69
+
70
+ def clean_name(name: str) -> str:
71
+ """Clean and standardize a single name.
72
+
73
+ - Convert to lowercase
74
+ - Strip whitespace
75
+ - Remove non-alphabetic characters
76
+
77
+ Args:
78
+ name: Input name string
79
+
80
+ Returns:
81
+ Cleaned name
82
+ """
83
+ if not isinstance(name, str):
84
+ return ""
85
+
86
+ # Basic cleaning
87
+ cleaned = name.strip().lower()
88
+
89
+ # Keep only alphabetic characters
90
+ cleaned = "".join(c for c in cleaned if c.isalpha())
91
+
92
+ return cleaned
93
+
94
+
95
+ def clean_names_in_df(df: pd.DataFrame, name_column: str) -> pd.DataFrame:
96
+ """Clean names in a DataFrame column.
97
+
98
+ Args:
99
+ df: Input DataFrame
100
+ name_column: Column containing names
101
+
102
+ Returns:
103
+ DataFrame with added __cleaned_name column and filtered rows
104
+ """
105
+ result = df.copy()
106
+
107
+ # Handle empty DataFrame
108
+ if len(result) == 0:
109
+ result["__cleaned_name"] = pd.Series([], dtype=str)
110
+ return result
111
+
112
+ # Clean names
113
+ result["__cleaned_name"] = result[name_column].apply(clean_name)
114
+
115
+ # Filter out invalid names
116
+ result = result[result["__cleaned_name"].str.len() > 2]
117
+
118
+ # Drop duplicates based on cleaned name
119
+ result = result.drop_duplicates(subset=["__cleaned_name"], keep="first")
120
+
121
+ return result
122
+
123
+
124
+ def get_app_file_path(filename: str) -> str:
125
+ """Get path for cached application data.
126
+
127
+ Args:
128
+ filename: Name of file
129
+
130
+ Returns:
131
+ Full path to file in app data directory
132
+ """
133
+ user_dir = os.path.expanduser("~")
134
+ app_data_dir = os.path.join(user_dir, ".instate")
135
+
136
+ if not os.path.exists(app_data_dir):
137
+ os.makedirs(app_data_dir)
138
+
139
+ return os.path.join(app_data_dir, filename)
140
+
141
+
142
+ def download_file(url: str, target: str) -> bool:
143
+ """Download file with progress bar.
144
+
145
+ Args:
146
+ url: URL to download from
147
+ target: Target file path
148
+
149
+ Returns:
150
+ True if successful, False otherwise
151
+ """
152
+ try:
153
+ r = requests.get(url, stream=True)
154
+
155
+ if r.status_code == 200:
156
+ chunk_size = 64 * 1024
157
+ total_size = int(r.headers.get("content-length", 0)) / chunk_size + 1
158
+
159
+ with open(target, "wb") as f:
160
+ for data in tqdm(
161
+ r.iter_content(chunk_size),
162
+ total=round(total_size, 1),
163
+ unit_scale=chunk_size / 1024,
164
+ unit="KB",
165
+ desc="Downloading",
166
+ ):
167
+ f.write(data)
168
+ return True
169
+ else:
170
+ print(f"Download failed with status code: {r.status_code}")
171
+ return False
172
+ except Exception as e:
173
+ print(f"Download error: {e}")
174
+ return False
175
+
176
+
177
+ def load_electoral_data(dataset: str = "v1") -> pd.DataFrame:
178
+ """Load electoral rolls data, downloading if needed.
179
+
180
+ Args:
181
+ dataset: Dataset version to load
182
+
183
+ Returns:
184
+ DataFrame with electoral rolls data
185
+ """
186
+ global _CACHE
187
+
188
+ cache_key = f"electoral_{dataset}"
189
+ if cache_key in _CACHE:
190
+ return _CACHE[cache_key]
191
+
192
+ # Check if data exists locally
193
+ filename = f"instate_unique_ln_state_prop_{dataset}.csv.gz"
194
+ data_path = get_app_file_path(filename)
195
+
196
+ if not os.path.exists(data_path):
197
+ # First try to find the file in the package data directory
198
+ package_data_dir = os.path.join(os.path.dirname(__file__), "data")
199
+ local_path = os.path.join(package_data_dir, filename)
200
+
201
+ if os.path.exists(local_path):
202
+ print("Copying electoral rolls data from package...")
203
+ import shutil
204
+
205
+ shutil.copy2(local_path, data_path)
206
+ else:
207
+ print(f"Downloading electoral rolls data ({dataset})...")
208
+ if not download_file(ELECTORAL_DATA_URLS[dataset], data_path):
209
+ raise RuntimeError("Failed to download electoral data")
210
+
211
+ # Load data
212
+ df = pd.read_csv(data_path)
213
+ df.rename(columns={"last_name": "__last_name"}, inplace=True)
214
+
215
+ # Cache it
216
+ _CACHE[cache_key] = df
217
+
218
+ return df
219
+
220
+
221
+ def load_gru_model():
222
+ """Load GRU model for state prediction.
223
+
224
+ Returns:
225
+ Loaded PyTorch model
226
+ """
227
+ global _CACHE
228
+
229
+ if "gru_model" in _CACHE:
230
+ return _CACHE["gru_model"]
231
+
232
+ # Check if model exists
233
+ model_path = get_app_file_path("instate_gru.pth")
234
+
235
+ if not os.path.exists(model_path):
236
+ print("Downloading GRU model...")
237
+ if not download_file(MODEL_URLS["gru"], model_path):
238
+ raise RuntimeError("Failed to download GRU model")
239
+
240
+ # Load model
241
+ from .constants import GRU_HIDDEN_SIZE, GRU_N_LETTERS, GT_KEYS
242
+ from .nnets import GRU_net
243
+
244
+ device = torch.device("cpu")
245
+ model = GRU_net(GRU_N_LETTERS, GRU_HIDDEN_SIZE, len(GT_KEYS))
246
+ model.load_state_dict(torch.load(model_path, map_location=device))
247
+ model.eval()
248
+
249
+ _CACHE["gru_model"] = model
250
+
251
+ return model
252
+
253
+
254
+ def load_lstm_model():
255
+ """Load LSTM model for language prediction.
256
+
257
+ Returns:
258
+ Loaded PyTorch model and supporting data
259
+ """
260
+ global _CACHE
261
+
262
+ if "lstm_model" in _CACHE:
263
+ return _CACHE["lstm_model"], _CACHE["lstm_data"]
264
+
265
+ # Import constants instead of loading from files
266
+ from .constants import (
267
+ CHAR_TO_IDX,
268
+ IDX_TO_LANG,
269
+ NUM_LANGUAGES,
270
+ VOCAB_SIZE,
271
+ )
272
+ # LanguagePredictor is now defined in this file
273
+
274
+ # Model configuration
275
+ embedding_dim = 50
276
+ hidden_dim = 128
277
+
278
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
279
+ model = LanguagePredictor(VOCAB_SIZE, embedding_dim, hidden_dim, NUM_LANGUAGES)
280
+ model.to(device)
281
+
282
+ # Load weights
283
+ data_dir = os.path.dirname(__file__)
284
+ model_file = os.path.join(data_dir, "data", "state_lang_labels.pt")
285
+ if torch.cuda.is_available():
286
+ model.load_state_dict(torch.load(model_file))
287
+ else:
288
+ model.load_state_dict(torch.load(model_file, map_location=torch.device("cpu")))
289
+
290
+ model.eval()
291
+
292
+ # Cache everything
293
+ lstm_data = {"char2idx": CHAR_TO_IDX, "idx2lang": IDX_TO_LANG, "device": device}
294
+
295
+ _CACHE["lstm_model"] = model
296
+ _CACHE["lstm_data"] = lstm_data
297
+
298
+ return model, lstm_data
299
+
300
+
301
+ def load_language_lookup_data():
302
+ """Load data for KNN language lookup.
303
+
304
+ Returns:
305
+ DataFrame with lastname to language mapping
306
+ """
307
+ import tarfile
308
+
309
+ global _CACHE
310
+
311
+ if "lang_lookup" in _CACHE:
312
+ return _CACHE["lang_lookup"]
313
+
314
+ data_file_name = "lastname_langs_india"
315
+ data_path = get_app_file_path(data_file_name)
316
+
317
+ if not os.path.exists(data_path + ".csv"):
318
+ data_dir = os.path.dirname(__file__)
319
+ gz_path = os.path.join(data_dir, "data", f"{data_file_name}.csv.tar.gz")
320
+ print("Extracting language lookup data...")
321
+ with tarfile.open(gz_path, "r:gz") as tar:
322
+ tar.extract(f"{data_file_name}.csv", data_path, filter="data")
323
+
324
+ df = pd.read_csv(f"{data_path}/{data_file_name}.csv")
325
+ _CACHE["lang_lookup"] = df
326
+
327
+ return df
328
+
329
+
330
+ class LanguagePredictor(nn.Module):
331
+ """LSTM model for predicting languages from names.
332
+
333
+ This model uses character embeddings and LSTM to predict the top 3 most
334
+ likely languages for a given name.
335
+ """
336
+
337
+ def __init__(
338
+ self, num_chars, embedding_dim=64, lstm_hidden_dim=128, num_languages=37
339
+ ):
340
+ super().__init__()
341
+ self.embedding = nn.Embedding(num_chars, embedding_dim)
342
+ self.lstm = nn.LSTM(embedding_dim, lstm_hidden_dim, batch_first=True)
343
+ self.fc1 = nn.Linear(lstm_hidden_dim, num_languages)
344
+ self.fc2 = nn.Linear(lstm_hidden_dim, num_languages)
345
+ self.fc3 = nn.Linear(lstm_hidden_dim, num_languages)
346
+
347
+ def forward(self, x, lengths):
348
+ x = self.embedding(x)
349
+ x = nn.utils.rnn.pack_padded_sequence(
350
+ x, lengths, batch_first=True, enforce_sorted=False
351
+ )
352
+ _, (h_n, _) = self.lstm(x)
353
+ h_n = h_n.squeeze(0)
354
+ out1 = self.fc1(h_n)
355
+ out2 = self.fc2(h_n)
356
+ out3 = self.fc3(h_n)
357
+ return out1, out2, out3
instate/constants.py ADDED
@@ -0,0 +1,127 @@
1
+ """
2
+ Constants for the instate package.
3
+
4
+ Contains static data that was previously stored in small files.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ # Language list (originally from langs.txt)
10
+ LANGUAGES = [
11
+ "sindhi",
12
+ "nepali",
13
+ "kannada",
14
+ "marathi",
15
+ "mizo",
16
+ "adi",
17
+ "garo",
18
+ "tagin",
19
+ "assamese",
20
+ "hindi",
21
+ "odia",
22
+ "french",
23
+ "punjabi",
24
+ "naga languages",
25
+ "english",
26
+ "chenchu",
27
+ "urdu",
28
+ "bengali",
29
+ "maithili",
30
+ "dogri",
31
+ "kokborok",
32
+ "santali",
33
+ "kashmiri",
34
+ "gujarati",
35
+ "apatani",
36
+ "tulu",
37
+ "konkani",
38
+ "telugu",
39
+ "malayalam",
40
+ "tamil",
41
+ "meitei",
42
+ "khasi",
43
+ "gondi",
44
+ "bodo",
45
+ "nishi",
46
+ "chakma",
47
+ "pahari and kumauni",
48
+ ]
49
+
50
+ # Character to index mapping (originally from char2idx.json)
51
+ CHAR_TO_IDX = {
52
+ "<PAD>": 0,
53
+ "n": 1,
54
+ "g": 2,
55
+ "i": 3,
56
+ "m": 4,
57
+ "c": 5,
58
+ "w": 6,
59
+ "u": 7,
60
+ "e": 8,
61
+ "v": 9,
62
+ "d": 10,
63
+ "a": 11,
64
+ "l": 12,
65
+ "t": 13,
66
+ "s": 14,
67
+ "q": 15,
68
+ "b": 16,
69
+ "f": 17,
70
+ "o": 18,
71
+ "z": 19,
72
+ "p": 20,
73
+ "r": 21,
74
+ "k": 22,
75
+ "h": 23,
76
+ "y": 24,
77
+ "x": 25,
78
+ "j": 26,
79
+ }
80
+
81
+ # Derived mappings for convenience
82
+ LANG_TO_IDX = {lang: idx for idx, lang in enumerate(LANGUAGES)}
83
+ IDX_TO_LANG = {idx: lang for lang, idx in LANG_TO_IDX.items()}
84
+
85
+ # Model dimensions
86
+ VOCAB_SIZE = len(CHAR_TO_IDX)
87
+ NUM_LANGUAGES = len(LANGUAGES)
88
+
89
+ # GRU model constants for state prediction
90
+ GT_KEYS = [
91
+ "Andaman and Nicobar Islands",
92
+ "Andhra Pradesh",
93
+ "Arunachal Pradesh",
94
+ "Assam",
95
+ "Bihar",
96
+ "Chandigarh",
97
+ "Dadra and Nagar Haveli",
98
+ "Daman and Diu",
99
+ "Delhi",
100
+ "Goa",
101
+ "Gujarat",
102
+ "Haryana",
103
+ "Jharkhand",
104
+ "Jammu and Kashmir and Ladakh",
105
+ "Karnataka",
106
+ "Kerala",
107
+ "Maharashtra",
108
+ "Manipur",
109
+ "Meghalaya",
110
+ "Mizoram",
111
+ "Madhya Pradesh",
112
+ "Nagaland",
113
+ "Odisha",
114
+ "Puducherry",
115
+ "Punjab",
116
+ "Rajasthan",
117
+ "Sikkim",
118
+ "Telangana",
119
+ "Tripura",
120
+ "Uttar Pradesh",
121
+ "Uttarakhand",
122
+ ]
123
+
124
+ # GRU model configuration
125
+ GRU_HIDDEN_SIZE = 2048
126
+ GRU_ALL_LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.,;"
127
+ GRU_N_LETTERS = len(GRU_ALL_LETTERS)
Binary file
@@ -0,0 +1,36 @@
1
+ state,official_languages,addl_official_languages,most_spoken_lang,second_most_spoken_lang,third_most_spoken_lang,fourth_most_spoken_lang,fifth_most_spoken_lang
2
+ Andaman and Nicobar Islands,"Hindi, English",,Bengali,Hindi,Tamil,Telugu,Malayalam
3
+ Andhra Pradesh,Telugu,Urdu,Telugu,Urdu,Tamil,Chenchu,Kannada
4
+ Arunachal Pradesh,English,,Nishi,Adi,Apatani,Tagin,Hindi
5
+ Assam,Assamese,"Bengali, Bodo",Assamese,Bengali,Bodo,Hindi,Nepali
6
+ Bihar,Hindi,Urdu,Hindi (including Bihari languages),Maithili,Urdu,Bengali,Santali
7
+ Chandigarh,English,,Hindi,Punjabi,Urdu,Nepali,Bengali
8
+ Chhattisgarh,Hindi,Chhattisgarhi,Hindi,Odia,Bengali,Telugu,Marathi
9
+ Dadra and Nagar Haveli and Daman and Diu,"Hindi, English",Gujarati,Gujarati,Hindi,Marathi,Konkani,Bengali
10
+ Delhi,"Hindi, English","Urdu, Punjabi",Hindi,Punjabi,Urdu,Bengali,Maithili
11
+ Goa,Konkani,Marathi,Konkani,Marathi,Hindi,Kannada,Urdu
12
+ Gujarat,Gujarati,,Gujarati,Hindi,Marathi,Sindhi,Urdu
13
+ Haryana,Hindi,Punjabi,Hindi,Punjabi,Urdu,Bengali,Maithili
14
+ Himachal Pradesh,Hindi,Sanskrit,Hindi (including Pahari languages),Punjabi,Nepali,Kashmiri,Dogri
15
+ Jammu and Kashmir and Ladakh,"Kashmiri, Dogri, Hindi, Urdu, English",,Kashmiri,Dogri,Hindi,Punjabi,Urdu
16
+ Jharkhand,Hindi,"Angika, Bengali, Bhojpuri, Bhumij, Ho, Kharia, Khortha, Kurmali, Kurukh, Magahi, Maithili, Mundari, Nagpuri, Odia, Santali, Urdu",Hindi (including Bihari languages),Santali,Bengali,Urdu,Odia
17
+ Karnataka,Kannada,,Kannada,Telugu,Tamil,Marathi,Kodava
18
+ Kerala,Malayalam,English,Malayalam,Tamil,Tulu,Kannada,Konkani
19
+ Lakshadweep,"English, Hindi",,Malayalam,Dhivehi,Tamil,Hindi,Telugu
20
+ Madhya Pradesh,Hindi,,Hindi,Marathi,Urdu,Sindhi,Gujarati
21
+ Maharashtra,Marathi,,Marathi,Hindi,Urdu,Gujarati,Telugu
22
+ Manipur,Meitei,English,Meitei,Nepali,Hindi,Bengali,English
23
+ Meghalaya,English,Khasi,Khasi,Garo,Bengali,Nepali,Hindi
24
+ Mizoram,"English, Hindi, Mizo",,Mizo,English,Hindi,Meitei,Chakma
25
+ Nagaland,English,,Naga languages,English,Hindi,Assamese,Meitei
26
+ Odisha,Odia,,Odia,Hindi,Santali,Urdu,Telugu
27
+ Puducherry,"Tamil, English, French",,Tamil,Telugu,Malayalam,French,English
28
+ Punjab,Punjabi,,Punjabi,Hindi,Urdu,Bengali,English
29
+ Rajasthan,Hindi,English,Hindi (including Rajasthani languages),Punjabi,Gujarati,Sindhi,Urdu
30
+ Sikkim,"English, Nepali","Bhutia, Gurung, Lepcha, Limbu, Manggar, Mukhia, Newari, Rai, Sherpa, Tamang",Nepali,Hindi,Bengali,Urdu,Punjabi
31
+ Tamil Nadu,Tamil,English,Tamil,Telugu,Kannada,Urdu,Malayalam
32
+ Telangana,Telugu,Urdu,Telugu,Urdu,Gondi,Kannada,Marathi
33
+ Tripura,"Bengali, English, Kokborok",,Bengali,Kokborok,Chakma,English,Meitei
34
+ Uttar Pradesh,Hindi,Urdu,Hindi (including UP local dialects),Urdu,Punjabi,Nepali,Bengali
35
+ Uttarakhand,Hindi,Sanskrit,Hindi,Pahari and Kumauni,Urdu,Punjabi,Bengali
36
+ West Bengal,"Bengali, English",,Bengali,Hindi,Santali,Urdu,Nepali
instate/electoral.py ADDED
@@ -0,0 +1,141 @@
1
+ """
2
+ Electoral rolls based name-to-state lookup.
3
+
4
+ Functions for looking up state distributions from 2017 Indian electoral rolls data.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+
11
+ import pandas as pd
12
+
13
+
14
+ def get_state_distribution(
15
+ names: pd.DataFrame | list[str], name_column: str | None = None
16
+ ) -> pd.DataFrame:
17
+ """Get P(state|lastname) from 2017 Indian electoral rolls.
18
+
19
+ This returns the empirical distribution of a lastname across Indian states
20
+ based on the electoral rolls data. This is the Bayes optimal estimate
21
+ given the observed frequencies.
22
+
23
+ Args:
24
+ names: DataFrame containing names or list of name strings.
25
+ Names are automatically cleaned (lowercase, stripped).
26
+ name_column: If names is a DataFrame, the column containing names.
27
+ If None and DataFrame has 'name' or 'lastname', uses that.
28
+
29
+ Returns:
30
+ DataFrame with original data plus 31 state probability columns.
31
+ State columns are named by state (e.g., 'delhi', 'punjab').
32
+ Values are proportions (0-1) representing P(state|lastname).
33
+
34
+ Examples:
35
+ >>> names = ["dhingra", "sood", "gowda"]
36
+ >>> result = get_state_distribution(names)
37
+ >>> result[["name", "delhi", "punjab", "karnataka"]]
38
+
39
+ >>> df = pd.DataFrame({"lastname": ["dhingra", "sood"]})
40
+ >>> result = get_state_distribution(df, "lastname")
41
+ >>> result.columns[:5].tolist()
42
+ """
43
+ from ._utils import clean_names_in_df, load_electoral_data, prepare_name_dataframe
44
+
45
+ # Convert to DataFrame if needed
46
+ df = prepare_name_dataframe(names, name_column)
47
+
48
+ # Clean names for matching
49
+ df = clean_names_in_df(df, df.columns[0])
50
+
51
+ # Load electoral rolls data
52
+ electoral_data = load_electoral_data()
53
+
54
+ # Merge to get state distributions
55
+ # Electoral data has __last_name as key
56
+ result = pd.merge(
57
+ df, electoral_data, left_on="__cleaned_name", right_on="__last_name", how="left"
58
+ )
59
+
60
+ # Drop temporary columns
61
+ result = result.drop(columns=["__cleaned_name", "__last_name"], errors="ignore")
62
+
63
+ return result
64
+
65
+
66
+ def get_state_languages(
67
+ states: pd.DataFrame | list[str], state_column: str | None = None
68
+ ) -> pd.DataFrame:
69
+ """Map Indian states to their official languages.
70
+
71
+ Based on census data, returns the official language(s) for each state.
72
+
73
+ Args:
74
+ states: DataFrame containing states or list of state names.
75
+ state_column: If states is a DataFrame, the column containing state names.
76
+
77
+ Returns:
78
+ DataFrame with state and official_languages columns.
79
+ If input was DataFrame, adds official_languages column.
80
+
81
+ Examples:
82
+ >>> states = ["Delhi", "Punjab", "Karnataka"]
83
+ >>> result = get_state_languages(states)
84
+ >>> result[["state", "official_languages"]]
85
+
86
+ >>> df = pd.DataFrame({"state_name": ["Delhi", "Punjab"]})
87
+ >>> result = get_state_languages(df, "state_name")
88
+ """
89
+ # Prepare DataFrame
90
+ if isinstance(states, list):
91
+ df = pd.DataFrame({"state": states})
92
+ state_col = "state"
93
+ else:
94
+ df = states.copy()
95
+ if state_column is None:
96
+ # Try to find state column
97
+ possible_cols = [c for c in df.columns if "state" in c.lower()]
98
+ if not possible_cols:
99
+ raise ValueError("state_column must be specified for DataFrame input")
100
+ state_col = possible_cols[0]
101
+ else:
102
+ state_col = state_column
103
+
104
+ # Load state-language mapping
105
+ data_dir = os.path.dirname(__file__)
106
+ state_lang_path = os.path.join(data_dir, "data", "state_to_languages.csv")
107
+ state_lang_map = pd.read_csv(state_lang_path)
108
+
109
+ # Merge to add languages
110
+ result = df.merge(state_lang_map, left_on=state_col, right_on="state", how="left")
111
+
112
+ # Clean up duplicate state column if needed
113
+ if state_col != "state" and "state_y" not in result.columns:
114
+ result = result.drop(columns=["state"], errors="ignore")
115
+
116
+ return result
117
+
118
+
119
+ def list_available_states() -> list[str]:
120
+ """List all states available in the electoral rolls dataset.
121
+
122
+ Returns:
123
+ List of state names available in the data.
124
+
125
+ Examples:
126
+ >>> states = list_available_states()
127
+ >>> len(states)
128
+ 31
129
+ >>> "Delhi" in states
130
+ True
131
+ """
132
+ from ._utils import load_electoral_data
133
+
134
+ electoral_data = load_electoral_data()
135
+ # Get column names, excluding non-state columns
136
+ state_cols = [
137
+ col
138
+ for col in electoral_data.columns
139
+ if not col.startswith("__") and col != "last_name"
140
+ ]
141
+ return sorted(state_cols)
instate/nnets.py ADDED
@@ -0,0 +1,46 @@
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+ from .constants import GRU_ALL_LETTERS, GRU_HIDDEN_SIZE, GRU_N_LETTERS
5
+
6
+ # For backward compatibility with existing code
7
+ n_hidden = GRU_HIDDEN_SIZE
8
+ all_letters = GRU_ALL_LETTERS
9
+ n_letters = GRU_N_LETTERS
10
+
11
+
12
+ def infer(net, name: str):
13
+ net.eval()
14
+ name_ohe = name_rep(name)
15
+ hidden = net.init_hidden()
16
+
17
+ for i in range(name_ohe.size()[0]):
18
+ output, hidden = net(name_ohe[i], hidden)
19
+
20
+ return output
21
+
22
+
23
+ def name_rep(name: str):
24
+ rep = torch.zeros(len(name), 1, n_letters)
25
+ for index, letter in enumerate(name):
26
+ pos = all_letters.find(letter)
27
+ rep[index][0][pos] = 1
28
+ return rep
29
+
30
+
31
+ class GRU_net(nn.Module):
32
+ def __init__(self, input_size, hidden_size, output_size):
33
+ super().__init__()
34
+ self.hidden_size = hidden_size
35
+ self.gru_cell = nn.GRU(input_size, hidden_size)
36
+ self.h2o = nn.Linear(hidden_size, output_size)
37
+ self.softmax = nn.LogSoftmax(dim=2)
38
+
39
+ def forward(self, input_, hidden):
40
+ out, hidden = self.gru_cell(input_.view(1, 1, -1), hidden)
41
+ output = self.h2o(hidden)
42
+ output = self.softmax(output)
43
+ return output.view(1, -1), hidden
44
+
45
+ def init_hidden(self):
46
+ return torch.zeros(1, 1, self.hidden_size)
instate/predict.py ADDED
@@ -0,0 +1,235 @@
1
+ """
2
+ Neural network predictions for names not in electoral rolls.
3
+
4
+ Functions for predicting states and languages using trained models.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from functools import partial
10
+
11
+ import pandas as pd
12
+ import torch
13
+ from Levenshtein import distance
14
+
15
+
16
+ def predict_state(
17
+ names: pd.DataFrame | list[str],
18
+ name_column: str | None = None,
19
+ top_k: int = 3,
20
+ model: str = "gru",
21
+ ) -> pd.DataFrame:
22
+ """Predict most likely Indian states for given names using neural network.
23
+
24
+ Uses a trained GRU model to predict which Indian states a person with
25
+ the given lastname is most likely to be from. This is useful for names
26
+ not found in the electoral rolls data.
27
+
28
+ Args:
29
+ names: DataFrame containing names or list of name strings.
30
+ Names are automatically cleaned (lowercase, stripped).
31
+ name_column: If names is a DataFrame, the column containing names.
32
+ top_k: Number of top states to return (default: 3).
33
+ model: Model to use for prediction. Currently only "gru" supported.
34
+
35
+ Returns:
36
+ DataFrame with name and predicted_states columns.
37
+ predicted_states contains a list of top_k state names.
38
+
39
+ Examples:
40
+ >>> names = ["dhingra", "sood", "gowda"]
41
+ >>> result = predict_state(names, top_k=3)
42
+ >>> result["predicted_states"][0]
43
+ ['Delhi', 'Punjab', 'Haryana']
44
+
45
+ >>> df = pd.DataFrame({"lastname": ["sharma", "patel"]})
46
+ >>> result = predict_state(df, "lastname", top_k=2)
47
+ >>> len(result["predicted_states"][0])
48
+ 2
49
+ """
50
+ from ._utils import clean_name, load_gru_model, prepare_name_dataframe
51
+ from .constants import GT_KEYS
52
+ from .nnets import infer
53
+
54
+ if model != "gru":
55
+ raise ValueError(f"Model '{model}' not supported. Use 'gru'.")
56
+
57
+ # Prepare DataFrame
58
+ df = prepare_name_dataframe(names, name_column)
59
+ name_col = df.columns[0]
60
+
61
+ # Load model
62
+ gru_model = load_gru_model()
63
+
64
+ # Predict for each name
65
+ predictions = []
66
+ for name in df[name_col]:
67
+ cleaned = clean_name(name)
68
+ if not cleaned or len(cleaned) < 3:
69
+ predictions.append([])
70
+ continue
71
+
72
+ # Run inference
73
+ output = infer(gru_model, cleaned)
74
+ _, indices = output.topk(top_k)
75
+ idx_list = indices.numpy().flatten().tolist()
76
+ pred_states = [GT_KEYS[i] for i in idx_list]
77
+ predictions.append(pred_states)
78
+
79
+ # Add predictions to DataFrame
80
+ result = df.copy()
81
+ result["predicted_states"] = predictions
82
+
83
+ return result
84
+
85
+
86
+ def predict_language(
87
+ names: pd.DataFrame | list[str],
88
+ name_column: str | None = None,
89
+ top_k: int = 3,
90
+ model: str = "lstm",
91
+ ) -> pd.DataFrame:
92
+ """Predict most likely languages for given names.
93
+
94
+ Two methods available:
95
+ - "lstm": Neural network prediction using trained LSTM model
96
+ - "knn": K-nearest neighbor lookup in language database
97
+
98
+ Args:
99
+ names: DataFrame containing names or list of name strings.
100
+ name_column: If names is a DataFrame, the column containing names.
101
+ top_k: Number of top languages to return (default: 3).
102
+ Note: KNN method returns only the single best match.
103
+ model: Prediction method - "lstm" (neural) or "knn" (lookup).
104
+
105
+ Returns:
106
+ DataFrame with name and predicted_languages columns.
107
+ For LSTM: predicted_languages contains list of top_k languages.
108
+ For KNN: predicted_languages contains single best language.
109
+
110
+ Examples:
111
+ >>> names = ["sood", "chintalapati"]
112
+ >>> result = predict_language(names, model="lstm")
113
+ >>> result["predicted_languages"][0]
114
+ ['hindi', 'punjabi', 'urdu']
115
+
116
+ >>> result_knn = predict_language(names, model="knn")
117
+ >>> result_knn["predicted_languages"][0]
118
+ 'hindi'
119
+
120
+ >>> df = pd.DataFrame({"name": ["patel", "sharma"]})
121
+ >>> result = predict_language(df, "name", model="lstm", top_k=2)
122
+ >>> len(result["predicted_languages"][0])
123
+ 2
124
+ """
125
+ from ._utils import prepare_name_dataframe
126
+
127
+ # Prepare DataFrame
128
+ df = prepare_name_dataframe(names, name_column)
129
+ name_col = df.columns[0]
130
+
131
+ if model == "lstm":
132
+ predictions = _predict_language_lstm(df[name_col], top_k)
133
+ elif model == "knn":
134
+ predictions = _predict_language_knn(df[name_col])
135
+ else:
136
+ raise ValueError(f"Model '{model}' not supported. Use 'lstm' or 'knn'.")
137
+
138
+ # Add predictions to DataFrame
139
+ result = df.copy()
140
+ result["predicted_languages"] = predictions
141
+
142
+ return result
143
+
144
+
145
+ def _predict_language_lstm(names: pd.Series, top_k: int = 3) -> list:
146
+ """
147
+ Internal function for LSTM language prediction.
148
+ """
149
+ from ._utils import clean_name, load_lstm_model
150
+
151
+ model, lstm_data = load_lstm_model()
152
+ char2idx = lstm_data["char2idx"]
153
+ idx2lang = lstm_data["idx2lang"]
154
+ device = lstm_data["device"]
155
+
156
+ predictions = []
157
+
158
+ for name in names:
159
+ cleaned = clean_name(name)
160
+ if not cleaned or len(cleaned) < 3:
161
+ predictions.append([])
162
+ continue
163
+
164
+ # Convert name to indices
165
+ try:
166
+ name_indices = [char2idx.get(char, 0) for char in cleaned]
167
+ except Exception:
168
+ predictions.append([])
169
+ continue
170
+
171
+ # Prepare tensor
172
+ with torch.no_grad():
173
+ name_tensor = (
174
+ torch.tensor(name_indices, dtype=torch.long).unsqueeze(0).to(device)
175
+ )
176
+ lengths = torch.tensor([len(cleaned)], dtype=torch.long)
177
+
178
+ # Get predictions for top 3 language outputs
179
+ out1, out2, out3 = model(name_tensor, lengths)
180
+
181
+ # Get top predictions from each output
182
+ pred_first = torch.argmax(out1, dim=1)
183
+ pred_second = torch.argmax(out2, dim=1)
184
+ pred_third = torch.argmax(out3, dim=1)
185
+
186
+ # Ensure unique predictions
187
+ if pred_second == pred_first:
188
+ pred_second = torch.topk(out2, k=2, dim=1)[1][0][1]
189
+ if pred_third == pred_first or pred_third == pred_second:
190
+ pred_third = torch.topk(out3, k=3, dim=1)[1][0][2]
191
+
192
+ # Convert to language names
193
+ langs = [
194
+ idx2lang[pred_first.item()],
195
+ idx2lang[pred_second.item()],
196
+ idx2lang[pred_third.item()],
197
+ ]
198
+
199
+ # Return only top_k languages
200
+ predictions.append(langs[:top_k])
201
+
202
+ return predictions
203
+
204
+
205
+ def _predict_language_knn(names: pd.Series) -> list:
206
+ """
207
+ Internal function for KNN language lookup.
208
+ """
209
+ from ._utils import clean_name, load_language_lookup_data
210
+
211
+ lang_data = load_language_lookup_data()
212
+ lang_cols = lang_data.columns[1:] # Skip lastname column
213
+
214
+ predictions = []
215
+
216
+ for name in names:
217
+ cleaned = clean_name(name)
218
+ if not cleaned or len(cleaned) < 3:
219
+ predictions.append("")
220
+ continue
221
+
222
+ # Calculate edit distance to all names in database
223
+ # Use partial to avoid lambda scope issue
224
+ distances = lang_data["last_name"].apply(partial(distance, cleaned))
225
+
226
+ # Get top 3 nearest names
227
+ nearest_indices = distances.nsmallest(3).index
228
+
229
+ # Sum language scores for nearest names and get max
230
+ lang_scores = lang_data.loc[nearest_indices, lang_cols].sum()
231
+ best_lang = lang_scores.idxmax()
232
+
233
+ predictions.append(best_lang)
234
+
235
+ return predictions
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.3
2
+ Name: instate
3
+ Version: 1.0.0
4
+ Summary: Instate: predict the state of residence from last name
5
+ Keywords: predict,state,residence,last name
6
+ Author: Atul Dhingra, Gaurav Sood, Rajashekar Chintalapati
7
+ Author-email: Atul Dhingra <dhingra.atul92@gmail.com>, Gaurav Sood <gsood07@gmail.com>, Rajashekar Chintalapati <rajshekar.ch@gmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Topic :: Utilities
18
+ Requires-Dist: pandas>=1.5.0
19
+ Requires-Dist: numpy>=1.21.0,<2.0.0
20
+ Requires-Dist: torch>=2.0.0
21
+ Requires-Dist: levenshtein>=0.20.0
22
+ Requires-Dist: requests>=2.25.0
23
+ Requires-Dist: tqdm>=4.64.0
24
+ Requires-Dist: sphinx>=7.0 ; extra == 'docs'
25
+ Requires-Dist: furo>=2024.1.29 ; extra == 'docs'
26
+ Requires-Dist: sphinx-autodoc-typehints>=1.25 ; extra == 'docs'
27
+ Requires-Dist: sphinx-copybutton>=0.5 ; extra == 'docs'
28
+ Requires-Dist: myst-parser>=2.0 ; extra == 'docs'
29
+ Requires-Python: >=3.11, <3.14
30
+ Project-URL: Bug Tracker, https://github.com/appeler/instate/issues
31
+ Project-URL: Homepage, https://github.com/appeler/instate
32
+ Project-URL: Repository, https://github.com/appeler/instate
33
+ Provides-Extra: docs
34
+ Description-Content-Type: text/markdown
35
+
36
+ ## instate: predict spoken language and the state of residence from last name
37
+
38
+ [![image](https://github.com/appeler/instate/workflows/test/badge.svg)](https://github.com/appeler/instate/actions?query=workflow%3Atest)
39
+ [![image](https://img.shields.io/pypi/v/instate.svg)](https://pypi.python.org/pypi/instate)
40
+ [![Documentation](https://github.com/appeler/instate/actions/workflows/docs.yml/badge.svg)](https://github.com/appeler/instate/actions/workflows/docs.yml)
41
+ [![image](https://static.pepy.tech/badge/instate)](https://pepy.tech/project/instate)
42
+
43
+ Using the Indian electoral rolls data (2017), we provide a Python
44
+ package that takes the last name of a person and gives its distribution
45
+ across states. This package can also predict the spoken language of the
46
+ person based on the last name.
47
+
48
+ # Potential Use Cases
49
+
50
+ India has 22 official languages. To serve such a diverse language base
51
+ is a challenge for businesses and surveyors. To the extent that
52
+ businesses have access to the last name (and no other information) and
53
+ in the absence of other data that allows us to model a person\'s spoken
54
+ language, the distribution of last names across states is the best we
55
+ have.
56
+
57
+ # Dataset
58
+
59
+ Refer
60
+ [lastname_langs_india.csv.tar.gz](https://github.com/appeler/instate/blob/main/instate/data/lastname_langs_india.csv.tar.gz)
61
+ for the dataset, that will be used to predict/lookup the spoken language
62
+ based on the last name.
63
+
64
+ Refer
65
+ [lastname_langs_india_top3.csv.tar.gz](https://github.com/appeler/instate/blob/main/instate/data/lastname_langs_india_top3.csv.tar.gz)
66
+ for the dataset, that will be used to predict the top-3 spoken languages
67
+ based on the last name. A LSTM model has been trained on this dataset to
68
+ predict the top-3 spoken languages.
69
+
70
+ Refer
71
+ [notebooks](https://github.com/appeler/instate/tree/main/instate/notebooks)
72
+ for the notebooks that were used to prepare above datasets and train the
73
+ models.
74
+
75
+ # Web UI
76
+
77
+ Streamlit App.:
78
+ <https://appeler-instate-streamlitstreamlit-app-e39m4c.streamlit.app/>
79
+
80
+ # Installation
81
+
82
+ We strongly recommend installing [instate]{.title-ref} inside a Python
83
+ virtual environment (see [venv
84
+ documentation](https://docs.python.org/3/library/venv.html#creating-virtual-environments))
85
+
86
+ pip install instate
87
+
88
+ # Examples
89
+
90
+ from instate import last_state
91
+ last_dat <- pd.read_csv("last_dat.csv")
92
+ last_state_dat <- last_state(last_dat, "dhingra")
93
+ print(last_state_dat)
94
+
95
+ # API
96
+
97
+ instate provides 4 main functions for predicting state and language from Indian lastnames.
98
+
99
+ ## Electoral Rolls Lookup
100
+
101
+ - **get_state_distribution** - Get P(state|lastname) from 2017 electoral rolls data
102
+
103
+ ```python
104
+ import instate
105
+
106
+ # With list of names
107
+ names = ["sharma", "patel", "singh"]
108
+ result = instate.get_state_distribution(names)
109
+ print(result[["name", "Delhi", "Gujarat", "Punjab"]].head())
110
+
111
+ # With DataFrame
112
+ import pandas as pd
113
+ df = pd.DataFrame({"lastname": ["sharma", "patel"]})
114
+ result = instate.get_state_distribution(df, "lastname")
115
+ print(result.shape) # (2, 33) - 2 names + 31 state columns
116
+ ```
117
+
118
+ - **get_state_languages** - Map states to their official languages
119
+
120
+ ```python
121
+ # Map states to languages
122
+ states = ["Delhi", "Punjab", "Gujarat"]
123
+ result = instate.get_state_languages(states)
124
+ print(result[["state", "official_languages"]])
125
+
126
+ # state official_languages
127
+ # 0 Delhi Hindi, English
128
+ # 1 Punjab Punjabi
129
+ # 2 Gujarat Gujarati
130
+ ```
131
+
132
+ ## Neural Network Predictions
133
+
134
+ - **predict_state** - Predict likely states using trained GRU model
135
+
136
+ ```python
137
+ # Predict top 3 most likely states
138
+ names = ["sharma", "patel", "singh"]
139
+ result = instate.predict_state(names, top_k=3)
140
+ print(result["predicted_states"].iloc[0])
141
+ # ['Delhi', 'Uttar Pradesh', 'Bihar']
142
+ ```
143
+
144
+ - **predict_language** - Predict likely languages using LSTM or k-nearest neighbor
145
+
146
+ ```python
147
+ # LSTM neural network prediction (top 3)
148
+ result = instate.predict_language(names, model="lstm", top_k=3)
149
+ print(result["predicted_languages"].iloc[0])
150
+ # ['hindi', 'punjabi', 'urdu']
151
+
152
+ # K-nearest neighbor lookup (single best)
153
+ result = instate.predict_language(names, model="knn")
154
+ print(result["predicted_languages"].iloc[0])
155
+ # 'hindi'
156
+ ```
157
+
158
+ ## Complete Example
159
+
160
+ ```python
161
+ import pandas as pd
162
+ import instate
163
+
164
+ # Sample data
165
+ df = pd.DataFrame({
166
+ "person_id": [1, 2, 3],
167
+ "lastname": ["sharma", "patel", "singh"]
168
+ })
169
+
170
+ # Get state distributions from electoral rolls
171
+ state_dist = instate.get_state_distribution(df, "lastname")
172
+ print("Electoral rolls data shape:", state_dist.shape)
173
+
174
+ # Predict states with neural network
175
+ predicted_states = instate.predict_state(df, "lastname", top_k=3)
176
+ print("Top 3 predicted states:", predicted_states["predicted_states"].iloc[0])
177
+
178
+ # Predict languages
179
+ predicted_langs = instate.predict_language(df, "lastname", model="lstm", top_k=3)
180
+ print("Top 3 predicted languages:", predicted_langs["predicted_languages"].iloc[0])
181
+
182
+ # Map states to languages
183
+ states_df = pd.DataFrame({"state": ["Delhi", "Gujarat", "Punjab"]})
184
+ lang_map = instate.get_state_languages(states_df)
185
+ print("State language mapping:")
186
+ print(lang_map[["state", "official_languages"]])
187
+ ```
188
+
189
+ # Data
190
+
191
+ The underlying data for the package can be accessed at:
192
+ <https://doi.org/10.7910/DVN/ZXMVTJ>
193
+
194
+ # Evaluation
195
+
196
+ The model has a top-3 accuracy of 85.3% on [unseen
197
+ names](https://github.com/appeler/instate/blob/main/instate/models/model_dnn_gpu.ipynb).
198
+ The KNN model does quite well. See the details
199
+ [here](https://github.com/appeler/instate/blob/main/instate/models/KNN_cosine_distance_simple_avg_modal_state.ipynb)
200
+ The name-to-language lookup has an accuracy of 67.9%. The
201
+ name-to-language model prediction has an accuracy of 72.2%.
202
+
203
+ # Authors
204
+
205
+ Atul Dhingra, Gaurav Sood and Rajashekar Chintalapati
206
+
207
+ # Contributor Code of Conduct
208
+
209
+ The project welcomes contributions from everyone! In fact, it depends on
210
+ it. To maintain this welcoming atmosphere, and to collaborate in a fun
211
+ and productive way, we expect contributors to the project to abide by
212
+ the [Contributor Code of
213
+ Conduct](http://contributor-covenant.org/version/1/0/0/).
214
+
215
+ # License
216
+
217
+ The package is released under the [MIT
218
+ License](https://opensource.org/licenses/MIT).
219
+
220
+ ## 🔗 Adjacent Repositories
221
+
222
+ - [appeler/ethnicolr2](https://github.com/appeler/ethnicolr2) — Ethnicolr implementation with new models in pytorch
223
+ - [appeler/naampy](https://github.com/appeler/naampy) — Infer Sociodemographic Characteristics from Names Using Indian Electoral Rolls
224
+ - [appeler/ethnicolr](https://github.com/appeler/ethnicolr) — Predict Race and Ethnicity Based on the Sequence of Characters in a Name
225
+ - [appeler/parsernaam](https://github.com/appeler/parsernaam) — AI name parsing. Predict first or last name using a DL model.
226
+ - [appeler/ethnicolor](https://github.com/appeler/ethnicolor) — Race and Ethnicity based on name using data from census, voter reg. files, etc.
@@ -0,0 +1,14 @@
1
+ instate/__init__.py,sha256=sfIy0tU2asluoXBjWRgQEOOEl8u_iKaLhEs9F_wV3as,924
2
+ instate/_utils.py,sha256=ITEjqTcQIry_xxWEtOvXiGGxDI0Gvv9SqPfASCwfcEM,9692
3
+ instate/constants.py,sha256=v12Bpw64UWqEUiH0ZWVld-dW4PFs_ZXvubkMQn3qzC4,2201
4
+ instate/data/instate_unique_ln_state_prop_v1.csv.gz,sha256=IfmHWpnwdGi_o-rQVL45kVmndyV6Ex1eO8m6hbXJ_XY,11166988
5
+ instate/data/lastname_langs_india.csv.tar.gz,sha256=DkwB0H6fNsD-qLAvxPSrbUSaHJEMX-l2aaYdAkytr-U,7470521
6
+ instate/data/lastname_langs_india_top3.csv.tar.gz,sha256=AQYOTSiI0uKF2qIARDYQRwQnUzceD6uwrTMCq1JzU1s,7470581
7
+ instate/data/state_lang_labels.pt,sha256=l_HDwC4fx0ebQdSheN10_SJrwjaRn76xUUcKmFIrfPE,434872
8
+ instate/data/state_to_languages.csv,sha256=GkXL9qwstNIWOrGplSbZAnHrkD7T5Q9C9xZY095xcbk,2781
9
+ instate/electoral.py,sha256=M2UnJrQtKWSEEMiFjIRVvw1RcNoTheSudOt3NyGzmu8,4634
10
+ instate/nnets.py,sha256=rHvXpqryWtYoinTW3zN2MIn1I7KYcRcjpNVbGYzsXyI,1266
11
+ instate/predict.py,sha256=dXl7CrxvnmXKcqtlI6DIkCir642DS8InjO1bPHo9eNg,7465
12
+ instate-1.0.0.dist-info/WHEEL,sha256=z-mOpxbJHqy3cq6SvUThBZdaLGFZzdZPtgWLcP2NKjQ,79
13
+ instate-1.0.0.dist-info/METADATA,sha256=ighh53kIzWntBIvj73_-5FQrw2oyKZKhUMjIZEIjwIw,8369
14
+ instate-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.15
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any