第2章 基础图表类型

第2章 基础图表类型

第2章 基础图表类型

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 下章预告

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

文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有
  • 上一篇:
  • 下一篇: