10、Python数据分析与可视化

63 阅读18分钟

Python数据分析与可视化

1. 数据分析概述

作为Java开发者,您可能已经使用过Java的数据处理库,如Apache Commons Math或Java Stream API。Python在数据分析领域有着显著优势,拥有丰富的库和工具,使数据处理、分析和可视化变得简单高效。

数据分析标准流程

flowchart TD
    A[数据获取] -->|导入数据| B[数据清洗]
    B -->|处理缺失值和异常值| C[探索性数据分析]
    C -->|统计分析| D[数据可视化]
    D -->|图表展示| E[特征工程]
    E -->|创建新特征| F[建模与分析]
    F -->|应用算法| G[结果解释]
    G -->|业务洞察| H[报告与决策]
    
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style C fill:#dfd,stroke:#333,stroke-width:2px
    style D fill:#fdd,stroke:#333,stroke-width:2px
    style E fill:#ddf,stroke:#333,stroke-width:2px
    style F fill:#ffd,stroke:#333,stroke-width:2px
    style G fill:#dff,stroke:#333,stroke-width:2px
    style H fill:#fdf,stroke:#333,stroke-width:2px

Python数据科学生态系统

  • NumPy:科学计算的基础库,提供多维数组对象和相关操作
  • pandas:基于NumPy的数据分析工具,提供DataFrame对象
  • Matplotlib:经典的可视化库,提供类似MATLAB的绘图API
  • Seaborn:基于Matplotlib的高级可视化库,提供更美观的图表
  • SciPy:科学计算库,提供统计、优化、积分等功能
  • scikit-learn:机器学习库,提供各种算法和工具

2. NumPy基础

NumPy是Python科学计算的基础库,提供了多维数组对象、各种派生对象(如掩码数组和矩阵)以及用于数组快速操作的各种API。

安装NumPy

pip install numpy

创建数组

import numpy as np

# 从列表创建数组
arr1 = np.array([1, 2, 3, 4, 5])
print(arr1)  # 输出: [1 2 3 4 5]

# 创建二维数组
arr2 = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2)
# 输出:
# [[1 2 3]
#  [4 5 6]]

# 创建特殊数组
zeros = np.zeros((3, 4))  # 3x4的全0数组
ones = np.ones((2, 3))    # 2x3的全1数组
identity = np.eye(3)      # 3x3的单位矩阵
random_arr = np.random.random((2, 2))  # 2x2的随机数组

# 创建等间隔数组
linear = np.linspace(0, 10, 5)  # 0到10之间的5个等间隔数
print(linear)  # 输出: [ 0.   2.5  5.   7.5 10. ]

arange = np.arange(0, 10, 2)    # 0到10之间步长为2的数组
print(arange)  # 输出: [0 2 4 6 8]

数组属性和方法

arr = np.array([[1, 2, 3], [4, 5, 6]])

# 数组属性
print(arr.shape)     # 输出: (2, 3) - 数组形状
print(arr.ndim)      # 输出: 2 - 数组维度
print(arr.size)      # 输出: 6 - 数组元素总数
print(arr.dtype)     # 输出: int64 - 数组元素类型

# 数组变形
reshaped = arr.reshape(3, 2)  # 将2x3数组变形为3x2
print(reshaped)
# 输出:
# [[1 2]
#  [3 4]
#  [5 6]]

flattened = arr.flatten()  # 将多维数组展平为一维
print(flattened)  # 输出: [1 2 3 4 5 6]

# 数组转置
transposed = arr.T
print(transposed)
# 输出:
# [[1 4]
#  [2 5]
#  [3 6]]

数组索引和切片

arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

# 基本索引
print(arr[0, 0])    # 输出: 1 - 第一行第一列
print(arr[1, 2])    # 输出: 7 - 第二行第三列

# 切片
print(arr[0:2, 1:3])
# 输出:
# [[2 3]
#  [6 7]]

# 高级索引
print(arr[[0, 2], [1, 3]])  # 输出: [2 12] - (0,1)和(2,3)位置的元素

# 布尔索引
mask = arr > 5
print(arr[mask])  # 输出: [ 6  7  8  9 10 11 12] - 所有大于5的元素

数组操作

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 数组运算
print(a + b)      # 输出: [5 7 9] - 元素级加法
print(a - b)      # 输出: [-3 -3 -3] - 元素级减法
print(a * b)      # 输出: [4 10 18] - 元素级乘法
print(a / b)      # 输出: [0.25 0.4  0.5 ] - 元素级除法
print(a ** 2)     # 输出: [1 4 9] - 元素级平方

# 矩阵运算
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

print(np.dot(A, B))  # 矩阵乘法
# 输出:
# [[19 22]
#  [43 50]]

print(np.linalg.inv(A))  # 矩阵求逆
# 输出:
# [[-2.   1. ]
#  [ 1.5 -0.5]]

# 统计函数
arr = np.array([1, 2, 3, 4, 5])
print(np.sum(arr))       # 输出: 15 - 求和
print(np.mean(arr))      # 输出: 3.0 - 平均值
print(np.std(arr))       # 输出: 1.4142135623730951 - 标准差
print(np.min(arr))       # 输出: 1 - 最小值
print(np.max(arr))       # 输出: 5 - 最大值
print(np.median(arr))    # 输出: 3.0 - 中位数

广播机制

NumPy的广播机制允许对不同形状的数组进行算术运算:

# 标量与数组运算
arr = np.array([1, 2, 3, 4])
print(arr * 2)  # 输出: [2 4 6 8]

# 不同形状数组的运算
a = np.array([[1, 2, 3], [4, 5, 6]])  # 形状(2, 3)
b = np.array([10, 20, 30])            # 形状(3,)
print(a + b)
# 输出:
# [[11 22 33]
#  [14 25 36]]

3. pandas基础

pandas是基于NumPy的数据分析工具,提供了DataFrame等数据结构和数据分析功能。

安装pandas

pip install pandas

Series对象

Series是带有标签的一维数组:

import pandas as pd

# 从列表创建Series
s = pd.Series([1, 3, 5, 7, 9])
print(s)
# 输出:
# 0    1
# 1    3
# 2    5
# 3    7
# 4    9
# dtype: int64

# 指定索引
s = pd.Series([1, 3, 5, 7, 9], index=['a', 'b', 'c', 'd', 'e'])
print(s)
# 输出:
# a    1
# b    3
# c    5
# d    7
# e    9
# dtype: int64

# 从字典创建Series
data = {'a': 1, 'b': 3, 'c': 5, 'd': 7, 'e': 9}
s = pd.Series(data)
print(s)
# 输出与上面相同

# 访问元素
print(s['a'])    # 输出: 1 - 通过标签访问
print(s[0])      # 输出: 1 - 通过位置访问
print(s[['a', 'c', 'e']])  # 访问多个元素
# 输出:
# a    1
# c    5
# e    9
# dtype: int64

DataFrame对象

DataFrame是带有标签的二维表格:

# 从字典创建DataFrame
data = {
    'Name': ['John', 'Anna', 'Peter', 'Linda'],
    'Age': [28, 34, 29, 42],
    'City': ['New York', 'Paris', 'Berlin', 'London']
}
df = pd.DataFrame(data)
print(df)
# 输出:
#     Name  Age      City
# 0   John   28  New York
# 1   Anna   34     Paris
# 2  Peter   29    Berlin
# 3  Linda   42    London

# 指定索引
df = pd.DataFrame(data, index=['p1', 'p2', 'p3', 'p4'])
print(df)
# 输出:
#      Name  Age      City
# p1   John   28  New York
# p2   Anna   34     Paris
# p3  Peter   29    Berlin
# p4  Linda   42    London

# 从CSV文件创建DataFrame
# df = pd.read_csv('data.csv')

# 从Excel文件创建DataFrame
# df = pd.read_excel('data.xlsx')

DataFrame基本操作

# 查看数据
print(df.head(2))  # 前2行
print(df.tail(2))  # 后2行
print(df.info())   # 数据信息
print(df.describe())  # 数值列的统计摘要

# 访问数据
print(df['Name'])  # 访问单列
print(df[['Name', 'Age']])  # 访问多列

# loc - 基于标签的索引
print(df.loc['p1'])  # 访问单行
print(df.loc['p1':'p3'])  # 访问多行
print(df.loc['p1', 'Name'])  # 访问特定元素
print(df.loc['p1':'p3', ['Name', 'City']])  # 访问子DataFrame

# iloc - 基于位置的索引
print(df.iloc[0])  # 访问第一行
print(df.iloc[0:3])  # 访问前三行
print(df.iloc[0, 0])  # 访问第一行第一列
print(df.iloc[0:3, [0, 2]])  # 访问子DataFrame

# 条件过滤
print(df[df['Age'] > 30])  # 年龄大于30的行
print(df[(df['Age'] > 30) & (df['City'] == 'London')])  # 组合条件

数据处理

# 添加列
df['Country'] = ['USA', 'France', 'Germany', 'UK']
print(df)

# 修改值
df.loc['p1', 'Age'] = 29
print(df)

# 删除列
df_copy = df.copy()
df_copy = df_copy.drop('Country', axis=1)
print(df_copy)

# 删除行
df_copy = df.copy()
df_copy = df_copy.drop('p1', axis=0)
print(df_copy)

# 处理缺失值
df_missing = pd.DataFrame({
    'A': [1, 2, None, 4],
    'B': [5, None, 7, 8],
    'C': [9, 10, 11, None]
})

print(df_missing.isnull())  # 检查缺失值
print(df_missing.fillna(0))  # 填充缺失值
print(df_missing.dropna())   # 删除包含缺失值的行
print(df_missing.dropna(axis=1))  # 删除包含缺失值的列

数据聚合和分组

# 创建示例数据
data = {
    'Category': ['A', 'B', 'A', 'B', 'A', 'B'],
    'Value1': [10, 20, 30, 40, 50, 60],
    'Value2': [100, 200, 300, 400, 500, 600]
}
df = pd.DataFrame(data)
print(df)

# 分组聚合
grouped = df.groupby('Category')
print(grouped.sum())  # 每个类别的总和
print(grouped.mean())  # 每个类别的平均值
print(grouped.count())  # 每个类别的计数

# 多种聚合
print(grouped.agg(['sum', 'mean', 'count']))

# 自定义聚合
print(grouped.agg({
    'Value1': ['sum', 'mean'],
    'Value2': ['min', 'max']
}))

数据合并和连接

# 创建示例数据
df1 = pd.DataFrame({
    'ID': [1, 2, 3, 4],
    'Name': ['John', 'Anna', 'Peter', 'Linda'],
    'Age': [28, 34, 29, 42]
})

df2 = pd.DataFrame({
    'ID': [1, 2, 3, 5],
    'City': ['New York', 'Paris', 'Berlin', 'Rome'],
    'Salary': [50000, 60000, 55000, 65000]
})

# 合并(merge) - 类似SQL连接
merged = pd.merge(df1, df2, on='ID')
print(merged)

# 不同类型的合并
left_merge = pd.merge(df1, df2, on='ID', how='left')
right_merge = pd.merge(df1, df2, on='ID', how='right')
outer_merge = pd.merge(df1, df2, on='ID', how='outer')

# 连接(concat) - 按行或列拼接
concat_rows = pd.concat([df1, df1])  # 按行连接
concat_cols = pd.concat([df1, df2], axis=1)  # 按列连接

# 连接(join) - 基于索引的合并
df1_indexed = df1.set_index('ID')
df2_indexed = df2.set_index('ID')
joined = df1_indexed.join(df2_indexed)
print(joined)

数据透视表

# 创建示例数据
data = {
    'Date': pd.date_range(start='2023-01-01', periods=6),
    'Product': ['A', 'B', 'A', 'B', 'A', 'B'],
    'Region': ['East', 'East', 'West', 'West', 'East', 'West'],
    'Sales': [100, 200, 150, 250, 120, 220]
}
df = pd.DataFrame(data)
print(df)

# 创建透视表
pivot = pd.pivot_table(
    df,
    values='Sales',
    index='Product',
    columns='Region',
    aggfunc='sum'
)
print(pivot)

# 多级索引透视表
pivot_multi = pd.pivot_table(
    df,
    values='Sales',
    index=['Product', pd.Grouper(key='Date', freq='M')],
    columns='Region',
    aggfunc='sum'
)
print(pivot_multi)

4. Matplotlib基础

Matplotlib是Python最流行的可视化库,提供了类似MATLAB的绘图API。

安装Matplotlib

pip install matplotlib

基本绘图

import matplotlib.pyplot as plt
import numpy as np

# 创建数据
x = np.linspace(0, 10, 100)
y = np.sin(x)

# 创建图形
plt.figure(figsize=(10, 6))  # 设置图形大小

# 绘制线图
plt.plot(x, y, 'b-', label='sin(x)')  # 'b-'表示蓝色实线

# 添加标题和标签
plt.title('Sine Wave')
plt.xlabel('x')
plt.ylabel('sin(x)')

# 添加图例
plt.legend()

# 添加网格
plt.grid(True)

# 显示图形
plt.show()

多子图

# 创建数据
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
y3 = np.tan(x)
y4 = x**2

# 创建2x2的子图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# 在每个子图上绘制不同的图形
axes[0, 0].plot(x, y1)
axes[0, 0].set_title('Sine')

axes[0, 1].plot(x, y2)
axes[0, 1].set_title('Cosine')

axes[1, 0].plot(x, y3)
axes[1, 0].set_title('Tangent')

axes[1, 1].plot(x, y4)
axes[1, 1].set_title('Square')

# 调整子图之间的间距
plt.tight_layout()

# 显示图形
plt.show()

常见图表类型

线图
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

plt.figure(figsize=(10, 6))
plt.plot(x, y1, 'b-', label='sin(x)')
plt.plot(x, y2, 'r--', label='cos(x)')
plt.title('Sine and Cosine Waves')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(True)
plt.show()
散点图
# 创建随机数据
np.random.seed(42)
x = np.random.rand(50)
y = np.random.rand(50)
colors = np.random.rand(50)
sizes = 1000 * np.random.rand(50)

plt.figure(figsize=(10, 6))
plt.scatter(x, y, c=colors, s=sizes, alpha=0.5, cmap='viridis')
plt.colorbar()  # 添加颜色条
plt.title('Scatter Plot')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
柱状图
categories = ['A', 'B', 'C', 'D', 'E']
values = [25, 40, 30, 55, 15]

plt.figure(figsize=(10, 6))
plt.bar(categories, values, color='skyblue')
plt.title('Bar Chart')
plt.xlabel('Category')
plt.ylabel('Value')
plt.show()
直方图
# 创建正态分布数据
data = np.random.normal(0, 1, 1000)

plt.figure(figsize=(10, 6))
plt.hist(data, bins=30, alpha=0.7, color='skyblue', edgecolor='black')
plt.title('Histogram')
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.grid(True, alpha=0.3)
plt.show()
饼图
labels = ['A', 'B', 'C', 'D', 'E']
sizes = [15, 30, 25, 10, 20]
explode = (0, 0.1, 0, 0, 0)  # 突出第二个扇形

plt.figure(figsize=(10, 6))
plt.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%',
        shadow=True, startangle=90)
plt.axis('equal')  # 保证饼图是圆的
plt.title('Pie Chart')
plt.show()
箱线图
# 创建多组数据
data = [np.random.normal(0, std, 100) for std in range(1, 6)]

plt.figure(figsize=(10, 6))
plt.boxplot(data, patch_artist=True)
plt.title('Box Plot')
plt.xlabel('Group')
plt.ylabel('Value')
plt.grid(True, alpha=0.3)
plt.show()
热图
import seaborn as sns

# 创建相关矩阵
corr_matrix = np.corrcoef(np.random.randn(10, 5))

plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', vmin=-1, vmax=1)
plt.title('Correlation Matrix')
plt.show()

自定义图表样式

# 设置全局样式
plt.style.use('seaborn-darkgrid')

# 创建数据
x = np.linspace(0, 10, 100)
y = np.sin(x)

# 创建图形和坐标轴
fig, ax = plt.subplots(figsize=(10, 6))

# 绘制数据
ax.plot(x, y, linewidth=2, color='#ff7f0e', linestyle='-', marker='o', 
        markersize=5, markevery=10, label='sin(x)')

# 设置标题和标签
ax.set_title('Customized Sine Wave', fontsize=16, fontweight='bold')
ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('sin(x)', fontsize=12)

# 设置坐标轴范围
ax.set_xlim(0, 10)
ax.set_ylim(-1.5, 1.5)

# 添加网格
ax.grid(True, linestyle='--', alpha=0.7)

# 添加图例
ax.legend(fontsize=12)

# 添加水平线和垂直线
ax.axhline(y=0, color='k', linestyle='-', alpha=0.3)
ax.axvline(x=5, color='r', linestyle='--', alpha=0.3)

# 添加文本注释
ax.text(5, 0.5, 'Important Point', fontsize=12, 
        ha='center', va='center', bbox=dict(facecolor='white', alpha=0.5))

# 添加箭头注释
ax.annotate('Local Maximum', xy=(1.5, 1), xytext=(2.5, 1.3),
            arrowprops=dict(facecolor='black', shrink=0.05))

# 保存图形
# plt.savefig('custom_plot.png', dpi=300, bbox_inches='tight')

# 显示图形
plt.show()

5. Seaborn基础

Seaborn是基于Matplotlib的高级可视化库,提供了更美观的图表和更简洁的API。

安装Seaborn

pip install seaborn

基本使用

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

# 设置样式
sns.set_theme(style="whitegrid")

# 加载示例数据集
tips = sns.load_dataset("tips")
print(tips.head())

常见图表类型

关系图
# 散点图
plt.figure(figsize=(10, 6))
sns.scatterplot(x="total_bill", y="tip", hue="time", size="size", data=tips)
plt.title("Relationship between Total Bill and Tip")
plt.show()

# 线图
plt.figure(figsize=(10, 6))
sns.lineplot(x="total_bill", y="tip", hue="time", data=tips)
plt.title("Relationship between Total Bill and Tip")
plt.show()

# 回归图
plt.figure(figsize=(10, 6))
sns.regplot(x="total_bill", y="tip", data=tips)
plt.title("Regression between Total Bill and Tip")
plt.show()

# 成对关系图
plt.figure(figsize=(12, 10))
sns.pairplot(tips, hue="time")
plt.suptitle("Pairwise Relationships", y=1.02)
plt.show()
分布图
# 直方图
plt.figure(figsize=(10, 6))
sns.histplot(data=tips, x="total_bill", kde=True)
plt.title("Distribution of Total Bill")
plt.show()

# 核密度估计图
plt.figure(figsize=(10, 6))
sns.kdeplot(data=tips, x="total_bill", hue="time", fill=True)
plt.title("Density of Total Bill by Time")
plt.show()

# 箱线图
plt.figure(figsize=(10, 6))
sns.boxplot(x="day", y="total_bill", data=tips)
plt.title("Total Bill by Day")
plt.show()

# 小提琴图
plt.figure(figsize=(10, 6))
sns.violinplot(x="day", y="total_bill", hue="sex", data=tips, split=True)
plt.title("Total Bill by Day and Sex")
plt.show()
分类图
# 条形图
plt.figure(figsize=(10, 6))
sns.barplot(x="day", y="total_bill", data=tips)
plt.title("Average Total Bill by Day")
plt.show()

# 计数图
plt.figure(figsize=(10, 6))
sns.countplot(x="day", hue="sex", data=tips)
plt.title("Count of Bills by Day and Sex")
plt.show()

# 点图
plt.figure(figsize=(10, 6))
sns.pointplot(x="day", y="total_bill", hue="time", data=tips)
plt.title("Average Total Bill by Day and Time")
plt.show()
矩阵图
# 热图
corr = tips.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(corr, annot=True, cmap="coolwarm")
plt.title("Correlation Matrix")
plt.show()

# 聚类图
plt.figure(figsize=(12, 10))
sns.clustermap(corr, annot=True, cmap="coolwarm")
plt.title("Clustered Correlation Matrix")
plt.show()

高级自定义

# 创建FacetGrid进行多图绘制
g = sns.FacetGrid(tips, col="time", row="sex", height=4, aspect=1.5)
g.map_dataframe(sns.scatterplot, x="total_bill", y="tip")
g.add_legend()
plt.show()

# 使用catplot创建分类图
g = sns.catplot(
    data=tips, kind="bar",
    x="day", y="total_bill", hue="sex",
    palette="dark", alpha=.6, height=6, aspect=1.5
)
g.set_axis_labels("Day", "Total Bill")
g.legend.set_title("Sex")
plt.show()

# 使用jointplot创建联合分布图
plt.figure(figsize=(10, 8))
sns.jointplot(
    data=tips,
    x="total_bill", y="tip",
    kind="reg", truncate=False,
    xlim=(0, 60), ylim=(0, 12),
    color="m", height=8
)
plt.show()

6. 数据分析案例

让我们通过一个完整的案例来整合NumPy、pandas和可视化库的使用。

案例:销售数据分析

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

# 设置样式
plt.style.use('seaborn-whitegrid')
sns.set_palette("deep")

# 创建模拟销售数据
np.random.seed(42)

# 日期范围
dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='D')

# 产品类别
categories = ['Electronics', 'Clothing', 'Food', 'Books', 'Home']

# 创建数据框
data = []
for date in dates:
    for category in categories:
        # 添加季节性和周末效应
        seasonal_factor = 1.0 + 0.3 * np.sin(2 * np.pi * date.dayofyear / 365)
        weekend_factor = 1.5 if date.dayofweek >= 5 else 1.0
        
        # 基础销售量
        base_sales = np.random.normal(loc=100, scale=20)
        
        # 应用因子
        sales = base_sales * seasonal_factor * weekend_factor
        
        # 添加一些随机促销效应
        if np.random.random() < 0.05:  # 5%的概率有促销
            sales *= np.random.uniform(1.5, 2.5)
        
        # 添加到数据列表
        data.append({
            'Date': date,
            'Category': category,
            'Sales': max(0, sales),  # 确保销售额非负
            'Weekday': date.day_name(),
            'Month': date.month_name(),
            'Quarter': f"Q{(date.month-1)//3+1}"
        })

# 创建DataFrame
sales_df = pd.DataFrame(data)
print(sales_df.head())

# 1. 数据探索
print("\n基本统计信息:")
print(sales_df.describe())

print("\n按类别分组的销售统计:")
category_stats = sales_df.groupby('Category')['Sales'].agg(['count', 'mean', 'std', 'min', 'max'])
print(category_stats)

print("\n按月份分组的销售统计:")
monthly_stats = sales_df.groupby('Month')['Sales'].mean().reindex([
    'January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December'
])
print(monthly_stats)

# 2. 可视化分析

# 设置图形大小
plt.figure(figsize=(15, 10))

# 2.1 总销售趋势
plt.subplot(2, 2, 1)
daily_sales = sales_df.groupby('Date')['Sales'].sum()
plt.plot(daily_sales.index, daily_sales.values)
plt.title('Daily Total Sales')
plt.xlabel('Date')
plt.ylabel('Sales')
plt.xticks(rotation=45)
plt.tight_layout()

# 2.2 类别销售比较
plt.subplot(2, 2, 2)
category_sales = sales_df.groupby('Category')['Sales'].sum()
sns.barplot(x=category_sales.index, y=category_sales.values)
plt.title('Total Sales by Category')
plt.xlabel('Category')
plt.ylabel('Sales')
plt.xticks(rotation=45)
plt.tight_layout()

# 2.3 月度销售趋势
plt.subplot(2, 2, 3)
monthly_sales = sales_df.groupby('Month')['Sales'].sum()
monthly_sales = monthly_sales.reindex([
    'January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December'
])
sns.barplot(x=monthly_sales.index, y=monthly_sales.values)
plt.title('Total Sales by Month')
plt.xlabel('Month')
plt.ylabel('Sales')
plt.xticks(rotation=45)
plt.tight_layout()

# 2.4 星期几销售分布
plt.subplot(2, 2, 4)
weekday_sales = sales_df.groupby('Weekday')['Sales'].sum()
weekday_sales = weekday_sales.reindex([
    'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
])
sns.barplot(x=weekday_sales.index, y=weekday_sales.values)
plt.title('Total Sales by Weekday')
plt.xlabel('Weekday')
plt.ylabel('Sales')
plt.xticks(rotation=45)
plt.tight_layout()

plt.tight_layout(pad=3.0)
plt.show()

# 3. 高级分析

# 3.1 热图:月份和类别的销售关系
plt.figure(figsize=(12, 8))
month_category_sales = sales_df.pivot_table(
    index='Month', 
    columns='Category', 
    values='Sales', 
    aggfunc='sum'
)
month_category_sales = month_category_sales.reindex([
    'January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December'
])
sns.heatmap(month_category_sales, annot=True, fmt='.0f', cmap='YlGnBu')
plt.title('Sales Heatmap: Month vs Category')
plt.tight_layout()
plt.show()

# 3.2 箱线图:各类别销售分布
plt.figure(figsize=(12, 6))
sns.boxplot(x='Category', y='Sales', data=sales_df)
plt.title('Sales Distribution by Category')
plt.xlabel('Category')
plt.ylabel('Sales')
plt.tight_layout()
plt.show()

# 3.3 小提琴图:季度销售分布
plt.figure(figsize=(12, 6))
sns.violinplot(x='Quarter', y='Sales', data=sales_df, inner='quartile')
plt.title('Sales Distribution by Quarter')
plt.xlabel('Quarter')
plt.ylabel('Sales')
plt.tight_layout()
plt.show()

# 3.4 成对关系图:数值变量之间的关系
# 为了演示,添加一些额外的数值特征
sales_df['UnitPrice'] = np.random.uniform(10, 100, size=len(sales_df))
sales_df['Quantity'] = sales_df['Sales'] / sales_df['UnitPrice']

plt.figure(figsize=(10, 8))
sns.pairplot(sales_df[['Sales', 'UnitPrice', 'Quantity', 'Date']], 
             kind='scatter', diag_kind='kde')
plt.suptitle('Relationships Between Numerical Variables', y=1.02)
plt.tight_layout()
plt.show()

# 4. 时间序列分析
# 按日期和类别分组的销售趋势
plt.figure(figsize=(15, 10))
for category in categories:
    category_data = sales_df[sales_df['Category'] == category]
    daily_sales = category_data.groupby('Date')['Sales'].sum()
    plt.plot(daily_sales.index, daily_sales.values, label=category)

plt.title('Daily Sales Trend by Category')
plt.xlabel('Date')
plt.ylabel('Sales')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

# 5. 结论和见解
# (这部分通常是基于分析结果撰写的文本,这里只是示例)
print("\n分析结论:")
print("1. 销售额在周末显著高于工作日")
print("2. 电子产品类别的销售额最高,但也有最大的波动")
print("3. 销售额在第四季度达到峰值,可能是由于假日购物季")
print("4. 所有类别都显示出明显的季节性模式")

7. 实用数据分析技巧

数据清洗

# 处理缺失值
def handle_missing_values(df):
    # 检查缺失值
    print("缺失值统计:")
    print(df.isnull().sum())
    
    # 填充数值列的缺失值
    numeric_cols = df.select_dtypes(include=['number']).columns
    for col in numeric_cols:
        df[col].fillna(df[col].median(), inplace=True)
    
    # 填充分类列的缺失值
    categorical_cols = df.select_dtypes(include=['object']).columns
    for col in categorical_cols:
        df[col].fillna(df[col].mode()[0], inplace=True)
    
    return df

# 处理异常值
def handle_outliers(df, columns, method='iqr'):
    for col in columns:
        if method == 'iqr':
            # IQR方法
            Q1 = df[col].quantile(0.25)
            Q3 = df[col].quantile(0.75)
            IQR = Q3 - Q1
            lower_bound = Q1 - 1.5 * IQR
            upper_bound = Q3 + 1.5 * IQR
            
            # 替换异常值为边界值
            df[col] = np.where(df[col] < lower_bound, lower_bound, df[col])
            df[col] = np.where(df[col] > upper_bound, upper_bound, df[col])
        
        elif method == 'zscore':
            # Z-score方法
            from scipy import stats
            z_scores = stats.zscore(df[col])
            abs_z_scores = np.abs(z_scores)
            filtered_entries = (abs_z_scores < 3)  # 保留z-score小于3的值
            df[col] = np.where(abs_z_scores >= 3, df[col].median(), df[col])
    
    return df

# 特征工程
def feature_engineering(df):
    # 日期特征
    if 'Date' in df.columns and pd.api.types.is_datetime64_any_dtype(df['Date']):
        df['Year'] = df['Date'].dt.year
        df['Month'] = df['Date'].dt.month
        df['Day'] = df['Date'].dt.day
        df['DayOfWeek'] = df['Date'].dt.dayofweek
        df['IsWeekend'] = df['DayOfWeek'].isin([5, 6]).astype(int)
    
    # 分类特征编码
    categorical_cols = df.select_dtypes(include=['object']).columns
    for col in categorical_cols:
        df[f'{col}_encoded'] = df[col].astype('category').cat.codes
    
    return df

数据分析流程

def analyze_dataset(df, target_column=None):
    """
    对数据集进行全面分析
    
    参数:
    df: pandas DataFrame, 要分析的数据集
    target_column: str, 目标变量列名(如果有)
    """
    print("="*50)
    print("数据集基本信息")
    print("="*50)
    
    # 1. 基本信息
    print(f"数据集形状: {df.shape}")
    print(f"数据集列: {df.columns.tolist()}")
    print("\n数据类型:")
    print(df.dtypes)
    
    # 2. 缺失值分析
    print("\n缺失值统计:")
    missing = df.isnull().sum()
    missing_percent = (missing / len(df)) * 100
    missing_info = pd.DataFrame({
        '缺失值数量': missing,
        '缺失值百分比': missing_percent
    })
    print(missing_info[missing_info['缺失值数量'] > 0])
    
    # 3. 描述性统计
    print("\n数值列描述性统计:")
    print(df.describe().T)
    
    print("\n分类列描述性统计:")
    categorical_cols = df.select_dtypes(include=['object']).columns
    for col in categorical_cols:
        print(f"\n{col} 值计数:")
        print(df[col].value_counts().head())
    
    # 4. 相关性分析
    if len(df.select_dtypes(include=['number']).columns) > 1:
        print("\n相关性矩阵:")
        corr = df.select_dtypes(include=['number']).corr()
        print(corr)
        
        # 可视化相关性矩阵
        plt.figure(figsize=(12, 10))
        sns.heatmap(corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1)
        plt.title('Correlation Matrix')
        plt.show()
    
    # 5. 目标变量分析(如果提供)
    if target_column and target_column in df.columns:
        print(f"\n目标变量 '{target_column}' 分析:")
        
        if pd.api.types.is_numeric_dtype(df[target_column]):
            print("\n目标变量统计:")
            print(df[target_column].describe())
            
            # 目标变量分布
            plt.figure(figsize=(12, 6))
            plt.subplot(1, 2, 1)
            sns.histplot(df[target_column], kde=True)
            plt.title(f'{target_column} Distribution')
            
            plt.subplot(1, 2, 2)
            sns.boxplot(y=df[target_column])
            plt.title(f'{target_column} Box Plot')
            plt.tight_layout()
            plt.show()
            
            # 与其他数值变量的关系
            numeric_cols = df.select_dtypes(include=['number']).columns
            numeric_cols = [col for col in numeric_cols if col != target_column]
            
            if len(numeric_cols) > 0:
                plt.figure(figsize=(15, len(numeric_cols) * 4))
                for i, col in enumerate(numeric_cols, 1):
                    plt.subplot(len(numeric_cols), 1, i)
                    sns.scatterplot(x=col, y=target_column, data=df)
                    plt.title(f'{col} vs {target_column}')
                plt.tight_layout()
                plt.show()
        
        else:  # 分类目标变量
            print("\n目标变量计数:")
            print(df[target_column].value_counts())
            
            # 目标变量分布
            plt.figure(figsize=(10, 6))
            sns.countplot(x=target_column, data=df)
            plt.title(f'{target_column} Distribution')
            plt.xticks(rotation=45)
            plt.show()
    
    print("="*50)
    print("分析完成")
    print("="*50)

8. 练习:数据分析项目

练习1:股票数据分析

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

# 设置样式
plt.style.use('seaborn-whitegrid')
sns.set_palette("deep")

# 创建模拟股票数据
def generate_stock_data(tickers, start_date, end_date):
    date_range = pd.date_range(start=start_date, end=end_date, freq='B')  # 工作日
    data = []
    
    for ticker in tickers:
        # 初始价格
        price = np.random.uniform(50, 200)
        
        # 波动参数
        volatility = np.random.uniform(0.01, 0.03)
        drift = np.random.uniform(0.0001, 0.0005)
        
        for date in date_range:
            # 生成价格变动
            daily_return = np.random.normal(drift, volatility)
            price *= (1 + daily_return)
            
            # 生成交易量
            volume = int(np.random.normal(1000000, 300000))
            volume = max(100000, volume)  # 确保交易量为正
            
            # 计算开盘价、最高价、最低价
            open_price = price * np.random.uniform(0.99, 1.01)
            high_price = price * np.random.uniform(1.0, 1.03)
            low_price = price * np.random.uniform(0.97, 1.0)
            
            data.append({
                'Date': date,
                'Ticker': ticker,
                'Open': open_price,
                'High': high_price,
                'Low': low_price,
                'Close': price,
                'Volume': volume
            })
    
    return pd.DataFrame(data)

# 生成数据
tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'FB']
start_date = '2022-01-01'
end_date = '2022-12-31'
stock_df = generate_stock_data(tickers, start_date, end_date)

# 数据分析任务
# 1. 计算每只股票的日收益率
stock_df['DailyReturn'] = stock_df.groupby('Ticker')['Close'].pct_change()

# 2. 计算每只股票的累积收益率
stock_df['CumulativeReturn'] = stock_df.groupby('Ticker')['Close'].apply(
    lambda x: x / x.iloc[0] - 1
)

# 3. 计算每只股票的波动率(标准差)
volatility = stock_df.groupby('Ticker')['DailyReturn'].std() * np.sqrt(252)  # 年化
print("年化波动率:")
print(volatility)

# 4. 可视化股价走势
plt.figure(figsize=(15, 8))
for ticker in tickers:
    ticker_data = stock_df[stock_df['Ticker'] == ticker]
    plt.plot(ticker_data['Date'], ticker_data['Close'], label=ticker)

plt.title('Stock Price Trends')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()
plt.grid(True)
plt.show()

# 5. 可视化累积收益率
plt.figure(figsize=(15, 8))
for ticker in tickers:
    ticker_data = stock_df[stock_df['Ticker'] == ticker]
    plt.plot(ticker_data['Date'], ticker_data['CumulativeReturn'], label=ticker)

plt.title('Cumulative Returns')
plt.xlabel('Date')
plt.ylabel('Return')
plt.legend()
plt.grid(True)
plt.show()

# 6. 相关性分析
pivot_close = stock_df.pivot_table(index='Date', columns='Ticker', values='Close')
returns = pivot_close.pct_change().dropna()

plt.figure(figsize=(12, 10))
sns.heatmap(returns.corr(), annot=True, cmap='coolwarm', vmin=-1, vmax=1)
plt.title('Correlation of Stock Returns')
plt.show()

# 7. 交易量分析
plt.figure(figsize=(15, 8))
sns.boxplot(x='Ticker', y='Volume', data=stock_df)
plt.title('Trading Volume by Stock')
plt.ylabel('Volume')
plt.yscale('log')  # 对数刻度更容易比较
plt.show()

# 8. 风险与收益分析
mean_returns = returns.mean() * 252  # 年化收益率
std_returns = returns.std() * np.sqrt(252)  # 年化标准差

plt.figure(figsize=(12, 8))
plt.scatter(std_returns, mean_returns, s=100)

# 添加标签
for i, ticker in enumerate(tickers):
    plt.annotate(ticker, (std_returns[i], mean_returns[i]), 
                 xytext=(5, 5), textcoords='offset points')

plt.xlabel('Risk (Annual Standard Deviation)')
plt.ylabel('Expected Return (Annual)')
plt.title('Risk vs. Return')
plt.grid(True)
plt.show()

9. 今日总结

  • NumPy是Python科学计算的基础库,提供了多维数组对象和各种数学函数
  • pandas是强大的数据分析工具,提供了DataFrame等数据结构和数据处理功能
  • Matplotlib是Python的基础可视化库,提供了类似MATLAB的绘图API
  • Seaborn是基于Matplotlib的高级可视化库,提供了更美观的图表和更简洁的API
  • 数据分析通常包括数据清洗、探索性分析、可视化和高级分析等步骤
  • Python的数据科学生态系统非常丰富,可以满足各种数据分析需求

10. 明日预告

明天我们将学习Python的测试与调试技术,包括单元测试、集成测试、调试工具和性能分析等内容。这些技能对于确保代码质量和提高开发效率至关重要。