第31单元 pandas 数据结构
pandas模块为已经具有丰富数据结构的Python增加了两个新的数据容器:Series和DataFrame。series是具有标签的(也就是具有索引的)一维矢量。frame是一个行和列都具有标签的表格,它与Excel电子表格和MySQL表格并无不同。frame的每一列都是一个series。除了一些特殊情况外,pandas对frame和series的处理方式是相似的。
frame和series不是简单的存储容器。它们都内置了进行各种数据整理操作的工具,如:
单级和分级索引
处理缺失数据
对整个列和表进行算术和布尔运算
数据库类型的操作(比如合并和聚合)
绘制各个列和整个表格
从文件读取数据并将数据写入文件
frame和series非常方便,当你在处理一维或二维表格数据时,都应该使用它们。
series
series是一维的数据矢量。和numpy数组一样(想要了解更多关于数组的内容,请参考第21单元),series是同构的:series的所有数据项必须是相同的数据类型。
可以使用任意序列(比如列表、元组或数组)创建一个简单的series。下面我们通过一组美国近期通货膨胀的数据,来展示pandas的series。首先需要说明的是,为了强调数据的不变性,我使用了一个元组来表示之前计算好的通货膨胀数据。另外,下面的例子并不需要经济学或财政学方面的高级知识!
import pandas as pd # 最后的值是错误的,我们稍后再修改它! inflation = pd.Series((2.2, 3.4, 2.8, 1.6, 2.3, 2.7, 3.4, 3.2, 2.8, 3.8, \ -0.4, 1.6, 3.2, 2.1, 1.5, 1.5)) ➾ 0 2.2 ➾ 1 3.4 ➾ 2 2.8 ➾ 3 1.6 ➾ 4 2.3 ➾ 5 2.7 ➾ 6 3.4 ➾ 7 3.2 ➾ 8 2.8 ➾ 9 3.8 ➾ 10 -0.4 ➾ 11 1.6 ➾ 12 3.2 ➾ 13 2.1 ➾ 14 1.5 ➾ 15 1.5 ➾ dtype: float64
函数len()是Python内置的标准通用函数,它也适用于series:
len(inflation) ➾ 16
一个series,两个series,三个series……
series这个单词的单数形式和复数形式一样。它的由来可以追溯到拉丁语serere,其含义是加入或连接。
一个简单的series,比如刚刚创建的inflation,具有默认的整数索引:第一项的标签为0,第二项为1,以此类推。一个series的values属性包含了所有series值的列表;index属性指的是series的索引(索引是另一种pandas数据类型);index.values属性指的是所有索引值组成的数组。
inflation.values ➾ array([ 2.2, 3.4, 2.8, 1.6, 2.3, 2.7, 3.4, 3.2, 2.8, ➾ 3.8, -0.4, 1.6, 3.2, 2.1, 1.5, 1.5]) inflation.index ➾ Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], ➾ dtype='int64') inflation.index.values ➾ array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
注意pandas是如何避免重复造轮子的,它使用numpy数组作为其底层存储工具!
所有这些数组(以及它们所代表的series属性)都是可变的,这一点有些令人惊讶。更改values、index和index.values实际上会改变series的值和索引。我们用这个事实来修改通货膨胀数据中的最后一项,把它改成一个错误的值:
inflation.values[-1] = 1.6
关于series存在这样一个问题:它看起来像一个数组,并且它的行为也和数组一样,于是根据“鸭子测试”1的原理,series就是数组。例如,对于上面的例子,我们很难说明series的第一项对应的是哪一年的通胀数据。当然,你可以创建另一个series来保存年份,并保证两个series始终在一起使用,但是我不得不提醒读者,当我还在读高中的时候,就有人告诫我,像这样并列地使用series终将导致灾难。所以,让我们用一个自定义的索引来创建series,索引通过字典的方式传递给Series的构造函数。字典键作为series的索引,索引是series中不可分割的一部分:
1鸭子测试(Duck Test)是对一种归纳推理方式的幽默说法。它可以解释为:如果它看起来像鸭子,游泳像鸭子,叫声像鸭子,那么它可能就是只鸭子。——译者注
inflation = pd.Series({1999 : 2.2, «more items», 2014 : 1.6, 2015 : np.nan}) ➾ 1999 2.2 ➾ «more items» ➾ 2014 1.6 ➾ 2015 NaN
另外,也可以使用任意一个序列创建新的索引,然后将其附加到现有的series中:
inflation.index = pd.Index(range(1999, 2015)) inflation[2015] = numpy.nan ➾ 1999 2.2 ➾ «more items» ➾ 2014 1.6 ➾ 2015 NaN
series的值和索引可以有各自的名称,通过相应的name属性进行访问和分配。这些名称实际上起到了文档的作用,它们提醒我们(以及未来的核心读者)这两个序列的本质:
inflation.index.name = "Year" inflation.name = "%" ➾ Year ➾ 1999 2.2 ➾ «more items» ➾ 2014 1.6 ➾ 2015 NaN ➾ Name: %, dtype: float64
通过在交互式命令行中打印series或者输入其名称,可以查看整个series的信息,包括所有的索引名称。也可以调用head()和tail()函数,这两个函数分别返回一个series的前五行和最后五行:
inflation.head() ➾ Year ➾ 1999 2.2 ➾ 2000 3.4 ➾ 2001 2.8 ➾ 2002 1.6 ➾ 2003 2.3 ➾ Name: %, dtype: float64 inflation.tail() ➾ Year ➾ 2011 3.2 ➾ 2012 2.1 ➾ 2013 1.5 ➾ 2014 1.6 ➾ 2015 NaN ➾ Name: %, dtype: float64
与head()和tail()函数相比,也许你会更喜欢使用图片的方式(一图胜千言),可以参考下图(我们将在第8章进一步介绍绘图工具。)
series非常适合于观测一个变量的情况。但是,许多数据集都具有多个变量,这就轮到frame上场了。
frame
frame是一个行和列都具有标签的表。可以通过二维numpy数组、元组列表、Python字典或一个frame构造出一个新的frame。如果使用的是字典,则字典的键作为列的名称,字典的值(必须是序列)作为列值。如果使用的是frame,则pandas会将列名从源frame复制到新frame。如果使用的是数组,可以通过可选参数columns(列名称组成的序列)提供列名称。沿用numpy的方式,frame索引为编号为0的轴(“垂直”维),frame列为编号为1的轴(“水平”维)。
我们使用美国酒精滥用和酒精中毒研究所22011年公开的监测报告来研究frame。报告给出了1977—2009年美国每个州每年每种酒(啤酒、葡萄酒和烈酒)的人均消费量。
2pubs.niaaa.nih.gov/publications/surveillance92/CONS09.pdf
NIAAA报告
NIAAA报告是一个非常优秀的数据源,因此我将其经过预处理的一个副本作为了本书“代码”项,供读者浏览。不过要记住:不要喝酒,专心做数据科学!
可以将相同长度的行元组或其他序列的列表作为数据行传递给构造函数,来创建一个具有列名和索引的简单frame。(此处使用了第37单元的一个pandas CSV读取器,创建了完整的alco frame,然后选出了其中一年的数据)。
alco2009 = pd.DataFrame([(1.20, 0.22, 0.58), (1.31, 0.54, 1.16), (1.19, 0.38, 0.74), «more rows»], columns=("Beer", "Wine", "Spirits"), index=("Alabama", "Alaska", «more states»)) ➾ Beer Wine Spirits ➾ Alabama 1.20 0.22 0.58 ➾ Alaska 1.31 0.54 1.16 ➾ Arizona 1.19 0.38 0.74 ➾ «more rows»
你也可以使用列的字典,其结果与上面的代码相同:
alco2009 = pd.DataFrame({"Beer" : (1.20, 1.31, 1.19, «more rows»), "Wine" : (0.22, 0.54, 0.38, «more rows»), "Spirits" : (0.58, 1.16, 0.74, «more rows»)}, index=("Alabama", "Alaska", «more states»))
对于单个frame列的访问,可以使用字典或对象符号。但是,如果要添加新列,就必须使用字典符合。如果使用对象符号,则pandas会创建一个新的frame属性。和series一样,frame也有head()和tail()函数。(真的熊猫3也是有尾巴的!)
3pandas也可译为熊猫。——译者注
alco2009["Wine"].head() ➾ State ➾ Alabama 0.22 ➾ Alaska 0.54 ➾ Arizona 0.38 ➾ Arkansas 0.17 ➾ California 0.55 ➾ Name: Wine, dtype: float64 alco2009.Beer.tail() ➾ State ➾ Virginia 1.11 ➾ Washington 1.09 ➾ West Virginia 1.24 ➾ Wisconsin 1.49 ➾ Wyoming 1.45 ➾ Name: Beer, dtype: float64
和series一样,frame也支持广播:可以使用一条赋值语句给某列的所有行分配一个值。列甚至可以不存在;当列不存在时,pandas会自动创建它。
alco2009["Total"] = 0 alco2009.head() ➾ Beer Wine Spirits Total ➾ State ➾ Alabama 1.20 0.22 0.58 0 ➾ Alaska 1.31 0.54 1.16 0 ➾ Arizona 1.19 0.38 0.74 0 ➾ Arkansas 1.07 0.17 0.60 0 ➾ California 1.05 0.55 0.73 0
代码里对总量(Total)的设置显然是错误的,我们将在第36单元介绍如何用算术运算来修正这个值。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论