绘图和可视化

绘图时,在ipython里运行

1
2
>>> %matplotlib
Using matplotlib backend: TkAgg

可以直接绘制图形,不然每次绘制图形最后必须调用plt.show()才能看到图形。

本文里使用的CSV测试数据可以从这里下载

matplotlib简单入门

关于matplotlib的使用入门,可以参考:

matplotlib教程

使用pandas和seaborn绘图

数据可视化方面, matplotlib算是比较底层的工具了。 pandas也简单内置了一些常用的画图方法。seaborn则是另一个专门用于数据统计的可视化库。

注意

在代码中引入searborn会修改matplotlib默认的画图颜色和样式,即便不使用searborn画图,也可以引入它来提高画图的美化效果。

线性图

SeriesDataFrame都有plot方法来绘制一些基本图形,默认调用plot方法会绘制线性图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
>>> s = pd.Series(np.random.randn(10).cumsum(), index=np.arange(0, 100, 10))

>>> s
0     0.471154
10    0.037093
20   -0.130165
30   -0.907087
40   -0.495938
50    0.525166
60    0.822416
70    0.667376
80    1.488170
90    2.041607
dtype: float64

>>> s.plot()

SeriesPlot

默认使用Seriesindex作为x轴。也可以通过参数use_index=False来设置不用Seriesindex作为x轴。其它可以定制的参数如下:

Series Plot Method

DataFrameplot方法会把每一列绘制成一条线,并且自动绘制图例

1
2
3
4
5
6
>>> df = pd.DataFrame(np.random.randn(10, 4).cumsum(0),
...                   columns=['A', 'B', 'C', 'D'],
...                    index=np.arange(0, 100, 10))
...

>>> df.plot()

DataFrame plot

DataFrameplot方法常用的参数如下: DataFrame plot method

条形图(Bar Plots)

plot.bar()plot.barh()可以用来绘垂直和水平的制柱状图。

大多数pandas的plot方法都可以接受ax参数(一个matplotlib subplot对象),让我们更方便在表格样式中绘制子图。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> fig, axes = plt.subplots(2, 1)

>>> data = pd.Series(np.random.rand(16), index=list('abcdefghijklmnop'))

>>> data.plot.bar(ax=axes[0], color='k', alpha=0.7)
<matplotlib.axes._subplots.AxesSubplot at 0x1076809e8>

>>> data.plot.barh(ax=axes[1], color='k', alpha=0.7)
<matplotlib.axes._subplots.AxesSubplot at 0x10be498d0>

Series plot bar

DataFrame在做柱状图时会自动按照列分组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
>>> df = pd.DataFrame(np.random.rand(6, 4),
...                   index=['one', 'two', 'three', 'four', 'five', 'six'],
...                   columns=pd.Index(['A', 'B', 'C', 'D'], name='Genus'))
...

>>> df
Genus         A         B         C         D
one    0.682972  0.387897  0.052612  0.249418
two    0.628619  0.415778  0.412352  0.455799
three  0.330864  0.472638  0.780744  0.606024
four   0.665816  0.172358  0.294163  0.038945
five   0.390196  0.354316  0.566566  0.592186
six    0.556651  0.721578  0.857398  0.939181

>>> df.plot.bar()

DataFrame plot bar

我们可以可以通过参数stacked=True来创建堆叠的柱状图

1
>>> df.plot.barh(stacked=True, alpha=0.5)

DataFrame plot barh

小贴士:

一个比较常见的关于Series绘制柱状图就是使用value_counts, 可以很直观的看到统计的数据

>>> s = pd.Series(np.random.randint(0, 10, 20))

>>> s
0     4
1     9
2     0
3     4
4     9
5     0
6     2
7     8
8     0
9     6
10    3
11    6
12    9
13    4
14    7
15    1
16    1
17    3
18    7
19    6

>>> s.value_counts().plot.bar()

让我们来看一个统计聚会人数多少和星期几的关系,

 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
>>> tips = pd.read_csv('examples/tips.csv')

# size: 聚会人数
# day: 星期几
>>> tips.head()
   total_bill   tip smoker  day    time  size
0       16.99  1.01     No  Sun  Dinner     2
1       10.34  1.66     No  Sun  Dinner     3
2       21.01  3.50     No  Sun  Dinner     3
3       23.68  3.31     No  Sun  Dinner     2
4       24.59  3.61     No  Sun  Dinner     4

# 可以得知聚会人数介于1-6之间
>>> tips['size'].unique()
array([2, 3, 4, 1, 6, 5])

>>> tips[tips['size'] == 1]
     total_bill   tip smoker   day    time  size
67         3.07  1.00    Yes   Sat  Dinner     1
82        10.07  1.83     No  Thur   Lunch     1
111        7.25  1.00     No   Sat  Dinner     1
222        8.58  1.92    Yes   Fri   Lunch     1

# 以星期为index, size为列统计数据
>>> party_counts = pd.crosstab(tips['day'], tips['size'])

>>> party_counts
size  1   2   3   4  5  6
day
Fri   1  16   1   1  0  0
Sat   2  53  18  13  1  0
Sun   0  39  15  18  3  1
Thur  1  48   4   5  1  3

# 排除1个人的情况
>>> party_counts = party_counts.loc[:, 2:5]

>>> party_counts
size   2   3   4  5
day
Fri   16   1   1  0
Sat   53  18  13  1
Sun   39  15  18  3
Thur  48   4   5  1

# 计算星期的总人数
>>> party_counts.sum(1)
day
Fri     18
Sat     85
Sun     75
Thur    58
dtype: int64

# 计算size占星期的百分比
>>> party_pcts = party_counts.div(party_counts.sum(1), axis=0)

>>> party_pcts
size         2         3         4         5
day
Fri   0.888889  0.055556  0.055556  0.000000
Sat   0.623529  0.211765  0.152941  0.011765
Sun   0.520000  0.200000  0.240000  0.040000
Thur  0.827586  0.068966  0.086207  0.017241

party_pcts.plot.bar()

从上图可以看出,周末的时候,聚会人数(size)多的是在周末。

当数据需要聚合和汇总时,使用seaborn更方便一点。让我们来计算每天的小费情况

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
>>> import seaborn as sns

>>> tips['tip_pct'] = tips['tip'] / (tips['total_bill'] - tips['tip'])

>>> tips.head()
   total_bill   tip smoker  day    time  size   tip_pct
0       16.99  1.01     No  Sun  Dinner     2  0.063204
1       10.34  1.66     No  Sun  Dinner     3  0.191244
2       21.01  3.50     No  Sun  Dinner     3  0.199886
3       23.68  3.31     No  Sun  Dinner     2  0.162494
4       24.59  3.61     No  Sun  Dinner     4  0.172069

>>> sns.barplot(x='tip_pct', y='day', data=tips, orient='h')

我们还可以通过hue参数来让它来添加额外的分类

1
>>> sns.barplot(x='tip_pct', y='day', hue='time', data=tips, orient='h')

我们还可以改变searborn的绘图样式

1
>>> sns.set(style="whitegrid")

柱状图和密度图(Histograms and Density Plots)

使用SeriesDataFrameplot函数

首先我们看一下小费百分比的分布区间

  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
>>> pd.cut(tips['tip_pct'], 50)
0      (0.0345, 0.0853]
1         (0.182, 0.23]
2         (0.182, 0.23]
3        (0.134, 0.182]
4        (0.134, 0.182]
5         (0.182, 0.23]
6        (0.278, 0.327]
7       (0.0853, 0.134]
8        (0.134, 0.182]
9        (0.278, 0.327]
10        (0.182, 0.23]
11       (0.134, 0.182]
12      (0.0853, 0.134]
13        (0.182, 0.23]
14        (0.23, 0.278]
15        (0.182, 0.23]
16        (0.182, 0.23]
17       (0.278, 0.327]
18        (0.23, 0.278]
19        (0.182, 0.23]
20       (0.278, 0.327]
21       (0.134, 0.182]
22       (0.134, 0.182]
23        (0.23, 0.278]
24        (0.182, 0.23]
25       (0.134, 0.182]
26       (0.134, 0.182]
27        (0.182, 0.23]
28        (0.23, 0.278]
29       (0.134, 0.182]
             ...
214      (0.278, 0.327]
215     (0.0853, 0.134]
216     (0.0853, 0.134]
217      (0.134, 0.182]
218       (0.182, 0.23]
219     (0.0853, 0.134]
220       (0.182, 0.23]
221      (0.327, 0.375]
222      (0.278, 0.327]
223       (0.23, 0.278]
224     (0.0853, 0.134]
225      (0.134, 0.182]
226       (0.23, 0.278]
227      (0.134, 0.182]
228       (0.23, 0.278]
229      (0.134, 0.182]
230     (0.0853, 0.134]
231       (0.23, 0.278]
232      (0.375, 0.423]
233      (0.134, 0.182]
234       (0.23, 0.278]
235      (0.134, 0.182]
236     (0.0853, 0.134]
237    (0.0345, 0.0853]
238      (0.134, 0.182]
239       (0.23, 0.278]
240    (0.0345, 0.0853]
241     (0.0853, 0.134]
242     (0.0853, 0.134]
243       (0.182, 0.23]
Name: tip_pct, Length: 244, dtype: category
Categories (50, interval[float64]): [(0.0345, 0.0853] < (0.0853, 0.134] < (0.134, 0.182] <
                                     (0.182, 0.23] ... (2.259, 2.307] < (2.307, 2.356] <
                                     (2.356, 2.404] < (2.404, 2.452]]

>>> pd.value_counts(bins)
(0.134, 0.182]      76
(0.182, 0.23]       57
(0.23, 0.278]       41
(0.0853, 0.134]     33
(0.278, 0.327]      15
(0.0345, 0.0853]    12
(0.327, 0.375]       4
(0.375, 0.423]       3
(0.713, 0.762]       1
(0.472, 0.52]        1
(2.404, 2.452]       1
(1.873, 1.921]       0
(1.969, 2.018]       0
(0.81, 0.858]        0
(0.762, 0.81]        0
(1.921, 1.969]       0
(0.665, 0.713]       0
(0.617, 0.665]       0
(0.568, 0.617]       0
(0.52, 0.568]        0
(0.423, 0.472]       0
(0.907, 0.955]       0
(2.018, 2.066]       0
(2.066, 2.114]       0
(2.114, 2.163]       0
(2.163, 2.211]       0
(2.211, 2.259]       0
(2.259, 2.307]       0
(2.307, 2.356]       0
(0.858, 0.907]       0
(0.955, 1.003]       0
(1.824, 1.873]       0
(1.438, 1.486]       0
(1.776, 1.824]       0
(1.728, 1.776]       0
(1.679, 1.728]       0
(1.631, 1.679]       0
(1.583, 1.631]       0
(1.535, 1.583]       0
(1.486, 1.535]       0
(1.39, 1.438]        0
(1.003, 1.051]       0
(1.341, 1.39]        0
(1.293, 1.341]       0
(1.245, 1.293]       0
(2.356, 2.404]       0
(1.148, 1.196]       0
(1.1, 1.148]         0
(1.051, 1.1]         0
(1.196, 1.245]       0
Name: tip_pct, dtype: int64

从上面可以看出,小费百分比在(0.134, 0.182]里的最多,下面的图形也能反应这一点

1
2
>>> tips['tip_pct'].plot.hist(bins=50)
<matplotlib.axes._subplots.AxesSubplot at 0x12607ce10>

1
2
>>> tips['tip_pct'].plot.density()
<matplotlib.axes._subplots.AxesSubplot at 0x12607ce10>

使用searborndistplot方法更简便,可以同时在一个图里绘制柱状图和密度曲线

1
2
3
4
5
6
7
8
>>> comp1 = np.random.normal(0, 1, size=200)

>>> comp2 = np.random.normal(10, 2, size=200)

>>> values = pd.Series(np.concatenate([comp1, comp2]))

>>> sns.distplot(values, bins=100, color='k')
<matplotlib.axes._subplots.AxesSubplot at 0x120e1c588>

散点图或点图(Scatter or Point Plots)

使用seabornregplot函数,不仅可以画散点图,还可以绘制一个线性回归线

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
>>> macro = pd.read_csv('examples/macrodata.csv')
>>> data = macro[['cpi', 'm1', 'tbilrate', 'unemp']]

>>> trans_data = np.log(data).diff().dropna()

>>> trans_data[-5:]
          cpi        m1  tbilrate     unemp
198 -0.007904  0.045361 -0.396881  0.105361
199 -0.021979  0.066753 -2.277267  0.139762
200  0.002340  0.010286  0.606136  0.160343
201  0.008419  0.037461 -0.200671  0.127339
202  0.008894  0.012202 -0.405465  0.042560

# 以m1为x轴,unemp为y轴
>>> sns.regplot('m1', 'unemp', data=trans_data)
<matplotlib.axes._subplots.AxesSubplot at 0x116c026d8>

>>> plt.title('Changes in log %s versus log %s' % ('m1', 'unemp'))
Text(0.5,1,'Changes in log m1 versus log unemp')

使用pairplot函数可以绘制散点图矩阵

1
2
>>> sns.pairplot(trans_data, diag_kind='kde', plot_kws={'alpha': 0.2})
<seaborn.axisgrid.PairGrid at 0x12760e0b8>

多面网格和类别数据(Facet Grids and Categorical Data)

如果遇到数据集需要额外的分组维度,可以利用多面网格。seaborn有一个有用的内建函数factorplot

day/time/smoker来展示小费的百分比

1
2
3
4
5
6
>>> tips = pd.read_csv('examples/tips.csv')

>>> tips['tip_pct'] = tips['tip'] / (tips['total_bill'] - tips['tip'])

>>> sns.factorplot(x='day', y='tip_pct', hue='time', col='smoker', kind='bar', data=tips[tips.tip_pct < 1])
<seaborn.axisgrid.FacetGrid at 0x12724b390>

除了按照time作为bar分组之外,我们还可以通过添加参数row=time添加额外的面

1
2
>>> sns.factorplot(x='day', y='tip_pct', row='time', col='smoker', kind='bar', data=tips[tips.tip_pct < 1])
<seaborn.axisgrid.FacetGrid at 0x1261bc6d8>

factorplot还支持一些其它的绘图参数, 如绘制box图

1
2
>>> sns.factorplot(x='tip_pct', y='day', kind='box', data=tips[tips.tip_pct < 0.5])
<seaborn.axisgrid.FacetGrid at 0x127caeac8>

其它的python可视化工具

除了上面提到的之外,还有许多开源的可视化工具值得我们去探索

对于制作用于打印或网页的静态图形,作者推荐使用matplotlib以及pandas和seaborn这样的工具。对于其他的数据可视化需求,去学一个有用的工具可能会有帮助。