Pandas 是在 NumPy 基础上建立的新程序库,提供了一种高效的 DataFrame数据结构。 DataFrame 本质上是一种带行标签和列标签、支持相同类型数据和缺失值的多维数组。
NumPy 的 ndarray 数据结构为数值计算任务中常见的干净整齐、组织良好的数据提供了许多不可或缺的功能。虽然它在这方面做得很好,但是当我们需要处理更灵活的数据任务(如为数据添加标签、处理缺失值等),或者需要做一些不是对每个元素都进行广播映射的计算(如分组、透视表等)时, NumPy 的限制就非常明显了,而这些都是分析各种非结构化数据时很重要的一部分。建立在 NumPy 数组结构上的Pandas,尤其是它的 Series 和 DataFrame 对象,为数据科学家们处理那些消耗大量时间的“数据清理”(data munging)任务提供了捷径。
和之前导入 NumPy 并使用别名 np 一样,我们将导入 Pandas 并使用别名 pd:
import numpy as npimport pandas as pd
Pandas 有三个基本数据结构: Series、 DataFrame 和 Index。
Pandas的Series对象
Pandas 的 Series 对象是一个带索引数据构成的一维数组。可以用一个数组创建 Series 对象,如下所示:
In[2]: data = pd.Series([0.25, 0.5, 0.75, 1.0])dataOut[2]: 0 0.251 0.502 0.753 1.00dtype: float64
从上面的结果中,你会发现 Series 对象将一组数据和一组索引绑定在一起,我们可以通过values 属性和 index 属性获取数据。 values 属性返回的结果与 NumPy 数组类似:
In[3]: data.valuesOut[3]: array([ 0.25, 0.5 , 0.75, 1. ])
index 属性返回的结果是一个类型为 pd.Index 的类数组对象,我们将在后面的内容里详细介绍它:
In[4]: data.indexOut[4]: RangeIndex(start=0, stop=4, step=1)
和 NumPy 数组一样,数据可以通过 Python 的中括号索引标签获取:
In[5]: data[1]Out[5]: 0.5In[6]: data[1:3]Out[6]: 1 0.502 0.75dtype: float64
1. Serise是通用的NumPy数组
到目前为止,我们可能觉得 Series 对象和一维 NumPy 数组基本可以等价交换,但两者间的本质差异其实是索引: NumPy 数组通过隐式定义的整数索引获取数值,而 Pandas 的Series 对象用一种显式定义的索引与数值关联。
显式索引的定义让 Series 对象拥有了更强的能力。例如,索引不再仅仅是整数,还可以是任意想要的类型。如果需要,完全可以用字符串定义索引:
In[7]: data = pd.Series([0.25, 0.5, 0.75, 1.0],index=['a', 'b', 'c', 'd'])dataOut[7]: a 0.25b 0.50c 0.75d 1.00dtype: float64
获取数值的方式与之前一样:
In[8]: data['b']Out[8]: 0.5
也可以使用不连续或不按顺序的索引:
In[9]: data = pd.Series([0.25, 0.5, 0.75, 1.0],index=[2, 5, 3, 7])dataOut[9]: 2 0.255 0.503 0.757 1.00dtype: float64In[10]: data[5]Out[10]: 0.5
2. Series是特殊的字典
可以把 Pandas 的 Series 对象看成一种特殊的 Python 字典。字典是一种将任意键映射到一组任意值的数据结构,而 Series 对象其实是一种将类型键映射到一组类型值的数据结构。类型至关重要:就像 NumPy 数组背后特定类型的经过编译的代码使得它在某些操作上比普通的 Python 列表更加高效一样, Pandas Series 的类型信息使得它在某些操作上比Python 的字典更高效。
我们可以直接用 Python 的字典创建一个 Series 对象,让 Series 对象与字典的类比更加清晰:
In[11]: population_dict = {'California': 38332521,'Texas': 26448193,'New York': 19651127,'Florida': 19552860,'Illinois': 12882135}population = pd.Series(population_dict)populationOut[11]:California 38332521Florida 19552860Illinois 12882135New York 19651127Texas 26448193dtype: int64
用字典创建 Series 对象时,其索引默认按照顺序排列。典型的字典数值获取方式仍然有效:
In[12]: population['California']Out[12]:38332521
和字典不同, Series 对象还支持数组形式的操作,比如切片:
In[13]: population['California':'Illinois']Out[13]:California 38332521Florida 19552860Illinois 12882135dtype: int64
3. 创建Series对象
我们已经见过几种创建 Pandas 的 Series 对象的方法,都是像这样的形式:
>>> pd.Series(data, index=index)
其中, index 是一个可选参数, data 参数支持多种数据类型。
例如, data 可以是列表或 NumPy 数组,这时 index 默认值为整数序列:
In[14]: pd.Series([2, 4, 6])Out[14]:0 21 42 6dtype: int64
data 也可以是一个标量,创建 Series 对象时会重复填充到每个索引上:
In[15]: pd.Series(5, index=[100, 200, 300])Out[15]:100 5200 5300 5dtype: int64
data 还可以是一个字典, index 默认是排序的字典键:
In[16]: pd.Series({2:'a', 1:'b', 3:'c'})Out[16]:1 b2 a3 cdtype: object
每一种形式都可以通过显式指定索引筛选需要的结果:
In[17]: pd.Series({2:'a', 1:'b', 3:'c'}, index=[3, 2])Out[17]:3 c2 adtype: object
这里需要注意的是, Series 对象只会保留显式定义的键值对。
Pandas的DataFrame对象
Pandas 的另一个基础数据结构是 DataFrame。和上一节介绍的 Series 对象一样, DataFrame 既可以作为一个通用型 NumPy 数组,也可以看作特殊的 Python 字典。
1. DataFrame是通用的NumPy数组
如果将 Series 类比为带灵活索引的一维数组,那么 DataFrame 就可以看作是一种既有灵活的行索引,又有灵活列名的二维数组。就像你可以把二维数组看成是有序排列的一维数组一样,你也可以把 DataFrame 看成是有序排列的若干 Series 对象。这里的“排列”指的是它们拥有共同的索引。
下面用上一节中美国五个州面积的数据创建一个新的 Series 来进行演示:
In[18]:area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297,'Florida': 170312, 'Illinois': 149995}area = pd.Series(area_dict)areaOut[18]:California 423967Florida 170312Illinois 149995New York 141297Texas 695662dtype: int64
再结合之前创建的 population 的 Series 对象,用一个字典创建一个包含这些信息的二维对象:
In[19]: states = pd.DataFrame({'population': population,'area': area})statesOut[19]: area populationCalifornia 423967 38332521Florida 170312 19552860Illinois 149995 12882135New York 141297 19651127Texas 695662 26448193
和 Series 对象一样, DataFrame 也有一个 index 属性可以获取索引标签:
In[20]: states.indexOut[20]:Index(['California', 'Florida', 'Illinois', 'New York', 'Texas'], dtype='object')
另外, DataFrame 还有一个 columns 属性,是存放列标签的 Index 对象:
In[21]: states.columnsOut[21]: Index(['area', 'population'], dtype='object')
因此 DataFrame 可以看作一种通用的 NumPy 二维数组,它的行与列都可以通过索引获取。
2. DataFrame是特殊的字典
与 Series 类似,我们也可以把 DataFrame 看成一种特殊的字典。字典是一个键映射一个值,而 DataFrame 是一列映射一个 Series 的数据。例如,通过 ‘area’ 的列属性可以返回包含面积数据的 Series 对象:
In[22]: states['area']Out[22]:California 423967Florida 170312Illinois 149995New York 141297Texas 695662Name: area, dtype: int64
这里需要注意的是,在 NumPy 的二维数组里, data[0] 返回第一行;而在 DataFrame 中,data[‘col0’] 返回第一列。因此,最好把 DataFrame 看成一种通用字典,而不是通用数组,即使这两种看法在不同情况下都是有用的。
3. 创建DataFrame对象
Pandas 的 DataFrame 对象可以通过许多方式创建,这里举几个常用的例子。
(1) 通过单个 Series 对象创建。 DataFrame 是一组 Series 对象的集合,可以用单个 Series创建一个单列的DataFrame:
In[23]: pd.DataFrame(population, columns=['population'])Out[23]: populationCalifornia 38332521Florida 19552860Illinois 12882135New York 19651127Texas 26448193
(2) 通过字典列表创建。任何元素是字典的列表都可以变成 DataFrame。用一个简单的列表
综合来创建一些数据:
In[24]: data = [{'a': i, 'b': 2 * i}for i in range(3)]pd.DataFrame(data)Out[24]: a b0 0 01 1 22 2 4
即使字典中有些键不存在, Pandas 也会用缺失值 NaN(不是数字, not a number)来表示:
In[25]: pd.DataFrame([{'a': 1, 'b': 2}, {'b': 3, 'c': 4}])Out[25]: a b c0 1.0 2 NaN1 NaN 3 4.0
(3) 通过 Series 对象字典创建。就像之前见过的那样, DataFrame 也可以用一个由 Series对象构成的字典创建:
In[26]: pd.DataFrame({'population': population,'area': area})Out[26]: area populationCalifornia 423967 38332521Florida 170312 19552860Illinois 149995 12882135New York 141297 19651127Texas 695662 26448193
(4) 通过 NumPy 二维数组创建。假如有一个二维数组,就可以创建一个可以指定行列索引值的 DataFrame。如果不指定行列索引值,那么行列默认都是整数索引值:
In[27]: pd.DataFrame(np.random.rand(3, 2),columns=['foo', 'bar'],index=['a', 'b', 'c'])Out[27]: foo bara 0.865257 0.213169b 0.442759 0.108267c 0.047110 0.905718
Series数据选择方法
如前所述, Series 对象与一维 NumPy 数组和标准 Python 字典在许多方面都一样。只要牢牢记住这两个类比,就可以帮助我们更好地理解 Series 对象的数据索引与选择模式。
将Series看作字典
和字典一样, Series 对象提供了键值对的映射:
In[1]: import pandas as pddata = pd.Series([0.25, 0.5, 0.75, 1.0],index=['a', 'b', 'c', 'd'])dataOut[1]: a 0.25b 0.50c 0.75d 1.00dtype: float64In[2]: data['b']Out[2]: 0.5
Series 对象还可以用字典语法调整数据。就像你可以通过增加新的键扩展字典一样,你也可以通过增加新的索引值扩展 Series:
In[6]: data['e'] = 1.25dataOut[6]: a 0.25b 0.50c 0.75d 1.00e 1.25dtype: float64
将Series看作一维数组
Series 不仅有着和字典一样的接口,而且还具备和 NumPy 数组一样的数组数据选择功能,包括索引、 掩码等操作,具体示例如下所示:
In[7]: # 将显式索引作为切片data['a':'c']Out[7]: a 0.25b 0.50c 0.75dtype: float64In[8]: # 将隐式整数索引作为切片data[0:2]Out[8]: a 0.25b 0.50dtype: float64In[9]: # 掩码data[(data > 0.3) & (data < 0.8)]Out[9]: b 0.50c 0.75dtype: float64
在以上示例中,切片是绝大部分混乱之源。需要注意的是,当使用显式索引(即data[‘a’:’c’])作切片时,结果包含最后一个索引;而当使用隐式索引(即 data[0:2])作切片时,结果不包含最后一个索引。
补充:
这些切片和取值的习惯用法经常会造成混乱。例如,如果你的 Series 是显式整数索引,那么 data[1] 这样的取值操作会使用显式索引,而 data[1:3] 这样的切片操作却会使用隐式索引。
In[11]: data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])dataOut[11]:1 a3 b5 cdtype: objectIn[12]: # 取值操作是显式索引data[1]Out[12]: 'a'In[13]: # 切片操作是隐式索引data[1:3]Out[13]:3 b5 cdtype: object
由于整数索引很容易造成混淆,所以 Pandas 提供了一些索引器(indexer)属性来作为取值
的方法。它们不是 Series 对象的函数方法,而是暴露切片接口的属性。
第一种索引器是 loc 属性,表示取值和切片都是显式的:
In[14]: data.loc[1]Out[14]: 'a'In[15]: data.loc[1:3]Out[15]:1 a3 bdtype: object
第二种是 iloc 属性,表示取值和切片都是 Python 形式的隐式索引(从 0 开始,左闭右开):
In[16]: data.iloc[1]Out[16]: 'b'In[17]: data.iloc[1:3]Out[17]:3 b5 cdtype: object
DataFrame数据选择方法
前面曾提到, DataFrame 在有些方面像二维或结构化数组,在有些方面又像一个共享索引的若干 Series 对象构成的字典。这两种类比可以帮助我们更好地掌握这种数据结构的数据选择方法。
将DataFrame看作字典
第一种类比是把 DataFrame 当作一个由若干 Series 对象构成的字典。让我们用之前的美国五州面积与人口数据来演示:
In[18]: area = pd.Series({'California': 423967, 'Texas': 695662,'New York': 141297, 'Florida': 170312,'Illinois': 149995})pop = pd.Series({'California': 38332521, 'Texas': 26448193,'New York': 19651127, 'Florida': 19552860,'Illinois': 12882135})data = pd.DataFrame({'area':area, 'pop':pop})dataOut[18]: area popCalifornia 423967 38332521Florida 170312 19552860Illinois 149995 12882135New York 141297 19651127Texas 695662 26448193
两个 Series 分别构成 DataFrame 的一列,可以通过对列名进行字典形式(dictionary-style)的取值获取数据:
In[19]: data['area']Out[19]:California 423967Florida 170312Illinois 149995New York 141297Texas 695662Name: area, dtype: int64
同样,也可以用属性形式(attribute-style)选择纯字符串列名的数据:
In[20]: data.areaOut[20]:California 423967Florida 170312Illinois 149995New York 141297Texas 695662Name: area, dtype: int64
对同一个对象进行属性形式与字典形式的列数据,结果是相同的:
In[21]: data.area is data['area']Out[21]: True
虽然属性形式的数据选择方法很方便,但是它并不是通用的。如果列名不是纯字符串,或者列名与 DataFrame 的方法同名,那么就不能用属性索引。 例如, DataFrame 有一个 pop()方法,如果用 data.pop 就不会获取 ‘pop’ 列,而是显示为方法:
In[22]: data.pop is data['pop']Out[22]: False
另外,还应该避免对用属性形式选择的列直接赋值(即可以用 data[‘pop’] = z,但不要用data.pop = z)。
和前面介绍的 Series 对象一样,还可以用字典形式的语法调整对象,如果要增加一列可以这样做:
In[23]: data['density'] = data['pop'] / data['area']dataOut[23]: area pop densityCalifornia 423967 38332521 90.413926Florida 170312 19552860 114.806121Illinois 149995 12882135 85.883763New York 141297 19651127 139.076746Texas 695662 26448193 38.018740
将DataFrame看作二维数组
前面曾提到,可以把 DataFrame 看成是一个增强版的二维数组,用 values 属性按行查看数组数据:
In[24]: data.valuesOut[24]: array([[ 4.23967000e+05, 3.83325210e+07, 9.04139261e+01],[ 1.70312000e+05, 1.95528600e+07, 1.14806121e+02],[ 1.49995000e+05, 1.28821350e+07, 8.58837628e+01],[ 1.41297000e+05, 1.96511270e+07, 1.39076746e+02],[ 6.95662000e+05, 2.64481930e+07, 3.80187404e+01]])
理解了这一点,就可以把许多数组操作方式用在 DataFrame 上。例如,可以对 DataFrame 进行行列转置:
In[25]: data.TOut[25]:California Florida Illinois New York Texasarea 4.239670e+05 1.703120e+05 1.499950e+05 1.412970e+05 6.956620e+05pop 3.833252e+07 1.955286e+07 1.288214e+07 1.965113e+07 2.644819e+07density 9.041393e+01 1.148061e+02 8.588376e+01 1.390767e+02 3.801874e+01
通过字典形式对列进行取值显然会限制我们把 DataFrame 作为 NumPy 数组可以获得的能力,尤其是当我们在 DataFrame 数组中使用单个行索引获取一行数据时:
In[26]: data.values[0]Out[26]: array([ 4.23967000e+05, 3.83325210e+07, 9.04139261e+01])
而获取一列数据就需要向 DataFrame 传递单个列索引:
In[27]: data['area']Out[27]:California 423967Florida 170312Illinois 149995New York 141297Texas 695662Name: area, dtype: int64
其他取值方法
还有一些取值方法和前面介绍过的方法不太一样。它们虽然看着有点奇怪,但是在实践中还是很好用的。首先,如果对单个标签取值就选择列,而对多个标签用切片就选择行:
In[33]: data['Florida':'Illinois']Out[33]: area pop densityFlorida 170312 19552860 114.806121Illinois 149995 12882135 85.883763
切片也可以不用索引值,而直接用行数来实现:
In[34]: data[1:3]Out[34]: area pop densityFlorida 170312 19552860 114.806121Illinois 149995 12882135 85.883763
与之类似,掩码操作也可以直接对每一行进行过滤,而不需要使用 loc 索引器:
In[35]: data[data.density > 100]Out[35]: area pop densityFlorida 170312 19552860 114.806121New York 141297 19651127 139.076746
这两种操作方法其实与 NumPy 数组的语法类似,虽然它们与 Pandas 的操作习惯不太一致,但是在实践中非常好用。
