aoi学院

Aisaka's Blog, School of Aoi, Aisaka University

Python-第18篇《数据可视化入门》

导语

辛辛苦苦做出完美数据表,结果领导只看了一眼说“看不懂”?今天咱们给数字“化妆”——把枯燥的表格变成一目了然的图表。柱状图PK部门业绩,折线图看利润走势,饼图分析成本结构。


本篇目标

  • 掌握matplotlib三种核心图表
  • 用财务案例演练数据可视化
  • 学会图表美化(颜色、标签、字体)
  • 制作多图表组合的数据报告
  • 保存高质量图片用于汇报

一、准备工作:安装库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 命令行运行
# pip install matplotlib pandas

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

# 设置中文字体(解决中文乱码)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题

print("✅ 环境配置完成!")

二、柱状图:部门业绩大比拼

最直观的“谁高谁低”,适合对比不同类别的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# ========== 案例:各部门月度收入 ==========
def demo_bar_chart():
"""柱状图演示"""

# 准备数据
data = {
'部门': ['销售部', '市场部', '技术部', '财务部', '人力部'],
'收入(万元)': [850, 620, 480, 320, 280],
'成本(万元)': [420, 380, 350, 180, 160]
}
df = pd.DataFrame(data)

# 创建图表
plt.figure(figsize=(10, 6))

# 设置x轴位置
x = np.arange(len(df['部门']))
width = 0.35 # 柱子宽度

# 绘制柱子
bars1 = plt.bar(x - width/2, df['收入(万元)'], width,
label='收入', color='#4CAF50', alpha=0.8)
bars2 = plt.bar(x + width/2, df['成本(万元)'], width,
label='成本', color='#FF9800', alpha=0.8)

# 添加数值标签
for bar in bars1:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2, height,
f'{height}', ha='center', va='bottom', fontweight='bold')

for bar in bars2:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2, height,
f'{height}', ha='center', va='bottom', fontweight='bold')

# 设置标题和标签
plt.title('各部门月度收支对比', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('部门', fontsize=12)
plt.ylabel('金额(万元)', fontsize=12)
plt.xticks(x, df['部门'])
plt.legend()

# 添加背景网格
plt.grid(axis='y', linestyle='--', alpha=0.7)

# 自动调整布局
plt.tight_layout()

# 保存图片
plt.savefig('部门业绩对比.png', dpi=150, bbox_inches='tight')
print("✅ 已保存: 部门业绩对比.png")

plt.show()

# 测试一下
# demo_bar_chart()

三、折线图:利润走势全掌握

看趋势、看波动,时间序列数据的最佳选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# ========== 案例:本年度利润走势 ==========
def demo_line_chart():
"""折线图演示"""

# 生成模拟月度数据
months = ['1月', '2月', '3月', '4月', '5月', '6月',
'7月', '8月', '9月', '10月', '11月', '12月']

# 模拟利润数据(万元)
profit = [120, 135, 158, 142, 165, 178,
195, 210, 188, 225, 240, 260]

# 创建图表
plt.figure(figsize=(12, 6))

# 绘制折线
plt.plot(months, profit, marker='o', linewidth=2.5,
markersize=8, color='#2196F3', markerfacecolor='#FF5722')

# 添加数值标签
for i, v in enumerate(profit):
plt.text(i, v + 5, f'{v}', ha='center', va='bottom',
fontweight='bold', fontsize=10)

# 添加趋势线
z = np.polyfit(range(len(profit)), profit, 1)
p = np.poly1d(z)
plt.plot(months, p(range(len(profit))),
linestyle='--', color='#9C27B0', alpha=0.6, label='趋势线')

# 高亮最大值和最小值
max_idx = profit.index(max(profit))
min_idx = profit.index(min(profit))

plt.scatter([months[max_idx]], [profit[max_idx]],
color='red', s=150, marker='*', label='最大值', zorder=5)
plt.scatter([months[min_idx]], [profit[min_idx]],
color='green', s=150, marker='^', label='最小值', zorder=5)

# 设置标题和标签
plt.title('2025年度月度利润走势', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('月份', fontsize=12)
plt.ylabel('利润(万元)', fontsize=12)
plt.legend()

# 设置y轴从0开始
plt.ylim(bottom=0)

# 网格线
plt.grid(True, linestyle='--', alpha=0.6)

# 旋转x轴标签避免重叠
plt.xticks(rotation=45)

plt.tight_layout()
plt.savefig('利润走势.png', dpi=150, bbox_inches='tight')
print("✅ 已保存: 利润走势.png")

plt.show()

# 测试一下
# demo_line_chart()

四、饼图:成本结构一目了然

展示占比关系,“钱花在哪儿了”最清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# ========== 案例:成本结构分析 ==========
def demo_pie_chart():
"""饼图演示"""

# 成本数据
cost_items = ['原材料', '人工工资', '房租水电', '营销推广', '其他']
cost_values = [45, 25, 15, 10, 5]

# 创建图表
plt.figure(figsize=(10, 8))

# 自定义颜色
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8']

# 突出显示最大的一块
explode = [0.1 if v == max(cost_values) else 0 for v in cost_values]

# 绘制饼图
wedges, texts, autotexts = plt.pie(
cost_values, labels=cost_items, colors=colors,
explode=explode, autopct='%1.1f%%', startangle=140,
textprops={'fontsize': 11, 'fontweight': 'bold'},
shadow=True
)

# 美化百分比文字
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontsize(12)

# 标题
plt.title('2025年度成本结构分析',
fontsize=16, fontweight='bold', pad=30)

# 添加图例
plt.legend(title="成本项目", loc='upper left', bbox_to_anchor=(1, 0.5))

plt.axis('equal') # 保证饼图是圆形

plt.tight_layout()
plt.savefig('成本结构.png', dpi=150, bbox_inches='tight')
print("✅ 已保存: 成本结构.png")

plt.show()

# 测试一下
# demo_pie_chart()

五、财务数据综合可视化

来个真实场景:分析公司三年的财务数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# ========== 综合案例:三年财务对比 ==========
def comprehensive_finance_report():
"""生成综合财务报告"""

# 创建模拟数据
years = ['2023年', '2024年', '2025年']

# 各年度数据(单位:万元)
data = {
'年营收': [2800, 3200, 3650],
'净利润': [320, 385, 450],
'现金流': [450, 520, 580],
'研发投入': [180, 240, 300]
}

# 创建一个2x2的子图布局
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
fig.suptitle('公司三年财务数据综合分析报告',
fontsize=20, fontweight='bold', y=0.98)

# 1. 营收柱状图(左上)
axes[0,0].bar(years, data['年营收'], color=['#FF9999','#66B2FF','#99FF99'])
axes[0,0].set_title('年营收对比', fontweight='bold', fontsize=12)
axes[0,0].set_ylabel('营收(万元)')
for i, v in enumerate(data['年营收']):
axes[0,0].text(i, v + 50, f'{v}', ha='center', fontweight='bold')

# 2. 净利润折线图(右上)
axes[0,1].plot(years, data['净利润'], marker='o', linewidth=3, markersize=10,
color='#FF5722')
axes[0,1].set_title('净利润趋势', fontweight='bold', fontsize=12)
axes[0,1].set_ylabel('净利润(万元)')
axes[0,1].grid(True, linestyle='--', alpha=0.6)

# 3. 现金流饼图(左下)
axes[1,0].pie(data['现金流'], labels=years, autopct='%1.1f%%',
colors=['#FFD700','#C0C0C0','#CD7F32'], startangle=90)
axes[1,0].set_title('现金流分布', fontweight='bold', fontsize=12)

# 4. 研发投入堆叠柱状图(右下)
# 创建堆叠数据
base = np.zeros(len(years))
colors = ['#8BC34A', '#FFC107', '#9C27B0']
labels = ['人工费用', '设备采购', '其他投入']

# 模拟数据结构
rd_detail = {
'人工费用': [100, 130, 160],
'设备采购': [50, 70, 90],
'其他投入': [30, 40, 50]
}

bottom = np.zeros(len(years))
for i, (label, values) in enumerate(rd_detail.items()):
axes[1,1].bar(years, values, bottom=bottom,
label=label, color=colors[i])
bottom += values

axes[1,1].set_title('研发投入明细', fontweight='bold', fontsize=12)
axes[1,1].set_ylabel('投入(万元)')
axes[1,1].legend(loc='upper left', bbox_to_anchor=(1, 1))

# 调整布局
plt.tight_layout(rect=[0, 0, 1, 0.96])

# 保存高质量图片
plt.savefig('财务综合分析报告.png', dpi=300, bbox_inches='tight')
print("✅ 已保存: 财务综合分析报告.png (高清版)")

plt.show()

# 测试综合报告
# comprehensive_finance_report()

六、图表美化进阶技巧

6.1 整体风格定制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def style_demo():
"""展示不同风格"""

# 准备数据
x = ['Q1', 'Q2', 'Q3', 'Q4']
y = [85, 92, 78, 95]

# 内置风格
styles = ['default', 'seaborn-v0_8', 'ggplot', 'bmh', 'dark_background']

fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.flatten()

for i, style in enumerate(styles):
if i >= len(axes):
break

plt.style.use(style)

axes[i].plot(x, y, marker='o', linewidth=3)
axes[i].set_title(f'style: {style}', fontweight='bold')
axes[i].set_facecolor('#f0f0f0')

# 恢复默认风格
plt.style.use('default')

plt.tight_layout()
plt.show()

# style_demo()

6.2 配色方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def color_palette_demo():
"""展示专业配色"""

# 财务蓝主题
finance_blue = ['#1f77b4', '#aec7e8', '#006ba4', '#6897bb']

# 暖色主题
warm_colors = ['#ff7f0e', '#ffbb78', '#ff5500', '#ffaa33']

# 灰色系(适合正式报告)
gray_scale = ['#2f2f2f', '#7f7f7f', '#bcbd22', '#dbdb8d']

# 创建示例
data = [45, 35, 20]

fig, axes = plt.subplots(1, 3, figsize=(15, 5))

for ax, colors, title in zip(axes, [finance_blue, warm_colors, gray_scale],
['财务蓝', '暖色系', '商务灰']):
ax.pie(data, labels=['A', 'B', 'C'], autopct='%1.1f%%', colors=colors)
ax.set_title(title)

plt.show()

# color_palette_demo()

七、完整项目:财务数据可视化工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# ========== 财务数据可视化工具 - 完整版 ==========
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from datetime import datetime

class FinanceVisualizer:
"""财务数据可视化工具"""

def __init__(self):
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
self.data = None

def load_data(self, filepath):
"""加载CSV数据"""
try:
self.data = pd.read_csv(filepath)
print(f"✅ 加载成功: {len(self.data)}行数据")
return True
except Exception as e:
print(f"❌ 加载失败: {e}")
return False

def create_bar_chart(self, x_col, y_col, title="柱状图"):
"""通用柱状图"""
if self.data is None:
print("⚠️ 请先加载数据")
return

plt.figure(figsize=(10, 6))

# 如果是字符串类别
if self.data[x_col].dtype == 'object':
plt.bar(self.data[x_col], self.data[y_col],
color='#4CAF50', alpha=0.8)
else:
# 如果是数值,用散点
plt.scatter(self.data[x_col], self.data[y_col],
s=100, color='#FF5722')

plt.title(title, fontsize=14, fontweight='bold')
plt.xlabel(x_col)
plt.ylabel(y_col)
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.xticks(rotation=45)
plt.tight_layout()

filename = f"{title}_{datetime.now().strftime('%Y%m%d')}.png"
plt.savefig(filename, dpi=150)
print(f"✅ 已保存: {filename}")
plt.show()

def create_line_chart(self, x_col, y_cols, title="折线图"):
"""多系列折线图"""
if self.data is None:
print("⚠️ 请先加载数据")
return

plt.figure(figsize=(12, 6))

if isinstance(y_cols, str):
y_cols = [y_cols]

colors = plt.cm.tab10(np.linspace(0, 1, len(y_cols)))

for i, y_col in enumerate(y_cols):
plt.plot(self.data[x_col], self.data[y_col],
marker='o', label=y_col, color=colors[i], linewidth=2)

plt.title(title, fontsize=14, fontweight='bold')
plt.xlabel(x_col)
plt.ylabel('数值')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.6)
plt.xticks(rotation=45)
plt.tight_layout()

filename = f"{title}_{datetime.now().strftime('%Y%m%d')}.png"
plt.savefig(filename, dpi=150)
print(f"✅ 已保存: {filename}")
plt.show()

def create_pie_chart(self, labels_col, values_col, title="饼图"):
"""饼图"""
if self.data is None:
print("⚠️ 请先加载数据")
return

plt.figure(figsize=(10, 8))

# 检查数据是否有效
if self.data[values_col].sum() <= 0:
print("⚠️ 数据无效,无法绘制饼图")
return

colors = plt.cm.Set3(np.linspace(0, 1, len(self.data)))

wedges, texts, autotexts = plt.pie(
self.data[values_col],
labels=self.data[labels_col],
autopct='%1.1f%%',
colors=colors,
startangle=140,
textprops={'fontsize': 10}
)

for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontweight('bold')

plt.title(title, fontsize=14, fontweight='bold')
plt.axis('equal')
plt.tight_layout()

filename = f"{title}_{datetime.now().strftime('%Y%m%d')}.png"
plt.savefig(filename, dpi=150)
print(f"✅ 已保存: {filename}")
plt.show()

def create_report(self, charts_config, title="数据报告"):
"""生成多图表报告"""
n = len(charts_config)
cols = 2
rows = (n + 1) // 2

fig, axes = plt.subplots(rows, cols, figsize=(15, rows*5))

if rows == 1:
axes = axes.reshape(1, -1)

axes = axes.flatten()

for i, config in enumerate(charts_config):
ax = axes[i]

if config['type'] == 'bar':
ax.bar(self.data[config['x']], self.data[config['y']],
color=config.get('color', '#4CAF50'))
ax.set_title(config.get('title', ''), fontweight='bold')
ax.set_ylabel(config['y'])
ax.grid(axis='y', linestyle='--', alpha=0.7)

elif config['type'] == 'line':
for y_col in config['y']:
ax.plot(self.data[config['x']], self.data[y_col],
marker='o', label=y_col)
ax.set_title(config.get('title', ''), fontweight='bold')
ax.legend()
ax.grid(True, linestyle='--', alpha=0.6)

elif config['type'] == 'pie':
ax.pie(self.data[config['y']], labels=self.data[config['x']],
autopct='%1.1f%%')
ax.set_title(config.get('title', ''), fontweight='bold')
ax.axis('equal')

else:
ax.text(0.5, 0.5, '未知图表类型', ha='center', va='center')

ax.set_xlabel(config['x'])

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

fig.suptitle(title, fontsize=16, fontweight='bold', y=0.98)
plt.tight_layout(rect=[0, 0, 1, 0.96])

filename = f"{title}_{datetime.now().strftime('%Y%m%d')}.png"
plt.savefig(filename, dpi=300)
print(f"✅ 报告已保存: {filename} (高清)")
plt.show()

def main():
"""主菜单"""
visualizer = FinanceVisualizer()

while True:
print("\n" + "=" * 50)
print("财务数据可视化工具")
print("=" * 50)
print("1. 加载CSV数据")
print("2. 创建柱状图")
print("3. 创建折线图")
print("4. 创建饼图")
print("5. 生成综合报告")
print("6. 退出")
print("=" * 50)

choice = input("请选择: ").strip()

if choice == "1":
filepath = input("CSV文件路径: ").strip()
visualizer.load_data(filepath)

elif choice == "2":
if visualizer.data is not None:
x = input("X轴列名: ").strip()
y = input("Y轴列名: ").strip()
title = input("图表标题: ").strip() or "柱状图"
visualizer.create_bar_chart(x, y, title)
else:
print("⚠️ 请先加载数据")

elif choice == "3":
if visualizer.data is not None:
x = input("X轴列名: ").strip()
y = input("Y轴列名(多个用逗号分隔): ").strip().split(',')
title = input("图表标题: ").strip() or "折线图"
visualizer.create_line_chart(x, y, title)
else:
print("⚠️ 请先加载数据")

elif choice == "4":
if visualizer.data is not None:
labels = input("标签列名: ").strip()
values = input("数值列名: ").strip()
title = input("图表标题: ").strip() or "饼图"
visualizer.create_pie_chart(labels, values, title)
else:
print("⚠️ 请先加载数据")

elif choice == "5":
if visualizer.data is not None:
print("\n请配置报告(输入图表数量):")
n = int(input("图表数量: ").strip())

configs = []
for i in range(n):
print(f"\n图表 {i+1} 配置:")
t = input(" 类型(bar/line/pie): ").strip()
x = input(" X轴列名: ").strip()
y = input(" Y轴列名: ").strip()
title = input(" 图表标题: ").strip()

configs.append({
'type': t,
'x': x,
'y': y if t == 'pie' else [y],
'title': title
})

title = input("总标题: ").strip() or "数据报告"
visualizer.create_report(configs, title)
else:
print("⚠️ 请先加载数据")

elif choice == "6":
print("👋 再见!")
break

else:
print("请输入1-6!")

if __name__ == "__main__":
main()

八、知识点加油站

8.1 matplotlib核心组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"""
Figure: 整个画布
Axes: 坐标系(子图)
Axis: 坐标轴
Artist: 所有可见元素(线、文字、点)

常用设置:
plt.figure(figsize=(宽, 高)) # 创建画布
plt.title() # 标题
plt.xlabel() / plt.ylabel() # 坐标轴标签
plt.xticks() / plt.yticks() # 刻度标签
plt.legend() # 图例
plt.grid() # 网格线
plt.savefig() # 保存
plt.show() # 显示
"""

8.2 颜色代码参考

颜色代码适用场景
财务蓝#1f77b4主数据
警告橙#ff7f0e异常值
成功绿#2ca02c增长/正向
危险红#d62728亏损/警告
中性灰#7f7f7f辅助线

九、总结

  • ✅ 柱状图(对比不同类别)
  • ✅ 折线图(展示时间趋势)
  • ✅ 饼图(分析占比结构)
  • ✅ 多图表组合报告
  • ✅ 图表美化和保存

下篇预告

第19篇《面向对象编程》——理解“类”和“对象”,代码也能像搭积木一样组合!


🤖 Powered by Kimi K2 Thinking 💻 内容经葵葵🌻审核与修改