ltc-code 0.2.27__tar.gz → 0.2.28__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 (71) hide show
  1. {ltc_code-0.2.27 → ltc_code-0.2.28}/PKG-INFO +1 -1
  2. {ltc_code-0.2.27 → ltc_code-0.2.28}/pyproject.toml +1 -1
  3. {ltc_code-0.2.27 → ltc_code-0.2.28}/pyproject.toml.orig +1 -1
  4. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/build_nsc_outcomes.py +62 -15
  5. ltc_code-0.2.28/src/ltc_code/plot_bars.py +84 -0
  6. {ltc_code-0.2.27 → ltc_code-0.2.28}/README.md +0 -0
  7. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/aspire.py +0 -0
  8. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/check_cmo_apps.do +0 -0
  9. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/christel_house.py +0 -0
  10. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/democracy_prep.py +0 -0
  11. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/green_dot.py +0 -0
  12. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/helpers.py +0 -0
  13. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/ilt.py +0 -0
  14. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/kipp_nj.py +0 -0
  15. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/main.py +0 -0
  16. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/mappings.py +0 -0
  17. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/rocketship.py +0 -0
  18. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260614_new_build_scripts_update/yes_prep.py +0 -0
  19. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260630_census_disclosure/aspire.py +0 -0
  20. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260630_census_disclosure/christel_house.py +0 -0
  21. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260630_census_disclosure/democracy_prep.py +0 -0
  22. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260630_census_disclosure/green_dot.py +0 -0
  23. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260630_census_disclosure/ilt.py +0 -0
  24. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260630_census_disclosure/kipp.py +0 -0
  25. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260630_census_disclosure/kipp_nj.py +0 -0
  26. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260630_census_disclosure/rocketship.py +0 -0
  27. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260630_census_disclosure/yes_prep.py +0 -0
  28. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260706_ceprscripts/BALANCE.do +0 -0
  29. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260706_ceprscripts/FS.do +0 -0
  30. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260706_ceprscripts/ITT.do +0 -0
  31. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260706_ceprscripts/TOT.do +0 -0
  32. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260706_ceprscripts/apps_helpers.py +0 -0
  33. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260706_ceprscripts/harmony.py +0 -0
  34. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260706_ceprscripts/main.py +0 -0
  35. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260712_uncommon_scripts/main.py +0 -0
  36. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260712_uncommon_scripts/mappings.py +0 -0
  37. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/20260712_uncommon_scripts/uncommon.py +0 -0
  38. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/__init__.py +0 -0
  39. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/aspire.py +0 -0
  40. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/check_cmo_apps.do +0 -0
  41. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/christel_house.py +0 -0
  42. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/green_dot.py +0 -0
  43. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/helpers.py +0 -0
  44. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/june13.py +0 -0
  45. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/june2.py +0 -0
  46. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/june30.py +0 -0
  47. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/june5.py +0 -0
  48. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/june7.py +0 -0
  49. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/kipp_nj.py +0 -0
  50. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/kipp_tx.py +0 -0
  51. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/main.py +0 -0
  52. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/make_summary_stats_table.py +0 -0
  53. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/mappings.py +0 -0
  54. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/may27.py +0 -0
  55. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/__init__.py +0 -0
  56. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/dhs_stem/dhs_stem_cip_additions_2024.csv +0 -0
  57. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/dhs_stem/extract_dhs_stem_cips.R +0 -0
  58. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/dhs_stem/stemList2024.pdf +0 -0
  59. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/input/.gitkeep +0 -0
  60. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/naics.py +0 -0
  61. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/output/.gitkeep +0 -0
  62. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/raw/ipeds_data.dta +0 -0
  63. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/nsc/run_nsc_outcomes.py +0 -0
  64. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/polars_dates.py +0 -0
  65. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/rocketship.py +0 -0
  66. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/schema_mapping.py +0 -0
  67. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/school_name_xwalk/__init__.py +0 -0
  68. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/school_name_xwalk/all_schools_with_ccd.csv +0 -0
  69. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/school_name_xwalk/merge_school_ccd.py +0 -0
  70. {ltc_code-0.2.27 → ltc_code-0.2.28}/src/ltc_code/signal_var_calcs.py +0 -0
  71. {ltc_code-0.2.27 → ltc_code-0.2.28}/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.27
3
+ Version: 0.2.28
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.27"
3
+ version = "0.2.28"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ltc-code"
3
- version = "0.2.27"
3
+ version = "0.2.28"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -1198,9 +1198,10 @@ for year in range(1, N_YEARS_OUT + 1):
1198
1198
  [
1199
1199
  (
1200
1200
  pl.col("_overlaps")
1201
- * pl.col(f"college_{college_type}")
1202
- .fill_null(0)
1203
- .cast(pl.Int8)
1201
+ * (
1202
+ pl.lit(1) if college_type == "any"
1203
+ else pl.col(f"college_{college_type}").fill_null(0).cast(pl.Int8)
1204
+ )
1204
1205
  )
1205
1206
  .max()
1206
1207
  .alias(f"att_{college_type}_{label}")
@@ -1258,9 +1259,10 @@ for age in AGE_ATTENDANCE_RANGE:
1258
1259
  [
1259
1260
  (
1260
1261
  pl.col("_overlaps")
1261
- * pl.col(f"college_{college_type}")
1262
- .fill_null(0)
1263
- .cast(pl.Int8)
1262
+ * (
1263
+ pl.lit(1) if college_type == "any"
1264
+ else pl.col(f"college_{college_type}").fill_null(0).cast(pl.Int8)
1265
+ )
1264
1266
  )
1265
1267
  .max()
1266
1268
  .alias(f"att_{college_type}_{age}")
@@ -1878,6 +1880,54 @@ nsc_outcomes = nsc_outcomes.with_columns(
1878
1880
  .alias("adj_cmp_rate_2yr"),
1879
1881
  )
1880
1882
 
1883
+ # Separate the fully coarse prediction by first-institution sector.
1884
+ nsc_outcomes = nsc_outcomes.with_columns(
1885
+ pl.when(
1886
+ pl.col("adj_cmp_rate_coarse").is_not_null()
1887
+ & pl.col("college_years_firstinst").is_in([1, 2])
1888
+ )
1889
+ .then(0.0)
1890
+ .otherwise(pl.col("adj_cmp_rate_coarse"))
1891
+ .alias("adj_cmp_rate_coarse_4yr"),
1892
+ pl.when(
1893
+ pl.col("adj_cmp_rate_coarse").is_not_null()
1894
+ & (pl.col("college_years_firstinst") == 4)
1895
+ )
1896
+ .then(0.0)
1897
+ .otherwise(pl.col("adj_cmp_rate_coarse"))
1898
+ .alias("adj_cmp_rate_coarse_2yr"),
1899
+ )
1900
+
1901
+ # Give each IPEDS institution one tier, excluding missing crosswalk tiers.
1902
+ ipeds_tiers = college_ref.select("unitid", "tier").drop_nulls().unique()
1903
+ ipeds_tier_completion = ipeds_completion_with_sector.join(
1904
+ ipeds_tiers, on="unitid", how="left", validate="1:1"
1905
+ ).filter(pl.col("college_sector") == "4yr")
1906
+
1907
+ # Coarsen one bucket at a time, always starting from the detailed prediction.
1908
+ tier_buckets = {"t12": [1, 2], "t34": [3, 4], "t58": [5, 6, 7, 8]}
1909
+ for label, tiers in tier_buckets.items():
1910
+ bucket_rate = (
1911
+ ipeds_tier_completion.filter(pl.col("tier").is_in(tiers))
1912
+ .select(
1913
+ pl.col("completers_150pct_ip").sum()
1914
+ / pl.col("cohort_adj_150pct_ip").sum()
1915
+ )
1916
+ .item()
1917
+ )
1918
+ nsc_outcomes = nsc_outcomes.with_columns(
1919
+ pl.when(
1920
+ (pl.col("college_years_firstinst") == 4)
1921
+ & pl.col("tier_firstinst").is_in(tiers)
1922
+ & (pl.col("att_any_byY4") == 1)
1923
+ & pl.col("adj_cmp_rate_4yr").is_not_null()
1924
+ )
1925
+ .then(bucket_rate)
1926
+ .otherwise(pl.col("adj_cmp_rate_4yr"))
1927
+ .alias(f"adj_cmp_rate_4yr_coarse_{label}")
1928
+ )
1929
+ print(f"Four-year IPEDS-cohort-weighted completion rate, {label}: {bucket_rate}")
1930
+
1881
1931
  # Match the available 1098-T calendar years, including the missing 2015 year.
1882
1932
  tax_years = list(range(2011, 2015)) + list(range(2016, 2023))
1883
1933
  nsc_outcomes = nsc_outcomes.with_columns(
@@ -1888,6 +1938,7 @@ nsc_outcomes = nsc_outcomes.with_columns(
1888
1938
  .alias(f"att_any_1098_{age}")
1889
1939
  for age in AGE_ATTENDANCE_RANGE
1890
1940
  ]
1941
+
1891
1942
  )
1892
1943
 
1893
1944
 
@@ -1966,6 +2017,9 @@ keep_columns = [
1966
2017
  "adj_cmp_rate",
1967
2018
  "adj_cmp_rate_2yr",
1968
2019
  "adj_cmp_rate_4yr",
2020
+ "adj_cmp_rate_coarse_2yr",
2021
+ "adj_cmp_rate_coarse_4yr",
2022
+ *[f"adj_cmp_rate_4yr_coarse_{label}" for label in tier_buckets],
1969
2023
  "adj_cmp_rate_coarse",
1970
2024
  "adj_cmp_rate_coarse_sample",
1971
2025
  "adj_cmp_rate_coarsen_4yr",
@@ -2046,15 +2100,8 @@ coarse_audit = observable_y4.select(
2046
2100
 
2047
2101
  if coarse_audit.item(0, "k_mean_coarse_missing") > 0:
2048
2102
  raise ValueError("k_mean_coarse is unexpectedly missing for observable students.")
2049
- if coarse_audit.item(0, "adj_cmp_rate_coarse_missing") > 0:
2050
- raise ValueError(
2051
- "adj_cmp_rate_coarse is unexpectedly missing for observable students."
2052
- )
2053
- if coarse_audit.item(0, "adj_cmp_rate_coarse_sample_missing") > 0:
2054
- raise ValueError(
2055
- "adj_cmp_rate_coarse_sample is unexpectedly missing for observable students."
2056
- )
2057
-
2103
+ # Attendees without a classified first college can have missing coarse rates.
2104
+ # Report those counts below rather than treating them as a build failure.
2058
2105
  print("Coarse outcome audit:")
2059
2106
  print(coarse_audit)
2060
2107
 
@@ -0,0 +1,84 @@
1
+ # --- Import necessary packages ---
2
+ # Required libraries: polars (input DataFrame), pandas, plotnine, oi-tools.
3
+ # Install in your analysis project: uv add polars pandas plotnine oi-tools
4
+ from typing import Optional
5
+ import pandas as pd
6
+ from oi_tools.figures import theme_oi
7
+ from plotnine import (
8
+ aes, annotate, coord_cartesian, geom_col, geom_errorbar, geom_text,
9
+ geom_vline, ggplot, labs, scale_x_continuous, theme,
10
+ )
11
+
12
+
13
+ def plot_bars(
14
+ df,
15
+ y_col: str,
16
+ plot_label: str,
17
+ which_control: str = "ccm",
18
+ scale: float = 1,
19
+ sample_col: Optional[str] = None,
20
+ ):
21
+ """Use the first row, or one pair per row when sample_col is provided.
22
+
23
+ df is a Polars DataFrame containing ccm (or which_control), coef, se,
24
+ and N_ppl. scale=1/1000 adds K; scale=100 adds %; otherwise no suffix.
25
+ y_col is the axis title; sample_col may label samples or outcomes.
26
+ The treated interval is control + coef +/- 1.96 * se, as before.
27
+ """
28
+ suffix = "K" if scale == 1 / 1000 else "%" if scale == 100 else ""
29
+ n_samples = df.height if sample_col is not None else 1
30
+ frames, notes = [], []
31
+
32
+ for i in range(n_samples):
33
+ control = df.item(i, which_control) * scale
34
+ effect = df.item(i, "coef") * scale
35
+ se = df.item(i, "se") * abs(scale)
36
+ n_ppl = df.item(i, "N_ppl") / 1000
37
+
38
+ frames.append(pd.DataFrame({
39
+ "group": ["Control", "Treated"],
40
+ "x": [3 * i + 1, 3 * i + 2],
41
+ "value": [control, control + effect],
42
+ "ci_lo": [None, control + effect - 1.96 * se],
43
+ "ci_hi": [None, control + effect + 1.96 * se],
44
+ "label": [f"{control:.1f}{suffix}",
45
+ f"{control + effect:.1f}{suffix}"],
46
+ }))
47
+ sample = f"{df.item(i, sample_col)}\n" if sample_col is not None else ""
48
+ notes.append({
49
+ "x": 3 * i + 1.5,
50
+ "label": (f"{sample}Delta = {effect:.1f}{suffix} (se = {se:.1f}{suffix})"
51
+ f"\n(N_ppl = {n_ppl:.1f}K)"),
52
+ })
53
+
54
+ plot_data = pd.concat(frames, ignore_index=True)
55
+ treated = plot_data.iloc[1::2]
56
+ y_min = min(0.5 * plot_data["value"].min(), treated["ci_lo"].min(),
57
+ plot_data["value"].min())
58
+ top = max(plot_data["value"].max(), treated["ci_hi"].max())
59
+ span = max(top - y_min, 1e-6)
60
+ y_max = top + 0.35 * span
61
+ plot_data["label_y"] = plot_data[["value", "ci_hi"]].max(axis=1) + 0.025 * span
62
+
63
+ p = (
64
+ ggplot(plot_data, aes(x="x", y="value"))
65
+ + theme_oi()
66
+ + geom_col(width=0.5, fill="#F8766D")
67
+ + geom_errorbar(
68
+ data=treated, mapping=aes(ymin="ci_lo", ymax="ci_hi"), width=0.15,
69
+ )
70
+ + labs(title=plot_label, x="", y=y_col)
71
+ + geom_text(aes(y="label_y", label="label"), va="bottom", size=10)
72
+ + scale_x_continuous(breaks=plot_data["x"].tolist(),
73
+ labels=plot_data["group"].tolist())
74
+ + coord_cartesian(ylim=(y_min, y_max))
75
+ + theme(figure_size=(max(6, 3 * n_samples), 5.5))
76
+ )
77
+ for note in notes:
78
+ p += annotate("text", x=note["x"], y=top + 0.23 * span,
79
+ label=note["label"], size=10, fontweight="bold")
80
+ if n_samples > 1:
81
+ p += geom_vline(xintercept=[3 * i for i in range(1, n_samples)],
82
+ linetype="dashed", color="grey", size=0.5)
83
+
84
+ return p
File without changes