ltc-code 0.1.95__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.95 → ltc_code-0.1.96}/PKG-INFO +1 -1
- {ltc_code-0.1.95 → ltc_code-0.1.96}/pyproject.toml +1 -1
- ltc_code-0.1.96/src/ltc_code/june13.py +294 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/README.md +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/__init__.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/aspire.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/check_cmo_apps.do +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/christel_house.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/green_dot.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/helpers.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/june2.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/june5.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/june7.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/kipp_nj.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/kipp_tx.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/main.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/mappings.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/may27.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/polars_dates.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/rocketship.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/schema_mapping.py +0 -0
- {ltc_code-0.1.95 → ltc_code-0.1.96}/src/ltc_code/yes_prep.py +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
from typing import Callable, Optional, Sequence, Union
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def impute_offers(
|
|
7
|
+
frame: Union[pl.DataFrame, pl.LazyFrame],
|
|
8
|
+
offer_col: str,
|
|
9
|
+
lottery_number_col: Optional[str] = None,
|
|
10
|
+
waitlist_number_col: Optional[str] = None,
|
|
11
|
+
enrollment_col: Optional[str] = None,
|
|
12
|
+
lottery_imputation: Optional[Union[pl.Expr, Callable[[Frame], pl.Expr]]] = None,
|
|
13
|
+
waitlist_imputation: Optional[Union[pl.Expr, Callable[[Frame], pl.Expr]]] = None,
|
|
14
|
+
enrollment_imputation: Optional[Union[pl.Expr, Callable[[Frame], pl.Expr]]] = None,
|
|
15
|
+
sibling_priority_cols: Optional[Sequence[str]] = None,
|
|
16
|
+
) -> Union[pl.DataFrame, pl.LazyFrame]:
|
|
17
|
+
"""
|
|
18
|
+
Impute offer indicators in a Polars DataFrame or LazyFrame.
|
|
19
|
+
|
|
20
|
+
The function treats null offers as 0. It sets ``offer_col`` to 1 for rows
|
|
21
|
+
that satisfy any supplied or default imputation rule, and creates:
|
|
22
|
+
|
|
23
|
+
- ``offer_imputed_ln``: newly imputed from lottery number information
|
|
24
|
+
- ``offer_imputed_wn``: newly imputed from waitlist number information
|
|
25
|
+
- ``offer_imputed_enr``: newly imputed from enrollment information
|
|
26
|
+
|
|
27
|
+
Flags are only set for rows whose original offer was 0 or null. Rows that
|
|
28
|
+
already had an observed offer are never flagged. Multiple flags may be 1
|
|
29
|
+
for the same row if multiple rules independently imply an offer.
|
|
30
|
+
|
|
31
|
+
Default rules:
|
|
32
|
+
- Lottery: find the highest lottery number among observed offers, excluding
|
|
33
|
+
sibling-priority offers when ``sibling_priority_cols`` is supplied. Impute
|
|
34
|
+
offers for all originally non-offered rows with lottery numbers at or
|
|
35
|
+
below that cutoff.
|
|
36
|
+
- Enrollment: impute offers for originally non-offered enrolled rows.
|
|
37
|
+
- Waitlist:
|
|
38
|
+
* If enrollment information is supplied, first infer offers from
|
|
39
|
+
enrollment, then use the highest waitlist number among those
|
|
40
|
+
enrollment-implied offers as the waitlist cutoff.
|
|
41
|
+
* If enrollment information is not supplied, use the highest waitlist
|
|
42
|
+
number among observed offers as the waitlist cutoff.
|
|
43
|
+
Then impute offers for all originally non-offered rows with waitlist
|
|
44
|
+
numbers at or below the cutoff.
|
|
45
|
+
|
|
46
|
+
Custom rules may be supplied as Polars expressions or as callables that
|
|
47
|
+
accept the frame and return a Polars expression.
|
|
48
|
+
|
|
49
|
+
Example
|
|
50
|
+
-------
|
|
51
|
+
>>> df = pl.DataFrame({
|
|
52
|
+
... "offer": [1, 0, 0, None],
|
|
53
|
+
... "lottery_num": [10, 11, 12, 13],
|
|
54
|
+
... "waitlist_num": [None, 1, 2, 3],
|
|
55
|
+
... "enrolled": [0, 0, 1, 0],
|
|
56
|
+
... "sib_1": [0, 0, 0, 0],
|
|
57
|
+
... "sib_2": [0, 0, 0, 0],
|
|
58
|
+
... })
|
|
59
|
+
>>> impute_offers(
|
|
60
|
+
... df,
|
|
61
|
+
... offer_col="offer",
|
|
62
|
+
... lottery_number_col="lottery_num",
|
|
63
|
+
... waitlist_number_col="waitlist_num",
|
|
64
|
+
... enrollment_col="enrolled",
|
|
65
|
+
... sibling_priority_cols=["sib_1", "sib_2"],
|
|
66
|
+
... )
|
|
67
|
+
shape: (4, 9)
|
|
68
|
+
...
|
|
69
|
+
"""
|
|
70
|
+
if not isinstance(frame, (pl.DataFrame, pl.LazyFrame)):
|
|
71
|
+
raise TypeError("frame must be a Polars DataFrame or LazyFrame.")
|
|
72
|
+
|
|
73
|
+
original_offer = "__original_offer_for_imputation__"
|
|
74
|
+
|
|
75
|
+
def as_rule_expr(rule: Union[pl.Expr, Callable[[Frame], pl.Expr]]) -> pl.Expr:
|
|
76
|
+
expr = rule(frame) if callable(rule) else rule
|
|
77
|
+
if not isinstance(expr, pl.Expr):
|
|
78
|
+
raise TypeError("Imputation rules must be Polars expressions.")
|
|
79
|
+
return expr.fill_null(False)
|
|
80
|
+
|
|
81
|
+
def any_sibling_priority(cols: Optional[Sequence[str]]) -> pl.Expr:
|
|
82
|
+
if not cols:
|
|
83
|
+
return pl.lit(False)
|
|
84
|
+
|
|
85
|
+
priority_expr = pl.lit(False)
|
|
86
|
+
for col in cols:
|
|
87
|
+
priority_expr = priority_expr | (pl.col(col).fill_null(0) == 1)
|
|
88
|
+
return priority_expr
|
|
89
|
+
|
|
90
|
+
work = frame.with_columns(pl.col(offer_col).fill_null(0).alias(original_offer))
|
|
91
|
+
originally_no_offer = pl.col(original_offer) == 0
|
|
92
|
+
|
|
93
|
+
lottery_rule = None
|
|
94
|
+
if lottery_imputation is not None:
|
|
95
|
+
lottery_rule = as_rule_expr(lottery_imputation)
|
|
96
|
+
elif lottery_number_col is not None:
|
|
97
|
+
observed_regular_offer = (
|
|
98
|
+
(pl.col(original_offer) == 1)
|
|
99
|
+
& ~any_sibling_priority(sibling_priority_cols)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
lottery_cutoff = (
|
|
103
|
+
pl.col(lottery_number_col)
|
|
104
|
+
.filter(observed_regular_offer)
|
|
105
|
+
.max()
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
lottery_rule = (
|
|
109
|
+
pl.col(lottery_number_col).is_not_null()
|
|
110
|
+
& (pl.col(lottery_number_col) <= lottery_cutoff)
|
|
111
|
+
).fill_null(False)
|
|
112
|
+
|
|
113
|
+
enrollment_rule = None
|
|
114
|
+
if enrollment_imputation is not None:
|
|
115
|
+
enrollment_rule = as_rule_expr(enrollment_imputation)
|
|
116
|
+
elif enrollment_col is not None:
|
|
117
|
+
enrollment_rule = (pl.col(enrollment_col).fill_null(0) == 1).fill_null(False)
|
|
118
|
+
|
|
119
|
+
waitlist_rule = None
|
|
120
|
+
if waitlist_imputation is not None:
|
|
121
|
+
waitlist_rule = as_rule_expr(waitlist_imputation)
|
|
122
|
+
elif waitlist_number_col is not None:
|
|
123
|
+
if enrollment_rule is not None:
|
|
124
|
+
waitlist_cutoff_source = originally_no_offer & enrollment_rule
|
|
125
|
+
else:
|
|
126
|
+
waitlist_cutoff_source = pl.col(original_offer) == 1
|
|
127
|
+
|
|
128
|
+
waitlist_cutoff = (
|
|
129
|
+
pl.col(waitlist_number_col)
|
|
130
|
+
.filter(waitlist_cutoff_source)
|
|
131
|
+
.max()
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
waitlist_rule = (
|
|
135
|
+
pl.col(waitlist_number_col).is_not_null()
|
|
136
|
+
& (pl.col(waitlist_number_col) <= waitlist_cutoff)
|
|
137
|
+
).fill_null(False)
|
|
138
|
+
|
|
139
|
+
false_expr = pl.lit(False)
|
|
140
|
+
lottery_rule = lottery_rule if lottery_rule is not None else false_expr
|
|
141
|
+
waitlist_rule = waitlist_rule if waitlist_rule is not None else false_expr
|
|
142
|
+
enrollment_rule = enrollment_rule if enrollment_rule is not None else false_expr
|
|
143
|
+
|
|
144
|
+
ln_flag = originally_no_offer & lottery_rule
|
|
145
|
+
enr_flag = originally_no_offer & enrollment_rule
|
|
146
|
+
wn_flag = originally_no_offer & waitlist_rule
|
|
147
|
+
|
|
148
|
+
any_imputation = ln_flag | wn_flag | enr_flag
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
work.with_columns(
|
|
152
|
+
[
|
|
153
|
+
pl.when(ln_flag)
|
|
154
|
+
.then(1)
|
|
155
|
+
.otherwise(0)
|
|
156
|
+
.cast(pl.Int8)
|
|
157
|
+
.alias("offer_imputed_ln"),
|
|
158
|
+
pl.when(wn_flag)
|
|
159
|
+
.then(1)
|
|
160
|
+
.otherwise(0)
|
|
161
|
+
.cast(pl.Int8)
|
|
162
|
+
.alias("offer_imputed_wn"),
|
|
163
|
+
pl.when(enr_flag)
|
|
164
|
+
.then(1)
|
|
165
|
+
.otherwise(0)
|
|
166
|
+
.cast(pl.Int8)
|
|
167
|
+
.alias("offer_imputed_enr"),
|
|
168
|
+
pl.when((pl.col(original_offer) == 1) | any_imputation)
|
|
169
|
+
.then(1)
|
|
170
|
+
.otherwise(0)
|
|
171
|
+
.cast(pl.Int8)
|
|
172
|
+
.alias(offer_col),
|
|
173
|
+
]
|
|
174
|
+
)
|
|
175
|
+
.drop(original_offer)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# mappings.py
|
|
182
|
+
|
|
183
|
+
import polars as pl
|
|
184
|
+
|
|
185
|
+
FINAL_SCHEMA = {
|
|
186
|
+
"cmo_name": {"dtype": pl.String, "default": None},
|
|
187
|
+
"cmo_region": {"dtype": pl.String, "default": None},
|
|
188
|
+
"ren_num": {"dtype": pl.String, "default": None},
|
|
189
|
+
"sid_cepr": {"dtype": pl.String, "default": None},
|
|
190
|
+
|
|
191
|
+
"school_name": {"dtype": pl.String, "default": None},
|
|
192
|
+
"school_year": {"dtype": pl.Int64, "default": None},
|
|
193
|
+
"entry_grade_clean": {"dtype": pl.String, "default": None},
|
|
194
|
+
"priority_group": {"dtype": pl.Int64, "default": None},
|
|
195
|
+
"risk_id_name": {"dtype": pl.String, "default": None},
|
|
196
|
+
"risk_id": {"dtype": pl.Int64, "default": None},
|
|
197
|
+
|
|
198
|
+
"offer": {"dtype": pl.Int8, "default": 0},
|
|
199
|
+
"waitlist": {"dtype": pl.Int8, "default": 0},
|
|
200
|
+
"initial_offer": {"dtype": pl.Int8, "default": 0},
|
|
201
|
+
"waitlist_offer": {"dtype": pl.Int8, "default": 0},
|
|
202
|
+
"offer_accepted": {"dtype": pl.Int8, "default": None},
|
|
203
|
+
"accept_school": {"dtype": pl.String, "default": None},
|
|
204
|
+
"offer_imputed_ln": {"dtype": pl.Int8, "default": 0},
|
|
205
|
+
"offer_imputed_wn": {"dtype": pl.Int8, "default": 0},
|
|
206
|
+
"offer_imputed_enr": {"dtype": pl.Int8, "default": 0},
|
|
207
|
+
|
|
208
|
+
"priority_group_name": {"dtype": pl.String, "default": None},
|
|
209
|
+
"priority_group_clean": {"dtype": pl.String, "default": None},
|
|
210
|
+
"sibling": {"dtype": pl.Int8, "default": 0},
|
|
211
|
+
"sibling_concur": {"dtype": pl.Int8, "default": 0},
|
|
212
|
+
"staff": {"dtype": pl.Int8, "default": 0},
|
|
213
|
+
"zoned": {"dtype": pl.Int8, "default": 0},
|
|
214
|
+
"transfer": {"dtype": pl.Int8, "default": 0},
|
|
215
|
+
|
|
216
|
+
"application_cancel": {"dtype": pl.Int8, "default": 0},
|
|
217
|
+
"late_app": {"dtype": pl.Int8, "default": 0},
|
|
218
|
+
"former_dupe": {"dtype": pl.Int8, "default": 0},
|
|
219
|
+
|
|
220
|
+
"enroll": {"dtype": pl.Int8, "default": 0},
|
|
221
|
+
"enroll_school": {"dtype": pl.Int8, "default": 0},
|
|
222
|
+
"enroll_year": {"dtype": pl.Int8, "default": 0},
|
|
223
|
+
"enroll_school_year": {"dtype": pl.Int8, "default": 0},
|
|
224
|
+
"enroll_missing": {"dtype": pl.Int8, "default": 1},
|
|
225
|
+
|
|
226
|
+
"enrollment_years": {"dtype": pl.Int64, "default": None},
|
|
227
|
+
"year_enrolled": {"dtype": pl.Int64, "default": None},
|
|
228
|
+
"withdraw": {"dtype": pl.Int8, "default": 0},
|
|
229
|
+
"waitlist_number": {"dtype": pl.Int64, "default": None},
|
|
230
|
+
"lottery_number": {"dtype": pl.Int64, "default": None},
|
|
231
|
+
"offer_date": {"dtype": pl.Date, "default": None},
|
|
232
|
+
"application_date": {"dtype": pl.Date, "default": None},
|
|
233
|
+
"birth_cohort": {"dtype": pl.Int64, "default": None},
|
|
234
|
+
"age": {"dtype": pl.Int64, "default": None},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
from typing import Mapping, Union
|
|
240
|
+
|
|
241
|
+
import polars as pl
|
|
242
|
+
|
|
243
|
+
import mappings
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def apply_final_schema(
|
|
247
|
+
frame: Union[pl.DataFrame, pl.LazyFrame],
|
|
248
|
+
schema: Mapping[str, Mapping[str, object]] = mappings.FINAL_SCHEMA,
|
|
249
|
+
) -> Union[pl.DataFrame, pl.LazyFrame]:
|
|
250
|
+
"""
|
|
251
|
+
Restrict a Polars DataFrame/LazyFrame to the final LTC application schema.
|
|
252
|
+
|
|
253
|
+
Existing columns are kept, cast to their declared dtype, and ordered
|
|
254
|
+
according to ``schema``. Columns not listed in ``schema`` are dropped.
|
|
255
|
+
Missing schema columns are created from their declared defaults.
|
|
256
|
+
|
|
257
|
+
The schema should be a mapping like:
|
|
258
|
+
|
|
259
|
+
{
|
|
260
|
+
"offer": {"dtype": pl.Int8, "default": 0},
|
|
261
|
+
"school_name": {"dtype": pl.String, "default": None},
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
Defaults may be either scalar values or Polars expressions. Expression
|
|
265
|
+
defaults are useful when a missing final column should be derived from
|
|
266
|
+
other columns.
|
|
267
|
+
"""
|
|
268
|
+
if not isinstance(frame, (pl.DataFrame, pl.LazyFrame)):
|
|
269
|
+
raise TypeError("frame must be a Polars DataFrame or LazyFrame.")
|
|
270
|
+
|
|
271
|
+
existing_columns = (
|
|
272
|
+
frame.collect_schema().names()
|
|
273
|
+
if isinstance(frame, pl.LazyFrame)
|
|
274
|
+
else frame.columns
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
final_columns = []
|
|
278
|
+
|
|
279
|
+
for column, spec in schema.items():
|
|
280
|
+
dtype = spec["dtype"]
|
|
281
|
+
default = spec.get("default")
|
|
282
|
+
|
|
283
|
+
if column in existing_columns:
|
|
284
|
+
expr = pl.col(column)
|
|
285
|
+
elif isinstance(default, pl.Expr):
|
|
286
|
+
expr = default
|
|
287
|
+
else:
|
|
288
|
+
expr = pl.lit(default)
|
|
289
|
+
|
|
290
|
+
final_columns.append(
|
|
291
|
+
expr.cast(dtype, strict=False).alias(column)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return frame.select(final_columns)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|