|

Pandas 数据可视化

Pandas 数据可视化详解

1. Pandas 内置绘图基础

1.1 基本绘图方法

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 设置matplotlib支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']  # 中文字体
plt.rcParams['axes.unicode_minus'] = False  # 正常显示负号

# 创建示例数据
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=100, freq='D')
df = pd.DataFrame({
    'date': dates,
    'sales': np.random.randn(100).cumsum() + 1000,
    'profit': np.random.randn(100).cumsum() + 500,
    'region': np.random.choice(['North', 'South', 'East', 'West'], 100),
    'category': np.random.choice(['A', 'B', 'C'], 100)
}, index=dates)

print("数据预览:")
print(df.head())

1.1.1 基本线图

# 简单线图
df['sales'].plot()
plt.title('销售趋势')
plt.xlabel('日期')
plt.ylabel('销售额')
plt.show()

# 多线图
df[['sales', 'profit']].plot(figsize=(10, 6))
plt.title('销售与利润趋势')
plt.ylabel('数值')
plt.legend(title='指标')
plt.show()

# 自定义样式
df.plot(x='date', y=['sales', 'profit'], 
        figsize=(12, 6),
        title='销售与利润时间序列',
        grid=True,
        style=['-', '--'],
        linewidth=2)
plt.tight_layout()
plt.show()

1.1.2 柱状图

# 单变量柱状图
df['sales'].hist(bins=20, figsize=(10, 6))
plt.title('销售分布直方图')
plt.xlabel('销售额')
plt.ylabel('频数')
plt.show()

# 分类柱状图
regional_sales = df.groupby('region')['sales'].sum()
regional_sales.plot(kind='bar', figsize=(8, 6), color='skyblue')
plt.title('各地区总销售额')
plt.xlabel('地区')
plt.ylabel('销售额')
plt.xticks(rotation=45)
plt.show()

# 水平柱状图
regional_sales.plot(kind='barh', figsize=(8, 6))
plt.title('各地区总销售额(水平)')
plt.xlabel('销售额')
plt.ylabel('地区')
plt.tight_layout()
plt.show()

1.2 高级内置图表

1.2.1 散点图

# 基本散点图
df.plot.scatter(x='sales', y='profit', figsize=(8, 6), alpha=0.6)
plt.title('销售与利润散点图')
plt.xlabel('销售额')
plt.ylabel('利润')
plt.show()

# 带颜色和大小的散点图
scatter = df.plot.scatter(x='sales', y='profit', 
                         c=df.index.dayofyear,  # 按天数着色
                         s=df['sales']/10,     # 按销售额控制点大小
                         cmap='viridis',
                         alpha=0.7,
                         figsize=(10, 8))
plt.colorbar(scatter.collections[0])
plt.title('销售-利润散点图(时间着色,大小表示销售额)')
plt.show()

1.2.2 饼图和面积图

# 饼图
category_sales = df.groupby('category')['sales'].sum()
category_sales.plot(kind='pie', figsize=(8, 8), autopct='%1.1f%%')
plt.title('各品类销售占比')
plt.ylabel('')  # 移除y轴标签
plt.show()

# 面积图
df[['sales', 'profit']].plot(kind='area', figsize=(12, 6), alpha=0.7)
plt.title('销售与利润面积图')
plt.ylabel('数值')
plt.legend(loc='upper left')
plt.show()

1.2.3 箱线图

# 单个箱线图
df.boxplot(column='sales', figsize=(8, 6))
plt.title('销售数据箱线图')
plt.ylabel('销售额')
plt.show()

# 分组箱线图
df.boxplot(column='sales', by='region', figsize=(10, 6))
plt.suptitle('各地区销售箱线图')  # 移除默认标题
plt.title('')
plt.ylabel('销售额')
plt.show()

# 多变量箱线图
df[['sales', 'profit']].boxplot(figsize=(10, 6))
plt.title('销售与利润箱线图')
plt.ylabel('数值')
plt.show()

2. 分组数据可视化

2.1 透视表可视化

# 创建透视表
pivot_sales = df.pivot_table(values='sales', 
                            index='region', 
                            columns=df.index.month, 
                            aggfunc='sum')

# 热力图
import seaborn as sns
plt.figure(figsize=(10, 6))
sns.heatmap(pivot_sales, annot=True, cmap='YlOrRd', fmt='.0f')
plt.title('月度地区销售热力图')
plt.xlabel('月份')
plt.ylabel('地区')
plt.show()

# 堆叠柱状图
pivot_sales.plot(kind='bar', stacked=True, figsize=(12, 6))
plt.title('各地区月度销售堆叠图')
plt.xlabel('地区')
plt.ylabel('销售额')
plt.legend(title='月份')
plt.xticks(rotation=45)
plt.show()

2.2 分组统计图

# 分组统计
group_stats = df.groupby('region').agg({
    'sales': ['mean', 'sum', 'count'],
    'profit': 'mean'
}).round(2)

# 选择特定统计绘制
means = group_stats['sales']['mean']
sums = group_stats['sales']['sum']

x = np.arange(len(means))
width = 0.35

fig, ax = plt.subplots(figsize=(10, 6))
bars1 = ax.bar(x - width/2, means, width, label='平均销售额', alpha=0.8)
bars2 = ax.bar(x + width/2, sums/1000, width, label='总销售额(千)', alpha=0.8)

ax.set_xlabel('地区')
ax.set_ylabel('销售额')
ax.set_title('各地区销售统计对比')
ax.set_xticks(x)
ax.set_xticklabels(means.index)
ax.legend()
ax.tick_params(axis='x', rotation=45)

plt.tight_layout()
plt.show()

3. 时间序列可视化

3.1 基础时间序列图

# 设置日期为索引
ts_df = df.set_index('date')

# 基础时间序列
ts_df[['sales', 'profit']].plot(figsize=(12, 6))
plt.title('时间序列分析')
plt.ylabel('数值')
plt.show()

# 移动平均
ts_df['sales_ma7'] = ts_df['sales'].rolling(window=7).mean()
ts_df['sales_ma30'] = ts_df['sales'].rolling(window=30).mean()

ts_df[['sales', 'sales_ma7', 'sales_ma30']].plot(figsize=(12, 6))
plt.title('销售趋势与移动平均')
plt.ylabel('销售额')
plt.legend(['实际值', '7日均值', '30日均值'])
plt.show()

3.2 季节性和趋势分解

from statsmodels.tsa.seasonal import seasonal_decompose
import warnings
warnings.filterwarnings('ignore')

# 季节性分解(月频数据)
monthly_sales = ts_df['sales'].resample('M').sum()

# 分解(假设月度季节性)
decomposition = seasonal_decompose(monthly_sales, model='additive', period=12)
fig = decomposition.plot()
fig.set_size_inches(12, 8)
plt.suptitle('时间序列分解(趋势、季节性、残差)')
plt.show()

# 绘制分解结果
fig, axes = plt.subplots(4, 1, figsize=(12, 10))
decomposition.observed.plot(ax=axes[0], title='原始数据')
decomposition.trend.plot(ax=axes[1], title='趋势')
decomposition.seasonal.plot(ax=axes[2], title='季节性')
decomposition.resid.plot(ax=axes[3], title='残差')
plt.tight_layout()
plt.show()

3.3 相关性时间序列

# 滚动相关性
rolling_corr = ts_df['sales'].rolling(window=30).corr(ts_df['profit'])

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))

# 原始序列
ts_df[['sales', 'profit']].plot(ax=ax1, secondary_y='profit')
ax1.set_title('销售与利润时间序列')
ax1.set_ylabel('销售')

# 滚动相关性
rolling_corr.plot(ax=ax2, color='red', linewidth=2)
ax2.axhline(y=0, color='black', linestyle='--', alpha=0.5)
ax2.set_title('销售与利润滚动相关性(30天窗口)')
ax2.set_ylabel('相关系数')

plt.tight_layout()
plt.show()

4. 统计图表

4.1 分布分析

# 密度图和直方图
fig, axes = plt.subplots(1, 2, figsize=(15, 6))

# 密度图
df['sales'].plot(kind='density', ax=axes[0], color='blue', bw_method=0.3)
axes[0].set_title('销售密度分布')
axes[0].set_xlabel('销售额')

# 直方图
df['sales'].plot(kind='hist', bins=30, ax=axes[1], color='green', alpha=0.7)
axes[1].set_title('销售直方图')
axes[1].set_xlabel('销售额')

plt.tight_layout()
plt.show()

# Q-Q图(正态性检验)
from scipy import stats
stats.probplot(df['sales'], dist="norm", plot=plt)
plt.title('销售数据Q-Q图(正态性检验)')
plt.show()

4.2 相关性可视化

# 相关系数矩阵
corr_matrix = df[['sales', 'profit']].corr()

plt.figure(figsize=(8, 6))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0,
            square=True, fmt='.3f')
plt.title('相关系数热力图')
plt.tight_layout()
plt.show()

# 配对图
sns.pairplot(df[['sales', 'profit']], diag_kind='kde', kind='reg')
plt.suptitle('销售与利润配对图', y=1.02)
plt.show()

5. 高级可视化技巧

5.1 自定义样式和主题

# 设置全局样式
plt.style.use('seaborn-v0_8-darkgrid')  # 或其他样式
# plt.style.use('ggplot')
# plt.style.use('seaborn-v0_8')

# 自定义颜色和样式
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
fig, axes = plt.subplots(2, 2, figsize=(15, 10))

# 1. 自定义线图
axes[0,0].plot(df.index, df['sales'], color=colors[0], linewidth=2, marker='o', markersize=4)
axes[0,0].set_title('自定义线图', fontsize=14, fontweight='bold')
axes[0,0].grid(True, alpha=0.3)

# 2. 自定义柱状图
regional_sales = df.groupby('region')['sales'].mean()
bars = axes[0,1].bar(regional_sales.index, regional_sales.values, 
                    color=colors[:len(regional_sales)], alpha=0.8, edgecolor='black')
axes[0,1].set_title('自定义柱状图')
axes[0,1].tick_params(axis='x', rotation=45)

# 添加数值标签
for bar in bars:
    height = bar.get_height()
    axes[0,1].text(bar.get_x() + bar.get_width()/2., height,
                  f'{height:.0f}', ha='center', va='bottom')

# 3. 散点图
scatter = axes[1,0].scatter(df['sales'], df['profit'], 
                           c=df.index.dayofyear, cmap='viridis', s=50, alpha=0.6)
plt.colorbar(scatter, ax=axes[1,0])
axes[1,0].set_title('自定义散点图')

# 4. 箱线图
df.boxplot(column='sales', by='region', ax=axes[1,1], patch_artist=True)
axes[1,1].set_title('自定义箱线图')

plt.tight_layout()
plt.show()

5.2 子图布局

# 复杂子图布局
fig = plt.figure(figsize=(16, 12))

# 1x2布局
ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=2)
ax2 = plt.subplot2grid((3, 3), (0, 2))
ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=3)
ax4 = plt.subplot2grid((3, 3), (2, 0), colspan=2)
ax5 = plt.subplot2grid((3, 3), (2, 2))

# 主时间序列
df['sales'].plot(ax=ax1, title='销售时间序列', linewidth=2)
ax1.set_ylabel('销售额')

# 地区对比
regional_sales = df.groupby('region')['sales'].sum()
regional_sales.plot(kind='bar', ax=ax2, title='地区销售汇总', color=colors)
ax2.tick_params(axis='x', rotation=45)
ax2.set_ylabel('总销售额')

# 移动平均
df['sales_ma'] = df['sales'].rolling(7).mean()
df[['sales', 'sales_ma']].plot(ax=ax3, title='销售与7日移动平均', secondary_y='sales_ma')
ax3.set_ylabel('销售额')

# 分布
df['sales'].hist(bins=20, ax=ax4, alpha=0.7, color='skyblue')
ax4.set_title('销售分布')
ax4.set_xlabel('销售额')
ax4.set_ylabel('频数')

# 饼图
category_sales = df.groupby('category')['sales'].sum()
ax5.pie(category_sales.values, labels=category_sales.index, autopct='%1.1f%%', 
        colors=colors[:len(category_sales)])
ax5.set_title('品类销售占比')

plt.tight_layout()
plt.show()

6. 与Seaborn集成

6.1 高级统计可视化

import seaborn as sns

# 设置Seaborn样式
sns.set_style("whitegrid")
sns.set_palette("husl")

# 分类散点图
plt.figure(figsize=(12, 8))
sns.scatterplot(data=df, x='sales', y='profit', hue='region', size='sales', 
                sizes=(20, 200), alpha=0.7)
plt.title('销售与利润散点图(按地区着色)')
plt.show()

# 分类箱线图
plt.figure(figsize=(10, 6))
sns.boxplot(data=df, x='region', y='sales')
plt.title('各地区销售箱线图')
plt.xticks(rotation=45)
plt.show()

# 小提琴图
plt.figure(figsize=(10, 6))
sns.violinplot(data=df, x='region', y='sales')
plt.title('各地区销售小提琴图')
plt.xticks(rotation=45)
plt.show()

6.2 相关性分析可视化

# 配对图
sns.pairplot(df[['sales', 'profit', 'region']], hue='region', diag_kind='hist')
plt.suptitle('多变量配对图', y=1.02)
plt.show()

# 回归图
plt.figure(figsize=(10, 6))
sns.lmplot(data=df, x='sales', y='profit', hue='region', 
           scatter_kws={'alpha':0.6}, line_kws={'linewidth':2})
plt.title('销售与利润回归分析')
plt.show()

# 联合分布
plt.figure(figsize=(10, 6))
sns.jointplot(data=df, x='sales', y='profit', kind='hex', bins=30)
plt.suptitle('销售与利润六边形联合分布')
plt.show()

7. 交互式可视化

7.1 Plotly集成

import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# 交互式线图
fig = px.line(df, x='date', y=['sales', 'profit'], 
              title='交互式销售与利润趋势',
              labels={'value': '数值', 'date': '日期'})
fig.update_layout(hovermode='x unified')
fig.show()

# 交互式散点图
fig = px.scatter(df, x='sales', y='profit', 
                color='region', size='sales',
                hover_data=['category'],
                title='交互式销售-利润散点图')
fig.show()

# 3D散点图
fig = px.scatter_3d(df, x='sales', y='profit', z=df.index.dayofyear,
                   color='region', size='sales',
                   title='3D销售分析')
fig.show()

7.2 Bokeh可视化

from bokeh.plotting import figure, show
from bokeh.models import HoverTool, ColumnDataSource
from bokeh.io import output_notebook
# output_notebook()  # Jupyter环境

# 准备数据
source = ColumnDataSource(df)

# 创建图形
p = figure(title="销售数据交互图", height=400, width=800,
           tools="pan,wheel_zoom,box_zoom,reset,hover,save")

# 添加散点
p.scatter('sales', 'profit', source=source, size=8,
          color='region', fill_alpha=0.6,
          legend_group='region')

# 悬停工具
hover = p.select_one(HoverTool)
hover.tooltips = [
    ("日期", "@date{%Y-%m-%d}"),
    ("销售额", "@sales{0.0f}"),
    ("利润", "@profit{0.0f}"),
    ("地区", "@region")
]
hover.formatters = {"date": "datetime"}

# 添加趋势线
sales_trend = df['sales'].rolling(7).mean()
p.line(df.index, sales_trend, color='red', line_width=2, legend_label='7日均值')

show(p)

8. 自定义可视化函数

8.1 自动化报告生成

class PandasVisualizer:
    """Pandas数据可视化工具类"""

    def __init__(self, df):
        self.df = df
        self.fig_size = (12, 8)

    def plot_time_series(self, columns=None, rolling_windows=[7, 30]):
        """时间序列图"""
        if columns is None:
            columns = self.df.select_dtypes(include=[np.number]).columns

        ts_df = self.df.set_index('date') if 'date' in self.df else self.df

        fig, axes = plt.subplots(len(columns), 1, figsize=(self.fig_size[0], self.fig_size[1]*len(columns)))
        if len(columns) == 1:
            axes = [axes]

        for i, col in enumerate(columns):
            ax = axes[i]
            ts_df[col].plot(ax=ax, linewidth=2, label=col)

            # 移动平均
            for window in rolling_windows:
                ma = ts_df[col].rolling(window).mean()
                ma.plot(ax=ax, alpha=0.7, label=f'{window}日均值')

            ax.set_title(f'{col}时间序列分析')
            ax.legend()
            ax.grid(True, alpha=0.3)

        plt.tight_layout()
        plt.show()

    def plot_category_analysis(self, cat_col, value_col, plot_type='bar'):
        """分类分析图"""
        group_data = self.df.groupby(cat_col)[value_col].agg(['mean', 'sum', 'count'])

        fig, axes = plt.subplots(1, 2, figsize=self.fig_size)

        # 平均值柱状图
        group_data['mean'].plot(kind=plot_type, ax=axes[0], color='skyblue')
        axes[0].set_title(f'{cat_col}平均{value_col}')
        axes[0].tick_params(axis='x', rotation=45)

        # 总数柱状图
        group_data['sum'].plot(kind=plot_type, ax=axes[1], color='lightcoral')
        axes[1].set_title(f'{cat_col}总{value_col}')
        axes[1].tick_params(axis='x', rotation=45)

        plt.tight_layout()
        plt.show()

    def plot_correlation_matrix(self, numeric_cols=None):
        """相关性矩阵热力图"""
        if numeric_cols is None:
            numeric_cols = self.df.select_dtypes(include=[np.number]).columns

        corr_matrix = self.df[numeric_cols].corr()

        plt.figure(figsize=self.fig_size)
        sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0,
                   square=True, fmt='.3f')
        plt.title('相关系数热力图')
        plt.tight_layout()
        plt.show()

    def generate_summary_report(self):
        """生成完整分析报告"""
        print("生成可视化报告...")

        # 1. 分布分析
        self.df.hist(figsize=(15, 10), bins=20, alpha=0.7)
        plt.suptitle('数据分布直方图')
        plt.tight_layout()
        plt.show()

        # 2. 箱线图
        self.df.boxplot(figsize=(12, 8))
        plt.title('各变量箱线图')
        plt.xticks(rotation=45)
        plt.show()

        # 3. 相关性
        self.plot_correlation_matrix()

        # 4. 时间序列(如果有日期)
        if 'date' in self.df.columns or self.df.index.dtype == 'datetime64[ns]':
            self.plot_time_series()

        print("报告生成完成!")

# 使用示例
visualizer = PandasVisualizer(df)
visualizer.generate_summary_report()
visualizer.plot_category_analysis('region', 'sales')

9. 输出和保存

9.1 保存静态图片

# 保存为不同格式
df['sales'].plot()
plt.title('销售趋势')
plt.savefig('sales_trend.png', dpi=300, bbox_inches='tight')
plt.savefig('sales_trend.pdf', bbox_inches='tight')
plt.savefig('sales_trend.svg', bbox_inches='tight')
plt.show()

# 批量保存
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
# ... 绘图代码 ...
plt.savefig('report_multi.png', dpi=300, bbox_inches='tight')
plt.close()  # 关闭图形防止内存泄漏

9.2 HTML报告

from pathlib import Path

def create_html_report(df, output_dir='reports'):
    """创建HTML可视化报告"""
    Path(output_dir).mkdir(exist_ok=True)

    # 创建HTML模板
    html_content = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>Pandas数据可视化报告</title>
        <style>
            body { font-family: Arial, sans-serif; margin: 40px; }
            .chart-container { margin: 20px 0; text-align: center; }
            h1, h2 { color: #2c3e50; }
            img { max-width: 100%; height: auto; }
        </style>
    </head>
    <body>
    """

    # 基本统计
    html_content += f"<h1>数据概览 (形状: {df.shape})</h1>"
    html_content += f"<p>时间范围: {df['date'].min()} 到 {df['date'].max()}</p>"

    # 保存图表
    fig, ax = plt.subplots(figsize=(12, 6))
    df['sales'].plot(ax=ax)
    plt.title('销售趋势')
    plt.savefig(f'{output_dir}/sales_trend.png', dpi=300, bbox_inches='tight')
    plt.close()

    html_content += '<div class="chart-container"><h2>销售趋势</h2><img src="sales_trend.png"></div>'

    # 地区分析
    regional_sales = df.groupby('region')['sales'].sum()
    fig, ax = plt.subplots(figsize=(10, 6))
    regional_sales.plot(kind='bar', ax=ax)
    plt.title('各地区总销售额')
    plt.savefig(f'{output_dir}/regional_sales.png', dpi=300, bbox_inches='tight')
    plt.close()

    html_content += '<div class="chart-container"><h2>地区销售分析</h2><img src="regional_sales.png"></div>'

    html_content += """
    </body>
    </html>
    """

    with open(f'{output_dir}/report.html', 'w', encoding='utf-8') as f:
        f.write(html_content)

    print(f"HTML报告已保存到 {output_dir}/report.html")

# 生成报告
create_html_report(df)

10. 性能优化和最佳实践

10.1 大数据可视化优化

def optimize_large_plot(df, sample_frac=0.1, max_points=10000):
    """优化大数据可视化"""
    if len(df) > max_points:
        # 采样显示
        df_sample = df.sample(frac=sample_frac, random_state=42)
        print(f"数据量过大,已采样 {len(df_sample)} 条记录进行可视化")
        return df_sample
    return df

# 下采样时间序列
def downsample_timeseries(df, freq='W', columns=None):
    """时间序列下采样"""
    if columns is None:
        columns = df.select_dtypes(include=[np.number]).columns

    ts_df = df.set_index('date') if 'date' in df else df
    return ts_df[columns].resample(freq).agg({
        col: ['mean', 'sum'] for col in columns
    })

# 示例
large_sample = optimize_large_plot(df, sample_frac=0.5)
weekly_sales = downsample_timeseries(df, freq='W')
weekly_sales.plot(figsize=(12, 6))
plt.title('周度销售汇总')
plt.show()

10.2 最佳实践

# 1. 设置合理的图形尺寸
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['font.size'] = 12

# 2. 统一颜色方案
colors = plt.cm.Set1(np.linspace(0, 1, 8))  # 固定调色板

# 3. 添加统计信息
def add_stats_annotation(ax, data, x_col, y_col):
    """添加统计注释"""
    corr = data[x_col].corr(data[y_col])
    r2 = corr**2
    ax.text(0.05, 0.95, f'r = {corr:.3f}\nR² = {r2:.3f}', 
            transform=ax.transAxes, fontsize=10,
            bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))
    return ax

# 4. 动态图表调整
def adaptive_plot(df, max_cols=4):
    """自适应子图布局"""
    n_cols = min(max_cols, len(df.columns))
    n_rows = (len(df.columns) + n_cols - 1) // n_cols

    fig, axes = plt.subplots(n_rows, n_cols, figsize=(5*n_cols, 4*n_rows))
    axes = np.array(axes).flatten() if n_rows > 1 or n_cols > 1 else [axes]

    for i, col in enumerate(df.columns):
        if i < len(axes):
            df[col].plot(ax=axes[i])
            axes[i].set_title(col)

    # 隐藏多余子图
    for i in range(len(df.columns), len(axes)):
        axes[i].set_visible(False)

    plt.tight_layout()
    plt.show()

# 使用最佳实践
fig, ax = plt.subplots(figsize=(10, 6))
sns.scatterplot(data=df, x='sales', y='profit', ax=ax)
add_stats_annotation(ax, df, 'sales', 'profit')
plt.title('优化后的散点图')
plt.show()

Pandas内置的绘图功能结合Matplotlib和Seaborn提供了强大的数据可视化能力。通过掌握基础图表类型、分组可视化、时间序列分析和交互式图表,可以创建专业的数据可视化报告。关键是根据数据特点选择合适的图表类型,并通过样式优化和统计注释增强可读性和洞察力。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注