未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
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 ✅ 描述性统计的核心价值
描述性统计是数据分析的第一步,就像医生看病要先测体温、血压一样:
- 了解数据全貌:不要急于建模,先听数据说话
- 发现异常和问题:及时发现数据质量问题
- 指导后续分析:为深入分析提供方向
- 支持业务决策:用事实而非假设做决策
🔧 实用技巧总结
-
选择合适的指标:
- 对称分布 → 用平均数
- 偏态分布 → 用中位数
- 比较相对离散程度 → 比率尺度、均值为正且不接近零时可考虑变异系数
-
解读统计结果:
- 数字背后的业务含义
- 异常值的可能原因
- 趋势变化的影响因素
-
可视化辅助:
- 直方图看分布形状
- 箱线图识别异常值
- 散点图看变量关系
📊 常用统计指标速查表
| 指标类型 | 具体指标 | 作用 | 适用场景 |
|---|---|---|---|
| 中心趋势 | 平均数 | 数据重心 | 对称分布 |
| 中位数 | 中间位置 | 偏态分布 | |
| 众数 | 最常见值 | 分类数据 | |
| 离散程度 | 标准差 | 平均偏离 | 同单位比较 |
| 变异系数 | 相对离散 | 比率尺度且均值为正、不接近零时比较 | |
| 四分位距 | 稳健离散 | 有异常值时 | |
| 分布形状 | 偏度 | 对称性 | 判断分布类型 |
| 峰度 | 尾部特征相关指标 | 辅助比较尾部特征,不单独判定峰形或集中程度 |
🔗 知识连接
info 🌐 描述性统计在数据科学中的桥梁作用
描述性统计是连接原始数据与深度分析的桥梁,为后续建模奠定基础
前置基础技能:
- Pandas数据清洗
- Matplotlib
- 🧮 数学统计基础:统计学 - 理解指标的数学含义
后续深入方向:
- 🤖 机器学习准备:机器学习 - 为建模做数据准备
- 🔍 特征工程:数据分析专业能力/特征工程 - 基于统计洞察设计特征
- 📈 假设检验:假设检验与推断统计 - 从描述走向推断
业务应用场景:
- 💼 业务分析:业务数据洞察 - 用统计支持商业决策
- 📊 用户画像:用户数据分析 - 描绘用户特征
- 🎯 A/B测试基础:A/B测试方法 - 实验前的基线分析
工具技术栈:
- 🐍 Python统计库:SciPy与统计建模
- 📊 可视化增强:Plotly交互图表 与 BI报表
- 🗄️ 数据库统计:SQL统计函数 - 在数据库层面做统计
创建时间:2024-06-29
最后更新:2024-06-29
学习时长:90分钟
难度等级:⭐⭐⭐(中级)
#数据统计 #描述性分析 #统计指标 #EDA #Python #数据洞察 #业务分析