ltc-code 0.1.99__tar.gz → 0.2.0__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.99 → ltc_code-0.2.0}/PKG-INFO +1 -1
- {ltc_code-0.1.99 → ltc_code-0.2.0}/pyproject.toml +1 -1
- ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/aspire.py +337 -0
- ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/christel_house.py +598 -0
- ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/democracy_prep.py +947 -0
- ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/green_dot.py +417 -0
- ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/ilt.py +557 -0
- ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/kipp.py +381 -0
- ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/kipp_nj.py +1383 -0
- ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/rocketship.py +962 -0
- ltc_code-0.2.0/src/ltc_code/20260630_census_disclosure/yes_prep.py +436 -0
- ltc_code-0.2.0/src/ltc_code/june30.py +216 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/README.md +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/.DS_Store +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/aspire.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/check_cmo_apps.do +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/christel_house.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/democracy_prep.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/green_dot.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/helpers.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/ilt.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/kipp_nj.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/main.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/mappings.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/rocketship.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/20260614_new_build_scripts_update/yes_prep.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/__init__.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/aspire.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/check_cmo_apps.do +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/christel_house.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/green_dot.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/helpers.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/june13.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/june2.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/june5.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/june7.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/kipp_nj.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/kipp_tx.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/main.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/mappings.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/may27.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/polars_dates.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/rocketship.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/schema_mapping.py +0 -0
- {ltc_code-0.1.99 → ltc_code-0.2.0}/src/ltc_code/yes_prep.py +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# --- Import Necessary Libraries ---
|
|
2
|
+
import polars as pl
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import sys
|
|
5
|
+
sys.path.append("/projects/from_research1_arts867_opportunity/ltc/code")
|
|
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 shutil
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# --- Create log file and start timer ---
|
|
21
|
+
logging.basicConfig(
|
|
22
|
+
filename=APPS_LOGS / "aspire_apps.log",
|
|
23
|
+
filemode="w", # "w"=overwrite existing log
|
|
24
|
+
level=logging.INFO,
|
|
25
|
+
format="%(asctime)s - %(message)s",
|
|
26
|
+
force=True, #reconfigure logging even if already set up
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Start timer
|
|
30
|
+
start = time.perf_counter()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
###########################################################################################################################################################################
|
|
34
|
+
# --- Define necessary paths ---
|
|
35
|
+
###########################################################################################################################################################################
|
|
36
|
+
|
|
37
|
+
# --- Input + Output ---
|
|
38
|
+
INPUT = CMO_IDS / "harvard_ltc_2006_2021_asp_rsch.sas7bdat"
|
|
39
|
+
OUTPUT_LONG = CMO_APPS / "long" / "aspire.dta"
|
|
40
|
+
OUTPUT_WIDE = CMO_APPS / "wide" / "aspire.dta"
|
|
41
|
+
OUTPUT_SCHOOLS = SCHOOLS / "aspire_schools.csv"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
###########################################################################################################################################################################
|
|
46
|
+
# --- Scan in the cmos data, join on piks, filter to those who are piked, and drop sid_cepr ---
|
|
47
|
+
###########################################################################################################################################################################
|
|
48
|
+
|
|
49
|
+
# Store full data
|
|
50
|
+
full = (
|
|
51
|
+
scan_readstat(INPUT)
|
|
52
|
+
.join(
|
|
53
|
+
pl.scan_csv(XWALKS / "sid_cepr_pik_xwalk.csv", infer_schema=False),
|
|
54
|
+
how="left",
|
|
55
|
+
on=["sid_cepr", "cmo_name"],
|
|
56
|
+
validate="m:1",
|
|
57
|
+
)
|
|
58
|
+
.filter(
|
|
59
|
+
(pl.col("pik").is_not_null())
|
|
60
|
+
& (pl.col("pik") != "")
|
|
61
|
+
)
|
|
62
|
+
.drop("DQB_SOURCE_ID", "sid_cepr")
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
###########################################################################################################################################################################
|
|
67
|
+
# --- Scan in lottery dataset, code up offers/risksets and deduplicate the applications based on rules ---
|
|
68
|
+
###########################################################################################################################################################################
|
|
69
|
+
|
|
70
|
+
# Define keys for dedupping function
|
|
71
|
+
DEDUP_KEYS = ["pik", "school_year", "entry_grade_clean", "school_name",]
|
|
72
|
+
|
|
73
|
+
# Take the max of these for dupes (ORDER MATTERS HERE MAKE SURE MOST IMPORTANT IS LISTED AT TOP)
|
|
74
|
+
EVER_COLS = ["offer_accepted", "offer", "waitlist", "withdraw",]
|
|
75
|
+
|
|
76
|
+
# Prioritize records with more filled out info for these cols
|
|
77
|
+
INFO_COLS = ["lottery_number", "waitlist_number", "priority_group_name", "status_timestamp",]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Scan in the lottery data and clean stuff
|
|
81
|
+
lot = (
|
|
82
|
+
full
|
|
83
|
+
.filter(pl.col("data_type")=="lottery") # filter to lotto
|
|
84
|
+
.pipe(drop_all_null)
|
|
85
|
+
.with_columns(
|
|
86
|
+
pl.lit("CALIFORNIA").alias("cmo_region"),
|
|
87
|
+
pl.lit(None, dtype=pl.Int64).alias("late_app"),
|
|
88
|
+
)
|
|
89
|
+
.with_columns(
|
|
90
|
+
apply_status_mapping("status", ASPIRE_STATUS_MAP), # map the statuses to relevant offer/waitlist variables
|
|
91
|
+
)
|
|
92
|
+
.with_columns( # In this context, missing lottery waitlist numbers are offers
|
|
93
|
+
pl.when(pl.col("waitlist_number") == "0")
|
|
94
|
+
.then(1)
|
|
95
|
+
.otherwise(pl.col("offer"))
|
|
96
|
+
.alias("offer")
|
|
97
|
+
)
|
|
98
|
+
.rename({
|
|
99
|
+
"priority_name": "priority_group_name",
|
|
100
|
+
})
|
|
101
|
+
.pipe(resolve_duplicate_applications, DEDUP_KEYS, EVER_COLS, INFO_COLS)
|
|
102
|
+
.pipe( # create priority specific flags based on priority group name
|
|
103
|
+
add_priority_flags,
|
|
104
|
+
priority_col="priority_group_name",
|
|
105
|
+
cmo="aspire",
|
|
106
|
+
)
|
|
107
|
+
.with_columns( # create number representing the priority group (is not ordinally ranked b/c i dont see reason to do so)
|
|
108
|
+
pl.col("priority_group_clean")
|
|
109
|
+
.rank(method="dense")
|
|
110
|
+
.cast(pl.Int32)
|
|
111
|
+
.alias("priority_group"),
|
|
112
|
+
)
|
|
113
|
+
).collect()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
###########################################################################################################################################################################
|
|
117
|
+
# --- Scan in waitlist, enrollment, and name datasets then join on waitlist/enrollment/name indicators, and years of enrollment ---
|
|
118
|
+
###########################################################################################################################################################################
|
|
119
|
+
|
|
120
|
+
# Create risk keys
|
|
121
|
+
risk_keys = ["school_year", "entry_grade_clean", "school_name", "priority_group"]
|
|
122
|
+
|
|
123
|
+
# Scan in the waitlist data and collapse to get wl flag
|
|
124
|
+
wl = (
|
|
125
|
+
full
|
|
126
|
+
.filter(pl.col("data_type")=="waitlist") # filter to waitlist
|
|
127
|
+
.select(DEDUP_KEYS)
|
|
128
|
+
.unique()
|
|
129
|
+
.with_columns(
|
|
130
|
+
pl.lit(1).alias("in_wl"),
|
|
131
|
+
)
|
|
132
|
+
).collect()
|
|
133
|
+
|
|
134
|
+
# Scan in the enrollment data and collapse to get enroll flags
|
|
135
|
+
enr_student = (
|
|
136
|
+
full
|
|
137
|
+
.filter(pl.col("data_type")=="enroll") # filter to waitlist
|
|
138
|
+
.select(["pik", "school_year"])
|
|
139
|
+
.unique()
|
|
140
|
+
.group_by("pik")
|
|
141
|
+
.agg(
|
|
142
|
+
pl.lit(1).alias("enroll"), # flag for if the student ever enrolled in the cmo
|
|
143
|
+
pl.len().alias("enrollment_years"), # number of years the student was enrolled in the cmo
|
|
144
|
+
pl.col("school_year").cast(pl.Int32, strict=False).min().alias("year_enrolled"),
|
|
145
|
+
)
|
|
146
|
+
).collect()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# Scan in the enrollment data and collapse to get flag for if enrolled at a given school
|
|
150
|
+
enr_school = (
|
|
151
|
+
full
|
|
152
|
+
.filter(pl.col("data_type")=="enroll") # filter to waitlist
|
|
153
|
+
.select(["pik", "school_name"])
|
|
154
|
+
.unique()
|
|
155
|
+
.with_columns(
|
|
156
|
+
pl.lit(1).alias("enroll_school"), # flag for if the student ever enrolled at the school to which they applied
|
|
157
|
+
)
|
|
158
|
+
).collect()
|
|
159
|
+
|
|
160
|
+
# Scan in the enrollment data and collapse to get flag for if enrolled in a given year
|
|
161
|
+
enr_year = (
|
|
162
|
+
full
|
|
163
|
+
.filter(pl.col("data_type")=="enroll") # filter to waitlist
|
|
164
|
+
.select(["pik", "school_year"])
|
|
165
|
+
.unique()
|
|
166
|
+
.with_columns(
|
|
167
|
+
pl.lit(1).alias("enroll_year"), # flag for if the student enrolled at any schools from the lottery year they applied
|
|
168
|
+
)
|
|
169
|
+
).collect()
|
|
170
|
+
|
|
171
|
+
# Scan in the enrollment data and collapse to get flag for if enrolled in a given year
|
|
172
|
+
enr_school_year = (
|
|
173
|
+
full
|
|
174
|
+
.filter(pl.col("data_type")=="enroll") # filter to waitlist
|
|
175
|
+
.select(["pik", "school_year", "school_name"])
|
|
176
|
+
.unique()
|
|
177
|
+
.with_columns(
|
|
178
|
+
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
|
|
179
|
+
)
|
|
180
|
+
).collect()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# Join on all the useful info from wl/enr, create risk sets/lottery variables, and then keep just the variables in the desired schema
|
|
184
|
+
final = (
|
|
185
|
+
lot
|
|
186
|
+
.join(wl, on=DEDUP_KEYS, how="left", validate="m:1")
|
|
187
|
+
.join(enr_student, on="pik", how="left", validate="m:1")
|
|
188
|
+
.join(enr_school, on=["pik", "school_name"], how="left", validate="m:1")
|
|
189
|
+
.join(enr_year, on=["pik", "school_year"], how="left", validate="m:1")
|
|
190
|
+
.join(enr_school_year, on=["pik", "school_year", "school_name"], how="left", validate="m:1")
|
|
191
|
+
.with_columns(
|
|
192
|
+
pl.coalesce(
|
|
193
|
+
pl.col("in_wl"),
|
|
194
|
+
pl.col("waitlist"),
|
|
195
|
+
).alias("waitlist"),
|
|
196
|
+
|
|
197
|
+
pl.col("enroll").fill_null(0).alias("enroll"),
|
|
198
|
+
pl.col("enrollment_years").fill_null(0).alias("enrollment_years"),
|
|
199
|
+
pl.col("enroll_school").fill_null(0).alias("enroll_school"),
|
|
200
|
+
pl.col("enroll_year").fill_null(0).alias("enroll_year"),
|
|
201
|
+
pl.col("enroll_school_year").fill_null(0).alias("enroll_school_year"),
|
|
202
|
+
)
|
|
203
|
+
.with_columns(
|
|
204
|
+
waitlist_offer=(
|
|
205
|
+
(pl.col("offer") == 1)
|
|
206
|
+
& (pl.col("waitlist") == 1)
|
|
207
|
+
).cast(pl.Int8),
|
|
208
|
+
|
|
209
|
+
initial_offer=(
|
|
210
|
+
(pl.col("offer") == 1)
|
|
211
|
+
& (pl.col("waitlist") != 1)
|
|
212
|
+
).cast(pl.Int8),
|
|
213
|
+
)
|
|
214
|
+
.pipe(keep_final_apps_schema)
|
|
215
|
+
.with_columns(
|
|
216
|
+
pl.concat_str(
|
|
217
|
+
[pl.col(c).cast(pl.String) for c in risk_keys],
|
|
218
|
+
separator="_",
|
|
219
|
+
).alias("risk_id_name")
|
|
220
|
+
)
|
|
221
|
+
.with_columns( # create number representing the risk id
|
|
222
|
+
pl.col("risk_id_name")
|
|
223
|
+
.rank(method="dense")
|
|
224
|
+
.cast(pl.Int32)
|
|
225
|
+
.alias("risk_id"),
|
|
226
|
+
)
|
|
227
|
+
.pipe(drop_all_null)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
###########################################################################################################################################################################
|
|
233
|
+
# --- Create collapsed version of the stata dataset ---
|
|
234
|
+
###########################################################################################################################################################################
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# Define cols we want to take the max of and the grouping key
|
|
238
|
+
max_cols = [
|
|
239
|
+
"offer",
|
|
240
|
+
"initial_offer",
|
|
241
|
+
"waitlist_offer",
|
|
242
|
+
"offer_accepted",
|
|
243
|
+
"enroll",
|
|
244
|
+
"sibling",
|
|
245
|
+
"transfer",
|
|
246
|
+
"zoned",
|
|
247
|
+
"staff",
|
|
248
|
+
"sibling_concur",
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
group_key = ["pik"]
|
|
252
|
+
|
|
253
|
+
first_cols = [
|
|
254
|
+
c for c in final.columns
|
|
255
|
+
if c not in group_key + max_cols + ["risk_id"]
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
# Create the collapsed dataset
|
|
259
|
+
final_collapsed = (
|
|
260
|
+
final
|
|
261
|
+
.filter(pl.col("application_cancel") !=1)
|
|
262
|
+
.group_by(group_key)
|
|
263
|
+
.agg(
|
|
264
|
+
# max variables
|
|
265
|
+
[pl.max(c).alias(c) for c in max_cols]
|
|
266
|
+
|
|
267
|
+
+
|
|
268
|
+
|
|
269
|
+
# all risk sets student participcated in
|
|
270
|
+
[
|
|
271
|
+
pl.col("risk_id")
|
|
272
|
+
.unique()
|
|
273
|
+
.sort()
|
|
274
|
+
.str.join("_")
|
|
275
|
+
.alias("risk_set")
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
+
|
|
279
|
+
|
|
280
|
+
# everything else from first row
|
|
281
|
+
[
|
|
282
|
+
pl.first(c).alias(c)
|
|
283
|
+
for c in first_cols
|
|
284
|
+
]
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
###########################################################################################################################################################################
|
|
290
|
+
# --- Create unique schools dataset ---
|
|
291
|
+
###########################################################################################################################################################################
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# Scan in the combined data and collaprse to unique schools
|
|
295
|
+
sch_ren_map = (
|
|
296
|
+
full
|
|
297
|
+
.select(["cmo_name", "school_name", "ren_num"])
|
|
298
|
+
.with_columns(
|
|
299
|
+
pl.lit("CALIFORNIA").alias("cmo_region"),
|
|
300
|
+
)
|
|
301
|
+
.drop_nulls(["school_name"])
|
|
302
|
+
.group_by(["cmo_name", "cmo_region", "school_name"])
|
|
303
|
+
.agg(
|
|
304
|
+
pl.len().alias("n_obs"),
|
|
305
|
+
|
|
306
|
+
pl.col("ren_num")
|
|
307
|
+
.drop_nulls()
|
|
308
|
+
.unique()
|
|
309
|
+
.sort()
|
|
310
|
+
.cast(pl.String)
|
|
311
|
+
.str.join("+")
|
|
312
|
+
.alias("ren_nums")
|
|
313
|
+
)
|
|
314
|
+
.select(["cmo_name", "cmo_region", "school_name", "n_obs", "ren_nums"])
|
|
315
|
+
.sort("n_obs", descending=True)
|
|
316
|
+
).collect()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
###########################################################################################################################################################################
|
|
320
|
+
# --- Save data ---
|
|
321
|
+
###########################################################################################################################################################################
|
|
322
|
+
|
|
323
|
+
# Save unique schools dataset
|
|
324
|
+
sch_ren_map.write_csv(OUTPUT_SCHOOLS)
|
|
325
|
+
|
|
326
|
+
# Save student apps dataset to stata
|
|
327
|
+
final.to_pandas().to_stata(OUTPUT_LONG, write_index=False, version=118)
|
|
328
|
+
|
|
329
|
+
# Save collapsed student dataset to stata
|
|
330
|
+
final_collapsed.to_pandas().to_stata(OUTPUT_WIDE, write_index=False, version=118)
|
|
331
|
+
|
|
332
|
+
# End timer
|
|
333
|
+
elapsed = time.perf_counter() - start
|
|
334
|
+
print(f"Finished in {elapsed / 60:.2f} minutes")
|
|
335
|
+
logging.info(f"Finished in {elapsed / 60:.2f} minutes")
|
|
336
|
+
|
|
337
|
+
|