โก ๊ฐ๊ตฌ๊ณ ์ ํจ๊ณผ(within) ๋ถ์ + 2020 ์ข ๋์ธ ์์ ์ DiDยถ
ํตํฉ ๊ฐ๊ตฌํ์ผ NaSTaBHW.csv(์ง์ ๊ฐ๊ตฌID hid ร 17์ฐจ wide). 2์ ๊ณ ์ ํจ๊ณผ(๊ฐ๊ตฌ+์ฐ๋)๋ก within ์๋-์ฃผ๋ฅ ๊ด๊ณ๋ฅผ ์๋ณ.
Inย [1]:
import pandas as pd, numpy as np, csv, os
import statsmodels.formula.api as smf
D=r"D:\Project\2026_2\์ฃผ๋ฅ์ธ\data\nastab"; INT=os.path.join(D,"NaSTaBHW.csv")
MISS={-1,-9,-8,-99,-999,999999}
with open(INT,encoding="utf-8-sig") as f: hdr=set(next(csv.reader(f)))
need=["hid"]; percols={}
for w in range(1,18):
ww=f"{w:02d}"
d={"con":f"h{ww}ca002","inc":f"h{ww}ca004","alc1":f"h{ww}cd002","alc2":f"h{ww}cd004",
"wt":f"h{ww}cwt","wine":f"h{ww}cd006","mak":f"h{ww}cd008"}
d={k:v for k,v in d.items() if v in hdr}; percols[w]=d; need+=list(d.values())
df=pd.read_csv(INT,usecols=need,encoding="utf-8-sig",low_memory=False)
print("๊ฐ๊ตฌ์:",len(df),"| ์ฝ์ ์ด:",len(need))
long ํจ๋ ๊ตฌ์ฑ (๊ฐ๊ตฌร์ฐจ์)ยถ
Inย [2]:
def clean(s):
s=pd.to_numeric(s,errors="coerce"); return s.where(~s.isin(MISS))
recs=[]
for w in range(1,18):
c=percols[w]
if "inc" not in c or "con" not in c: continue
sub=pd.DataFrame({"hid":df["hid"]}); sub["wave"]=w; sub["year"]=2006+w
sub["con"]=clean(df[c["con"]]).where(lambda x:x>0)
sub["inc"]=clean(df[c["inc"]]).where(lambda x:x>0)
sub["alc"]=clean(df[c["alc1"]]).fillna(0)+clean(df[c["alc2"]]).fillna(0)
sub["wt"]=clean(df[c["wt"]]) if "wt" in c else 1.0
sub["wine"]=clean(df[c["wine"]]).fillna(0) if "wine" in c else np.nan
sub["mak"]=clean(df[c["mak"]]).fillna(0) if "mak" in c else np.nan
recs.append(sub)
p=pd.concat(recs,ignore_index=True)
p=p[p["inc"].notna()&p["con"].notna()&p["wt"].notna()&(p["wt"]>0)]
p["share"]=p["alc"]/p["con"]*100; p["linc"]=np.log(p["inc"]); p["lincsq"]=p["linc"]**2
nobs=p.groupby("hid").size(); p=p[p["hid"].isin(nobs[nobs>=2].index)]
print(f"๊ด์ธก {len(p):,} ยท ๊ฐ๊ตฌ {p['hid'].nunique():,} ยท ํ๊ท {len(p)/p['hid'].nunique():.1f}ํ")
๊ฐ๊ตฌ within ๋๋ฏผ ํ 2์ ๊ณ ์ ํจ๊ณผ ํ๊ท (๊ฐ๊ตฌํด๋ฌ์คํฐ SE)ยถ
Inย [3]:
g=p.groupby("hid")
for c in ["share","linc","lincsq"]: p[c+"_w"]=p[c]-g[c].transform("mean")
m1=smf.ols("share_w ~ linc_w + C(year)",data=p).fit(cov_type="cluster",cov_kwds={"groups":p["hid"]})
m2=smf.ols("share_w ~ linc_w + lincsq_w + C(year)",data=p).fit(cov_type="cluster",cov_kwds={"groups":p["hid"]})
print(f"[์ ํ] log์๋ = {m1.params['linc_w']:+.4f} (p{m1.pvalues['linc_w']:.1e})")
print(f"[2์ฐจ] log์๋={m2.params['linc_w']:+.3f}(p{m2.pvalues['linc_w']:.2g}), (log์๋)^2={m2.params['lincsq_w']:+.4f}(p{m2.pvalues['lincsq_w']:.2g})")
ํด์: ๊ฐ์ ๊ฐ๊ตฌ์์ ์๋์ด ์ค๋ฅด๋ฉด ์ฃผ๋ฅ ์ง์ถ๋น์ค์ ์ํญ ๊ฐ์(โ0.09/log์๋, p<0.001). โ ํก๋จ๋ฉด ์ญU์์ "์ ์๋ ์ต์ " ๊ตฌ๊ฐ์ ๊ฐ๊ตฌ ๊ตฌ์ฑํจ๊ณผ(์ ์๋=๊ณ ๋ นยท๋น์์ฃผ)์ด๊ณ , ๊ฐ๊ตฌ ๋ด ํ๋์ ์๋ํ๋ ฅ์ฑ<1. ์ค์ฐ์ธต ์ต๋ค๋ถ๋ด์ ๊ฐ๊ตฌ ๊ฐ ๊ตฌ์กฐ์ ์ฌ์ค.
2020 ์ข ๋์ธ ์์ ์ DiD โ ๋ง๊ฑธ๋ฆฌยท์ ํต์ฃผ(์ฒ์น) vs ์์ธ(๋์กฐ)ยถ
2024 ๊ธฐ์คํ๋งค๋น์จ์ ์์ง ๋ฐ์ดํฐ ๋ฐ(์ต์ 2023). 2020 ์ข ๋์ธ๋ ์ฝ๋ก๋ ์ค์ฒฉยท๋งฅ์ฃผ ๋ถ๋ฆฌ๋ถ๊ฐ โ ์์ ์ ์ฐธ๊ณ ์น.
Inย [4]:
dd=p[p["wine"].notna()&p["mak"].notna()].copy(); dd=dd[(dd.year>=2016)&(dd.year<=2023)]
dd["post"]=(dd.year>=2020).astype(int)
A=dd[["hid","post","wt","mak"]].rename(columns={"mak":"exp"}); A["treat"]=1
B=dd[["hid","post","wt","wine"]].rename(columns={"wine":"exp"}); B["treat"]=0
dl=pd.concat([A,B],ignore_index=True)
m3=smf.ols("exp ~ treat*post",data=dl).fit(cov_type="cluster",cov_kwds={"groups":dl["hid"]})
print(f"DiD ์ํธ์์ฉ(treat:post) = {m3.params['treat:post']:+.4f} (p {m3.pvalues['treat:post']:.3g})")
print("โ ํต๊ณ์ ์ ์์ฑ ์์. ์ธ์ ์ธ๊ณผ DiD๋ 18์ฐจ(2025์กฐ์ฌ)ยท2024 ๊ธฐ์คํ๋งค๋น์จ๋ก ์ฌ์ถ์ ๊ถ์ฅ.")