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.
- {ltc_code-0.1.94 → ltc_code-0.1.96}/PKG-INFO +1 -1
- {ltc_code-0.1.94 → ltc_code-0.1.96}/pyproject.toml +1 -1
- ltc_code-0.1.96/src/ltc_code/aspire.py +405 -0
- ltc_code-0.1.96/src/ltc_code/check_cmo_apps.do +347 -0
- ltc_code-0.1.96/src/ltc_code/christel_house.py +659 -0
- ltc_code-0.1.96/src/ltc_code/helpers.py +2748 -0
- ltc_code-0.1.96/src/ltc_code/june13.py +294 -0
- ltc_code-0.1.96/src/ltc_code/main.py +213 -0
- ltc_code-0.1.96/src/ltc_code/mappings.py +948 -0
- ltc_code-0.1.96/src/ltc_code/rocketship.py +1078 -0
- ltc_code-0.1.96/src/ltc_code/yes_prep.py +502 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/README.md +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/__init__.py +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/green_dot.py +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/june2.py +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/june5.py +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/june7.py +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/kipp_nj.py +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/kipp_tx.py +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/may27.py +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/polars_dates.py +0 -0
- {ltc_code-0.1.94 → ltc_code-0.1.96}/src/ltc_code/schema_mapping.py +0 -0
|
@@ -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
|
+
"""
|