未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合具备Pandas和Matplotlib基础的读者学习模拟薪资数据的描述统计、分组比较、相关分析与综合画像,不是城市现状或企业薪酬公平性的实证报告。当前中心趋势、离散程度、分布绘图和异常值检测代码仍存在列索引截断及语法错误,分布绘图还将双子图返回的Axes数组当作单个Axes调用,不能作为可直接复制运行的完整练习。薪资数组拼接、部门标签对应关系及综合画像的零偏度解释已无旧说明所指错误。各类离散程度、相关强度及行动建议阈值仅作教学示例;变异系数应限于合适的比率尺度且均值为正、不接近零的情况,IQR与Z-score标记只是需进一步核查的候选异常。平均数与中位数、偏度和峰度不能单独证明分布类型,相关性不代表因果,薪酬决策需结合岗位、职级与业务背景。

📊 数据统计与描述分析 - 让数据开口说话

abstract 📋 这节课你会学到什么

  • 🎯 学习目标:像新闻记者一样用数据讲故事
  • 📚 需要基础:Pandas数据清洗
  • ⏱️ 学习时间:1.5小时,深入理解数据特征
  • 🎮 学习方式:从记者采访到数据侦探,发现数据的秘密
  • 💡 核心思想:数据本身会说话,我们要学会倾听

🤔 什么是描述性统计?从记者采访说起

想象你是一位记者,要写一篇关于某个城市的报道:

传统记者的方法:

# 随机采访几个人就下结论
interview_results = ["这个城市很好", "这个城市一般", "这个城市不错"]
conclusion = "根据采访,这个城市总体不错"
print(conclusion)  # 但这个结论可靠吗?

数据记者的方法:

import pandas as pd
import numpy as np

# 构造假设城市的示例数据,不代表真实城市或全国比较结果
city_data = pd.DataFrame({
    '人口': [1000000],
    '平均收入': [8500],
    '教育水平': [0.85],  # 大学毕业率
    '空气质量': [78],    # 空气质量指数
    '交通便利性': [0.92],
    '医疗资源': [0.88]
})

# 用数据说话
print("📊 城市画像报告:")
print(f"人口规模:{city_data['人口'].iloc[0]:,}人(示例值,不作城市规模分类)")
print(f"收入水平:{city_data['平均收入'].iloc[0]:,}元(示例值,未与全国同口径数据比较)")
print(f"大学毕业率:{city_data['教育水平'].iloc[0]:.1%}(示例值,不据此判定教育发达程度)")

tip 💡 描述性统计的核心价值

让数据自己讲故事,而不是我们强加故事给数据


📈 数据的四个维度:位置、散布、形状、异常

中心趋势:数据的"重心"在哪里

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 创建员工薪资数据示例
np.random.seed(42)
salaries = pd.DataFrame({
    '部门': np.repeat(['技术', '销售', '市场', '人事', '财务'], 200),
    '薪资': np.concatenate([
        np.random.normal(12000, 2000, 200),  # 技术部门
        np.random.normal(8000, 1500, 200),   # 销售部门
        np.random.normal(9000, 1800, 200),   # 市场部门
        np.random.normal(7000, 1200, 200),   # 人事部门
        np.random.normal(10000, 1600, 200)   # 财务部门
    ]),
    '工作年限': np.random.randint(1, 16, 1000),
    '绩效评分': np.random.normal(85, 10, 1000)
})

def analyze_central_tendency(data, column):
    """分析中心趋势的三个指标"""
    
    print(f"📊 {column}的中心趋势分析")
    print("=" * 30)
    
 # 计算三个核心指标
    mean_val = data[colum.mean()
    median_val = data[colum.median()
    modes = data[column].mode()
    mode_val = modes.tolist() if not modes.empty else "无众数(无有效值)"
    
    print(f"平均数 (Mean): {mean_val:.2f}")
    print(f"  → 所有数据的算术平均,受极值影响")
    
    print(f"中位数 (Median): {median_val:.2f}")
    print(f"  → 排序后中间的数,不受极值影响")
    
    print(f"众数 (Mode): {mode_val}")
    print(f"  → 出现频率最高的数")
    
 # 三个指标的关系分析
    if abs(mean_val - median_val) < mean_val * 0.05:
        distribution = "平均数与中位数接近,仍需结合图形与其他指标判断分布"
    elif mean_val > median_val:
        distribution = "平均数高于中位数,可能提示右偏,需结合分布图确认"
    else:
        distribution = "平均数低于中位数,可能提示左偏,需结合分布图确认"
    
    print(f"\n🔍 分布特征:{distribution}")
    
    return {
        'mean': mean_val,
        'median': median_val,
        'mode': mode_val,
        'distribution': distribution
    }

# 分析薪资的中心趋势
salary_stats = analyze_central_tendency(salaries, '薪资')

离散程度:数据有多"散"

def analyze_dispersion(data, column):
    """分析数据的离散程度"""
    
    print(f"\n📏 {column}的离散程度分析")
    print("=" * 30)
    
 # 计算离散指标
    range_val = data[column].max() - data[column].min()
    std_val = data[colum.std()
    var_val = data[column].var()
    cv_val = std_val / data[colum.mean() * 100  # 变异系数
    
 # 四分位数
    q1 = data[colum.quantile(0.25)
    q3 = data[colum.quantile(0.75)
    iqr = q3 - q1
    
    print(f"极差 (Range): {range_val:.2f}")
    print(f"  → 最大值与最小值的差距")
    
    print(f"标准差 (Std): {std_val:.2f}")
    print(f"  → 数据偏离平均数的平均程度")
    
    print(f"四分位距 (IQR): {iqr:.2f}")
    print(f"  → 中间50%数据的范围(Q3-Q1)")
    
    print(f"变异系数 (CV): {cv_val:.1f}%")
    print(f"  → 标准差与平均数的比值,便于比较")
    
 # 离散程度判断
    if cv_val < 15:
        dispersion_level = "低离散(数据比较集中)"
    elif cv_val < 30:
        dispersion_level = "中等离散(数据适度分散)"
    else:
        dispersion_level = "高离散(数据非常分散)"
    
    print(f"\n🎯 离散程度:{dispersion_level}")
    
    return {
        'range': range_val,
        'std': std_val,
        'iqr': iqr,
        'cv': cv_val
    }

# 分析薪资的离散程度
dispersion_stats = analyze_dispersion(salaries, '薪资')

分布形状:数据长什么"样子"

def analyze_distribution_shape(data, column):
    """分析数据分布的形状特征"""
    
    print(f"\n📐 {column}的分布形状分析")
    print("=" * 30)
    
 # 计算偏度和峰度
    skewness = data[column].skew()
    kurtosis = data[column].kurtosis()
    
    print(f"偏度 (Skewness): {skewness:.3f}")
    if abs(skewness) < 0.5:
        skew_interpretation = "偏度较小,但不能据此判定正态或严格对称"
    elif skewness > 0:
        skew_interpretation = "右偏分布(长尾在右侧)"
    else:
        skew_interpretation = "左偏分布(长尾在左侧)"
    print(f"  → {skew_interpretation}")
    
    print(f"\n峰度 (Kurtosis): {kurtosis:.3f}")
    if abs(kurtosis) < 0.5:
        kurt_interpretation = "接近正态分布的峰度"
    elif kurtosis > 0:
        kurt_interpretation = "超额峰度为正,提示尾部特征与正态分布不同,不能仅据此判断峰形"
    else:
        kurt_interpretation = "超额峰度为负,提示尾部特征与正态分布不同,不能仅据此判断峰形"
    print(f"  → {kurt_interpretation}")
    
 # 绘制分布图
    fig, axes = plt.subplots(1, 2, figsize=(12, 5))
    
 # 直方图
    axes[0].hist(data[column], bins=30, alpha=0.7, color='skyblue', edgecolor='black')
    axes.axvline(data[colum.mean(), color='red', linestyle='--', label=f'平均数: {data[colum.mean():.0f}')
    axes.axvline(data[colum.median(), color='green', linestyle='--', label=f'中位数: {data[colum.median():.0f}')
    axes[0].set_title(f'{column}分布直方图')
    axes[0].set_xlabel(column)
    axes[0].set_ylabel('频数')
    axes[0].legend()
    
 # 箱线图
    axes[1].boxplot(data[column])
    axes[1].set_title(f'{column}箱线图')
    axes[1].set_ylabel(column)
    
    plt.tight_layout()
    plt.show()
    
    return {
        'skewness': skewness,
        'kurtosis': kurtosis,
        'shape_summary': f"{skew_interpretation}, {kurt_interpretation}"
    }

# 分析薪资分布形状
shape_stats = analyze_distribution_shape(salaries, '薪资')

🔍 多维度描述分析:从单一到综合

分组统计:不同角度看数据

def group_analysis(data):
    """分组统计分析"""
    
    print("👥 按部门分组的薪资分析")
    print("=" * 35)
    
 # 按部门分组统计
    dept_stats = data.groupby('部门')['薪资'].agg([
        'count', 'mean', 'median', 'std', 'min', 'max'
    ]).round(2)
    
    dept_stats.columns = ['人数', '平均薪资', '薪资中位数', '薪资标准差', '最低薪资', '最高薪资']
    
    print(dept_stats)
    
 # 计算各部门薪资差异
    print(f"\n💰 部门薪资差异分析:")
    max_avg = dept_stats['平均薪资'].max()
    min_avg = dept_stats['平均薪资'].min()
    max_dept = dept_stats['平均薪资'].idxmax()
    min_dept = dept_stats['平均薪资'].idxmin()
    
    print(f"最高薪资部门:{max_dept}({max_avg:.0f}元)")
    print(f"最低薪资部门:{min_dept}({min_avg:.0f}元)")
    print(f"部门间薪资差距:{max_avg - min_avg:.0f}元({(max_avg/min_avg-1)*100:.1f}%)")
    
 # 薪资公平性分析
    overall_cv = data['薪资'].std() / data['薪资'].mean() * 100
    print(f"\n⚖️ 薪资公平性分析:")
    print(f"整体薪资变异系数:{overall_cv:.1f}%")
    
    if overall_cv < 20:
        fairness = "按示例阈值,薪资相对离散程度较低;不能据此判定公平"
    elif overall_cv < 35:
        fairness = "按示例阈值,薪资相对离散程度居中;公平性需结合岗位与职级分析"
    else:
        fairness = "按示例阈值,薪资相对离散程度较高;需结合岗位与职级解释差异"
    
    print(f"公平性评估:{fairness}")
    
    return dept_stats

# 执行分组分析
group_stats = group_analysis(salaries)

相关性分析:变量间的关系

def correlation_analysis(data):
    """变量间相关性分析"""
    
    print("\n🔗 变量相关性分析")
    print("=" * 25)
    
 # 计算数值变量间的相关系数
    numeric_cols = ['薪资', '工作年限', '绩效评分']
    correlation_matrix = data[numeric_cols].corr()
    
    print("📊 相关系数矩阵:")
    print(correlation_matrix.round(3))
    
 # 解释相关性强度
    print(f"\n🔍 相关性解读:")
    
 # 薪资与工作年限的相关性
    salary_experience_corr = correlation_matrix.loc['薪资', '工作年限']
    print(f"薪资 vs 工作年限:{salary_experience_corr:.3f}")
    
    if abs(salary_experience_corr) > 0.7:
        strength = "强相关"
    elif abs(salary_experience_corr) > 0.3:
        strength = "中等相关"
    else:
        strength = "弱相关"
    
    direction = "正相关" if salary_experience_corr > 0 else "负相关" if salary_experience_corr < 0 else "零线性相关"
    print(f"  → {strength}{direction}:系数为正表示线性同向关联,为负表示线性反向关联;零相关不标方向,相关性不代表因果")
    
 # 薪资与绩效的相关性
    salary_performance_corr = correlation_matrix.loc['薪资', '绩效评分']
    print(f"\n薪资 vs 绩效评分:{salary_performance_corr:.3f}")
    
    if abs(salary_performance_corr) > 0.7:
        strength = "强相关"
    elif abs(salary_performance_corr) > 0.3:
        strength = "中等相关"
    else:
        strength = "弱相关"
    
    direction = "正相关" if salary_performance_corr > 0 else "负相关" if salary_performance_corr < 0 else "零线性相关"
    print(f"  → {strength}{direction}:系数为正表示线性同向关联,为负表示线性反向关联;零相关不标方向,相关性不代表因果")
    
 # 可视化相关性
    plt.figure(figsize=(8, 6))
    sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0, 
                square=True, fmt='.3f')
    plt.title('变量相关性热力图')
    plt.show()
    
    return correlation_matrix

# 执行相关性分析
correlation_matrix = correlation_analysis(salaries)

🚨 异常值检测:发现数据中的"意外"

def detect_outliers(data, column):
    """检测异常值"""
    
    print(f"\n🚨 {column}异常值检测")
    print("=" * 25)
    
 # 方法1:IQR方法
    Q1 = data[colum.quantile(0.25)
    Q3 = data[colum.quantile(0.75)
    IQR = Q3 - Q1
    
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    
    outliers_iqr = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
    
    print(f"📊 IQR方法检测结果:")
    print(f"正常范围:{lower_bound:.2f} ~ {upper_bound:.2f}")
    print(f"异常值数量:{len(outliers_iqr)}个({len(outliers_iqr)/len(data)*100:.1f}%)")
    
    if len(outliers_iqr) > 0:
        print(f"异常值范围:{outliers_iqr[column].min():.2f} ~ {outliers_iqr[column].max():.2f}")
    
 # 方法2:Z-score方法
    z_scores = np.abs((data[column] - data[column].mean()) / data[column].std())
    outliers_zscore = data[z_scores > 3]
    
    print(f"\n📊 Z-score方法检测结果:")
    print(f"异常值数量:{len(outliers_zscore)}个({len(outliers_zscore)/len(data)*100:.1f}%)")
    
 # 异常值的可能原因分析
    print(f"\n🔍 异常值分析:")
    if len(outliers_iqr) > 0:
        print("可能原因:")
        print("  • 数据录入错误")
        print("  • 特殊情况(如高管薪资、新人薪资)")
        print("  • 数据分布本身的特征")
        print("  • 需要进一步业务验证")
    
 # 处理建议
    outlier_rate = len(outliers_iqr) / len(data) * 100
    if outlier_rate < 1:
        suggestion = "异常值较少,可保留或单独分析"
    elif outlier_rate < 5:
        suggestion = "适量异常值,建议业务验证后决定"
    else:
        suggestion = "异常值较多,需要检查数据质量"
    
    print(f"\n💡 处理建议:{suggestion}")
    
    return outliers_iqr, outliers_zscore

# 检测薪资异常值
outliers_iqr, outliers_zscore = detect_outliers(salaries, '薪资')

📋 综合报告:数据的完整画像

class DataProfiler:
    """数据画像生成器"""
    
    def __init__(self, data):
        self.data = data
        
    def generate_profile_report(self, target_column):
        """生成完整的数据画像报告"""
        
        print("📋 数据画像综合报告")
        print("=" * 50)
        
 # 基本信息
        print("📊 基本信息:")
        print(f"数据总量:{len(self.data):,}条")
        print(f"分析字段:{target_column}")
        print(f"数据类型:{self.data[target_column].dtype}")
        print(f"缺失值:{self.data[target_column].isnull().sum()}个")
        
 # 核心统计指标
        stats = self.data[target_column].describe()
        print(f"\n📈 核心统计指标:")
        print(f"平均值:{stats['mean']:.2f}")
        print(f"中位数:{stats['50%']:.2f}")
        print(f"标准差:{stats['std']:.2f}")
        print(f"最小值:{stats['min']:.2f}")
        print(f"最大值:{stats['max']:.2f}")
        
 # 分布特征
        skew = self.data[target_column].skew()
        kurt = self.data[target_column].kurtosis()
        
        print(f"\n📐 分布特征:")
        print(f"偏度:{skew:.3f}({'正偏度' if skew > 0 else '负偏度' if skew < 0 else '零偏度,不据此判定对称'})")
        print(f"超额峰度:{kurt:.3f}(不能仅据此判定峰形或正态性)")
        
 # 业务解读
        print(f"\n💼 业务解读:")
        mean_val = stats['mean']
        median_val = stats['50%']
        
        if abs(mean_val - median_val) < mean_val * 0.05:
            distribution_insight = "平均数与中位数接近,不能据此判断分布均匀或多数薪资接近平均水平"
        elif mean_val > median_val:
            distribution_insight = "平均数高于中位数,需结合分布图与分组统计判断高值影响和薪资差异"
        else:
            distribution_insight = "平均数低于中位数,需结合分布图与岗位背景判断低值影响,不能据此判定公平性"
        
        print(f"  • {distribution_insight}")
        
 # 行动建议
        cv = stats['std'] / stats['mean'] * 100
        print(f"\n🎯 行动建议:")
        
        if cv < 20:
            print("  • 按示例阈值离散程度较低;是否保持政策需结合岗位、职级和业务目标评估")
        elif cv < 35:
            print("  • 按示例阈值离散程度居中;是否优化绩效导向需另行评估")
        else:
            print("  • 薪资差异较大,建议:")
            print("    - 检查薪资等级设计的合理性")
            print("    - 结合岗位和绩效制度评估薪资差异,不能仅凭变异系数决定绩效挂钩政策")
            print("    - 关注员工满意度和流失率")
        
        return {
            'basic_stats': stats,
            'skewness': skew,
            'kurtosis': kurt,
            'cv': cv
        }

# 生成薪资数据画像报告
profiler = DataProfiler(salaries)
profile_report = profiler.generate_profile_report('薪资')

🎯 总结与最佳实践

success ✅ 描述性统计的核心价值

描述性统计是数据分析的第一步,就像医生看病要先测体温、血压一样:

  • 了解数据全貌:不要急于建模,先听数据说话
  • 发现异常和问题:及时发现数据质量问题
  • 指导后续分析:为深入分析提供方向
  • 支持业务决策:用事实而非假设做决策

🔧 实用技巧总结

  1. 选择合适的指标:

    • 对称分布 → 用平均数
    • 偏态分布 → 用中位数
    • 比较相对离散程度 → 比率尺度、均值为正且不接近零时可考虑变异系数
  2. 解读统计结果:

    • 数字背后的业务含义
    • 异常值的可能原因
    • 趋势变化的影响因素
  3. 可视化辅助:

    • 直方图看分布形状
    • 箱线图识别异常值
    • 散点图看变量关系

📊 常用统计指标速查表

指标类型 具体指标 作用 适用场景
中心趋势 平均数 数据重心 对称分布
中位数 中间位置 偏态分布
众数 最常见值 分类数据
离散程度 标准差 平均偏离 同单位比较
变异系数 相对离散 比率尺度且均值为正、不接近零时比较
四分位距 稳健离散 有异常值时
分布形状 偏度 对称性 判断分布类型
峰度 尾部特征相关指标 辅助比较尾部特征,不单独判定峰形或集中程度

🔗 知识连接

info 🌐 描述性统计在数据科学中的桥梁作用

描述性统计是连接原始数据与深度分析的桥梁,为后续建模奠定基础

前置基础技能:

后续深入方向:

业务应用场景:

  • 💼 业务分析:业务数据洞察 - 用统计支持商业决策
  • 📊 用户画像:用户数据分析 - 描绘用户特征
  • 🎯 A/B测试基础:A/B测试方法 - 实验前的基线分析

工具技术栈:

  • 🐍 Python统计库:SciPy与统计建模
  • 📊 可视化增强:Plotly交互图表 与 BI报表
  • 🗄️ 数据库统计:SQL统计函数 - 在数据库层面做统计

创建时间:2024-06-29
最后更新:2024-06-29
学习时长:90分钟
难度等级:⭐⭐⭐(中级)

#数据统计 #描述性分析 #统计指标 #EDA #Python #数据洞察 #业务分析