四时宝库

程序员的知识宝库

Python 数据分析——Pandas 数据对象

Series和DataFrame是Pandas中最常用的两个对象。先介绍这两种对象的基本概念以及常用属性,在后续将介绍对它们进行操作和运算的各种函数和方法。

一、Series对象

Series是Pandas中最基本的对象,它定义了NumPy的ndarray对象的接口__array__(),因此可以用NumPy的数组处理函数直接对Series对象进行处理。Series对象除了支持使用位置作为下标存取元素之外,还可以使用索引标签作为下标存取元素,这个功能与字典类似。每个Series对象实际上都由两个数组组成:

·index:它是从ndarray数组继承的Index索引对象,保存标签信息。若创建Series对象时不指定index,将自动创建一个表示位置下标的索引。

·values:保存元素值的ndarray数组,NumPy的函数都对此数组进行处理。

下面创建一个Series对象,并查看上述两个属性:

s = pd.Series([1, 2, 3, 4, 5], index=["a", "b", "c", "d", "e"])
print u" 索引:", s.index
print u"值数组:", s.values
 索引: Index([u'a', u'b', u'c', u'd', u'e'], dtype='object')
值数组: [1 2 3 4 5]

Series对象的下标运算同时支持位置和标签两种形式:

print u"位置下标   s[2]:", s[2]
print u"标签下标 s['d']:", s['d']
位置下标   s[2]: 3
标签下标 s['d']: 4

Series对象还支持位置切片和标签切片。位置切片遵循Python的切片规则,包括起始位置,但不包括结束位置;但标签切片则同时包括起始标签和结束标签。

和ndarray数组一样,还可以使用位置列表或位置数组存取元素,同样也可以使用标签列表和标签数组。

Series对象同时具有数组和字典的功能,因此它也支持字典的一些方法,例如Series.iteritems():

list(s.iteritems())
[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]

当两个Series对象进行操作符运算时,Pandas会按照标签对齐元素,也就是说运算操作符会对标签相同的两个元素进行计算。在下面的例子中,s中标签为'b'的元素和s2中标签为'b'的元素相加得到结果中的22。当某一方的标签不存在时,默认以NaN(Not a Number)填充。由于NaN是浮点数中的一个特殊值,因此输出的Series对象的元素类型被转换为float64。

二、DataFrame对象

DataFrame对象(数据表)是Pandas中最常用的数据对象。Pandas提供了将许多数据结构转换为DataFrame对象的方法,还提供了许多输入输出函数来将各种文件格式转换成DataFrame对象。在介绍这些函数之前,我们需要先理解DataFrame对象中的一些概念。

1.DataFrame的各个组成元素

下面的程序调用read_csv()从Soils-simple.csv读入数据,通过index_col参数指定第0和第1列为行索引,用parse_dates参数指定进行日期转换的列。在指定列时可以使用列的序号或列名。所得到的DataFrame对象如图1所示,图中标识出了DataFrame的各个组成部分的名称。

图1 DataFrame的结构

df_soil = pd.read_csv("data/Soils-simple.csv", index_col=[0, 1], parse_dates=["Date"])
df_soil.columns.name = "Measures"

由图1可知DataFrame对象是一个二维表格。其中,每列中的元素类型必须一致,而不同的列可以拥有不同的元素类型。在本例中,有4列浮点数类型、1列日期类型和1列object类型。object类型的列可以保存任何Python对象,在Pandas中字符串列使用object类型。dtypes属性可以获得表示各个列类型的Series对象:

df_soil.dtypes
Measures
pH               float64
Dens             float64
Ca               float64
Conduc           float64
Date      datetime64[ns]
Name              object
dtype: object

与数组类似,通过shape属性可以得到DataFrame的行数和列数:

df_soil.shape
(6, 6)

DataFrame对象拥有行索引和列索引,可以通过索引标签对其中的数据进行存取。index属性保存行索引,而columns属性保存列索引。在本例中列索引是一个Index对象,索引对象的名称可以通过其name属性存取:

print df_soil.columns
print df_soil.columns.name
Index([u'pH', u'Dens', u'Ca', u'Conduc', u'Date', u'Name'],
dtype='object', name=u'Measures')
Measures

行索引是一个表示多级索引的MultiIndex对象,每级的索引名可以通过names属性存取:

print df_soil.index
print df_soil.index.names
MultiIndex(levels=[[u'0-10', u'10-30'], [u'Depression', u'Slope', u'Top']],
labels=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]],
names=[u'Depth', u'Contour'])
[u'Depth', u'Contour']

与二维数组相同,DataFrame对象也有两个轴,它的第0轴为纵轴,第1轴为横轴。当某个方法或函数有axis、orient等参数时,该参数可以使用整数0和1或者"index"和"columns"来表示纵轴方向和横轴方向。

[]运算符可以通过列索引标签获取指定的列,当下标是单个标签时,所得到的是Series对象,例如df_soil["pH"],而当下标是列表时,则得到一个新的DataFrame对象,例如df_soil[["Dens", "Ca"]]:

.loc[]可通过行索引标签获取指定的行,例如df.loc["0-10", "Top"]获得Depth为"0-10"、Contour为"Top"的行,而df.loc["10-30"]获取Depth为"10-30"的所有行。当结果为一行时得到的是Series对象,结果为多行时得到的是DataFrame对象。注意由于原数据中列的类型不统一,因此得到的Series对象的类型被转换为最通用的object类型。

values属性将DataFrame对象转换成数组,由于本例中的列类型不统一,所得到的数组是一个元素类型为object的数组。

df_soil.values.dtype
dtype('O')

2.将内存中的数据转换为DataFrame对象

调用DataFrame()可以将多种格式的数据转换成DataFrame对象,它的三个参数data、index和columns分别为数据、行索引和列索引。data参数可以是:

·二维数组或者能转换为二维数组的嵌套列表。

·字典:字典中的每对“键-值”将成为DataFrame对象的列。值可以是一维数组、列表或Series对象。

在下面的程序中,?在将一个形状为(4, 2)的二维数组转换成DataFrame对象,通过index和columns参数指定行和列的索引。?将字典转换为DataFrame对象,其列索引由字典的键决定,行索引由index参数指定。?将结构数组转换为DataFrame对象,其列索引由结构数组的字段名决定,行索引默认为从0开始的整数序列。

此外还可以调用以from_开头的类方法,将特定格式的数据转换成DataFrame对象。from_dict()将字典转换为DataFrame对象,其orient参数可以指定字典键对应的方向,默认值为"columns",表示把字典的键转换为列索引,即字典中的每个值与一列对应。而orient参数为"index"时,字典中的每个值与一行对应。当字典为嵌套字典,即字典的值为字典时,另外一个轴的索引值由第二层字典中的键决定。下面分别将列表字典和嵌套字典转换为DataFrame对象。嵌套字典中缺失的数据使用NaN表示:

from_items()将“(键,值)”序列转换为DataFrame对象,其中“值”是表示一维数据的列表、数组或Series对象。当其orient参数为"index"时,需要通过columns指定列索引。

items = dict1.items()
df1 = pd.DataFrame.from_items(items, orient="index", columns=["A", "B", "C"])
df2 = pd.DataFrame.from_items(items, orient="columns")
df1            df2  
----------      -------
A  B  C         a  b
a  1  2  3      0  1  4
b  4  5  6      1  2  5
2  3  6

3.将DataFrame对象转换为其他格式的数据

to_dict()方法将DataFrame对象转换为字典,它的orient参数决定字典元素的类型:

print df2.to_dict(orient="records") #字典列表
print df2.to_dict(orient="list") #列表字典
print df2.to_dict(orient="dict") #嵌套字典
[{'a': 1, 'b': 4}, {'a': 2, 'b': 5}, {'a': 3, 'b': 6}]
{'a': [1, 2, 3], 'b': [4, 5, 6]}
{'a': {0: 1, 1: 2, 2: 3}, 'b': {0: 4, 1: 5, 2: 6}}

to_records()方法可以将DataFrame对象转换为结构数组,若其index参数为True(默认值),则其返回的数组中包含行索引数据:

print df2.to_records().dtype
print df2.to_records(index=False).dtype
[('index', '<i8'), ('a', '<i8'), ('b', '<i8')]
[('a', '<i8'), ('b', '<i8')]

Pandas还提供了许多全局函数来从各种格式的文件读取数据,而各种以to开头的方法可以将其输出到文件中,关于文件的输入输出将在后面详细介绍。

三、Index对象

Index对象保存索引标签数据,它可以快速找到标签对应的整数下标,这种将标签映射到整数下标的功能与Python的字典类似。其values属性可以获得保存标签的数组,与Series一样,字符串使用object类型的数组保存:

index = df_soil.columns
index.values
array(['pH', 'Dens', 'Ca', 'Conduc', 'Date', 'Name'], dtype=object)

Index对象可当作一维数组,通过与NumPy数组相同的下标操作可以得到一个新的Index对象,但是Index对象是只读的,因此一旦创建就无法修改其中的元素。

print index[[1, 3]]
print index[index > 'c']
print index[1::2]
Index([u'Dens', u'Conduc'], dtype='object', name=u'Measures')
Index([u'pH'], dtype='object', name=u'Measures')
Index([u'Dens', u'Conduc', u'Name'], dtype='object', name=u'Measures')

Index对象也具有字典的映射功能,它将数组中的值映射到其位置:

·Index.get_loc(value):获得单个值value的下标。

·Index.get_indexer(values):获得一组值values的下标,当值不存在时,得到-1。

print index.get_loc('Ca')
print index.get_indexer(['Dens', 'Conduc', 'nothing'])
2
[ 1  3 -1]

可以直接调用Index()来创建Index对象,然后传递给DataFrame()的index或columns参数。由于Index是不可变对象,因此多个数据对象的索引可以是同一个Index对象。

index = pd.Index(["A", "B", "C", "D", "E"], name="level")
s1 = pd.Series([1, 2, 3, 4, 5], index=index)
df1 = pd.DataFrame({"a":[1, 2, 3, 4, 5], "b":[6, 7, 8, 9, 10]}, index=index)
print s1.index is df1.index
True

四、MultiIndex对象

MultiIndex表示多级索引,它从Index继承,其中的多级标签采用元组对象来表示。下面通过[]获取其中的单个元素,调用get_loc()和get_indexer()以获取单个标签和多个标签对应的下标。

mindex = df_soil.index
print mindex[1]
print mindex.get_loc(("0-10", "Slope"))
print mindex.get_indexer([("10-30", "Top"), ("0-10", "Depression"), "nothing"])
('0-10', 'Slope')
1
[ 5  0 -1]

在MultiIndex内部并不直接保存元组对象,而是使用多个Index对象保存索引中每级的标签:

print mindex.levels[0]
print mindex.levels[1]
Index([u'0-10', u'10-30'], dtype='object', name=u'Depth')
Index([u'Depression', u'Slope', u'Top'], dtype='object', name=u'Contour')

然后使用多个整数数组保存这些标签的下标:

print mindex.labels[0]
print mindex.labels[1]
FrozenNDArray([0, 0, 0, 1, 1, 1], dtype='int8')
FrozenNDArray([0, 1, 2, 0, 1, 2], dtype='int8')

下面的代码通过levels和labels属性得到多级索引中所有元组的列表,该列表也可以通过tolist()方法获得:

level0, level1 = mindex.levels
label0, label1 = mindex.labels
zip(level0[label0], level1[label1])
[('0-10', 'Depression'),
('0-10', 'Slope'),
('0-10', 'Top'),
('10-30', 'Depression'),
('10-30', 'Slope'),
('10-30', 'Top')]

当将一个元组列表传递给Index()时,将自动创建MultiIndex对象。若希望创建元素类型为元组的Index对象,可以设置tupleize_cols参数为False:

pd.Index([("A", "x"), ("A", "y"), ("B", "x"), ("B", "y")], name=["class1", "class2"])
MultiIndex(levels=[[u'A', u'B'], [u'x', u'y']],
labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
names=[u'class1', u'class2'])

此外可以使用以from_开头的MultiIndex类方法从特定的数据结构创建MultiIndex对象。例如from_arrays()方法从多个数组创建MultiIndex对象:

class1 = ["A", "A", "B", "B"]
class2 = ["x", "y", "x", "y"]
pd.MultiIndex.from_arrays([class1, class2], names=["class1", "class2"])
MultiIndex(levels=[[u'A', u'B'], [u'x', u'y']],
labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
names=[u'class1', u'class2'])

from_product()则从多个集合的笛卡尔积创建MultiIndex对象。下面的程序将所创建的MultiIndex对象传递给index和columns参数,所创建的DataFrame对象的行和列使用同一个多级索引对象:

五、常用的函数参数

在后续章节我们会详细介绍各种常用函数的用法。表1列出了一些常用的函数参数。

表1 常用的函数参数

参数名

常用值

说明

axis

0、1

运算对应的轴

level

整数或索引的级别名

指定运算对应的级别

fill_value

数值

指定运算中出现的NaN的替代填充值

skipna

布尔值

运算是否跳过NaN

index

序列

指定行索引

columns

序列

指定列索引

numeric_only

布尔值

是否只针对数值进行运算

func

可调用对象

指定回调函数

inplace

布尔值

是否原地更新,若为否,则返回新对象

encoding

"utf8"

指定文本编码

dropna

布尔值

是否删除包含NaN的行

例如mean()函数计算平均值,如果不指定axis参数,则沿着第0轴计算每列的平均值。如果指定axis参数为1,则计算每行的平均值。如果指定level参数,则针对多级索引中指定级别中相同标签对应的元素的平均值:


六、DataFrame的内部结构

DataFrame对象内部使用NumPy数组保存数据,因此也会出现和数组相同的共享数据存储区的问题。为了帮助读者理解Pandas的内存管理,让我们看看DataFrame对象的内部结构。

下面的程序通过GraphvizDataFrame绘制df_soil的内部结构,结果如图2所示。图中的实线箭头表示一般属性,虚线箭头表示由property创建的属性,读取这些属性时实际上是获得它们对应的函数的返回值。在分析DataFrame对象的内部结构时,我们重点关注实线箭头所表示的属性。

from scpy2.common import GraphvizDataFrame
%dot GraphvizDataFrame.graphviz(df_soil)

图2 DataFrame对象的内部结构

DataFrame对象的columns属性是Index对象,而index属性是表示多级索引的MultiIndex对象。Index对象的索引功能由其_engine属性——一个ObjectEngine对象提供,该对象使用一个哈希表PyObjectHashTable对象将标签映射到与其对应的整数下标。下面的代码获取列标签"Date"对应的整数下标:

df_soil.columns._engine.mapping.get_item("Date")
4

DataFrame对象的数据都保存在_data属性中,它是一个BlockManager对象,其blocks属性是一个列表,其中有一个FloatBlock对象、一个DatetimeBlock对象和一个ObjectBlock对象。这些对象是管理实际数据的数据块,其values属性是保存数据的数组。

DataFrame对象尽量用一个数组保存相同类型的列,而将不同类型的列保存在不同的数组中。这些数组的形状为(4, 6)、(1, 6)和(1, 6)。它们的第0轴的长度对应4个浮点数列、1个时间列和1个字符串列。由此可知DataFrame中的整列数据是保存在连续的内存空间中,这有助于提高数据的存取速度。

当通过[]获取某一列时,所得到的Series对象与原DataFrame对象共享内存。下面查看保存Series对象数据的数组的base属性,也就是df_soil._data.blocks[0].values:

s = df_soil["Dens"]
s.values.base is df_soil._data.blocks[0].values
True

当通过[]获取多列时,将复制所有的数据,因此保存新DataFrame对象数据的数组的base属性为None:

print df_soil[["Dens"]]._data.blocks[0].values.base
None

如果DataFrame对象只有一个数据块,则通过values属性得到的数组是数据块中数组的转置,因此它与DataFrame对象共享内存。例如在下面的程序中,df_float中所有列的元素类型相同,它只有一个数据块,因此df_float.values所得到的数组与df_float中保存数据的数组共享内存。

df_float = df_soil[['pH', 'Dens', 'Ca', 'Conduc']]
df_float.values.base is df_float._data.blocks[0].values
True

当DataFrame对象只有一个数据块时,获取其行数据所得到的Series对象也与其共享内存:

df_float.loc["0-10", "Top"].values.base is df_float._data.blocks[0].values
True

而当BlockManager中使用多个数组保存数据时,则返回这些数据的拷贝,数组的元素类型为最通用的元素类型,以保存各种格式的数据。在下面的例子中,df.values的元素类型为object,因为它需要同时保存浮点数、时间和字符串。?

df_soil.values.dtype
dtype('O')

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接