《DataFrame可视化与高级方法》
让数据自己"说话":掌握Pandas可视化与高级方法,从数据探索到深度洞察
一、数据可视化
数据可视化是将抽象数据转化为直观图形的艺术。在数据分析中,一张好的图表抵得上千言万语。Pandas内置的绘图功能基于Matplotlib,让数据探索变得更加简单。
1.1 plot方法:多图表类型一站式解决
plot方法是Pandas中最强大的可视化工具,支持10多种图表类型,满足大多数数据展示需求。
python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 设置中文字体和图表样式
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
plt.style.use('seaborn-v0_8')
# 创建示例数据集
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=24, freq='M')
company_data = pd.DataFrame({
'日期': dates,
'销售额': np.random.randint(1000, 5000, 24) + np.linspace(0, 2000, 24),
'利润': np.random.randint(200, 1000, 24) + np.linspace(0, 800, 24),
'成本': np.random.randint(500, 3000, 24) + np.linspace(0, 1200, 24),
'员工数': np.random.randint(50, 150, 24),
'客户满意度': np.random.uniform(3.5, 4.8, 24)
})
# 设置日期为索引
company_data.set_index('日期', inplace=True)
print("公司经营数据:")
print(company_data.head())
1.1.1 折线图:趋势分析的最佳选择
折线图适合展示数据随时间变化的趋势,是时间序列分析的首选。
python
print("=== 折线图分析 ===")
# 创建画布
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
fig.suptitle('公司经营趋势分析', fontsize=16, fontweight='bold')
# 1. 单线图:销售额趋势
company_data['销售额'].plot(
ax=axes[0, 0],
title='销售额趋势',
style='b-o',
linewidth=2,
markersize=6,
grid=True,
alpha=0.8
)
axes[0, 0].set_xlabel('日期')
axes[0, 0].set_ylabel('销售额(万元)')
axes[0, 0].tick_params(axis='x', rotation=45)
# 2. 多线图:销售额、利润、成本对比
company_data[['销售额', '利润', '成本']].plot(
ax=axes[0, 1],
title='经营指标对比',
linewidth=2,
grid=True,
alpha=0.8
)
axes[0, 1].set_xlabel('日期')
axes[0, 1].set_ylabel('金额(万元)')
axes[0, 1].tick_params(axis='x', rotation=45)
axes[0, 1].legend(loc='best')
# 3. 面积图:成本结构
company_data[['利润', '成本']].plot.area(
ax=axes[0, 2],
title='利润与成本面积图',
alpha=0.5,
grid=True
)
axes[0, 2].set_xlabel('日期')
axes[0, 2].set_ylabel('金额(万元)')
axes[0, 2].tick_params(axis='x', rotation=45)
# 4. 双Y轴图:销售额与员工数关系
ax1 = axes[1, 0]
ax2 = ax1.twinx()
color1 = 'tab:blue'
color2 = 'tab:red'
ax1.plot(company_data.index, company_data['销售额'], color=color1, linewidth=2, label='销售额')
ax1.set_xlabel('日期')
ax1.set_ylabel('销售额(万元)', color=color1)
ax1.tick_params(axis='y', labelcolor=color1)
ax1.tick_params(axis='x', rotation=45)
ax2.plot(company_data.index, company_data['员工数'], color=color2, linestyle='--', linewidth=2, label='员工数')
ax2.set_ylabel('员工数', color=color2)
ax2.tick_params(axis='y', labelcolor=color2)
axes[1, 0].set_title('销售额 vs 员工数(双Y轴)')
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
# 5. 带标记的特殊点
axes[1, 1].plot(company_data.index, company_data['客户满意度'], 'g-', linewidth=2, marker='o', markersize=6)
# 标记最高点和最低点
max_idx = company_data['客户满意度'].idxmax()
min_idx = company_data['客户满意度'].idxmin()
axes[1, 1].plot(max_idx, company_data.loc[max_idx, '客户满意度'], 'r^', markersize=10, label='最高点')
axes[1, 1].plot(min_idx, company_data.loc[min_idx, '客户满意度'], 'rv', markersize=10, label='最低点')
axes[1, 1].annotate(f'{company_data.loc[max_idx, "客户满意度"]:.2f}',
xy=(max_idx, company_data.loc[max_idx, '客户满意度']),
xytext=(5, 5), textcoords='offset points')
axes[1, 1].annotate(f'{company_data.loc[min_idx, "客户满意度"]:.2f}',
xy=(min_idx, company_data.loc[min_idx, '客户满意度']),
xytext=(5, -15), textcoords='offset points')
axes[1, 1].set_title('客户满意度趋势')
axes[1, 1].set_xlabel('日期')
axes[1, 1].set_ylabel('客户满意度')
axes[1, 1].tick_params(axis='x', rotation=45)
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)
# 6. 子图:多指标展示
company_data.plot(subplots=True, layout=(2, 2), figsize=(12, 8),
title='经营指标子图展示', grid=True,
legend=False, sharex=True)
axes[1, 2].axis('off') # 隐藏最后一个子图
plt.tight_layout()
plt.show()
# 交互式图表(需要plotly,可选)
try:
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# 创建交互式图表
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('销售额趋势', '利润趋势', '成本趋势', '客户满意度趋势'),
specs=[[{'type': 'scatter'}, {'type': 'scatter'}],
[{'type': 'scatter'}, {'type': 'scatter'}]]
)
# 添加销售额轨迹
fig.add_trace(
go.Scatter(x=company_data.index, y=company_data['销售额'],
mode='lines+markers', name='销售额',
line=dict(color='blue', width=2)),
row=1, col=1
)
# 添加利润轨迹
fig.add_trace(
go.Scatter(x=company_data.index, y=company_data['利润'],
mode='lines+markers', name='利润',
line=dict(color='green', width=2)),
row=1, col=2
)
# 添加成本轨迹
fig.add_trace(
go.Scatter(x=company_data.index, y=company_data['成本'],
mode='lines+markers', name='成本',
line=dict(color='red', width=2)),
row=2, col=1
)
# 添加客户满意度轨迹
fig.add_trace(
go.Scatter(x=company_data.index, y=company_data['客户满意度'],
mode='lines+markers', name='客户满意度',
line=dict(color='orange', width=2)),
row=2, col=2
)
fig.update_layout(height=800, title_text="公司经营数据交互式仪表板")
fig.show()
except ImportError:
print("\n提示:安装plotly库可生成交互式图表")
print("命令:pip install plotly")
1.1.2 柱状图:对比分析的利器
柱状图适合展示分类数据对比,特别是不同类别之间的差异。
python
print("=== 柱状图分析 ===")
# 创建月度汇总数据
monthly_data = company_data.copy()
monthly_data['月份'] = monthly_data.index.strftime('%Y-%m')
monthly_summary = monthly_data.groupby('月份').agg({
'销售额': 'sum',
'利润': 'sum',
'成本': 'sum'
}).round(2)
# 取最近6个月数据
recent_months = monthly_summary.tail(6)
# 创建画布
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle('月度经营对比分析', fontsize=16, fontweight='bold')
# 1. 垂直柱状图:销售额对比
recent_months['销售额'].plot.bar(
ax=axes[0, 0],
title='各月销售额对比',
color='skyblue',
edgecolor='black',
alpha=0.8,
rot=45
)
axes[0, 0].set_xlabel('月份')
axes[0, 0].set_ylabel('销售额(万元)')
axes[0, 0].grid(axis='y', alpha=0.3)
# 添加数值标签
for i, v in enumerate(recent_months['销售额']):
axes[0, 0].text(i, v + 50, f'{v:,.0f}', ha='center', va='bottom', fontsize=9)
# 2. 水平柱状图:利润对比
recent_months['利润'].plot.barh(
ax=axes[0, 1],
title='各月利润对比',
color='lightgreen',
edgecolor='black',
alpha=0.8
)
axes[0, 1].set_xlabel('利润(万元)')
axes[0, 1].set_ylabel('月份')
axes[0, 1].grid(axis='x', alpha=0.3)
# 3. 分组柱状图:多指标对比
x = np.arange(len(recent_months))
width = 0.25
bars1 = axes[1, 0].bar(x - width, recent_months['销售额'], width,
label='销售额', color='skyblue', alpha=0.8)
bars2 = axes[1, 0].bar(x, recent_months['利润'], width,
label='利润', color='lightgreen', alpha=0.8)
bars3 = axes[1, 0].bar(x + width, recent_months['成本'], width,
label='成本', color='lightcoral', alpha=0.8)
axes[1, 0].set_title('经营指标分组对比')
axes[1, 0].set_xlabel('月份')
axes[1, 0].set_ylabel('金额(万元)')
axes[1, 0].set_xticks(x)
axes[1, 0].set_xticklabels(recent_months.index, rotation=45)
axes[1, 0].legend()
axes[1, 0].grid(axis='y', alpha=0.3)
# 4. 堆积柱状图:成本结构
bottom_values = np.zeros(len(recent_months))
colors = ['#FF9999', '#66B2FF', '#99FF99', '#FFCC99']
categories = ['材料成本', '人力成本', '营销成本', '其他成本']
# 模拟成本结构数据
np.random.seed(42)
cost_structure = pd.DataFrame(
np.random.dirichlet(np.ones(4), size=len(recent_months)) * recent_months['成本'].values[:, np.newaxis],
index=recent_months.index,
columns=categories
)
for i, col in enumerate(categories):
axes[1, 1].bar(recent_months.index, cost_structure[col],
bottom=bottom_values, label=col, color=colors[i], alpha=0.8)
bottom_values += cost_structure[col].values
axes[1, 1].set_title('成本结构分析(堆积图)')
axes[1, 1].set_xlabel('月份')
axes[1, 1].set_ylabel('成本(万元)')
axes[1, 1].tick_params(axis='x', rotation=45)
axes[1, 1].legend()
axes[1, 1].grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
# 进阶:百分比堆积柱状图
fig, ax = plt.subplots(figsize=(10, 6))
bottom_values = np.zeros(len(recent_months))
for i, col in enumerate(categories):
percentages = cost_structure[col] / cost_structure.sum(axis=1) * 100
ax.bar(recent_months.index, percentages,
bottom=bottom_values, label=col, color=colors[i], alpha=0.8)
bottom_values += percentages.values
ax.set_title('成本结构百分比分析')
ax.set_xlabel('月份')
ax.set_ylabel('成本占比 (%)')
ax.tick_params(axis='x', rotation=45)
ax.legend(loc='upper left', bbox_to_anchor=(1, 1))
ax.grid(axis='y', alpha=0.3)
# 添加百分比标签
for i, month in enumerate(recent_months.index):
cumulative = 0
for j, col in enumerate(categories):
percentage = cost_structure.loc[month, col] / cost_structure.loc[month].sum() * 100
if percentage > 5: # 只显示大于5%的标签
ax.text(i, cumulative + percentage/2, f'{percentage:.1f}%',
ha='center', va='center', fontsize=9, color='white')
cumulative += percentage
plt.tight_layout()
plt.show()
1.1.3 散点图与箱线图:分布与异常值检测
散点图展示变量间关系,箱线图展示数据分布和异常值。
python
print("=== 散点图与箱线图分析 ===")
# 创建更丰富的示例数据
np.random.seed(42)
n_samples = 200
product_data = pd.DataFrame({
'价格': np.random.uniform(10, 1000, n_samples),
'销量': np.random.poisson(500, n_samples) + (np.random.randn(n_samples) * 50),
'评分': np.random.uniform(3.0, 5.0, n_samples),
'类别': np.random.choice(['电子产品', '服装', '食品', '家居', '图书'], n_samples),
'广告投入': np.random.uniform(100, 10000, n_samples),
'利润率': np.random.uniform(0.1, 0.5, n_samples)
})
# 计算收入
product_data['收入'] = product_data['价格'] * product_data['销量']
product_data['广告效果'] = product_data['收入'] / product_data['广告投入']
print("产品数据概览:")
print(product_data.head())
print(f"\n数据形状:{product_data.shape}")
# 创建画布
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
fig.suptitle('产品数据分析', fontsize=16, fontweight='bold')
# 1. 基础散点图:价格 vs 销量
scatter1 = axes[0, 0].scatter(product_data['价格'], product_data['销量'],
c=product_data['评分'], cmap='viridis',
alpha=0.6, s=50, edgecolors='black', linewidth=0.5)
axes[0, 0].set_title('价格 vs 销量(颜色表示评分)')
axes[0, 0].set_xlabel('价格(元)')
axes[0, 0].set_ylabel('销量')
axes[0, 0].grid(True, alpha=0.3)
# 添加颜色条
plt.colorbar(scatter1, ax=axes[0, 0], label='评分')
# 2. 气泡图:价格 vs 收入(气泡大小表示利润率)
bubble_sizes = product_data['利润率'] * 500 # 调整气泡大小
scatter2 = axes[0, 1].scatter(product_data['价格'], product_data['收入'],
s=bubble_sizes, c=product_data['广告效果'],
cmap='coolwarm', alpha=0.6, edgecolors='black', linewidth=0.5)
axes[0, 1].set_title('价格 vs 收入(气泡大小=利润率,颜色=广告效果)')
axes[0, 1].set_xlabel('价格(元)')
axes[0, 1].set_ylabel('收入(元)')
axes[0, 1].grid(True, alpha=0.3)
# 添加颜色条
plt.colorbar(scatter2, ax=axes[0, 1], label='广告效果')
# 3. 分类散点图:不同类别的价格分布
categories = product_data['类别'].unique()
colors = plt.cm.Set3(np.linspace(0, 1, len(categories)))
for i, category in enumerate(categories):
cat_data = product_data[product_data['类别'] == category]
axes[0, 2].scatter(cat_data['价格'], cat_data['销量'],
color=colors[i], label=category, alpha=0.7, s=50)
axes[0, 2].set_title('不同类别产品的价格-销量分布')
axes[0, 2].set_xlabel('价格(元)')
axes[0, 2].set_ylabel('销量')
axes[0, 2].legend()
axes[0, 2].grid(True, alpha=0.3)
# 4. 箱线图:价格分布
product_data.boxplot(column='价格', by='类别', ax=axes[1, 0], grid=True)
axes[1, 0].set_title('各品类价格分布')
axes[1, 0].set_xlabel('产品类别')
axes[1, 0].set_ylabel('价格(元)')
axes[1, 0].tick_params(axis='x', rotation=45)
# 5. 小提琴图(使用seaborn)
try:
import seaborn as sns
sns.violinplot(x='类别', y='评分', data=product_data, ax=axes[1, 1])
axes[1, 1].set_title('各品类评分分布(小提琴图)')
axes[1, 1].set_xlabel('产品类别')
axes[1, 1].set_ylabel('评分')
axes[1, 1].tick_params(axis='x', rotation=45)
axes[1, 1].grid(True, alpha=0.3)
except ImportError:
# 如果seaborn不可用,使用箱线图替代
product_data.boxplot(column='评分', by='类别', ax=axes[1, 1], grid=True)
axes[1, 1].set_title('各品类评分分布(箱线图)')
axes[1, 1].set_xlabel('产品类别')
axes[1, 1].set_ylabel('评分')
axes[1, 1].tick_params(axis='x', rotation=45)
# 6. 六边形箱图:价格 vs 收入密度
hexbin = axes[1, 2].hexbin(product_data['价格'], product_data['收入'],
gridsize=30, cmap='YlOrRd', alpha=0.8)
axes[1, 2].set_title('价格 vs 收入密度图')
axes[1, 2].set_xlabel('价格(元)')
axes[1, 2].set_ylabel('收入(元)')
axes[1, 2].grid(True, alpha=0.3)
# 添加颜色条
plt.colorbar(hexbin, ax=axes[1, 2], label='数据点密度')
plt.tight_layout()
plt.show()
# 高级散点图矩阵(需要seaborn)
try:
import seaborn as sns
# 选择数值列
numeric_cols = ['价格', '销量', '评分', '广告投入', '利润率', '收入']
sns_df = product_data[numeric_cols]
# 创建散点图矩阵
scatter_matrix = sns.pairplot(sns_df, diag_kind='kde',
plot_kws={'alpha': 0.6, 's': 30, 'edgecolor': 'k'})
scatter_matrix.fig.suptitle('产品数据散点图矩阵', y=1.02)
plt.show()
except ImportError:
# 使用pandas的散点图矩阵
from pandas.plotting import scatter_matrix
scatter_matrix(product_data[numeric_cols], alpha=0.6, figsize=(12, 8), diagonal='kde')
plt.suptitle('产品数据散点图矩阵', y=1.02)
plt.show()
1.2 hist方法:分布分析的核心工具
直方图是理解数据分布的基础工具,hist方法提供了丰富的自定义选项。
python
print("=== 直方图分析 ===")
# 创建多种分布的数据
np.random.seed(42)
distribution_data = pd.DataFrame({
'正态分布': np.random.normal(100, 20, 1000),
'偏态分布': np.random.exponential(50, 1000),
'双峰分布': np.concatenate([
np.random.normal(50, 10, 500),
np.random.normal(150, 15, 500)
]),
'均匀分布': np.random.uniform(0, 200, 1000),
'类别': np.random.choice(['A', 'B', 'C'], 1000)
})
print("分布数据概览:")
print(distribution_data.describe())
# 创建画布
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
fig.suptitle('数据分布分析', fontsize=16, fontweight='bold')
# 1. 基础直方图
axes[0, 0].hist(distribution_data['正态分布'], bins=30,
color='skyblue', edgecolor='black', alpha=0.7)
axes[0, 0].set_title('正态分布直方图')
axes[0, 0].set_xlabel('数值')
axes[0, 0].set_ylabel('频数')
axes[0, 0].grid(True, alpha=0.3)
# 添加统计信息
mean = distribution_data['正态分布'].mean()
std = distribution_data['正态分布'].std()
axes[0, 0].axvline(mean, color='red', linestyle='--', linewidth=2, label=f'均值: {mean:.1f}')
axes[0, 0].axvline(mean + std, color='green', linestyle=':', linewidth=1.5, label=f'±1标准差')
axes[0, 0].axvline(mean - std, color='green', linestyle=':', linewidth=1.5)
axes[0, 0].legend()
# 2. 密度直方图
distribution_data['正态分布'].plot.hist(
ax=axes[0, 1],
bins=30,
density=True,
color='lightgreen',
edgecolor='black',
alpha=0.7
)
axes[0, 1].set_title('密度直方图')
axes[0, 1].set_xlabel('数值')
axes[0, 1].set_ylabel('密度')
axes[0, 1].grid(True, alpha=0.3)
# 添加核密度估计曲线
distribution_data['正态分布'].plot.kde(ax=axes[0, 1], color='darkgreen', linewidth=2)
# 3. 分组直方图
for category in distribution_data['类别'].unique():
cat_data = distribution_data[distribution_data['类别'] == category]['正态分布']
axes[0, 2].hist(cat_data, bins=20, alpha=0.6, label=category, density=True)
axes[0, 2].set_title('按类别分组直方图')
axes[0, 2].set_xlabel('数值')
axes[0, 2].set_ylabel('密度')
axes[0, 2].legend()
axes[0, 2].grid(True, alpha=0.3)
# 4. 累积分布直方图
axes[1, 0].hist(distribution_data['正态分布'], bins=30,
cumulative=True, color='orange',
edgecolor='black', alpha=0.7)
axes[1, 0].set_title('累积分布直方图')
axes[1, 0].set_xlabel('数值')
axes[1, 0].set_ylabel('累积频数')
axes[1, 0].grid(True, alpha=0.3)
# 5. 对比直方图:不同分布对比
axes[1, 1].hist(distribution_data['偏态分布'], bins=30,
color='lightcoral', alpha=0.6, label='偏态分布', density=True)
axes[1, 1].hist(distribution_data['双峰分布'], bins=30,
color='lightblue', alpha=0.6, label='双峰分布', density=True)
axes[1, 1].set_title('不同分布对比')
axes[1, 1].set_xlabel('数值')
axes[1, 1].set_ylabel('密度')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)
# 6. 2D直方图(热力图)
if 'seaborn' in sys.modules:
# 使用seaborn创建2D直方图
sns.histplot(x=distribution_data['正态分布'],
y=distribution_data['偏态分布'],
ax=axes[1, 2], bins=30, cmap='viridis')
else:
# 使用matplotlib创建2D直方图
h = axes[1, 2].hist2d(distribution_data['正态分布'],
distribution_data['偏态分布'],
bins=30, cmap='viridis')
plt.colorbar(h[3], ax=axes[1, 2])
axes[1, 2].set_title('2D直方图(正态分布 vs 偏态分布)')
axes[1, 2].set_xlabel('正态分布')
axes[1, 2].set_ylabel('偏态分布')
axes[1, 2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 高级:分面直方图(使用seaborn)
if 'seaborn' in sys.modules:
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 创建分面网格
g = sns.FacetGrid(distribution_data, col='类别', hue='类别',
col_wrap=2, height=4, aspect=1.2)
g.map(sns.histplot, '正态分布', kde=True, bins=20)
g.add_legend()
g.fig.suptitle('按类别分面的正态分布直方图', y=1.02)
plt.show()
1.3 高级可视化技巧
python
print("=== 高级可视化技巧 ===")
# 创建时间序列数据用于高级可视化
np.random.seed(42)
time_series_data = pd.DataFrame({
'时间': pd.date_range('2023-01-01', periods=100, freq='D'),
'指标A': np.random.randn(100).cumsum() + 100,
'指标B': np.random.randn(100).cumsum() + 80,
'指标C': np.random.randn(100).cumsum() + 120,
'事件': np.random.choice(['无事件', '促销', '节日', '危机'], 100, p=[0.7, 0.1, 0.1, 0.1])
})
time_series_data.set_index('时间', inplace=True)
# 创建画布
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle('高级可视化技巧', fontsize=16, fontweight='bold')
# 1. 带置信区间的折线图
for i, col in enumerate(['指标A', '指标B', '指标C']):
# 计算滚动平均值和标准差
rolling_mean = time_series_data[col].rolling(window=7).mean()
rolling_std = time_series_data[col].rolling(window=7).std()
# 绘制折线
axes[0, 0].plot(time_series_data.index, rolling_mean,
label=col, linewidth=2, alpha=0.8)
# 绘制置信区间
axes[0, 0].fill_between(time_series_data.index,
rolling_mean - 1.96 * rolling_std,
rolling_mean + 1.96 * rolling_std,
alpha=0.2)
axes[0, 0].set_title('带95%置信区间的滚动平均')
axes[0, 0].set_xlabel('时间')
axes[0, 0].set_ylabel('指标值')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
axes[0, 0].tick_params(axis='x', rotation=45)
# 2. 日历热图(简化版)
# 创建日历数据
calendar_data = pd.DataFrame({
'日期': pd.date_range('2023-01-01', '2023-12-31', freq='D'),
'数值': np.random.randn(365).cumsum() + 100
})
calendar_data['月份'] = calendar_data['日期'].dt.month
calendar_data['日'] = calendar_data['日期'].dt.day
# 创建月份×日的透视表
calendar_pivot = calendar_data.pivot_table(values='数值',
index='日',
columns='月份',
aggfunc='mean')
# 绘制热图
im = axes[0, 1].imshow(calendar_pivot, aspect='auto', cmap='YlOrRd')
axes[0, 1].set_title('日历热图(2023年每日数据)')
axes[0, 1].set_xlabel('月份')
axes[0, 1].set_ylabel('日')
axes[0, 1].set_xticks(range(12))
axes[0, 1].set_xticklabels(range(1, 13))
axes[0, 1].set_yticks(range(0, 31, 5))
axes[0, 1].set_yticklabels(range(1, 32, 5))
# 添加颜色条
plt.colorbar(im, ax=axes[0, 1], label='指标值')
# 3. 雷达图(需要自定义函数)
def create_radar_chart(categories, values, title, ax):
"""创建雷达图"""
N = len(categories)
# 计算每个角度
angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles += angles[:1] # 闭合多边形
# 闭合数据
values = list(values)
values += values[:1]
# 绘制雷达图
ax.plot(angles, values, linewidth=2, linestyle='solid')
ax.fill(angles, values, alpha=0.4)
# 设置刻度标签
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)
# 设置标题
ax.set_title(title, size=14, y=1.1)
# 设置网格
ax.grid(True)
# 创建示例数据
categories = ['销售额', '利润', '客户数', '满意度', '市场份额', '增长率']
values1 = [4.5, 4.0, 3.8, 4.2, 3.5, 4.1] # 公司A
values2 = [3.8, 4.2, 4.0, 3.5, 4.1, 3.9] # 公司B
# 创建雷达图
create_radar_chart(categories, values1, '公司A表现雷达图', axes[1, 0])
create_radar_chart(categories, values2, '公司B表现雷达图', axes[1, 1])
# 调整布局
axes[1, 0].set_rlabel_position(30)
axes[1, 1].set_rlabel_position(30)
plt.tight_layout()
plt.show()
# 4. 3D散点图(需要mpl_toolkits)
try:
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# 创建3D数据
np.random.seed(42)
n_points = 100
x = np.random.randn(n_points)
y = np.random.randn(n_points)
z = np.random.randn(n_points)
colors = np.random.rand(n_points)
sizes = np.random.uniform(20, 100, n_points)
# 绘制3D散点图
scatter = ax.scatter(x, y, z, c=colors, s=sizes, alpha=0.6,
cmap='viridis', edgecolors='black', linewidth=0.5)
ax.set_title('3D散点图')
ax.set_xlabel('X轴')
ax.set_ylabel('Y轴')
ax.set_zlabel('Z轴')
# 添加颜色条
plt.colorbar(scatter, ax=ax, label='颜色值')
plt.show()
except ImportError:
print("提示:3D图形需要mpl_toolkits,可以使用Axes3D")
二、高级数据分析方法
2.1 nunique:唯一值分析
nunique方法用于计算唯一值的数量,是理解数据多样性的重要工具。
python
print("=== 唯一值分析nunique ===")
# 创建包含多种数据类型的示例数据
data_diversity = pd.DataFrame({
'客户ID': [f'C{1000+i:04d}' for i in range(1000)],
'城市': np.random.choice(['北京', '上海', '广州', '深圳', '杭州', '成都', '武汉'], 1000),
'产品类别': np.random.choice(['电子产品', '服装', '食品', '家居', '图书', '美妆'], 1000),
'购买渠道': np.random.choice(['线上', '线下', 'APP', '小程序'], 1000),
'年龄段': np.random.choice(['18-25', '26-35', '36-45', '46-55', '56+'], 1000),
'购买金额': np.random.uniform(10, 5000, 1000),
'购买日期': pd.to_datetime(np.random.choice(
pd.date_range('2023-01-01', '2023-12-31', freq='D'), 1000
)),
'是否会员': np.random.choice([True, False], 1000, p=[0.3, 0.7])
})
print("数据概览:")
print(data_diversity.head())
print(f"\n数据形状:{data_diversity.shape}")
# 1. 基础唯一值统计
print("\n1. 各列唯一值数量:")
unique_counts = data_diversity.nunique()
print(unique_counts)
# 2. 唯一值比例分析
print("\n2. 唯一值比例分析:")
total_rows = len(data_diversity)
unique_ratio = (unique_counts / total_rows * 100).round(2)
unique_analysis = pd.DataFrame({
'唯一值数量': unique_counts,
'总行数': total_rows,
'唯一值比例(%)': unique_ratio,
'数据类型': data_diversity.dtypes
})
print(unique_analysis)
# 3. 分组唯一值分析
print("\n3. 分组唯一值分析:")
# 各城市的产品类别多样性
city_product_diversity = data_diversity.groupby('城市')['产品类别'].nunique()
print("各城市销售的产品类别数量:")
print(city_product_diversity.sort_values(ascending=False))
# 各产品类别的客户城市分布
product_city_diversity = data_diversity.groupby('产品类别')['城市'].nunique()
print("\n各产品类别的销售城市数量:")
print(product_city_diversity.sort_values(ascending=False))
# 4. 多列组合唯一性
print("\n4. 多列组合唯一性分析:")
# 客户-产品组合唯一性
customer_product_pairs = data_diversity[['客户ID', '产品类别']].drop_duplicates()
print(f"唯一的客户-产品组合数:{len(customer_product_pairs)}")
print(f"平均每个客户购买的产品类别数:{len(customer_product_pairs) / data_diversity['客户ID'].nunique():.2f}")
# 5. 时间维度唯一值分析
print("\n5. 时间维度唯一值分析:")
data_diversity['购买月份'] = data_diversity['购买日期'].dt.to_period('M')
# 每月新增客户数
monthly_new_customers = data_diversity.groupby('购买月份')['客户ID'].nunique()
print("每月购买客户数:")
print(monthly_new_customers.head())
# 6. 条件唯一值分析
print("\n6. 条件唯一值分析:")
# 会员与非会员的购买渠道多样性
member_channel_diversity = data_diversity.groupby('是否会员')['购买渠道'].nunique()
print("会员与非会员的购买渠道多样性:")
print(member_channel_diversity)
# 高消费客户(购买金额>1000)的产品偏好
high_value_customers = data_diversity[data_diversity['购买金额'] > 1000]
high_value_product_diversity = high_value_customers.groupby('客户ID')['产品类别'].nunique()
print(f"\n高消费客户平均购买产品类别数:{high_value_product_diversity.mean():.2f}")
# 7. 唯一值变化趋势分析
print("\n7. 唯一值变化趋势分析:")
# 计算滚动唯一值(最近30天)
data_diversity_sorted = data_diversity.sort_values('购买日期')
data_diversity_sorted['滚动30天唯一客户数'] = (
data_diversity_sorted.set_index('购买日期')
.groupby(pd.Grouper(freq='D'))['客户ID']
.nunique()
.rolling(window=30, min_periods=1)
.mean()
.values
)
print("滚动30天唯一客户数趋势(前10天):")
print(data_diversity_sorted[['购买日期', '滚动30天唯一客户数']].drop_duplicates().head(10))
# 可视化唯一值分析
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. 各列唯一值数量条形图
bars = axes[0, 0].bar(range(len(unique_counts)), unique_counts.values)
axes[0, 0].set_title('各列唯一值数量')
axes[0, 0].set_xlabel('列名')
axes[0, 0].set_ylabel('唯一值数量')
axes[0, 0].set_xticks(range(len(unique_counts)))
axes[0, 0].set_xticklabels(unique_counts.index, rotation=45, ha='right')
axes[0, 0].grid(axis='y', alpha=0.3)
# 添加数值标签
for i, bar in enumerate(bars):
height = bar.get_height()
axes[0, 0].text(bar.get_x() + bar.get_width()/2., height + 5,
f'{int(height)}', ha='center', va='bottom', fontsize=9)
# 2. 各城市产品类别多样性
city_product_diversity.sort_values().plot.barh(ax=axes[0, 1], color='lightgreen')
axes[0, 1].set_title('各城市产品类别多样性')
axes[0, 1].set_xlabel('产品类别数量')
axes[0, 1].grid(axis='x', alpha=0.3)
# 3. 每月唯一客户数趋势
monthly_new_customers.plot(ax=axes[1, 0], marker='o', linewidth=2)
axes[1, 0].set_title('每月唯一客户数趋势')
axes[1, 0].set_xlabel('月份')
axes[1, 0].set_ylabel('客户数')
axes[1, 0].tick_params(axis='x', rotation=45)
axes[1, 0].grid(True, alpha=0.3)
# 4. 会员与非会员渠道多样性
member_channel_diversity.plot.pie(ax=axes[1, 1], autopct='%1.1f%%',
colors=['lightblue', 'lightcoral'])
axes[1, 1].set_title('会员 vs 非会员渠道多样性')
plt.tight_layout()
plt.show()
2.2 value_counts:频次分析
value_counts是数据探索中最常用的方法之一,用于计算每个值的出现频率。
python
print("=== 频次分析value_counts ===")
# 1. 基础频次分析
print("\n1. 基础频次分析:")
# 城市分布
city_counts = data_diversity['城市'].value_counts()
print("客户城市分布:")
print(city_counts)
# 产品类别分布
product_counts = data_diversity['产品类别'].value_counts()
print("\n产品类别分布:")
print(product_counts)
# 2. 相对频率分析
print("\n2. 相对频率分析:")
city_percentage = data_diversity['城市'].value_counts(normalize=True) * 100
print("城市分布百分比:")
print(city_percentage.round(2))
# 3. 排序控制
print("\n3. 排序控制:")
# 按值升序排序
city_counts_asc = data_diversity['城市'].value_counts(ascending=True)
print("城市分布(升序):")
print(city_counts_asc)
# 4. 分组频次分析
print("\n4. 分组频次分析:")
# 各城市的购买渠道分布
city_channel_counts = data_diversity.groupby('城市')['购买渠道'].value_counts()
print("各城市的购买渠道分布:")
print(city_channel_counts.head(15))
# 展开为宽格式
city_channel_pivot = data_diversity.pivot_table(
index='城市',
columns='购买渠道',
values='客户ID',
aggfunc='count',
fill_value=0
)
print("\n各城市购买渠道分布(宽格式):")
print(city_channel_pivot)
# 5. 条件频次分析
print("\n5. 条件频次分析:")
# 高消费客户的购买渠道偏好
high_value_counts = data_diversity[data_diversity['购买金额'] > 1000]['购买渠道'].value_counts()
print("高消费客户购买渠道偏好:")
print(high_value_counts)
# 会员与非会员的产品偏好对比
member_product_counts = data_diversity[data_diversity['是否会员'] == True]['产品类别'].value_counts()
non_member_product_counts = data_diversity[data_diversity['是否会员'] == False]['产品类别'].value_counts()
print("\n会员产品偏好:")
print(member_product_counts.head())
print("\n非会员产品偏好:")
print(non_member_product_counts.head())
# 6. 时间序列频次分析
print("\n6. 时间序列频次分析:")
# 每月各产品类别销售频次
monthly_product_counts = data_diversity.groupby(
[data_diversity['购买日期'].dt.to_period('M'), '产品类别']
).size().unstack(fill_value=0)
print("每月产品类别销售频次(前3个月):")
print(monthly_product_counts.head(3))
# 7. 分箱频次分析(连续数据分箱)
print("\n7. 分箱频次分析:")
# 购买金额分箱
amount_bins = pd.cut(data_diversity['购买金额'], bins=5)
amount_bin_counts = amount_bins.value_counts().sort_index()
print("购买金额分箱分布:")
print(amount_bin_counts)
# 自定义分箱
custom_bins = [0, 100, 500, 1000, 5000]
custom_labels = ['低消费(<100)', '中低消费(100-500)', '中等消费(500-1000)', '高消费(>1000)']
data_diversity['消费等级'] = pd.cut(data_diversity['购买金额'],
bins=custom_bins,
labels=custom_labels,
include_lowest=True)
print("\n消费等级分布:")
print(data_diversity['消费等级'].value_counts().sort_index())
# 8. 多列组合频次分析
print("\n8. 多列组合频次分析:")
# 城市×产品类别组合频次
city_product_combo = data_diversity.groupby(['城市', '产品类别']).size()
print("城市×产品类别组合频次(Top 10):")
print(city_product_combo.sort_values(ascending=False).head(10))
# 展开为热力图格式
city_product_matrix = city_product_combo.unstack(fill_value=0)
print("\n城市×产品类别矩阵(部分):")
print(city_product_matrix.head())
# 可视化频次分析
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. 城市分布条形图
city_counts.plot.bar(ax=axes[0, 0], color='skyblue', edgecolor='black')
axes[0, 0].set_title('客户城市分布')
axes[0, 0].set_xlabel('城市')
axes[0, 0].set_ylabel('客户数')
axes[0, 0].tick_params(axis='x', rotation=45)
axes[0, 0].grid(axis='y', alpha=0.3)
# 2. 产品类别分布饼图
product_counts.plot.pie(ax=axes[0, 1], autopct='%1.1f%%',
startangle=90, shadow=True)
axes[0, 1].set_title('产品类别分布')
axes[0, 1].set_ylabel('') # 隐藏ylabel
# 3. 城市×购买渠道热力图
im = axes[1, 0].imshow(city_channel_pivot, aspect='auto', cmap='YlOrRd')
axes[1, 0].set_title('城市×购买渠道热力图')
axes[1, 0].set_xlabel('购买渠道')
axes[1, 0].set_ylabel('城市')
axes[1, 0].set_xticks(range(len(city_channel_pivot.columns)))
axes[1, 0].set_xticklabels(city_channel_pivot.columns, rotation=45, ha='right')
axes[1, 0].set_yticks(range(len(city_channel_pivot.index)))
axes[1, 0].set_yticklabels(city_channel_pivot.index)
# 添加颜色条
plt.colorbar(im, ax=axes[1, 0], label='客户数')
# 4. 消费等级分布条形图
data_diversity['消费等级'].value_counts().sort_index().plot.bar(
ax=axes[1, 1], color='lightgreen', edgecolor='black'
)
axes[1, 1].set_title('消费等级分布')
axes[1, 1].set_xlabel('消费等级')
axes[1, 1].set_ylabel('客户数')
axes[1, 1].tick_params(axis='x', rotation=45)
axes[1, 1].grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
# 进阶:创建交互式频次分析仪表板
try:
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# 创建子图
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('城市分布', '产品类别分布', '消费等级分布', '购买渠道分布'),
specs=[[{'type': 'bar'}, {'type': 'pie'}],
[{'type': 'bar'}, {'type': 'bar'}]]
)
# 城市分布
fig.add_trace(
go.Bar(x=city_counts.index, y=city_counts.values,
name='城市', marker_color='skyblue'),
row=1, col=1
)
# 产品类别分布
fig.add_trace(
go.Pie(labels=product_counts.index, values=product_counts.values,
name='产品类别'),
row=1, col=2
)
# 消费等级分布
consumption_counts = data_diversity['消费等级'].value_counts().sort_index()
fig.add_trace(
go.Bar(x=consumption_counts.index, y=consumption_counts.values,
name='消费等级', marker_color='lightgreen'),
row=2, col=1
)
# 购买渠道分布
channel_counts = data_diversity['购买渠道'].value_counts()
fig.add_trace(
go.Bar(x=channel_counts.index, y=channel_counts.values,
name='购买渠道', marker_color='lightcoral'),
row=2, col=2
)
# 更新布局
fig.update_layout(height=800, title_text="客户数据频次分析仪表板",
showlegend=False)
fig.show()
except ImportError:
print("\n提示:安装plotly库可生成交互式仪表板")
2.3 describe:全面统计摘要
describe方法提供数据的全面统计摘要,是数据探索的第一步。
python
print("=== 统计摘要describe ===")
# 1. 基础统计摘要
print("\n1. 基础统计摘要:")
basic_description = data_diversity.describe()
print("数值列统计摘要:")
print(basic_description)
# 2. 包含所有列的统计摘要
print("\n2. 包含所有列的统计摘要:")
all_description = data_diversity.describe(include='all')
print("所有列统计摘要:")
print(all_description)
# 3. 指定分位数
print("\n3. 指定分位数统计摘要:")
custom_percentiles = [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99]
custom_description = data_diversity.describe(percentiles=custom_percentiles)
print("自定义分位数统计摘要:")
print(custom_description[['购买金额']]) # 只显示购买金额列
# 4. 排除特定类型
print("\n4. 排除对象类型统计摘要:")
numeric_only = data_diversity.describe(exclude=['object'])
print("数值型列统计摘要:")
print(numeric_only)
# 5. 分组统计摘要
print("\n5. 分组统计摘要:")
# 按城市分组统计
city_group_description = data_diversity.groupby('城市')['购买金额'].describe()
print("各城市购买金额统计摘要:")
print(city_group_description)
# 按产品类别分组统计
product_group_description = data_diversity.groupby('产品类别')['购买金额'].describe()
print("\n各产品类别购买金额统计摘要:")
print(product_group_description)
# 6. 时间序列统计摘要
print("\n6. 时间序列统计摘要:")
data_diversity['购买月份'] = data_diversity['购买日期'].dt.to_period('M')
monthly_stats = data_diversity.groupby('购买月份')['购买金额'].agg(['mean', 'std', 'min', 'max', 'count'])
print("每月购买金额统计:")
print(monthly_stats.head())
# 7. 自定义统计函数
print("\n7. 自定义统计函数:")
def comprehensive_stats(series):
"""自定义全面统计函数"""
stats_dict = {
'数量': series.count(),
'缺失数': series.isnull().sum(),
'均值': series.mean(),
'中位数': series.median(),
'标准差': series.std(),
'变异系数': series.std() / series.mean() if series.mean() != 0 else np.nan,
'偏度': series.skew(),
'峰度': series.kurtosis(),
'最小值': series.min(),
'25%分位数': series.quantile(0.25),
'75%分位数': series.quantile(0.75),
'最大值': series.max(),
'范围': series.max() - series.min(),
'IQR': series.quantile(0.75) - series.quantile(0.25)
}
return pd.Series(stats_dict)
custom_stats = data_diversity['购买金额'].pipe(comprehensive_stats)
print("购买金额自定义统计:")
print(custom_stats)
# 8. 多指标统计摘要
print("\n8. 多指标统计摘要:")
# 创建多指标统计函数
def multi_metric_summary(df, group_col=None):
"""多指标统计摘要"""
metrics = {
'购买金额': ['mean', 'std', 'min', 'max', 'count'],
'是否会员': ['mean'], # 比例
'客户ID': ['nunique'] # 唯一客户数
}
if group_col:
return df.groupby(group_col).agg(metrics)
else:
return df.agg({
'购买金额': ['mean', 'std', 'min', 'max', 'count'],
'是否会员': 'mean',
'客户ID': 'nunique'
})
multi_summary = multi_metric_summary(data_diversity, '城市')
print("各城市多指标统计摘要:")
print(multi_summary)
# 可视化统计摘要
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. 购买金额分布箱线图
data_diversity.boxplot(column='购买金额', ax=axes[0, 0])
axes[0, 0].set_title('购买金额分布箱线图')
axes[0, 0].set_ylabel('购买金额')
axes[0, 0].grid(True, alpha=0.3)
# 2. 各城市购买金额箱线图
city_amount_data = [data_diversity[data_diversity['城市']==city]['购买金额'].values
for city in data_diversity['城市'].unique()]
axes[0, 1].boxplot(city_amount_data, labels=data_diversity['城市'].unique())
axes[0, 1].set_title('各城市购买金额分布')
axes[0, 1].set_xlabel('城市')
axes[0, 1].set_ylabel('购买金额')
axes[0, 1].tick_params(axis='x', rotation=45)
axes[0, 1].grid(True, alpha=0.3)
# 3. 每月购买金额趋势
monthly_stats[['mean', 'min', 'max']].plot(ax=axes[1, 0])
axes[1, 0].set_title('每月购买金额趋势')
axes[1, 0].set_xlabel('月份')
axes[1, 0].set_ylabel('购买金额')
axes[1, 0].tick_params(axis='x', rotation=45)
axes[1, 0].legend(['平均', '最小', '最大'])
axes[1, 0].grid(True, alpha=0.3)
# 4. 统计指标雷达图
def create_stat_radar(ax, stats_series, title):
"""创建统计指标雷达图"""
categories = stats_series.index.tolist()
values = stats_series.values.tolist()
# 归一化处理(0-1范围)
values_normalized = [(v - min(values)) / (max(values) - min(values))
if max(values) != min(values) else 0.5
for v in values]
N = len(categories)
angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles += angles[:1]
values_normalized += values_normalized[:1]
ax.plot(angles, values_normalized, linewidth=2, linestyle='solid')
ax.fill(angles, values_normalized, alpha=0.4)
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories, fontsize=9)
ax.set_title(title, size=12)
ax.grid(True)
# 选择部分统计指标
selected_stats = custom_stats[['均值', '标准差', '偏度', '峰度', '范围', 'IQR']]
create_stat_radar(axes[1, 1], selected_stats, '购买金额统计指标雷达图')
plt.tight_layout()
plt.show()
# 创建交互式统计摘要仪表板
try:
import plotly.figure_factory as ff
# 创建统计摘要表
stats_table = ff.create_table(basic_description.round(2))
stats_table.update_layout(title='数值列统计摘要表')
stats_table.show()
# 创建分布图
fig = px.histogram(data_diversity, x='购买金额', nbins=50,
title='购买金额分布直方图',
marginal='box') # 边缘添加箱线图
fig.show()
except ImportError:
print("\n提示:安装plotly.figure_factory可创建精美统计表格")
2.4 copy:数据副本管理
copy方法用于创建数据的副本,在数据处理中非常重要,可以避免意外的数据修改。
python
print("=== 数据副本管理copy ===")
# 1. 浅拷贝 vs 深拷贝
print("\n1. 浅拷贝与深拷贝对比:")
# 原始数据
original_df = pd.DataFrame({
'A': [1, 2, 3],
'B': [4, 5, 6],
'C': [[7, 8], [9, 10], [11, 12]] # 包含可变对象
})
print("原始DataFrame:")
print(original_df)
print(f"原始数据内存地址:{id(original_df)}")
# 浅拷贝
shallow_copy = original_df.copy(deep=False)
print(f"\n浅拷贝内存地址:{id(shallow_copy)}")
print(f"A列内存地址是否相同:{original_df['A'].values.base is shallow_copy['A'].values.base}")
# 深拷贝
deep_copy = original_df.copy(deep=True)
print(f"\n深拷贝内存地址:{id(deep_copy)}")
print(f"A列内存地址是否相同:{original_df['A'].values.base is deep_copy['A'].values.base}")
# 2. 修改测试
print("\n2. 修改测试:")
# 修改原始数据
original_df.loc[0, 'A'] = 100
print("修改原始数据后:")
print(f"原始数据 A[0]: {original_df.loc[0, 'A']}")
print(f"浅拷贝 A[0]: {shallow_copy.loc[0, 'A']}")
print(f"深拷贝 A[0]: {deep_copy.loc[0, 'A']}")
# 修改列表元素(测试嵌套对象)
original_df.loc[0, 'C'][0] = 999
print(f"\n修改原始数据嵌套列表后:")
print(f"原始数据 C[0]: {original_df.loc[0, 'C']}")
print(f"浅拷贝 C[0]: {shallow_copy.loc[0, 'C']}")
print(f"深拷贝 C[0]: {deep_copy.loc[0, 'C']}")
# 3. 实际应用场景
print("\n3. 实际应用场景:")
def process_data_with_copy(df, use_deep_copy=True):
"""使用副本处理数据的安全函数"""
if use_deep_copy:
df_copy = df.copy(deep=True)
else:
df_copy = df.copy(deep=False)
# 数据处理逻辑
df_copy['处理后的A'] = df_copy['A'] * 2
df_copy['处理后的B'] = df_copy['B'] + 10
return df_copy
# 安全处理数据
processed_deep = process_data_with_copy(original_df, use_deep_copy=True)
processed_shallow = process_data_with_copy(original_df, use_deep_copy=False)
print("使用深拷贝处理后的数据:")
print(processed_deep)
print("\n使用浅拷贝处理后的数据:")
print(processed_shallow)
# 4. 性能对比
print("\n4. 性能对比:")
import time
large_df = pd.DataFrame(np.random.randn(10000, 100))
# 测试深拷贝性能
start = time.time()
deep_copy_large = large_df.copy(deep=True)
deep_copy_time = time.time() - start
# 测试浅拷贝性能
start = time.time()
shallow_copy_large = large_df.copy(deep=False)
shallow_copy_time = time.time() - start
print(f"深拷贝10000×100数据耗时:{deep_copy_time:.4f}秒")
print(f"浅拷贝10000×100数据耗时:{shallow_copy_time:.4f}秒")
print(f"性能差异:{deep_copy_time/shallow_copy_time:.1f}倍")
# 5. 内存使用对比
print("\n5. 内存使用对比:")
def get_memory_usage(df):
"""获取DataFrame内存使用"""
return df.memory_usage(deep=True).sum() / 1024**2 # MB
original_memory = get_memory_usage(large_df)
deep_copy_memory = get_memory_usage(deep_copy_large)
shallow_copy_memory = get_memory_usage(shallow_copy_large)
print(f"原始数据内存:{original_memory:.2f} MB")
print(f"深拷贝内存:{deep_copy_memory:.2f} MB")
print(f"浅拷贝内存:{shallow_copy_memory:.2f} MB")
# 6. 最佳实践建议
print("\n6. 最佳实践建议:")
def copy_best_practices():
"""copy方法最佳实践"""
recommendations = [
"1. 默认使用深拷贝(deep=True),确保数据安全",
"2. 仅在确定不会修改嵌套对象时使用浅拷贝",
"3. 大数据集考虑使用浅拷贝节省内存",
"4. 在数据处理流水线中及时创建副本",
"5. 使用副本避免SettingWithCopyWarning警告"
]
for rec in recommendations:
print(f" {rec}")
copy_best_practices()
# 7. 链式操作中的copy使用
print("\n7. 链式操作中的copy使用:")
# 错误示例:链式操作可能导致警告
try:
# 这可能产生SettingWithCopyWarning
chained_df = original_df[original_df['A'] > 1]
chained_df['新列'] = chained_df['A'] * 2
print("链式操作(可能产生警告)")
except Warning as w:
print(f"捕获到警告:{w}")
# 正确示例:使用copy
safe_df = original_df[original_df['A'] > 1].copy()
safe_df['新列'] = safe_df['A'] * 2
print("\n使用copy的安全链式操作:")
print(safe_df)
2.5 reset_index:索引重置
reset_index方法用于重置索引,将索引转换为列,或重新生成默认整数索引。
python
print("=== 索引重置reset_index ===")
# 创建具有多级索引的复杂数据
np.random.seed(42)
multi_index_data = pd.DataFrame({
'销售额': np.random.randint(1000, 5000, 24),
'利润': np.random.randint(200, 1500, 24),
'成本': np.random.randint(500, 3000, 24)
})
# 创建多级索引
multi_index = pd.MultiIndex.from_product([
['2023-Q1', '2023-Q2', '2023-Q3', '2023-Q4'],
['电子产品', '服装', '食品', '家居', '图书', '美妆']
], names=['季度', '品类'])
multi_index_data.index = multi_index
print("原始多级索引数据:")
print(multi_index_data.head(12))
print(f"\n索引级别:{multi_index_data.index.names}")
# 1. 基础重置索引
print("\n1. 基础重置索引:")
reset_basic = multi_index_data.reset_index()
print("重置索引后(默认):")
print(reset_basic.head())
# 2. 删除原索引
print("\n2. 删除原索引:")
reset_drop = multi_index_data.reset_index(drop=True)
print("删除原索引后:")
print(reset_drop.head())
# 3. 重置特定级别
print("\n3. 重置特定级别:")
# 只重置季度级别
reset_level0 = multi_index_data.reset_index(level=0)
print("重置季度级别后:")
print(reset_level0.head())
# 只重置品类级别
reset_level1 = multi_index_data.reset_index(level=1)
print("\n重置品类级别后:")
print(reset_level1.head())
# 4. 自定义列名
print("\n4. 自定义列名:")
reset_custom = multi_index_data.reset_index(names=['销售季度', '产品类别'])
print("自定义列名后:")
print(reset_custom.head())
# 5. 处理重复索引
print("\n5. 处理重复索引:")
# 创建有重复索引的数据
dup_index_data = pd.DataFrame({
'值': [1, 2, 3, 4, 5, 6]
}, index=['A', 'A', 'B', 'B', 'C', 'C'])
print("有重复索引的数据:")
print(dup_index_data)
# 默认不允许重复索引
try:
dup_reset = dup_index_data.reset_index()
print("\n重置索引后:")
print(dup_reset)
except Exception as e:
print(f"\n重置索引错误:{e}")
# 允许重复索引
dup_reset_safe = dup_index_data.reset_index(allow_duplicates=True)
print("\n允许重复索引后:")
print(dup_reset_safe)
# 6. 重置索引后的数据处理
print("\n6. 重置索引后的数据处理:")
# 分组后重置索引示例
grouped_data = multi_index_data.groupby(level=0).agg({
'销售额': 'sum',
'利润': 'mean',
'成本': ['min', 'max']
})
print("分组聚合数据(多级列索引):")
print(grouped_data)
# 重置列索引
grouped_reset = grouped_data.copy()
grouped_reset.columns = ['_'.join(col).strip() for col in grouped_reset.columns.values]
grouped_reset = grouped_reset.reset_index()
print("\n重置列索引后:")
print(grouped_reset)
# 7. 实际应用场景
print("\n7. 实际应用场景:")
def prepare_data_for_visualization(df):
"""为可视化准备数据:重置索引"""
# 确保数据适合绘图
if isinstance(df.index, pd.MultiIndex):
# 多级索引转为普通列
df_prepared = df.reset_index()
elif df.index.name is not None:
# 有名称的索引转为列
df_prepared = df.reset_index()
else:
# 普通整数索引,保持原样
df_prepared = df.copy()
# 扁平化多级列索引
if isinstance(df_prepared.columns, pd.MultiIndex):
df_prepared.columns = ['_'.join(col).strip() for col in df_prepared.columns.values]
return df_prepared
# 应用准备函数
prepared_data = prepare_data_for_visualization(multi_index_data)
print("为可视化准备的数据:")
print(prepared_data.head())
# 可视化展示
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 1. 多级索引数据的透视表
pivot_data = multi_index_data.reset_index().pivot_table(
values='销售额',
index='季度',
columns='品类',
aggfunc='sum'
)
im = axes[0].imshow(pivot_data, aspect='auto', cmap='YlOrRd')
axes[0].set_title('季度×品类销售额热图')
axes[0].set_xlabel('品类')
axes[0].set_ylabel('季度')
axes[0].set_xticks(range(len(pivot_data.columns)))
axes[0].set_xticklabels(pivot_data.columns, rotation=45, ha='right')
axes[0].set_yticks(range(len(pivot_data.index)))
axes[0].set_yticklabels(pivot_data.index)
# 添加颜色条
plt.colorbar(im, ax=axes[0], label='销售额')
# 2. 重置索引后的条形图
quarterly_sales = multi_index_data.groupby(level=0)['销售额'].sum().reset_index()
quarterly_sales.columns = ['季度', '总销售额']
quarterly_sales.plot.bar(x='季度', y='总销售额',
ax=axes[1], color='skyblue',
edgecolor='black', alpha=0.8)
axes[1].set_title('各季度销售额对比')
axes[1].set_xlabel('季度')
axes[1].set_ylabel('总销售额')
axes[1].tick_params(axis='x', rotation=45)
axes[1].grid(axis='y', alpha=0.3)
# 添加数值标签
for i, v in enumerate(quarterly_sales['总销售额']):
axes[1].text(i, v + 100, f'{v:,.0f}', ha='center', va='bottom', fontsize=9)
plt.tight_layout()
plt.show()
2.6 info:数据信息概览
info方法提供数据的元数据信息,是了解数据结构和质量的重要工具。
python
print("=== 数据信息概览info ===")
# 创建包含多种数据类型和缺失值的数据
complex_info_data = pd.DataFrame({
'客户ID': pd.Series([f'C{1000+i:04d}' for i in range(10)] + [None] * 2),
'姓名': ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十', '郑一', '王二', None, None],
'年龄': pd.Series([25, 30, None, 35, 32, 29, 27, 31, 33, 26, 28, 34], dtype='Int64'),
'年收入': pd.Series([15.5, 20.2, 18.8, 25.3, 22.1, 19.5, 17.2, 23.4, 21.7, 16.8, None, 24.6]),
'是否会员': pd.Series([True, False, True, True, False, True, False, True, False, True, None, False]),
'注册日期': pd.to_datetime(['2020-01-15', '2019-03-20', None, '2021-06-10',
'2018-11-05', '2022-02-28', '2020-07-12',
'2019-09-30', '2021-01-25', '2022-05-18', None, '2020-12-01']),
'城市': pd.Categorical(['北京', '上海', '广州', '北京', '深圳', '上海',
'杭州', '北京', '成都', '广州', '武汉', '深圳']),
'消费等级': pd.Categorical(['高', '中', '低', '高', '中', '低',
'高', '中', '低', '高', '中', '低'],
categories=['低', '中', '高'], ordered=True),
'备注': pd.Series(['VIP客户', None, '新客户', '长期客户', None, '活跃客户',
'沉默客户', None, '流失风险', '高价值客户', None, '一般客户'])
})
print("复杂数据示例:")
print(complex_info_data)
# 1. 基础info
print("\n1. 基础信息概览:")
complex_info_data.info()
# 2. 详细模式
print("\n2. 详细模式:")
complex_info_data.info(verbose=True)
# 3. 内存使用分析
print("\n3. 内存使用分析:")
complex_info_data.info(memory_usage='deep')
# 4. 自定义输出
print("\n4. 自定义输出:")
import io
# 将info输出到字符串缓冲区
buffer = io.StringIO()
complex_info_data.info(buf=buffer, show_counts=True, memory_usage='deep')
info_output = buffer.getvalue()
# 解析并格式化info输出
print("格式化后的info输出:")
print("-" * 50)
print(info_output)
print("-" * 50)
# 5. 提取info信息进行分析
def analyze_info(df):
"""分析DataFrame的info信息"""
info_dict = {
'行数': len(df),
'列数': len(df.columns),
'总内存(MB)': df.memory_usage(deep=True).sum() / 1024**2,
'数据类型分布': {},
'缺失值分析': {},
'分类数据信息': {}
}
# 数据类型分布
dtype_counts = df.dtypes.value_counts()
for dtype, count in dtype_counts.items():
info_dict['数据类型分布'][str(dtype)] = count
# 缺失值分析
missing_counts = df.isnull().sum()
missing_percentage = (missing_counts / len(df) * 100).round(2)
for col in df.columns:
if missing_counts[col] > 0:
info_dict['缺失值分析'][col] = {
'缺失数量': int(missing_counts[col]),
'缺失比例(%)': missing_percentage[col]
}
# 分类数据信息
categorical_cols = df.select_dtypes(include=['category']).columns
for col in categorical_cols:
info_dict['分类数据信息'][col] = {
'类别数': df[col].nunique(),
'类别列表': df[col].cat.categories.tolist(),
'是否有序': df[col].cat.ordered
}
return info_dict
# 执行分析
info_analysis = analyze_info(complex_info_data)
print("\n5. 深度信息分析:")
print(f"数据规模:{info_analysis['行数']}行 × {info_analysis['列数']}列")
print(f"内存使用:{info_analysis['总内存(MB)']:.2f} MB")
print("\n数据类型分布:")
for dtype, count in info_analysis['数据类型分布'].items():
print(f" {dtype:25}: {count}列")
if info_analysis['缺失值分析']:
print("\n缺失值分析:")
for col, stats in info_analysis['缺失值分析'].items():
print(f" {col:10}: {stats['缺失数量']}个缺失 ({stats['缺失比例(%)']}%)")
if info_analysis['分类数据信息']:
print("\n分类数据信息:")
for col, stats in info_analysis['分类数据信息'].items():
print(f" {col:10}: {stats['类别数']}个类别, 有序: {stats['是否有序']}")
# 6. 信息可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. 数据类型分布饼图
dtype_data = pd.Series(info_analysis['数据类型分布'])
dtype_data.plot.pie(ax=axes[0, 0], autopct='%1.1f%%', startangle=90)
axes[0, 0].set_title('数据类型分布')
axes[0, 0].set_ylabel('')
# 2. 缺失值分析条形图
if info_analysis['缺失值分析']:
missing_df = pd.DataFrame(info_analysis['缺失值分析']).T
missing_df['缺失比例(%)'].plot.bar(ax=axes[0, 1], color='lightcoral', edgecolor='black')
axes[0, 1].set_title('各列缺失值比例')
axes[0, 1].set_xlabel('列名')
axes[0, 1].set_ylabel('缺失比例 (%)')
axes[0, 1].tick_params(axis='x', rotation=45)
axes[0, 1].grid(axis='y', alpha=0.3)
else:
axes[0, 1].text(0.5, 0.5, '无缺失值', ha='center', va='center', fontsize=12)
axes[0, 1].set_title('缺失值分析')
axes[0, 1].axis('off')
# 3. 内存使用分析
memory_by_column = complex_info_data.memory_usage(deep=True, index=False)
memory_by_column.plot.bar(ax=axes[1, 0], color='lightgreen', edgecolor='black')
axes[1, 0].set_title('各列内存使用')
axes[1, 0].set_xlabel('列名')
axes[1, 0].set_ylabel('内存 (bytes)')
axes[1, 0].tick_params(axis='x', rotation=45)
axes[1, 0].grid(axis='y', alpha=0.3)
# 添加数值标签(转换为KB)
for i, v in enumerate(memory_by_column):
axes[1, 0].text(i, v + 100, f'{v/1024:.1f}KB', ha='center', va='bottom', fontsize=8)
# 4. 分类数据类别数
if info_analysis['分类数据信息']:
category_counts = {col: stats['类别数']
for col, stats in info_analysis['分类数据信息'].items()}
pd.Series(category_counts).plot.bar(ax=axes[1, 1], color='skyblue', edgecolor='black')
axes[1, 1].set_title('分类数据类别数')
axes[1, 1].set_xlabel('列名')
axes[1, 1].set_ylabel('类别数')
axes[1, 1].grid(axis='y', alpha=0.3)
else:
axes[1, 1].text(0.5, 0.5, '无分类数据', ha='center', va='center', fontsize=12)
axes[1, 1].set_title('分类数据分析')
axes[1, 1].axis('off')
plt.tight_layout()
plt.show()
# 7. 信息监控和报告生成
class DataFrameInspector:
"""DataFrame检查器"""
def __init__(self, df):
self.df = df
self.report = {}
def generate_report(self):
"""生成完整检查报告"""
self._analyze_structure()
self._analyze_data_quality()
self._analyze_memory()
self._generate_summary()
return self.report
def _analyze_structure(self):
"""分析数据结构"""
self.report['结构'] = {
'行数': len(self.df),
'列数': len(self.df.columns),
'索引类型': type(self.df.index).__name__,
'列数据类型分布': self.df.dtypes.value_counts().to_dict(),
'多级索引': isinstance(self.df.index, pd.MultiIndex),
'多级列索引': isinstance(self.df.columns, pd.MultiIndex)
}
def _analyze_data_quality(self):
"""分析数据质量"""
missing_stats = self.df.isnull().sum()
missing_stats = missing_stats[missing_stats > 0]
self.report['数据质量'] = {
'总缺失值数': self.df.isnull().sum().sum(),
'缺失值列数': len(missing_stats),
'缺失值详情': missing_stats.to_dict(),
'重复行数': self.df.duplicated().sum(),
'唯一值分析': {col: self.df[col].nunique() for col in self.df.columns}
}
def _analyze_memory(self):
"""分析内存使用"""
memory_stats = self.df.memory_usage(deep=True)
self.report['内存'] = {
'总内存(bytes)': memory_stats.sum(),
'总内存(MB)': memory_stats.sum() / 1024**2,
'各列内存': memory_stats.to_dict(),
'内存优化建议': self._get_memory_optimization_suggestions()
}
def _get_memory_optimization_suggestions(self):
"""获取内存优化建议"""
suggestions = []
# 检查整数列优化
int_cols = self.df.select_dtypes(include=['int64']).columns
for col in int_cols:
col_min = self.df[col].min()
col_max = self.df[col].max()
if col_min >= 0 and col_max < 256:
suggestions.append(f"列 '{col}' 可优化为 uint8 (当前: int64)")
elif col_min >= -128 and col_max < 128:
suggestions.append(f"列 '{col}' 可优化为 int8 (当前: int64)")
# 检查浮点数列优化
float_cols = self.df.select_dtypes(include=['float64']).columns
for col in float_cols:
suggestions.append(f"列 '{col}' 可考虑优化为 float32 (当前: float64)")
# 检查对象列优化
obj_cols = self.df.select_dtypes(include=['object']).columns
for col in obj_cols:
unique_ratio = self.df[col].nunique() / len(self.df)
if unique_ratio < 0.5:
suggestions.append(f"列 '{col}' 可优化为 category (唯一值比例: {unique_ratio:.1%})")
return suggestions if suggestions else ["当前数据类型已较优化"]
def _generate_summary(self):
"""生成总结"""
total_missing = self.report['数据质量']['总缺失值数']
total_cells = len(self.df) * len(self.df.columns)
completeness = (1 - total_missing / total_cells) * 100 if total_cells > 0 else 100
self.report['总结'] = {
'数据完整性': f"{completeness:.1f}%",
'主要问题': self._identify_main_issues(),
'建议行动': self._generate_actions()
}
def _identify_main_issues(self):
"""识别主要问题"""
issues = []
# 检查高缺失率列
missing_details = self.report['数据质量']['缺失值详情']
for col, count in missing_details.items():
missing_rate = count / len(self.df) * 100
if missing_rate > 20:
issues.append(f"列 '{col}' 缺失率过高 ({missing_rate:.1f}%)")
# 检查低基数列
unique_analysis = self.report['数据质量']['唯一值分析']
for col, count in unique_analysis.items():
if count == 1:
issues.append(f"列 '{col}' 只有一个唯一值,可能无效")
return issues if issues else ["无明显数据质量问题"]
def _generate_actions(self):
"""生成建议行动"""
actions = [
"1. 使用fillna处理缺失值",
"2. 使用drop_duplicates删除重复行",
"3. 考虑优化数据类型以节省内存"
]
# 添加特定建议
suggestions = self.report['内存']['内存优化建议']
for i, suggestion in enumerate(suggestions[:3]): # 只显示前3个
actions.append(f"{i+4}. {suggestion}")
return actions
def print_report(self):
"""打印检查报告"""
print("=" * 60)
print("DataFrame检查报告")
print("=" * 60)
print(f"\n1. 数据结构:")
structure = self.report['结构']
print(f" 行数: {structure['行数']:,}")
print(f" 列数: {structure['列数']}")
print(f" 索引类型: {structure['索引类型']}")
print(f"\n2. 数据质量:")
quality = self.report['数据质量']
print(f" 总缺失值: {quality['总缺失值数']:,}")
print(f" 缺失值列数: {quality['缺失值列数']}")
if quality['缺失值详情']:
print(" 缺失值详情:")
for col, count in quality['缺失值详情'].items():
print(f" - {col}: {count}个")
print(f" 重复行数: {quality['重复行数']}")
print(f"\n3. 内存使用:")
memory = self.report['内存']
print(f" 总内存: {memory['总内存(MB)']:.2f} MB")
print(" 内存优化建议:")
for suggestion in memory['内存优化建议']:
print(f" - {suggestion}")
print(f"\n4. 总结:")
summary = self.report['总结']
print(f" 数据完整性: {summary['数据完整性']}")
print(" 主要问题:")
for issue in summary['主要问题']:
print(f" - {issue}")
print(" 建议行动:")
for action in summary['建议行动']:
print(f" {action}")
print("\n" + "=" * 60)
# 使用检查器
print("\n8. DataFrame检查器演示:")
inspector = DataFrameInspector(complex_info_data)
report = inspector.generate_report()
inspector.print_report()
2.7 apply与map:灵活的数据转换
apply和map是Pandas中最灵活的数据转换方法,可以对每个元素、每行或每列应用自定义函数。
python
print("=== 数据转换apply与map ===")
# 创建销售数据示例
sales_data = pd.DataFrame({
'订单ID': [f'ORD{1000+i:04d}' for i in range(20)],
'产品': np.random.choice(['iPhone 14', 'MacBook Pro', 'iPad Air', 'AirPods', 'Watch'], 20),
'数量': np.random.randint(1, 10, 20),
'单价': np.random.uniform(100, 2000, 20).round(2),
'折扣率': np.random.uniform(0, 0.3, 20).round(2),
'地区': np.random.choice(['华北', '华东', '华南', '华中', '西北'], 20),
'客户类型': np.random.choice(['新客户', '老客户', 'VIP客户'], 20)
})
# 计算基础列
sales_data['总价'] = sales_data['数量'] * sales_data['单价']
sales_data['折后价'] = sales_data['总价'] * (1 - sales_data['折扣率'])
print("原始销售数据:")
print(sales_data.head())
# 1. apply 基础应用
print("\n1. apply基础应用:")
# 对每行应用函数
def calculate_profit(row):
"""计算每行利润(假设成本是单价的70%)"""
cost = row['单价'] * 0.7
revenue = row['折后价']
profit = revenue - (cost * row['数量'])
profit_margin = profit / revenue if revenue > 0 else 0
return pd.Series({
'成本': cost * row['数量'],
'利润': profit,
'利润率': profit_margin
})
# 应用函数
profit_calc = sales_data.apply(calculate_profit, axis=1)
sales_data = pd.concat([sales_data, profit_calc], axis=1)
print("添加利润计算后:")
print(sales_data[['订单ID', '产品', '总价', '折后价', '成本', '利润', '利润率']].head())
# 2. map 基础应用
print("\n2. map基础应用:")
# 创建映射字典
product_category = {
'iPhone 14': '手机',
'MacBook Pro': '电脑',
'iPad Air': '平板',
'AirPods': '配件',
'Watch': '穿戴设备'
}
# 应用映射
sales_data['产品类别'] = sales_data['产品'].map(product_category)
print("添加产品类别后:")
print(sales_data[['订单ID', '产品', '产品类别']].head())
# 3. 复杂apply:多条件判断
print("\n3. 复杂apply:多条件判断")
def classify_order(row):
"""订单分类"""
# 基于金额分类
if row['折后价'] > 5000:
amount_class = '大额订单'
elif row['折后价'] > 1000:
amount_class = '中等订单'
else:
amount_class = '小额订单'
# 基于利润率分类
if row['利润率'] > 0.3:
profit_class = '高利润'
elif row['利润率'] > 0.1:
profit_class = '中等利润'
else:
profit_class = '低利润'
# 综合分类
if amount_class == '大额订单' and profit_class == '高利润':
overall_class = '优质订单'
elif amount_class == '小额订单' and profit_class == '低利润':
overall_class = '边缘订单'
else:
overall_class = '普通订单'
return pd.Series({
'金额分类': amount_class,
'利润分类': profit_class,
'综合分类': overall_class
})
# 应用复杂分类
order_classification = sales_data.apply(classify_order, axis=1)
sales_data = pd.concat([sales_data, order_classification], axis=1)
print("订单分类结果:")
print(sales_data[['订单ID', '折后价', '利润率', '金额分类', '利润分类', '综合分类']].head())
# 4. map 高级应用:条件映射
print("\n4. map高级应用:条件映射")
# 创建条件映射函数
def map_discount_tier(discount_rate):
"""根据折扣率映射折扣等级"""
if discount_rate >= 0.2:
return '高折扣'
elif discount_rate >= 0.1:
return '中折扣'
elif discount_rate > 0:
return '低折扣'
else:
return '无折扣'
# 应用条件映射
sales_data['折扣等级'] = sales_data['折扣率'].map(map_discount_tier)
print("折扣等级映射结果:")
print(sales_data[['订单ID', '折扣率', '折扣等级']].head())
# 5. apply 与 groupby 结合
print("\n5. apply与groupby结合:")
# 按地区分组后应用函数
def analyze_region(group):
"""分析地区销售数据"""
analysis = pd.Series({
'订单数': len(group),
'总销售额': group['折后价'].sum(),
'平均订单金额': group['折后价'].mean(),
'最大订单': group['折后价'].max(),
'最小订单': group['折后价'].min(),
'总利润': group['利润'].sum(),
'平均利润率': group['利润率'].mean(),
'客户类型分布': group['客户类型'].value_counts().to_dict()
})
return analysis
# 应用地区分析
region_analysis = sales_data.groupby('地区').apply(analyze_region)
print("地区分析结果:")
print(region_analysis)
# 6. map 与 Series 结合
print("\n6. map与Series结合:")
# 创建产品价格参考Series
product_price_ref = pd.Series({
'iPhone 14': 5999,
'MacBook Pro': 12999,
'iPad Air': 4799,
'AirPods': 1399,
'Watch': 2999
})
# 计算价格差异
sales_data['参考价格'] = sales_data['产品'].map(product_price_ref)
sales_data['价格差异'] = sales_data['单价'] - sales_data['参考价格']
sales_data['价格差异百分比'] = (sales_data['价格差异'] / sales_data['参考价格'] * 100).round(2)
print("价格差异分析:")
print(sales_data[['订单ID', '产品', '单价', '参考价格', '价格差异', '价格差异百分比']].head())
# 7. 性能优化:向量化操作
print("\n7. 性能优化:向量化操作")
# 创建大数据集
np.random.seed(42)
large_sales_data = pd.DataFrame({
'单价': np.random.uniform(100, 2000, 100000),
'数量': np.random.randint(1, 10, 100000),
'折扣率': np.random.uniform(0, 0.3, 100000)
})
# 方法1:使用apply(慢)
import time
start = time.time()
large_sales_data['总价_apply'] = large_sales_data.apply(
lambda row: row['单价'] * row['数量'] * (1 - row['折扣率']), axis=1
)
apply_time = time.time() - start
# 方法2:向量化操作(快)
start = time.time()
large_sales_data['总价_vectorized'] = (
large_sales_data['单价'] *
large_sales_data['数量'] *
(1 - large_sales_data['折扣率'])
)
vectorized_time = time.time() - start
print(f"apply方法耗时:{apply_time:.4f}秒")
print(f"向量化方法耗时:{vectorized_time:.4f}秒")
print(f"性能提升:{apply_time/vectorized_time:.1f}倍")
# 验证结果一致性
results_match = np.allclose(
large_sales_data['总价_apply'],
large_sales_data['总价_vectorized']
)
print(f"结果一致性:{results_match}")
# 可视化apply和map的应用
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. 利润分布
axes[0, 0].hist(sales_data['利润'], bins=20, color='lightgreen',
edgecolor='black', alpha=0.7)
axes[0, 0].set_title('利润分布')
axes[0, 0].set_xlabel('利润')
axes[0, 0].set_ylabel('频数')
axes[0, 0].axvline(sales_data['利润'].mean(), color='red',
linestyle='--', label=f'平均利润: {sales_data["利润"].mean():.2f}')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
# 2. 订单分类分布
order_class_counts = sales_data['综合分类'].value_counts()
order_class_counts.plot.bar(ax=axes[0, 1], color='skyblue', edgecolor='black')
axes[0, 1].set_title('订单分类分布')
axes[0, 1].set_xlabel('订单分类')
axes[0, 1].set_ylabel('订单数')
axes[0, 1].tick_params(axis='x', rotation=45)
axes[0, 1].grid(axis='y', alpha=0.3)
# 3. 地区销售分析
region_sales = sales_data.groupby('地区')['折后价'].sum().sort_values()
region_sales.plot.barh(ax=axes[1, 0], color='lightcoral', edgecolor='black')
axes[1, 0].set_title('各地区销售额')
axes[1, 0].set_xlabel('销售额')
axes[1, 0].grid(axis='x', alpha=0.3)
# 4. 折扣等级 vs 利润率
discount_profit = sales_data.groupby('折扣等级')['利润率'].mean()
discount_profit.plot.bar(ax=axes[1, 1], color='gold', edgecolor='black')
axes[1, 1].set_title('折扣等级 vs 平均利润率')
axes[1, 1].set_xlabel('折扣等级')
axes[1, 1].set_ylabel('平均利润率')
axes[1, 1].grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
# 创建交互式仪表板
try:
import plotly.express as px
# 创建散点图:单价 vs 利润率,按产品类别着色
fig1 = px.scatter(sales_data, x='单价', y='利润率',
color='产品类别', size='数量',
hover_data=['订单ID', '产品', '地区'],
title='单价 vs 利润率(按产品类别)')
fig1.show()
# 创建箱线图:各地区利润率分布
fig2 = px.box(sales_data, x='地区', y='利润率',
color='地区', title='各地区利润率分布')
fig2.show()
# 创建条形图:订单分类分布
fig3 = px.bar(order_class_counts.reset_index(),
x='index', y='综合分类',
title='订单分类分布',
labels={'index': '订单分类', '综合分类': '订单数'})
fig3.show()
except ImportError:
print("\n提示:安装plotly库可生成交互式图表")