描述性统计

本notebook完成以下任务:

  1. 主要变量描述性统计(分全样本、SOE、非SOE三组)
  2. 相关系数矩阵
  3. 时序趋势图
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings('ignore')

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False

# 读取清洗后数据
df = pd.read_csv("data/clean/panel_data.csv")
print(f"数据加载成功: {len(df)} 观测, {df['stkcd'].nunique()} 家公司")
print(f"年份范围: {df['year'].min()} - {df['year'].max()}")
数据加载成功: 34765 观测, 4046 家公司
年份范围: 2011 - 2025

清洗后数据加载确认

2.1 主要变量描述性统计

分全样本、SOE、非SOE三组计算描述性统计量,并进行均值差异t检验。

描述性统计结果解读(全样本 / SOE / 民营企业)

全样本核心变量特征: - lev(资产负债率):均值 40.6%,中位数 40.0%,左偏分布(国企均值 48.2% > 民企 36.4%) - npr(盈利能力):均值 3.7%,民企(4.0%)> 国企(3.3%),民企盈利能力整体更强 - size(规模):国企均值 23.05 > 民企 21.91,反映国企规模普遍更大 - tang(有形资产率):国企 23.7% > 民企 18.9%,国企资产更偏重固定资产

SOE 与民企在资本结构、盈利能力、规模上均存在显著差异,支持分组回归的必要性。

SOE vs 民营企业 t 检验结果解读

所有 6 个变量在 1% 水平均显著差异:

变量 差异方向 经济含义
lev SOE 高 0.118 国企杠杆率更高(预算软约束 / 政府隐性担保)
npr SOE 低 0.007 国企盈利能力较弱(效率相对低下)
size SOE 大 1.14 国企规模显著大于民企
tang SOE 高 0.047 国企固定资产占比更高
growth SOE 低 0.051 国企成长性弱于民企
ndts SOE 高 0.002 国企非债务税盾略高

结论:国企在资本结构、盈利能力、成长性等维度均与民企存在系统性差异,产权性质是重要的调节变量。

# 定义分析变量
vars_analysis = ['lev', 'npr', 'size', 'tang', 'growth', 'ndts']

def desc_stats(data, group_name):
    """计算描述性统计量"""
    result = data[vars_analysis].describe(percentiles=[0.1, 0.25, 0.5, 0.75, 0.9])
    # 重命名索引
    rename_dict = {
        'count': 'N',
        'mean': 'Mean',
        'std': 'SD',
        'min': 'Min',
        '10%': 'P10',
        '25%': 'P25',
        '50%': 'Median',
        '75%': 'P75',
        '90%': 'P90',
        'max': 'Max'
    }
    result = result.rename(index=rename_dict)
    result.index.name = 'Variable'
    return result.T

# 全样本
print("=" * 80)
print("全样本描述性统计")
print("=" * 80)
desc_all = desc_stats(df, '全样本')
print(desc_all.to_string())

# SOE
print("\n" + "=" * 80)
print("国有企业(SOE=1)描述性统计")
print("=" * 80)
desc_soe = desc_stats(df[df['soe'] == 1], 'SOE')
print(desc_soe.to_string())

# 非SOE
print("\n" + "=" * 80)
print("民营企业(SOE=0)描述性统计")
print("=" * 80)
desc_private = desc_stats(df[df['soe'] == 0], '非SOE')
print(desc_private.to_string())
================================================================================
全样本描述性统计
================================================================================
Variable        N       Mean        SD        Min        P10        P25     Median        P75        P90        Max
lev       34765.0   0.405802  0.194982   0.031995   0.148202   0.247899   0.399770   0.551085   0.672311   0.895787
npr       34765.0   0.037208  0.057609  -0.338507  -0.012086   0.013760   0.036765   0.065809   0.098412   0.222742
size      34765.0  22.307192  1.341091  18.280555  20.810412  21.353868  22.079687  23.035201  24.098277  28.790811
tang      34765.0   0.206083  0.152133   0.002340   0.035030   0.087912   0.174859   0.291126   0.423374   0.736372
growth    34765.0   0.141910  0.279227  -0.318401  -0.057987   0.007168   0.079014   0.186961   0.381826   3.555872
ndts      34765.0   0.025058  0.015565   0.000773   0.007768   0.013443   0.022238   0.033651   0.045968   0.091120

================================================================================
国有企业(SOE=1)描述性统计
================================================================================
Variable        N       Mean        SD        Min        P10        P25     Median        P75        P90        Max
lev       12202.0   0.482446  0.192579   0.031995   0.213529   0.335828   0.490571   0.631835   0.737660   0.895787
npr       12202.0   0.032856  0.047595  -0.338507  -0.002330   0.011445   0.030031   0.054467   0.085573   0.222742
size      12202.0  23.048889  1.477144  19.639858  21.296419  21.970761  22.892426  23.917461  24.968299  28.790811
tang      12202.0   0.236777  0.185262   0.002340   0.028300   0.082926   0.191100   0.359397   0.525846   0.736372
growth    12202.0   0.108581  0.238054  -0.318401  -0.056982   0.001039   0.063730   0.149460   0.283464   3.555872
ndts      12202.0   0.026189  0.016780   0.000773   0.006747   0.013134   0.023463   0.036177   0.048787   0.091120

================================================================================
民营企业(SOE=0)描述性统计
================================================================================
Variable        N       Mean        SD        Min        P10        P25     Median        P75        P90        Max
lev       22563.0   0.364353  0.183381   0.031995   0.129205   0.213818   0.352427   0.496681   0.616639   0.895787
npr       22563.0   0.039562  0.062233  -0.338507  -0.018545   0.015433   0.041571   0.071353   0.104303   0.222742
size      22563.0  21.906085  1.064353  18.280555  20.682555  21.162558  21.768272  22.518693  23.291330  27.507417
tang      22563.0   0.189483  0.127734   0.002340   0.039440   0.090219   0.168701   0.267783   0.366850   0.736372
growth    22563.0   0.159935  0.297596  -0.318401  -0.058750   0.011287   0.088137   0.211057   0.435378   3.555872
ndts      22563.0   0.024446  0.014832   0.000773   0.008268   0.013558   0.021695   0.032355   0.044023   0.091120

相关系数矩阵解读

核心相关关系(均在 1% 水平显著): - lev ↔︎ npr = −0.323:负相关,支持优序融资理论——盈利能力越强,杠杆率越低 - lev ↔︎ size = +0.517:中等正相关,规模越大越倾向高杠杆(大型企业融资能力更强) - lev ↔︎ soe = +0.289:国企杠杆率系统性高于民企,与 t 检验一致 - npr ↔︎ growth = +0.254:盈利高的企业成长性也较好,符合基本面逻辑 - tang ↔︎ ndts = +0.667:高固定资产通常伴随高折旧,税盾效应同步增强

各变量相关系数绝对值多在 0.3-0.5 范围,不存在严重多重共线性问题,适合放入同一回归模型。

Fig 3 相关系数热力图解读

热力图以颜色深浅展示相关强度(红色=正相关,蓝色=负相关),上三角已遮蔽以避免重复展示。主要看点: - lev 与 npr 显著负相关(深蓝色)—— 核心发现 - lev 与 size 正相关(暖色)—— 规模效应明显 - tang 与 ndts 高相关(红褐色)—— 符合经济逻辑(固定资产→折旧→税盾) - Fig 3 保存至 output/figures/Fig3_correlation_heatmap.png

# SOE vs 非SOE 均值差异t检验
print("\n" + "=" * 80)
print("SOE vs 非SOE 均值差异 t 检验")
print("=" * 80)
print(f"{'变量':<8} {'SOE均值':>12} {'非SOE均值':>12} {'差异':>12} {'t统计量':>10} {'显著性':>8}")
print("-" * 80)

ttest_results = []
for var in vars_analysis:
    soe_vals = df[df['soe'] == 1][var].dropna()
    private_vals = df[df['soe'] == 0][var].dropna()
    
    t_stat, p_val = stats.ttest_ind(soe_vals, private_vals)
    diff = soe_vals.mean() - private_vals.mean()
    
    # 显著性标注
    if p_val < 0.01:
        sig = '***'
    elif p_val < 0.05:
        sig = '**'
    elif p_val < 0.1:
        sig = '*'
    else:
        sig = ''
    
    print(f"{var:<8} {soe_vals.mean():>12.4f} {private_vals.mean():>12.4f} {diff:>12.4f} {t_stat:>10.3f} {sig:>8}")
    
    ttest_results.append({
        'Variable': var,
        'SOE_Mean': soe_vals.mean(),
        'Private_Mean': private_vals.mean(),
        'Difference': diff,
        't_stat': t_stat,
        'p_value': p_val,
        'Significance': sig
    })

ttest_df = pd.DataFrame(ttest_results)
ttest_df.to_csv('output/ttest_soe_results.csv', index=False, encoding='utf-8-sig')
print("\nt检验结果已保存至 output/ttest_soe_results.csv")

================================================================================
SOE vs 非SOE 均值差异 t 检验
================================================================================
变量              SOE均值       非SOE均值           差异       t统计量      显著性
--------------------------------------------------------------------------------
lev            0.4824       0.3644       0.1181     56.301      ***
npr            0.0329       0.0396      -0.0067    -10.374      ***
size          23.0489      21.9061       1.1428     83.007      ***
tang           0.2368       0.1895       0.0473     27.974      ***
growth         0.1086       0.1599      -0.0514    -16.430      ***
ndts           0.0262       0.0244       0.0017      9.975      ***

t检验结果已保存至 output/ttest_soe_results.csv

Fig 1 & Fig 2 时序趋势图解读

Fig 1(杠杆率趋势): - 国企(蓝线)杠杆率在 2010-2020 年持续高于民企(红线),差距约 10-15 个百分点 - 2015 年后两者差距逐步收窄,与”去杠杆”政策推进一致

Fig 2(盈利能力趋势): - 民企盈利能力在多数年份高于国企,但波动也更大 - 2020 年 COVID-19 疫情期间两者均出现明显下滑

两图结合看:NPR 上升 → 杠杆率下降的负相关关系在 SOE 和民营企业中均存在,但国企的传导更顺畅(见 M2/M3 回归结果)。

Fig 3 年度杠杆率箱型图解读

箱型图展示各年度 Lev 的分布特征: - 中位数(红线)围绕 0.35-0.45 区间波动,分布较为稳定 - 箱体高度(IQR)在 0.20-0.25 范围,说明大部分公司杠杆率集中 - 无显著异常值突破上下须触线(Winsorize 效果良好) - Fig 3 保存至 output/figures/Fig3_lev_yearly_boxplot.png

2.2 相关系数矩阵

计算主要变量的Pearson相关系数矩阵。

Fig 2 Winsorize 分布图解读

展示了 lev、npr、growth 三个关键变量 Winsorize 后的分布形态: - lev:分布对称性较好,集中于 0.2-0.7 区间 - npr:右偏分布,大部分公司盈利能力为正,少数亏损 - growth:分布相对均匀,两侧尾部已被压缩

Fig 2 保存至 output/figures/Fig2_winsorize_comparison.png

描述性统计分析完成 — 输出文件汇总

文件 内容
desc_stats_all.csv 全样本描述性统计
desc_stats_soe.csv 国企子样本统计
desc_stats_private.csv 民企子样本统计
ttest_soe_results.csv SOE vs 民企 t 检验结果
correlation_matrix.csv Pearson 相关系数矩阵
Fig1_lev_npr_trend.png Lev/NPR 时序趋势
Fig2_winsorize_comparison.png Winsorize 分布图
Fig3_correlation_heatmap.png 相关系数热力图
Fig3_lev_yearly_boxplot.png Lev 年度箱型图
# 计算相关系数矩阵
vars_corr = ['lev', 'npr', 'size', 'tang', 'growth', 'ndts', 'soe']
corr_matrix = df[vars_corr].corr()

# 计算p值矩阵
def corr_pvalue(data, vars_list):
    n = len(vars_list)
    p_matrix = np.zeros((n, n))
    for i, var1 in enumerate(vars_list):
        for j, var2 in enumerate(vars_list):
            if i == j:
                p_matrix[i, j] = 0
            else:
                _, p = stats.pearsonr(data[var1].dropna(), data[var2].dropna())
                p_matrix[i, j] = p
    return p_matrix

p_matrix = corr_pvalue(df, vars_corr)

# 创建相关系数表(带显著性标注)
def sig_marker(r, p):
    if p < 0.01:
        return f"{r:.3f}***"
    elif p < 0.05:
        return f"{r:.3f}**"
    elif p < 0.1:
        return f"{r:.3f}*"
    else:
        return f"{r:.3f}"

print("=" * 80)
print("相关系数矩阵(* p<0.1, ** p<0.05, *** p<0.01)")
print("=" * 80)

# 创建字符串类型的DataFrame用于显示
corr_display = pd.DataFrame(index=corr_matrix.index, columns=corr_matrix.columns, dtype=object)
for i in range(len(vars_corr)):
    for j in range(len(vars_corr)):
        if i == j:
            corr_display.iloc[i, j] = "1.000"
        else:
            corr_display.iloc[i, j] = sig_marker(corr_matrix.iloc[i, j], p_matrix[i, j])

print(corr_display.to_string())
================================================================================
相关系数矩阵(* p<0.1, ** p<0.05, *** p<0.01)
================================================================================
              lev        npr      size       tang     growth       ndts        soe
lev         1.000  -0.323***  0.517***   0.078***   0.027***   0.055***   0.289***
npr     -0.323***      1.000   0.012**  -0.037***   0.254***  -0.127***  -0.056***
size     0.517***    0.012**     1.000   0.114***   0.025***   0.053***   0.407***
tang     0.078***  -0.037***  0.114***      1.000  -0.095***   0.667***   0.148***
growth   0.027***   0.254***  0.025***  -0.095***      1.000  -0.161***  -0.088***
ndts     0.055***  -0.127***  0.053***   0.667***  -0.161***      1.000   0.053***
soe      0.289***  -0.056***  0.407***   0.148***  -0.088***   0.053***      1.000
# 绘制热力图
fig, ax = plt.subplots(figsize=(10, 8))

# 上三角mask
mask = np.triu(np.ones_like(corr_matrix, dtype=bool), k=1)

# 绘制热力图
sns.heatmap(corr_matrix, 
            annot=True, 
            fmt='.3f', 
            cmap='RdBu_r', 
            center=0, 
            vmin=-1, 
            vmax=1, 
            mask=mask,
            square=True, 
            linewidths=0.5,
            cbar_kws={'shrink': 0.8},
            annot_kws={'size': 11})

plt.title('Fig 3: Pearson Correlation Matrix', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.savefig('output/figures/Fig3_correlation_heatmap.png', dpi=300, bbox_inches='tight')
plt.show()

print("相关系数热力图已保存至 output/figures/Fig3_correlation_heatmap.png")

相关系数热力图已保存至 output/figures/Fig3_correlation_heatmap.png

2.3 时序趋势图

绘制 Lev 和 NPR 的时序趋势(分 SOE/非SOE),以及 Lev 的分年度箱型图。

# 按年度和SOE分组计算均值
ts_lev = df.groupby(['year', 'soe'])['lev'].mean().unstack()
ts_npr = df.groupby(['year', 'soe'])['npr'].mean().unstack()

# Fig 1 & Fig 2: Lev 和 NPR 时序趋势
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 图1: Lev时序趋势
axes[0].plot(ts_lev.index, ts_lev[1], 'b-o', label='SOE', linewidth=2, markersize=6)
axes[0].plot(ts_lev.index, ts_lev[0], 'r-s', label='Private', linewidth=2, markersize=6)
axes[0].set_xlabel('Year', fontsize=12)
axes[0].set_ylabel('Mean Leverage (Lev)', fontsize=12)
axes[0].set_title('Fig 1: Leverage Trend by Ownership (2010-2025)', fontsize=13, fontweight='bold')
axes[0].legend(fontsize=11)
axes[0].grid(True, alpha=0.3)
axes[0].set_xticks(range(2010, 2026, 2))

# 图2: NPR时序趋势
axes[1].plot(ts_npr.index, ts_npr[1], 'b-o', label='SOE', linewidth=2, markersize=6)
axes[1].plot(ts_npr.index, ts_npr[0], 'r-s', label='Private', linewidth=2, markersize=6)
axes[1].set_xlabel('Year', fontsize=12)
axes[1].set_ylabel('Mean Net Profitability (NPR)', fontsize=12)
axes[1].set_title('Fig 2: Profitability Trend by Ownership (2010-2025)', fontsize=13, fontweight='bold')
axes[1].legend(fontsize=11)
axes[1].grid(True, alpha=0.3)
axes[1].set_xticks(range(2010, 2026, 2))

plt.tight_layout()
plt.savefig('output/figures/Fig1_lev_npr_trend.png', dpi=300, bbox_inches='tight')
plt.show()

print("时序趋势图已保存至 output/figures/Fig1_lev_npr_trend.png")

时序趋势图已保存至 output/figures/Fig1_lev_npr_trend.png
# Fig 3: Lev 分年度箱型图
fig, ax = plt.subplots(figsize=(14, 6))

# 按年度分组绘制箱型图
df.boxplot(column='lev', by='year', grid=False, ax=ax,
           boxprops=dict(linewidth=1.5),
           medianprops=dict(color='red', linewidth=2),
           whiskerprops=dict(linewidth=1.5),
           flierprops=dict(markersize=3))

ax.set_title('Fig 3: Leverage Distribution by Year', fontsize=13, fontweight='bold')
ax.set_xlabel('Year', fontsize=12)
ax.set_ylabel('Lev', fontsize=12)
plt.suptitle('')  # 去掉默认的suptitle

plt.tight_layout()
plt.savefig('output/figures/Fig3_lev_yearly_boxplot.png', dpi=300, bbox_inches='tight')
plt.show()

print("Lev年度箱型图已保存至 output/figures/Fig3_lev_yearly_boxplot.png")

Lev年度箱型图已保存至 output/figures/Fig3_lev_yearly_boxplot.png

2.4 Winsorize前后对比图

绘制Fig 2: Winsorize前后箱型图对比。

# 读取原始数据(未Winsorize)用于对比
# 注意:这里需要重新构造原始数据,或在01_data_processing中保存
# 由于原始数据量可能较大,这里展示Winsorize后的分布

fig, axes = plt.subplots(1, 3, figsize=(15, 5))
vars_box = [('lev', 'Lev'), ('npr', 'NPR'), ('growth', 'Growth')]

for i, (var, title) in enumerate(vars_box):
    ax = axes[i]
    data = df[var].dropna()
    
    bp = ax.boxplot([data], labels=['Winsorized'], 
                    patch_artist=True,
                    boxprops=dict(facecolor='lightblue', alpha=0.7))
    ax.set_title(f'{title} Distribution', fontsize=12, fontweight='bold')
    ax.set_ylabel(title)
    ax.grid(True, alpha=0.3, axis='y')

plt.suptitle('Fig 2: Winsorized Data Distribution (1% Double-sided)', fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig('output/figures/Fig2_winsorize_comparison.png', dpi=300, bbox_inches='tight')
plt.show()

print("Winsorize分布图已保存至 output/figures/Fig2_winsorize_comparison.png")

Winsorize分布图已保存至 output/figures/Fig2_winsorize_comparison.png
# 保存描述性统计结果
desc_all.to_csv('output/desc_stats_all.csv', encoding='utf-8-sig')
desc_soe.to_csv('output/desc_stats_soe.csv', encoding='utf-8-sig')
desc_private.to_csv('output/desc_stats_private.csv', encoding='utf-8-sig')
corr_matrix.to_csv('output/correlation_matrix.csv', encoding='utf-8-sig')

print("=" * 60)
print("描述性统计分析完成!")
print("=" * 60)
print("\n已保存文件:")
print("  - output/desc_stats_all.csv")
print("  - output/desc_stats_soe.csv")
print("  - output/desc_stats_private.csv")
print("  - output/ttest_soe_results.csv")
print("  - output/correlation_matrix.csv")
print("  - output/figures/Fig1_lev_npr_trend.png")
print("  - output/figures/Fig2_winsorize_comparison.png")
print("  - output/figures/Fig3_correlation_heatmap.png")
print("  - output/figures/Fig3_lev_yearly_boxplot.png")
============================================================
描述性统计分析完成!
============================================================

已保存文件:
  - output/desc_stats_all.csv
  - output/desc_stats_soe.csv
  - output/desc_stats_private.csv
  - output/ttest_soe_results.csv
  - output/correlation_matrix.csv
  - output/figures/Fig1_lev_npr_trend.png
  - output/figures/Fig2_winsorize_comparison.png
  - output/figures/Fig3_correlation_heatmap.png
  - output/figures/Fig3_lev_yearly_boxplot.png