第11章 分析报告与项目组织
11.1 使用 describe 进行整体统计概览
在数据分析过程中,我们经常需要快速了解数据集的整体情况。pandas 的 describe() 方法就是我们的得力助手,它能一键生成数值型数据的统计摘要。
describe() 方法会自动计算数值列的各种统计指标,包括计数、均值、标准差、最小值、四分位数和最大值。这对于初步探索数据分布特征非常有用。
describe() 方法实例表格
| 功能名称 | 实例调用方法 | 具体功能、注意事项、必需参数/可选参数 |
|---|---|---|
| 基础统计概览 | df.describe() | 默认只对数值列进行统计,返回计数、均值、标准差、最小值、25%/50%/75%分位数、最大值 |
| 包含所有数据类型 | df.describe(include='all') | include参数可设置为'all'包含所有列,或指定特定数据类型如'object' |
| 自定义百分位数 | df.describe(percentiles=[0.1, 0.9]) | percentiles参数可自定义要显示的分位数,默认是[0.25, 0.5, 0.75] |
下面是一个完整的示例代码,展示如何使用 describe() 方法:
# 导入必要的库
import pandas as pd
import numpy as np
# 创建示例数据集
np.random.seed(42) # 设置随机种子确保结果可重现
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve'],
'age': [25, 30, 35, 28, 32],
'salary': [50000, 60000, 75000, 55000, 68000],
'department': ['IT', 'HR', 'IT', 'Finance', 'IT']
}
df = pd.DataFrame(data)
# 添加一些缺失值用于演示
df.loc[2, 'salary'] = np.nan
try:
# 基础统计概览 - 只显示数值列
print("=== 基础统计概览 ===")
basic_stats = df.describe()
print(basic_stats)
print("\n")
# 包含所有数据类型的统计概览
print("=== 包含所有数据类型的统计概览 ===")
all_stats = df.describe(include='all')
print(all_stats)
print("\n")
# 自定义百分位数
print("=== 自定义百分位数 (10% 和 90%) ===")
custom_percentiles = df.describe(percentiles=[0.1, 0.9])
print(custom_percentiles)
print("\n")
# 只针对特定列进行统计
print("=== 只针对年龄列的详细统计 ===")
age_stats = df['age'].describe()
print(age_stats)
except Exception as e:
print(f"处理数据时发生错误: {e}")注意事项:
describe()默认只处理数值型数据(int64、float64等)- 对于非数值列,使用
include='object'或include='all'参数 - 当数据中存在缺失值时,
describe()会自动忽略它们进行计算 - 返回的结果本身也是一个 DataFrame,可以进一步处理或保存
这个小节介绍了如何使用 describe() 方法快速获得数据集的统计概览,帮助我们快速了解数据的基本特征和分布情况,为后续的深入分析奠定基础。
11.2 构建自定义分析函数与管道
在实际的数据分析工作中,我们经常会遇到重复性的数据处理任务。这时候,构建自定义分析函数和使用管道操作就显得尤为重要,它们能让我们的代码更加简洁、可读和可维护。
pandas 提供了强大的管道(pipe)功能,允许我们将多个函数按顺序应用到 DataFrame 上,形成清晰的数据处理流程。
自定义函数与管道常用方法表格
| 功能名称 | 实例调用方法 | 具体功能、注意事项、必需参数/可选参数 |
|---|---|---|
| 自定义数据清洗函数 | def clean_data(df): return df | 函数必须接受 DataFrame 并返回 DataFrame |
| 应用单个自定义函数 | df.pipe(clean_data) | pipe() 方法将 DataFrame 作为第一个参数传递给函数 |
| 链式管道操作 | df.pipe(func1).pipe(func2).pipe(func3) | 可以连续应用多个函数,形成数据处理流水线 |
| 带参数的管道函数 | df.pipe(func_with_params, param1=value1) | 可以向管道函数传递额外参数 |
下面是一个完整的示例,展示如何构建自定义分析函数并使用管道:
# 导入必要的库
import pandas as pd
import numpy as np
# 创建示例数据集
np.random.seed(42)
data = {
'product_id': ['P001', 'P002', 'P003', 'P004', 'P005'],
'sales': [100, 150, np.nan, 200, 180],
'price': [10.5, 15.0, 12.8, np.nan, 14.2],
'category': ['A', 'B', 'A', 'C', 'B']
}
df = pd.DataFrame(data)
# 定义自定义数据清洗函数
def handle_missing_values(df):
"""
处理缺失值:数值列用均值填充,分类列用众数填充
"""
df_cleaned = df.copy()
# 数值列用均值填充
numeric_columns = df_cleaned.select_dtypes(include=[np.number]).columns
df_cleaned[numeric_columns] = df_cleaned[numeric_columns].fillna(df_cleaned[numeric_columns].mean())
# 分类列用众数填充
categorical_columns = df_cleaned.select_dtypes(exclude=[np.number]).columns
for col in categorical_columns:
df_cleaned[col] = df_cleaned[col].fillna(df_cleaned[col].mode()[0] if not df_cleaned[col].mode().empty else 'Unknown')
return df_cleaned
def add_calculated_columns(df):
"""
添加计算列:总收入 = 销售数量 * 价格
"""
df_with_revenue = df.copy()
df_with_revenue['revenue'] = df_with_revenue['sales'] * df_with_revenue['price']
return df_with_revenue
def filter_outliers(df, column, threshold=2):
"""
过滤异常值:移除超出均值±threshold*标准差的记录
"""
df_filtered = df.copy()
mean_val = df_filtered[column].mean()
std_val = df_filtered[column].std()
lower_bound = mean_val - threshold * std_val
upper_bound = mean_val + threshold * std_val
df_filtered = df_filtered[(df_filtered[column] >= lower_bound) & (df_filtered[column] <= upper_bound)]
return df_filtered
try:
# 使用管道操作组合多个自定义函数
print("原始数据:")
print(df)
print("\n")
# 应用管道:先处理缺失值,再添加计算列,最后过滤异常值
processed_df = (df
.pipe(handle_missing_values)
.pipe(add_calculated_columns)
.pipe(filter_outliers, column='revenue', threshold=1.5))
print("处理后的数据:")
print(processed_df)
print("\n")
# 单独测试每个函数
print("单独测试缺失值处理:")
missing_handled = df.pipe(handle_missing_values)
print(missing_handled)
except Exception as e:
print(f"处理数据时发生错误: {e}")注意事项:
- 管道中的每个函数都必须接受 DataFrame 作为第一个参数并返回 DataFrame
- 函数应该尽量保持无副作用,即不修改原始数据而是返回新数据
- 管道操作是从左到右依次执行的,顺序很重要
- 可以在管道中混合使用 pandas 内置方法和自定义函数
这节介绍了如何构建自定义分析函数并使用管道操作来创建清晰、可维护的数据处理流程,大大提高了代码的可读性和复用性。
11.3 生成分析报告:Markdown + 图表整合
在完成数据分析后,我们需要将结果整理成易于理解的报告。将 Markdown 文本与图表整合是生成专业分析报告的有效方式。Jupyter Notebook 本身就支持这种混合格式,但我们也可以通过编程方式动态生成包含图表的 Markdown 报告。
这种方法特别适合自动化报告生成,比如每日/每周的数据监控报告。
报告生成常用方法表格
| 功能名称 | 实例调用方法 | 具体功能、注意事项、必需参数/可选参数 |
|---|---|---|
| 保存图表为图片 | plt.savefig('chart.png') | 将 matplotlib 图表保存为图片文件,支持多种格式 |
| 生成 Markdown 文本 | f"# 标题\n\n " | 使用 f-string 或模板引擎生成包含图片引用的 Markdown |
| 自动化报告生成 | write_report(data, charts) | 封装整个报告生成逻辑到函数中 |
| 图片编码嵌入 | base64.b64encode(img_bytes) | 将图片直接编码嵌入到 HTML 中,避免外部文件依赖 |
下面是一个完整的示例,展示如何生成包含图表的 Markdown 分析报告:
# 导入必要的库
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import os
from datetime import datetime
# 创建示例数据集
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=30, freq='D')
sales_data = {
'date': dates,
'sales': np.random.normal(1000, 200, 30).cumsum() + 10000,
'customers': np.random.poisson(50, 30).cumsum() + 500
}
df = pd.DataFrame(sales_data)
def create_sales_trend_chart(df, filename='sales_trend.png'):
"""
创建销售趋势图表并保存为图片
参数:
df: 包含日期和销售数据的DataFrame
filename: 保存的图片文件名
返回:
图片文件路径
"""
plt.figure(figsize=(10, 6))
plt.plot(df['date'], df['sales'], marker='o', linewidth=2, markersize=4)
plt.title('Sales Trend Over Time', fontsize=16, fontweight='bold')
plt.xlabel('Date')
plt.ylabel('Sales Amount ($)')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig(filename, dpi=300, bbox_inches='tight')
plt.close() # 关闭图形避免内存泄漏
return filename
def create_correlation_chart(df, filename='correlation.png'):
"""
创建相关性散点图并保存为图片
参数:
df: 包含销售和客户数据的DataFrame
filename: 保存的图片文件名
返回:
图片文件路径
"""
plt.figure(figsize=(8, 6))
plt.scatter(df['customers'], df['sales'], alpha=0.7, s=50)
plt.title('Sales vs Customers Correlation', fontsize=16, fontweight='bold')
plt.xlabel('Number of Customers')
plt.ylabel('Sales Amount ($)')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(filename, dpi=300, bbox_inches='tight')
plt.close()
return filename
def generate_markdown_report(df, chart_files, report_filename='analysis_report.md'):
"""
生成包含统计数据和图表的Markdown报告
参数:
df: 分析用的DataFrame
chart_files: 图表文件路径列表
report_filename: 报告文件名
"""
# 获取基本统计信息
stats_summary = df.describe()
# 构建Markdown内容
markdown_content = f"""# 数据分析报告
生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
## 数据概览
本报告基于 {len(df)} 条记录进行分析。
### 基本统计信息
| 指标 | 销售额 | 客户数 |
|------|--------|--------|
| 平均值 | ${stats_summary['sales']['mean']:.2f} | {stats_summary['customers']['mean']:.0f} |
| 最小值 | ${stats_summary['sales']['min']:.2f} | {stats_summary['customers']['min']:.0f} |
| 最大值 | ${stats_summary['sales']['max']:.2f} | {stats_summary['customers']['max']:.0f} |
| 标准差 | ${stats_summary['sales']['std']:.2f} | {stats_summary['customers']['std']:.0f} |
## 趋势分析

## 相关性分析

## 结论
- 销售额呈现稳定增长趋势
- 客户数量与销售额呈现正相关关系
- 建议继续关注客户获取策略以维持销售增长
"""
# 写入Markdown文件
with open(report_filename, 'w', encoding='utf-8') as f:
f.write(markdown_content)
return report_filename
try:
# 创建图表
sales_chart = create_sales_trend_chart(df)
correlation_chart = create_correlation_chart(df)
# 生成报告
report_file = generate_markdown_report(df, [sales_chart, correlation_chart])
print(f"分析报告已生成: {report_file}")
print(f"图表文件: {sales_chart}, {correlation_chart}")
# 验证文件是否创建成功
if os.path.exists(report_file):
print("报告生成成功!")
else:
print("报告生成失败!")
except Exception as e:
print(f"生成报告时发生错误: {e}")注意事项:
- 图表文件路径要相对于 Markdown 文件的位置正确
- 使用
plt.close()避免在循环中生成大量图表时内存泄漏 - Markdown 文件编码建议使用 UTF-8 以支持中文
- 图片分辨率(dpi)设置要足够高以保证打印质量
- 在自动化环境中要注意文件权限和存储空间
这节介绍了如何将数据分析结果和可视化图表整合到 Markdown 报告中,实现了分析结果的专业化呈现和自动化生成。
11.4 使用 Jupyter Notebook 组织分析项目
Jupyter Notebook 是数据分析师的瑞士军刀,它不仅能让我们交互式地探索数据,还能很好地组织整个分析项目。合理使用 Notebook 的结构和功能,可以让我们的分析过程更加清晰、可重复和可分享。
一个好的 Notebook 项目应该有清晰的结构、适当的注释和良好的代码组织。
Jupyter Notebook 项目组织最佳实践表格
| 功能名称 | 实例调用方法 | 具体功能、注意事项、必需参数/可选参数 |
|---|---|---|
| 项目结构组织 | 创建 notebooks/, data/, output/ 目录 | 标准项目目录结构,便于团队协作 |
| 魔法命令使用 | %matplotlib inline, %load_ext autoreload | 提高开发效率的内置命令 |
| 参数化 Notebook | papermill execute notebook.ipynb -p param value | 使用 papermill 实现 Notebook 参数化执行 |
| 版本控制友好 | 避免在 Notebook 中存储大量输出 | 减少 Git 冲突,提高协作效率 |
下面是一个完整的示例,展示如何组织一个 Jupyter Notebook 分析项目:
# 这个示例展示的是项目结构和组织方式,而不是可直接运行的代码
# 实际的 Notebook 项目应该按照以下结构组织
"""
项目根目录/
├── README.md # 项目说明文档
├── requirements.txt # Python 依赖包列表
├── notebooks/ # Jupyter Notebook 文件目录
│ ├── 01_data_exploration.ipynb # 数据探索
│ ├── 02_data_cleaning.ipynb # 数据清洗
│ ├── 03_analysis.ipynb # 主要分析
│ └── 04_reporting.ipynb # 报告生成
├── data/ # 原始数据目录
│ ├── raw/ # 原始数据文件
│ └── processed/ # 处理后的数据文件
├── output/ # 输出结果目录
│ ├── figures/ # 生成的图表
│ └── reports/ # 生成的报告
└── src/ # 自定义Python模块
├── utils.py # 工具函数
└── analysis.py # 分析函数
"""
# 在 Notebook 中常用的魔法命令示例
# 注意:这些命令在普通 Python 脚本中无法使用,只能在 Jupyter Notebook 中使用
"""
# 在 Jupyter Notebook 的单元格中使用:
# 1. 内联显示图表
%matplotlib inline
# 2. 自动重载导入的模块(开发时很有用)
%load_ext autoreload
%autoreload 2
# 3. 测量代码执行时间
%%time
# 你的代码
# 4. 显示变量信息
%whos
# 5. 清理输出(减少文件大小)
from IPython.display import clear_output
clear_output()
"""
# 自定义工具函数示例(保存在 src/utils.py 中)
def load_data(filepath):
"""
加载数据的通用函数
"""
import pandas as pd
return pd.read_csv(filepath)
def save_results(df, filename):
"""
保存分析结果的通用函数
"""
import pandas as pd
df.to_csv(f"output/{filename}", index=False)
# 在 Notebook 中导入和使用自定义模块
"""
# 在 Notebook 中:
import sys
sys.path.append('src')
from utils import load_data, save_results
from analysis import perform_analysis
# 加载数据
df = load_data('data/raw/sales_data.csv')
# 执行分析
results = perform_analysis(df)
# 保存结果
save_results(results, 'analysis_results.csv')
"""
# 使用 papermill 参数化执行 Notebook 的命令行示例
"""
# 在命令行中执行:
# papermill notebooks/analysis_template.ipynb notebooks/analysis_output.ipynb -p date_range "2023-01-01:2023-12-31" -p region "North"
"""注意事项:
- Notebook 文件应该按分析流程顺序编号(01_, 02_, 03_...)
- 避免在 Notebook 中硬编码文件路径,使用相对路径或配置文件
- 定期清理 Notebook 的输出以减小文件大小,便于版本控制
- 敏感信息(如 API 密钥)不要直接写在 Notebook 中,使用环境变量或配置文件
- 对于复杂的分析逻辑,应该提取到独立的 Python 模块中,而不是全部写在 Notebook 里
这节介绍了如何使用 Jupyter Notebook 有效地组织数据分析项目,包括项目结构、开发技巧和协作最佳实践,帮助我们建立专业、可维护的分析工作流。
"