2.1 线图(Line Plot)

2.1.1 基本线图

线图是最常用的图表类型,用于显示数据随时间或其他连续变量的变化趋势。

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from datetime import datetime, timedelta

class LinePlotDemo:
    """线图演示类"""
    
    def __init__(self):
        # 设置中文字体
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        plt.rcParams['axes.unicode_minus'] = False
        
        # 生成示例数据
        self.x = np.linspace(0, 10, 100)
        self.y1 = np.sin(self.x)
        self.y2 = np.cos(self.x)
        self.y3 = np.sin(self.x) * np.exp(-self.x/10)
    
    def basic_line_plot(self):
        """基本线图"""
        plt.figure(figsize=(10, 6))
        
        # 基本线图
        plt.plot(self.x, self.y1)
        
        plt.title('基本线图')
        plt.xlabel('X轴')
        plt.ylabel('Y轴')
        plt.grid(True, alpha=0.3)
        plt.show()
    
    def multiple_lines(self):
        """多条线图"""
        plt.figure(figsize=(12, 8))
        
        # 绘制多条线
        plt.plot(self.x, self.y1, 'b-', linewidth=2, label='sin(x)')
        plt.plot(self.x, self.y2, 'r--', linewidth=2, label='cos(x)')
        plt.plot(self.x, self.y3, 'g:', linewidth=3, label='衰减sin(x)')
        
        plt.title('多条线图对比', fontsize=16)
        plt.xlabel('X轴', fontsize=12)
        plt.ylabel('Y轴', fontsize=12)
        plt.legend(fontsize=12)
        plt.grid(True, alpha=0.3)
        plt.show()
    
    def line_styles_demo(self):
        """线型样式演示"""
        fig, axes = plt.subplots(2, 2, figsize=(15, 12))
        
        # 线型样式
        line_styles = ['-', '--', '-.', ':']
        style_names = ['实线', '虚线', '点划线', '点线']
        
        for i, (style, name) in enumerate(zip(line_styles, style_names)):
            row, col = i // 2, i % 2
            axes[row, col].plot(self.x, self.y1, linestyle=style, 
                              linewidth=3, label=name)
            axes[row, col].set_title(f'线型: {name}')
            axes[row, col].legend()
            axes[row, col].grid(True, alpha=0.3)
        
        plt.tight_layout()
        plt.show()
    
    def markers_demo(self):
        """标记样式演示"""
        markers = ['o', 's', '^', 'D', 'v', '<', '>', 'p', '*', 'h']
        marker_names = ['圆形', '方形', '上三角', '菱形', '下三角', 
                       '左三角', '右三角', '五角形', '星形', '六角形']
        
        fig, axes = plt.subplots(2, 5, figsize=(20, 8))
        axes = axes.flatten()
        
        for i, (marker, name) in enumerate(zip(markers, marker_names)):
            x_sample = self.x[::10]  # 采样数据点
            y_sample = self.y1[::10]
            
            axes[i].plot(x_sample, y_sample, marker=marker, 
                        markersize=8, linewidth=2, label=name)
            axes[i].set_title(f'标记: {name}')
            axes[i].legend()
            axes[i].grid(True, alpha=0.3)
        
        plt.tight_layout()
        plt.show()
    
    def time_series_plot(self):
        """时间序列线图"""
        # 生成时间序列数据
        dates = pd.date_range('2023-01-01', periods=365, freq='D')
        np.random.seed(42)
        
        # 模拟股价数据
        price = 100
        prices = [price]
        for _ in range(364):
            change = np.random.normal(0, 2)
            price += change
            prices.append(price)
        
        # 计算移动平均
        df = pd.DataFrame({'date': dates, 'price': prices})
        df['ma_7'] = df['price'].rolling(window=7).mean()
        df['ma_30'] = df['price'].rolling(window=30).mean()
        
        plt.figure(figsize=(15, 8))
        
        # 绘制价格和移动平均线
        plt.plot(df['date'], df['price'], 'b-', linewidth=1, 
                alpha=0.7, label='日价格')
        plt.plot(df['date'], df['ma_7'], 'r-', linewidth=2, 
                label='7日移动平均')
        plt.plot(df['date'], df['ma_30'], 'g-', linewidth=2, 
                label='30日移动平均')
        
        plt.title('股价时间序列图', fontsize=16)
        plt.xlabel('日期', fontsize=12)
        plt.ylabel('价格', fontsize=12)
        plt.legend(fontsize=12)
        plt.grid(True, alpha=0.3)
        
        # 格式化x轴日期
        plt.xticks(rotation=45)
        plt.tight_layout()
        plt.show()

# 使用示例
line_demo = LinePlotDemo()
line_demo.basic_line_plot()
line_demo.multiple_lines()
line_demo.line_styles_demo()
line_demo.markers_demo()
line_demo.time_series_plot()

2.1.2 高级线图技巧

class AdvancedLinePlot:
    """高级线图技巧"""
    
    def __init__(self):
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        plt.rcParams['axes.unicode_minus'] = False
    
    def filled_area_plot(self):
        """填充区域图"""
        x = np.linspace(0, 10, 100)
        y1 = np.sin(x)
        y2 = np.cos(x)
        
        plt.figure(figsize=(12, 8))
        
        # 填充区域
        plt.fill_between(x, y1, alpha=0.3, label='sin(x)区域')
        plt.fill_between(x, y2, alpha=0.3, label='cos(x)区域')
        
        # 绘制线条
        plt.plot(x, y1, 'b-', linewidth=2, label='sin(x)')
        plt.plot(x, y2, 'r-', linewidth=2, label='cos(x)')
        
        # 填充两线之间的区域
        plt.fill_between(x, y1, y2, where=(y1 >= y2), 
                        color='green', alpha=0.2, 
                        interpolate=True, label='sin≥cos区域')
        
        plt.title('填充区域图', fontsize=16)
        plt.xlabel('X轴')
        plt.ylabel('Y轴')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.show()
    
    def step_plot(self):
        """阶梯图"""
        x = np.arange(0, 10, 1)
        y = np.random.randint(1, 10, len(x))
        
        plt.figure(figsize=(12, 6))
        
        # 不同的阶梯样式
        plt.step(x, y, where='pre', linewidth=2, label='pre')
        plt.step(x, y+2, where='mid', linewidth=2, label='mid')
        plt.step(x, y+4, where='post', linewidth=2, label='post')
        
        plt.title('阶梯图样式对比', fontsize=16)
        plt.xlabel('X轴')
        plt.ylabel('Y轴')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.show()
    
    def error_bars(self):
        """误差条图"""
        x = np.arange(0, 10, 1)
        y = np.random.rand(len(x)) * 10
        yerr = np.random.rand(len(x)) * 2
        xerr = np.random.rand(len(x)) * 0.5
        
        plt.figure(figsize=(12, 8))
        
        # 带误差条的线图
        plt.errorbar(x, y, yerr=yerr, xerr=xerr, 
                    fmt='o-', linewidth=2, markersize=8,
                    capsize=5, capthick=2, 
                    label='数据点±误差')
        
        plt.title('带误差条的线图', fontsize=16)
        plt.xlabel('X轴')
        plt.ylabel('Y轴')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.show()
    
    def log_scale_plot(self):
        """对数坐标图"""
        x = np.logspace(0, 3, 100)  # 10^0 到 10^3
        y1 = x**2
        y2 = x**3
        
        fig, axes = plt.subplots(2, 2, figsize=(15, 12))
        
        # 线性坐标
        axes[0, 0].plot(x, y1, 'b-', label='x²')
        axes[0, 0].plot(x, y2, 'r-', label='x³')
        axes[0, 0].set_title('线性坐标')
        axes[0, 0].legend()
        axes[0, 0].grid(True)
        
        # 半对数坐标(Y轴对数)
        axes[0, 1].semilogy(x, y1, 'b-', label='x²')
        axes[0, 1].semilogy(x, y2, 'r-', label='x³')
        axes[0, 1].set_title('Y轴对数坐标')
        axes[0, 1].legend()
        axes[0, 1].grid(True)
        
        # 半对数坐标(X轴对数)
        axes[1, 0].semilogx(x, y1, 'b-', label='x²')
        axes[1, 0].semilogx(x, y2, 'r-', label='x³')
        axes[1, 0].set_title('X轴对数坐标')
        axes[1, 0].legend()
        axes[1, 0].grid(True)
        
        # 双对数坐标
        axes[1, 1].loglog(x, y1, 'b-', label='x²')
        axes[1, 1].loglog(x, y2, 'r-', label='x³')
        axes[1, 1].set_title('双对数坐标')
        axes[1, 1].legend()
        axes[1, 1].grid(True)
        
        plt.tight_layout()
        plt.show()

# 使用示例
advanced_line = AdvancedLinePlot()
advanced_line.filled_area_plot()
advanced_line.step_plot()
advanced_line.error_bars()
advanced_line.log_scale_plot()

2.2 散点图(Scatter Plot)

2.2.1 基本散点图

class ScatterPlotDemo:
    """散点图演示类"""
    
    def __init__(self):
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        plt.rcParams['axes.unicode_minus'] = False
        
        # 生成示例数据
        np.random.seed(42)
        self.n = 100
        self.x = np.random.randn(self.n)
        self.y = 2 * self.x + np.random.randn(self.n)
        self.colors = np.random.rand(self.n)
        self.sizes = 1000 * np.random.rand(self.n)
    
    def basic_scatter(self):
        """基本散点图"""
        plt.figure(figsize=(10, 8))
        
        plt.scatter(self.x, self.y)
        
        plt.title('基本散点图', fontsize=16)
        plt.xlabel('X轴')
        plt.ylabel('Y轴')
        plt.grid(True, alpha=0.3)
        plt.show()
    
    def colored_scatter(self):
        """彩色散点图"""
        plt.figure(figsize=(12, 8))
        
        # 使用颜色映射
        scatter = plt.scatter(self.x, self.y, c=self.colors, 
                            cmap='viridis', alpha=0.7, s=60)
        
        # 添加颜色条
        plt.colorbar(scatter, label='颜色值')
        
        plt.title('彩色散点图', fontsize=16)
        plt.xlabel('X轴')
        plt.ylabel('Y轴')
        plt.grid(True, alpha=0.3)
        plt.show()
    
    def sized_scatter(self):
        """大小变化的散点图"""
        plt.figure(figsize=(12, 8))
        
        # 点的大小表示第三个维度
        scatter = plt.scatter(self.x, self.y, s=self.sizes, 
                            c=self.colors, cmap='plasma', 
                            alpha=0.6, edgecolors='black', linewidth=0.5)
        
        plt.colorbar(scatter, label='颜色值')
        
        plt.title('大小和颜色变化的散点图', fontsize=16)
        plt.xlabel('X轴')
        plt.ylabel('Y轴')
        plt.grid(True, alpha=0.3)
        
        # 添加大小图例
        sizes_legend = [50, 200, 500, 1000]
        for size in sizes_legend:
            plt.scatter([], [], s=size, c='gray', alpha=0.6, 
                       edgecolors='black', linewidth=0.5,
                       label=f'大小 {size}')
        plt.legend(title='点大小', loc='upper left')
        
        plt.show()
    
    def categorical_scatter(self):
        """分类散点图"""
        # 生成分类数据
        np.random.seed(42)
        categories = ['A', 'B', 'C']
        n_per_cat = 50
        
        data = []
        for i, cat in enumerate(categories):
            x = np.random.normal(i*2, 0.5, n_per_cat)
            y = np.random.normal(i*1.5, 0.8, n_per_cat)
            data.append((x, y, cat))
        
        plt.figure(figsize=(12, 8))
        
        colors = ['red', 'blue', 'green']
        markers = ['o', 's', '^']
        
        for (x, y, cat), color, marker in zip(data, colors, markers):
            plt.scatter(x, y, c=color, marker=marker, s=60, 
                       alpha=0.7, label=f'类别 {cat}', edgecolors='black')
        
        plt.title('分类散点图', fontsize=16)
        plt.xlabel('X轴')
        plt.ylabel('Y轴')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.show()
    
    def correlation_analysis(self):
        """相关性分析散点图"""
        # 生成不同相关性的数据
        np.random.seed(42)
        n = 100
        
        # 强正相关
        x1 = np.random.randn(n)
        y1 = 0.8 * x1 + 0.2 * np.random.randn(n)
        
        # 强负相关
        x2 = np.random.randn(n)
        y2 = -0.8 * x2 + 0.2 * np.random.randn(n)
        
        # 无相关
        x3 = np.random.randn(n)
        y3 = np.random.randn(n)
        
        # 非线性相关
        x4 = np.random.randn(n)
        y4 = x4**2 + 0.5 * np.random.randn(n)
        
        fig, axes = plt.subplots(2, 2, figsize=(15, 12))
        
        # 计算并显示相关系数
        datasets = [(x1, y1, '强正相关'), (x2, y2, '强负相关'), 
                   (x3, y3, '无相关'), (x4, y4, '非线性相关')]
        
        for i, (x, y, title) in enumerate(datasets):
            row, col = i // 2, i % 2
            
            axes[row, col].scatter(x, y, alpha=0.6, s=50)
            
            # 计算相关系数
            corr = np.corrcoef(x, y)[0, 1]
            
            # 添加趋势线(对于线性相关)
            if i < 3:  # 前三个是线性的
                z = np.polyfit(x, y, 1)
                p = np.poly1d(z)
                axes[row, col].plot(sorted(x), p(sorted(x)), 'r--', alpha=0.8)
            
            axes[row, col].set_title(f'{title}\n相关系数: {corr:.3f}')
            axes[row, col].grid(True, alpha=0.3)
        
        plt.tight_layout()
        plt.show()

# 使用示例
scatter_demo = ScatterPlotDemo()
scatter_demo.basic_scatter()
scatter_demo.colored_scatter()
scatter_demo.sized_scatter()
scatter_demo.categorical_scatter()
scatter_demo.correlation_analysis()

2.2.2 高级散点图技巧

class AdvancedScatterPlot:
    """高级散点图技巧"""
    
    def __init__(self):
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        plt.rcParams['axes.unicode_minus'] = False
    
    def bubble_chart(self):
        """气泡图"""
        # 模拟国家数据:GDP vs 人口 vs 人均收入
        np.random.seed(42)
        countries = ['中国', '美国', '印度', '德国', '日本', 
                    '英国', '法国', '巴西', '意大利', '加拿大']
        
        gdp = np.random.uniform(1, 20, len(countries))  # GDP (万亿美元)
        population = np.random.uniform(0.1, 14, len(countries))  # 人口 (亿)
        income = np.random.uniform(10, 80, len(countries))  # 人均收入 (千美元)
        
        plt.figure(figsize=(14, 10))
        
        # 创建气泡图
        scatter = plt.scatter(gdp, income, s=population*50, 
                            c=population, cmap='viridis', 
                            alpha=0.7, edgecolors='black', linewidth=1)
        
        # 添加国家标签
        for i, country in enumerate(countries):
            plt.annotate(country, (gdp[i], income[i]), 
                        xytext=(5, 5), textcoords='offset points',
                        fontsize=10, alpha=0.8)
        
        plt.colorbar(scatter, label='人口 (亿)')
        
        plt.title('世界主要国家经济指标气泡图', fontsize=16)
        plt.xlabel('GDP (万亿美元)', fontsize=12)
        plt.ylabel('人均收入 (千美元)', fontsize=12)
        plt.grid(True, alpha=0.3)
        
        # 添加气泡大小图例
        bubble_sizes = [1, 5, 10]
        for size in bubble_sizes:
            plt.scatter([], [], s=size*50, c='gray', alpha=0.7, 
                       edgecolors='black', linewidth=1,
                       label=f'{size}亿人')
        plt.legend(title='人口规模', loc='upper left')
        
        plt.show()
    
    def density_scatter(self):
        """密度散点图"""
        from scipy.stats import gaussian_kde
        
        # 生成密集的数据点
        np.random.seed(42)
        n = 1000
        x = np.random.multivariate_normal([0, 0], [[1, 0.5], [0.5, 1]], n)
        
        fig, axes = plt.subplots(1, 3, figsize=(18, 6))
        
        # 普通散点图
        axes[0].scatter(x[:, 0], x[:, 1], alpha=0.5, s=20)
        axes[0].set_title('普通散点图')
        axes[0].grid(True, alpha=0.3)
        
        # 基于密度的颜色映射
        xy = np.vstack([x[:, 0], x[:, 1]])
        z = gaussian_kde(xy)(xy)
        
        scatter = axes[1].scatter(x[:, 0], x[:, 1], c=z, 
                                cmap='viridis', s=20, alpha=0.7)
        axes[1].set_title('密度颜色映射')
        axes[1].grid(True, alpha=0.3)
        plt.colorbar(scatter, ax=axes[1], label='密度')
        
        # 六边形分箱图
        hb = axes[2].hexbin(x[:, 0], x[:, 1], gridsize=20, 
                           cmap='viridis', alpha=0.8)
        axes[2].set_title('六边形分箱图')
        axes[2].grid(True, alpha=0.3)
        plt.colorbar(hb, ax=axes[2], label='计数')
        
        plt.tight_layout()
        plt.show()
    
    def marginal_plots(self):
        """边际分布图"""
        # 生成二维正态分布数据
        np.random.seed(42)
        mean = [0, 0]
        cov = [[1, 0.6], [0.6, 1]]
        x, y = np.random.multivariate_normal(mean, cov, 500).T
        
        # 创建带边际分布的散点图
        fig = plt.figure(figsize=(12, 10))
        
        # 定义网格
        gs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)
        
        # 主散点图
        ax_main = fig.add_subplot(gs[1:, :-1])
        ax_main.scatter(x, y, alpha=0.6, s=30)
        ax_main.set_xlabel('X轴')
        ax_main.set_ylabel('Y轴')
        ax_main.grid(True, alpha=0.3)
        
        # X轴边际分布
        ax_top = fig.add_subplot(gs[0, :-1], sharex=ax_main)
        ax_top.hist(x, bins=30, alpha=0.7, color='skyblue', edgecolor='black')
        ax_top.set_ylabel('频次')
        ax_top.tick_params(labelbottom=False)
        
        # Y轴边际分布
        ax_right = fig.add_subplot(gs[1:, -1], sharey=ax_main)
        ax_right.hist(y, bins=30, orientation='horizontal', 
                     alpha=0.7, color='lightcoral', edgecolor='black')
        ax_right.set_xlabel('频次')
        ax_right.tick_params(labelleft=False)
        
        plt.suptitle('带边际分布的散点图', fontsize=16)
        plt.show()
    
    def animated_scatter(self):
        """动画散点图"""
        import matplotlib.animation as animation
        
        # 生成动画数据
        np.random.seed(42)
        n_points = 50
        n_frames = 100
        
        # 初始位置
        x = np.random.randn(n_points)
        y = np.random.randn(n_points)
        colors = np.random.rand(n_points)
        
        fig, ax = plt.subplots(figsize=(10, 8))
        
        # 初始化散点图
        scat = ax.scatter(x, y, c=colors, s=100, alpha=0.7, cmap='viridis')
        
        ax.set_xlim(-3, 3)
        ax.set_ylim(-3, 3)
        ax.set_title('动画散点图')
        ax.grid(True, alpha=0.3)
        
        def animate(frame):
            global x, y
            
            # 更新位置(随机游走)
            x += np.random.normal(0, 0.05, n_points)
            y += np.random.normal(0, 0.05, n_points)
            
            # 边界反弹
            x = np.clip(x, -3, 3)
            y = np.clip(y, -3, 3)
            
            # 更新散点图数据
            scat.set_offsets(np.column_stack((x, y)))
            
            return scat,
        
        anim = animation.FuncAnimation(fig, animate, frames=n_frames, 
                                     interval=100, blit=True, repeat=True)
        
        plt.show()
        return anim

# 使用示例
advanced_scatter = AdvancedScatterPlot()
advanced_scatter.bubble_chart()
advanced_scatter.density_scatter()
advanced_scatter.marginal_plots()
# anim = advanced_scatter.animated_scatter()  # 取消注释运行动画

2.3 柱状图(Bar Chart)

2.3.1 基本柱状图

class BarChartDemo:
    """柱状图演示类"""
    
    def __init__(self):
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        plt.rcParams['axes.unicode_minus'] = False
        
        # 示例数据
        self.categories = ['产品A', '产品B', '产品C', '产品D', '产品E']
        self.values = [23, 45, 56, 78, 32]
        self.quarters = ['Q1', 'Q2', 'Q3', 'Q4']
        self.sales_data = {
            '产品A': [20, 35, 30, 25],
            '产品B': [25, 30, 35, 40],
            '产品C': [15, 25, 20, 30]
        }
    
    def basic_bar_chart(self):
        """基本柱状图"""
        plt.figure(figsize=(10, 6))
        
        bars = plt.bar(self.categories, self.values, 
                      color='skyblue', edgecolor='navy', linewidth=1.2)
        
        # 在柱子上添加数值标签
        for bar, value in zip(bars, self.values):
            plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
                    str(value), ha='center', va='bottom', fontsize=12)
        
        plt.title('基本柱状图', fontsize=16)
        plt.xlabel('产品类别')
        plt.ylabel('销售数量')
        plt.grid(True, alpha=0.3, axis='y')
        plt.show()
    
    def horizontal_bar_chart(self):
        """水平柱状图"""
        plt.figure(figsize=(10, 6))
        
        bars = plt.barh(self.categories, self.values, 
                       color='lightcoral', edgecolor='darkred', linewidth=1.2)
        
        # 添加数值标签
        for bar, value in zip(bars, self.values):
            plt.text(bar.get_width() + 1, bar.get_y() + bar.get_height()/2,
                    str(value), ha='left', va='center', fontsize=12)
        
        plt.title('水平柱状图', fontsize=16)
        plt.xlabel('销售数量')
        plt.ylabel('产品类别')
        plt.grid(True, alpha=0.3, axis='x')
        plt.show()
    
    def grouped_bar_chart(self):
        """分组柱状图"""
        x = np.arange(len(self.quarters))
        width = 0.25
        
        plt.figure(figsize=(12, 8))
        
        colors = ['#ff9999', '#66b3ff', '#99ff99']
        
        for i, (product, sales) in enumerate(self.sales_data.items()):
            offset = (i - 1) * width
            bars = plt.bar(x + offset, sales, width, 
                          label=product, color=colors[i], 
                          edgecolor='black', linewidth=0.8)
            
            # 添加数值标签
            for bar, value in zip(bars, sales):
                plt.text(bar.get_x() + bar.get_width()/2, 
                        bar.get_height() + 0.5,
                        str(value), ha='center', va='bottom', fontsize=10)
        
        plt.title('季度销售对比 - 分组柱状图', fontsize=16)
        plt.xlabel('季度')
        plt.ylabel('销售数量')
        plt.xticks(x, self.quarters)
        plt.legend()
        plt.grid(True, alpha=0.3, axis='y')
        plt.show()
    
    def stacked_bar_chart(self):
        """堆叠柱状图"""
        plt.figure(figsize=(12, 8))
        
        bottom = np.zeros(len(self.quarters))
        colors = ['#ff9999', '#66b3ff', '#99ff99']
        
        for i, (product, sales) in enumerate(self.sales_data.items()):
            bars = plt.bar(self.quarters, sales, bottom=bottom,
                          label=product, color=colors[i],
                          edgecolor='black', linewidth=0.8)
            
            # 添加数值标签
            for j, (bar, value) in enumerate(zip(bars, sales)):
                plt.text(bar.get_x() + bar.get_width()/2,
                        bottom[j] + value/2,
                        str(value), ha='center', va='center', 
                        fontsize=10, fontweight='bold')
            
            bottom += sales
        
        plt.title('季度销售对比 - 堆叠柱状图', fontsize=16)
        plt.xlabel('季度')
        plt.ylabel('销售数量')
        plt.legend()
        plt.grid(True, alpha=0.3, axis='y')
        plt.show()
    
    def percentage_stacked_bar(self):
        """百分比堆叠柱状图"""
        # 计算百分比
        data_array = np.array(list(self.sales_data.values()))
        totals = data_array.sum(axis=0)
        percentages = (data_array / totals) * 100
        
        plt.figure(figsize=(12, 8))
        
        bottom = np.zeros(len(self.quarters))
        colors = ['#ff9999', '#66b3ff', '#99ff99']
        
        for i, (product, _) in enumerate(self.sales_data.items()):
            bars = plt.bar(self.quarters, percentages[i], bottom=bottom,
                          label=product, color=colors[i],
                          edgecolor='black', linewidth=0.8)
            
            # 添加百分比标签
            for j, (bar, value) in enumerate(zip(bars, percentages[i])):
                if value > 5:  # 只显示大于5%的标签
                    plt.text(bar.get_x() + bar.get_width()/2,
                            bottom[j] + value/2,
                            f'{value:.1f}%', ha='center', va='center',
                            fontsize=10, fontweight='bold')
            
            bottom += percentages[i]
        
        plt.title('季度销售占比 - 百分比堆叠柱状图', fontsize=16)
        plt.xlabel('季度')
        plt.ylabel('销售占比 (%)')
        plt.ylim(0, 100)
        plt.legend()
        plt.grid(True, alpha=0.3, axis='y')
        plt.show()

# 使用示例
bar_demo = BarChartDemo()
bar_demo.basic_bar_chart()
bar_demo.horizontal_bar_chart()
bar_demo.grouped_bar_chart()
bar_demo.stacked_bar_chart()
bar_demo.percentage_stacked_bar()

2.3.2 高级柱状图技巧

class AdvancedBarChart:
    """高级柱状图技巧"""
    
    def __init__(self):
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        plt.rcParams['axes.unicode_minus'] = False
    
    def gradient_bar_chart(self):
        """渐变色柱状图"""
        categories = ['A', 'B', 'C', 'D', 'E']
        values = [23, 45, 56, 78, 32]
        
        plt.figure(figsize=(12, 8))
        
        # 创建渐变色
        colors = plt.cm.viridis(np.linspace(0, 1, len(categories)))
        
        bars = plt.bar(categories, values, color=colors, 
                      edgecolor='black', linewidth=1.5)
        
        # 添加阴影效果
        for bar in bars:
            bar.set_alpha(0.8)
        
        # 添加数值标签
        for bar, value in zip(bars, values):
            plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
                    str(value), ha='center', va='bottom', 
                    fontsize=12, fontweight='bold')
        
        plt.title('渐变色柱状图', fontsize=16)
        plt.xlabel('类别')
        plt.ylabel('数值')
        plt.grid(True, alpha=0.3, axis='y')
        plt.show()
    
    def error_bar_chart(self):
        """带误差条的柱状图"""
        categories = ['实验A', '实验B', '实验C', '实验D']
        means = [20, 35, 30, 25]
        errors = [2, 3, 4, 1]
        
        plt.figure(figsize=(10, 8))
        
        bars = plt.bar(categories, means, yerr=errors, 
                      capsize=5, capthick=2, 
                      color='lightblue', edgecolor='navy',
                      error_kw={'elinewidth': 2, 'ecolor': 'red'})
        
        # 添加数值标签
        for bar, mean, error in zip(bars, means, errors):
            plt.text(bar.get_x() + bar.get_width()/2, 
                    bar.get_height() + error + 0.5,
                    f'{mean}±{error}', ha='center', va='bottom', fontsize=11)
        
        plt.title('实验结果对比(带误差条)', fontsize=16)
        plt.xlabel('实验组')
        plt.ylabel('测量值')
        plt.grid(True, alpha=0.3, axis='y')
        plt.show()
    
    def multi_level_bar_chart(self):
        """多级分类柱状图"""
        # 多级分类数据
        regions = ['北方', '南方']
        cities = ['城市A', '城市B', '城市C']
        products = ['产品X', '产品Y']
        
        # 生成示例数据
        np.random.seed(42)
        data = np.random.randint(10, 50, (len(regions), len(cities), len(products)))
        
        fig, axes = plt.subplots(1, 2, figsize=(16, 8))
        
        x = np.arange(len(cities))
        width = 0.35
        
        for i, region in enumerate(regions):
            for j, product in enumerate(products):
                offset = (j - 0.5) * width
                bars = axes[i].bar(x + offset, data[i, :, j], width,
                                  label=product, alpha=0.8)
                
                # 添加数值标签
                for bar, value in zip(bars, data[i, :, j]):
                    axes[i].text(bar.get_x() + bar.get_width()/2,
                                bar.get_height() + 0.5,
                                str(value), ha='center', va='bottom', fontsize=10)
            
            axes[i].set_title(f'{region}地区销售情况')
            axes[i].set_xlabel('城市')
            axes[i].set_ylabel('销售量')
            axes[i].set_xticks(x)
            axes[i].set_xticklabels(cities)
            axes[i].legend()
            axes[i].grid(True, alpha=0.3, axis='y')
        
        plt.tight_layout()
        plt.show()
    
    def waterfall_chart(self):
        """瀑布图"""
        categories = ['起始值', '增加A', '增加B', '减少C', '增加D', '最终值']
        values = [100, 20, 30, -15, 25, 0]  # 最终值将被计算
        
        # 计算累积值
        cumulative = [values[0]]
        for i in range(1, len(values)-1):
            cumulative.append(cumulative[-1] + values[i])
        cumulative.append(cumulative[-1])  # 最终值
        values[-1] = cumulative[-1]  # 更新最终值
        
        plt.figure(figsize=(12, 8))
        
        colors = ['blue'] + ['green' if v > 0 else 'red' for v in values[1:-1]] + ['blue']
        
        # 绘制柱子
        bars = []
        for i, (cat, val, cum, color) in enumerate(zip(categories, values, cumulative, colors)):
            if i == 0 or i == len(categories) - 1:
                # 起始值和最终值从0开始
                bar = plt.bar(i, cum, color=color, alpha=0.7, edgecolor='black')
                bars.append(bar)
            else:
                # 中间的变化值
                if val > 0:
                    bottom = cumulative[i-1]
                    bar = plt.bar(i, val, bottom=bottom, color=color, 
                                 alpha=0.7, edgecolor='black')
                else:
                    bottom = cumulative[i]
                    bar = plt.bar(i, abs(val), bottom=bottom, color=color, 
                                 alpha=0.7, edgecolor='black')
                bars.append(bar)
                
                # 添加连接线
                if i < len(categories) - 1:
                    plt.plot([i+0.4, i+0.6], [cumulative[i], cumulative[i]], 
                            'k--', alpha=0.5)
        
        # 添加数值标签
        for i, (val, cum) in enumerate(zip(values, cumulative)):
            if i == 0 or i == len(categories) - 1:
                plt.text(i, cum + 2, str(int(cum)), ha='center', va='bottom', 
                        fontsize=11, fontweight='bold')
            else:
                plt.text(i, cum + 2, f'{val:+d}', ha='center', va='bottom', 
                        fontsize=11, fontweight='bold')
        
        plt.title('业绩变化瀑布图', fontsize=16)
        plt.xlabel('变化因素')
        plt.ylabel('数值')
        plt.xticks(range(len(categories)), categories, rotation=45)
        plt.grid(True, alpha=0.3, axis='y')
        plt.tight_layout()
        plt.show()

# 使用示例
advanced_bar = AdvancedBarChart()
advanced_bar.gradient_bar_chart()
advanced_bar.error_bar_chart()
advanced_bar.multi_level_bar_chart()
advanced_bar.waterfall_chart()

2.4 直方图(Histogram)

2.4.1 基本直方图

class HistogramDemo:
    """直方图演示类"""
    
    def __init__(self):
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        plt.rcParams['axes.unicode_minus'] = False
        
        # 生成示例数据
        np.random.seed(42)
        self.normal_data = np.random.normal(100, 15, 1000)
        self.uniform_data = np.random.uniform(50, 150, 1000)
        self.exponential_data = np.random.exponential(2, 1000)
    
    def basic_histogram(self):
        """基本直方图"""
        plt.figure(figsize=(10, 6))
        
        n, bins, patches = plt.hist(self.normal_data, bins=30, 
                                   color='skyblue', edgecolor='black', alpha=0.7)
        
        plt.title('基本直方图 - 正态分布', fontsize=16)
        plt.xlabel('数值')
        plt.ylabel('频次')
        plt.grid(True, alpha=0.3, axis='y')
        
        # 添加统计信息
        mean_val = np.mean(self.normal_data)
        std_val = np.std(self.normal_data)
        plt.axvline(mean_val, color='red', linestyle='--', linewidth=2, 
                   label=f'均值: {mean_val:.2f}')
        plt.axvline(mean_val + std_val, color='orange', linestyle='--', 
                   label=f'+1σ: {mean_val + std_val:.2f}')
        plt.axvline(mean_val - std_val, color='orange', linestyle='--', 
                   label=f'-1σ: {mean_val - std_val:.2f}')
        
        plt.legend()
        plt.show()
    
    def multiple_histograms(self):
        """多个直方图对比"""
        fig, axes = plt.subplots(2, 2, figsize=(15, 12))
        
        # 正态分布
        axes[0, 0].hist(self.normal_data, bins=30, color='skyblue', 
                       edgecolor='black', alpha=0.7)
        axes[0, 0].set_title('正态分布')
        axes[0, 0].grid(True, alpha=0.3)
        
        # 均匀分布
        axes[0, 1].hist(self.uniform_data, bins=30, color='lightgreen', 
                       edgecolor='black', alpha=0.7)
        axes[0, 1].set_title('均匀分布')
        axes[0, 1].grid(True, alpha=0.3)
        
        # 指数分布
        axes[1, 0].hist(self.exponential_data, bins=30, color='lightcoral', 
                       edgecolor='black', alpha=0.7)
        axes[1, 0].set_title('指数分布')
        axes[1, 0].grid(True, alpha=0.3)
        
        # 对数正态分布
        lognormal_data = np.random.lognormal(0, 1, 1000)
        axes[1, 1].hist(lognormal_data, bins=30, color='plum', 
                       edgecolor='black', alpha=0.7)
        axes[1, 1].set_title('对数正态分布')
        axes[1, 1].grid(True, alpha=0.3)
        
        plt.tight_layout()
        plt.show()
    
    def overlapping_histograms(self):
        """重叠直方图"""
        # 生成两组数据
        group1 = np.random.normal(100, 15, 1000)
        group2 = np.random.normal(110, 12, 1000)
        
        plt.figure(figsize=(12, 8))
        
        # 绘制重叠直方图
        plt.hist(group1, bins=30, alpha=0.7, label='组别A', 
                color='skyblue', edgecolor='black')
        plt.hist(group2, bins=30, alpha=0.7, label='组别B', 
                color='lightcoral', edgecolor='black')
        
        plt.title('两组数据对比直方图', fontsize=16)
        plt.xlabel('数值')
        plt.ylabel('频次')
        plt.legend()
        plt.grid(True, alpha=0.3, axis='y')
        plt.show()
    
    def normalized_histogram(self):
        """归一化直方图"""
        fig, axes = plt.subplots(1, 3, figsize=(18, 6))
        
        # 频次直方图
        axes[0].hist(self.normal_data, bins=30, color='skyblue', 
                    edgecolor='black', alpha=0.7)
        axes[0].set_title('频次直方图')
        axes[0].set_ylabel('频次')
        axes[0].grid(True, alpha=0.3)
        
        # 密度直方图
        axes[1].hist(self.normal_data, bins=30, density=True, 
                    color='lightgreen', edgecolor='black', alpha=0.7)
        axes[1].set_title('密度直方图')
        axes[1].set_ylabel('密度')
        axes[1].grid(True, alpha=0.3)
        
        # 添加理论密度曲线
        x = np.linspace(self.normal_data.min(), self.normal_data.max(), 100)
        mean_val = np.mean(self.normal_data)
        std_val = np.std(self.normal_data)
        theoretical_density = (1/(std_val * np.sqrt(2 * np.pi))) * \
                            np.exp(-0.5 * ((x - mean_val) / std_val) ** 2)
        axes[1].plot(x, theoretical_density, 'r-', linewidth=2, 
                    label='理论密度')
        axes[1].legend()
        
        # 累积分布
        axes[2].hist(self.normal_data, bins=30, cumulative=True, 
                    density=True, color='plum', edgecolor='black', alpha=0.7)
        axes[2].set_title('累积分布直方图')
        axes[2].set_ylabel('累积概率')
        axes[2].grid(True, alpha=0.3)
        
        plt.tight_layout()
        plt.show()
    
    def histogram_statistics(self):
        """直方图统计分析"""
        plt.figure(figsize=(14, 10))
        
        # 创建子图
        gs = plt.GridSpec(3, 2, height_ratios=[2, 1, 1])
        
        # 主直方图
        ax1 = plt.subplot(gs[0, :])
        n, bins, patches = ax1.hist(self.normal_data, bins=50, 
                                   color='skyblue', edgecolor='black', alpha=0.7)
        
        # 计算统计量
        mean_val = np.mean(self.normal_data)
        median_val = np.median(self.normal_data)
        mode_bin = bins[np.argmax(n)]
        std_val = np.std(self.normal_data)
        
        # 添加统计线
        ax1.axvline(mean_val, color='red', linestyle='-', linewidth=2, 
                   label=f'均值: {mean_val:.2f}')
        ax1.axvline(median_val, color='green', linestyle='--', linewidth=2, 
                   label=f'中位数: {median_val:.2f}')
        ax1.axvline(mode_bin, color='orange', linestyle=':', linewidth=2, 
                   label=f'众数: {mode_bin:.2f}')
        
        ax1.set_title('数据分布统计分析', fontsize=16)
        ax1.set_xlabel('数值')
        ax1.set_ylabel('频次')
        ax1.legend()
        ax1.grid(True, alpha=0.3)
        
        # 箱线图
        ax2 = plt.subplot(gs[1, :])
        box_plot = ax2.boxplot(self.normal_data, vert=False, patch_artist=True)
        box_plot['boxes'][0].set_facecolor('lightblue')
        ax2.set_xlabel('数值')
        ax2.set_title('箱线图')
        ax2.grid(True, alpha=0.3)
        
        # Q-Q图
        from scipy import stats
        ax3 = plt.subplot(gs[2, 0])
        stats.probplot(self.normal_data, dist="norm", plot=ax3)
        ax3.set_title('Q-Q图(正态性检验)')
        ax3.grid(True, alpha=0.3)
        
        # 统计信息表
        ax4 = plt.subplot(gs[2, 1])
        ax4.axis('off')
        
        stats_text = f"""
        统计摘要:
        样本数量: {len(self.normal_data)}
        均值: {mean_val:.3f}
        中位数: {median_val:.3f}
        标准差: {std_val:.3f}
        最小值: {np.min(self.normal_data):.3f}
        最大值: {np.max(self.normal_data):.3f}
        偏度: {stats.skew(self.normal_data):.3f}
        峰度: {stats.kurtosis(self.normal_data):.3f}
        """
        
        ax4.text(0.1, 0.9, stats_text, transform=ax4.transAxes, 
                fontsize=11, verticalalignment='top',
                bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))
        
        plt.tight_layout()
        plt.show()

# 使用示例
hist_demo = HistogramDemo()
hist_demo.basic_histogram()
hist_demo.multiple_histograms()
hist_demo.overlapping_histograms()
hist_demo.normalized_histogram()
hist_demo.histogram_statistics()

2.5 饼图(Pie Chart)

2.5.1 基本饼图

class PieChartDemo:
    """饼图演示类"""
    
    def __init__(self):
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        plt.rcParams['axes.unicode_minus'] = False
        
        # 示例数据
        self.labels = ['产品A', '产品B', '产品C', '产品D', '其他']
        self.sizes = [30, 25, 20, 15, 10]
        self.colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#ff99cc']
    
    def basic_pie_chart(self):
        """基本饼图"""
        plt.figure(figsize=(10, 8))
        
        plt.pie(self.sizes, labels=self.labels, autopct='%1.1f%%', 
               colors=self.colors, startangle=90)
        
        plt.title('市场份额分布', fontsize=16)
        plt.axis('equal')  # 确保饼图是圆形的
        plt.show()
    
    def exploded_pie_chart(self):
        """分离式饼图"""
        plt.figure(figsize=(10, 8))
        
        # 设置分离距离
        explode = (0.1, 0, 0, 0, 0)  # 只分离第一个扇形
        
        wedges, texts, autotexts = plt.pie(self.sizes, labels=self.labels, 
                                          autopct='%1.1f%%', colors=self.colors,
                                          explode=explode, startangle=90,
                                          shadow=True)
        
        # 美化文本
        for autotext in autotexts:
            autotext.set_color('white')
            autotext.set_fontweight('bold')
            autotext.set_fontsize(12)
        
        plt.title('市场份额分布(突出显示)', fontsize=16)
        plt.axis('equal')
        plt.show()
    
    def donut_chart(self):
        """环形图"""
        fig, axes = plt.subplots(1, 2, figsize=(16, 8))
        
        # 简单环形图
        wedges, texts, autotexts = axes[0].pie(self.sizes, labels=self.labels,
                                              autopct='%1.1f%%', colors=self.colors,
                                              startangle=90, pctdistance=0.85)
        
        # 创建中心圆形来形成环形
        centre_circle = plt.Circle((0, 0), 0.70, fc='white')
        axes[0].add_artist(centre_circle)
        axes[0].set_title('简单环形图')
        
        # 多层环形图
        # 外层数据
        outer_sizes = [40, 30, 20, 10]
        outer_labels = ['类别1', '类别2', '类别3', '类别4']
        
        # 内层数据(细分)
        inner_sizes = [20, 20, 15, 15, 10, 10, 5, 5]
        inner_colors = ['#ff9999', '#ffb3b3', '#66b3ff', '#99ccff', 
                       '#99ff99', '#b3ffb3', '#ffcc99', '#ffd9b3']
        
        # 绘制外层
        wedges1, texts1 = axes[1].pie(outer_sizes, labels=outer_labels,
                                     radius=1, colors=self.colors[:4],
                                     startangle=90, labeldistance=1.1)
        
        # 绘制内层
        wedges2, texts2 = axes[1].pie(inner_sizes, radius=0.7,
                                     colors=inner_colors, startangle=90)
        
        axes[1].set_title('多层环形图')
        
        plt.tight_layout()
        plt.show()
    
    def advanced_pie_features(self):
        """高级饼图功能"""
        fig, axes = plt.subplots(2, 2, figsize=(16, 12))
        
        # 带连接线的饼图
        wedges, texts, autotexts = axes[0, 0].pie(self.sizes, labels=self.labels,
                                                 autopct='%1.1f%%', colors=self.colors,
                                                 startangle=90, pctdistance=0.85,
                                                 explode=(0.05, 0.05, 0.05, 0.05, 0.05))
        
        # 美化文本
        for text in texts:
            text.set_fontsize(10)
        for autotext in autotexts:
            autotext.set_color('white')
            autotext.set_fontweight('bold')
        
        axes[0, 0].set_title('带连接线的饼图')
        
        # 半圆饼图
        theta1, theta2 = 0, 180
        wedges, texts, autotexts = axes[0, 1].pie(self.sizes, labels=self.labels,
                                                 autopct='%1.1f%%', colors=self.colors,
                                                 startangle=theta1, counterclock=False,
                                                 wedgeprops=dict(width=0.5))
        axes[0, 1].set_title('半圆饼图')
        
        # 3D效果饼图(使用阴影模拟)
        explode = (0.1, 0, 0, 0, 0)
        wedges, texts, autotexts = axes[1, 0].pie(self.sizes, labels=self.labels,
                                                 autopct='%1.1f%%', colors=self.colors,
                                                 explode=explode, shadow=True,
                                                 startangle=90)
        axes[1, 0].set_title('带阴影效果的饼图')
        
        # 嵌套饼图
        # 外层:总体分类
        outer_labels = ['技术', '销售', '管理']
        outer_sizes = [50, 30, 20]
        outer_colors = ['#ff9999', '#66b3ff', '#99ff99']
        
        # 内层:详细分类
        inner_labels = ['开发', '测试', '直销', '渠道', '高管', '中层']
        inner_sizes = [30, 20, 20, 10, 10, 10]
        inner_colors = ['#ffb3b3', '#ff6666', '#99ccff', '#3399ff', 
                       '#b3ffb3', '#66ff66']
        
        # 绘制外层
        axes[1, 1].pie(outer_sizes, labels=outer_labels, radius=1,
                      colors=outer_colors, startangle=90, 
                      wedgeprops=dict(width=0.3, edgecolor='white'))
        
        # 绘制内层
        axes[1, 1].pie(inner_sizes, labels=inner_labels, radius=0.7,
                      colors=inner_colors, startangle=90,
                      wedgeprops=dict(width=0.4, edgecolor='white'),
                      textprops={'fontsize': 8})
        
        axes[1, 1].set_title('嵌套饼图')
        
        plt.tight_layout()
        plt.show()

# 使用示例
pie_demo = PieChartDemo()
pie_demo.basic_pie_chart()
pie_demo.exploded_pie_chart()
pie_demo.donut_chart()
pie_demo.advanced_pie_features()

2.6 箱线图(Box Plot)

2.6.1 基本箱线图

class BoxPlotDemo:
    """箱线图演示类"""
    
    def __init__(self):
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
        plt.rcParams['axes.unicode_minus'] = False
        
        # 生成示例数据
        np.random.seed(42)
        self.data1 = np.random.normal(100, 15, 200)
        self.data2 = np.random.normal(80, 20, 200)
        self.data3 = np.random.normal(90, 10, 200)
        self.data4 = np.random.normal(70, 25, 200)
    
    def basic_box_plot(self):
        """基本箱线图"""
        plt.figure(figsize=(10, 6))
        
        box_plot = plt.boxplot([self.data1], patch_artist=True)
        box_plot['boxes'][0].set_facecolor('lightblue')
        
        plt.title('基本箱线图', fontsize=16)
        plt.ylabel('数值')
        plt.xticks([1], ['数据集'])
        plt.grid(True, alpha=0.3, axis='y')
        plt.show()
    
    def multiple_box_plots(self):
        """多个箱线图对比"""
        data = [self.data1, self.data2, self.data3, self.data4]
        labels = ['产品A', '产品B', '产品C', '产品D']
        colors = ['lightblue', 'lightgreen', 'lightcoral', 'lightyellow']
        
        plt.figure(figsize=(12, 8))
        
        box_plot = plt.boxplot(data, labels=labels, patch_artist=True)
        
        # 设置颜色
        for patch, color in zip(box_plot['boxes'], colors):
            patch.set_facecolor(color)
            patch.set_alpha(0.7)
        
        plt.title('多产品性能对比箱线图', fontsize=16)
        plt.xlabel('产品类别')
        plt.ylabel('性能指标')
        plt.grid(True, alpha=0.3, axis='y')
        plt.show()
    
    def horizontal_box_plot(self):
        """水平箱线图"""
        data = [self.data1, self.data2, self.data3, self.data4]
        labels = ['产品A', '产品B', '产品C', '产品D']
        
        plt.figure(figsize=(12, 8))
        
        box_plot = plt.boxplot(data, labels=labels, vert=False, patch_artist=True)
        
        # 设置颜色
        colors = plt.cm.Set3(np.linspace(0, 1, len(data)))
        for patch, color in zip(box_plot['boxes'], colors):
            patch.set_facecolor(color)
        
        plt.title('水平箱线图', fontsize=16)
        plt.xlabel('性能指标')
        plt.ylabel('产品类别')
        plt.grid(True, alpha=0.3, axis='x')
        plt.show()
    
    def customized_box_plot(self):
        """自定义箱线图"""
        data = [self.data1, self.data2, self.data3, self.data4]
        labels = ['产品A', '产品B', '产品C', '产品D']
        
        plt.figure(figsize=(14, 10))
        
        # 自定义箱线图样式
        box_plot = plt.boxplot(data, labels=labels, patch_artist=True,
                              notch=True,  # 添加缺口
                              showmeans=True,  # 显示均值
                              meanline=True,  # 均值显示为线
                              showfliers=True,  # 显示异常值
                              flierprops=dict(marker='o', markerfacecolor='red', 
                                            markersize=8, alpha=0.5))
        
        # 自定义颜色和样式
        colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99']
        for patch, color in zip(box_plot['boxes'], colors):
            patch.set_facecolor(color)
            patch.set_alpha(0.8)
        
        # 自定义中位线
        for median in box_plot['medians']:
            median.set_color('black')
            median.set_linewidth(2)
        
        # 自定义均值线
        for mean in box_plot['means']:
            mean.set_color('red')
            mean.set_linewidth(2)
        
        plt.title('自定义样式箱线图', fontsize=16)
        plt.xlabel('产品类别')
        plt.ylabel('性能指标')
        plt.grid(True, alpha=0.3, axis='y')
        
        # 添加图例
        from matplotlib.lines import Line2D
        legend_elements = [Line2D([0], [0], color='black', lw=2, label='中位数'),
                          Line2D([0], [0], color='red', lw=2, label='均值'),
                          Line2D([0], [0], marker='o', color='w', 
                                markerfacecolor='red', markersize=8, 
                                alpha=0.5, label='异常值', linestyle='None')]
        plt.legend(handles=legend_elements, loc='upper right')
        
        plt.show()
    
    def violin_plot_comparison(self):
        """小提琴图对比"""
        data = [self.data1, self.data2, self.data3, self.data4]
        labels = ['产品A', '产品B', '产品C', '产品D']
        
        fig, axes = plt.subplots(1, 2, figsize=(16, 8))
        
        # 箱线图
        box_plot = axes[0].boxplot(data, labels=labels, patch_artist=True)
        colors = ['lightblue', 'lightgreen', 'lightcoral', 'lightyellow']
        for patch, color in zip(box_plot['boxes'], colors):
            patch.set_facecolor(color)
        
        axes[0].set_title('箱线图')
        axes[0].set_ylabel('数值')
        axes[0].grid(True, alpha=0.3)
        
        # 小提琴图
        violin_plot = axes[1].violinplot(data, positions=range(1, len(data)+1),
                                        showmeans=True, showmedians=True)
        
        # 设置小提琴图颜色
        for pc, color in zip(violin_plot['bodies'], colors):
            pc.set_facecolor(color)
            pc.set_alpha(0.7)
        
        axes[1].set_title('小提琴图')
        axes[1].set_ylabel('数值')
        axes[1].set_xticks(range(1, len(labels)+1))
        axes[1].set_xticklabels(labels)
        axes[1].grid(True, alpha=0.3)
        
        plt.tight_layout()
        plt.show()

# 使用示例
box_demo = BoxPlotDemo()
box_demo.basic_box_plot()
box_demo.multiple_box_plots()
box_demo.horizontal_box_plot()
box_demo.customized_box_plot()
box_demo.violin_plot_comparison()

2.7 本章总结

2.7.1 学习要点回顾

  1. 线图(Line Plot)

    • 适用于连续数据的趋势展示
    • 支持多种线型、标记和样式
    • 可以显示时间序列数据
    • 支持填充区域、误差条等高级功能
  2. 散点图(Scatter Plot)

    • 用于显示两个变量之间的关系
    • 支持颜色和大小映射第三、四维度
    • 适合相关性分析和分类展示
    • 可以制作气泡图和密度图
  3. 柱状图(Bar Chart)

    • 适用于分类数据的比较
    • 支持垂直和水平方向
    • 可以制作分组、堆叠和百分比图
    • 支持误差条和渐变效果
  4. 直方图(Histogram)

    • 用于显示数据分布
    • 支持密度和累积分布
    • 可以进行统计分析
    • 适合数据探索和质量检查
  5. 饼图(Pie Chart)

    • 显示部分与整体的关系
    • 支持分离、环形和多层结构
    • 适合比例数据展示
    • 注意避免过多分类
  6. 箱线图(Box Plot)

    • 显示数据的五数概括
    • 便于识别异常值
    • 适合多组数据比较
    • 可以与小提琴图结合使用

2.7.2 实践练习

# 练习1: 综合图表面板
def practice_comprehensive_dashboard():
    """创建一个包含多种图表类型的综合面板"""
    # TODO: 创建2x3的子图布局
    # 包含:时间序列线图、相关性散点图、销售柱状图、
    #      数据分布直方图、市场份额饼图、性能箱线图
    pass

# 练习2: 交互式图表
def practice_interactive_charts():
    """创建带有交互功能的图表"""
    # TODO: 添加鼠标悬停、点击事件
    # 实现图表之间的联动效果
    pass

# 练习3: 数据故事讲述
def practice_data_storytelling():
    """使用图表讲述数据故事"""
    # TODO: 设计一系列相关图表
    # 展示数据的发现和洞察
    pass

2.7.3 常见问题

Q: 如何选择合适的图表类型? A: 根据数据类型和分析目的选择:连续数据用线图,分类比较用柱状图,关系分析用散点图,分布分析用直方图。

Q: 如何处理重叠的数据点? A: 使用透明度(alpha)、抖动(jitter)、密度图或六边形分箱图。

Q: 饼图什么时候不适用? A: 分类过多(>7个)、需要精确比较、数据变化趋势分析时不适用。

Q: 如何美化图表? A: 合理使用颜色、调整字体大小、添加网格、设置合适的图例位置。

2.7.4 下章预告

下一章我们将学习图表样式与美化,包括颜色系统、字体设置、主题应用等,让你的图表更加专业和美观。