1、唯一值、值计数以及成员资格
还有一类方法可以从一维Series的值中抽取信息。以下面这个Series为例:
In [1]: import pandas as pd In [2]: import numpy as np In [3]: obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])
第一个函数是unique,它可以得到Series中的唯一值数组:
In [4]: uniques = obj.unique() In [5]: uniques Out[5]: array(['c', 'a', 'd', 'b'], dtype=object)
返回的唯一值是未排序的,如果需要的话,可以对结果再次进行排序(uniques.sort())。value_counts用于计算一个Series中各值出现的频率:
In [6]: obj.value_counts() Out[6]: c 3 a 3 b 2 d 1 dtype: int64
为了便于查看,结果Series是按值频率降序排列的。value_counts还是一个顶级pandas方法,可用于任何数组或序列:
In [7]: pd.value_counts(obj.values, sort=False) Out[7]: a 3 c 3 b 2 d 1 dtype: int64
最后是isin,它用于判断矢量化集合的成员资格,可用于选取Series中或DataFrame列中数据的子集:
In [8]: mask = obj.isin(['b', 'c']) In [9]: mask Out[9]: 0 True 1 False 2 False 3 False 4 False 5 True 6 True 7 True 8 True dtype: bool In [10]: obj[mask] Out[10]: 0 c 5 b 6 b 7 c 8 c dtype: object
有时,你可能希望得到DataFrame中多个相关列的一张柱状图。例如:
In [11]: data = pd.DataFrame({'Qu1': [1, 3, 4, 3, 4], 'Qu2': [2, 3, 1, 2, 3], 'Qu3': [1, 5, 2, 4, 4]}) In [12]: data Out[12]: Qu1 Qu2 Qu3 0 1 2 1 1 3 3 5 2 4 1 2 3 3 2 4 4 4 3 4 [5 rows x 3 columns]
将pandas.value_counts传给该DataFrame的apply函数,就会出现:
In [13]: result = data.apply(pd.value_counts).fillna(0) In [14]: result Out[14]: Qu1 Qu2 Qu3 1 1 1 1 2 0 2 1 3 2 2 0 4 2 0 2 5 0 0 1 [5 rows x 3 columns]
2、处理缺失值
缺失数据(missing data)在大部分数据分析应用中都很常见。pandas的设计目标之一就是让缺失数据的处理任务尽量轻松。例如,pandas对象上的所有描述统计都排除了缺失数据。
pandas使用浮点值NaN(Not a Number)表示浮点和非浮点数组中的缺失数据。它只是一个便于被检测出来的标记而已:
In [15]: string_data = pd.Series(['aardvark', 'artichoke', np.nan, 'avocado']) In [16]: string_data Out[16]: 0 aardvark 1 artichoke 2 NaN 3 avocado dtype: object In [17]: string_data.isnull() Out[17]: 0 False 1 False 2 True 3 False dtype: bool
Python内置的None值也会被当做NA处理:
In [18]: string_data[0] = None In [19]: string_data.isnull() Out[19]: 0 True 1 False 2 True 3 False dtype: bool
3、滤除缺失数据
过滤掉缺失数据的办法有很多种。纯手工操作永远都是一个办法,但dropna可能会更实用一些。对于一个Series,dropna返回一个仅含非空数据和索引值的Series:
In [20]: from numpy import nan as NA In [21]: data = pd.Series([1, NA, 3.5, NA, 7]) In [22]: data.dropna() Out[22]: 0 1.0 2 3.5 4 7.0 dtype: float64
当然,也可以通过布尔型索引达到这个目的:
In [23]: data[data.notnull()] Out[23]: 0 1.0 2 3.5 4 7.0 dtype: float64
而对于DataFrame对象,事情就有点复杂了。你可能希望丢弃全NA或含有NA的行或列。dropna默认丢弃任何含有缺失值的行:
In [24]: data = pd.DataFrame([[1., 6.5, 3.], [1., NA, NA], [NA, NA, NA], [NA, 6.5, 3.]]) In [25]: cleaned = data.dropna() In [26]: data Out[26]: 0 1 2 0 1 6.5 3 1 1 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3 [4 rows x 3 columns] In [27]: cleaned Out[27]: 0 1 2 0 1 6.5 3 [1 rows x 3 columns]
传入how='all'将只丢弃全为NA的那些行:
In [29]: data.dropna(how='all') Out[29]: 0 1 2 0 1 6.5 3 1 1 NaN NaN 3 NaN 6.5 3 [3 rows x 3 columns]
要用这种方式丢弃列,只需传入axis=1即可:
In [30]: data[4] = NA In [31]: data Out[31]: 0 1 2 4 0 1 6.5 3 NaN 1 1 NaN NaN NaN 2 NaN NaN NaN NaN 3 NaN 6.5 3 NaN [4 rows x 4 columns] In [32]: data.dropna(axis=1, how='all') Out[32]: 0 1 2 0 1 6.5 3 1 1 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3 [4 rows x 3 columns]
另一个滤除DataFrame行的问题涉及时间序列数据。假设你只想留下一部分观测数据,可以用thresh参数实现此目的:
In [33]: df = pd.DataFrame(np.random.randn(7, 3)) In [34]: df.ix[:4, 1] = NA; df.ix[:2, 2] = NA In [35]: df Out[35]: 0 1 2 0 1.259190 NaN NaN 1 0.181184 NaN NaN 2 -0.305915 NaN NaN 3 1.157043 NaN -0.305271 4 0.279270 NaN -2.275010 5 0.304463 -0.009671 -1.042706 6 -1.311779 0.158454 0.604686 [7 rows x 3 columns] In [36]: df.dropna(thresh=3) Out[36]: 0 1 2 5 0.304463 -0.009671 -1.042706 6 -1.311779 0.158454 0.604686 [2 rows x 3 columns]
4、填充缺失数据
你可能不想滤除缺失数据(有可能会丢弃跟它有关的其他数据),而是希望通过其他方式填充那些“空洞”。对于大多数情况而言,fillna方法是最主要的函数。通过一个常数调用fillna就会将缺失值替换为那个常数值:
In [37]: df.fillna(0) Out[37]: 0 1 2 0 1.259190 0.000000 0.000000 1 0.181184 0.000000 0.000000 2 -0.305915 0.000000 0.000000 3 1.157043 0.000000 -0.305271 4 0.279270 0.000000 -2.275010 5 0.304463 -0.009671 -1.042706 6 -1.311779 0.158454 0.604686 [7 rows x 3 columns]
若是通过一个字典调用fillna,就可以实现对不同的列填充不同的值:
In [38]: df.fillna({1: 0.5, 3: -1}) Out[38]: 0 1 2 0 1.259190 0.500000 NaN 1 0.181184 0.500000 NaN 2 -0.305915 0.500000 NaN 3 1.157043 0.500000 -0.305271 4 0.279270 0.500000 -2.275010 5 0.304463 -0.009671 -1.042706 6 -1.311779 0.158454 0.604686 [7 rows x 3 columns]
fillna默认会返回新对象,但也可以对现有对象进行就地修改:
In [39]: # 总是返回被填充对象的引用 In [40]: _ = df.fillna(0, inplace=True) In [41]: df Out[41]: 0 1 2 0 1.259190 0.000000 0.000000 1 0.181184 0.000000 0.000000 2 -0.305915 0.000000 0.000000 3 1.157043 0.000000 -0.305271 4 0.279270 0.000000 -2.275010 5 0.304463 -0.009671 -1.042706 6 -1.311779 0.158454 0.604686 [7 rows x 3 columns]
对reindex有效的那些插值方法也可用于fillna:
In [43]: df = pd.DataFrame(np.random.randn(6, 3)) In [44]: df.ix[2:, 1] = NA; df.ix[4:, 2] = NA In [45]: df Out[45]: 0 1 2 0 1.268895 -2.015949 -0.435110 1 -0.820148 -1.569651 1.176337 2 -1.488435 NaN 0.585900 3 1.118005 NaN -1.040445 4 -1.583587 NaN NaN 5 -1.457912 NaN NaN [6 rows x 3 columns] In [46]: df.fillna(method='ffill') Out[46]: 0 1 2 0 1.268895 -2.015949 -0.435110 1 -0.820148 -1.569651 1.176337 2 -1.488435 -1.569651 0.585900 3 1.118005 -1.569651 -1.040445 4 -1.583587 -1.569651 -1.040445 5 -1.457912 -1.569651 -1.040445 [6 rows x 3 columns] In [47]: df.fillna(method='ffill', limit=2) Out[47]: 0 1 2 0 1.268895 -2.015949 -0.435110 1 -0.820148 -1.569651 1.176337 2 -1.488435 -1.569651 0.585900 3 1.118005 -1.569651 -1.040445 4 -1.583587 NaN -1.040445 5 -1.457912 NaN -1.040445 [6 rows x 3 columns]
只要稍微动动脑子,你就可以利用fillna实现许多别的功能。例如,你可以传入Series的平均值或中位数:
In [48]: data = pd.Series([1., NA, 3.5, NA, 7]) In [49]: data.fillna(data.mean()) Out[49]: 0 1.000000 1 3.833333 2 3.500000 3 3.833333 4 7.000000 dtype: float64
5、层次化索引
层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理高维度数据。我们先来看一个简单的例子:创建一个Series,并用一个由列表或数组组成的列表作为索引。
In [50]: data = pd.Series(np.random.randn(10), index=[['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'd', 'd'], [1, 2, 3, 1, 2, 3, 1, 2, 2, 3]]) In [51]: data Out[51]: a 1 -2.224864 2 -0.234775 3 -1.143708 b 1 0.186533 2 0.251844 3 0.680129 c 1 -1.360122 2 0.906973 d 2 -0.727403 3 1.494060 dtype: float64
这就是带有MultiIndex索引的Series的格式化输出形式。索引之间的“间隔”表示“直接使用上面的标签”:
In [52]: data.index Out[52]: MultiIndex(levels=[[u'a', u'b', u'c', u'd'], [1, 2, 3]], labels=[[0, 0, 0, 1, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 1, 2, 0, 1, 1, 2]])
对于一个层次化索引的对象,选取数据子集的操作很简单:
In [53]: data['b'] Out[53]: 1 0.186533 2 0.251844 3 0.680129 dtype: float64 In [54]: data['b':'c'] Out[54]: b 1 0.186533 2 0.251844 3 0.680129 c 1 -1.360122 2 0.906973 dtype: float64 In [55]: data[['b', 'd']] Out[55]: b 1 0.186533 2 0.251844 3 0.680129 d 2 -0.727403 3 1.494060 dtype: float64
有时甚至还可以在“内层”中进行选取:
In [1]: import pandas as pd In [2]: import numpy as np In [3]: data = pd.Series(np.random.randn(10), index=[['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'd', 'd'], [1, 2, 3, 1, 2, 3, 1, 2, 2, 3]]) In [4]: data[:, 2] Out[4]: a 1.199212 b 1.098834 c -0.483223 d 0.933022 dtype: float64
层次化索引在数据重塑和基于分组的操作(如透视表生成)中扮演着重要的角色。比如,这段数据可以通过其unstack方法被重新安排到一个DataFrame中:
In [5]: data.unstack() Out[5]: 1 2 3 a 1.365918 1.199212 0.006834 b -1.329883 1.098834 -0.179380 c 0.756257 -0.483223 NaN d NaN 0.933022 -0.304902 [4 rows x 3 columns]
unstack的逆运算是stack:
In [6]: data.unstack().stack() Out[6]: a 1 1.365918 2 1.199212 3 0.006834 b 1 -1.329883 2 1.098834 3 -0.179380 c 1 0.756257 2 -0.483223 d 2 0.933022 3 -0.304902 dtype: float64
对于一个DataFrame,每条轴都可以有分层索引:
In [7]: frame = pd.DataFrame(np.arange(12).reshape((4, 3)), index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]], columns=[['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']]) In [8]: frame Out[8]: Ohio Colorado Green Red Green a 1 0 1 2 2 3 4 5 b 1 6 7 8 2 9 10 11 [4 rows x 3 columns]
各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们会显示在控制台输出中(不要将索引名称跟轴标签混为一谈):
In [9]: frame.index.names = ['key1', 'key2'] In [10]: frame.columns.names = ['state', 'color'] In [11]: frame Out[11]: state Ohio Colorado color Green Red Green key1 key2 a 1 0 1 2 2 3 4 5 b 1 6 7 8 2 9 10 11 [4 rows x 3 columns]
由于有了分部的列索引,因此可以轻松选取列分组:
In [12]: frame['Ohio'] Out[12]: color Green Red key1 key2 a 1 0 1 2 3 4 b 1 6 7 2 9 10 [4 rows x 2 columns]
可以单独创建MultiIndex然后复用。上面那个DataFrame中的(分级的)列可以这样创建:
In [15]: pd.MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']], names=['state', 'color']) Out[15]: MultiIndex(levels=[[u'Colorado', u'Ohio'], [u'Green', u'Red']], labels=[[1, 1, 0], [0, 1, 0]], names=[u'state', u'color'])