ltc-code 0.2.16__tar.gz → 0.2.18__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.
Files changed (73) hide show
  1. {ltc_code-0.2.16 → ltc_code-0.2.18}/PKG-INFO +1 -1
  2. {ltc_code-0.2.16 → ltc_code-0.2.18}/pyproject.toml +1 -1
  3. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/build_nsc_outcomes.py +167 -56
  4. ltc_code-0.2.18/src/ltc_code/nsc/raw/ipeds_data.dta +0 -0
  5. ltc_code-0.2.16/src/ltc_code/.DS_Store +0 -0
  6. ltc_code-0.2.16/src/ltc_code/nsc/.DS_Store +0 -0
  7. ltc_code-0.2.16/src/ltc_code/nsc/raw/.DS_Store +0 -0
  8. {ltc_code-0.2.16 → ltc_code-0.2.18}/README.md +0 -0
  9. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/aspire.py +0 -0
  10. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/check_cmo_apps.do +0 -0
  11. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/christel_house.py +0 -0
  12. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/democracy_prep.py +0 -0
  13. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/green_dot.py +0 -0
  14. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/helpers.py +0 -0
  15. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/ilt.py +0 -0
  16. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/kipp_nj.py +0 -0
  17. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/main.py +0 -0
  18. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/mappings.py +0 -0
  19. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/rocketship.py +0 -0
  20. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260614_new_build_scripts_update/yes_prep.py +0 -0
  21. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260630_census_disclosure/aspire.py +0 -0
  22. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260630_census_disclosure/christel_house.py +0 -0
  23. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260630_census_disclosure/democracy_prep.py +0 -0
  24. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260630_census_disclosure/green_dot.py +0 -0
  25. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260630_census_disclosure/ilt.py +0 -0
  26. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260630_census_disclosure/kipp.py +0 -0
  27. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260630_census_disclosure/kipp_nj.py +0 -0
  28. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260630_census_disclosure/rocketship.py +0 -0
  29. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260630_census_disclosure/yes_prep.py +0 -0
  30. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260706_ceprscripts/BALANCE.do +0 -0
  31. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260706_ceprscripts/FS.do +0 -0
  32. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260706_ceprscripts/ITT.do +0 -0
  33. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260706_ceprscripts/TOT.do +0 -0
  34. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260706_ceprscripts/apps_helpers.py +0 -0
  35. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260706_ceprscripts/harmony.py +0 -0
  36. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260706_ceprscripts/main.py +0 -0
  37. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260712_uncommon_scripts/main.py +0 -0
  38. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260712_uncommon_scripts/mappings.py +0 -0
  39. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/20260712_uncommon_scripts/uncommon.py +0 -0
  40. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/__init__.py +0 -0
  41. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/aspire.py +0 -0
  42. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/check_cmo_apps.do +0 -0
  43. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/christel_house.py +0 -0
  44. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/green_dot.py +0 -0
  45. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/helpers.py +0 -0
  46. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/june13.py +0 -0
  47. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/june2.py +0 -0
  48. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/june30.py +0 -0
  49. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/june5.py +0 -0
  50. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/june7.py +0 -0
  51. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/kipp_nj.py +0 -0
  52. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/kipp_tx.py +0 -0
  53. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/main.py +0 -0
  54. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/mappings.py +0 -0
  55. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/may27.py +0 -0
  56. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/__init__.py +0 -0
  57. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/input/.gitkeep +0 -0
  58. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/naics.csv +0 -0
  59. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/naics.py +0 -0
  60. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/naics_raw.xlsx +0 -0
  61. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/output/.gitkeep +0 -0
  62. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/raw/CREDENTIAL_LEVEL_LOOKUP_TABLE.xlsx +0 -0
  63. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/raw/IPEDS_IC_2013.csv +0 -0
  64. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/raw/IPEDS_IC_manual.xlsx +0 -0
  65. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/raw/chetty/mrc_table11.dta +0 -0
  66. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/raw/chetty/mrc_table2.dta +0 -0
  67. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/raw/college_crosswalk.xls +0 -0
  68. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/raw/directory.dta +0 -0
  69. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/nsc/run_nsc_outcomes.py +0 -0
  70. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/polars_dates.py +0 -0
  71. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/rocketship.py +0 -0
  72. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/schema_mapping.py +0 -0
  73. {ltc_code-0.2.16 → ltc_code-0.2.18}/src/ltc_code/yes_prep.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ltc-code
3
- Version: 0.2.16
3
+ Version: 0.2.18
4
4
  Summary: Add your description here
5
5
  Requires-Dist: fastexcel>=0.16,<0.20
6
6
  Requires-Dist: polars>=1.36.1,<1.42
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ltc-code"
3
- version = "0.2.16"
3
+ version = "0.2.18"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -54,24 +54,24 @@ CHETTY_MRC_TABLE11 = next(
54
54
  )
55
55
  # Chetty OPE-to-super-OPE crosswalk used to attach tier to NSC schools.
56
56
 
57
+ IPEDS_DATA = RAW_NSC / "ipeds_data.dta"
58
+ # Institution-level IPEDS outcomes, including completion within 150% of normal time.
59
+
57
60
  INPUT_NSC = PACKAGE_NSC / "input"
58
61
  # Folder for secured Census inputs that are not packaged with the code.
59
62
 
63
+ OUTPUT_NSC = PACKAGE_NSC / "output"
64
+ # Folder for final student-level NSC outcomes.
65
+
60
66
  APPS = INPUT_NSC / "apps.csv"
61
67
  # Application/student records; source for unique sid_cepr/cohort_lottery.
62
68
 
63
- NSC_RECORDS = INPUT_NSC / "nsc_records.csv"
64
- # Long NSC enrollment and degree records.
65
-
66
69
  INPUT_NSC_OLD = INPUT_NSC / "nsc_records_old.dta"
67
70
  # Older NSC pull, kept so the build can append old and new NSC versions.
68
71
 
69
- INPUT_NSC_NEW = INPUT_NSC / "nsc_records_new.dta"
72
+ INPUT_NSC_NEW = INPUT_NSC / "nsc_records_new.csv"
70
73
  # Newer NSC pull with studyid-based rows.
71
74
 
72
- OUTPUT_NSC = PACKAGE_NSC / "output"
73
- # Package-local output folder for NSC outcome files.
74
-
75
75
  OUTCOMES = OUTPUT_NSC / "nsc_outcomes.parquet"
76
76
  # Final student-level NSC outcomes file.
77
77
 
@@ -99,17 +99,13 @@ college_crosswalk = (
99
99
  columns=["ID_IPEDS", "ID_OPE", "ID_FSC", "ID_FSC_long", "name"],
100
100
  )
101
101
  .with_columns(
102
- pl.when(
103
- pl.col("ID_FSC_long").is_not_null()
104
- & (pl.col("ID_FSC_long").cast(pl.String).str.strip_chars() != "")
105
- )
106
- .then(pl.col("ID_FSC_long").cast(pl.String).str.strip_chars())
107
- .when(pl.col("ID_FSC").is_not_null())
108
- .then(
109
- pl.col("ID_FSC").cast(pl.String).str.strip_chars().str.replace(r"\.0$", "")
110
- + pl.lit("-00")
102
+ pl.coalesce(
103
+ [
104
+ pl.col("ID_FSC_long").cast(pl.String).str.strip_chars(),
105
+ pl.col("ID_FSC").cast(pl.String).str.strip_chars() + pl.lit("-00")
106
+ ]
111
107
  )
112
- .otherwise(None)
108
+ .str.replace(r"\.0$", "")
113
109
  .alias("ID_FSC"),
114
110
  pl.col("ID_IPEDS").cast(pl.Int64, strict=False).alias("unitid"),
115
111
  pl.col("ID_OPE").cast(pl.Int64, strict=False),
@@ -231,24 +227,32 @@ directory = (
231
227
  .select("unitid", "college_name_directory", "years_directory", "ownership_directory")
232
228
  )
233
229
 
234
- # Chetty/Barron tiers are keyed at super-OPE level. Table 11 maps school OPE IDs
235
- # to those super-OPE groups, which is the same route Sarah's Stata code uses.
236
- chetty_tiers = (
230
+ # Chetty college outcomes are keyed at super-OPE level. Table 11 maps school
231
+ # OPE IDs to those groups, which is the same route Sarah's Stata code uses.
232
+ chetty_college_outcomes = (
237
233
  scan_readstat(
238
234
  CHETTY_MRC_TABLE2,
239
235
  missing_string_as_null=True,
240
236
  value_labels_as_strings=False,
241
237
  )
242
- .select("super_opeid", "tier", "tier_name")
238
+ .select("super_opeid", "tier", "tier_name", "k_mean")
243
239
  .collect()
244
240
  .with_columns(
245
241
  pl.col("super_opeid").cast(pl.Int64, strict=False),
246
242
  pl.col("tier").cast(pl.Int8, strict=False),
243
+ pl.col("k_mean").cast(pl.Float64, strict=False),
247
244
  )
248
245
  .drop_nulls("super_opeid")
249
246
  .unique("super_opeid", keep="first")
250
247
  )
251
248
 
249
+ k_mean_neverattend = chetty_college_outcomes.filter(
250
+ pl.col("super_opeid") == -99
251
+ ).item(0, "k_mean")
252
+ k_mean_insuffdata = chetty_college_outcomes.filter(
253
+ pl.col("super_opeid") == -1
254
+ ).item(0, "k_mean")
255
+
252
256
  chetty_ope_crosswalk = (
253
257
  scan_readstat(
254
258
  CHETTY_MRC_TABLE11,
@@ -265,10 +269,30 @@ chetty_ope_crosswalk = (
265
269
  )
266
270
 
267
271
  chetty_by_opeid = (
268
- chetty_ope_crosswalk.join(chetty_tiers, on="super_opeid", how="left")
272
+ chetty_ope_crosswalk.join(
273
+ chetty_college_outcomes, on="super_opeid", how="left"
274
+ )
269
275
  .unique("opeid", keep="first")
270
276
  )
271
277
 
278
+ # IPEDS reports completion within 150% of normal time: six years at four-year
279
+ # colleges and three years at two-year colleges.
280
+ ipeds_completion = (
281
+ scan_readstat(
282
+ IPEDS_DATA,
283
+ missing_string_as_null=True,
284
+ value_labels_as_strings=False,
285
+ )
286
+ .select("unitid", "completion_rate_150pct_ip")
287
+ .collect()
288
+ .with_columns(
289
+ pl.col("unitid").cast(pl.Int64, strict=False),
290
+ pl.col("completion_rate_150pct_ip").cast(pl.Float64, strict=False),
291
+ )
292
+ .drop_nulls("unitid")
293
+ .unique("unitid", keep="first")
294
+ )
295
+
272
296
 
273
297
  ###########################################################
274
298
  # Build college reference data
@@ -280,6 +304,7 @@ college_ref = (
280
304
  .join(ipeds_manual, on="unitid", how="left")
281
305
  .join(directory, on="unitid", how="left")
282
306
  .join(chetty_by_opeid, on="opeid", how="left", validate="m:1")
307
+ .join(ipeds_completion, on="unitid", how="left", validate="m:1")
283
308
  .with_columns(
284
309
  pl.coalesce(
285
310
  [
@@ -318,8 +343,6 @@ college_ref = (
318
343
  "ID_FSC",
319
344
  "unitid",
320
345
  "ID_OPE",
321
- "opeid",
322
- "super_opeid",
323
346
  "college_name",
324
347
  "college_years",
325
348
  "college_ownership",
@@ -327,9 +350,9 @@ college_ref = (
327
350
  "college_4yr",
328
351
  "college_2yr",
329
352
  "tier",
330
- "tier_name",
331
- "superopeid_name",
332
353
  "college_elite",
354
+ "k_mean",
355
+ "completion_rate_150pct_ip",
333
356
  )
334
357
  )
335
358
 
@@ -340,22 +363,25 @@ college_ref = (
340
363
 
341
364
  # Start from unique students. cohort_lottery is the birth cohort; cohort_18 is
342
365
  # the year the student turns 18 and anchors Sarah's Y1/Y2/... outcome windows.
343
- if str(APPS).endswith(".dta"):
344
- apps_raw = scan_readstat(
345
- APPS, missing_string_as_null=True, value_labels_as_strings=True
346
- ).collect()
347
- else:
348
- apps_raw = pl.read_csv(APPS)
366
+
349
367
 
350
368
  apps = (
351
- apps_raw.select("sid_cepr", "cohort_lottery")
369
+ scan_readstat(
370
+ APPS, missing_string_as_null=True, value_labels_as_strings=True
371
+ )
372
+ .select("sid_cepr", "cohort_lottery")
352
373
  .drop_nulls(["sid_cepr", "cohort_lottery"])
353
374
  .with_columns(
354
375
  pl.col("sid_cepr").cast(pl.Int64),
355
376
  pl.col("cohort_lottery").cast(pl.Int64),
356
377
  )
357
- .unique(["sid_cepr", "cohort_lottery"])
378
+ .filter(
379
+ pl.col("cohort_lottery").is_between(1990, 2005)
380
+ )
381
+ .sort("cohort_lottery")
382
+ .unique(["sid_cepr"])
358
383
  .with_columns((pl.col("cohort_lottery") + 18).alias("cohort_18"))
384
+ .collect()
359
385
  )
360
386
 
361
387
  # These branch-code fixes mirror the Sarah code path that maps known bad FSC
@@ -441,7 +467,11 @@ fsc_branch_fixes = [
441
467
  # The real NSC file uses raw NSC names; Sarah renames these before building
442
468
  # outcomes. This block makes those same names explicit in Polars.
443
469
  nsc_old = (
444
- scan_readstat(INPUT_NSC_OLD, missing_string_as_null=True, value_labels_as_strings=True)
470
+ scan_readstat(INPUT_NSC_OLD, missing_string_as_null=True, value_labels_as_strings=True, schema_overrides={
471
+ "searchdate": pl.String
472
+ }
473
+ )
474
+ .collect()
445
475
  .with_columns(
446
476
  pl.lit(2023).alias("NSCdatayear"),
447
477
 
@@ -450,11 +480,41 @@ nsc_old = (
450
480
  .cast(pl.Int32)
451
481
  .alias("searchbeginyear")
452
482
  )
453
- .collect()
454
483
  )
455
484
 
456
485
  nsc_new = (
457
- scan_readstat(INPUT_NSC_NEW, missing_string_as_null=True, value_labels_as_strings=True)
486
+ pl.scan_csv(INPUT_NSC_NEW, infer_schema=False, null_values=[""], try_parse_dates=False, ignore_errors=False,)
487
+ .rename({
488
+ "Study ID": "studyid",
489
+ "Record Found Y/N": "recordfoundyn",
490
+ "College Code/Branch": "collegecodebranch",
491
+ "College Name": "collegename",
492
+ "College State": "collegestate",
493
+ "2-year / 4-year": "2year4year",
494
+ "Public / Private": "publicprivate",
495
+ "Enrollment Begin": "enrollmentbegin",
496
+ "Enrollment End": "enrollmentend",
497
+ "Enrollment Status": "enrollmentstatus",
498
+ "Class Level": "classlevel",
499
+ "Enrollment Major 1": "enrollmentmajor1",
500
+ "Enrollment CIP 1": "enrollmentcip1",
501
+ "Enrollment Major 2": "enrollmentmajor2",
502
+ "Enrollment CIP 2": "enrollmentcip2",
503
+ "Graduated?": "graduated",
504
+ "Graduation Date": "graduationdate",
505
+ "Degree Title": "degreetitle",
506
+ "Degree Major 1": "degreemajor1",
507
+ "Degree CIP 1": "degreecip1",
508
+ "Degree Major 2": "degreemajor2",
509
+ "Degree CIP 2": "degreecip2",
510
+ "Degree Major 3": "degreemajor3",
511
+ "Degree CIP 3": "degreecip3",
512
+ "Degree Major 4": "degreemajor4",
513
+ "Degree CIP 4": "degreecip4",
514
+ "College Sequence": "collegesequence",
515
+ "Student Age at start of term": "studentageatstartofterm",
516
+
517
+ })
458
518
  .with_row_index("origsrt", offset=1)
459
519
  .filter(pl.col("studyid").is_not_null())
460
520
  .with_columns(
@@ -463,7 +523,7 @@ nsc_new = (
463
523
  .explode("sid_cepr")
464
524
  .filter(pl.col("sid_cepr") != "")
465
525
  .with_columns(
466
- pl.col("sid_cepr").cast(pl.Int64, strict=False)
526
+ pl.col("sid_cepr").cast(pl.String).str.replace_all("_", "").cast(pl.Int64, strict=False)
467
527
  )
468
528
  .filter(
469
529
  pl.col("sid_cepr").is_not_null()
@@ -706,6 +766,8 @@ enroll_weeks = (
706
766
  "college_2yr",
707
767
  "tier",
708
768
  "college_elite",
769
+ "k_mean",
770
+ "completion_rate_150pct_ip",
709
771
  "term_start_date",
710
772
  "term_end_date",
711
773
  "week_start",
@@ -763,6 +825,8 @@ enroll = (
763
825
  pl.col("college_2yr").max(),
764
826
  pl.col("tier").drop_nulls().min(),
765
827
  pl.col("college_elite").max(),
828
+ pl.col("k_mean").drop_nulls().first(),
829
+ pl.col("completion_rate_150pct_ip").drop_nulls().first(),
766
830
  pl.col("_term_att").max(),
767
831
  pl.col("week").min().alias("week_start"),
768
832
  pl.col("week").max().alias("week_end"),
@@ -867,9 +931,20 @@ first_institution = (
867
931
  .first()
868
932
  .alias("college_name_firstinst"),
869
933
  pl.col("college_years").first().alias("college_years_firstinst"),
934
+ pl.col("k_mean").first().alias("k_mean_firstinst"),
935
+ pl.col("completion_rate_150pct_ip")
936
+ .first()
937
+ .alias("completion_rate_150pct_firstinst"),
870
938
  )
871
939
  )
872
940
 
941
+ first_institution = first_institution.with_columns(
942
+ pl.col("k_mean_firstinst")
943
+ .fill_null(k_mean_insuffdata)
944
+ .alias("k_mean"),
945
+ pl.col("completion_rate_150pct_firstinst").alias("predicted_completion"),
946
+ )
947
+
873
948
  enrollment_outcomes = enrollment_outcomes.join(
874
949
  first_institution, on="sid_cepr", how="left"
875
950
  )
@@ -920,6 +995,7 @@ degree_title = pl.col("_degree_title_clean").fill_null("")
920
995
  degrees = (
921
996
  nsc.filter(pl.col("graduated") == "Y")
922
997
  .filter(pl.col("graduated_date").is_not_null())
998
+ .filter(pl.col("graduated_date") <= NSC_CUTOFF_DATE)
923
999
  .with_columns(
924
1000
  pl.col("degree_title")
925
1001
  .cast(pl.String)
@@ -1198,6 +1274,13 @@ for year in range(1, N_YEARS_OUT + 1):
1198
1274
  (pl.col("_by_end") * pl.col("_degree").is_in(["AA", "BA"]).cast(pl.Int8))
1199
1275
  .max()
1200
1276
  .alias(f"cmp_any_byY{year}"),
1277
+ (
1278
+ pl.col("_by_end")
1279
+ * pl.col("_degree").is_in(["AA", "BA"]).cast(pl.Int8)
1280
+ * pl.col("college_elite").fill_null(0).cast(pl.Int8)
1281
+ )
1282
+ .max()
1283
+ .alias(f"cmp_elite_byY{year}"),
1201
1284
  )
1202
1285
  )
1203
1286
 
@@ -1256,20 +1339,24 @@ degree_outcomes = degree_outcomes.join(degree_details, on="sid_cepr", how="left"
1256
1339
 
1257
1340
  # Merge enrollment and degree outcomes back to the unique application universe.
1258
1341
  nsc_outcomes = (
1259
- apps.join(enrollment_outcomes, on="sid_cepr", how="left")
1260
- .join(degree_outcomes, on="sid_cepr", how="left")
1342
+ apps.join(enrollment_outcomes, on="sid_cepr", how="left", validate="m:1")
1343
+ .join(degree_outcomes, on="sid_cepr", how="left", validate="m:1")
1344
+ .with_columns(
1345
+ pl.when(pl.col("ID_FSC_firstinst").is_null())
1346
+ .then(k_mean_neverattend)
1347
+ .otherwise(pl.col("k_mean"))
1348
+ .alias("k_mean"),
1349
+ pl.when(pl.col("ID_FSC_firstinst").is_null())
1350
+ .then(0.0)
1351
+ .otherwise(pl.col("predicted_completion"))
1352
+ .alias("predicted_completion"),
1353
+ )
1261
1354
  )
1262
1355
 
1263
1356
  for year in range(1, N_YEARS_OUT + 1):
1264
- nsc_outcomes = nsc_outcomes.with_columns(
1265
- (pl.col("cohort_18") + year - 1).alias("_in_start_year"),
1266
- pl.col("cohort_18").alias("_by_start_year"),
1267
- (pl.col("cohort_18") + year).alias("_by_end_year"),
1268
- ).with_columns(
1269
- pl.date(pl.col("_in_start_year"), 7, 1).alias("_in_start"),
1270
- pl.date(pl.col("_by_start_year"), 7, 1).alias("_by_start"),
1271
- pl.date(pl.col("_by_end_year"), 6, 30).alias("_by_end"),
1272
- )
1357
+ in_start = pl.date(pl.col("cohort_18") + year - 1, 7, 1)
1358
+ by_start = pl.date(pl.col("cohort_18"), 7, 1)
1359
+ by_end = pl.date(pl.col("cohort_18") + year, 6, 30)
1273
1360
 
1274
1361
  att_in_columns = [
1275
1362
  column
@@ -1291,7 +1378,7 @@ for year in range(1, N_YEARS_OUT + 1):
1291
1378
 
1292
1379
  nsc_outcomes = nsc_outcomes.with_columns(
1293
1380
  [
1294
- pl.when(pl.col("_in_start") > NSC_CUTOFF_DATE)
1381
+ pl.when(in_start > NSC_CUTOFF_DATE)
1295
1382
  .then(None)
1296
1383
  .when(pl.col(column).is_null())
1297
1384
  .then(0)
@@ -1302,7 +1389,7 @@ for year in range(1, N_YEARS_OUT + 1):
1302
1389
  ]
1303
1390
  +
1304
1391
  [
1305
- pl.when(pl.col("_by_start") > NSC_CUTOFF_DATE)
1392
+ pl.when(by_start > NSC_CUTOFF_DATE)
1306
1393
  .then(None)
1307
1394
  .when(pl.col(column).is_null())
1308
1395
  .then(0)
@@ -1313,7 +1400,7 @@ for year in range(1, N_YEARS_OUT + 1):
1313
1400
  ]
1314
1401
  +
1315
1402
  [
1316
- pl.when(pl.col("_by_end") > NSC_CUTOFF_DATE)
1403
+ pl.when(by_end > NSC_CUTOFF_DATE)
1317
1404
  .then(None)
1318
1405
  .when(pl.col(column).is_null())
1319
1406
  .then(0)
@@ -1330,21 +1417,21 @@ for year in range(1, N_YEARS_OUT + 1):
1330
1417
  ###########################################################
1331
1418
 
1332
1419
  outcome_columns = [
1333
- "att_any_inY1", "att_any_inY2", "att_any_inY3", "att_any_inY4",
1334
- "att_4yr_inY1", "att_4yr_inY2", "att_4yr_inY3", "att_4yr_inY4",
1420
+ "att_any_inY1", "att_any_inY2", "att_any_inY3", "att_any_inY4", "att_any_inY5", "att_any_inY6", "att_any_inY7", "att_any_inY8",
1421
+ "att_4yr_inY1", "att_4yr_inY2", "att_4yr_inY3", "att_4yr_inY4", "att_4yr_inY5", "att_4yr_inY6", "att_4yr_inY7", "att_4yr_inY8",
1335
1422
  "att_2yr_inY1", "att_2yr_inY2", "att_2yr_inY3", "att_2yr_inY4",
1336
1423
  "att_elite_inY1", "att_elite_inY2", "att_elite_inY3", "att_elite_inY4",
1337
1424
 
1338
1425
  "att_any_byY2", "att_any_byY3", "att_any_byY4",
1339
1426
  "att_4yr_byY2", "att_4yr_byY3", "att_4yr_byY4",
1340
1427
  "att_2yr_byY2", "att_2yr_byY3", "att_2yr_byY4",
1341
- "att_elite_byY2", "att_elite_byY3", "att_elite_byY4",
1428
+ "att_elite_byY2", "att_elite_byY3", "att_elite_byY4", "att_elite_byY5", "att_elite_byY6",
1342
1429
 
1343
1430
  "cmp_any_byY4", "cmp_any_byY5", "cmp_any_byY6", "cmp_any_byY7", "cmp_any_byY8",
1344
1431
  "cmp_AA_byY4", "cmp_AA_byY5", "cmp_AA_byY6", "cmp_AA_byY7", "cmp_AA_byY8",
1345
1432
  "cmp_BA_byY4", "cmp_BA_byY5", "cmp_BA_byY6", "cmp_BA_byY7", "cmp_BA_byY8",
1346
1433
 
1347
-
1434
+ "cmp_elite_byY4", "cmp_elite_byY5", "cmp_elite_byY6", "cmp_elite_byY7", "cmp_elite_byY8",
1348
1435
  ]
1349
1436
 
1350
1437
  nsc_outcomes = nsc_outcomes.with_columns(
@@ -1355,6 +1442,11 @@ nsc_outcomes = nsc_outcomes.with_columns(
1355
1442
 
1356
1443
  keep_columns = [
1357
1444
  "sid_cepr",
1445
+ "k_mean",
1446
+ "predicted_completion",
1447
+ "ID_FSC_firstinst",
1448
+ "college_name_firstinst",
1449
+ "completion_rate_150pct_firstinst",
1358
1450
  ] + outcome_columns
1359
1451
 
1360
1452
  nsc_outcomes_final = nsc_outcomes.select(keep_columns)
@@ -1368,6 +1460,25 @@ nsc_outcomes.with_columns(
1368
1460
  print(f"Wrote {OUTCOMES}")
1369
1461
  print(f"Wrote {OUTCOMES.with_suffix('.csv')}")
1370
1462
  print(f"Rows: {nsc_outcomes.height}, columns: {len(nsc_outcomes.columns)}")
1463
+
1464
+ first_college_coverage = (
1465
+ nsc_outcomes.filter(pl.col("ID_FSC_firstinst").is_not_null())
1466
+ .group_by("ID_FSC_firstinst")
1467
+ .agg(
1468
+ pl.col("k_mean_firstinst").is_not_null().any().alias("has_k_mean"),
1469
+ pl.col("completion_rate_150pct_firstinst")
1470
+ .is_not_null()
1471
+ .any()
1472
+ .alias("has_completion_rate"),
1473
+ )
1474
+ )
1475
+ print(
1476
+ "First-college coverage: "
1477
+ f"{first_college_coverage['has_k_mean'].sum()}/"
1478
+ f"{first_college_coverage.height} with college-specific k_mean; "
1479
+ f"{first_college_coverage['has_completion_rate'].sum()}/"
1480
+ f"{first_college_coverage.height} with IPEDS completion rate"
1481
+ )
1371
1482
  print(
1372
1483
  nsc_outcomes.select("sid_cepr", "cohort_lottery", "recovered_nsc_outcome").sort(
1373
1484
  "sid_cepr"
Binary file
Binary file
File without changes