可视化数值分布
数据科学家研究的许多变量是“定量的”或“数值型的”。它们的取值是可以进行算术运算的数字。我们已经看到的例子包括书籍章节中句号的数量、电影的票房收入以及美国人口的年龄。
分类变量的取值可以被赋予数值编码,但这并不使其成为定量变量。在我们按年龄组分析人口普查数据的例子中,分类变量 SEX 的数值编码为 1 表示“男性”,2 表示“女性”,0 表示两组 1 和 2 的合计。虽然0、1和2是数字,但在这种情况下,用2减1,或取0、1、2的平均值,或对这三个值进行其他算术运算都是没有意义的。SEX 是一个分类变量,即使其取值被赋予了数字编码。
from datascience import *
import numpy as np
path_data = '../../../assets/data/'
%matplotlib inline
import matplotlib.pyplot as plots
plots.style.use('fivethirtyeight')
对于我们的主要示例,我们将回到之前在可视化分类数据时研究过的数据集。那就是表格 top,其中包含美国有史以来最卖座电影的数据(截至2017年)。为方便起见,这里再次描述该表。
第一列包含电影名称。第二列包含制作该电影的工作室名称。第三列包含国内票房收入(美元),第四列包含按2016年票价计算的票房收入。第五列包含电影发行的年份。
列表中共有200部电影。以下是按 Gross 列中未经调整的票房收入排名的前十名。
top = Table.read_table(path_data + 'top_movies_2017.csv')
# Make the numbers in the Gross and Gross (Adjusted) columns look nicer:
top.set_format([2, 3], NumberFormatter)
Title | Studio | Gross | Gross (Adjusted) | Year
Gone with the Wind | MGM | 198,676,459 | 1,796,176,700 | 1939
Star Wars | Fox | 460,998,007 | 1,583,483,200 | 1977
The Sound of Music | Fox | 158,671,368 | 1,266,072,700 | 1965
E.T.: The Extra-Terrestrial | Universal | 435,110,554 | 1,261,085,000 | 1982
Titanic | Paramount | 658,672,302 | 1,204,368,000 | 1997
The Ten Commandments | Paramount | 65,500,000 | 1,164,590,000 | 1956
Jaws | Universal | 260,000,000 | 1,138,620,700 | 1975
Doctor Zhivago | MGM | 111,721,910 | 1,103,564,200 | 1965
The Exorcist | Warner Brothers | 232,906,145 | 983,226,600 | 1973
Snow White and the Seven Dwarves | Disney | 184,925,486 | 969,010,000 | 1937
... (190 rows omitted)在本节中,我们将绘制 Gross (Adjusted) 列中数值变量的分布图。为简单起见,让我们创建一个包含所需信息的较小表格。由于三位数比九位数更容易处理,让我们以百万美元为单位来衡量 Adjusted Gross 收入。注意 np.round 如何用于在该列的每个条目中仅保留两位小数。
millions = top.select(0).with_columns('Adjusted Gross',
np.round(top.column(3)/1e6, 2))
millions
Title | Adjusted Gross
Gone with the Wind | 1796.18
Star Wars | 1583.48
The Sound of Music | 1266.07
E.T.: The Extra-Terrestrial | 1261.08
Titanic | 1204.37
The Ten Commandments | 1164.59
Jaws | 1138.62
Doctor Zhivago | 1103.56
The Exorcist | 983.23
Snow White and the Seven Dwarves | 969.01
... (190 rows omitted)数据分箱
观察定量变量 Adjusted Gross 的取值。由于数值测量得相当精细,每个单独取值上很可能只有一部电影。更有趣的是将数值分组到称为“箱”的区间中,并查看每个箱中有多少部电影。这个过程称为“分箱”。
箱中个体(即行)的计数可以使用 bin 方法计算,类似于分类数据中使用的 group 方法。bin 方法接受一个列标签或索引作为参数,以及一个可选参数,你可以在其中指定想要的箱。
结果是一个两列表格,包含每个箱中的行数。第一列列出箱的左端点(但请参见下面关于最后一个箱的说明)。
让我们尝试该方法并检查输出的细节。为了选择一些箱,我们将先查看 Adjusted Gross 的最小值和最大值。
adj_gross = millions.column('Adjusted Gross')
min(adj_gross), max(adj_gross)
(338.41, 1796.18)让我们尝试宽度为100的箱,从300到2000。你也可以做出其他选择。通常的做法是先从看似合理的地方开始,然后根据结果进行调整。
bin_counts = millions.bin('Adjusted Gross', bins=np.arange(300,2001,100))
bin_counts.show()
<IPython.core.display.HTML object>让我们检查第0列,即 bin 列。该列指定了每个箱的左端点,最后一行除外,如下所述。
由于箱将数轴分割成区间,它们是连续的。因此我们必须注意端点处的值。按照 Python 的惯例,除最后一个箱外,每个箱包含其左端点但不包含其右端点。
为了说明这一点,我们将使用符号 [a, b) 来表示包含所有大于等于 a 且严格小于 b 的值的箱。
要理解表格的第一行,你还必须查看第二行。这两行告诉我们,在箱 [300, 400) 中有68部电影。也就是说,有68部电影的调整后票房收入至少为3亿美元但低于4亿美元。
一般来说,Adjusted Gross count 列中的每个元素统计了所有 Adjusted Gross 值中大于等于 bin 中的值但小于 bin 中下一个值的数量。
最后一个箱: 注意最后一行的 bin 值2000。这不是任何箱的左端点,而是最后一个箱的右端点。这个箱与其他箱不同,它的形式为 [a, b],包含两个端点的数据。在我们的示例中这无关紧要,因为没有电影赚了20亿美元(即2000百万)。但分箱的这一方面很重要,以便在你希望箱正好以数据的最大值结束时加以注意。最后一个箱的所有计数出现在倒数第二行,最后一行的计数始终为零。
还有其他使用 bin 方法的方式。如果不指定任何箱,默认会在数据的最小值和最大值之间生成10个等宽的箱。这通常有助于快速了解分布情况,但箱的端点往往看起来不太美观。
millions.bin('Adjusted Gross').show()
<IPython.core.display.HTML object>你可以指定等宽箱的数量。例如,选项 bins=4 会生成4个等间距的箱。
millions.bin('Adjusted Gross', bins=4)
bin | Adjusted Gross count
338.41 | 177
702.852 | 15
1067.3 | 6
1431.74 | 2
1796.18 | 0但对于定量数据,箱不必等宽。我们稍后会看到一个不等宽箱的示例。
直方图
“直方图”是定量变量分布的可视化。它看起来很像条形图,但有一些重要的区别,我们将在本节中检查这些区别。首先,让我们绘制调整后收入的直方图。
hist 方法生成一列中数值的直方图。可选的 unit 参数用于两个轴上的标签。下面的直方图显示了调整后票房收入的分布,以百万2016年美元为单位。我们没有指定箱,因此 hist 在数据的最小值和最大值之间创建了10个等宽的箱。
millions.hist('Adjusted Gross', unit="Million Dollars")
A histogram with 'Adjusted Gross (Million Dollars)' on the x-xis and 'Percent per Million Dollars' on the y-axis. The graph has the highest bar on the left, x below 500 and y about 0.4. The next bar to the right is approximately half the first bar's height at a bit below 0.2. The remaining bars are all smaller and trail to the right until about x=1750.该图有两个数值轴。我们先快速看一下水平轴,然后仔细检查垂直轴。现在,只需注意“垂直轴不代表百分比”。
水平轴
虽然在这个数据集中,没有电影的票房收入恰好位于两个箱之间的边界上,但 hist 必须处理可能出现在边界上的值的情况。因此,hist 使用与 bin 方法相同的“端点约定”。箱包含其左端点的数据,但不包含其右端点的数据,最右边的箱除外,它包含两个端点的数据。
我们可以看到有10个箱(有些条形太矮而难以看到),并且它们都具有相同的宽度。我们还可以看到,没有电影的票房收入低于3亿美元;这是因为我们只考虑最卖座的电影。
要确切看到箱的端点位置有点困难。因此很难准确判断一个条形在哪里结束,下一个在哪里开始。
可选参数 bins 可与 hist 一起使用,以像 bin 方法那样精确指定箱的端点。我们将从设置 bins 中的数字为300、400、500等开始,以2000结束。
millions.hist('Adjusted Gross', bins=np.arange(300,2001,100), unit="Million Dollars")
A histogram with the same axes as before (Adjusted Gross and Percent per Million Dollars) with smaller bars. Again, the tallest bars are on the left hand side of the graph and the height of the bars decrease dramatically and are quite small by x=750.该图的水平轴更易于阅读。例如,即使没有标注,你也能准确看出600的位置。
极少数的电影票房达到10亿美元(1000百万)或更多。这导致图形“向右偏斜”,或者非正式地说,具有“长长的右尾”。大 populations 中收入或租金等变量的分布也通常具有这种形状。
面积原则
直方图的两个轴都是数值型的,因此你可以对它们进行算术运算。例如,你可以将任一轴或两个轴上的值乘以一个因子。为了了解这如何影响视觉感知,让我们暂时离开直方图,看看 Flowing Data 网站为此目的提供的图形。这是两款iPad型号电池尺寸的比较。

较大的电池应该比小的电池大70%。所以它应该更大,但不是两倍大。然而,图片中较大的电池看起来几乎是较小的电池的四倍大小。
导致这个问题的原因是,眼睛将“面积”作为大小的度量,而不仅仅是高度或宽度。在图片中,两个维度都增加了70%,导致面积产生乘法效应。
可视化的面积原则指出,当我们用具有两个维度的图形(如矩形)来表示一个数量时,该图形的“面积”应该代表该数量。
直方图:一般原则与计算
直方图遵循面积原则,并具有两个定义性属性:
- 箱按比例绘制并且是连续的(尽管有些可能为空),因为水平轴上的值是数值型的,因此在数轴上具有固定位置。
- 每个条形的“面积”与箱中的条目数成比例。
属性2是绘制直方图的关键,通常通过以下方式实现:
$$ \mbox{条形的面积} ~=~ \mbox{箱中条目的百分比} $$
由于面积代表百分比,高度则代表百分比以外的其他东西。高度的数值计算仅使用条形是矩形这一事实:
$$ \mbox{条形的面积} = \mbox{条形的高度} \times \mbox{箱的宽度} $$
因此
$$ \mbox{条形的高度} ~=~ \frac{\mbox{条形的面积}}{\mbox{箱的宽度}} ~=~ \frac{\mbox{箱中条目的百分比}}{\mbox{箱的宽度}} $$
高度的单位是“每单位水平轴上的百分比”。高度是箱中的百分比相对于箱宽度的度量。因此它被称为“密度”或“拥挤度”。
使用这种方法绘制时,称直方图是在“密度标度”上绘制的。在此标度上: - 每个条形的面积等于落在相应箱中的数据值的百分比。 - 直方图中所有条形的总面积是100%。从比例的角度来看,我们可以说直方图中所有条形的面积“之和为1”。
垂直轴:密度标度
正如我们刚刚看到的,每个条形的高度是落入相应箱的元素百分比除以箱的宽度。我们现在来看看 hist 是如何计算上面直方图所有条形的高度的。
为了便于参考,这里是直方图再次展示。
millions.hist('Adjusted Gross', bins=np.arange(300,2001,100), unit="Million Dollars")
A histogram with 'Adjusted Gross (Million Dollars)' on the x-xis and 'Percent per Million Dollars' on the y-axis. The graph has the tallest bars on the left side from about x=250 to x=750. After x=750 to anout x=1750 the bars are all shorter than 0.05.回顾一下,表格 bin_counts 包含直方图中所有箱的计数,由 bins=np.arange(300, 2000, 100) 指定。同时请记住一共有200部电影。
bin_counts.show(3)
<IPython.core.display.HTML object>箱 [300, 400) 包含68部电影。这占所有电影的34%:
$$ \mbox{百分比} = \frac{68}{200} \cdot 100 = 34 $$
箱 [300, 400) 的宽度为 $400 - 300 = 100$。因此
$$ \mbox{高度} = \frac{34}{100} = 0.34 $$
单位: 条形的高度是34%除以1亿美元,因此高度是每百万美元0.34%。
条形的高度“不是”箱中条目的百分比。它是箱中条目的百分比相对于箱的空间大小。这就是为什么高度衡量拥挤度或密度。垂直轴被称为处于密度标度上。
为什么不直接绘制计数?
在垂直轴上绘制密度而不是计数或百分比的主要原因是,能够比较直方图并用平滑曲线近似它们,在平滑曲线中比例由曲线下的面积表示。
例如,在课程的后面部分,你将看到近似钟形的直方图。下图显示了这样的形状。金色阴影区域恰好是曲线下总面积的95%。请注意,基于你在图中看到的面积,这相当可信,即使轴上没有数字。这就是我们绘制直方图时让面积代表百分比的原因。

在密度标度上绘制直方图还使我们能够比较基于不同数据集大小或不同箱选择的直方图。在这种情况下,箱计数和百分比可能都不能直接比较。但如果两个直方图都使用密度标度绘制,那么面积和密度就是可比较的。
如果直方图具有不等宽的箱,那么在密度标度上绘制是可解释性的要求。对于某些变量,不等宽的箱可能是自然的。例如,在美国教育体系中,小学包括1-5年级,初中包括6-8年级,高中包括9-12年级,学士学位还需要四年。关于教育年限的数据可能会使用这些区间进行分箱。事实上,无论变量是什么,箱都不必相等。在数据左端或右端值不多的地方,设置一个非常宽的箱是很常见的。
让我们使用不等宽的箱绘制调整后票房收入的直方图,然后看看如果绘制计数而不是密度会发生什么。
uneven = make_array(300, 350, 400, 500, 1800)
millions.hist('Adjusted Gross', bins=uneven, unit="Million Dollars")
A histogram with 'Adjusted Gross (Million Dollars)' on the x-xis and 'Percent per Million Dollars' on the y-axis. The bars have uneven widths: 300 to 350, 350 to 400, 400 to 500, and 500 to just above 1750. The first bar has a height between 0.1 and 0.2, the second bar has a height above 0.5, the third bar has a height of 0.3, and the last, longest bar has a short height, just above 0.注意,[400, 500) 条形的高度(每百万美元0.3%)与上面的直方图相同。
其他条形的面积像往常一样代表箱中的百分比。bin 方法允许我们查看每个箱中的计数。
millions.bin('Adjusted Gross', bins=uneven)
bin | Adjusted Gross count
300 | 14
350 | 54
400 | 60
500 | 72
1800 | 0箱 [300, 350) 只有14部电影,而箱 [500, 1800] 有72部电影。但 [500, 1800] 箱上的条形比 [300, 350) 箱上的条形矮得多。[500, 1800] 箱非常宽,以至于它的72部电影比狭窄的 [300, 350) 箱中的14部电影要稀疏得多。换句话说,在 [500, 1800] 区间上密度更低。
相反,如果使用 normed=False 选项仅绘制计数,如下所示,图形看起来完全不同并且会歪曲数据。
millions.hist('Adjusted Gross', bins=uneven, normed=False)
A histogram is shown with 'Adjusted Gross' on the x-axis and 'Count' on the y-axis. The same bins as the previous histogram are used (300 to 350, 350 to 400, 400 to 500, and 500 to 1800). The first bar has a height of about 15, the second bar has a height a bit above 50, the third bar has a height of 60, and the last bar has a height just above 70.即使使用了 hist,“上图中的图形不是直方图”。它误导性地夸大了票房至少5亿美元的电影。每个条形的高度简单地绘制为箱中的电影数量,而没有考虑箱宽度的差异。在这张基于计数的图中,电影分布的形状完全丢失了。
平顶与细节级别
尽管密度标度使用面积正确地表示了百分比,但将值分组到箱中会丢失一些细节。
再看下图中 [400, 500) 的箱。条形的平顶,在每百万美元0.3%的水平上,掩盖了电影在该箱内分布有些不均匀的事实。
millions.hist('Adjusted Gross', bins=uneven, unit="Million Dollars")
A histogram with 'Adjusted Gross (Million Dollars)' on the x-xis and 'Percent per Million Dollars' on the y-axis. The bars have uneven widths: 300 to 350, 350 to 400, 400 to 500, and 500 to just above 1750. The first bar has a height between 0.1 and 0.2, the second bar has a height above 0.5, the third bar has a height of 0.3, and the last, longest bar has a short height, just above 0.为了看到这一点,让我们将 [400, 500) 箱拆分为10个更窄的箱,每个宽度为1000万美元。
some_tiny_bins = make_array(
300, 350, 400, 410, 420, 430, 440, 450, 460, 470, 480, 490, 500, 1800)
millions.hist('Adjusted Gross', bins=some_tiny_bins, unit='Million Dollars')
The same histogram as directly above is shown, but the third bar has been split into ten tiny bars. The highest of those tiny bars is the left most one with a height of about 0.45. There are also two other tiny bars towards the right with height 0.4. The remaining 7 tiny bars are all at or less than height of 0.3一些细长的条形高于0.3,另一些则更矮。通过在整个箱上设置一个高度为0.3的平顶,我们决定忽略更精细的细节,并使用该平顶水平作为一个粗略近似。通常(尽管并非总是如此),这足以理解分布的大致形状。
高度作为粗略近似。 这个观察为我们提供了一种不同的思考高度的方式。 再次查看之前直方图中的 [400, 500) 箱。如我们所见,该箱宽1亿美元,包含30%的数据。因此相应条形的高度是每百万美元0.3%。
现在将箱视为由100个窄箱组成,每个宽100万美元。条形的高度“每百万美元0.3%”意味着作为一个粗略近似,0.3%的电影落在每个宽度为100万美元的100个细长箱中。
我们拥有用于绘制直方图的整个数据集。因此,我们可以将直方图绘制到数据和我们的耐心所允许的精细细节程度。更小的箱将产生更详细的图像。然而,如果你在书籍或网站上看到一个直方图,并且无法访问底层数据集,那么清楚地理解由平顶产生的“粗略近似”就变得很重要。
计算所有高度
我们知道如何找到每个直方图条形的高度。让我们利用这一点来开发一次性计算所有高度的代码。
我们将以下面的直方图为例。箱在数组 uneven 中指定。
millions.hist('Adjusted Gross', bins=uneven, unit="Million Dollars")
A histogram with 'Adjusted Gross (Million Dollars)' on the x-xis and 'Percent per Million Dollars' on the y-axis. The bars have uneven widths: 300 to 350, 350 to 400, 400 to 500, and 500 to just above 1750. The first bar has a height between 0.1 and 0.2, the second bar has a height above 0.5, the third bar has a height of 0.3, and the last, longest bar has a short height, just above 0.我们将从获取箱中的计数开始。
histogram_elements = millions.bin('Adjusted Gross', bins=uneven).relabeled(1, 'count')
histogram_elements
bin | count
300 | 14
350 | 54
400 | 60
500 | 72
1800 | 0现在我们可以添加一列包含每个箱中的百分比。
total_count = sum(histogram_elements.column('count'))
percents = np.round(100*histogram_elements.column('count')/total_count, 2)
histogram_elements = histogram_elements.with_columns('percent', percents)
histogram_elements
bin | count | percent
300 | 14 | 7
350 | 54 | 27
400 | 60 | 30
500 | 72 | 36
1800 | 0 | 0第0列包含所有箱的左端点,除了其最后一个元素是最后一个箱的右端点。因此我们可以使用 np.diff 来找出所有箱的宽度。然后我们将宽度添加到 histogram_elements 表中,首先删除最后一行。
bin_widths = np.diff(histogram_elements.column('bin'))
num_bins = histogram_elements.num_rows - 1 # the number of bins
histogram_elements = histogram_elements.take(
np.arange(num_bins)).with_columns(
'width', bin_widths
)
histogram_elements
bin | count | percent | width
300 | 14 | 7 | 50
350 | 54 | 27 | 50
400 | 60 | 30 | 100
500 | 72 | 36 | 1300最后,我们可以添加一列高度。
heights = np.round(
histogram_elements.column('percent')/histogram_elements.column('width'),2)
histogram_elements = histogram_elements.with_columns('height', heights)
histogram_elements
bin | count | percent | width | height
300 | 14 | 7 | 50 | 0.14
350 | 54 | 27 | 50 | 0.54
400 | 60 | 30 | 100 | 0.3
500 | 72 | 36 | 1300 | 0.03为方便起见,这里是直方图再次展示。将其与上面的表格进行比较,以确认高度的计算是正确的。
millions.hist('Adjusted Gross', bins=uneven)
A histogram with 'Adjusted Gross' on the x-xis and 'Percent per unit' on the y-axis. The bars have uneven widths: 300 to 350, 350 to 400, 400 to 500, and 500 to just above 1750. The first bar has a height between 0.1 and 0.2, the second bar has a height above 0.5, the third bar has a height of 0.3, and the last, longest bar has a short height, just above 0.条形图与直方图的区别
- 条形图每个类别显示一个数值量。它们通常用于显示分类变量的分布。直方图显示定量变量的分布。
- 条形图中所有条形具有相同的宽度,连续条形之间有相等的间距。条形可以按任意顺序排列,因为分布是分类的。直方图的条形是连续的;箱按比例绘制在数轴上。
- 条形图中条形的长度(如果垂直绘制则为高度)与每个类别中的计数成比例。直方图中条形的高度衡量密度;直方图中条形的“面积”与箱中的计数成比例。