未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适合学习偏度计算、统计检验、模拟业务分析、变换比较与滚动监控,不宜直接作为生产工具包复用。客户细分仍直接组合分别截尾的订单金额与购买频次数组,可能因长度不一致产生广播错误,即使长度相同也不能保证同一客户对应,订单级金额与客户级频次亦未统一粒度;金融VaR数值已采用损失口径,但图中仍将该数值直接标在收益率横轴上,不能将该竖线理解为收益率左尾阈值。零偏度或低偏度不能证明对称、均匀或正态,检验不显著及Bootstrap区间包含零也不能证明对称性;固定分位数截尾不是异常值识别,其后偏度仅描述保留样本。金融、业务、质量建议及监控阈值仅为模拟讨论规则,不能仅凭偏度推断客户占比、满意度、投资尾部风险或过程稳定性,Cp/Cpk解释还需结合稳定过程、变异估计口径和适当的分布条件。本次仅作全文结构审阅,未运行代码或开展外部事实核验。
📐 偏度(Skewness)
abstract 📋 本节概览
- 🎯 学习目标:掌握偏度的概念、计算和实际应用
- 📚 前置知识:描述性统计|描述性统计 | 正态分布|正态分布
- ⏱️ 预计用时:8分钟
🤔 什么是偏度?
note 📖 **偏度的定义**
偏度是衡量数据分布不对称程度的统计量,描述分布相对于对称分布(如正态分布)的偏斜方向和程度。
数学表达式: $$\text{Skewness} = \frac{E[(X-\mu)^3]}{\sigma^3} = \frac{\mu_3}{\sigma^3}$$
其中:
- $\mu_3$ 是三阶中心矩
- $\sigma$ 是标准差
- $E[·]$ 表示期望值
偏度值的含义:
- 偏度 = 0:三阶中心矩为零,但不必然说明分布对称;对称分布在三阶绝对矩有限且标准差非零时偏度为零(如非退化正态分布)
- 偏度 > 0:右偏(正偏),尾部向右延伸
- 偏度 < 0:左偏(负偏),尾部向左延伸
生活例子:
- 收入分布:通常右偏,少数高收入者拉长右尾
- 考试成绩:可能左偏,大多数学生分数较高
- 房价分布:通常右偏,豪宅拉高平均价格
- 年龄分布:在某些群体中可能呈现不同偏斜
📊 偏度的类型与特征
1. 三种偏度类型
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import seaborn as sns
def plot_skewness_types():
"""展示不同类型的偏度"""
fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(2, 3, figsize=(15, 10))
# 1. 对称分布(偏度≈0)
np.random.seed(42)
symmetric_data = np.random.normal(0, 1, 1000)
skew_sym = stats.skew(symmetric_data)
ax1.hist(symmetric_data, bins=30, density=True, alpha=0.7, color='blue', edgecolor='black')
ax1.set_title(f'对称分布\n偏度 = {skew_sym:.3f}')
ax1.set_ylabel('密度')
ax1.grid(True, alpha=0.3)
# 标注均值、中位数、众数
mean_sym = np.mean(symmetric_data)
median_sym = np.median(symmetric_data)
ax1.axvline(mean_sym, color='red', linestyle='-', linewidth=2, label=f'均值={mean_sym:.2f}')
ax1.axvline(median_sym, color='green', linestyle='--', linewidth=2, label=f'中位数={median_sym:.2f}')
ax1.legend()
# 2. 右偏分布(正偏度)
right_skewed_data = np.random.lognormal(0, 0.8, 1000)
skew_right = stats.skew(right_skewed_data)
ax2.hist(right_skewed_data, bins=30, density=True, alpha=0.7, color='orange', edgecolor='black')
ax2.set_title(f'右偏分布(正偏)\n偏度 = {skew_right:.3f}')
ax2.grid(True, alpha=0.3)
mean_right = np.mean(right_skewed_data)
median_right = np.median(right_skewed_data)
ax2.axvline(mean_right, color='red', linestyle='-', linewidth=2, label=f'均值={mean_right:.2f}')
ax2.axvline(median_right, color='green', linestyle='--', linewidth=2, label=f'中位数={median_right:.2f}')
ax2.legend()
# 3. 左偏分布(负偏度)
left_skewed_data = 10 - np.random.lognormal(0, 0.6, 1000)
skew_left = stats.skew(left_skewed_data)
ax3.hist(left_skewed_data, bins=30, density=True, alpha=0.7, color='green', edgecolor='black')
ax3.set_title(f'左偏分布(负偏)\n偏度 = {skew_left:.3f}')
ax3.grid(True, alpha=0.3)
mean_left = np.mean(left_skewed_data)
median_left = np.median(left_skewed_data)
ax3.axvline(mean_left, color='red', linestyle='-', linewidth=2, label=f'均值={mean_left:.2f}')
ax3.axvline(median_left, color='green', linestyle='--', linewidth=2, label=f'中位数={median_left:.2f}')
ax3.legend()
# 4-6. 箱线图对比
data_sets = [symmetric_data, right_skewed_data, left_skewed_data]
labels = ['对称分布', '右偏分布', '左偏分布']
colors = ['blue', 'orange', 'green']
for i, (data, label, color) in enumerate(zip(data_sets, labels, colors)):
ax = [ax4, ax5, ax6][i]
bp = ax.boxplot(data, patch_artist=True, labels=[label])
bp['boxes'][0].set_facecolor(color)
bp['boxes'][0].set_alpha(0.7)
# 标注关键统计量
q1, median, q3 = np.percentile(data, [25, 50, 75])
mean_val = np.mean(data)
ax.scatter([1], [mean_val], color='red', s=100, marker='D', label='均值')
ax.set_title(f'{label}\n偏度 = {stats.skew(data):.3f}')
ax.grid(True, alpha=0.3)
if i == 0:
ax.legend()
plt.tight_layout()
plt.show()
# 数值比较
print("偏度类型对比:")
print("=" * 50)
datasets = [
("对称分布", symmetric_data),
("右偏分布", right_skewed_data),
("左偏分布", left_skewed_data)
]
for name, data in datasets:
mean_val = np.mean(data)
median_val = np.median(data)
skew_val = stats.skew(data)
print(f"\n{name}:")
print(f" 均值: {mean_val:.3f}")
print(f" 中位数: {median_val:.3f}")
print(f" 偏度: {skew_val:.3f}")
print(f" 均值-中位数: {mean_val - median_val:.3f}")
if abs(skew_val) < 0.5:
skew_desc = "近似对称"
elif skew_val > 0:
if skew_val < 1:
skew_desc = "轻度右偏"
elif skew_val < 2:
skew_desc = "中度右偏"
else:
skew_desc = "高度右偏"
else:
if skew_val > -1:
skew_desc = "轻度左偏"
elif skew_val > -2:
skew_desc = "中度左偏"
else:
skew_desc = "高度左偏"
print(f" 偏度程度: {skew_desc}")
plot_skewness_types()
2. 偏度的计算方法
def calculate_skewness_methods():
"""演示偏度的不同计算方法"""
np.random.seed(42)
# 生成右偏数据
data = np.random.lognormal(1, 0.5, 1000)
print("偏度的计算方法:")
print("=" * 40)
# 1. 手动计算 - 第三中心矩方法
n = len(data)
mean = np.mean(data)
std = np.std(data, ddof=0) # 与三阶样本中心矩使用相同的n分母,对应stats.skew默认的未校正矩偏度
# 第三中心矩
third_moment = np.mean((data - mean)**3)
skew_manual = third_moment / (std**3)
print(f"1. 手动计算(第三中心矩法):")
print(f" 均值: {mean:.4f}")
print(f" 标准差: {std:.4f}")
print(f" 第三中心矩: {third_moment:.4f}")
print(f" 偏度: {skew_manual:.4f}")
# 2. Pearson第一偏度系数
median = np.median(data)
from scipy.stats import gaussian_kde
kde_first = gaussian_kde(data)
mode_grid = np.linspace(data.min(), data.max(), 1000)
mode_first = mode_grid[np.argmax(kde_first(mode_grid))]
pearson_skew1 = (mean - mode_first) / std
print(f"\n2. Pearson第一偏度系数:")
print(f" 公式: (均值 - 众数) / 标准差;本例众数由核密度峰值近似")
print(f" 偏度: {pearson_skew1:.4f}")
# 3. Pearson第二偏度系数
# 使用均值、中位数和标准差,不需要估计众数;下方核密度计算不参与第二系数
from scipy.stats import gaussian_kde
kde = gaussian_kde(data)
x_range = np.linspace(data.min(), data.max(), 1000)
kde_values = kde(x_range)
mode_approx = x_range[np.argmax(kde_values)]
pearson_skew2 = 3 * (mean - median) / std
print(f"\n3. Pearson第二偏度系数:")
print(f" 公式: 3 × (均值 - 中位数) / 标准差")
print(f" 偏度: {pearson_skew2:.4f}")
# 4. scipy.stats 计算
scipy_skew = stats.skew(data)
print(f"\n4. SciPy统计函数:")
print(f" 偏度: {scipy_skew:.4f}")
# 5. 未校正与偏差校正的样本偏度
unadjusted_skew = stats.skew(data, bias=True)
adjusted_skew = stats.skew(data, bias=False)
print(f"\n5. 样本偏度口径对比:")
print(f" 未校正矩偏度: {unadjusted_skew:.4f}")
print(f" 偏差校正样本偏度: {adjusted_skew:.4f}")
# 6. 偏度的置信区间
# 使用bootstrap方法
n_bootstrap = 1000
bootstrap_skews = []
for _ in range(n_bootstrap):
bootstrap_sample = np.random.choice(data, size=len(data), replace=True)
bootstrap_skews.append(stats.skew(bootstrap_sample))
skew_ci_lower = np.percentile(bootstrap_skews, 2.5)
skew_ci_upper = np.percentile(bootstrap_skews, 97.5)
print(f"\n6. 偏度的95%置信区间 (Bootstrap):")
print(f" [{skew_ci_lower:.4f}, {skew_ci_upper:.4f}]")
# 7. 样本量对偏度估计的影响
print(f"\n7. 样本量对偏度估计的影响:")
sample_sizes = [50, 100, 500, 1000, 5000]
true_data = np.random.lognormal(1, 0.5, 10000) # 真实总体
true_skew = stats.skew(true_data)
print(f" 真实偏度: {true_skew:.4f}")
print(" 样本量 -> 估计偏度 (标准误差)")
for size in sample_sizes:
sample_skews = []
for _ in range(100): # 100次重复采样
sample = np.random.choice(true_data, size=size, replace=False)
sample_skews.append(stats.skew(sample))
mean_skew = np.mean(sample_skews)
std_skew = np.std(sample_skews, ddof=1)
print(f" {size:4d} -> {mean_skew:.4f} ({std_skew:.4f})")
calculate_skewness_methods()
💻 Python实现
1. 基本计算与可视化
def skewness_analysis_toolkit():
"""偏度分析的完整工具包"""
def analyze_skewness(data, name="数据"):
"""分析数据的偏度"""
# 基本统计量
mean_val = np.mean(data)
median_val = np.median(data)
std_val = np.std(data, ddof=1)
skew_val = stats.skew(data)
# 偏度解释
if abs(skew_val) < 0.5:
skew_desc = "近似对称"
color = 'blue'
elif skew_val > 0:
if skew_val < 1:
skew_desc = "轻度右偏"
color = 'orange'
elif skew_val < 2:
skew_desc = "中度右偏"
color = 'red'
else:
skew_desc = "高度右偏"
color = 'darkred'
else:
if skew_val > -1:
skew_desc = "轻度左偏"
color = 'lightgreen'
elif skew_val > -2:
skew_desc = "中度左偏"
color = 'green'
else:
skew_desc = "高度左偏"
color = 'darkgreen'
print(f"{name}偏度分析:")
print("-" * 30)
print(f"样本量: {len(data)}")
print(f"均值: {mean_val:.4f}")
print(f"中位数: {median_val:.4f}")
print(f"标准差: {std_val:.4f}")
print(f"偏度: {skew_val:.4f}")
print(f"偏度程度: {skew_desc}")
print(f"均值-中位数: {mean_val - median_val:.4f}")
return {
'mean': mean_val,
'median': median_val,
'std': std_val,
'skewness': skew_val,
'description': skew_desc,
'color': color
}
def plot_skewness_analysis(data, name="数据", ax=None):
"""绘制偏度分析图"""
if ax is None:
fig, ax = plt.subplots(1, 1, figsize=(10, 6))
# 分析偏度
result = analyze_skewness(data, name)
# 绘制直方图
ax.hist(data, bins=30, density=True, alpha=0.7,
color=result['color'], edgecolor='black')
# 添加统计线
ax.axvline(result['mean'], color='red', linestyle='-',
linewidth=2, label=f"均值 = {result['mean']:.2f}")
ax.axvline(result['median'], color='blue', linestyle='--',
linewidth=2, label=f"中位数 = {result['median']:.2f}")
# 尝试拟合正态分布作为对比
x_range = np.linspace(data.min(), data.max(), 100)
normal_fit = stats.norm.pdf(x_range, result['mean'], result['std'])
ax.plot(x_range, normal_fit, 'g--', linewidth=2, alpha=0.7,
label="正态分布拟合")
ax.set_title(f'{name} - {result["description"]}\n偏度 = {result["skewness"]:.3f}')
ax.set_xlabel('值')
ax.set_ylabel('密度')
ax.legend()
ax.grid(True, alpha=0.3)
return result
# 示例应用:分析不同类型的数据
print("偏度分析工具包演示:")
print("=" * 50)
# 生成不同偏度的示例数据
np.random.seed(42)
datasets = {
"正态分布": np.random.normal(50, 10, 1000),
"收入分布": np.random.lognormal(3.5, 0.8, 1000),
"考试成绩": 100 - np.random.exponential(15, 1000),
"年龄分布": np.random.gamma(2, 15, 1000) + 18
}
# 分析每个数据集
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
axes = axes.flatten()
results = {}
for i, (name, data) in enumerate(datasets.items()):
# 演示固定分位数截尾,不等同于异常值识别;后续偏度仅描述截尾样本
data = data[(data > np.percentile(data, 1)) & (data < np.percentile(data, 99))]
print(f"\n{i+1}. {name}:")
result = plot_skewness_analysis(data, name, axes[i])
results[name] = result
plt.tight_layout()
plt.show()
# 偏度对比总结
print(f"\n偏度对比总结:")
print("=" * 50)
print(f"{'数据类型':<10} {'偏度':<8} {'程度':<10} {'均值-中位数':<12}")
print("-" * 50)
for name, result in results.items():
diff = result['mean'] - result['median']
print(f"{name:<10} {result['skewness']:<8.3f} {result['description']:<10} {diff:<12.3f}")
return analyze_skewness, plot_skewness_analysis
# 使用工具包
analyze_func, plot_func = skewness_analysis_toolkit()
2. 偏度检验
def skewness_tests():
"""偏度的统计检验方法"""
print("偏度统计检验:")
print("=" * 40)
# 生成测试数据
np.random.seed(42)
# 不同偏度的数据
test_datasets = {
"正态分布": np.random.normal(0, 1, 500),
"右偏分布": np.random.exponential(1, 500),
"左偏分布": -np.random.exponential(1, 500),
"轻度右偏": np.random.gamma(3, 1, 500)
}
for name, data in test_datasets.items():
print(f"\n{name}:")
print("-" * 20)
# 计算偏度
skew_val = stats.skew(data)
n = len(data)
print(f"样本偏度: {skew_val:.4f}")
# 1. D'Agostino偏度检验
# 使用正态总体参照检验偏度是否偏离零,不是任意分布的通用对称性检验
# stats.skewtest要求至少8个观测;本例各组有500个观测
z_skew, p_value_skew = stats.skewtest(data)
print(f"D'Agostino偏度检验(正态参照):")
print(f" 检验统计量 Z = {z_skew:.4f}")
print(f" p值 = {p_value_skew:.4f}")
if p_value_skew < 0.05:
print(f" 结论: 在正态参照下,样本偏度显著偏离零 (p < 0.05);这不是一般对称性检验")
else:
print(f" 结论: 在正态参照下,未发现偏度显著偏离零 (p ≥ 0.05);不能证明分布对称或正态")
# 2. Jarque-Bera检验 (偏度和峰度的联合检验)
jb_stat, jb_pvalue = stats.jarque_bera(data)
print(f"Jarque-Bera检验:")
print(f" JB统计量 = {jb_stat:.4f}")
print(f" p值 = {jb_pvalue:.4f}")
if jb_pvalue < 0.05:
print(f" 结论: 拒绝正态性假设 (p < 0.05)")
else:
print(f" 结论: 未拒绝正态性假设 (p ≥ 0.05),不代表已证明正态性")
# 3. 偏度的置信区间
# 使用bootstrap方法
n_bootstrap = 1000
bootstrap_skews = []
for _ in range(n_bootstrap):
bootstrap_sample = np.random.choice(data, size=len(data), replace=True)
bootstrap_skews.append(stats.skew(bootstrap_sample))
ci_lower = np.percentile(bootstrap_skews, 2.5)
ci_upper = np.percentile(bootstrap_skews, 97.5)
print(f"偏度95%置信区间: [{ci_lower:.4f}, {ci_upper:.4f}]")
# 判断偏度区间是否包含0;包含0不能证明对称性
if ci_lower <= 0 <= ci_upper:
print(f" 置信区间包含0,当前区间估计未排除零偏度;不能据此证明分布对称")
else:
print(f" 置信区间不包含0,支持非对称性")
skewness_tests()
🎯 实际应用场景
1. 金融数据分析
def financial_skewness_analysis():
"""金融数据的偏度分析"""
np.random.seed(42)
print("金融数据偏度分析:")
print("=" * 40)
# 模拟不同类型股票的收益率数据
trading_days = 252 # 一年的交易日
# 不同风险特征的股票
stocks = {
"蓝筹股": np.random.normal(0.0008, 0.02, trading_days), # 低风险,接近对称
"成长股": np.random.lognormal(-0.001, 0.03, trading_days) - 1, # 轻度右偏
"小盘股": np.concatenate([
np.random.normal(0.001, 0.025, int(trading_days*0.8)), # 大部分正常交易
np.random.normal(-0.05, 0.01, trading_days - int(trading_days*0.8)) # 偶尔大跌
]), # 左偏
"科技股": np.concatenate([
np.random.normal(0.002, 0.03, int(trading_days*0.9)),
np.random.normal(0.08, 0.02, trading_days - int(trading_days*0.9)) # 偶尔暴涨
]) # 右偏
}
# 分析每只股票
analysis_results = {}
for stock_name, returns in stocks.items():
# 转换为百分比
returns_pct = returns * 100
# 基本统计
mean_return = np.mean(returns_pct)
std_return = np.std(returns_pct, ddof=1)
skew_return = stats.skew(returns_pct)
# 风险指标
var_95 = np.percentile(-returns_pct, 95) # 损失=-收益率,采用95%损失分位数作为VaR;单位为百分比
# 偏度的风险含义
if skew_return > 0:
risk_interpretation = "样本矩偏度为正;极端正收益概率需结合具体阈值和尾部分布另行估计"
elif skew_return < 0:
risk_interpretation = "样本矩偏度为负;极端负收益概率和损失风险需结合尾部分布另行估计"
else:
risk_interpretation = "样本矩偏度为零;不能据此判断正负极端收益概率相当"
analysis_results[stock_name] = {
'returns': returns_pct,
'mean': mean_return,
'std': std_return,
'skewness': skew_return,
'var_95': var_95,
'risk_interpretation': risk_interpretation
}
print(f"\n{stock_name}:")
print(f" 年化收益率: {mean_return * 252:.2f}%")
print(f" 年化波动率: {std_return * np.sqrt(252):.2f}%")
print(f" 偏度: {skew_return:.4f}")
print(f" 95% VaR: {var_95:.2f}%")
print(f" 风险特征: {risk_interpretation}")
# 可视化对比
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
axes = [ax1, ax2, ax3, ax4]
for i, (stock_name, result) in enumerate(analysis_results.items()):
returns = result['returns']
# 绘制收益率分布
axes[i].hist(returns, bins=30, density=True, alpha=0.7,
color=plt.cm.Set1(i), edgecolor='black')
# 标注统计量
axes[i].axvline(result['mean'], color='red', linestyle='-',
linewidth=2, label=f"均值={result['mean']:.3f}%")
axes[i].axvline(np.median(returns), color='blue', linestyle='--',
linewidth=2, label=f"中位数={np.median(returns):.3f}%")
axes[i].axvline(result['var_95'], color='orange', linestyle=':',
linewidth=2, label=f"95% VaR={result['var_95']:.2f}%")
# 拟合正态分布对比
x_range = np.linspace(returns.min(), returns.max(), 100)
normal_fit = stats.norm.pdf(x_range, result['mean'], result['std'])
axes[i].plot(x_range, normal_fit, 'g--', alpha=0.7,
label="正态分布拟合")
axes[i].set_title(f'{stock_name}\n偏度 = {result["skewness"]:.3f}')
axes[i].set_xlabel('日收益率 (%)')
axes[i].set_ylabel('密度')
axes[i].legend()
axes[i].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 投资组合偏度分析
print(f"\n投资组合偏度分析:")
print("-" * 25)
# 等权重投资组合
portfolio_returns = np.mean([result['returns'] for result in analysis_results.values()], axis=0)
portfolio_skew = stats.skew(portfolio_returns)
print(f"等权重投资组合偏度: {portfolio_skew:.4f}")
# 不同权重的影响
print(f"\n不同权重配置的偏度影响:")
weight_scenarios = {
"保守型": [0.6, 0.2, 0.1, 0.1], # 重配蓝筹股
"平衡型": [0.25, 0.25, 0.25, 0.25], # 等权重
"激进型": [0.1, 0.2, 0.3, 0.4], # 重配小盘股和科技股
}
stock_names = list(analysis_results.keys())
for scenario_name, weights in weight_scenarios.items():
weighted_returns = np.zeros(trading_days)
for i, (stock_name, weight) in enumerate(zip(stock_names, weights)):
weighted_returns += weight * analysis_results[stock_name]['returns']
scenario_skew = stats.skew(weighted_returns)
scenario_var = np.percentile(-weighted_returns, 95) # 与单资产一致的损失口径VaR
print(f"{scenario_name}: 偏度={scenario_skew:.4f}, 95% VaR={scenario_var:.2f}%")
# 偏度交易策略建议
print(f"\n基于偏度的投资建议:")
print("-" * 25)
print("1. 负偏度资产需要更高的风险溢价补偿")
print("2. 正偏度资产可能被高估(彩票效应)")
print("3. 组合构建时考虑偏度分散化")
print("4. 使用期权等衍生品对冲偏度风险")
financial_skewness_analysis()
2. 业务数据分析
def business_skewness_analysis():
"""电商业务数据的偏度分析"""
np.random.seed(42)
print("电商业务数据偏度分析:")
print("=" * 40)
# 模拟电商平台的各种业务数据
n_customers = 10000
# 不同业务指标的数据生成
business_data = {
"订单金额": np.concatenate([
np.random.lognormal(3.5, 0.8, int(n_customers * 0.8)), # 普通订单
np.random.lognormal(6, 0.5, int(n_customers * 0.2)) # 大额订单
]),
"客户年龄": np.concatenate([
np.random.normal(35, 8, int(n_customers * 0.6)), # 主力客户群
np.random.normal(22, 3, int(n_customers * 0.3)), # 年轻客户
np.random.normal(55, 5, int(n_customers * 0.1)) # 老年客户
]),
"月购买次数": np.random.poisson(2.5, n_customers), # 泊松分布,右偏
"客户评分": np.concatenate([
1 + np.random.beta(8, 2, int(n_customers * 0.7)) * 4, # 大多数高评分
np.random.uniform(1, 5, int(n_customers * 0.3)) # 少数低评分
]),
"页面停留时间": np.random.lognormal(2, 1, n_customers) # 对数正态分布,明显右偏
}
# 分析每个业务指标
print("各业务指标偏度分析:")
print("-" * 30)
skewness_results = {}
for metric_name, data in business_data.items():
# 演示固定分位数截尾,不等同于异常值识别;保留样本的偏度不能代表原始尾部
q1, q99 = np.percentile(data, [1, 99])
clean_data = data[(data >= q1) & (data <= q99)]
# 计算统计量
mean_val = np.mean(clean_data)
median_val = np.median(clean_data)
std_val = np.std(clean_data, ddof=1)
skew_val = stats.skew(clean_data)
# 业务解释
if metric_name == "订单金额":
if skew_val > 1:
business_insight = "少数大额订单拉高平均值,需关注高价值客户"
else:
business_insight = "订单金额分布相对均匀"
elif metric_name == "客户年龄":
if abs(skew_val) < 0.5:
business_insight = "客户年龄样本的矩偏度较低;是否均匀及群体构成需结合分布图和分组占比判断"
elif skew_val > 0:
business_insight = "年轻客户占主导,老年客户较少"
else:
business_insight = "老年客户占主导,年轻客户较少"
elif metric_name == "月购买次数":
if skew_val > 0:
business_insight = "大多数客户购买频次低,少数客户高频购买"
else:
business_insight = "客户购买频次分布相对均匀"
elif metric_name == "客户评分":
if skew_val < 0:
business_insight = "客户满意度较高,但需关注少数不满意客户"
else:
business_insight = "客户评分分布需要改善"
else: # 页面停留时间
if skew_val > 1:
business_insight = "大多数用户浏览时间短,少数用户深度浏览"
else:
business_insight = "用户浏览时间分布相对均匀"
skewness_results[metric_name] = {
'data': clean_data,
'mean': mean_val,
'median': median_val,
'std': std_val,
'skewness': skew_val,
'insight': business_insight
}
print(f"\n{metric_name}:")
print(f" 均值: {mean_val:.2f}")
print(f" 中位数: {median_val:.2f}")
print(f" 偏度: {skew_val:.3f}")
print(f" 业务洞察: {business_insight}")
# 可视化分析
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
axes = axes.flatten()
for i, (metric_name, result) in enumerate(skewness_results.items()):
if i >= len(axes):
break
data = result['data']
# 绘制直方图
axes[i].hist(data, bins=50, density=True, alpha=0.7,
color=plt.cm.Set3(i), edgecolor='black')
# 添加统计线
axes[i].axvline(result['mean'], color='red', linestyle='-',
linewidth=2, label=f"均值={result['mean']:.1f}")
axes[i].axvline(result['median'], color='blue', linestyle='--',
linewidth=2, label=f"中位数={result['median']:.1f}")
# 标题和标签
axes[i].set_title(f'{metric_name}\n偏度 = {result["skewness"]:.3f}')
if metric_name == "订单金额":
axes[i].set_xlabel('订单金额 (元)')
elif metric_name == "客户年龄":
axes[i].set_xlabel('年龄 (岁)')
elif metric_name == "月购买次数":
axes[i].set_xlabel('购买次数')
elif metric_name == "客户评分":
axes[i].set_xlabel('评分 (1-5分)')
else:
axes[i].set_xlabel('停留时间 (分钟)')
axes[i].set_ylabel('密度')
axes[i].legend()
axes[i].grid(True, alpha=0.3)
# 删除多余的子图
if len(skewness_results) < len(axes):
for j in range(len(skewness_results), len(axes)):
fig.delaxes(axes[j])
plt.tight_layout()
plt.show()
# 业务策略建议
print(f"\n基于偏度分析的业务策略建议:")
print("=" * 40)
for metric_name, result in skewness_results.items():
skew_val = result['skewness']
print(f"\n{metric_name}:")
if metric_name == "订单金额" and skew_val > 1:
print(" • 针对高价值客户制定专属服务策略")
print(" • 通过套餐、满减等促销提升客单价")
print(" • 关注长尾客户的需求挖掘")
elif metric_name == "客户年龄":
if skew_val > 0.5:
print(" • 加强对老年客户群体的开发")
print(" • 优化产品和服务适应年龄差异")
elif skew_val < -0.5:
print(" • 加强对年轻客户群体的开发")
print(" • 关注新兴消费趋势和偏好")
elif metric_name == "月购买次数" and skew_val > 1:
print(" • 识别和维护高频购买客户")
print(" • 制定客户忠诚度计划")
print(" • 分析低频客户的购买障碍")
elif metric_name == "客户评分" and skew_val < -0.5:
print(" • 重点改善少数低评分订单的问题")
print(" • 建立客户反馈快速响应机制")
print(" • 持续优化产品和服务质量")
elif metric_name == "页面停留时间" and skew_val > 1:
print(" • 分析深度浏览用户的行为特征")
print(" • 优化页面设计提高用户参与度")
print(" • 针对不同浏览深度制定营销策略")
# 客户细分建议
print(f"\n客户细分建议:")
print("-" * 15)
# 基于订单金额和购买频次的二维细分
order_amounts = skewness_results["订单金额"]["data"]
purchase_freq = skewness_results["月购买次数"]["data"]
# 创建客户价值矩阵
amount_high = order_amounts > np.percentile(order_amounts, 70)
freq_high = purchase_freq > np.percentile(purchase_freq, 70)
segment_counts = {
"高价值高频客户": np.sum(amount_high & freq_high),
"高价值低频客户": np.sum(amount_high & ~freq_high),
"低价值高频客户": np.sum(~amount_high & freq_high),
"低价值低频客户": np.sum(~amount_high & ~freq_high)
}
for segment, count in segment_counts.items():
percentage = count / len(order_amounts) * 100
print(f"{segment}: {count}人 ({percentage:.1f}%)")
business_skewness_analysis()
3. 质量控制中的偏度应用
def quality_control_skewness():
"""质量控制中的偏度分析"""
np.random.seed(42)
print("产品质量偏度分析:")
print("=" * 40)
# 模拟不同产品线的质量数据
n_samples = 1000
quality_data = {
"A产品线": np.random.normal(100, 2, n_samples), # 正态分布,符合规格
"B产品线": np.concatenate([
np.random.normal(100, 1.5, int(n_samples * 0.9)), # 大部分正常
np.random.normal(95, 1, int(n_samples * 0.1)) # 少数偏低
]), # 左偏,可能有系统性问题
"C产品线": np.concatenate([
np.random.normal(100, 1.5, int(n_samples * 0.85)), # 大部分正常
np.random.normal(105, 1, int(n_samples * 0.15)) # 少数偏高
]), # 右偏,可能过度加工
"D产品线": np.random.uniform(98, 102, n_samples) # 模拟均匀分布;仅凭边际分布不能判断过程是否稳定
}
# 质量规格
target_value = 100
lower_spec = 95
upper_spec = 105
print("质量规格:")
print(f"目标值: {target_value}")
print(f"规格范围: [{lower_spec}, {upper_spec}]")
# 分析每条产品线
quality_results = {}
for product_line, data in quality_data.items():
# 基本统计
mean_val = np.mean(data)
std_val = np.std(data, ddof=1)
skew_val = stats.skew(data)
# 过程能力分析
cp = (upper_spec - lower_spec) / (6 * std_val) # 过程能力
cpk = min((upper_spec - mean_val) / (3 * std_val),
(mean_val - lower_spec) / (3 * std_val)) # 过程能力指数
# 合格率计算
within_spec = np.sum((data >= lower_spec) & (data <= upper_spec))
conformance_rate = within_spec / len(data)
# 偏度对质量的影响分析
if abs(skew_val) < 0.5:
skew_impact = "样本矩偏度较低;不能仅凭偏度判断对称性或质量稳定性"
risk_level = "低"
elif skew_val > 0.5:
if np.mean(data > upper_spec) > 0.01:
skew_impact = "右偏分布,存在过度加工风险"
risk_level = "中"
else:
skew_impact = "右偏分布,但在规格范围内"
risk_level = "低"
else: # skew_val < -0.5
if np.mean(data < lower_spec) > 0.01:
skew_impact = "左偏分布,存在不合格品风险"
risk_level = "高"
else:
skew_impact = "左偏分布,但在规格范围内"
risk_level = "中"
quality_results[product_line] = {
'data': data,
'mean': mean_val,
'std': std_val,
'skewness': skew_val,
'cp': cp,
'cpk': cpk,
'conformance_rate': conformance_rate,
'skew_impact': skew_impact,
'risk_level': risk_level
}
print(f"\n{product_line}:")
print(f" 均值: {mean_val:.2f}")
print(f" 标准差: {std_val:.2f}")
print(f" 偏度: {skew_val:.3f}")
print(f" Cp: {cp:.3f}")
print(f" Cpk: {cpk:.3f}")
print(f" 合格率: {conformance_rate:.1%}")
print(f" 偏度影响: {skew_impact}")
print(f" 风险等级: {risk_level}")
# 可视化质量分布
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
axes = [ax1, ax2, ax3, ax4]
for i, (product_line, result) in enumerate(quality_results.items()):
data = result['data']
# 绘制直方图
axes[i].hist(data, bins=40, density=True, alpha=0.7,
color=plt.cm.Set2(i), edgecolor='black')
# 添加规格线
axes[i].axvline(target_value, color='green', linestyle='-',
linewidth=2, label=f'目标值={target_value}')
axes[i].axvline(lower_spec, color='red', linestyle='--',
linewidth=2, label=f'下限={lower_spec}')
axes[i].axvline(upper_spec, color='red', linestyle='--',
linewidth=2, label=f'上限={upper_spec}')
axes[i].axvline(result['mean'], color='blue', linestyle=':',
linewidth=2, label=f"均值={result['mean']:.1f}")
# 标注不合格区域
x_range = np.linspace(data.min(), data.max(), 1000)
y_range = stats.norm.pdf(x_range, result['mean'], result['std'])
# 填充不合格区域
x_lower = x_range[x_range < lower_spec]
if len(x_lower) > 0:
y_lower = stats.norm.pdf(x_lower, result['mean'], result['std'])
axes[i].fill_between(x_lower, y_lower, alpha=0.3, color='red')
x_upper = x_range[x_range > upper_spec]
if len(x_upper) > 0:
y_upper = stats.norm.pdf(x_upper, result['mean'], result['std'])
axes[i].fill_between(x_upper, y_upper, alpha=0.3, color='red')
axes[i].set_title(f'{product_line}\n偏度={result["skewness"]:.3f}, Cpk={result["cpk"]:.3f}')
axes[i].set_xlabel('测量值')
axes[i].set_ylabel('密度')
axes[i].legend()
axes[i].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 改进建议
print(f"\n质量改进建议:")
print("=" * 20)
for product_line, result in quality_results.items():
print(f"\n{product_line}:")
if result['risk_level'] == "高":
print(" 🚨 紧急改进需要:")
print(" - 立即调查导致左偏的根本原因")
print(" - 检查设备校准和工艺参数")
print(" - 加强过程监控和质量检验")
elif result['risk_level'] == "中":
print(" ⚠️ 建议改进:")
if result['skewness'] > 0:
print(" - 检查是否存在过度加工")
print(" - 优化工艺参数,避免过量投入")
else:
print(" - 监控过程趋势,防止质量下滑")
print(" - 适当调整工艺参数")
else:
print(" ✅ 质量良好:")
print(" - 维持现有工艺水平")
print(" - 继续监控过程稳定性")
# 基于Cpk的建议
if result['cpk'] < 1.0:
print(" - Cpk偏低,需要提高过程能力")
elif result['cpk'] < 1.33:
print(" - Cpk可接受,但仍有提升空间")
else:
print(" - Cpk良好,过程能力充足")
# 过程监控建议
print(f"\n过程监控建议:")
print("-" * 15)
print("1. 建立基于偏度的预警系统")
print("2. 当偏度超过±0.5时触发调查")
print("3. 结合Cpk和偏度进行综合评估")
print("4. 定期进行偏度趋势分析")
print("5. 针对不同偏度类型制定相应的纠正措施")
quality_control_skewness()
🛠️ 实用技巧
1. 偏度的数据预处理
def handle_skewed_data():
"""处理偏斜数据的各种方法"""
np.random.seed(42)
# 生成强右偏数据
skewed_data = np.random.lognormal(2, 1, 1000)
print("处理偏斜数据的方法:")
print("=" * 40)
# 原始数据分析
original_skew = stats.skew(skewed_data)
print(f"原始数据偏度: {original_skew:.4f}")
# 方法1: 对数变换
log_data = np.log(skewed_data)
log_skew = stats.skew(log_data)
# 方法2: 平方根变换
sqrt_data = np.sqrt(skewed_data)
sqrt_skew = stats.skew(sqrt_data)
# 方法3: Box-Cox变换
from scipy.stats import boxcox
boxcox_data, lambda_param = boxcox(skewed_data)
boxcox_skew = stats.skew(boxcox_data)
# 方法4: Yeo-Johnson变换(可处理负值)
from scipy.stats import yeojohnson
yj_data, yj_lambda = yeojohnson(skewed_data)
yj_skew = stats.skew(yj_data)
# 方法5: 分位数变换
from sklearn.preprocessing import QuantileTransformer
qt = QuantileTransformer(output_distribution='normal', random_state=42)
qt_data = qt.fit_transform(skewed_data.reshape(-1, 1)).flatten()
qt_skew = stats.skew(qt_data)
# 结果对比
transformations = {
"原始数据": (skewed_data, original_skew),
"对数变换": (log_data, log_skew),
"平方根变换": (sqrt_data, sqrt_skew),
"Box-Cox变换": (boxcox_data, boxcox_skew),
"Yeo-Johnson变换": (yj_data, yj_skew),
"分位数变换": (qt_data, qt_skew)
}
print(f"\n变换效果对比:")
print(f"{'方法':<15} {'偏度':<10} {'改善程度':<10}")
print("-" * 40)
for method, (data, skew_val) in transformations.items():
improvement = (abs(original_skew) - abs(skew_val)) / abs(original_skew) * 100
if method == "原始数据":
print(f"{method:<15} {skew_val:<10.4f} {'--':<10}")
else:
print(f"{method:<15} {skew_val:<10.4f} {improvement:<10.1f}%")
# 可视化对比
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
axes = axes.flatten()
for i, (method, (data, skew_val)) in enumerate(transformations.items()):
axes[i].hist(data, bins=30, density=True, alpha=0.7,
color=plt.cm.tab10(i), edgecolor='black')
axes[i].set_title(f'{method}\n偏度 = {skew_val:.3f}')
axes[i].set_ylabel('密度')
axes[i].grid(True, alpha=0.3)
# 添加正态分布拟合
mean_val = np.mean(data)
std_val = np.std(data, ddof=1)
x_range = np.linspace(data.min(), data.max(), 100)
normal_fit = stats.norm.pdf(x_range, mean_val, std_val)
axes[i].plot(x_range, normal_fit, 'r--', alpha=0.7, label='正态拟合')
axes[i].legend()
plt.tight_layout()
plt.show()
# 变换选择建议
print(f"\n变换方法选择建议:")
print("-" * 20)
print("1. 对数变换: 适用于右偏数据,数据必须为正")
print("2. 平方根变换: 适用于轻度右偏,数据必须非负")
print("3. Box-Cox变换: 自动寻找最优变换,数据必须为正")
print("4. Yeo-Johnson变换: 类似Box-Cox,但可处理负值")
print("5. 分位数变换: 强制转换为指定分布,但会改变数据关系")
print(f"\n最佳变换参数:")
print(f"Box-Cox λ = {lambda_param:.4f}")
print(f"Yeo-Johnson λ = {yj_lambda:.4f}")
return transformations
# 使用函数
transformations = handle_skewed_data()
2. 偏度监控系统
def skewness_monitoring_system():
"""构建偏度监控预警系统"""
class SkewnessMonitor:
def __init__(self, threshold=0.5, window_size=100):
self.threshold = threshold
self.window_size = window_size
self.data_buffer = []
self.skewness_history = []
self.alerts = []
def add_data(self, new_data):
"""添加新数据点"""
if isinstance(new_data, (list, np.ndarray)):
self.data_buffer.extend(new_data)
else:
self.data_buffer.append(new_data)
# 保持缓冲区大小
if len(self.data_buffer) > self.window_size * 2:
self.data_buffer = self.data_buffer[-self.window_size:]
# 计算当前偏度
if len(self.data_buffer) >= self.window_size:
current_skew = stats.skew(self.data_buffer[-self.window_size:])
self.skewness_history.append(current_skew)
# 检查是否需要预警
self._check_alert(current_skew)
return current_skew
return None
def _check_alert(self, current_skew):
"""检查是否触发预警"""
if abs(current_skew) > self.threshold:
alert_type = "右偏预警" if current_skew > 0 else "左偏预警"
self.alerts.append({
'timestamp': len(self.skewness_history) - 1,
'type': alert_type,
'skewness': current_skew,
'severity': self._get_severity(current_skew)
})
def _get_severity(self, skew_val):
"""获取预警严重程度"""
abs_skew = abs(skew_val)
if abs_skew > 2:
return "严重"
elif abs_skew > 1:
return "中等"
else:
return "轻微"
def get_status(self):
"""获取当前监控状态"""
if not self.skewness_history:
return "无数据"
current_skew = self.skewness_history[-1]
if abs(current_skew) <= self.threshold:
return "正常"
elif abs(current_skew) <= 1.0:
return "注意"
elif abs(current_skew) <= 2.0:
return "警告"
else:
return "严重"
def plot_monitoring(self):
"""绘制监控图表"""
if not self.skewness_history:
print("无数据可绘制")
return
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
# 偏度时间序列
timestamps = range(len(self.skewness_history))
ax1.plot(timestamps, self.skewness_history, 'b-', linewidth=2, label='偏度')
ax1.axhline(y=self.threshold, color='orange', linestyle='--',
label=f'预警阈值 ±{self.threshold}')
ax1.axhline(y=-self.threshold, color='orange', linestyle='--')
ax1.axhline(y=1, color='red', linestyle=':', alpha=0.7, label='高风险线 ±1')
ax1.axhline(y=-1, color='red', linestyle=':', alpha=0.7)
# 标记预警点
for alert in self.alerts:
color = {'轻微': 'yellow', '中等': 'orange', '严重': 'red'}[alert['severity']]
ax1.scatter(alert['timestamp'], alert['skewness'],
color=color, s=100, marker='^',
label=f"{alert['type']} ({alert['severity']})")
ax1.set_title('偏度监控时间序列')
ax1.set_xlabel('时间点')
ax1.set_ylabel('偏度值')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 最近数据分布
recent_data = self.data_buffer[-self.window_size:]
ax2.hist(recent_data, bins=30, density=True, alpha=0.7,
color='skyblue', edgecolor='black')
ax2.set_title(f'最近{len(recent_data)}个数据点的分布')
ax2.set_xlabel('数值')
ax2.set_ylabel('密度')
ax2.grid(True, alpha=0.3)
# 标注当前偏度
if self.skewness_history:
current_skew = self.skewness_history[-1]
ax2.text(0.02, 0.98, f'当前偏度: {current_skew:.3f}',
transform=ax2.transAxes, verticalalignment='top',
bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.8))
plt.tight_layout()
plt.show()
def generate_report(self):
"""生成监控报告"""
print("偏度监控报告")
print("=" * 30)
if not self.skewness_history:
print("无监控数据")
return
current_skew = self.skewness_history[-1]
status = self.get_status()
print(f"当前状态: {status}")
print(f"当前偏度: {current_skew:.4f}")
print(f"监控点数: {len(self.skewness_history)}")
print(f"预警次数: {len(self.alerts)}")
if self.alerts:
print(f"\n最近预警:")
for alert in self.alerts[-3:]: # 显示最近3次预警
print(f" 时间点{alert['timestamp']}: {alert['type']} "
f"(偏度={alert['skewness']:.3f}, {alert['severity']})")
# 演示监控系统
print("偏度监控系统演示:")
print("=" * 40)
# 创建监控器
monitor = SkewnessMonitor(threshold=0.5, window_size=50)
# 模拟数据流
np.random.seed(42)
# 第1阶段:正常数据
normal_data = np.random.normal(100, 10, 100)
for data_point in normal_data:
monitor.add_data(data_point)
print("第1阶段 (正常数据) 完成")
print(f"状态: {monitor.get_status()}")
# 第2阶段:逐渐变偏
skewed_data = np.random.lognormal(4.5, 0.3, 100)
for data_point in skewed_data:
monitor.add_data(data_point)
print(f"\n第2阶段 (偏斜数据) 完成")
print(f"状态: {monitor.get_status()}")
# 第3阶段:恢复正常
recovery_data = np.random.normal(100, 10, 50)
for data_point in recovery_data:
monitor.add_data(data_point)
print(f"\n第3阶段 (恢复正常) 完成")
print(f"状态: {monitor.get_status()}")
# 生成报告和图表
monitor.generate_report()
monitor.plot_monitoring()
return monitor
# 使用监控系统
monitor = skewness_monitoring_system()
🔗 相关知识
#统计学 #偏度 #描述性统计 #分布形状 #偏斜程度 #基础