Series和DataFrame对象都支持NumPy的数组接口,因此可以直接使用NumPy提供的ufunc函数对它们进行运算。此外它们还提供各种运算方法,例如max()、min()、mean()、std()等。这些函数都有如下三个常用参数: ·axis:指定运算对应的轴。 ·level:指定运算对应的索引级别。 ·skipna:运算是否自动跳过NaN。 ![]() 下面分别计算每列的平均值、每行的平均值以及行索引的第1级别Contour中每个等高线对应的平均值: ![]() 除了支持加减乘除等运算符之外,Pandas还提供了add()、sub()、mul()、div()、mod()等与二元运算符对应的函数。这些函数可以通过axis、level和fill_value等参数控制其运算行为。在下面的例子中,对不同的等高线的Ca的值乘上不同的系数,fill_value参数为1表示对于不存在的值或NaN使用默认值1。因此结果中,所有Depression对应的值为原来的0.9倍,Slope对应的值为原来的1.2倍,而Top对应的值保持不变。 s = pd.Series(dict(Depression=0.9, Slope=1.2))df_soil.Ca.mul(s, level=1, fill_value=1)Depth Contour 0-10 Depression 9.6Slope 15Top 1310-30 Depression 6.8Slope 11Top 10dtype: float64 Pandas还提供了rolling_*()函数来对序列中相邻的N个元素进行移动窗口运算。例如可以使用rolling_median()实现中值滤波,使用rolling_mean()计算移动平均。图1显示了使用这两个函数对带脉冲噪声的正弦波进行处理的结果。它们的第二个参数为窗口包含的元素个数,而center参数为True表示移动窗口以当前元素为中心。 ![]() 图1 中值滤波和移动平均 由于rolling_median()采用了更高效的算法,因此当窗口很大时它的运算速度比SciPy章节中介绍过的signal.order_filter()更快。
expanding_*()函数对序列进行扩展窗口运算,例如expanding_max()返回到每个元素为止的历史最大值。图2显示了expanding_max()、expanding_mean()和expanding_min()的运算结果。 请读者思考如何使用NumPy提供的ufunc函数计算图2中的三条曲线。 np.random.seed(42)x = np.cumsum(np.random.randn(400))x_max = pd.expanding_max(x)x_min = pd.expanding_min(x)x_mean = pd.expanding_mean(x) ![]() 图2 用expanding_*计算历史最大值、平均值、最小值 字符串处理 Series对象提供了大量的字符串处理方法,由于数量众多,因此Pandas使用了一个类似名称空间的对象str来包装这些字符串相关的方法。例如下面的程序调用str.upper()将序列中的所有字母都转换为大写:
Python中包含两种字符串:字节字符串和Unicode字符串。通过str.decode()可以将字节字符串按照指定的编码解码为Unicode字符串。例如在UTF-8编码中,一个汉字占用三个字符,因此下面的s_utf8中的字符串长度分别为6、9、12。当调用str.decode()将其转换为Unicode字符串的序列之后,其各个元素的长度为实际的文字个数。str.encode()可以把Unicode字符串按照指定的编码转换为字节字符串,在常用的汉字编码GB2312中,一个汉字占用两个字节,因此s_gb2312的元素长度分别为4、6、8。 ![]() 无论Series对象包含哪种字符串对象,其dtype属性都是object,因此无法根据它判断字符串类型。在处理文本数据时,需要格外注意字符串的类型。 可以对str使用整数或切片下标,相当于对Series对象中的每个元素进行下标运算,例如: print s_unicode.str[:2]0 北京1 北京2 北京dtype: object 字符串序列与字符串一样,支持加法和乘法运算,例如:
也可以使用str.cat()连接两个字符串序列的对应元素: print s_unicode.str.cat(s_abc, sep='-')0 北京-a1 北京市-b2 北京地区-cdtype: object 调用astype()方法可以对Series对象中的所有元素进行类型转换,例如下面将整数序列转换为字符串序列:
str中的有些方法可以对元素类型为列表的Series对象进行处理,例如下面调用str.split()将s中的每个字符串使用字符'|'分隔,所得到的结果s_list的元素类型为列表。然后调用它的str.join()方法以逗号连接每个列表中的元素: ![]() 对字符串序列进行处理时,经常会得到元素类型为列表的序列。Pandas没有提供处理这种序列的方法,不过可以通过str[]获取其中的元素: s_list.str[1]0 bc1 xyzdtype: object 或者先将其转换为嵌套列表,然后再转换为DataFrame对象:
Pandas还提供了一些正则表达式相关的方法。例如使用其中的str.extract()可以从字符串序列中抽取出需要的部分,得到DataFrame对象。下面的例子中,df_extract1对应的正则表达式包含三个未命名的组,因此其结果包含三个自动命名的列。而df_extract2对应的正则表达式包含两个命名组,因此其列名为组名。 df_extract1 = s.str.extract(r'(\w+)\|(\w+)\|(\w+)')df_extract2 = s.str.extract(r'(?P<A>\w+)\|(?P<B>\w+)|')df_extract1 df_extract2------------- -----------0 1 2 A B 0 a bc de 0 a bc 1 x xyz yz 1 x xyz 在处理数据时,经常会遇到这种以特定分隔符分隔关键字的数据,例如下面的数据可以用于表示有向图,其第一列为边的起点、第二列为以'|'分隔的多个终点。下面使用read_csv()读入该数据,得到一个两列的DataFrame对象:
可以使用下面的程序将上述数据转换为每行对应一条边的数据。❶nodes是一个元素类型为列表的Series对象。❷调用NumPy数组的repeat()方法将第一列数据重复相应的次数。由于repeat()只能接受32位整数,而str.len()返回的是64位整数,因此还需要进行类型转换。❸将嵌套列表平坦化,转换为一维数组。 ![]() ![]() 还可以把原始数据的第二列看作第一列数据的标签,为了后续的数据分析,通常使用str.get_dummies()将这种数据转换为布尔DataFrame对象,每一列与一个标签对应,元素值为1表示对应的行包含对应的标签: ![]() 当字符串操作很难用向量化的字符串方法表示时,可以使用map()函数,将针对每个元素运算的函数运用到整个序列之上: df[1].map(lambda s:max(s.split('|')))0 D1 F2 A3 CName: 1, dtype: object 当用字符串序列表示分类信息时,其中会有大量相同的字符串,将其转换为分类(Category)序列可以节省内存、提高运算效率。例如在下面的df_soil对象中,Contour、Depth和Gp列都是表示分类的数据,因此有许多重复的字符串。
下面循环调用astype('category')将这三列转换为分类列: for col in ['Contour', 'Depth', 'Gp']:df_soil[col] = df_soil[col].astype('category')print df_soil.dtypesContour categoryDepth categoryGp categorypH float64dtype: object 与名称空间对象str类似,元素类型为category的Series对象提供了名称空间对象cat,其中保存了与分类序列相关的各种属性和方法。例如cat.categories是保存所有分类的Index对象:
而cat.codes则是保存下标的整数序列,元素类型为int8,因此一个元素用一个字节表示。 ![]() 分类数据有无序和有序两种,无序分类中的不同分类无法比较大小,例如性别;有序分类则可以比较大小,例如年龄段。上面创建的三个分类列为无序分类,可以通过cat.as_ordered()和cat.as_unordered()在这两种分类之间相互转换。下面的程序通过cat.as_ordred()将深度分类列转换为有序分类,注意最后一行分类名之间使用“<”连接,表示是有序分类。 depth = df_soil.Depthdepth.cat.as_ordered().head() ------------------------------------------------------0 0-10 1 0-10 2 0-10 3 0-10 4 10-30 dtype: category Categories (4, object): [0-10 < 10-30 < 30-60 < 60-90] 如果需要自定义分类中的顺序,可以使用cat.reorder_categories()指定分类的顺序:
|
|