Python数据分析-pandas01:基本数据类型

0

Pandas简介

之前详细介绍了 NumPy 和它的 ndarray 多维数组对象,为 Python 多维数组提供了高效的存储和处理方法。

本章基于前面的知识,继续学习 Pandas 库提供的数据操作方法。Pandas 是在 NumPy 基础上建立的另一个第三方库,提供了一种高效的 DataFrame 数据结构。DataFrame 本质上是一种带行标签和列标签、支持相同类型数据和缺失值的多维数组。Pandas 不仅为带各种标签的数据提供了便利的存储与展示接口,还实现了许多强大的计算、查找和分类等操作。

pandas 在底层存储时使用的是 numpy 的数组,因此在安装 pandas 之前,需要确保已经安装了 numpy

在安装完成 pandas 之后,可以导入它检查一下版本号:

import pandas
pandas.__version__
'1.3.1'

和之前导入 numpy 并使用别名 np 一样,之后将导入 pandas 并使用别名 pd

import pandas as pd

Pandas的数据对象

Series对象

pandas 最简单的数据类型是 SeriesSeries 对象是一个带索引数据构成的一维数组。可以用一个数组创建 Series 对象,如下所示:

s01 = pd.Series([1, 2, 3, 4])
s01
0 1 1 2 2 3 3 4 dtype: int64

Series 对象将一组数据和一组索引绑定在一起,可以通过 .values 属性获取数据,通过 .index 属性获取索引。.values 属性返回的结果就是底层的 numpy 数组:

s01.values
array([1, 2, 3, 4], dtype=int64)
isinstance(s01.values, np.ndarray)
True

.index 属性返回的结果是一个类型为 Index 的索引对象。索引是 pandas 的特点之一,将在后面的内容里详细介绍它。

numpy 数组一样,可以通过中括号实现索引或切片:

s01[2]
3
s01[1:-1]
1 2 2 3 3 4 dtype: int64

这么看来,pandasSeriesnumpy 的一维数组很类似。但 Series 为数组提供了一种更加强大的索引系统:numpy 数组通过隐式定义的整数索引获取数值,而 pandasSeries 对象用可以手动指定索引与数值关联。

显式索引的定义让 Series 拥有了更好的可读性。例如,索引不再仅仅是整数,还可以是任意想要的类型。如果需要,完全可以用字符串定义索引:

s02 = pd.Series([0.25, 0.5, 0.75, 1.0], index=['a', 'b', 'c', 'd'])
s02
a 0.25 b 0.50 c 0.75 d 1.00 dtype: float64

在使用索引访问元素时,可以通过之前定义的字符索引获取数值:

s02['b']
0.5

这种数值的访问形式很像 Python 字典,可以将每个索引作为键映射到一个值。因此可以直接使用 Python 的字典创建一个 Series 对象,让两者的类比更加清晰:

s03 = pd.Series({
    'key1': 1,
    'key2': 2,
    'keyi': 5,
    'keyn': 10
})

由于 Python3.6+ 的字典是有序的,因此创建的 Series 对象索引也默认按照顺序排列。

除了可以使用像字典一样按键获取数值外,Series 的索引还可以用于切片操作:

s03['key2': 'keyn']
key2 2 keyi 5 keyn 10 dtype: int64

这种切片是包括终点位置的元素的,这和普通的索引形式不同。后续会详细介绍这种索引规则。

DataFrame对象

pandas 的另一个基础数据结构是 DataFrameDataFrame 可以看作是一种二维数组,但它既有行索引,又有列索引,因此它经常被当做一个表格使用。注意,DataFramepandas 的非常重要的一个数据类型,因为它可以很好地表达日常生活中处理的各种表格结构。DataFrame 与 Excel 中表格或 SQL 中表的概念非常相似,可以实现大多数 Excel 或 SQL 的表操作。

DataFrame 有许多创建方式,每种创建方式都代表着对它的理解。例如,和 Series 对象一样,DataFrame 可以看作一个更灵活的 numpy 二维数组。因此可以直接使用二维数据创建 DataFrame ,只要手动指定符合形状的行列索引即可:

df01 = pd.DataFrame(np.arange(12).reshape((3, 4)),
                    index=range(1, 4),
                    columns=['A', 'B', 'C', 'D'])
df01
ABCD
10123
24567
3891011

也可以把 DataFrame 看成是一种结构数组,每行表示一个结构元素,行索引表示每个结构元素的索引,列索引则表示的是结构的字段名。因此,当然可以使用 numpy 的结构数组来创建 DataFrame

pd.DataFrame(np.zeros(
    3, dtype=[('A', 'i8'), ('B', 'f8')]))
AB
000.0
100.0
200.0

结构数组的表述便于理解,但实际上可能会带来一些误解。例如,DataFrame 的索引和 numpy 的二维数组索引不太一样:在 numpy 的二维数组里,arr[0] 被认为是一个嵌套数组返回包含的第一个子数组;而在 pandas 中,第一层索引得到的结果是一个列的数据:

df01['A']
1 0 2 4 3 8 Name: A, dtype: int32

DataFrame 一种更好的理解方式是看作一个数组字典,每一个索引都可以看作是字典的一个键,对应一个列的数据;这些数据是一系列同种类型的数值,或者说一个 Series 。也就是说,DataFrame 是具有共同索引的若干 Series 对象组合。

因此,使用单个 Series 创建的是一个单列的 DataFrame

pd.DataFrame(pd.Series(
    {'bread': 279, 'biscuit': 78, 'strawberry': 26}))
0
bread279
biscuit78
strawberry26

如果能接受这种 DataFrame 的理解方式,不仅容易记住它的创建方式,也有助于明白对它的操作会得到什么样的结果。

创建 DataFrame 的方式非常丰富,只要能表达出这种二维的 {column: series} 的映射关系基本都可以用于创建它。例如,可以使用一组具有相同索引Series 对象创建它;由于 Series 可以被看作特殊的字典,因此也可以使用一个二维字典来创建一个 DataFrame 对象:

price = {'cake': 23, 'cookie': 6, 'bread': 10}
sales = {'cake': 5, 'cookie': 14, 'bread': 23}
df02 = pd.DataFrame({'price': price, 'sales': sales})
df02
pricesales
cake235
cookie614
bread1023

如果明白“DataFrame 是一组具有相同索引Series 对象组合”,就会知道字典的嵌套与被嵌套的关系:内层的字典应该要具有相同的键。

如果能理解这种嵌套关系,那么就可以很自然地明白数据获取方式:第一次索引将以列索引值为键,得到一个列作为字典;第二次索引将以行索引值为键,从得到字典中获取相应的元素:

df02['price']['cookie']
6

和 Python 字典一样,SeriesDataFrame 还支持 item assignment ,对 Series 的操作结果是更新项或添加项,对 DataFrame 的操作结果是更新列或添加列:

df02['sum'] = df02['price'] * df02['sales']
df02
pricesalessum
cake235115
cookie61484
bread1023230

Index对象

pandasIndex 对象是构成 SeriesDataFrame 显式索引的基础。

Series 是一维的数据,因此需要一个 Index 对象用于获取数据,这个对象被保存在它的 .index 属性中。而二维的 DataFrame 需要两个 Index 对象来定位数据,它们分别是代表行的 .index 属性和代表列的 .columns 属性:

df02.index
Index(['cake', 'cookie', 'bread'], dtype='object')
df02.columns
Index(['price', 'sales'], dtype='object')

Index 对象可以看作是一个不可变数组,因此可以像创建数组一样创建 Index 对象:

i01 = pd.Index([1, 3, 5, 6, 7])
i01
Int64Index([1, 3, 5, 6, 7], dtype='int64')

除了没有那么丰富的创建方法外,Index 对象的许多属性与操作都像数组,例如索引与切片:

i01[[0, 1, 2]]  # array indexing
Int64Index([1, 3, 5], dtype='int64')
i01[:-1:2]
Int64Index([1, 5], dtype='int64')

两者之间的不同在于 Index 对象是不可变的,这种特征使得多个 DataFrame 等类型之间共享索引时更加安全,可以避免修改索引而破坏表的结构。

一种对 Index 更精确的描述是有序集合pandas 对象被设计用于实现许多操作,如合并数据集,这就要求相同列之间需要合并。Index 对象遵循 Python 标准库的集合 set 数据结构的许多习惯用法,包括并集、交集、差集等:

i02 = pd.Index([1, 3, 5, 7, 9])
i03 = pd.Index([2, 3, 5, 7, 11])
i02 & i03  # intersection
Int64Index([3, 5, 7], dtype='int64')
i02.union(i03)
Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64')

高版本的 pandas 可能已经弃用了运算符重载的形式,或对其抛出警告,因此一般更推荐直接调用对象的方法实现这些运算。

京ICP备2021034974号
contact me by hello@frozencandles.fun