In [47]:
#导入15年数据
import os
import pandas as pd
import glob
import numpy as np
#导入15年的CHARLS调查数据
# 获取当前工作目录下的所有dta文件的路径
dta_files = glob.glob('D:\\论文\\数据\\2015年全国追踪调查\\*.dta')
# 创建一个空字典,用于存放dataframe对象和名称
dataframes = {}
# 遍历每个dta文件,读取数据并存入字典
for file in dta_files:
# 去掉文件名后缀,作为dataframe的名称,并在后面加上15,表示这是15年的
name = os.path.basename(file.split('.')[0])+ '15'
# 读取dta文件,返回一个dataframe对象
df = pd.read_stata(file)
# 将dataframe对象和名称存入字典
dataframes[name] = df
# 使用globals()函数将字典中的键和值分别赋值给全局变量和dataframe对象
globals().update(dataframes)
In [48]:
#导入18年数据
import os
import pandas as pd
import glob
import numpy as np#用同样的方法导入18年的CHARLS数据
# 获取当前工作目录下的所有dta文件的路径
dta_filess = glob.glob('D:\\论文\\数据\\2018年全国追踪调查\\*.dta')
# 创建一个空字典,用于存放dataframe对象和名称
dataframess = {}
# 遍历每个dta文件,读取数据并存入字典
for file in dta_filess:
# 去掉文件名后缀,作为dataframe的名称,并在后面加上15,表示这是15年的
name = os.path.basename(file.split('.')[0])+ '18'
# 读取dta文件,返回一个dataframe对象
dff = pd.read_stata(file)
# 将dataframe对象和名称存入字典
dataframess[name] = dff
# 使用globals()函数将字典中的键和值分别赋值给全局变量和dataframe对象
globals().update(dataframess)
In [53]:
city_gender_age_premium_ratio_district15 = pd.read_csv(r'D:\论文\最后一波一鼓作气\数据\city_gender_age_premium_ratio_district15.csv')
city_gender_age_premium_ratio_district18 = pd.read_csv(r'D:\论文\最后一波一鼓作气\数据\city_gender_age_premium_ratio_district18.csv')
In [51]:
import pandas as pd
# ========== A) 工具函数:规范列名 + 统一键名 ==========
def normalize_columns(df):
# 去前后空格、转小写、把连续空白折叠成一个下划线
df.columns = (
df.columns.astype(str)
.str.strip()
.str.replace(r'\s+', '_', regex=True)
.str.lower()
)
return df
def coerce_key(df, candidates, target):
"""
在 df 中查找 candidates 里的任一列名,若找到则重命名为 target;
支持大小写/空格差异(因 normalize_columns 已处理)。
"""
for c in candidates:
if c in df.columns:
if c != target:
df.rename(columns={c: target}, inplace=True)
return True
return False
def norm_id_series(s):
# 统一为字符串ID,并去掉可能的 '.0'
return (
s.astype(str).str.strip()
.str.replace(r'\.0$', '', regex=True)
)
# ========== B) 先规范列名 ==========
Household_Income15 = normalize_columns(Household_Income15)
Household_Income18 = normalize_columns(Household_Income18)
city_gender_age_premium_ratio_district15 = normalize_columns(city_gender_age_premium_ratio_district15)
city_gender_age_premium_ratio_district18 = normalize_columns(city_gender_age_premium_ratio_district18)
# ========== C) 统一键名 ==========
# 2015 用 ID
ok15_L = coerce_key(Household_Income15, candidates=['id','ID','Id'], target='id')
ok15_R = coerce_key(city_gender_age_premium_ratio_district15, candidates=['id','ID','Id'], target='id')
# 2018 用 householdID(统一为小写 'householdid')
ok18_L = coerce_key(Household_Income18, candidates=['householdid','household_id','id'], target='householdid')
ok18_R = coerce_key(city_gender_age_premium_ratio_district18, candidates=['householdid','household_id','id'], target='householdid')
# 若找不到对应键,抛出更友好的错误提示
if not ok15_L or not ok15_R:
raise KeyError("2015合并键未找到:请确认左/右表含有 'ID' 列。")
if not ok18_L or not ok18_R:
raise KeyError("2018合并键未找到:请确认左/右表含有 'householdID'(或 'ID')列。")
# ========== D) 统一键值格式 ==========
Household_Income15['id'] = norm_id_series(Household_Income15['id'])
city_gender_age_premium_ratio_district15['id'] = norm_id_series(city_gender_age_premium_ratio_district15['id'])
Household_Income18['householdid'] = norm_id_series(Household_Income18['householdid'])
city_gender_age_premium_ratio_district18['householdid'] = norm_id_series(city_gender_age_premium_ratio_district18['householdid'])
# ========== E) 右表去重并校验==========
city_gender_age_premium_ratio_district15 = city_gender_age_premium_ratio_district15.drop_duplicates('id', keep='first')
city_gender_age_premium_ratio_district18 = city_gender_age_premium_ratio_district18.drop_duplicates('householdid', keep='first')
# ========== F) 合并 + 去掉城市样本 +只留下2015未改革2018已改革的样本 ==========
Household_Income15 = pd.merge(
Household_Income15,
city_gender_age_premium_ratio_district15,
how='left',
on='id',
validate='many_to_one'
)
Household_Income15 = Household_Income15[Household_Income15['urban_nbs'].ne('urban')]
Household_Income15 = Household_Income15[(Household_Income15['policyintergration2015'] == 0) &
(Household_Income15['policyintergration2018'] == 1)]
Household_Income18 = pd.merge(
Household_Income18,
city_gender_age_premium_ratio_district18,
how='left',
on='householdid',
validate='many_to_one'
)
Household_Income18 = Household_Income18[Household_Income18['urban_nbs'].ne('urban')]
Household_Income18 = Household_Income18[(Household_Income18['policyintergration2015'] == 0) &
(Household_Income18['policyintergration2018'] == 1)]
# ========== G) 下面接你的原统计代码即可 ==========
cols15 = ['ge006','ge008','ge009_2','ge009_3','ge009_6','ge009_7','ge010_6']
cols18 = ['ge006_w4','ge008','ge009_2','ge009_3','ge009_6','ge009_7','ge010_6']
for c in cols15:
if c in Household_Income15.columns:
Household_Income15[c] = pd.to_numeric(Household_Income15[c], errors='coerce')
for c in cols18:
if c in Household_Income18.columns:
Household_Income18[c] = pd.to_numeric(Household_Income18[c], errors='coerce')
food15 = Household_Income15.get('ge006', 0).fillna(0) + Household_Income15.get('ge008', 0).fillna(0)
utilities15 = Household_Income15.get('ge009_2', 0).fillna(0) + Household_Income15.get('ge009_3', 0).fillna(0)
goodsent15 = Household_Income15.get('ge009_6', 0).fillna(0) + Household_Income15.get('ge009_7', 0).fillna(0)
medical15 = Household_Income15.get('ge010_6', 0).fillna(0)
food18 = Household_Income18.get('ge006_w4', 0).fillna(0) + Household_Income18.get('ge008', 0).fillna(0)
utilities18 = Household_Income18.get('ge009_2', 0).fillna(0) + Household_Income18.get('ge009_3', 0).fillna(0)
goodsent18 = Household_Income18.get('ge009_6', 0).fillna(0) + Household_Income18.get('ge009_7', 0).fillna(0)
medical18 = Household_Income18.get('ge010_6', 0).fillna(0)
def stats_nonzero(s):
s = pd.to_numeric(s, errors='coerce')
s = s[(s != 0) & s.notna()]
return float(s.mean()), float(s.std(ddof=1))
def stats_include_zero(s):
s = pd.to_numeric(s, errors='coerce').fillna(0)
return float(s.mean()), float(s.std(ddof=1))
def three_stats_nonzero(s15, s18):
m15, sd15 = stats_nonzero(s15)
m18, sd18 = stats_nonzero(s18)
mall, sdall = stats_nonzero(pd.concat([s15, s18], ignore_index=True))
return m15, sd15, m18, sd18, mall, sdall
def three_stats_zero(s15, s18):
m15, sd15 = stats_include_zero(s15)
m18, sd18 = stats_include_zero(s18)
mall, sdall = stats_include_zero(pd.concat([s15, s18], ignore_index=True))
return m15, sd15, m18, sd18, mall, sdall
food_stats = three_stats_nonzero(food15, food18)
utilities_stats = three_stats_nonzero(utilities15, utilities18)
goodsent_stats = three_stats_nonzero(goodsent15, goodsent18)
medical_stats = three_stats_zero(medical15, medical18)
index = ['食品、就餐及烟酒','水费及燃料费','日用品及文娱','医疗消费']
cols = ['2015均值','2015标准差','2018均值','2018标准差','全样本均值','全样本标准差']
result = pd.DataFrame([food_stats, utilities_stats, goodsent_stats, medical_stats], index=index, columns=cols)
result
Out[51]:
| 2015均值 | 2015标准差 | 2018均值 | 2018标准差 | 全样本均值 | 全样本标准差 | |
|---|---|---|---|---|---|---|
| 食品、就餐及烟酒 | 273.586103 | 340.242541 | 331.045380 | 616.601501 | 302.301066 | 498.706759 |
| 水费及燃料费 | 253.214344 | 948.897081 | 253.108017 | 467.982422 | 253.161197 | 748.173569 |
| 日用品及文娱 | 197.609609 | 1002.553956 | 124.608330 | 311.756714 | 161.270680 | 744.587117 |
| 医疗消费 | 4396.273210 | 20516.852859 | 6591.151855 | 28213.359375 | 5468.398406 | 24602.340388 |
In [54]:
import pandas as pd
# ========== 0) 工具:规范 ID 值 ==========
def norm_id_series(s):
return (
s.astype(str).str.strip()
.str.replace(r'\.0$', '', regex=True) # 处理 Excel 读入的 12345.0
)
def require_col(df, colname, dfname):
if colname not in df.columns:
raise KeyError(f"{dfname} 缺少列:{colname}")
# ========== 1) 合并:两年都用 ID ==========
# 1.1 检查并规范 ID
for _df, _name in [
(Health_Status_and_Functioning15, 'Health_Status_and_Functioning15'),
(Health_Status_and_Functioning18, 'Health_Status_and_Functioning18'),
(city_gender_age_premium_ratio_district15, 'city_gender_age_premium_ratio_district15'),
(city_gender_age_premium_ratio_district18, 'city_gender_age_premium_ratio_district18')
]:
require_col(_df, 'ID', _name)
_df['ID'] = norm_id_series(_df['ID'])
# 1.2 右表按 ID 去重,避免一对多放大
city_gender_age_premium_ratio_district15 = city_gender_age_premium_ratio_district15.drop_duplicates('ID', keep='first')
city_gender_age_premium_ratio_district18 = city_gender_age_premium_ratio_district18.drop_duplicates('ID', keep='first')
# 1.3 左连接并剔除城市样本并只留下2015未改革2018已改革的样本
Health_Status_and_Functioning15 = pd.merge(
Health_Status_and_Functioning15,
city_gender_age_premium_ratio_district15,
how='left',
on='ID',
validate='many_to_one'
)
Health_Status_and_Functioning15 = Health_Status_and_Functioning15[Health_Status_and_Functioning15['urban_nbs'] != 'Urban']
Health_Status_and_Functioning15 = Health_Status_and_Functioning15[(Health_Status_and_Functioning15['policyintergration2015'] == 0) &
(Health_Status_and_Functioning15['policyintergration2018'] == 1)]
Health_Status_and_Functioning18 = pd.merge(
Health_Status_and_Functioning18,
city_gender_age_premium_ratio_district18,
how='left',
on='ID',
validate='many_to_one'
)
Health_Status_and_Functioning18 = Health_Status_and_Functioning18[Health_Status_and_Functioning18['urban_nbs'] != 'Urban']
Health_Status_and_Functioning18 = Health_Status_and_Functioning18[(Health_Status_and_Functioning18['policyintergration2015'] == 0) &
(Health_Status_and_Functioning18['policyintergration2018'] == 1)]
# ========== 2) 变量映射 ==========
mapping = {
'高血压': ('da007_w2_2_1_', 'da007_1_'),
'癌症': ('da007_w2_2_4_', 'da007_4_'),
'心脏病': ('da007_w2_2_7_', 'da007_7_'),
'情感及精神问题': ('da007_w2_2_11_', 'da007_11_')
}
# ========== 3) 将 1/2 或 “1 Yes/2 No/空值” → 二元 {Yes=1, No/空=0} ==========
def to_binary(s):
s_str = s.astype(str).str.strip().str.lower()
yes = (
s_str.eq('1') |
s_str.str.startswith('1') |
s_str.eq('yes') |
s_str.str.contains(r'\byes\b', na=False)
)
return yes.fillna(False).astype(int)
# ========== 4) 统计函数(样本标准差;样本量<=1 时 sd=0) ==========
def mean_std(x):
m = x.mean()
n = x.count()
sd = x.std(ddof=1) if n > 1 else 0.0
return float(m), float(sd)
# ========== 5) 逐项计算:2015 / 2018 / 合并 ==========
rows = []
for label, (c15, c18) in mapping.items():
# 若列不存在则补 0,避免 KeyError
s15_raw = Health_Status_and_Functioning15[c15] if c15 in Health_Status_and_Functioning15.columns else pd.Series([None]*len(Health_Status_and_Functioning15))
s18_raw = Health_Status_and_Functioning18[c18] if c18 in Health_Status_and_Functioning18.columns else pd.Series([None]*len(Health_Status_and_Functioning18))
s15 = to_binary(s15_raw)
s18 = to_binary(s18_raw)
m15, sd15 = mean_std(s15)
m18, sd18 = mean_std(s18)
mall, sdall = mean_std(pd.concat([s15, s18], ignore_index=True))
rows.append([m15, sd15, m18, sd18, mall, sdall])
# ========== 6) 输出表格 ==========
cols = ['2015均值','2015标准差','2018均值','2018标准差','全样本均值','全样本标准差']
result = pd.DataFrame(rows, index=list(mapping.keys()), columns=cols)
# 可选:显示为百分比
# result = (result * 100).round(2)
result
Out[54]:
| 2015均值 | 2015标准差 | 2018均值 | 2018标准差 | 全样本均值 | 全样本标准差 | |
|---|---|---|---|---|---|---|
| 高血压 | 0.061224 | 0.239769 | 0.108872 | 0.311517 | 0.084357 | 0.277940 |
| 癌症 | 0.002783 | 0.052686 | 0.011059 | 0.104593 | 0.006801 | 0.082193 |
| 心脏病 | 0.027829 | 0.164503 | 0.063160 | 0.243281 | 0.044983 | 0.207279 |
| 情感及精神问题 | 0.003711 | 0.060808 | 0.013517 | 0.115488 | 0.008472 | 0.091656 |
In [55]:
import pandas as pd
import numpy as np
# —— 小工具 ——
def drop_urban(df):
return df[df['urban_nbs'] != 'Urban'] if 'urban_nbs' in df.columns else df
def mean_std(x):
# 只做数值化以便计算;不做任何重编码
x = pd.to_numeric(x, errors='coerce')
n = x.count()
m = float(x.mean())
sd = float(x.std(ddof=1)) if n > 1 else 0.0
return m, sd
def calc_3stats(s15, s18):
m15, sd15 = mean_std(s15)
m18, sd18 = mean_std(s18)
mall, sdall = mean_std(pd.concat([s15, s18], ignore_index=True))
return [m15, sd15, m18, sd18, mall, sdall]
# —— 1) 取样本:剔除城市样本(只删 Urban,不改任何值) +只留下2015未改革2018已改革的样本——
df15 = drop_urban(city_gender_age_premium_ratio_district15).copy()
df15 = df15[(df15['policyintergration2015']==0.0) & (df15['policyintergration2018']==1.0)]
df18 = drop_urban(city_gender_age_premium_ratio_district18).copy()
df18 = df18[(df18['policyintergration2015']==0.0) & (df18['policyintergration2018']==1.0)]
# —— 2) educationrevised → 教育终止年龄(唯一需要新生成的列)——
edu_end_age_map = {
1: 6, # 无正式教育
2: 9, # 未读完小学
3: 11, # 私塾≈小学
4: 11, # 小学毕业
5: 14, # 初中毕业
6: 17, # 高中毕业
7: 17, # 中专/职高
8: 20, # 大专
9: 22, # 本科
10: 24, # 硕士
11: 28, # 博士
-1: np.nan # 不知道→忽略
}
def edu_end_age(series):
x = pd.to_numeric(series, errors='coerce')
return x.map(edu_end_age_map)
df15['edu_end_age'] = edu_end_age(df15['educationrevised']) if 'educationrevised' in df15.columns else pd.Series(dtype='float')
df18['edu_end_age'] = edu_end_age(df18['educationrevised']) if 'educationrevised' in df18.columns else pd.Series(dtype='float')
# —— 3) 直接用原始列做统计(不做任何重编码/映射)——
# 年龄
s15_age = df15['age'] if 'age' in df15.columns else pd.Series(dtype='float')
s18_age = df18['age'] if 'age' in df18.columns else pd.Series(dtype='float')
# 性别(原始值直接统计)
s15_gender = df15['gender'] if 'gender' in df15.columns else pd.Series(dtype='float')
s18_gender = df18['gender'] if 'gender' in df18.columns else pd.Series(dtype='float')
# 有无配偶(原始 marriage 直接统计)
s15_marriage = df15['marriage'] if 'marriage' in df15.columns else pd.Series(dtype='float')
s18_marriage = df18['marriage'] if 'marriage' in df18.columns else pd.Series(dtype='float')
# 子女数量:2015 用 kids15,2018 用 kids18(原始值)
s15_kids = df15['kids15'] if 'kids15' in df15.columns else pd.Series(dtype='float')
s18_kids = df18['kids18'] if 'kids18' in df18.columns else pd.Series(dtype='float')
# 收入:2015 用 ic15,2018 用 ic18(原始值)
s15_ic = df15['ic15'] if 'ic15' in df15.columns else pd.Series(dtype='float')
s18_ic = df18['ic18'] if 'ic18' in df18.columns else pd.Series(dtype='float')
# —— 4) 组装结果表 ——
rows = []
rows.append( calc_3stats(s15_age, s18_age) ) # 年龄
rows.append( calc_3stats(s15_gender, s18_gender) ) # 性别
rows.append( calc_3stats(df15['edu_end_age'], df18['edu_end_age']) ) # 教育终止年龄(新列)
rows.append( calc_3stats(s15_marriage, s18_marriage) ) # 有无配偶(原始 marriage)
rows.append( calc_3stats(s15_kids, s18_kids) ) # 子女数量
rows.append( calc_3stats(s15_ic, s18_ic) ) # 收入
index = ['年龄','性别','教育终止年龄','有无配偶','子女数量','收入']
cols = ['2015均值','2015标准差','2018均值','2018标准差','全样本均值','全样本标准差']
result = pd.DataFrame(rows, index=index, columns=cols)
# result = result.round(3) # 可选
result
Out[55]:
| 2015均值 | 2015标准差 | 2018均值 | 2018标准差 | 全样本均值 | 全样本标准差 | |
|---|---|---|---|---|---|---|
| 年龄 | 59.482375 | 10.552695 | 61.822347 | 10.093041 | 60.624588 | 10.396535 |
| 性别 | 0.481261 | 0.499681 | 0.477336 | 0.499521 | 0.479360 | 0.499591 |
| 教育终止年龄 | 10.671276 | 3.445694 | 10.115119 | 3.405575 | 10.395880 | 3.437037 |
| 有无配偶 | 0.868110 | 0.338393 | 0.851023 | 0.356091 | 0.859838 | 0.347167 |
| 子女数量 | 0.982283 | 0.131936 | 0.979216 | 0.142678 | 0.980789 | 0.137273 |
| 收入 | 10609.812609 | 32715.144767 | 15113.054399 | 124985.216845 | 12793.568982 | 90172.041212 |