naics_embeddings / training /scripts /prepare_data.py
Joseph Warth
updated to flat embedding
a6067aa
from pathlib import Path
import pandas as pd
from sklearn.model_selection import train_test_split
RANDOM_STATE = 42
VALID_SIZE = 0.15
TEST_SIZE = 0.15
NAICS_SAMPLE_N = 5000
def clean_text(x):
if pd.isna(x):
return None
x = str(x).strip()
if x == "":
return None
return x
def clean_naics_2022(x):
if pd.isna(x):
return None
x = str(x)
x = "".join(ch for ch in x if ch.isdigit())
if x == "":
return None
x = x.zfill(6)
if len(x) != 6:
return None
return x
def split_one_class(group, valid_size=VALID_SIZE, test_size=TEST_SIZE, random_state=RANDOM_STATE):
n = len(group)
if n <= 2:
return group, group.iloc[0:0].copy(), group.iloc[0:0].copy()
if 3 <= n <= 5:
train_part, valid_part = train_test_split(
group,
test_size=valid_size,
random_state=random_state
)
return train_part, valid_part, group.iloc[0:0].copy()
train_valid_part, test_part = train_test_split(
group,
test_size=test_size,
random_state=random_state
)
valid_share_of_train_valid = valid_size / (1.0 - test_size)
train_part, valid_part = train_test_split(
train_valid_part,
test_size=valid_share_of_train_valid,
random_state=random_state
)
return train_part, valid_part, test_part
def prep_exio_df(df):
keep_cols = [
"Company Name",
"Company Description",
"2022 NAICS Code",
"2022 NAICS Title",
]
existing_keep_cols = [c for c in keep_cols if c in df.columns]
df = df[existing_keep_cols].copy()
rename_map = {
"Company Name": "company_name",
"Company Description": "company_description",
"2022 NAICS Code": "naics_2022",
"2022 NAICS Title": "naics_2022_title",
}
df = df.rename(columns={k: v for k, v in rename_map.items() if k in df.columns})
if "company_name" not in df.columns:
df["company_name"] = None
if "naics_2022_title" not in df.columns:
df["naics_2022_title"] = None
df["company_description"] = df["company_description"].apply(clean_text)
df["naics_2022"] = df["naics_2022"].apply(clean_naics_2022)
df["company_name"] = df["company_name"].apply(
lambda x: None if pd.isna(x) else str(x).strip()
)
df["naics_2022_title"] = df["naics_2022_title"].apply(
lambda x: None if pd.isna(x) else str(x).strip()
)
df["data_source"] = "exionaics"
return df[[
"company_name",
"company_description",
"naics_2022",
"naics_2022_title",
"data_source",
]].copy()
def prep_supplement_df(df):
keep_cols = ["naics_code", "naics_title", "naics_description"]
existing_keep_cols = [c for c in keep_cols if c in df.columns]
df = df[existing_keep_cols].copy()
rename_map = {
"naics_code": "naics_2022",
"naics_title": "naics_2022_title",
"naics_description": "company_description",
}
df = df.rename(columns=rename_map)
df["company_name"] = None
df["company_description"] = df["company_description"].apply(clean_text)
df["naics_2022"] = df["naics_2022"].apply(clean_naics_2022)
df["naics_2022_title"] = df["naics_2022_title"].apply(
lambda x: None if pd.isna(x) else str(x).strip()
)
# only true full 6-digit codes
df = df.dropna(subset=["company_description", "naics_2022"]).copy()
df = df[df["naics_2022"].str.fullmatch(r"\d{6}")].copy()
df["data_source"] = "naics_supplement"
return df[[
"company_name",
"company_description",
"naics_2022",
"naics_2022_title",
"data_source",
]].copy()
def main():
project_dir = Path(__file__).resolve().parents[2]
raw_dir = project_dir / "data" / "raw"
exio_path = raw_dir / "exionaics_raw.csv"
supplement_path = raw_dir / "2022_naics_supplemental.xlsx"
interim_dir = project_dir / "data" / "interim"
interim_dir.mkdir(parents=True, exist_ok=True)
exio_df = pd.read_csv(exio_path)
exio_df = prep_exio_df(exio_df)
exio_df = exio_df.dropna(subset=["company_description", "naics_2022"]).copy()
supplement_sample = pd.DataFrame(columns=exio_df.columns)
if NAICS_SAMPLE_N > 0 and supplement_path.exists():
supplement_df = pd.read_excel(supplement_path)
supplement_df = prep_supplement_df(supplement_df)
sample_n = min(NAICS_SAMPLE_N, len(supplement_df))
supplement_sample = supplement_df.sample(
n=NAICS_SAMPLE_N,
replace=True,
random_state=RANDOM_STATE
).copy()
df = pd.concat([exio_df, supplement_sample], ignore_index=True)
else:
df = exio_df.copy()
df["y2"] = df["naics_2022"].str[:2]
df["y3"] = df["naics_2022"].str[:3]
df["y4"] = df["naics_2022"].str[:4]
df["y5"] = df["naics_2022"].str[:5]
df["y6"] = df["naics_2022"]
df = df.drop_duplicates(subset=["company_description", "naics_2022"]).copy()
df = df.sample(frac=1, random_state=RANDOM_STATE).reset_index(drop=True)
class_counts = df["y6"].value_counts().sort_index()
train_parts = []
valid_parts = []
test_parts = []
for y6_value, group in df.groupby("y6", sort=True):
train_part, valid_part, test_part = split_one_class(group)
train_parts.append(train_part)
valid_parts.append(valid_part)
test_parts.append(test_part)
train_df = pd.concat(train_parts, axis=0).sample(frac=1, random_state=RANDOM_STATE).reset_index(drop=True)
valid_df = pd.concat(valid_parts, axis=0).sample(frac=1, random_state=RANDOM_STATE).reset_index(drop=True)
test_df = pd.concat(test_parts, axis=0).sample(frac=1, random_state=RANDOM_STATE).reset_index(drop=True)
train_y6 = set(train_df["y6"].unique())
valid_y6 = set(valid_df["y6"].unique())
test_y6 = set(test_df["y6"].unique())
missing_valid = sorted(valid_y6 - train_y6)
missing_test = sorted(test_y6 - train_y6)
if missing_valid:
raise ValueError(f"Validation contains y6 classes not in training: {missing_valid[:10]}")
if missing_test:
raise ValueError(f"Test contains y6 classes not in training: {missing_test[:10]}")
cleaned_path = interim_dir / "exionaics_2022_clean.csv"
train_path = interim_dir / "train.csv"
valid_path = interim_dir / "valid.csv"
test_path = interim_dir / "test.csv"
counts_path = interim_dir / "y6_class_counts.csv"
df.to_csv(cleaned_path, index=False)
train_df.to_csv(train_path, index=False)
valid_df.to_csv(valid_path, index=False)
test_df.to_csv(test_path, index=False)
class_counts.rename_axis("y6").reset_index(name="count").to_csv(counts_path, index=False)
print(f"Cleaned full dataset saved to: {cleaned_path}")
print(f"Train saved to: {train_path}")
print(f"Valid saved to: {valid_path}")
print(f"Test saved to: {test_path}")
print(f"Class counts saved to: {counts_path}")
print("\nShapes:")
print(f"ExioNAICS rows: {exio_df.shape}")
print(f"Supplement sampled rows: {supplement_sample.shape}")
print(f"Full: {df.shape}")
print(f"Train: {train_df.shape}")
print(f"Valid: {valid_df.shape}")
print(f"Test: {test_df.shape}")
print("\nUnique y6 counts:")
print(f"Full: {df['y6'].nunique()}")
print(f"Train: {train_df['y6'].nunique()}")
print(f"Valid: {valid_df['y6'].nunique()}")
print(f"Test: {test_df['y6'].nunique()}")
print("\nOverlap checks:")
print(f"Valid y6 missing from train: {len(missing_valid)}")
print(f"Test y6 missing from train: {len(missing_test)}")
print("\nData source counts:")
print(df["data_source"].value_counts(dropna=False))
print("\nSample rows:")
print(train_df[[
"company_description", "naics_2022", "y2", "y3", "y4", "y5", "y6", "data_source"
]].head())
if __name__ == "__main__":
main()