Domain: Human Resources & Employee Management

Use 'student-por.csv'.
You may use **only** `pandas` and `numpy` (no scipy, no hashlib, no statistics). Treat **all columns as strings initially**. Strip whitespace from every cell. Treat the literals `"nan"`, `"none"`, `"null"`, and `""` as missing **case-insensitive after trimming**. Create `row_id` as the original 0-based row index from the file **before any filtering**. Coerce `Dalc, Walc, goout, freetime, studytime, absences, G3, age` to numeric with `pd.to_numeric(errors="coerce")`. Drop rows where `higher` or `internet` is missing after cleaning. Keep only rows where `higher == "yes"` AND `internet == "yes"` (case-sensitive exact match after cleaning). Drop rows where any of `{Dalc, Walc, goout, freetime, studytime}` is missing.

Define `LRS = Dalc + Walc + goout + freetime − studytime` (do not round). Deduplicate by the key `(school, sex, age, address)` using a **stable** sort inside each key with this priority (ascending unless stated):

1. `miss_penalty = (# missing among {absences, G3})` ascending,
2. `absences` descending with missing treated as −∞ (so missing absences sorts last in descending),
3. `row_id` ascending,
   keep the first row per key. Call the resulting table RS and let `n = len(RS)`.

Quantiles must use NumPy **linear**: `np.quantile(x, q, method="linear")` (or `interpolation="linear"` if needed). No intermediate rounding; round final non-integers to **3 decimals**; integers exact.

## Deterministic custom partition (no hashlib)

For each RS row, build:
`token = school + "|" + sex + "|" + address + "|" + str(int(age))` using cleaned string values exactly.

Define custom hash:
`h = sum( (i+1) * ord(ch) for i, ch in enumerate(token) )`
(i starts at 0; `ord` is Unicode code point). Define:

* Group H1: rows where `h % 5` is in `{0,1,2}`
* Group H2: all other rows

Compute `Q80_LRS_H1 = quantile(LRS over H1, 0.80)` and define `A = H1 rows with LRS > Q80_LRS_H1` (strict). Let `m = len(A)`.

Define `Pivot` from `A.absences` (drop missing absences only for this step):

* If `(m % 4) in {0,1}` then `Pivot = median(absences in A)`
* Else `Pivot = mean(absences in A)`

Define `B = A rows with absences >= Pivot` (inclusive; rows with missing absences are excluded automatically by the comparison).

Within B, compute Tukey upper fence for **absences**:
`Q1 = quantile(absences_B, 0.25)`, `Q3 = quantile(absences_B, 0.75)`, `IQR = Q3 − Q1`, `Upper = Q3 + 1.5*IQR`.
Define `C = B rows with absences > Upper` (strict).

Now compute a global scalar on RS:
`S = int(abs(round(sum(LRS in RS), 0))) % 7`
(yes: sum LRS, round to 0 decimals, take abs, cast to int, mod 7).

If `S < 3`, define `FinalSet = C rows with G3 < mean(G3 in RS, ignoring missing)` (strict).
Else define `FinalSet = C rows with G3 <= median(G3 in RS, ignoring missing)` (inclusive).

Compute population z-scores on **RS** (ddof=0; ignore missing by dropping them consistently):
`z_abs = (absences − mean_pop(absences_RS)) / sd_pop(absences_RS)`
`z_g3  = (G3 − mean_pop(G3_RS)) / sd_pop(G3_RS)`
Then compute Pearson correlation between `z_abs` and `z_g3` **only on FinalSet rows** (z-scores must still be based on RS population moments). If correlation is undefined (fewer than 2 rows or zero variance), report `NaN`.

## Required outputs (exactly 7, in order)

1. `n`
2. `|H1|`
3. `m = |A|`
4. `|C|`
5. `|FinalSet|`
6. `Pivot` (3 decimals)
7. `pearson_corr_zabs_zg3_on_FinalSet` (3 decimals or NaN)

Grading rubric:
criterion: The reported n equals 42.
weight: 10
criterion: The reported |H1| equals 26.
weight: 10
criterion: The reported m=|A| equals 2.
weight: 10
criterion: The reported |C| equals 0.
weight: 10
criterion: The reported |FinalSet| equals 0.
weight: 10
criterion: The reported Pivot equals 8.0.
weight: 10
criterion: The reported pearson_corr_zabs_zg3_on_FinalSet is NaN (undefined correlation under the prompt rules).
weight: 10

Golden answer:
```python
import pandas as pd
import numpy as np

PATH = "student-por.csv"

MISSING_LITERALS = {"nan", "none", "null", ""}

# ---------- helpers ----------
def quantile_linear(x, q: float) -> float:
    x = np.asarray(x, dtype=float)
    if x.size == 0:
        return np.nan
    try:
        return float(np.quantile(x, q, method="linear"))
    except TypeError:
        return float(np.quantile(x, q, interpolation="linear"))

def pop_mean_sd(x) -> tuple[float, float]:
    x = np.asarray(x, dtype=float)
    if x.size == 0:
        return (np.nan, np.nan)
    mu = float(np.mean(x))
    sd = float(np.std(x, ddof=0))
    return mu, sd

def safe_median(x) -> float:
    x = np.asarray(x, dtype=float)
    if x.size == 0:
        return np.nan
    return float(np.median(x))

def safe_mean(x) -> float:
    x = np.asarray(x, dtype=float)
    if x.size == 0:
        return np.nan
    return float(np.mean(x))

def pearson_corr(x, y) -> float:
    x = np.asarray(x, dtype=float)
    y = np.asarray(y, dtype=float)
    if x.size < 2 or y.size < 2:
        return np.nan
    if np.std(x, ddof=0) == 0 or np.std(y, ddof=0) == 0:
        return np.nan
    return float(np.corrcoef(x, y)[0, 1])

def r3(v):
    if v is None:
        return None
    if isinstance(v, (int, np.integer)):
        return int(v)
    if isinstance(v, float) and (np.isnan(v) or np.isinf(v)):
        return float("nan")
    return float(np.round(float(v), 3))

# ---------- load as strings ----------
df_raw = pd.read_csv(PATH, dtype=str, keep_default_na=False)
df_raw["row_id"] = np.arange(len(df_raw), dtype=int)  # original 0-based before any filtering

# strip whitespace in every cell
for c in df_raw.columns:
    if df_raw[c].dtype == object:
        df_raw[c] = df_raw[c].astype(str).str.strip()

# convert missing literals to NA (case-insensitive after trim)
def to_missing(series: pd.Series) -> pd.Series:
    s = series.astype(str).str.strip()
    mask = s.str.lower().isin(MISSING_LITERALS)
    return s.mask(mask, pd.NA)

for c in df_raw.columns:
    if df_raw[c].dtype == object:
        df_raw[c] = to_missing(df_raw[c])

# coerce numerics
num_cols = ["Dalc", "Walc", "goout", "freetime", "studytime", "absences", "G3", "age"]
for c in num_cols:
    df_raw[c] = pd.to_numeric(df_raw[c], errors="coerce")

# drop rows where higher or internet missing; then filter exact yes/yes
df = df_raw.dropna(subset=["higher", "internet"])
df = df[(df["higher"] == "yes") & (df["internet"] == "yes")].copy()

# drop rows where any LRS component missing
df = df.dropna(subset=["Dalc", "Walc", "goout", "freetime", "studytime"]).copy()

# LRS (no rounding)
df["LRS"] = df["Dalc"] + df["Walc"] + df["goout"] + df["freetime"] - df["studytime"]

# ---------- deduplicate by (school, sex, age, address) with stable sort ----------
# miss_penalty: missing among {absences, G3}
df["miss_penalty"] = df[["absences", "G3"]].isna().sum(axis=1).astype(int)

# absences sorting: descending, missing treated as -inf (so last when descending)
df["abs_sort"] = df["absences"].astype(float)
df.loc[df["abs_sort"].isna(), "abs_sort"] = -np.inf

key_cols = ["school", "sex", "age", "address"]
sort_cols = key_cols + ["miss_penalty", "abs_sort", "row_id"]
asc = [True, True, True, True, True, False, True]  # abs_sort descending (False)

df_sorted = df.sort_values(sort_cols, ascending=asc, kind="mergesort")
RS = df_sorted.drop_duplicates(subset=key_cols, keep="first").copy()

n = int(len(RS))

# ---------- custom partition ----------
# token = school|sex|address|str(int(age))
# (assumes age is present; student-por has age filled)
token = (
    RS["school"].astype(str) + "|" +
    RS["sex"].astype(str) + "|" +
    RS["address"].astype(str) + "|" +
    RS["age"].astype(int).astype(str)
)

def custom_hash_sum(tok: str) -> int:
    # h = sum((i+1) * ord(ch) for i,ch in enumerate(token))
    return sum((i + 1) * ord(ch) for i, ch in enumerate(tok))

h_vals = token.map(custom_hash_sum).astype(int)
RS["h"] = h_vals

H1_mask = RS["h"].mod(5).isin([0, 1, 2])
H1 = RS[H1_mask].copy()
H2 = RS[~H1_mask].copy()

H1_count = int(len(H1))

# ---------- A from H1 ----------
Q80_LRS_H1 = quantile_linear(H1["LRS"].to_numpy(dtype=float), 0.80)
A = H1[H1["LRS"].to_numpy(dtype=float) > Q80_LRS_H1].copy()
m = int(len(A))

# Pivot from A.absences (drop missing only for this step)
A_abs = A["absences"].dropna().to_numpy(dtype=float)

if (m % 4) in (0, 1):
    Pivot = safe_median(A_abs)
else:
    Pivot = safe_mean(A_abs)

# ---------- B, C ----------
B = A[A["absences"].to_numpy(dtype=float) >= Pivot].copy()

abs_B = B["absences"].to_numpy(dtype=float)
Q1 = quantile_linear(abs_B, 0.25)
Q3 = quantile_linear(abs_B, 0.75)
IQR = Q3 - Q1
Upper = Q3 + 1.5 * IQR

C = B[B["absences"].to_numpy(dtype=float) > Upper].copy()
C_count = int(len(C))

# ---------- global scalar S on RS ----------
S = int(abs(np.round(RS["LRS"].to_numpy(dtype=float).sum(), 0))) % 7

# mean/median G3 in RS ignoring missing
G3_RS = RS["G3"].dropna().to_numpy(dtype=float)
mean_G3_RS = safe_mean(G3_RS)
median_G3_RS = safe_median(G3_RS)

if S < 3:
    FinalSet = C[C["G3"].to_numpy(dtype=float) < mean_G3_RS].copy()
else:
    FinalSet = C[C["G3"].to_numpy(dtype=float) <= median_G3_RS].copy()

Final_count = int(len(FinalSet))

# ---------- z-scores on RS (population moments, ddof=0) ----------
abs_RS = RS["absences"].dropna().to_numpy(dtype=float)
mean_abs, sd_abs = pop_mean_sd(abs_RS)

mean_g3, sd_g3 = pop_mean_sd(G3_RS)

# Create z scores for RS rows (NaN if missing or sd=0)
def z_from_series(series: pd.Series, mu: float, sd: float) -> np.ndarray:
    x = series.to_numpy(dtype=float)
    if np.isnan(sd) or sd == 0:
        return np.full_like(x, np.nan, dtype=float)
    return (x - mu) / sd

RS["z_abs"] = z_from_series(RS["absences"], mean_abs, sd_abs)
RS["z_g3"]  = z_from_series(RS["G3"], mean_g3, sd_g3)

# Pearson correlation ONLY on FinalSet rows, using z from RS
Final_idx = FinalSet.index
zx = RS.loc[Final_idx, "z_abs"].to_numpy(dtype=float)
zy = RS.loc[Final_idx, "z_g3"].to_numpy(dtype=float)

valid = ~np.isnan(zx) & ~np.isnan(zy)
corr = pearson_corr(zx[valid], zy[valid])

# ---------- required outputs (exactly 7, in order) ----------
out = {
    "n": n,
    "|H1|": H1_count,
    "m=|A|": m,
    "|C|": C_count,
    "|FinalSet|": Final_count,
    "Pivot": r3(Pivot),
    "pearson_corr_zabs_zg3_on_FinalSet": r3(corr),
}

print(out)
```

## GTFA

```json
{
  "n": 42,
  "|H1|": 26,
  "m=|A|": 2,
  "|C|": 0,
  "|FinalSet|": 0,
  "Pivot": 8.0,
  "pearson_corr_zabs_zg3_on_FinalSet": NaN
}
```