使用 Pandas 检查数据

您不需要成为数据科学家也能使用 Pandas 进行一些基本分析。

传统上,使用 Python 编程的人员使用该语言自带的数据类型,例如整数、字符串、列表、元组和字典。当然,您可以在 Python 中创建对象,但这些对象通常是基于这些基本数据结构构建的。

如果您是一位使用 Pandas 的数据科学家,那么您的大部分时间都花在了 NumPy 上。NumPy 可能感觉像是 Python 数据结构,但它在很多方面的行为都不同。这不仅是因为它的所有操作都通过向量工作,还因为底层数据实际上是一个 C 风格的数组。这使得 NumPy 非常快速和高效,对于给定的数字数组,它比传统的 Python 对象消耗的内存要少得多。

问题是,NumPy 的设计目的是快速,但对于某些人来说,它的级别也有些低。为了获得更多的功能和更灵活的接口,许多人使用 Pandas,这是一个 Python 包,它围绕 NumPy 数组提供了两个基本的包装器:一维 Series 对象和二维 Data Frame 对象。

我经常将 Pandas 描述为“Python 中的 Excel”,因为您可以使用它执行各种计算,以及对数据进行排序、搜索和绘制图表。

由于所有这些原因,Pandas 成为数据科学界的宠儿也就不足为奇了。但问题是:您不需要成为数据科学家也能享受 Pandas 的乐趣。它有很多出色的功能,对于那些原本会花费时间与列表、元组和字典搏斗的 Python 开发人员来说非常有用。

因此,在本文中,我将描述一些每个人都可以使用 Pandas 进行的基本分析,无论您是否是数据科学家。如果您曾经使用过 CSV 文件(您可能经常使用),我绝对建议您考虑使用 Pandas 来打开、读取、分析甚至写入它们。虽然我在本文中没有介绍,但 Pandas 也很好地处理 JSON 和 Excel。

创建数据帧

虽然可以从头开始使用 Python 数据结构或 NumPy 数组创建数据帧,但在我的经验中,更常见的是从文件创建。幸运的是,Pandas 可以从各种文件格式加载数据。

在您可以使用 Pandas 做任何事情之前,您必须加载它。在 Jupyter notebook 中,执行


%pylab inline
import pandas as pd

例如,Python 自带一个 csv 模块,它知道如何处理 CSV(逗号分隔值)格式的文件。但是,您需要遍历文件并对每一行/行执行一些操作。我经常发现使用 Pandas 处理此类文件更容易。例如,这是一个 CSV 文件


a,b,c,d
e,f,g,h
"i,j",k,l,m
n,o.p,q

您可以使用以下命令将其转换为数据帧


df = pd.read_csv('mycsv.csv')

现在您可以使用以下命令在 Jupyter 中查看数据帧的内容


df

问题是,您可能甚至没有意识到有各种各样的 CSV 文件。例如,考虑 Linux /etc/passwd 文件。它实际上不是 CSV 文件,但如果您仔细考虑一下,您会意识到格式是相同的。每个记录都在一行上,字段用“:”字符分隔。所以在这种情况下,您需要使用 sep 参数来指示分隔符


filename = '/etc/passwd'
df = pd.read_csv(filename, sep=':')
df.head()

命令 df.head(),很像 UNIX head 实用程序,显示数据帧的前几行。我经常使用这种方法来检查数据并确保数据传输正常。

在这种特定情况下,数据传输得很好,但是第一行(root 用户的记录!)被解释为标题行。幸运的是,您可以使用另一个参数来修复它


df = pd.read_csv(filename, sep=':', header=None)
df.head()

如果您传递 header 参数,您就是在告诉 Pandas 文件中的哪一行应该被视为标题。但是,如果您传递 None 作为值,您就是在告诉 Pandas 没有哪一行是标题。这没关系,但是您将获得整数(从 0 开始)作为列名。我宁愿使用真实名称,所以要指定它们,请执行


df = pd.read_csv(filename, sep=':', header=None,
                names=['username', 'password', 'uid',
                       'gid', 'name', 'homedir', 'shell'])
df.head()

问题是,您真的需要 password 列吗?考虑到在现代计算机上,它总是包含“x”,我认为您可以将其省略。所以,您可以说


df = pd.read_csv(filename, sep=':', header=None,
                 usecols=[0,2,3,4,5,6],
                names=['username', 'uid', 'gid',
                       'name', 'homedir', 'shell'])
df.head()

usecols 参数指示您要从文件中读取哪些列。

芝加哥被拖走的汽车

现在,您为什么要对 /etc/passwd 文件使用 Pandas 呢?您可能不会,但您可以想象,如果它适用于该文件,它也适用于其他文件。

例如,现在许多城市都在向公众提供数据。芝加哥在线发布了许多数据,所以让我们看一下,例如,您可以了解芝加哥过去 90 天内被拖走的汽车的信息。

您可以访问芝加哥数据门户网站 https://data.cityofchicago.org/browse。我点击了“towed vehicles”→“export”→“CSV”以获取 CSV 文件。然后我获取下载的文件并将其导入到 Pandas 中,使用


df = pd.read_csv('towed.csv')

不,我只是在开玩笑。您认为我有时间和精力下载文件然后加载它吗?我最喜欢的 Pandas 功能之一是,大多数处理文件的方法也可以处理 URL。所以,我可以这样说


url = 'https://data.cityofchicago.org/api/views/ygr5-vcbg/
↪rows.csv?accessType=DOWNLOAD'
df = pd.read_csv(url)

(请注意,当您阅读本文时,URL 很可能已经更改,或者它可能与我的会话信息相关联。如果不是,从我的角度来看,那就更好了!)

然后我查看数据帧,发现它有标题,分隔符是逗号,并且工作正常。我唯一要做的调整是将“拖车日期”列解析为日期,这样我就可以稍微玩一下它。我还指示,由于日期是美国格式,因此日期在前


df = pd.read_csv(url, parse_dates=['Tow Date'], dayfirst=False)
df.head()

您会注意到,第一列现在看起来有点不同,以年-月-日格式显示。这表明有一个时间戳。您还可以看到,如果您运行 df.info 方法,它会告诉您有关数据帧本身的信息


<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5560 entries, 0 to 5559
Data columns (total 10 columns):
Tow Date              5560 non-null datetime64[ns]
Make                  5537 non-null object
Style                 5538 non-null object
Model                 509 non-null object
Color                 5536 non-null object
Plate                 4811 non-null object
State                 5392 non-null object
Towed to Address      5560 non-null object
Tow Facility Phone    5559 non-null object
Inventory Number      5560 non-null int64
dtypes: datetime64[ns](1), int64(1), object(8)
memory usage: 434.5+ KB

如您所见,“Tow Date”列现在存储为 datetime64,这是一个包含日期和时间的 64 位字段。

所以,现在您有了这些数据,您可以用它做什么呢?我最喜欢的方法之一是 value_counts,它可以告诉您特定值在列中出现的次数。因此,您可以说以下内容来找出每个州拖走了多少辆车


df['State'].value_counts()

现在,这将是一个很长的列表,因此最好使用 head 限制从 value_counts 返回的序列


df['State'].value_counts().head(10)

现在您将看到在过去 90 天内在芝加哥被拖走车辆最多的十个州


IL    4948
IN     148
TX      48
WI      48
MN      28
IA      27
MI      19
GA      14
FL      14
TN      13

毫不奇怪,在芝加哥被拖走的大多数车辆来自伊利诺伊州,其次是附近的印第安纳州。至少对我来说,不太令人意外的是来自德克萨斯州的被拖走车辆数量很多,德克萨斯州并不是很近。

为了利用时间戳现在是真正的 datetime 对象这一事实,您可以找出车辆被拖走最常见的日期


2018-03-03    215
2018-03-04    195
2018-02-24    165
2018-03-25    148
2018-03-26    140
2018-03-24    135
2018-03-15    126
2018-03-21    122
2018-02-03    120
2018-03-22    117

如您所见,存在很大的差异。也许这种差异是由于星期几造成的?为了弄清楚,您可以使用 Pandas 为 datetime 列提供的 dt 代理对象。您可以使用它从 datetime 列中提取信息,然后对其进行分析。例如,您可以说


df['Tow Date'].dt.dayofweek

这将检索每个拖车日期的星期几。然后您可以使用 value_counts 汇总每周中每天拖走了多少辆车


df['Tow Date'].dt.dayofweek.value_counts()

结果如下


5    1143
4     831
3     820
6     798
2     794
0     640
1     534

以上表明,芝加哥星期五拖走的汽车比其他任何一天都多得多。这可能一直如此,或者可能只是对于这个 90 天的样本而言如此。

您还可以检查哪些品牌的汽车最常被拖走,或者颜色与被拖走的几率之间是否存在关联。

如果您想知道每周拖走了多少辆车怎么办?为此,您可以利用 Pandas 的一个很棒的功能,您可以在其中将数据帧的索引设置为时间戳列


df.set_index('Tow Date', inplace=True)

现在,您将使用时间戳而不是整数索引来访问行。但重点不是以这种方式访问事物。相反,它是 resample,类似于 SQL 中的 GROUP BY 查询。重采样可以以多种方式完成;这里要求它以一周为单位


df.resample('1W')

就其本身而言,这没有任何作用。但是,如果您随后计算每周的行数,您将获得更有趣的输出。对于每一列,您都会获得每周的条目数。数字有所不同,因为许多列都缺少 (NaN) 信息,这些信息不包含在计数中。这没关系;您可以只使用 Make 列,该列似乎已为每辆被拖走的车辆填写


df.resample('1W').count()['Make']

现在您应该得到一份非常好的报告,显示每周拖走了多少辆汽车


Tow Date
2017-12-31    103
2018-01-07    321
2018-01-14    209
2018-01-21    241
2018-01-28    250
2018-02-04    399
2018-02-11    248
2018-02-18    328
2018-02-25    587
2018-03-04    862
2018-03-11    495
2018-03-18    601
2018-03-25    754
2018-04-01    139
Freq: W-SUN, Name: Make, dtype: int64

与数字表格相比,人类更容易看到图形中的模式,因此您可能需要绘制此图,也使用 Pandas


df.resample('1W').count()['Make'].plot()

这将创建一个折线图,显示 3 月初被拖走的车辆数量激增。为什么?您可以查看温度和日历开始猜测,但您可以如此轻松地下载、检查和绘制此信息是一个很好的起点。

结论

Pandas 不仅仅是数据科学家进行数值分析的工具。它可以读取(和写入)CSV、Excel 和 JSON 等格式,这使其成为我处理这些格式的主要工具。再加上它可以汇总数据,包括基于时间戳的数据,您可能可以理解为什么 Pandas 如此受欢迎。

资源

Pandas 的主页是 这里,我在本文中提到的 Jupyter notebook 是 这里

最后,您可以通过 Pandas 中时间戳列上的 dt 对象访问的“datetime 组件”列表是 这里

Reuven Lerner 在世界各地的公司教授 Python、数据科学和 Git。您可以订阅他的免费每周“更好的开发者”电子邮件列表,并从他的书籍和课程中学习,网址为 http://lerner.co.il。Reuven 与他的妻子和孩子住在以色列的莫迪因。

加载 Disqus 评论