Week8-1
This commit is contained in:
29 files changed
+362221
No files matched your search
@@ -0,0 +1,134 @@
|
||||
"""Independently reconcile Stata datasets to every supplied Excel source cell."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import sys
|
||||
import math
|
||||
import hashlib
|
||||
import xlrd
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
results = {"method":"xlrd reads original source cells; pandas independently reads Stata dta", "checks":[], "passed":True}
|
||||
|
||||
def check(name, passed, **details):
|
||||
results["checks"].append({"name":name,"passed":bool(passed),**details})
|
||||
if not passed:
|
||||
results["passed"] = False
|
||||
print("FAIL", name, details)
|
||||
|
||||
def compare_frames(name, expected, actual, require_exact_numeric=False):
|
||||
check(name+" row_count",len(expected)==len(actual), expected=len(expected), actual=len(actual))
|
||||
check(name+" key_alignment",expected.index.equals(actual.index))
|
||||
if not expected.index.equals(actual.index):
|
||||
actual = actual.reindex(expected.index)
|
||||
for col in expected.columns:
|
||||
if col not in actual:
|
||||
check(name+" column "+col,False,reason="missing column")
|
||||
continue
|
||||
exp, got = expected[col], actual[col]
|
||||
if pd.api.types.is_datetime64_any_dtype(exp.dtype):
|
||||
ok = ((exp == got) | (exp.isna() & got.isna()))
|
||||
detail = {"kind":"date"}
|
||||
elif pd.api.types.is_numeric_dtype(got.dtype):
|
||||
exp = pd.to_numeric(exp.mask(exp.eq(""), np.nan),errors="raise").astype(float)
|
||||
got = got.astype(float)
|
||||
exact = (exp == got) | (exp.isna() & got.isna())
|
||||
ok = exact if require_exact_numeric else np.isclose(exp.to_numpy(),got.to_numpy(),rtol=1e-13,atol=1e-13,equal_nan=True)
|
||||
delta = np.abs(exp-got)
|
||||
detail = {"kind":"numeric","exact_equal_cells":int(exact.sum()),"missing_source":int(exp.isna().sum()),"missing_output":int(got.isna().sum()),"max_absolute_difference":float(delta.max()) if delta.notna().any() else 0}
|
||||
else:
|
||||
exp = exp.fillna("").astype(str)
|
||||
got = got.fillna("").astype(str)
|
||||
ok = exp == got
|
||||
detail = {"kind":"text","max_source_characters":int(exp.str.len().max()),"max_output_characters":int(got.str.len().max())}
|
||||
ok = np.asarray(ok)
|
||||
detail["cells_compared"] = int(len(exp))
|
||||
detail["mismatches"] = int((~ok).sum())
|
||||
if not ok.all():
|
||||
bad = np.flatnonzero(~ok)[:5]
|
||||
detail["examples"] = [{"key":str(exp.index[i]),"source":str(exp.iloc[i]),"output":str(got.iloc[i])} for i in bad]
|
||||
check(name+" column "+col,ok.all(),**detail)
|
||||
|
||||
financial_file = next((ROOT/"data").glob("FIN*.xls"))
|
||||
w=xlrd.open_workbook(financial_file)
|
||||
s=w.sheet_by_index(0)
|
||||
financial_cols=["Comcd","Lcomnm","A_Stkcd","B_Stkcd","H_Stkcd","Lstflg","Enddt","Reporttype","ROA","ROE"]
|
||||
financial=pd.DataFrame([s.row_values(i) for i in range(1,s.nrows)],columns=financial_cols)
|
||||
financial["Enddt"]=pd.to_datetime(financial.Enddt.map(lambda x:xlrd.xldate_as_datetime(x,w.datemode)))
|
||||
financial["resset_excel_row"]=np.arange(2,s.nrows+1)
|
||||
financial=financial.set_index("resset_excel_row").sort_index()
|
||||
fin_dta=pd.read_stata(ROOT/"resset_financial_all_years.dta",convert_categoricals=False)
|
||||
compare_frames("financial_source_vs_clean",financial,fin_dta.set_index("resset_excel_row").sort_index())
|
||||
|
||||
govs=[]
|
||||
source_hashes={financial_file.name:hashlib.sha256(financial_file.read_bytes()).hexdigest()}
|
||||
for f in sorted((ROOT/"data").glob("CG*.xls")):
|
||||
w=xlrd.open_workbook(f)
|
||||
s=w.sheet_by_index(0)
|
||||
df=pd.DataFrame([s.row_values(i) for i in range(3,s.nrows)],columns=s.row_values(0))
|
||||
df=df.rename(columns={"Stkcd":"stkcd"})
|
||||
df["csmar_source"]=f.name
|
||||
df["csmar_excel_row"]=np.arange(4,s.nrows+1)
|
||||
govs.append(df)
|
||||
source_hashes[f.name]=hashlib.sha256(f.read_bytes()).hexdigest()
|
||||
governance=pd.concat(govs,ignore_index=True).set_index(["csmar_source","csmar_excel_row"]).sort_index()
|
||||
gov_dta=pd.read_stata(ROOT/"csmar_governance_1999_2014.dta",convert_categoricals=False)
|
||||
compare_frames("governance_source_vs_clean",governance,gov_dta.set_index(["csmar_source","csmar_excel_row"]).sort_index())
|
||||
|
||||
merged=pd.read_stata(ROOT/"financial_governance_all_years.dta",convert_categoricals=False)
|
||||
check("merged_key_unique",not merged.duplicated(["stkcd","year"]).any())
|
||||
compare_frames("financial_source_vs_merged",financial,merged.loc[merged.in_resset==1].set_index("resset_excel_row").sort_index())
|
||||
compare_frames("governance_source_vs_merged",governance,merged.loc[merged.in_csmar==1].set_index(["csmar_source","csmar_excel_row"]).sort_index())
|
||||
|
||||
expected_fin=financial.reset_index()[["A_Stkcd","Enddt"]].rename(columns={"A_Stkcd":"stkcd"})
|
||||
expected_fin["year"]=expected_fin.Enddt.dt.year
|
||||
expected_gov=governance.reset_index()[["stkcd","Reptdt"]]
|
||||
expected_gov["year"]=expected_gov.Reptdt.str[:4].astype(int)
|
||||
expected_merge=expected_fin.merge(expected_gov,on=["stkcd","year"],how="outer",validate="1:1",indicator=True)
|
||||
status_map={"left_only":1,"right_only":2,"both":3}
|
||||
expected_merge["merge_status"]=expected_merge._merge.astype(str).map(status_map)
|
||||
expected_merge=expected_merge.set_index(["stkcd","year"]).sort_index()
|
||||
actual_merge=merged.set_index(["stkcd","year"]).sort_index()
|
||||
check("merge_keyset",expected_merge.index.equals(actual_merge.index))
|
||||
check("merge_status_every_row",np.array_equal(expected_merge.merge_status.to_numpy(),actual_merge._merge.to_numpy()))
|
||||
check("in_resset_flag",np.array_equal(merged.in_resset.to_numpy(),merged._merge.isin([1,3]).astype(int).to_numpy()))
|
||||
check("in_csmar_flag",np.array_equal(merged.in_csmar.to_numpy(),merged._merge.isin([2,3]).astype(int).to_numpy()))
|
||||
check("matched_flag",np.array_equal(merged.matched.to_numpy(),(merged._merge==3).astype(int).to_numpy()))
|
||||
check("complete_roa_roe_flag",np.array_equal(merged.complete_roa_roe.to_numpy(),((merged._merge==3)&merged.ROA.notna()&merged.ROE.notna()).astype(int).to_numpy()))
|
||||
check("unusual_code_retained",merged.loc[merged.code_format_exception==1,["stkcd","year"]].to_dict(orient="records")==[{"stkcd":"0991","year":1998},{"stkcd":"0991","year":1999}])
|
||||
|
||||
expected_annual=expected_merge.reset_index().groupby(["year","merge_status"]).size().unstack(fill_value=0)
|
||||
actual_annual=merged.groupby(["year","_merge"]).size().unstack(fill_value=0)
|
||||
check("annual_merge_counts",expected_annual.equals(actual_annual))
|
||||
|
||||
def verify_csv_against_dta(stem):
|
||||
dta=pd.read_stata(ROOT/(stem+".dta"),convert_categoricals=False)
|
||||
numeric=[col for col in dta if pd.api.types.is_numeric_dtype(dta[col].dtype)]
|
||||
dates=[col for col in dta if pd.api.types.is_datetime64_any_dtype(dta[col].dtype)]
|
||||
types={col:(float if col in numeric else str) for col in dta}
|
||||
csv=pd.read_csv(ROOT/(stem+".csv"),dtype=types,keep_default_na=False,
|
||||
na_values={col:[""] for col in numeric},float_precision="round_trip",encoding="utf-8")
|
||||
check(stem+" CSV column_names_order",list(dta.columns)==list(csv.columns),column_count=len(csv.columns))
|
||||
for col in dates:
|
||||
date_strings=csv[col]
|
||||
valid=date_strings.eq("") | date_strings.str.fullmatch(r"\d{4}-\d{2}-\d{2}")
|
||||
check(stem+" CSV ISO date "+col,valid.all())
|
||||
csv[col]=pd.to_datetime(date_strings.mask(date_strings.eq("")),format="%Y-%m-%d",errors="raise")
|
||||
compare_frames(stem+" DTA_vs_CSV",dta,csv,require_exact_numeric=True)
|
||||
return {"rows":len(csv),"columns":len(csv.columns),"numeric_columns":len(numeric),
|
||||
"date_columns":dates,"cells_compared":int(csv.size),"numeric_rule":"17-digit CSV parsed with float_precision=round_trip; exact equality including missing values"}
|
||||
|
||||
success_marker="WEEK8_ANALYSIS_COMPLETED_SUCCESSFULLY"
|
||||
check("Stata_success_log",success_marker in (ROOT/"week8_stata.log").read_text(encoding="utf-8"))
|
||||
results["csv_validation"]={stem:verify_csv_against_dta(stem) for stem in ["financial_governance_all_years","df_example_metrics"]}
|
||||
results["annual_counts"]={str(y):{str(k):int(v) for k,v in row.items()} for y,row in actual_annual.to_dict(orient="index").items()}
|
||||
results["summary"]={"financial_rows":len(financial),"governance_rows":len(governance),"merged_rows":len(merged),"merge_counts":{str(k):int(v) for k,v in merged._merge.value_counts().sort_index().items()},"financial_cells_compared_per_dataset":int(financial.size),"governance_cells_compared_per_dataset":int(governance.size),"matched_complete_roa_roe":int(merged.complete_roa_roe.sum()),"matched_missing_roa":int(merged.loc[merged.matched==1,"ROA"].isna().sum()),"matched_missing_roe":int(merged.loc[merged.matched==1,"ROE"].isna().sum())}
|
||||
results["source_sha256"]=source_hashes
|
||||
results["check_count"]=len(results["checks"])
|
||||
results["summary"]["all_cells_compared"]=sum(x.get("cells_compared",0) for x in results["checks"])
|
||||
(ROOT/"validation_results.json").write_text(json.dumps(results,ensure_ascii=False,indent=2,allow_nan=False),encoding="utf-8")
|
||||
print(json.dumps({"passed":results["passed"],"check_count":results["check_count"],"summary":results["summary"]},ensure_ascii=False,indent=2))
|
||||
if not results["passed"]:
|
||||
raise SystemExit(1)
|
||||
Reference in new issue
Block a user