ltc-code 0.1.94__tar.gz → 0.1.96__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ltc-code
3
- Version: 0.1.94
3
+ Version: 0.1.96
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ltc-code"
3
- version = "0.1.94"
3
+ version = "0.1.96"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -0,0 +1,405 @@
1
+ # --- Import Necessary Libraries ---
2
+ import polars as pl
3
+ import pandas as pd
4
+ import sys
5
+ sys.path.append(r"P:\Long_Term_Charter\programs\new_build")
6
+ from paths import *
7
+ from helpers import *
8
+ from mappings import *
9
+ from polars_readstat import scan_readstat
10
+ import glob
11
+ import unicodedata
12
+ import re
13
+ from typing import Union, List, Dict, Optional, Literal, Sequence
14
+ import time
15
+ import subprocess
16
+ import logging
17
+ import nameparser
18
+ import shutil
19
+ from nameparser import HumanName # Popular package for parsing out the subcomponents of a full name
20
+
21
+ pl.Config.set_tbl_cols(13)
22
+ pl.Config.set_tbl_rows(15)
23
+
24
+
25
+ # --- Create log file and start timer ---
26
+ logging.basicConfig(
27
+ filename=LOGS / "aspire_apps.log",
28
+ filemode="w", # "w"=overwrite existing log
29
+ level=logging.INFO,
30
+ format="%(asctime)s - %(message)s",
31
+ force=True, #reconfigure logging even if already set up
32
+ )
33
+
34
+ # Start timer
35
+ start = time.perf_counter()
36
+
37
+
38
+ ###########################################################################################################################################################################
39
+ # --- Define necessary paths ---
40
+ ###########################################################################################################################################################################
41
+
42
+ # --- Input + Temp + Output ---
43
+ INPUT = CMO_ID / "NO_PII" / "aspire.csv"
44
+ TEMP_CSV = CMO_APPS / "csv" / "temp" / "aspire_temp.csv"
45
+ OUTPUT_CSV = CMO_APPS / "csv" / "aspire.csv"
46
+ OUTPUT_STATA = CMO_APPS / "stata" / "aspire.dta"
47
+ OUTPUT_STATA_COLLAPSED = CMO_APPS / "stata_collapsed" / "aspire.dta"
48
+ OUTPUT_SCHOOLS = CLEAN / "new_build" / "schools" / "aspire_schools.csv"
49
+
50
+
51
+
52
+ ###########################################################################################################################################################################
53
+ # --- Scan in lottery dataset, code up offers/risksets and deduplicate the applications based on rules ---
54
+ ###########################################################################################################################################################################
55
+
56
+ # Define keys for dedupping function
57
+ DEDUP_KEYS = ["sid_cepr", "school_year", "entry_grade_clean", "school_name",]
58
+
59
+ # Take the max of these for dupes (ORDER MATTERS HERE MAKE SURE MOST IMPORTANT IS LISTED AT TOP)
60
+ EVER_COLS = ["offer_accepted", "offer", "waitlist", "withdraw",]
61
+
62
+ # Prioritize records with more filled out info for these cols
63
+ INFO_COLS = ["lottery_number", "waitlist_number", "priority_group_name", "status_timestamp",]
64
+
65
+
66
+ # Scan in the lottery data and clean stuff
67
+ lot = (
68
+ pl.scan_csv(INPUT, infer_schema=False, null_values=[], try_parse_dates=False, ignore_errors=False,)
69
+ .filter(pl.col("data_type")=="lottery") # filter to lotto
70
+ .pipe(drop_all_null)
71
+ .with_columns(
72
+ pl.lit("CALIFORNIA").alias("cmo_region"),
73
+ pl.lit(None, dtype=pl.Int64).alias("late_app"),
74
+ )
75
+ .with_columns(
76
+ apply_status_mapping("status", ASPIRE_STATUS_MAP), # map the statuses to relevant offer/waitlist variables
77
+ )
78
+ .with_columns( # code wl numb==0 as offer in this context)
79
+ pl.when(pl.col("waitlist_number") == "0")
80
+ .then(1)
81
+ .otherwise(pl.col("offer"))
82
+ .alias("offer")
83
+ )
84
+ .rename({
85
+ "priority_name": "priority_group_name",
86
+ })
87
+ .pipe(resolve_duplicate_applications, DEDUP_KEYS, EVER_COLS, INFO_COLS)
88
+ .pipe( # create priority specific flags based on priority group name
89
+ add_priority_flags,
90
+ priority_col="priority_group_name",
91
+ cmo="aspire",
92
+ )
93
+ .with_columns( # create number representing the priority group (is not ordinally ranked b/c i dont see reason to do so)
94
+ pl.col("priority_group_clean")
95
+ .rank(method="dense")
96
+ .cast(pl.Int32)
97
+ .alias("priority_group"),
98
+ )
99
+ ).collect()
100
+
101
+
102
+ ###########################################################################################################################################################################
103
+ # --- Scan in waitlist, enrollment, and name datasets then join on waitlist/enrollment/name indicators, and years of enrollment ---
104
+ ###########################################################################################################################################################################
105
+
106
+ # Create risk keys
107
+ risk_keys = ["school_year", "entry_grade_clean", "school_name", "priority_group"]
108
+
109
+ # Scan in the waitlist data and collapse to get wl flag
110
+ wl = (
111
+ pl.scan_csv(INPUT, infer_schema=False, null_values=[], try_parse_dates=False, ignore_errors=False,)
112
+ .filter(pl.col("data_type")=="waitlist") # filter to waitlist
113
+ .select(DEDUP_KEYS)
114
+ .unique()
115
+ .with_columns(
116
+ pl.lit(1).alias("in_wl"),
117
+ )
118
+ ).collect()
119
+
120
+ # Scan in the enrollment data and collapse to get enroll flags
121
+ enr_student = (
122
+ pl.scan_csv(INPUT, infer_schema=False, null_values=[], try_parse_dates=False, ignore_errors=False,)
123
+ .filter(pl.col("data_type")=="enroll") # filter to waitlist
124
+ .select(["sid_cepr", "school_year"])
125
+ .unique()
126
+ .group_by("sid_cepr")
127
+ .agg(
128
+ pl.lit(1).alias("enroll"), # flag for if the student ever enrolled in the cmo
129
+ pl.len().alias("enrollment_years"), # number of years the student was enrolled in the cmo
130
+ pl.col("school_year").cast(pl.Int32, strict=False).min().alias("year_enrolled"),
131
+ )
132
+ ).collect()
133
+
134
+
135
+ # Scan in the enrollment data and collapse to get flag for if enrolled at a given school
136
+ enr_school = (
137
+ pl.scan_csv(INPUT, infer_schema=False, null_values=[], try_parse_dates=False, ignore_errors=False,)
138
+ .filter(pl.col("data_type")=="enroll") # filter to waitlist
139
+ .select(["sid_cepr", "school_name"])
140
+ .unique()
141
+ .with_columns(
142
+ pl.lit(1).alias("enroll_school"), # flag for if the student ever enrolled at the school to which they applied
143
+ )
144
+ ).collect()
145
+
146
+ # Scan in the enrollment data and collapse to get flag for if enrolled in a given year
147
+ enr_year = (
148
+ pl.scan_csv(INPUT, infer_schema=False, null_values=[], try_parse_dates=False, ignore_errors=False,)
149
+ .filter(pl.col("data_type")=="enroll") # filter to waitlist
150
+ .select(["sid_cepr", "school_year"])
151
+ .unique()
152
+ .with_columns(
153
+ pl.lit(1).alias("enroll_year"), # flag for if the student enrolled at any schools from the lottery year they applied
154
+ )
155
+ ).collect()
156
+
157
+ # Scan in the enrollment data and collapse to get flag for if enrolled in a given year
158
+ enr_school_year = (
159
+ pl.scan_csv(INPUT, infer_schema=False, null_values=[], try_parse_dates=False, ignore_errors=False,)
160
+ .filter(pl.col("data_type")=="enroll") # filter to waitlist
161
+ .select(["sid_cepr", "school_year", "school_name"])
162
+ .unique()
163
+ .with_columns(
164
+ pl.lit(1).alias("enroll_school_year"), # flag for if the student enrolled in the school to which they applied in the year of the lottery
165
+ )
166
+ ).collect()
167
+
168
+ # Scan in the lname data and filter to aspire
169
+ lname = (
170
+ pl.scan_csv(XWALKS / "lname_clean_id_xwalk.csv", infer_schema=False, null_values=[], try_parse_dates=False, ignore_errors=False,)
171
+ .filter(pl.col("cmo_name")=="Aspire")
172
+ ).collect()
173
+
174
+ # Join on all the useful info from wl/enr, create risk sets/lottery variables, join on last names, and then keep just the variables in the desired schema
175
+ final = (
176
+ lot
177
+ .join(wl, on=DEDUP_KEYS, how="left", validate="m:1")
178
+ .join(enr_student, on="sid_cepr", how="left", validate="m:1")
179
+ .join(enr_school, on=["sid_cepr", "school_name"], how="left", validate="m:1")
180
+ .join(enr_year, on=["sid_cepr", "school_year"], how="left", validate="m:1")
181
+ .join(enr_school_year, on=["sid_cepr", "school_year", "school_name"], how="left", validate="m:1")
182
+ .with_columns(
183
+ pl.coalesce(
184
+ pl.col("in_wl"),
185
+ pl.col("waitlist"),
186
+ ).alias("waitlist"),
187
+
188
+ pl.col("enroll").fill_null(0).alias("enroll"),
189
+ pl.col("enrollment_years").fill_null(0).alias("enrollment_years"),
190
+ pl.col("enroll_school").fill_null(0).alias("enroll_school"),
191
+ pl.col("enroll_year").fill_null(0).alias("enroll_year"),
192
+ pl.col("enroll_school_year").fill_null(0).alias("enroll_school_year"),
193
+ )
194
+ .with_columns(
195
+ waitlist_offer=(
196
+ (pl.col("offer") == 1)
197
+ & (pl.col("waitlist") == 1)
198
+ ).cast(pl.Int8),
199
+
200
+ initial_offer=(
201
+ (pl.col("offer") == 1)
202
+ & (pl.col("waitlist") != 1)
203
+ ).cast(pl.Int8),
204
+ )
205
+ .pipe(keep_final_apps_schema)
206
+ .join(lname, on=["lname_clean_id", "cmo_name"], how="left", validate="m:1")
207
+ .with_columns(
208
+ pl.concat_str(
209
+ [pl.col(c).cast(pl.String) for c in risk_keys],
210
+ separator="_",
211
+ ).alias("risk_id_name")
212
+ )
213
+ .with_columns( # create number representing the risk id
214
+ pl.col("risk_id_name")
215
+ .rank(method="dense")
216
+ .cast(pl.Int32)
217
+ .alias("risk_id"),
218
+ )
219
+ .pipe(drop_all_null)
220
+ )
221
+
222
+
223
+
224
+ ###########################################################################################################################################################################
225
+ # --- Run the R scripts to get race joined on ---
226
+ ###########################################################################################################################################################################
227
+
228
+ # Save to csv
229
+ final.write_csv(TEMP_CSV)
230
+
231
+ # Run the R script to get race (NOTE: this can not be run interactively and you must run from terminal with py -m main)
232
+ subprocess.run(
233
+ ["Rscript", "impute_race.R", str(TEMP_CSV), str(OUTPUT_CSV)], check=True,
234
+ )
235
+
236
+
237
+
238
+ ###########################################################################################################################################################################
239
+ # --- Create collapsed version of the stata dataset ---
240
+ ###########################################################################################################################################################################
241
+
242
+ # Read the data back in
243
+ final = (
244
+ pl.read_csv(OUTPUT_CSV)
245
+ )
246
+
247
+
248
+ # Define cols we want to take the max of and the grouping key
249
+ max_cols = [
250
+ "offer",
251
+ "initial_offer",
252
+ "waitlist_offer",
253
+ "offer_accepted",
254
+ "enroll",
255
+ "sibling",
256
+ "transfer",
257
+ "zoned",
258
+ "staff",
259
+ "sibling_concur",
260
+ ]
261
+
262
+ group_key = ["sid_cepr"]
263
+
264
+ first_cols = [
265
+ c for c in final.columns
266
+ if c not in group_key + max_cols + ["risk_id"]
267
+ ]
268
+
269
+ # Create the collapsed dataset
270
+ final_collapsed = (
271
+ final
272
+ .filter(pl.col("application_cancel") !=1)
273
+ .group_by(group_key)
274
+ .agg(
275
+ # max variables
276
+ [pl.max(c).alias(c) for c in max_cols]
277
+
278
+ +
279
+
280
+ # all risk sets student participcated in
281
+ [
282
+ pl.col("risk_id")
283
+ .unique()
284
+ .sort()
285
+ .str.join("_")
286
+ .alias("risk_set")
287
+ ]
288
+
289
+ +
290
+
291
+ # everything else from first row
292
+ [
293
+ pl.first(c).alias(c)
294
+ for c in first_cols
295
+ ]
296
+ )
297
+ )
298
+
299
+
300
+ ###########################################################################################################################################################################
301
+ # --- Create unique schools dataset ---
302
+ ###########################################################################################################################################################################
303
+
304
+
305
+ # Scan in the combined data and collaprse to unique schools
306
+ sch_ren_map = (
307
+ pl.scan_csv(INPUT, infer_schema=False, null_values=[], try_parse_dates=False, ignore_errors=False,)
308
+ .select(["cmo_name", "school_name", "ren_num"])
309
+ .with_columns(
310
+ pl.lit("CALIFORNIA").alias("cmo_region"),
311
+ )
312
+ .drop_nulls(["school_name"])
313
+ .group_by(["cmo_name", "cmo_region", "school_name"])
314
+ .agg(
315
+ pl.len().alias("n_obs"),
316
+
317
+ pl.col("ren_num")
318
+ .drop_nulls()
319
+ .unique()
320
+ .sort()
321
+ .cast(pl.String)
322
+ .str.join("+")
323
+ .alias("ren_nums")
324
+ )
325
+ .select(["cmo_name", "cmo_region", "school_name", "n_obs"])
326
+ .sort("n_obs", descending=True)
327
+ ).collect()
328
+
329
+
330
+ ###########################################################################################################################################################################
331
+ # --- Save data ---
332
+ ###########################################################################################################################################################################
333
+
334
+ # Save unique schools dataset
335
+ sch_ren_map.write_csv(OUTPUT_SCHOOLS)
336
+
337
+ # Save student apps dataset to stata
338
+ final.to_pandas().to_stata(OUTPUT_STATA, write_index=False, version=118)
339
+
340
+ # Save collapsed student dataset to stata
341
+ final_collapsed.to_pandas().to_stata(OUTPUT_STATA_COLLAPSED, write_index=False, version=118)
342
+
343
+ # End timer
344
+ elapsed = time.perf_counter() - start
345
+ print(f"Finished in {elapsed / 60:.2f} minutes")
346
+ logging.info(f"Finished in {elapsed / 60:.2f} minutes")
347
+
348
+
349
+
350
+ ###########################################################################################################################################################################
351
+ # --- LOG COUNTS ---
352
+ ###########################################################################################################################################################################
353
+
354
+ """
355
+ logging.info(
356
+ "Lottery observations: %s",
357
+ final_no_pii
358
+ .filter(pl.col("data_type") == "lottery")
359
+ .height
360
+ )
361
+ logging.info(
362
+ "Waitlist observations: %s",
363
+ final_no_pii
364
+ .filter(pl.col("data_type") == "waitlist")
365
+ .height
366
+ )
367
+ logging.info(
368
+ "Enrollment observations: %s",
369
+ final_no_pii
370
+ .filter(pl.col("data_type") == "enroll")
371
+ .height
372
+ )
373
+ logging.info(
374
+ "Counts by school year:"
375
+ )
376
+ logging.info(
377
+ "\n%s",
378
+ final_no_pii
379
+ .group_by("school_year")
380
+ .len()
381
+ .sort("school_year")
382
+ )
383
+ logging.info(
384
+ "Counts by birth cohort:"
385
+ )
386
+ with pl.Config(
387
+ tbl_rows=-1,
388
+ tbl_cols=-1,
389
+ fmt_str_lengths=1000,
390
+ ):
391
+ logging.info(
392
+ "\n%s",
393
+ final_no_pii
394
+ .group_by("cohort_lottery")
395
+ .len()
396
+ .sort("cohort_lottery")
397
+ )
398
+ logging.info(
399
+ "Schema: \n%s",
400
+ "\n".join(
401
+ f"{col}: {dtype}"
402
+ for col, dtype in final_no_pii.schema.items()
403
+ )
404
+ )
405
+ """