数据可视化基础知识及python作图

1,602 阅读12分钟

1、数据可视化的概念与规范

1.1、什么是数据可视化

数据可视化是指利用统计图(表)对数据进行展示,目的是寻找数据中的规律,挖掘数据背后的价值。 斯图尔特卡德(Stuart K. Card)提出经典的数据可视化分析流程:

graph TD
数据分析 --> 布局和视觉编码 --> 渲染展示

好的数据可视化至少满足三个标准:准确、有效、简洁

以下是常用的统计图适用的数据类型:

  • 定性数据:柱状图、条形图、饼图、环形图、南丁格尔玫瑰图。
  • 定量数据:直方图、箱线图。
  • 时间序列数据:折线图、径向热力图。
  • 两个定量数据的关系图:散点图。
  • 多个定量数据的关系:相关矩阵图、平行坐标图、雷达图。

可视化的目标绝不是炫酷的统计图和复杂的展示,而是在数据挖掘规律的同时,尽量用最简单、最简洁的统计图来展示数据。

1.2、数据可视化规范

数据可视化主要包括统计图和统计表。一个规范的统计图应该包含以下要素:

  1. 统计图本身要选择要恰当,能够使用正确的统计图展示数据。避免出现不必要的标签、背景等。
  2. 统计图的横轴和纵轴要标注清楚,包括变量的单位等。
  3. 统计图要有图标题,标题一般在图的下方。标题要准确,并且有标号。
  4. 统计图的比例要协调。配色要简洁,尽量避免出现过多的颜色。在很多统计图中(如热图),颜色有一定的含义,切勿乱用。
  5. 对统计图要有适当的评述,尤其在报告中使用统计图。

统计表也要遵循一定的规范。在这里我们以频数频率表为例来说明制表的规范:

  1. 统计表由行和列组成,行列的标题需要准确。
  2. 统计表的内容大都由数字组成,如果是小数要保留相同的位数。
  3. 统计表要有表标题,一般位于表的上方。标题要准确,并且有标号。
  4. 统计表的形式以三线表为主,避免添加过多的线条导致统计表过于混乱。
  5. 与统计图一样,报告中需要对统计表进行一定的解读。

2、单变量的数据可视化

单变量数据可以简单划分为离散型数据和连续型数据。针对离散型数据:我们学习柱状图和饼图;针对连续型数据,我们学习直方图和箱线图;针对时间序列数据,学习折线图。数据可视化的工具有许多,这里有一篇文章总结的比较全面。

2.1、柱状图与条形图

在这篇文章中,我们使用Python的Matplotlib库来画图。

以下是柱状图和条形图对应的代码:

a. 柱状图

import matplotlib.pyplot as plt

# 数据
categories = ['A', 'B', 'C', 'D', 'E']
values = [23, 45, 56, 78, 90]

# 自定义纵坐标间距
y_ticks = range(0, 101, 20)

# 自定义颜色
#bar_color = bar_color = (135/255, 206/255, 250/255)  # RGB(135, 206, 250)

# 单独为每个柱子设置不同的颜色
bar_colors = [(135/255, 206/255, 250/255) if category != 'B' else 'darkblue' for category in categories]

# 绘图
plt.bar(categories, values, color = bar_colors)

# 自定义横坐标和纵坐标的名称
plt.xlabel('title_x')
plt.ylabel('title_y')

# 设置纵坐标间距
plt.yticks(y_ticks)

# 显示图形
plt.show()

image.png

b. 条形图

import matplotlib.pyplot as plt

# 数据
categories = ['A', 'B', 'C', 'D', 'E']
values = [23, 45, 56, 78, 90]

x_ticks = range(0, 101, 20)
# 自定义柱状图颜色
bar_colors = ['skyblue' if category != 'B' else 'darkblue' for category in categories]

# 绘图
plt.barh(categories, values, color=bar_colors)

# 自定义横坐标和纵坐标的名称
plt.xlabel('title_x')
plt.ylabel('title_y')

plt.xticks(x_ticks)
# 显示图形
plt.show()

image.png

2.2、饼图、环形图与南丁格尔玫瑰图

饼图一般用来展示占比。饼图主要利用角度或者面积来反映占比情况。人对于图形的接受程度从易到难分别是点、线、面积、体积。柱状图是利用柱高来反映频数,而饼图是利用扇形面积来反映占比情况。效果比较而言,柱状图更加直观。除特殊需要,尽量避免使用立体图。 以下是饼图的代码:

a. 饼图

import matplotlib.pyplot as plt

# 数据
sizes = [30, 20, 25, 15, 10]
labels = ['A', 'B', 'C', 'D', 'E']

# 自定义颜色
colors = ['skyblue'] * len(labels)

# 添加每个扇形之间的空隙
explode = [0.02] * len(labels)

# 绘图
plt.pie(sizes, labels=labels, colors=colors, explode=explode, autopct='%1.1f%%', textprops={'fontsize': 12})

# 添加标题
plt.title('title')

# 显示图形
plt.show()

image.png

b. 环形图

import matplotlib.pyplot as plt

# 数据
sizes = [30, 20, 25, 15, 10]
labels = ['A', 'B', 'C', 'D', 'E']

# 自定义颜色
colors = ['skyblue'] * len(labels)

# 添加每个扇形之间的空隙
explode = [0.02] * len(labels)

# 绘图
plt.pie(sizes, labels=labels, colors=colors, explode=explode, autopct='', textprops={'fontsize': 12}, wedgeprops={'width': 0.35})

# 添加标题
plt.title('title')

# 显示图形
plt.show()

image.png

分享一种更加炫酷的环形图:

from math import pi
import numpy as np
from matplotlib.patches import Patch
from matplotlib.lines import Line2D
fig, ax = plt.subplots(figsize=(6, 6))
ax = plt.subplot(projection='polar')
data = [82, 75, 91]
startangle = 90
colors = ['#4393E5', '#43BAE5', '#7AE6EA']
xs = [(i * pi *2)/ 100 for i in data]
ys = [-0.2, 1, 2.2]
left = (startangle * pi *2)/ 360 #this is to control where the bar starts
# plot bars and points at the end to make them round
for i, x in enumerate(xs):
    ax.barh(ys[i], x, left=left, height=1, color=colors[i])
    ax.scatter(x+left, ys[i], s=350, color=colors[i], zorder=2)
    ax.scatter(left, ys[i], s=350, color=colors[i], zorder=2)
    
plt.ylim(-4, 4)
# legend
legend_elements = [Line2D([0], [0], marker='o', color='w', label='Group A', markerfacecolor='#4393E5', markersize=10),
                  Line2D([0], [0], marker='o', color='w', label='Group B', markerfacecolor='#43BAE5', markersize=10),
                  Line2D([0], [0], marker='o', color='w', label='Group C', markerfacecolor='#7AE6EA', markersize=10)]
ax.legend(handles=legend_elements, loc='center', frameon=False)
# clear ticks, grids, spines
plt.xticks([])
plt.yticks([])
ax.spines.clear()
plt.show()

image.png

c. 南丁格尔玫瑰图

import matplotlib.pyplot as plt
import numpy as np

# 数据
categories = ['A', 'B', 'C', 'D', 'E']
values = [20, 30, 15, 10, 25]  # 数据

# 构建角度
num_categories = len(categories)
angles = np.linspace(0, 2 * np.pi, num_categories, endpoint=False)

# 将角度闭合
angles = np.concatenate((angles, [angles[0]]))

# 构建半径
radius = np.array(values)
radius = np.concatenate((radius, [radius[0]]))  # 将半径闭合

# 计算百分比
total = sum(values)
percentages = [f'{(value / total) * 100:.1f}%' for value in values]

# 绘图
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
bars = ax.bar(angles, radius, width=0.99, color='skyblue', alpha=0.75)

# 添加标签
ax.set_yticklabels([])  # 隐藏半径刻度标签
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)

# 添加标题
plt.title('title')

# 显示对应的百分比
for angle, value, percentage in zip(angles[:-1], radius[:-1], percentages):
    ax.text(angle, value, percentage, ha='center', va='center')

# 隐藏网格线
#ax.grid(False)

#只隐藏横线
ax.yaxis.grid(False)

# 显示图形
plt.show()

image.png

2.3、直方图

直方图是针对连续型数据绘制的一种统计图。

a. 频率直方图

import matplotlib.pyplot as plt
import numpy as np

# 生成随机数据
data = np.random.normal(loc=0, scale=1, size=1000)

# 绘制直方图
# 数据、箱数、颜色、边框颜色、间距
plt.hist(data, bins=30, color='skyblue', edgecolor='black', rwidth=0.8)

# 添加标题和标签
plt.title('Histogram of Random Data')
plt.xlabel('Value')
plt.ylabel('Frequency')

# 显示图形
plt.show()

image.png

b. 概率直方图

import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import gaussian_kde

# 生成随机数据
data = np.random.normal(loc=0, scale=1, size=1000)

# 绘制直方图
plt.hist(data, bins=30, density=True, color='skyblue', edgecolor='black', alpha=0.7, rwidth=0.8)  # 设置 density=True 参数

# 计算核密度估计
kde = gaussian_kde(data)
xmin, xmax = plt.xlim()
x = np.linspace(xmin, xmax, 100)
p = kde(x)

# 绘制概率密度曲线
plt.plot(x, p, 'k', linewidth=2)

# 添加标题和标签
plt.title('Histogram with Probability Density Function')
plt.xlabel('Value')
plt.ylabel('Probability Density')

# 显示图形
plt.show()

image.png

2.4、箱线图

箱线图在一定程度上可以反映数据的波动水平。箱线图中间一条横线是整组数据的中位线,代表数据的平均水平。箱子的上下两条线分别是上四分位数和下四分位数。箱线图最上方和最下方的线,有时是最大值和最小值,但有时候不是,因为可能会有离群值的出现。通常统计软件在绘制箱线图的时候,会做出一个判断,如果观测值大于 D=中位数+c×四分位数间距D=中位数+c\times四分位数间距,那么最上方一条线的取值就是DD,如果小于,则最上方一条线的取值是数据的最大值。最下方一条线同理。

a. 单个箱线图

import matplotlib.pyplot as plt
import numpy as np

# 生成随机数据
data = np.random.normal(loc=0, scale=1, size=100)

# 绘制箱线图
plt.boxplot(data)

# 添加标题和标签
plt.title('Boxplot of Random Data')
plt.xlabel('Data')

# 显示图形
plt.show()

image.png

b. 多个箱线图(1)

import matplotlib.pyplot as plt
import numpy as np

# 生成随机数据
data1 = np.random.normal(loc=0, scale=1, size=100)
data2 = np.random.normal(loc=1, scale=1, size=100)
data3 = np.random.normal(loc=0, scale=2, size=100)
data4 = np.random.normal(loc=1, scale=2, size=100)

# 绘制箱线图
plt.boxplot([data1, data2, data3, data4])

# 添加标题和标签
plt.title('Boxplot Comparison of Four Data Sets')
plt.xlabel('Data Set')
plt.ylabel('Value')

# 设置x轴刻度标签
plt.xticks([1, 2, 3, 4], ['Data Set 1', 'Data Set 2', 'Data Set 3', 'Data Set 4'])

# 显示图形
plt.show()

image.png

c. 多个箱线图(2)

import matplotlib.pyplot as plt
import numpy as np

# 创建四组随机数据
data1 = np.random.randn(100, 4)
data2 = np.random.randn(100, 4)

# 设置箱线图的位置
positions1 = np.array([1, 2, 3, 4])
positions2 = np.array([1.2, 2.2, 3.2, 4.2])  # 加上0.2的偏移量,防止重叠

# 绘制箱线图,并设置颜色
plt.boxplot(data1, positions=positions1, widths=0.2, boxprops=dict(color="blue"))
plt.boxplot(data2, positions=positions2, widths=0.2, boxprops=dict(color="red"))

# 设置横坐标标签
plt.xticks([1.1, 2.1, 3.1, 4.1], ['10', '11', '12', '13'])
# 设置横纵坐标名称
plt.xlabel('LogN')
plt.ylabel('Frobenius Error')

# 显示图形
plt.show()

image.png

2.5、折线图与径向热力图

折线图是针对时间序列数据绘制的统计图。介绍三种与时间相关的数据类型:

  1. 横截面数据:指在某一个时间点上,在多个对象上采集到的数据,比如2024年5月10日,全国所有城市的空气质量指数。
  2. 时间序列数据:指在一些时间点上,针对某一个对象采集的数据,反映事物随时间的变化,比如2024年4月10日到5月10日,银川市每天的空气质量指数。
  3. 面板数据:指在多个时间点上,对于同一批对象采集的数据,比如2024年1月1日至5月10日,全国所有城市每天的空气质量指数。

针对这大类数据的分析问题,有个专门的方向叫做空间统计学

折线图的横轴一般为时间,所以是带有顺序的,纵轴是某一指标的取值。

a. 折线图

import matplotlib.pyplot as plt
import pandas as pd

# 模拟时间和空气质量指数数据
dates = pd.date_range('2024-01-01', periods=10)
air_quality = [38, 35, 40, 45, 42, 45, 40, 35, 29, 45]

# 绘制折线图
plt.plot(dates, air_quality, marker='o', linestyle='-')

# 添加标题和标签
plt.title('Air Quality Index Over Time')
plt.xlabel('Time')
plt.ylabel('Air Quality Index')

# 设置纵轴取值范围从0开始
plt.ylim(0, 60)

# 设置x轴日期格式
plt.xticks(rotation=45, ha='right')

# 显示图形
plt.tight_layout()
plt.show()

image.png

b. 折线图与柱状图组合

import matplotlib.pyplot as plt
import pandas as pd

# 模拟时间和空气质量指数数据
dates = pd.date_range('2024-01-01', periods=10)
air_quality = [30, 35, 40, 45, 50, 45, 40, 35, 30, 25]
temperature = [10, 12, 15, 18, 20, 22, 25, 23, 20, 18]

# 创建第一个轴
fig, ax1 = plt.subplots()

# 绘制空气质量的柱状图
ax1.bar(dates, air_quality, color='b', alpha=0.5)

# 添加第一个轴的标签
ax1.set_xlabel('Time')
ax1.set_ylabel('Air Quality Index', color='b')
ax1.set_ylim(0, 60)
# 创建第二个轴
ax2 = ax1.twinx()

# 绘制温度的折线图
ax2.plot(dates, temperature, marker='s', linestyle='-', color='skyblue')

# 添加第二个轴的标签
ax2.set_ylabel('Temperature (C)', color='skyblue')

# 添加标题
plt.title('Air Quality Index (Bar) and Temperature (Line) Over Time')

# 设置纵轴取值范围从0开始
ax2.set_ylim(0, 30)

# 设置x轴日期格式
plt.xticks(ticks=dates, labels=[date.strftime('%d') for date in dates], rotation=45, ha='right')
# 显示图形
plt.tight_layout()
plt.show()

image.png

径向热力图常用于展示具有周期性的时间序列数据。暂时没有找到python画该图的简便好方法。用书上的例图替代一下。

375c95ad5d1c2942569496d74119b4f.jpg

3、变量间关系的数据可视化

3.1、2个定性变量

面对2个定性变量时,可以绘制柱状图展示其频数。

a. 堆叠柱状图

import matplotlib.pyplot as plt

# 数据
categories = ['A', 'B', 'C', 'D']
values1 = [20, 35, 30, 35]
values2 = [25, 32, 34, 20]

# 绘图
plt.bar(categories, values1, label='Group 1')
plt.bar(categories, values2, bottom=values1, label='Group 2')

# 添加图例
plt.legend()

# 添加标题和标签
plt.title('Stacked Bar Chart')
plt.xlabel('Categories')
plt.ylabel('Values')

# 显示图形
plt.show()

image.png

b.分开展示的柱状图

import numpy as np
import matplotlib.pyplot as plt

# 数据
categories = ['A', 'B', 'C', 'D']
group1_values = [20, 35, 30, 35]
group2_values = [25, 32, 34, 20]

# 设置柱子的宽度
bar_width = 0.35
index = np.arange(len(categories))

# 绘图
plt.bar(index - bar_width/2, group1_values, bar_width, label='Group 1')
plt.bar(index + bar_width/2, group2_values, bar_width, label='Group 2')

# 添加图例
plt.legend()

# 添加标题和标签
plt.title('Side-by-Side Bar Chart')
plt.xlabel('Categories')
plt.ylabel('Values')

# 设置x轴刻度
plt.xticks(index, categories)

# 显示图形
plt.show()

image.png

3.2、1个定性变量与1个定量变量

此种情况分组箱线图是最为有效的。

3.3、2个定量变量

散点图是用于展示两个定量变量的一种常用统计图。

import matplotlib.pyplot as plt
import numpy as np

# 生成随机数据
x = np.random.rand(100)
y = np.random.rand(100)

# 绘制散点图
plt.scatter(x, y)

# 添加标题和标签
plt.title('Scatter Plot')
plt.xlabel('X')
plt.ylabel('Y')

# 显示图形
plt.show()

image.png

3.4、多个定量变量

当变量的个数适中的时候,可以使用一组成对的散点图。

a.四个变量的成对散点图

import seaborn as sns
import pandas as pd
import numpy as np

# 生成随机数据
np.random.seed(0)
data = pd.DataFrame(np.random.randn(100, 4), columns=['A', 'B', 'C', 'D'])

# 绘制成对散点图
sns.pairplot(data)

# 显示图形
plt.show()

image.png

如果涉及多个定量数据,可以先计算每对变量的线性相关系数,然后画出热力图。

b. 热力图

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

# 生成随机数据
np.random.seed(0)
data = pd.DataFrame(np.random.randn(100, 10), columns=[f'Var{i}' for i in range(1, 11)])

# 计算相关系数矩阵
corr_matrix = data.corr()

# 绘制热力图
sns.heatmap(corr_matrix, annot=False, cmap='coolwarm', fmt=".2f")

# 添加标题
plt.title('Correlation Heatmap of Ten Variables')

# 显示图形
plt.show()

image.png

雷达图可以探索多变量数据的分布规律和模式。

c.雷达图

import numpy as np
import matplotlib.pyplot as plt

# 样本数和变量数
num_samples = 3
num_vars = 6

# 生成示例数据
data = np.random.randint(0, 100, size=(num_samples, num_vars))

# 角度
angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
angles += angles[:1]

# 绘图
fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))

# 颜色映射
cmap = plt.get_cmap('Purples')

# 遍历每个样本并绘制雷达线
for i in range(num_samples):
    stats = data[i]
    stats = np.concatenate((stats, [stats[0]]))
    ax.plot(angles, stats, linewidth=1, linestyle='solid', color=cmap(i / num_samples))
    ax.fill(angles, stats, color=cmap(i / num_samples), alpha=0.3)

# 设置标签名称
ax.set_xticks(angles[:-1])
ax.set_xticklabels(['A', 'B', 'C', 'D', 'E', 'F'])

# 添加标题
plt.title('Radar Chart')

# 显示图形
plt.show()

image.png

3.5、其它视图

参考网站:

The Python Graph Gallery

The-Python-Graph-Gallery (github)

参考书籍:

统计学基础(郭建华)