Python中可视化数据(直方图和密度图)
本文将全面介绍如何使用matplotlib和seaborn库在Python中使用直方图和密度图。在整个过程中,我们将探索一个真实世界的数据集,因为在网上提供大量资源的情况下,没有理由不使用实际数据!我们将可视化的NYCflights13数据,其中包含超过300,000个航班离开纽约市的航班2013年的观测数据。
在我们开始绘图之前先检查我们的数据。我们可以将数据读入熊猫数据框并显示前10行:
import pandas as pd
# Read in data and examine first 10 rows flights = pd.read_csv('data/formatted_flights.csv') flights.head(10)
航班到达延误在几分钟内,负值意味着航班较早(事实证明,航班往往会提早到达,只是从来没有当我们在他们身上!)有超过30万次的航班,至少延迟60分钟和最大延迟时间为120分钟。数据框中的另一列是我们可用于比较的航空公司的名称。
直方图
为了使 Python 中的基本直方图, 我们可以使用 matplotlib 或 seaborn。下面的代码显示两个库中创建等效数字的函数调用。对于绘图调用, 我们指定 binwidth 的数量。
# Import the libraries
import matplotlib.pyplot as plt
import seaborn as sns
# matplotlib histogram
plt.hist(flights['arr_delay'], color = 'blue', edgecolor = 'black',
bins = int(180/5))
# seaborn histogram
sns.distplot(flights['arr_delay'], hist=True, kde=False,
bins=int(180/5), color = 'blue',
hist_kws={'edgecolor':'black'})
# Add labels
plt.title('Histogram of Arrival Delays')
plt.xlabel('Delay (min)')
plt.ylabel('Flights')
对于大多数基本的直方图,我会使用matplotlib代码,因为它更简单,但我们稍后将使用seaborn distplot函数来创建不同的分布。
我怎么得到5分钟的binwidth?找出最佳binwidth的唯一方法是尝试多个值!下面的代码是在matplotlib中使用一系列binwidth来制作相同的图形。最终,对于binwidth没有正确或错误的答案,但我选择5分钟,因为我认为它最能代表分布。
# Show 4 different binwidths
for i, binwidth in enumerate([1, 5, 10, 15]):
# Set up the plot
ax = plt.subplot(2, 2, i + 1)
# Draw the plot
ax.hist(flights['arr_delay'], bins = int(180/binwidth),
color = 'blue', edgecolor = 'black')
# Title and labels
ax.set_title('Histogram with Binwidth = %d' % binwidth, size = 30)
ax.set_xlabel('Delay (min)', size = 22)
ax.set_ylabel('Flights', size= 22)
plt.tight_layout()
plt.show()
binwidth的选择会显着影响结果图。较小的带宽可能会使绘图混乱,但较大的带宽可能会掩盖数据中的细微差别。Matplotlib会自动为你选择一个合理的binwidth,但我喜欢在尝试了几个值后自己指定binwidth。没有真正的正确或错误答案,因此请尝试几个选项并查看哪些最适合您的特定数据。
当直方图失败时
直方图是开始探索从一个类别中提取的单个变量的一个很好的方法。但是, 当我们要比较一个变量分布在多个类别, 直方图有问题的可读性。例如, 如果我们要比较航空公司之间的到达延迟分布, 一个不太好的方法是在同一地块上为每家航空公司创建直方图:
(请注意,y轴已被标准化以考虑航空公司之间航班的不同数量。为此,请将参数传递norm_hist = True给sns.distplot函数调用。)
解决方案#1:并排直方图
我们可以将它们并排放置,而不是重叠航空公司的直方图。为此,我们为每家航空公司创建一个到达延迟列表,然后将其plt.hist作为列表列表传递给函数调用。我们必须为每家航空公司和一个标签指定不同的颜色,以便我们可以区分它们。代码,包括为每家航空公司创建列表如下:
# Make a separate list for each airline
x1 = list(flights[flights['name'] == 'United Air Lines Inc.']['arr_delay'])
x2 = list(flights[flights['name'] == 'JetBlue Airways']['arr_delay'])
x3 = list(flights[flights['name'] == 'ExpressJet Airlines Inc.']['arr_delay'])
x4 = list(flights[flights['name'] == 'Delta Air Lines Inc.']['arr_delay'])
x5 = list(flights[flights['name'] == 'American Airlines Inc.']['arr_delay'])
# Assign colors for each airline and the names
colors = ['#E69F00', '#56B4E9', '#F0E442', '#009E73', '#D55E00']
names = ['United Air Lines Inc.', 'JetBlue Airways', 'ExpressJet Airlines Inc.'',
'Delta Air Lines Inc.', 'American Airlines Inc.']
# Make the histogram using a list of lists
# Normalize the flights and assign colors and names
plt.hist([x1, x2, x3, x4, x5], bins = int(180/15), normed=True,
color = colors, label=names)
# Plot formatting
plt.legend()
plt.xlabel('Delay (min)')
plt.ylabel('Normalized Flights')
plt.title('Side-by-Side Histogram with Multiple Airlines')
默认情况下,如果我们传入列表列表,matplotlib将并排放置条形图。
解决方案 #2: 堆积条形图
我们可以通过将参数传递stacked = True给直方图调用来堆叠它们,而不是为每个航空公司并排绘制条形图:
# Stacked histogram with multiple airlines plt.hist([x1, x2, x3, x4, x5], bins = int(180/15), stacked=True, normed=True, color = colors, label=names)
那么,这绝对不是更好!在这里,每个航空公司都代表每个舱位的整体部分,但几乎不可能进行比较。我们使用直方图尝试的两种解决方案都不成功,因此是时候转移到密度图。
密度图
首先,什么是密度图?甲密度图是从数据中估计的直方图的平滑化,连续版本。最常见的估计形式被称为核密度估计。在这种方法中,在每个单独的数据点处绘制连续曲线(内核),然后将所有这些曲线相加在一起以进行单个平滑密度估计。最常用的内核是高斯(在每个数据点产生高斯钟形曲线)。
这里,x轴上的每条黑色小垂直线代表一个数据点。单个内核(本例中为高斯)在每个点上方用红色虚线表示。坚实的蓝色曲线是通过对各个高斯求和来创建的,并形成整体密度图。
x轴是变量的值,就像直方图一样,但y轴代表什么?密度图中的y轴是核密度估计的概率密度函数。但是,我们需要小心地指定这是一个概率密度,而不是概率。不同之处在于概率密度是x轴上每单位的概率。要转换为实际概率,我们需要在x轴上找到特定间隔下曲线下方的区域。有点令人困惑的是,因为这是一个概率密度而不是概率,所以y轴可以取大于1的值。密度图的唯一要求是曲线下的总面积合并为一。我通常倾向于将密度图上的y轴视为仅用于不同类别之间的相对比较的值。
Seaborn中的密度图
为了使 seaborn 中的密度图, 我们可以使用 distplot 或 kdeplot 函数。我将继续使用 distplot 函数, 因为它允许我们用一个函数调用进行多个分布。例如, 我们可以使一个密度图显示所有到达延迟在对应的直方图之上:
# Density Plot and Histogram of all arrival delays sns.distplot(flights['arr_delay'], hist=True, kde=True, bins=int(180/5), color = 'darkblue', hist_kws={'edgecolor':'black'}, kde_kws={'linewidth': 4})
曲线显示了密度图, 它实质上是直方图的平滑版本。y 轴是按密度计算的, 而直方图在缺省情况下是规范化的, 因此它与密度图具有相同的 y 尺度。
与直方图的 binwidth 类似, 密度图有一个参数称为带宽,绘图库将为我们选择一个合理的带宽值 (默认情况下使用 "斯科特" 估计), 而不像直方图的 binwidth, 我通常使用默认带宽。但是, 我们可以使用不同的带宽来查看是否有更好的选择。
请注意,更宽的带宽会使分配更平滑。我们也看到,即使我们将数据限制在-60到120分钟,密度图也超出了这些限制。这是密度图的一个潜在问题:因为它计算每个数据点的分布,所以它可以生成超出原始数据范围的数据。这可能意味着我们最终会得到原始数据中从来不存在的x轴上不可能的值!值得注意的是,我们也可以更改内核,这会改变在每个数据点处绘制的分布,从而改变整体分布。但是,对于大多数应用程序,默认的内核,高斯和默认的带宽估计工作得很好。
解决方案#3密度图
为了在同一个图上显示分布,我们可以遍历航空公司,每次调用distplot内核密度估计值设置为True并将直方图设置为False。下面是用多个航空公司绘制密度图的代码:
# List of five airlines to plot
airlines = ['United Air Lines Inc.', 'JetBlue Airways', 'ExpressJet Airlines Inc.'',
'Delta Air Lines Inc.', 'American Airlines Inc.']
# Iterate through the five airlines
for airline in airlines:
# Subset to the airline
subset = flights[flights['name'] == airline]
# Draw the density plot
sns.distplot(subset['arr_delay'], hist = False, kde = True,
kde_kws = {'linewidth': 3},
label = airline)
# Plot formatting
plt.legend(prop={'size': 16}, title = 'Airline')
plt.title('Density Plot with Multiple Airlines')
plt.xlabel('Delay (min)')
plt.ylabel('Density')
最后,我们已经达成了一个有效的解决方案!通过密度图,我们可以轻松地进行航空公司之间的比较,因为图形不那么混乱。我们得出这样的结论:所有这些航空公司都有几乎相同的到达延迟分布!但是,数据集中还有其他航空公司,我们可以绘制一个稍有不同的图形来说明密度图的另一个可选参数,并为图形着色。
阴影密度图
填充密度图可以帮助我们区分重叠分布。虽然这不总是一个好方法,但它可以帮助强调分布之间的差异。为了遮蔽密度图,我们传递shade = True给调用中的kde_kws参数distplot。
sns.distplot(subset ['arr_delay'],hist = False,kde = True, kde_kws = {'shade':True,'linewidth':3}, label = airline)
对于这个图表,我认为这是有道理的,因为阴影可以帮助我们区分它们重叠区域的地块。现在,我们终于获得了一些有用的信息:阿拉斯加航空公司的航班往往比联合航空公司的航班时间更早。
Rug Plots
如果要显示分布中的每个值而不仅是平滑密度,则可以添加小地图。这显示了x轴上的每个数据点,使我们可以看到所有的实际值。使用seaborn的好处distplot是,我们可以添加单个参数调用rug = True(具有一些格式化)的地毯情节。
# Subset to Alaska Airlines
subset = flights[flights['name'] == 'Alaska Airlines Inc.']
# Density Plot with Rug Plot
sns.distplot(subset['arr_delay'], hist = False, kde = True, rug = True,
color = 'darkblue',
kde_kws={'linewidth': 3},
rug_kws={'color': 'black'})
# Plot formatting
plt.title('Density Plot with Rug Plot for Alaska Airlines')
plt.xlabel('Delay (min)')
plt.ylabel('Density')
有了许多数据点, 地毯图会变得拥挤, 但是对于某些数据集来说, 查看每一个资料点都是有帮助的。